最终的可执行文件是否使用符号表来检查变量范围

Does the final executable use symbol tables to check variable scope

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

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

如果一个变量只有文件作用域,例如使用 static 关键字,它将在符号 table 中被标记为局部的。

但是,当链接器生成最终的 executable 文件时,所有文件遇到的每个条目是否都有一个最终符号 table?

我很困惑,因为如果我们有一个声明为静态的变量,意味着只有一个文件内的文件范围,当每次在 executable 中遇到这个变量时,编译器是否必须引用最终符号table看看它的实际作用域,还是会为它生成特殊的代码?

先谢谢了。

这是不正确的:

When a translation unit is compiled / assembled into a single object file, i understand that it creates a symbol table of every variable / function found.

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

However, when the linker produces the final executable file, is there a final symbol table there with every single entry encountered for all files?

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

如果你定义了一个静态变量XYX,编译时名称会消失。

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

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

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

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

When a translation unit is compiled / assembled into a single object file, i understand that it creates a symbol table of every variable / function found.

那是大部分准确:本地(又名堆栈,又名自动存储持续时间)变量永远不会放入符号[=​​86=](使用古老的调试格式时除外,例如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没有出现在符号table
  2. a_function_static 在其名称后附加了 "random" 数字。这样 a_function_static 在不同的函数中不会发生冲突。
  3. a_staticstatic_fnLOCAL link年龄

另请注意,虽然 a_staticstatic_fn 出现在符号 table 中,但这样做 只是 以帮助调试。局部符号不被后续link使用,可以安全删除。

运行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

when the linker produces the final executable file, is there a final symbol table there with every single entry encountered for all files?

是的。像这样添加 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

... 我省略了 un-interesting 个条目(有很多)。

 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

I was confused because if we have a variable declared as static meaning only file scope within one file, when this variable is encountered every time in the executable, does the compiler have to reference the final symbol table to see its actual scope, or does it generate special code for it?

在 link 阶段,(通常)根本不调用编译器。 linker 不会(不需要)关注 LOCAL 符号。

一般来说,linker 只会做两件事:

  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    [=14=]x10,%rsp
  13:   b8 00 00 00 00          mov    [=14=]x0,%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)。