究竟什么是目标文件中的符号引用?

时间:2017-10-01 18:53:57

标签: linker ld elf

我正在从程序员那里阅读计算机系统'透视,关于链接的章节。它解释了如何使用程序ld在linux x86-64中进行链接。作者声称,为了从可重定位目标文件构建可执行文件,链接器执行两项操作:符号解析和重定位。这是他们对符号分辨率的简要概述:

  

目标文件定义和引用符号,其中每个符号对应于函数,全局变量或静态变量(即,使用静态属性声明的任何C变量)。符号解析的目的是将每个符号引用与一个符号定义相关联。

但即使他们开始深入描述符号分辨率,他们也不会澄清符号引用的含义。那么可重定位目标文件中引用符号的确切程度如何?

2 个答案:

答案 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

这里有几点需要注意:

  1. 请注意bar如何在foo地址拨打0objdump如何知道被调用的foo? 它真的可以在地址0吗? (大多数现代系统使用PROT_NONE映射虚拟内存的零页面,因此不会发生读取或写入访问。)
  2. 请注意来自baz的{​​{1}}来电与mainfoo的来电不同?编译器知道barfoo相对于调用指令本身的位置,但不知道bar将在哪里。
  3. 因此,鉴于以上信息,链接器如何将其变为合理的东西?它不能:这里没有足够的信息。

    为了使链接器能够将对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)(两个引用:foobar)。