15

pimpl习惯用法通常用于允许更改动态链接库中的代码,而不会破坏 ABI 兼容性并且不必重新编译依赖于库的所有代码。

我看到的大多数解释都提到添加新的私有成员变量会更改类中公共和私有成员的偏移量。这对我来说很有意义。我不明白的是,这实际上是如何破坏依赖库的。

我已经阅读了大量关于 ELF 文件以及动态链接实际上是如何工作的内容,但我仍然看不到更改共享库中的类大小会如何破坏事情。

例如,这是我编写的一个测试应用程序 (a.out),它使用Interface::some_method来自测试共享库 (libInterface.so) 的代码 ():

aguthrie@ana:~/pimpl$ objdump -d -j .text a.out 
08048874 <main>:
...
 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>

随后转到包含地址 0x804a01c 的全局偏移表 (GOT):

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 中时,类大小的任何更改都应该在运行时解决,不是吗?

我在这里想念什么?

4

1 回答 1

19

主要问题是,当您分配一个类的新实例时(在堆栈上或通过new),调用代码需要知道对象的大小。如果您稍后更改对象的大小(通过添加私有成员),这会增加所需的大小;但是您的来电者仍在使用旧尺寸。所以你最终没有分配足够的空间来保存对象,然后对象的构造函数继续破坏堆栈(或堆),因为它假设它有足够的空间。

此外,如果您有任何内联成员函数,它们的代码(包括成员变量的偏移量)可能会内联到调用代码中。如果在末尾以外的任何地方添加私有成员,这些偏移量将不正确,也会导致内存损坏(注意:即使添加到末尾,大小不匹配仍然是一个问题)。

于 2011-10-08T19:35:16.620 回答