我正在从程序员那里阅读计算机系统'透视,关于链接的章节。它解释了如何使用程序ld在linux x86-64中进行链接。作者声称,为了从可重定位目标文件构建可执行文件,链接器执行两项操作:符号解析和重定位。这是他们对符号分辨率的简要概述:
目标文件定义和引用符号,其中每个符号对应于函数,全局变量或静态变量(即,使用静态属性声明的任何C变量)。符号解析的目的是将每个符号引用与一个符号定义相关联。
但即使他们开始深入描述符号分辨率,他们也不会澄清符号引用的含义。那么可重定位目标文件中引用符号的确切程度如何?
答案 0 :(得分:4)
考虑以下来源:
static int foo() { return 42; }
static int bar() { return foo() + 1; }
extern int baz();
int main()
{
return foo() + bar() + baz();
}
在gcc -c foo.c
之后,x86_64 Linux上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
如何在foo
地址拨打0
?
objdump
如何知道被调用的foo
?
它真的可以在地址0吗? (大多数现代系统使用PROT_NONE
映射虚拟内存的零页面,因此不会发生读取或写入访问。)baz
的{{1}}来电与main
和foo
的来电不同?编译器知道bar
和foo
相对于调用指令本身的位置,但不知道bar
将在哪里。因此,鉴于以上信息,链接器如何将其变为合理的东西?它不能:这里没有足够的信息。
为了使链接器能够将对baz
(我们尚未看到)的引用链接到对baz
的调用,它需要其他信息。在ELF系统上,此附加信息将写入此处的特殊部分baz
,其中包含:
.rela.text
是本书所讨论的“参考”,但没有定义。它告诉linke:如果你能找到$ 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
的{{1}}部分的字节[45-48]。
如果没有这样的定义?链接器将产生错误:
.text
最后,到达上面的第1点:foo.o
真的可以在地址0吗?
不,但地址$ 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
。它表示“在呼叫后的下一条指令的地址处调用例程,减去25”。如果最终二进制文件中的调用指令最终位于地址CALL
,则该调用的目标将为0x14
,这是CALL 0
将结束的位置({{1}之间的距离当链接器将0x400501
的{{1}}部分重新定位到不同的地址时,0x4004ed
不会改变(尽管链接器放松;但这是另一天的复杂主题)。
答案 1 :(得分:1)
受雇俄语的答案很好,但也有一个简短的答案:符号引用是指您使用变量(或函数名称)的任何时候。符号定义创建变量(或函数名称)。
因此,符号定义为int bar;
(只要它是全局的)或int foo() { ... }
。然后,符号引用为foo(bar)
(两个引用:foo
和bar
)。