在 ELF 可执行文件中,这被称为“ELF 解释器”。在 linux 上(例如)这是 /lib64/ld-linux-x86-64.so.2
这不是内核的一部分,并且[通常]与glibc等一起使用。人。
当内核执行 ELF 可执行文件时,它必须将可执行文件映射到用户空间内存。然后它会在内部查找一个称为INTERP [其中包含一个作为完整路径的字符串] 的特殊子部分。
然后内核将解释器映射到用户空间内存并将控制权转移给它。然后,解释器进行必要的链接/加载并启动程序。
因为ELF 代表“可扩展链接器格式”,这允许ELF 文件包含许多不同的子部分。
与文件配对的 ELF 解释器知道,而不是让内核不得不知道所有无数的扩展。
虽然在给定系统上通常只使用一种格式,但系统上可能有几种不同的 ELF 文件变体,每个变体都有自己的 ELF 解释器。
这将允许 [比如说] BSD ELF 文件在 Linux 系统上运行 [带有其他调整/支持],因为 ELF 文件将指向 BSD ELF 解释器而不是 linux 解释器。
更新:
每个进程(vlc 播放器、chrome)都有共享库 ld.so 作为其地址空间的一部分。
是的。我假设您正在查看/proc/<pid>/maps。这些是文件的映射(例如,使用mmap)。这与“加载”有些不同,后者可以暗示 [symbol] 链接。
所以主要是加载器在将可执行文件(代码和数据)加载到内存后,它加载并映射动态链接器(.so)到它的地址空间
理解这一点的最好方法是改写你刚才所说的话:
所以主要是内核在映射可执行文件(代码和数据)到内存之后,内核映射动态链接器(.所以)到程序地址空间
这基本上是正确的。内核还映射其他东西,例如bss 段和堆栈。然后它将argc、argv 和envp [环境变量的空间]“推”到堆栈上。
然后,确定ld.so的起始地址[通过读取文件的特殊部分],将其设置为恢复地址并启动线程。
到现在为止,都是内核在做事。内核几乎没有符号链接。
现在,ld.so 接管...
进一步加载共享库,映射和解析对库的引用。然后调用入口函数(_start)
由于原始可执行文件(例如vlc)已映射到内存中,ld.so 可以检查它以获取所需的共享库列表。它将这些符号映射到内存中,但不一定必须立即链接这些符号。
映射简单快捷——只需拨打mmap。
可执行文件的起始地址 [not 与 ld.so 的起始地址相混淆,取自 ELF 可执行文件的特殊部分。尽管与此起始地址相关的符号传统上称为_start,但它实际上可以命名为任何名称(例如__my_start),因为它是决定起始地址的节数据中的内容,而不是 em> 符号地址_start
将符号引用链接到符号定义是一个耗时的过程。因此,这会延迟到实际使用该符号。也就是说,如果一个程序引用了printf,链接器实际上不会尝试链接printf,直到程序第一次真正调用 printf
这有时称为“按需链接”或“按需链接”。请在此处查看我的答案:Which segments are affected by a copy-on-write? 以获得更详细的解释以及将可执行文件映射到用户空间时实际发生的情况。
如果您有兴趣,可以通过ldd /usr/bin/vlc 获取它使用的共享库的列表。如果您查看readelf -a /usr/bin/vlc 的输出,您将看到这些相同的共享库。此外,您将获得 ELF 解释器的完整路径,并且可以执行 readelf -a <full_path_to_interpreter> 并注意一些差异。您可以对 vlc 想要的任何 .so 文件重复此过程。
将所有这些与/proc/<pid>maps 等结合起来。人。可能有助于您的理解。