一个目标文件中的符号引用具体是什么?

10
我正在从程序员的角度阅读有关链接的计算机系统章节,其中介绍了在Linux x86-64中使用ld程序进行链接的原理。作者指出,为了从可重定位目标文件中构建可执行文件,链接器需要完成两个任务:符号解析和重定位。以下是他们对符号解析的简要概述:
目标文件定义和引用符号,其中每个符号对应于一个函数、一个全局变量或一个静态变量(即,任何使用static属性声明的C变量)。符号解析的目的是将每个符号引用与准确的符号定义关联起来。
然而,当他们开始深入描述符号解析时,他们并没有澄清“符号引用”的含义。那么,在可重定位目标文件中,符号到底是如何被引用的呢?
2个回答

12

考虑以下来源:

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

以下需要注意几点:

  1. 注意bar如何在地址0调用fooobjdump如何知道正在调用的是foo? 它真的在地址0吗?(大多数现代系统将虚拟内存的零页与PROT_NONE映射,因此不能在那里进行读写访问。)
  2. 注意从mainbaz的调用与对foobar的调用不同吗?编译器知道foobar相对于调用指令本身的位置,但它不知道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部分重新定位到另一个地址时,fooCALL之间的距离不会改变(尽管链接器松弛除外,但这是另一个复杂的话题)。


所以,.rela.text表包含条目,大致上包含有关链接时需要修改哪些文本部分以及该位置的符号引用(这是对符号表的索引,这就是我理解的符号值的含义)的信息。这正确吗? - user4180854
大致正确。符号值为0(因为符号未定义),符号表索引编码在Info字段中。 - Employed Russian

4
俄语母语者的答案很好,但是有一个简短的答案:符号引用是指每次使用变量(或函数名)的情况。符号定义创建一个变量(或函数名)。
因此,符号定义将是 int bar;(只要它是全局的),或者 int foo() { ... }。符号引用将是 foo(bar)(两个引用:foobar)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接