我想知道,链接器如何确定 printf 被称为 @ 0xd1:如果我查看符号表中的地址_printf我发现它是0x0,因为这个函数还没有重新定位。但是链接器和 objdump 如何知道应该在地址 1e 处进行重新定位?Objdump 说 DISP32 _printf,但我在 objfile 中找不到一个条目,说地址 1e 应该重新定位。
objdump -d -r -t test.obj
输出:
test.obj: file format pe-i386
SYMBOL TABLE:
[ 0](sec -2)(fl 0x00)(ty 0)(scl 103) (nx 1) 0x00000000 test.c
File
[ 2](sec 1)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 _main
[ 3](sec 1)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .text
AUX scnlen 0x29 nreloc 3 nlnno 0
[ 5](sec 2)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .data
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 7](sec 3)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .bss
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 9](sec 4)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .rdata
AUX scnlen 0x3 nreloc 0 nlnno 0
[ 11](sec 5)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .eh_frame
AUX scnlen 0x38 nreloc 1 nlnno 0
[ 13](sec 0)(fl 0x00)(ty 20)(scl 2) (nx 1) 0x00000000 ___main
AUX tagndx 0 ttlsiz 0x0 lnnos 0 next 0
[ 15](sec 0)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 _printf
Disassembly of section .text:
00000000 <_main>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 e4 f0 and $0xfffffff0,%esp
6: 83 ec 10 sub $0x10,%esp
9: e8 00 00 00 00 call e <_main+0xe>
a: DISP32 ___main
e: c7 44 24 04 05 00 00 movl $0x5,0x4(%esp)
15: 00
16: c7 04 24 00 00 00 00 movl $0x0,(%esp)
19: dir32 .rdata
1d: e8 00 00 00 00 call 22 <_main+0x22>
1e: DISP32 _printf
22: b8 00 00 00 00 mov $0x0,%eax
27: c9 leave
28: c3 ret
29: 90 nop
2a: 90 nop
2b: 90 nop
在我研究了PE/COFF-格式并查看了OBJ代码之后,我找到了重新定位条目的表格:
0x160:14 00 19 00 00 00 09 00 00 00 06 00 1E 00 00 00 ................
0x170:0F 00 00 00 14 00 2D 00 00 00 09 00 00 00 06 00 ......-.........
@ 0x16C 是_printf的条目。0x1E是调用指令的地址部分的地址。链接器将重新定位的符号插入到此位置。这是一个 32 位字。在0x174,您可以找到重新定位的类型。类型 id 为 14,并表示链接器应将此部分替换为 printf 的相对地址:
IMAGE_REL_I386_REL32 0x0014 目标的 32 位相对位移。这支持 x86 相对分支和调用指令。
当我上次查看Windows中的动态链接时,系统调用被编译为对"thunk"的调用,"thunk"是一个执行远调用的2行子例程。 因此,所有对printf的调用都将调用相同的thunk。 符号表给出了 thunk 的位置,链接器只需替换 thunk 中的一个地址,即可在加载包含 printf 的库时将所有调用链接到正确的远地址。 我无法想象这已经发生了多大变化。