【问题标题】:Does the final executable use symbol tables to check variable scope最终的可执行文件是否使用符号表来检查变量范围
【发布时间】:2018-03-22 07:42:44
【问题描述】:

我正在尝试深入了解链接和加载阶段。

当翻译单元被编译/组装成单个目标文件时,我知道它会为找到的每个变量/函数创建一个符号表。

如果一个变量只有文件范围,例如使用static关键字,它将在符号表中被标记为本地。

但是,当链接器生成最终的可执行文件时,那里是否有一个最终符号表,其中包含所有文件遇到的每个条目?

我很困惑,因为如果我们将一个变量声明为静态意味着一个文件中的文件范围,那么当每次在可执行文件中遇到这个变量时,编译器是否必须引用最终符号表才能看到它的实际范围,还是它会为它生成特殊代码?

先谢谢了。

【问题讨论】:

  • 视情况而定:有些符号可以保留用于调试目的

标签: linker compiler-construction executable elf


【解决方案1】:

当翻译单元被编译/组装成单个目标文件时,我知道它会为找到的每个变量/函数创建一个符号表。

这是大部分准确的:本地(又名堆栈,又名自动存储持续时间)变量永远不会放入符号表中(除非使用古老的调试格式,例如STABS)。

你不需要相信我的话:这很容易观察:

$ cat foo.c
int a_common_global;
int a_global = 42;
static int a_static = 43;

static int static_fn()
{
  return 44;
}

int global_fn()
{
  int a_local = static_fn();
  static int a_function_static = 1;
  return a_local + a_static + a_function_static;
}

$ gcc -c foo.c
$ readelf -Ws foo.o

Symbol table '.symtab' contains 14 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS foo.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4
     5: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    3 a_static
     6: 0000000000000000    11 FUNC    LOCAL  DEFAULT    1 static_fn
     7: 0000000000000008     4 OBJECT  LOCAL  DEFAULT    3 a_function_static.1800
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    7
    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
    11: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM a_common_global
    12: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 a_global
    13: 000000000000000b    34 FUNC    GLOBAL DEFAULT    1 global_fn

这里有几点值得注意:

  1. a_local 没有出现在符号表中
  2. a_function_static 在其名称后附加了“随机”数字。这样a_function_static 在不同的函数中不会发生冲突。
  3. a_staticstatic_fnLOCAL 链接

另请注意,虽然a_staticstatic_fn 出现在符号表中,但这样做是为了帮助调试。本地符号不被后续链接使用,可以安全删除。

运行后strip --strip-unneeded foo.o:

$ readelf -Ws foo.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    4
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7
     7: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM a_common_global
     8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 a_global
     9: 000000000000000b    34 FUNC    GLOBAL DEFAULT    1 global_fn

当链接器生成最终的可执行文件时,是否有一个最终符号表,其中包含所有文件遇到的每个条目?

是的。像这样添加main.c

$ cat main.c
extern int global_fn();

extern int a_global;
int a_common_global = 23;
int main()
{
  return global_fn() + a_common_global + a_global;
}

$ gcc -c main.c foo.c
$ gcc main.o foo.o
$ readelf -Ws a.out

Symbol table '.symtab' contains 69 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name

...我省略了不感兴趣的条目(有很多)。

 0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND

34: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
35: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS foo.c
36: 0000000000201030     4 OBJECT  LOCAL  DEFAULT   23 a_static
37: 000000000000061c    11 FUNC    LOCAL  DEFAULT   13 static_fn
38: 0000000000201034     4 OBJECT  LOCAL  DEFAULT   23 a_function_static.1800

50: 0000000000000627    34 FUNC    GLOBAL DEFAULT   13 global_fn

63: 00000000000005fa    34 FUNC    GLOBAL DEFAULT   13 main
64: 000000000020102c     4 OBJECT  GLOBAL DEFAULT   23 a_global

我很困惑,因为如果我们将一个变量声明为静态意味着一个文件中的文件范围,那么当每次在可执行文件中遇到这个变量时,编译器是否必须引用最终符号表才能看到它的实际范围,还是它会为它生成特殊代码?

在链接阶段,编译器(通常)根本不被调用。并且链接器不会(不需要)关注LOCAL 符号。

一般来说,链接器只做两件事:

  1. 将未定义的引用(例如从main.o 引用global_fna_global)解析为它们的定义(在foo.o 中)和
  2. 应用重定位。

foo.o 中为a_statica_function_static 应用重定位实际上并不需要它们的名字;只有它们在.data 部分中的偏移量,因为这个输出应该清楚:

$ objdump -dr foo.o
foo.o:     file format elf64-x86-64   
Disassembly of section .text:
...
000000000000000b <global_fn>:
   b:   55                      push   %rbp
   c:   48 89 e5                mov    %rsp,%rbp
   f:   48 83 ec 10             sub    $0x10,%rsp
  13:   b8 00 00 00 00          mov    $0x0,%eax
  18:   e8 e3 ff ff ff          callq  0 <static_fn>
  1d:   89 45 fc                mov    %eax,-0x4(%rbp)
  20:   8b 15 00 00 00 00       mov    0x0(%rip),%edx        # 26 <global_fn+0x1b>
            22: R_X86_64_PC32   .data
  26:   8b 45 fc                mov    -0x4(%rbp),%eax
  29:   01 c2                   add    %eax,%edx
  2b:   8b 05 00 00 00 00       mov    0x0(%rip),%eax        # 31 <global_fn+0x26>
            2d: R_X86_64_PC32   .data+0x4
  31:   01 d0                   add    %edx,%eax
  33:   c9                      leaveq
  34:   c3                      retq

注意偏移量0x220x2d 的重定位如何不说明名称(分别为a_statica_function_static.1800)。

【讨论】:

  • 好的,谢谢
  • 符号表中的值到底是什么,为什么.data在.text的反汇编部分? 22 日和 2 日到底发生了什么?
【解决方案2】:

这是不正确的:

当翻译单元被编译/组装成单个目标文件时,我知道它会为找到的每个变量/函数创建一个符号表。

目标文件将仅包含有关编译单元引用和定义的全局符号的信息。

但是,当链接器生成最终的可执行文件时,是否有一个最终符号表,其中包含所有文件遇到的每个条目?

可执行文件将包含通用符号(那些需要在可加载库中定义的符号)。可加载库仅包含通用符号,但它可以定义这些符号并引用它们。

如果您定义一个静态变量 XYX,则该名称会在您编译时消失。

如果您定义了一个全局函数(未在可加载库中导出),则该名称会在您链接时消失。

我在这里做了一点过度简化的是,编译器和链接器支持可选包含调试信息,这些信息可以描述处理过程中遇到的所有符号。

有关符号的调试信息必须包含有关定义该符号的模块的信息。

调试信息通常在对象和可执行文件中与运行或链接这些文件所需的信息完全分开。事实上,调试信息通常可以很容易地从这些文件中剥离出来。

【讨论】:

  • “目标文件将只包含有关全局符号的信息”——这不正确(至少在大多数 ELF 平台和大多数编译器上)。
  • 我一直在阅读,目标文件将包含一个符号表,其中包含找到的所有符号,本地和全局
  • 如果包含 IF 调试信息。对于实际链接,它不需要局部变量。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-08
  • 1970-01-01
相关资源
最近更新 更多