符号表

什么是符号表?

在编译的四个阶段中,最后一步链接的本质就是将不同的目标文件糅合到一块,生成最终可执行的二进制文件。而目标文件的互相糅合,实质上就是目标文件之间对地址的引用,就是对函数和变量的地址的引用。那怎么来完成这个过程呢?人们就想到了在每一个目标文件中存放一张记录了目标文件中所用到的所有符号以及其对应的符号值的表,链接的时候,目标文件之间就会互相寻找自己需要的符号来完成链接的过程,而这张表,就叫做符号表。

符号表长什么样

上面提到,一个程序,从源码到生成可执行文件有四个阶段:预编译,编译,汇编,链接。在汇编阶段,即生成目标文件的时候,就会产生符号表。每一个生成的elf文件都有符号表。符号表中的符号和源码中的变量名和函数名是一一对应的(对应的意思不代表生成的符号和源码中的函数变量名一定是一样的,而只是一种映射关系,典型的是在c++编译时,生成的符号和源码的符号是完全不一样的)。如下,我有一段测试代码:

//test.c

static int a;
int b;
static void test1(){
    return;
}

void test2(){
    return;
}

生成目标文件之后,用read -s test.o命令即可查看其中的符号表,如下:

haley@ubuntu:~/Desktop$ readelf -s test.o

Symbol table '.symtab' contains 12 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     5: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    3 a
     6: 0000000000000000     7 FUNC    LOCAL  DEFAULT    1 test1
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
    10: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM b
    11: 0000000000000007     7 FUNC    GLOBAL DEFAULT    1 test2

其中,value叫符号值,对于变量和函数而言,符号值就是他们的地址。size是一个符号值所占字节数,type是符号的类型,像变量的类型就是OBJECT,函数的类型就是FUNC。Bind列是符号的作用域,LOCAL表示是局部符号,GLOBAL表示是全局符号,WEAK表示是弱符号等等。Vis列表示符号的可见性,一般比较少用到。Ndx列表示符号所属的段的index(.text段,.data段等等)。Name列就是刚才说的与变量函数名一一对应的符号名了。在上面的例子中,b和test2是global的,而a和test1是local的。

来一个实际的例子说明一下:

//main.c

void test2();
int main(){ 
    test2();
    return 0;
}

//test.c
static int a;
int b ;
static void test1(){
    return;
}

void test2(){
    return;
}

上面有两个c源文件,然后我分别生成对应的目标文件main.o和test.o,导出他们的符号表如下:

//main.o的符号表

Symbol table '.symtab' contains 11 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     8: 0000000000000000    21 FUNC    GLOBAL DEFAULT    1 main
     9: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND test2 //注意这里,Ndx是UND的,且value为0


// test.o的符号表

Symbol table '.symtab' contains 12 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     5: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    3 a
     6: 0000000000000000     7 FUNC    LOCAL  DEFAULT    1 test1
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
    10: 0000000000000000     4 OBJECT  WEAK   DEFAULT    3 b
    11: 0000000000000007     7 FUNC    GLOBAL DEFAULT    1 test2

上面main.c引用了test.c的test2函数,但是在链接之前,main.o并不知道test2函数的定义和地址,所以main.o的符号表里将test2标记为UND(undefine的意思),地址值也缺省为0000000000000000,等待链接的时候寻找到定义了test2函数的目标文件的符号表,提取出test2函数的地址值。

下面来验证一下,用gcc test.o main.o将它们链接到一块,生成a.out可执行文件,导出a.out的符号表:

//a.out的符号表

Symbol table '.dynsym' contains 6 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
     5: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)

Symbol table '.symtab' contains 66 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
    ......
    31: 00000000000005f0     0 FUNC    LOCAL  DEFAULT   13 frame_dummy
    32: 0000000000200df0     0 OBJECT  LOCAL  DEFAULT   18 __frame_dummy_init_array_
    33: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test.c
    34: 0000000000201018     4 OBJECT  LOCAL  DEFAULT   23 a
    35: 00000000000005fa     7 FUNC    LOCAL  DEFAULT   13 test1
    36: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
    37: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    38: 0000000000000834     0 OBJECT  LOCAL  DEFAULT   17 __FRAME_END__
    49: 0000000000201010     0 NOTYPE  GLOBAL DEFAULT   22 _edata
    50: 0000000000000694     0 FUNC    GLOBAL DEFAULT   14 _fini
    51: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
    52: 0000000000201000     0 NOTYPE  GLOBAL DEFAULT   22 __data_start
    53: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    54: 0000000000201008     0 OBJECT  GLOBAL HIDDEN    22 __dso_handle
    55: 00000000000006a0     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    56: 0000000000000620   101 FUNC    GLOBAL DEFAULT   13 __libc_csu_init
    57: 0000000000201020     0 NOTYPE  GLOBAL DEFAULT   23 _end
    58: 00000000000004f0    43 FUNC    GLOBAL DEFAULT   13 _start
    59: 0000000000201010     0 NOTYPE  GLOBAL DEFAULT   23 __bss_start
    60: 0000000000000608    21 FUNC    GLOBAL DEFAULT   13 main
    61: 0000000000000601     7 FUNC    GLOBAL DEFAULT   13 test2 //重点关注
    62: 0000000000201010     0 OBJECT  GLOBAL HIDDEN    22 __TMC_END__

这里看到有两个符号表,一个是.dynsym,一个是.symtab。这里简单介绍一下,.dynsym是动态符号表,是动态链接的时候用到的,而.symtab是静态符号表,静态链接时用到的。这里我们只看.symtab。这里看到Num为61的行,可以发现test2已经不是UND了,而且地址值也有了是0000000000000601。说明链接是成功的!

这里插个题外话,在大型的项目中,其实链接完之后,因为已经链接完成了(已经完成了重定位操作),可执行文件中的.symtab,即静态符号表其实已经变得可有可无了。所以可以使用strip命令将静态符号表等无用得信息删掉,减小二进制文件的大小。但是.dynsym仍需保留,因为动态链接是在装载文件的时候完成的,符号信息不能删除。还有就是千万不要自作多情把strip命令也用在目标文件,因为目标文件是还没链接的,过早删除符号表会导致链接不了。

Strip

为什么需要用strip去符号?strip去符号去了什么?

符号表的主要作用在于链接时按着符号寻址,但在程序运行时就没有作用了,因此这个表即使去掉后也不会影响程序的运行。但是如果是动态链接函数,需要在运行时进行动态链接.

所以ELF文件里有两张符号表,一张叫符号表(.symtab),另一张叫动态符号表(.dynsym)。当去符号的时候,只用去掉符号表,而保留动态符号表

【参考文献】

results matching ""

    No results matching ""