静态链接和动态链接,PLT 的需求是什么?

Static and Dynamic Linking, What's the need for PLT?

我正在阅读这篇精彩的文章:https://www.technovelty.org/linux/plt-and-got-the-key-to-code-sharing-and-dynamic-libraries.html 关于动态和静态链接。

阅读完毕后,仍有 2 个问题没有回答或不够清楚,我无法理解。

1)

This is not fine for a shared library (.so). The whole point of a shared library is that applications pick-and-choose random permutations of libraries to achieve what they want. If your shared library is built to only work when loaded at one particular address everything may be fine — until another library comes along that was built also using that address. The problem is actually somewhat tractable — you can just enumerate every single shared library on the system and assign them all unique address ranges, ensuring that whatever combinations of library are loaded they never overlap. This is essentially what prelinking does (although that is a hint, rather than a fixed, required address base). Apart from being a maintenance nightmare, with 32-bit systems you rapidly start to run out of address-space if you try to give every possible library a unique location. Thus when you examine a shared library, they do not specify a particular base address to be loaded at

那么动态链接是如何解决这个问题的呢?一方面 write 提到我们不能使用相同的地址,另一方面他说使用多个地址会导致空闲内存不足。我看到一个矛盾听到(注意:我知道什么是虚拟地址)。

2)

This handles data, but what about function calls? The indirection used here is called a procedure linkage table or PLT. Code does not call an external function directly, but only via a PLT stub. Let's examine this:

我没明白,为什么数据处理与功能不同?像我们以前对普通变量所做的那样,将函数的地址保存在 GOT 中有什么问题?

On the one hand the write mentions we can't use same address and on the other hand he says using multiple addresses will cause lack of free memory.

在 Linux 大约 15-20 年前,在转向 ELF 之前,所有共享库都必须进行全局协调。这是一场维护噩梦,因为一个系统可以有数百个共享库。您 运行 超出了地址 space 为每个库分配唯一地址 的范围,即使其中一些库从未一起加载(但地址 [=55 的分配者=] range 事先不知道哪些库永远不会一起加载,因此可以加载到同一范围内)。

动态加载器通过在加载时将库放入任意地址范围来解决这个问题,重新定位它们,以便它们在刚刚加载的地址正确执行。

这里的好处是你不需要提前分割你的地址space。

why the handling of data is different that functions?

这是不同的,因为当您访问数据时,链接器不参与。第一次访问必须有效,并且必须在库可用之前重新定位数据。没有可以挂钩的函数调用来进行惰性动态链接。

但是对于函数调用,链接器可以参与。该程序调用 PLT“存根”函数 foo@plt。在 first 调用该存根时,它执行工作以 resolve 指向 actual foo() 定义,并且 保存 该指针。在随后的调用中,foo@plt 只是使用已经保存的指针直接跳转到 foo().

的定义

这称为惰性重定位,如果程序永远不会访问它具有调用点的许多库函数,它可以节省 很多 的工作。 (例如,计算数学表达式并可以调用任何 libm.so.6 函数的程序,但对于普通的简单输入或使用 --help,仅调用一对。)

您可以通过 运行 在有和没有 LD_BIND_NOW 环境变量(禁用延迟重定位)的情况下使用包含大量共享库的大型程序来观察延迟重定位的效果。

或者 gcc -fno-plt (https://gcc.gnu.org/ml/gcc-patches/2015-05/msg00225.html),GCC 将通过 GOT 内联调用,这意味着库函数在一次调用而不是两次调用中到达。 (一些 x86-64 Linux 发行版为其二进制包启用了此功能。)这需要早期绑定,但会略微降低每次调用的成本,因此对 long-运行ning 程序有好处。 (PLT + 早期绑定是两者中最糟糕的,除了在解析所有内容时具有缓存局部性。)