为什么链接器会在 .rela.plt 中生成看似无用的重定位?

Why does the linker generate seemingly useless relocations in .rela.plt?

首先,我正在玩的玩具程序:

prog.c:

int func1();

int main(int argc, char const *argv[])
{
    func1();
    return 0;
}

lib.c:

int func1()
{
    return 0;
}

构建:

gcc -O3 -g -shared -fpic ./lib.c -o liba.so
gcc prog.c -g -la -L. -o prog -Wl,-rpath=$PWD

为了完整性:

$ gcc --version
gcc (GCC) 6.3.1 

$ ld --version
GNU ld version 2.26.1

现在,我的问题。我已经确认了我所读到的关于动态符号的惰性绑定是如何工作的,即 func1 的 GOT 条目最初指向 PLT,指向跳转后的指令:

$ gdb prog    

(gdb) disassemble main
Dump of assembler code for function main:
   0x0000000000400666 <+0>: push   rbp
   0x0000000000400667 <+1>: mov    rbp,rsp
   0x000000000040066a <+4>: sub    rsp,0x10
   0x000000000040066e <+8>: mov    DWORD PTR [rbp-0x4],edi
   0x0000000000400671 <+11>:    mov    QWORD PTR [rbp-0x10],rsi
   0x0000000000400675 <+15>:    mov    eax,0x0
   0x000000000040067a <+20>:    call   0x400560 <func1@plt>    <<< call to shared lib via PLT
   0x000000000040067f <+25>:    mov    eax,0x0
   0x0000000000400684 <+30>:    leave  
   0x0000000000400685 <+31>:    ret    
End of assembler dump.
(gdb) disassemble 0x400560
Dump of assembler code for function func1@plt:
   0x0000000000400560 <+0>: jmp    QWORD PTR [rip+0x200ab2]        # 0x601018 <<< JMP to address stored in GOT
   0x0000000000400566 <+6>: push   0x0             <<< ... which initially points right back here
   0x000000000040056b <+11>:    jmp    0x400550
End of assembler dump. 
(gdb) x/g 0x601018
0x601018:   0x400566   <<< GOT point back right after the just-executed jump

这很好。现在,检查 0x601018 处的 .got.plt 部分,其中包含指向 0x400566 的指针,表明二进制文件本身保存了地址,而动态 linker 在加载时间。这是有道理的,因为这个地址在 link 时间是已知的:

$ readelf -x .got.plt ./prog 

Hex dump of section '.got.plt':
 NOTE: This section has relocations against it, but these have NOT been applied to this dump.
  0x00601000 ???????? ???????? ???????? ???????? ..`.............
  0x00601010 ???????? ???????? 66054000 ???????? ........f.@.....

(其中 66054000 是 little-endian 代表地址 0x400566 最初存储在 GOT 中)

很好,很好。但是后来...

$ readelf -r prog                             
<...>
Relocation section '.rela.plt' at offset 0x518 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000601018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 func1 + 0

这个GOT地址为什么会有重定位入口?我们已经看到正确的地址已经存在于二进制文件中,在 link 时间放置在那里。 我也实验性地编辑了这个重定位使其无效,程序运行良好。所以 reloc 似乎没有任何贡献。它在那里做什么,更一般地说,rela.plt 中的重定位实际上很重要的场景是什么?

更新#1:

需要说明的是,这是关于PIC码和64位码​​的。以下是相关的部分地址,以帮助阐明地址所属的位置:

$ readelf -S ./prog
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 9] .rela.dyn         RELA             00000000004004e8  000004e8
       0000000000000030  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400518  00000518
       0000000000000018  0000000000000018  AI       5    23     8
  [12] .plt              PROGBITS         0000000000400550  00000550
       0000000000000020  0000000000000010  AX       0     0     16
  [21] .dynamic          DYNAMIC          0000000000600e00  00000e00
       00000000000001f0  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000600ff0  00000ff0
       0000000000000010  0000000000000008  WA       0     0     8
  [23] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000020  0000000000000008  WA       0     0     8

更新#2:

.rela.plt 编辑部分 header 不会更改过程映像,因此我没有禁用 reloc。 我也曾尝试更改 reloc 地址(更改为另一个可写地址),这似乎也没有什么不同,但结果是解析器在延迟绑定期间没有使用该地址,尽管 reloc 的其余部分是。地址本身仅在使用 LD_BIND_NOW.

关闭惰性绑定时使用

感谢@yugr

This is fine. Now, examining the .got.plt section at 0x601018, which contains this pointer back to 0x400566 shows that the binary itself holds the address, without the dynamic linker doing anything at load--time.

不是真的。请注意,0x400566 处的代码最终跳转到 0x400550,即 PLT 存根 prior 的 16 个字节。 0x400550 处的代码会将 GOT 的地址压入堆栈并调用动态链接器。有关详细信息,请查看 this presentation(幻灯片 14)。

Which makes sense, since this address is known at link time

是吗?该地址将来自共享库,由于 ASLR 的原因,共享库将在启动时以随机地址加载,因此静态链接器无法知道该地址...

Why is there a relocation entry for this GOT address?

当 PLT 存根调用动态链接器时(在第一次调用时),它将 GOT 条目的地址传递给它。动态链接器将搜索 .rela.plt 以找出如何重新定位 GOT 条目(即函数名称和偏移量)。