为什么链接器会在 .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 条目(即函数名称和偏移量)。
首先,我正在玩的玩具程序:
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 条目(即函数名称和偏移量)。