Как добавление частной переменной-члена нарушает совместимость C ++ ABI?

Идиома pimpl обычно используется для того, чтобы разрешить изменение кода в динамически подключаемых библиотеках без нарушения совместимости с ABI и необходимости перекомпилировать весь код, который зависит от библиотеки.

В большинстве объяснений я вижу упоминание о том, что добавление новой частной переменной-члена изменяет смещения публичных и частных членов в классе. Это имеет смысл для меня. Я не понимаю, как на практике это действительно нарушает работу зависимых библиотек.

Я много читал о файлах ELF и о том, как на самом деле работает динамическое связывание, но я до сих пор не понимаю, как изменение размера класса в общей библиотеке может что-то сломать.

Например. Вот тестовое приложение (a.out), которое я написал, которое использует код ( Interface :: some_method ) из тестовой разделяемой библиотеки (libInterface.so):

aguthrie@ana:~/pimpl$ objdump -d -j .text a.out 
08048874 
: ... 8048891: e8 b2 fe ff ff call 8048748 <_ZN9Interface11some_methodEv@plt>

Вызов some_method ] использует таблицу процедурных связей (PLT):

aguthrie@ana:~/pimpl$ objdump -d -j .plt a.out 

08048748 <_ZN9Interface11some_methodEv@plt>:
 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>

, которая впоследствии переходит в глобальную таблицу смещения (GOT), где содержится адрес 0x804a01c:

aguthrie@ana:~/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                            ....

И затем здесь динамический компоновщик творит чудеса и просматривает все символы содержится в общих библиотеках в LD_LIBRARY_PATH, находит Interface :: some_method в libInterface.so и загружает свой код в GOT, поэтому при последующих вызовах some_method код в GOT фактически является сегментом кода из общей библиотеки.

Или что-то в этом роде.

Но, учитывая вышесказанное, я до сих пор не понимаю, как здесь играют роль размер класса разделяемой библиотеки или смещения его методов. Насколько я могу судить, приведенные выше шаги не зависят от размера класса. Похоже, что в a.out включен только символ имя метода в библиотеке. Любые изменения в размере класса должны разрешаться только во время выполнения, когда компоновщик загружает код в GOT, не так ли?

Что мне здесь не хватает?

13
задан linuxbuild 10 October 2011 в 18:55
поделиться