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

Posted

技术标签:

【中文标题】动态链接如何知道在哪里可以找到链接的文件?【英文标题】:How dynamic-linking know where to find the linked files? 【发布时间】:2020-12-07 09:43:52 【问题描述】:

我从一本书中读到了有关动态链接的事实:

例如在执行这个命令时:

gcc main.o lib.so.

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

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

main.o在执行main.out时只尝试链接文件,然后转而询问ld-linux.so链接什么文件。


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

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

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

所以肯定有一些误解。

【问题讨论】:

链接产生一个可执行文件。从您的示例中不清楚您的可执行文件是什么。 main.o 是一个目标文件,很可能成为可执行文件的一部分。因为您不命名可执行文件,所以答案很难谈论可执行文件。 【参考方案1】:

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 和man 3 dlsym)。例如,您可以随时动态加载和卸载新的 ELF 库。这通常用于插件和插件类型的功能。

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

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

最后,Linux 动态链接器实际上是 C 库的一部分,而不是 Linux 内核。更多详情,请阅读glibc runtime dynamic linker sources。

【讨论】:

我对动态链接的理解是否正确:执行gcc main.o lib.so 时,编译器会在最终的main.out 文件中创建一个.dynamic 部分,其中包含链接文件的名称(在这个案例是lib.so) 和一个包含ld-linux.so.interp 文件。而在执行main.out时,它要求ld-linux.so找到包含在main.out.dynamic部分中的.so文件? @ratsafalig:比这更复杂一点,此外,链接是由 binutils 的 ld 完成的(gcc 在内部调用所有必要的参数)。我将尝试修改我的回答,描述实际发生的情况。【参考方案2】:

相反,ld-linux.so 将有关 lib.so 的信息复制到 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) 和environ(7)。

请注意GNU binutils 和GCC(以及Linux 的kernel)都是free software - 您可以下载他们的源代码并进行改进。我建议研究他们的源代码。

或者至少使用strace(1) 或ltrace(1) 或gdb(1) 或pmap(1)(另请参阅proc(5))来了解您的gcc 进程正在做什么以及syscalls(2) 所涉及的内容。在您的可执行文件上也使用ldd(1) 和readelf(1) 和objdump(1)。另见elf(5)。

另请参阅this draft 报告和CHARIOT 和DECODER 项目以及Linux From Scratch 网站和Advanced Linux Programming、Drepper 的论文How to write shared libraries 和Linux Program Library HowTO。

【讨论】:

我对动态链接的理解是否正确:执行 gcc main.o lib.so 时,编译器会在最终的 main.out 文件中创建一个 .dynamic 部分,其中包含链接文件的名称(在这种情况下是 lib.so)和一个包含 ld-linux.so 的 .interp 文件。而在执行main.out的时候,它要求ld-linux.so去查找main.out的.dynamic部分中包含的.so文件? 我添加了更多参考资料。花几天时间阅读它们

以上是关于动态链接如何知道在哪里可以找到链接的文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何删除/删除 Firebase 中的动态链接? [关闭]

归档的动态链接在哪里?

如何编辑 Firebase DynamicLinks 404(未找到动态链接)页面?

我们可以静态链接动态 C 库吗?

QT应用程序(动态编译)如何查看链接了哪些.dll文件?

如何识别IDA反汇编中动态链接库中的函数