考虑以下来源:
static int foo() { return 42; }
static int bar() { return foo() + 1; }
extern int baz();
int main()
{
return foo() + bar() + baz();
}
在 x86_64 Linux 上使用 gcc -c foo.c
后,objdump -d foo.o
的输出如下:
foo.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <foo>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: b8 2a 00 00 00 mov $0x2a,%eax
9: 5d pop %rbp
a: c3 retq
000000000000000b <bar>:
b: 55 push %rbp
c: 48 89 e5 mov %rsp,%rbp
f: b8 00 00 00 00 mov $0x0,%eax
14: e8 e7 ff ff ff callq 0 <foo>
19: 83 c0 01 add $0x1,%eax
1c: 5d pop %rbp
1d: c3 retq
000000000000001e <main>:
1e: 55 push %rbp
1f: 48 89 e5 mov %rsp,%rbp
22: 53 push %rbx
23: 48 83 ec 08 sub $0x8,%rsp
27: b8 00 00 00 00 mov $0x0,%eax
2c: e8 cf ff ff ff callq 0 <foo>
31: 89 c3 mov %eax,%ebx
33: b8 00 00 00 00 mov $0x0,%eax
38: e8 ce ff ff ff callq b <bar>
3d: 01 c3 add %eax,%ebx
3f: b8 00 00 00 00 mov $0x0,%eax
44: e8 00 00 00 00 callq 49 <main+0x2b>
49: 01 d8 add %ebx,%eax
4b: 48 83 c4 08 add $0x8,%rsp
4f: 5b pop %rbx
50: 5d pop %rbp
51: c3 retq
以下需要注意几点:
- 注意
bar
如何在地址0
调用foo
?
objdump
如何知道正在调用的是foo
?
它真的在地址0吗?(大多数现代系统将虚拟内存的零页与PROT_NONE
映射,因此不能在那里进行读写访问。)
- 注意从
main
到baz
的调用与对foo
和bar
的调用不同吗?编译器知道foo
和bar
相对于调用指令本身的位置,但它不知道baz
将位于哪里。
因此,根据上述信息,链接器如何将其转换为有意义的内容?它不能:这里没有足够的信息。
为了使链接器能够将对baz
的引用链接到对baz
的调用中(我们还没有看到),它需要其他信息。在ELF系统上,该附加信息写入此处的一个特殊部分.rela.text
,其中包含:
$ readelf -Wr foo.o
Relocation section '.rela.text' at offset 0x5d0 contains 1 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000000045 0000000b00000002 R_X86_64_PC32 0000000000000000 baz - 4
那个就是书中提到但没有定义的“参考”。它告诉链接器:如果你可以找到baz
(在其他对象中)的定义,请取其地址,并将其(实际上是&baz - 4
因为 CALL
指令相对于CALL
指令后面的下一条指令)放入代码段的字节[45-48]中,即foo.o
文件的.text
节。
如果没有这样的定义?链接器将会产生一个错误:
$ gcc foo.o
foo.o: In function `main':
foo.c:(.text+0x45): undefined reference to `baz'
collect2: error: ld returned 1 exit status
最后,针对上面的第1点:是否可以将foo
放在地址0处?
不可以,但是位于地址0x14
处的CALL
指令并没有实际上说CALL 0
。它说的是“调用紧随该指令之后的下一条指令地址减去25的例程”。如果最终二进制文件中的该调用指令位于地址0x400501
,那么该调用的目标将是0x4004ed
,这就是foo
所在的位置(当链接器将foo.o
的.text
部分重新定位到另一个地址时,foo
与CALL
之间的距离不会改变(尽管链接器松弛除外,但这是另一个复杂的话题)。
0
(因为符号未定义),符号表索引编码在Info
字段中。 - Employed Russian