计算机系统:程序员的视角 (3ed)p733 上说
7.9 加载可执行目标文件
要运行可执行目标文件 prog,我们可以在 Linux shell 的命令行中键入其名称:
Linux> ./prog
由于
prog
不对应于内置 shell 命令,因此 shell 假定它prog
是一个可执行目标文件,它通过调用一些驻留在内存中的操作系统代码(称为装载机。任何Linux程序都可以通过调用该函数来调用加载器execve
,我们将在8.4.6节中详细介绍
在 p736 中:在动态链接期间
7.10 与共享库的动态链接
创建库后,我们将其链接到图 7.7 中的示例程序:
linux> gcc -o prog2l main2.c ./libvector.so
这将创建一个可执行目标文件,其形式可以在运行时
prog2l
链接。libvector.so
基本思想是在创建可执行文件时静态地进行一些链接,然后在加载程序时动态地完成链接过程。重要的是要认识到libvector.so 中的任何代码或数据部分实际上都没有复制到可执行文件中prog2l
在此刻。反而,链接器复制一些重定位和符号表信息libvector.so
这将允许在加载时解析对代码和数据的引用。当加载器加载并运行可执行文件时,它使用第 7.9 节中讨论的技术
prog2l
加载部分链接的可执行文件。prog2l
接下来,它注意到prog2l
包含一个.interp
部分,其中包含动态链接器的路径名,动态链接器本身就是一个共享对象(例如,ld-linux.so
在Linux系统上)。加载程序加载并运行动态链接器,而不是像通常那样将控制权传递给应用程序。 动态链接器然后完成链接任务通过执行以下重定位:
- 将文本和数据重新定位
libc.so
到某个内存段- 将文本和数据重定位
libvector.so
到另一个内存段- 将任何引用重新定位到由和
prog2l
定义的符号libc.so
libvector.so
上面的动态链接情况就是“静态加载,动态链接”的情况史蒂芬·基特的回复:
静态加载、动态链接:链接器又是 /usr/bin/ld,但带有共享库 (.so);装载机是二进制文件的解释器,例如 64 位 x86 上的 Debian 9 上的 /lib64/ld-linux-x86-64.so.2 (当前映射到 /lib/x86_64-linux-gnu/ld-2.24.so)由内核加载,内核还加载主可执行文件;
不同之处在于,CSAPP似乎说加载器是(后面的内核代码),execve()
而链接器是ld-linux.so
(在编译时没有发生链接ld
,而实际链接在加载时发生ld-linux.so
)。
动态链接中什么是链接器,什么是加载器?
谢谢。
答案1
这里重点强调一下:
动态链接器然后完成链接任务
错过了重要的词“完成”。链接器ld
开始链接任务,在构建时执行尽可能多的操作并准备完成它所需的数据结构。
然后,加载程序可以在加载程序和所需的库时完成链接任务:匹配符号并执行必要的重定位。
在CSAPP的术语中,动态加载器是内核中的ELF加载器,动态链接器是ld-linux.so
.
GNU C 库自己的文档ld.so
称为动态链接器/加载器。ld.so
(或ld-linux.so
) 本身执行相当多的加载以及链接,因此将这两个术语应用于它是准确的 - 内核加载可执行文件本身及其解释器(动态链接器/加载器),并且解释器加载所有其他所需的图书馆。
看程序如何运行:ELF 二进制文件了解这一切如何在 Linux 系统上工作的详细信息。
答案2
动态链接器是 call ld.so
。您可以在 下找到配置/etc/ld.so*
。大多数配置与搜索 .so 的路径有关。
ld
必须确保在完成可执行文件的链接之前所有函数都存在于共享库中(嗯......从技术上讲,不需要这样做,但确实如此 - 也就是说,如果您从从一台计算机到另一台计算机,.so 可能不同,并且具有新功能,但丢失了旧功能,并且二进制文件将无法运行)。
当链接器 ( ) 创建需要共享库的二进制文件时,它会在您的节ld
中保留一定数量的符号和相应的地址。.text
这就是动态链接器 ( ld.so
) 在运行时用来完成链接的内容。它将搜索相应 .so 文件中的符号,并在代码中所需的任何位置保存它们的地址(对于函数,它通常是一个jump
指令表,这样它们只能链接每个函数一次)。
当然,当你剥离二进制文件时,那些特殊符号不要被删除。
要查看将加载的库的列表,您可以ldd
针对可执行文件运行。特别是,它会向您显示选择哪个 .so 来解析符号(完整路径)。您可以使用环境变量更改搜索路径LD_LIBRARY_PATH
。这允许您针对不同或已卸载的 .so 文件进行测试(cmake、automake 也使用它们--rpath
,这是相同的,只是路径直接保存在二进制文件中)。
我不太确定哪一部分准确加载文件并启动它们。可能execve()
没有直接实现所有这些逻辑。但它肯定接近知道如何运行可执行文件的最低级别函数。
所以实际上,动态链接是非常简单与标准链接器相比的过程:
- 加载.so
- 在 .so 中搜索符号
- 保存符号地址
完毕。这就是为什么它这么快。
笔记:使用 可以实现进一步的活力dlopen()
,但听起来您并不是在谈论那部分。