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

时间:2018-03-22 07:42:44

标签: linker compiler-construction executable elf

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

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

如果变量仅使用static关键字具有文件范围,则它将在符号表中标记为local。

但是,当链接器生成最终的可执行文件时,是否有最终的符号表,并且所有文件都遇到了每个条目?

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

非常感谢。

2 个答案:

答案 0 :(得分: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关联
  4. 另请注意,虽然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
    
      

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

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

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

    1. 将未定义的引用(例如global_fna_global的引用)解析为其定义(此处为main.o)和
    2. 申请重新安置。
    3. foo.o中为a_statica_function_static应用重定位并不需要他们的名字;只有他们在foo.o部分内的偏移,因为这个输出应该清楚:

      .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 0x22的重定位如何不对名称(0x2da_static分别说明)。

答案 1 :(得分:0)

这是不正确的:

  

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

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

  

但是,当链接器生成最终的可执行文件时,是否有最终的符号表,并且所有文件都遇到了每个条目?

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

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

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

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

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

调试信息通常在对象和可执行文件中与运行或链接这些文件所需的信息完全分开。实际上,调试信息通常可以从这些文件中轻松删除。