为什么链接器会在.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条目(即函数名称和偏移量)。