首先,我正在玩的玩具程序:
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
的指针,表明二进制文件本身保存地址,而动态链接器在加载时不执行任何操作。这是有道理的,因为这个地址在链接时是已知的:
$ 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 rep。)
很好,很好。但是......然后......
$ 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是。仅当使用LD_BIND_NOW
关闭延迟绑定时才使用地址本身。
谢谢@yugr
答案 0 :(得分:2)
这很好。现在,检查0x601018处的.got.plt部分, 其中包含此指针返回0x400566显示 二进制本身保存地址,没有动态链接器 在装载时做任何事情。
不是真的。请注意,0x400566处的代码最终跳转到0x400550,即之前的>到PLT存根。 0x400550处的代码将在堆栈上推送GOT的地址并调用动态链接器。有关详细信息,请查看this presentation(幻灯片14)。
是吗?地址将来自共享库,由于ASLR,它将在启动时以随机地址加载,因此静态链接器无法知道地址......这是有道理的,因为这个地址在链接时已知
为什么有这个GOT地址的重定位条目?
当PLT存根调用动态链接器(第一次调用时)时,它会传递GOT条目的地址。动态链接器将搜索.rela.plt以了解如何重定位GOT条目(即函数名称和偏移量)。