就加载器而言,节无关紧要——它们被忽略了。加载器只查看段,并且可执行文件的每个可加载段都加载到指定的地址。然后加载器将触发动态链接器(如果在可执行文件中调用)来处理共享对象。通常,符号表和字符串表不在可加载段中,因此加载器会忽略它们。
所以依次回答你的问题:
1) 加载程序忽略 .init 和 .fini 部分。它们通常是一些可加载段的一部分,可执行文件中的初始代码将运行 .init 部分中的代码。动态链接器将加载共享对象的段,并调用每个入口点,该入口点将类似地调用某个加载段中的 .init 代码
2) 字符串/符号表只对链接有意义,对加载没有意义。所以动态链接器将查看它们以解决任何重定位并构建跳转表
3)重定位主要用于(静态)链接——可执行文件不应该有它们,它们在共享对象中应该很少见(它们通常是与位置无关的,所以不需要)。一些动态链接器根本无法处理重定位(不确定普通的 linux 动态链接器),因此它们无法加载仍然具有重定位的共享对象
4) .hash 部分只是加速符号查找的优化——而不是通过符号表对特定符号进行线性搜索,.hash 部分将直接带您到它。如果您愿意,您可以放心地忽略它们并慢慢进行符号查找。
编辑
对 ELF 加载程序的作用进行了简短的有点模糊的描述:
读取ELF文件的程序头
将文件的所有 LOAD 段加载到内存中。
如果程序头中有一个 INTERP 条目,则递归加载该二进制文件
调用程序的入口点。
差不多就是这样(设置堆栈有一些额外的麻烦,但这显然不是加载程序的一部分,而不是加载程序运行之前的进程设置的一部分)。
对于静态链接的可执行文件,没有 INTERP 条目,所以就差不多了。对于动态链接的可执行文件,INTERP 部分类似于“/lib/ld-linux.so.2”(字符串),因此对加载程序的递归调用将读取该二进制文件,加载所有 LOAD 部分,注意没有 INTERP 部分(因此没有进一步的递归调用),调用入口点,然后返回(此时基本可执行文件的加载程序将调用基本可执行文件的入口点)。
现在动态链接器是加载的第二个可执行文件 (/lib/ld-linux.so.2)。它所做的是去阅读原始二进制文件的 .dynamic 部分。这将告诉它要加载的共享对象列表和表格(.plt 部分 - 程序加载表)以填充这些共享对象中特定符号的地址。因此它将加载这些共享对象,查找其中的符号,并将它们的地址粘贴到该表中。每个共享对象都有自己的 .dynamic 部分,动态链接器将递归处理这些部分。符号查找会查看到目前为止加载的所有对象中的所有符号,因此主程序中的符号可能会“覆盖”其他共享对象中的符号,并将它们的地址卡在共享对象的 .plt 中。在加载每个共享对象及其所有依赖项之后,共享对象的入口点被调用。如果两个共享对象相互依赖(这是合法的),两者都将被加载并解析它们的 .plts,然后调用两个入口点,但不是以任何特别定义的顺序。
请注意,在上述所有内容中,重定位从未出现过。当无法在共享对象中指定的(虚拟)地址加载共享对象时,可能会发生重定位(因为该地址已经加载了其他内容)。发生这种情况时,需要重新定位共享对象以加载到不同的地址,这涉及查看对象中的所有重定位条目以查找需要修补以处理重定位地址的内容。
最后,符号引用在包含引用的对象的符号表中只有一个偏移量——链接器需要在已加载的所有其他对象的符号表中查找符号名称(字符串)以找出什么它指的是。每个对象都有自己的符号表,这些表除了逻辑上没有组合。符号查找遍历到目前为止已加载的所有对象,依次在每个符号表中查找该符号,查找定义该符号的条目。