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

Linker 最终可执行文件是否使用符号表检查变量范围,linker,compiler-construction,executable,elf,Linker,Compiler Construction,Executable,Elf,我试图深入理解链接和加载阶段 当一个翻译单元被编译/组装成一个单一的对象文件时,我知道它会为找到的每个变量/函数创建一个符号表 如果某个变量仅具有文件作用域(例如使用static关键字),它将在符号表中标记为local 但是,当链接器生成最终的可执行文件时,是否存在最终的符号表,其中包含所有文件遇到的每个条目 我很困惑,因为如果我们在一个文件中有一个声明为静态的变量,这意味着只有文件作用域,那么每次在可执行文件中遇到这个变量时,编译器是否必须引用最终的符号表来查看它的实际作用域,还是为它生成特殊

我试图深入理解链接和加载阶段

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

如果某个变量仅具有文件作用域(例如使用static关键字),它将在符号表中标记为local

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

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

谢谢。这是不正确的:

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


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

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

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

如果定义静态变量XYX,则编译时该名称将消失

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

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

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

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

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

这是最准确的:本地(aka stack,aka automatic storage duration)变量永远不会放入符号表中(除非使用古老的调试格式,例如)

你不必相信我的话:这是一个微不足道的观察:

$ 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
这里有几点值得注意:

  • a_local
    不出现在符号表中
  • a_函数\u static
    在其名称后面附加了“随机”数。这使得不同函数中的函数静态不会发生冲突
  • a_static
    static_fn
    具有
    LOCAL
    链接
  • 另请注意,当符号表中出现
    a_static
    static_fn
    时,这样做只是为了帮助调试。本地符号不被后续链接使用,可以安全删除

    运行
    strip之后--剥离不需要的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
    
    。。。我省略了不感兴趣的条目(有很多)

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

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

    通常,链接器只做两件事:

  • 将未定义的引用(例如从
    main.o
    global\u fn
    a\u global
    的引用)解析为它们的定义(在
    foo.o
    中)
  • 应用重新定位
  • foo.o
    中的
    a_static
    a_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
    
    $objdump-dr foo.o
    o:文件格式elf64-x86-64
    第节的分解。正文:
    ...
    000000000000000b:
    b:55%rbp
    c:48 89 e5 mov%rsp,%rbp
    f:48 83欧共体10分$0x10,%rsp
    13:b8 00 mov$0x0,%eax
    18:e8 e3 ff ff ff callq 0
    1d:89 45 fc移动%eax,-0x4(%rbp)
    20:8b 15 00 mov 0x0(%rip),%edx#26
    22:R_X86_64_PC32.数据
    26:8b 45 fc mov-0x4(%rbp),%eax
    29:01 c2添加%eax,%edx
    2b:8b 05 00 mov 0x0(%rip),%eax#31
    2d:R_X86_64_PC32。数据+0x4
    31:01 d0添加%edx,%eax
    33:c9-Q
    34:c3 retq
    

    请注意,偏移量
    0x22
    0x2d
    处的重定位如何不涉及名称(
    a_static
    a_function_static.1800
    分别)。

    这取决于:某些符号可以保留用于调试目的“对象文件将仅包含有关全局符号的信息”——这也是不正确的(至少在大多数ELF平台和大多数编译器上)我一直在读到目标文件将包含一个al的符号表
    $ 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