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