为什么链接器会在.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 : push rbp 0x0000000000400667 : mov rbp,rsp 0x000000000040066a : sub rsp,0x10 0x000000000040066e : mov DWORD PTR [rbp-0x4],edi 0x0000000000400671 : mov QWORD PTR [rbp-0x10],rsi 0x0000000000400675 : mov eax,0x0 0x000000000040067a : call 0x400560  <<< call to shared lib via PLT 0x000000000040067f : mov eax,0x0 0x0000000000400684 : leave 0x0000000000400685 : ret End of assembler dump. (gdb) disassemble 0x400560 Dump of assembler code for function func1@plt: 0x0000000000400560 : jmp QWORD PTR [rip+0x200ab2] # 0x601018 <<< JMP to address stored in GOT 0x0000000000400566 : push 0x0 <<< ... which initially points right back here 0x000000000040056b : 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表明二进制本身保存地址,而动态链接器在加载时不执行任何操作。 这是有道理的,因为这个地址在链接时是已知的:

 $ 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是最初存储在GOT中的地址0x400566的little-endian代表)

很好,很好。 但是之后…

 $ 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地址的重定位条目? 我们已经看到正确的地址已经存在,在二进制文件中,在链接时放置在那里。 我还试验性地编辑了这个重定位,使其无效,程序运行正常 。 所以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的节头不会更改过程映像,因此我没有禁用reloc。 我也试过更改reloc地址(到另一个可写地址),但似乎也没有什么区别,但事实certificate,在延迟绑定期间解析器不使用该地址,尽管其余的reloc是。 仅当使用LD_BIND_NOW关闭延迟绑定时才使用地址本身。

谢谢@yugr

这可以。 现在,检查0x601018处的.got.plt部分,其中包含此指针返回0x400566,表明二进制本身保存地址,而动态链接器在加载时不执行任何操作。

并不是的。 请注意,0x400566处的代码最终跳转到0x400550,即PLT存根之前的 16个字节。 0x400550处的代码将在堆栈上推送GOT的地址并调用动态链接器。 有关详细信息,请查看此演示文稿 (幻灯片14)。

这是有道理的,因为这个地址在链接时是已知的

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

为什么有这个GOT地址的重定位条目?

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