首先,我正在玩的玩具程序:
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
现在,我的问题。我已经确认了我所读到的有关动态符号的惰性绑定如何工作的内容,即
最初的 GOT 条目为func1
直接返回 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
这可以。现在,检查 .got.plt 部分0x601018
,其中包含此指针返回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.@.....
(where 66054000
是小端代表。为地址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
Why is there a relocation entry for this GOT address? we've seen that the correct address is already there, in the binary, placed there at link time. I've also experimentally edited this relocation to make it ineffectual, and the program runs fine. So the reloc seems to contribute nothing. What is it doing there, and more generally what are the scenarios in which the relocations in rela.plt
actually do matter?
更新#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