首页 > 解决方案 > 将 PLT 和 GOT 表用于共享库的主要原因是什么?

问题描述

我正在阅读 Ian Lance Taylor 关于链接器的文章:http: //inai.de/documents/Linkers.pdf

在第 9 页讨论共享对象时,他提到由于共享库可以加载到无法预测的虚拟地址的进程中,因此一旦知道地址,动态链接器将需要处理大量重定位。这会减慢加载速度。为了避免动态链接器完成大量重定位,程序链接器将函数引用更改为对 PLT 表的 PC 相关调用,并将全局/静态变量引用转换为对 GOT 表的引用。然后动态链接器只需要在加载时重定位 PLT/GOT 中的条目,而不是处理整个二进制文件中的重定位。

然而,这种对加载时间优化的关注让我感到困惑,因为这里似乎有一个更明显的问题,加速加载是无关紧要的。共享对象的全部意义在于,加载到物理内存中的单个共享对象现在可以映射到每个需要它的进程的虚拟地址空间。这可以通过更改一些页表来快速完成,并避免从磁盘加载库的新副本。

因此,如果动态链接器在共享库的主体中进行了任何重定位,这些更改将出现在也映射了该共享库的每个其他进程中,并且如果它出现在不同的虚拟地址上,它们将破坏该库。

正是出于这个原因,我们才有了 GOT 和 PLT。程序链接器将所有引用修改为对 GOT 和 PLT 的与位置无关的引用。然后动态链接器为每个进程唯一地重新定位 GOT 和 PLT 中的条目。共享库的主要内容在各进程间共享,但GOT和PLT对各进程是唯一的,不共享。

这种对 PLT 和 GOT 的理解正确吗?我根据我的理解推断了这里的一些机制,但我没有看到它可以工作的任何其他方式。

标签: compilationlinkershared-libraries

解决方案


您似乎缺少或不理解写时复制(CoW) 页面的概念。

两个进程可以mmap将磁盘上的同一个文件放入它们不同的虚拟地址中,并且操作系统可以为两个映射使用单个 RAM 物理页面(即,进程共享一个物理内存页面)。但是一旦一个进程更改了内存,就会为该进程创建一个副本,并且更改不会出现在另一个进程中(物理内存页面不再共享)。

因此,如果动态链接器在共享库的主体中进行了任何重定位,这些更改将出现在也映射了该共享库的每个其他进程中,

如果内存是 CoW,则不会。

正是因为这个原因,我们才有了 GOT 和 PLT

不,原因是优化(必须复制更少的页面)而不是您的(错误)理解所暗示的正确性。


推荐阅读