动态链接如何知道在哪里可以找到链接文件?

How dynamic-linking know where to find the linked files?

我从某本书中读到关于动态的事实-linking:

例如执行这条命令时:

gcc main.o lib.so.

main.o 不会复制 lib.so 的任何信息。

相反,ld-linux.so 将关于 lib.so 的信息复制到 ld-linux.so

main.o在执行main.out时只尝试linking文件,然后它会询问ld-linux.so什么文件到link.


我的问题很简单:ld-linux.so 究竟如何复制关于 lib.so 的信息?

它不能简单地复制信息说:alright, the main.out is linking to lib.so,可以吗?

如果是的话,那么ld-linux.so本身很快就会变得巨大。

所以肯定有人对此有误解。

Instead, the ld-linux.so copy infomations about lib.so into ld-linux.so.

这是错误的,至少在 Debian/Buster 或 Ubuntu 20.04 for x86-64 上是这样。

因为文件 /usr/lib/ld-linux.so.2 归 root 所有,随机用户(运行 gcc main.o lib.so)不可写。参见 credentials(7) and environ(7)

请注意,GNU binutils and GCC (and also the Linux kernel) are free software - 您可以下载它们的源代码并对其进行改进。我建议研究他们的源代码。

或者至少使用 strace(1) or ltrace(1) or gdb(1) or pmap(1) (see also proc(5)) to understand what your gcc process is doing and what syscalls(2) are involved. Use also ldd(1) and readelf(1) and objdump(1) on your executable. See also elf(5).

另见 this draft report and the CHARIOT & DECODER projects and the Linux From Scratch website and Advanced Linux Programming, Drepper's paper How to write shared libraries, and the Linux Program Library HowTO

man 8 ld-linux.so 手册页中对此进行了详细描述(链接到正确的上游,Linux 手册页项目)。

简而言之,简化了一点(忽略预加载的库、ELF DT_RPATH/DT_RUNPATH,以及二进制文件本身中需要那些动态库的各种选项):

ld-linux.so 在 LD_LIBRARY_PATH 环境变量中指定的目录中查找库(如果已定义)。

如果未定义或未在此处找到,ld-linux.so 检查 /etc/ld.so.cache 文件:二进制缓存,由 ldconfig 管理命令更新(自动 运行 由您的包管理器在必要时提供),包含(大多数)已知动态库的路径。

如果在那里找不到,ld-linux.so 检查是否在标准库目录中找到该库。


Linux 对二进制文件和动态库使用 ELF file format。这是一种非常结构化的格式。

每当你执行一个新的 ELF 二进制文件时,在最后,在 Linux 它归结为 execveexecveat 系统调用(或 exec_with_loader 系统调用一些架构)。

Linux 内核打开文件,检查适当的权限,并将 ELF 文件的相关部分映射到内存中。 (有一个模块,binfmt_misc,用于扩展内核将执行的文件类型。除了 ELF 文件,内核在文件的最开头识别 #! 以指示脚本,随后通过将改为执行的脚本解释器的路径。)

如果 ELF 文件是静态链接的,内核会让用户空间在 ELF 文件的起始点继续执行。 (请注意,这不是标准 C 库 main();标准 C 库实际上链接正确的初始化和退出代码。)

如果 ELF 文件是动态链接的,它有一个 DT_INTERP 程序头指定动态链接器的绝对路径。 (请注意,可以有多个;通常一个用于 64 位二进制文​​件,一个用于 32 位二进制文​​件。)内核会将其映射到内存中,然后将执行移交给它。

动态链接器将在进程的整个生命周期内保留在内存中。它提供了通过包含 公开的有用功能(特别参见 man 3 dl_iterate_phdr and man 3 dlsym)。例如,您可以随时动态加载和卸载新的 ELF 库。这通常用于插件和插件类型的功能。

动态链接器不仅在内存中查找和映射所有动态加载的库,处理它们的重定位记录和符号表,它还在将执行移交给原始二进制文件的起点之前做一些非常有用的事情。例如,Linux 动态链接器和静态链接器都提供了一种在加载所有动态库之后但在执行 main() 之前执行函数的方法(通过简单地标记函数 __attribute__((constructor)); 类似地用于在调用 main() returns 或 exit() 后执行函数(但如果进程由于信号而终止,或使用 _exit()/_Exit(),则通过标记这些函数 __attribute__((destructor))

请注意,我上面说的是“地图”而不是“加载”。这是因为Linux内核将数据从存储映射到内存,而不是传统意义上的“加载”。由于页面缓存,这也意味着无论您有多少个特定程序或库的副本 运行ning,实际上只有一个副本驻留在 RAM 中(除非您执行某些奇怪的恶作剧)。

最后,Linux动态链接器实际上是C库的一部分,而不是Linux内核。有关详细信息,请阅读 glibc runtime dynamic linker sources.