ELF64 加载程序如何知道更新 .got.plt 中的初始地址?

How does the ELF64 loader know to update the initial addresses in .got.plt?

考虑以下程序hello.c

#include <stdio.h>

int main(int argc, char** argv)
{
    printf("hello");
    return 0;
}

文件用gcc -o hello -Og -g hello.c编译,然后用gdb hello加载。

检查 GOT 以使用 p 'printf@got.plt' 调用 printf 得到

 = (<text from jump slot in .got.plt, no debug info>) 0x1036 <printf@plt+6>

这是相应PLT条目中第二条指令相对于节开头的偏移量。

启动程序并将其与 starti 链接后,p 'printf@got.plt' 现在给出

 = (<text from jump slot in .got.plt, no debug info>) 0x555555555036 <printf@plt+6>

对应PLT表项中第二条指令的绝对地址

我明白发生了什么事以及为什么。我的问题是动态linker/loader如何知道将节​​偏移量(0x1036)更新为绝对地址(0x555555555036)?

A p &'printf@got.plt' 链接前给出

 = (<text from jump slot in .got.plt, no debug info> *) 0x4018 <printf@got.plt>

readelf -r simple 显示此地址的重定位条目

Relocation section '.rela.plt' at offset 0x550 contains 1 entry:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000004018  000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0

但我对 System V Application Binary Interface AMD64 Architecture Processor Supplement, p.76 的阅读是这些重定位条目仅在 LD_BIND_NOW 为非空时使用。还有其他我错过的搬迁条目吗?相对于 GOT 的最终地址重新设置偏移量的机制是什么?

根据 Drepper 的 How To Write Shared Libraries,动态链接器重定位两种依赖项:

  1. 相对重定位:对同一对象内位置的依赖。链接器只是将对象的加载地址添加到目标目的地的偏移量。
  2. 符号重定位:基于复杂符号解析算法的更复杂和昂贵的过程。

对于 PLT 的 GOT,Drepper 指出(§1.5.5)在启动时,动态链接器使用指向相应 PLT 条目的第二条指令的地址填充 GOT 槽。 阅读 glibc 源代码表明链接器确实循环遍历 R_X86_64_JUMP_SLOT 重定位 (elf/do-rel.h:elf_dynamic_do_Rel) 并增加它们包含的偏移量 (sysdeps/x86_64/dl-machine.h:elf_machine_lazy_rel):

if (__glibc_likely (r_type == R_X86_64_JUMP_SLOT))
  {
    /* Prelink has been deprecated.  */
    if (__glibc_likely (map->l_mach.plt == 0))
      *reloc_addr += l_addr;
    else
      ...

当使用惰性 PLT 绑定时(默认情况)。