2011-10-08 14 views
11

El pimpl idiom se usa comúnmente para permitir el cambio de código en bibliotecas vinculadas dinámicamente sin romper la compatibilidad ABI y tener que volver a compilar todo el código que depende de la biblioteca.¿Cómo la adición de una variable de miembro privado rompe la compatibilidad de C++ ABI?

La mayoría de los explanations Veo mencionar que agregar una nueva variable de miembro privado cambia las compensaciones de los miembros públicos y privados de la clase. Eso tiene sentido para mí. Lo que no entiendo es cómo en la práctica esto realmente rompe las bibliotecas dependientes.

He leído mucho sobre archivos ELF y cómo funcionan realmente los enlaces dinámicos, pero todavía no veo cómo cambiar el tamaño de clase en la biblioteca compartida podría romper las cosas.

E.g. Aquí es una aplicación de prueba (a.out) escribí que utiliza código (Interface::some_method) a partir de una biblioteca compartida de prueba (libInterface.so):

[email protected]:~/pimpl$ objdump -d -j .text a.out 
08048874 <main>: 
... 
8048891: e8 b2 fe ff ff   call 8048748 <[email protected]> 

La llamada a some_method utiliza la tabla de vinculación procesal (PLT):

[email protected]:~/pimpl$ objdump -d -j .plt a.out 

08048748 <[email protected]>: 
8048748: ff 25 1c a0 04 08  jmp *0x804a01c 
804874e: 68 38 00 00 00   push $0x38 
8048753: e9 70 ff ff ff   jmp 80486c8 <_init+0x30> 

que posteriormente va a la Tabla Global Offset (GOT) donde está contenida la dirección 0x804a01c:

[email protected]:~/pimpl$ readelf -x 24 a.out 

Hex dump of section '.got.plt': 
    0x08049ff4 089f0408 00000000 00000000 de860408 ................ 
    0x0804a004 ee860408 fe860408 0e870408 1e870408 ................ 
    0x0804a014 2e870408 3e870408 4e870408 5e870408 ....>...N...^... 
    0x0804a024 6e870408 7e870408 8e870408 9e870408 n...~........... 
    0x0804a034 ae870408       .... 

Y entonces aquí es donde el enlazador dinámico wo muestra su magia y examina todos los símbolos contenidos en las bibliotecas compartidas en LD_LIBRARY_PATH, encuentra Interface::some_method en libInterface.so y carga su código en GOT, por lo que en llamadas posteriores al some_method, el código en GOT es en realidad el segmento de código del archivo compartido biblioteca.

O algo así.

Pero dado lo anterior, todavía no entiendo cómo el tamaño de clase de lib compartido o sus desplazamientos de método entran en juego aquí. Por lo que puedo decir, los pasos anteriores son independientes del tamaño de la clase. Parece que solo el símbolo del método en la biblioteca está incluido en a.out. Cualquier cambio en el tamaño de clase debería resolverse en tiempo de ejecución cuando el vinculador carga el código en el GOT, ¿no?

¿Qué me falta aquí?

Respuesta

15

El problema principal es que, cuando asigna una nueva instancia de una clase (ya sea en la pila, o por medio de new), el código de llamada necesita conocer el tamaño del objeto. Si más adelante cambia el tamaño del objeto (agregando un miembro privado), esto aumenta el tamaño necesario; Sin embargo, las personas que llaman siguen usando el tamaño antiguo. Así que terminas no asignando espacio suficiente para mantener el objeto, y el constructor del objeto luego corrompe la pila (o montón), porque asume que tiene suficiente espacio.

Además, si tiene alguna función de miembro en línea, su código (incluidas las compensaciones a las variables de miembro) puede insertarse en el código de llamada. Si agrega miembros privados en cualquier lugar que no sea el final, estos desplazamientos serán incorrectos, lo que provocará daños en la memoria (nota: incluso si agrega al final, la discrepancia de tamaño sigue siendo un problema).

+0

Aha! Lo tengo. De hecho, mirando un poco más en el desmontaje antes de que se llame al ctor de Interface, puedo ver que asigna el espacio (en este caso 4bytes) para el objeto. – adg

Cuestiones relacionadas