一、查看 .symtab 信息

源文件代码

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

unsigned long a = 100;
static int b = 100;
int c;

int main(void)
{
        time(&a);
        printf("hello world\n");
        return 0;
}

查看可从定位目标文件 .symtab 信息

$ gcc -c helloworld.c -o helloworld.o
$ readelf -s helloworld.o

Symbol table '.symtab' contains 15 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS helloworld.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000008     4 OBJECT  LOCAL  DEFAULT    3 b
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
    10: 0000000000000000     8 OBJECT  GLOBAL DEFAULT    3 a
    11: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM c
    12: 0000000000000000    31 FUNC    GLOBAL DEFAULT    1 main
    13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND time
    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts

举例分析

1. 符号 main

Num:       Value       Size Type    Bind   Vis      Ndx Name
12:   0000000000000000    31  FUNC    GLOBAL DEFAULT   1  main
  • 符号名:main
  • 类型:函数
  • 本地符号/全局符号:全局

extern修饰的全局变量和函数,都会被标记为GLOBAL

符号对应空间所在位置

  • 所在节:函数指令存在了编号为1.text
  • 节中位置:偏移为0,表示main指令从.text第一个字节处开始存放
  • 大小:从.text的第一个字节往后,占31字节

符号对应的空间有明确的定义位置(.text),表明符号是在本模块定义的。

2. 符号 time

Num:     Value          Size   Type    Bind    Vis      Ndx  Name
13: 0000000000000000     0    NOTYPE  GLOBAL  DEFAULT   UND  time
  • 符号名:time
  • 类型:因为在本模块中找不到time的定义,所以不清楚符号的类型
  • 本地符号/全局符号:全局

UDN 表示符号只是在本模块引用,但是不在本模块定义(在其它模块定义),属于全局符号

UDN 表示符号没有在本模块中定义,因此value/size都没有意义,所以都是0

3. 符号 b

Num:        Value          Size   Type    Bind    Vis      Ndx   Name
 5:    0000000000000008     4    OBJECT  LOCAL   DEFAULT    3     b
  • 符号名:b
  • 类型:全局变量(静态变量)
  • 本地符号/全局符号:本地的

static修饰的全局变量和函数,都会被标记为LOCAL本地符号

  • 所在节:第3节.data,放在了.data中,说明是初始化了的
  • 节中位置:.data节的第8个字节处(偏移)
  • 大小:4个字节(int类型,肯定是4个字节)

符号对应空间有明确的定义位置,表明符号是在本模块中定义的

4. 符号 c

Num:         Value        Size  Type    Bind    Vis      Ndx  Name
11:   0000000000000004     4   OBJECT  GLOBAL  DEFAULT   COM   c
  • 符号名:c
  • 类型:(静态变量(全局变量))
    COM表示未初始化,未被初始化在.bss节中,不过.bss也是空的
  • 本地符号/全局符号:全局的

符号对应空间所在位置

  • 所在节:未分配空间的都在.bss节,也就是第4节,这不过第4节在编译阶段其实并不存在
  • 节中位置:.bss的第4字节处,这只是理论上的,因为在.o中还没有.bss的实际空间
  • 大小:4个字节

符号对应空间有明确的定义位置,表明符号是在本模块定义的

5. 符号 helloworld.c

Num:       Value          Size   Type    Bind     Vis     Ndx    Name
1:    0000000000000000     0     FILE    LOCAL  DEFAULT   ABS   helloworld.c

ABS 表示这个符号并不需要链接处理。
FILE 表示这个符号是源文件名。
LOCAL 表示这个符号时本地符号
对于源文件名来说,并没有对应的空间,所以value/size没有意义,所以值为0

6. 其它哪些没有符号名的是什么情况

专门给链接器链接时使用的本地符号


二、.symtab 符号种类

文件a.c

extern int a_val = 100;
static float a_va2 = 200;

static void a_fun2(void)
{
    a_va2 += 100;
}

extern int a_fun1(int a)
{
    b_val = b_val+100;
    b_fun1();
    a_fun2();
}

文件b.c

int b_va1 = 300;

int b_fun2(void)
{
    ...
}

int main(void)
{
    fun1(100);
    a_val = a_va1 + 200;
}

extern
表示定义的函数和全局变量可以被其它模块引用,如果不写extern,默认就是extern
为了不麻烦,一般都不会明确的写extern

static
表示定义的函数和全局变量,只能被本模块有效


.symtab中所记录的符号,严格说起来就两种:

1.全局符号:当前模块定义以后,除了自己外,其它所有模块也可以引用

我们以a.o模块为例来讲:
a.o模块定义,可由其它模块引用的全局符号

  • a_fun1a.o定义,但是其它模块(b.o)可以引用
  • a_va1:a.o定义,但是其它模块(b.o)可以引用

a.o模块引用,但是由其它模块定义的全局符号

  • b_fun1a.o引用,但是由b.o定义
  • b_va1a.o引用,但是由b.o定义

2.本地符号:模块自己定义,而且只能由自己引用的符号

  • a.oa_fun2a_va2,由于加了static修饰,表示符号只在模块内有效,
    所以a_fun2a_va2属于典型的由本模块定义、而且只能由本模块引用的本地符号

旁注:有关static
在写C代码时,对于那些只在本模块定义和引用的函数与全局变量来说,
我们要养成使用static的好习惯,这样子符号就是只在本模块有效的本地符号

如果不写static的话,会带来命名冲突、错误调用等等一些列可能的不良后果,
特别是对于大型C工程文件来说,出现这类不良后果的概率非常高


三、.symtab 解读

.symtab 符号表包含很多条目,每个条目记录的就是一个符号的基本信息。

(1)每个条目所包含的符号基本信息有哪些呢?

int name;
int value;
int size;
char type;
char bind;
char section;

信息含义:

1. name

name 中记录的并不是名字的字符串,我们前面说过所有的字符串都是放在了.strtab中,
name里面只记录字符串在.strtab中的偏移,通过这个偏移就能在.strtab中索引到符号的名字

比如

name = 5 # 偏移5

假如.strtab中的内容为main\0fun2\0a_va\0......
使用偏移5.strtab中进行搜索,当遇到\0时就截止,那么取出来的就是符号fun2

2. value

放的是地址:指向符号所代表的空间

比如:
(a)如果符号是初始化了的全局变量

int a = 100; //全局变量
int main(void)
{
}

初始化了的全局变量,它的空间在.data节中,那么value中放的就是这个空间的起始地址,
通过value中的地址值,就可以找到符号对应的空间,这对于后续的“链接”操作来说很重要

(b)如果符号是函数的话
比如:

int fun()
{

}

fun 函数指令存放在了.text节中,value中放的就是fun函数在.text节中的起始地址,其实就是函数第一条指令的地址

(c)需要强调的地方

不过对于.o(可重定位目标文件)和可执行目标文件来说,value 的值有所不同

  • 可重定位目标文件 value 中放的只是相对于节起始地址的偏移
  • 可执行目标文件 value 中放的是绝对地址

“可重定位目标文件”被连接在一起后,value 中放的就是链接时重定位后的绝对地址

3. size

size 代表的是 value 所指向空间的大小,毕竟value 只是起始地址,不能说明空间的大小

比如:
(a)如果符号是初始化了的全局变量的话
size代表的全局变量在.data中所占字节数。

(b)如果符号是函数的话
size代表的是函数指令在.text中所占空间的大小

4. type

符号类型,有如下几种类型。

  • FUNC:符号代表的是函数
  • OBJECT:符号代表的是全局变量
  • FILE:符号是源文件的名字

5.bind

就两种情况,

  • LCOAL
  • GLOBAL

(a)bind=LOCAL 本地符号
表示符号是本地的:符号在模块中定义后,只能由本模块引用,static 修饰的全局变量和函数就是这种情况
比如:

文件 a.o

static int a = 100;

static int fun(int arg)
{
...
}

(b)bind=CLOBAL 全局符号
表示符号在本模块定义,但是可以被其它模块引用(使用),extern修饰的全局变量和函数就是这种情况
比如:
文件 a.o

int a = 100; 

int fun(int arg)
{
...
}

6. Ndx

Ndx 的值有四种情况:

  • 节索引号
  • ABS
  • UNDEF
  • COM

(a)section=节索引号
说明符号所对应的空间在哪个节里面。
· 如果Ndx == 1
符号代表的空间在.text节,说明符号代表的是函数,因为只有函数指令才会保存在.text中。

· 如果Ndx == 3
符号代表的空间在.data中,说明符号是初始化了的全局变量,因为只有初始化了的全局变量才会在.data

(b)Ndx=ABS
表示该符号不需要被“链接程序”处理。

比如,如果符号名是***.c,这个符号不是全局变量、不是函数,只是一个源文件名而已,
链接器(ld/collect2)在链接“可重定位目标文件”时,这个符号不需要被处理

(c)Ndx=UND
表示这个符号,只是在本模块中被引用了,这个符号并不是由本模块定义的,在本模块找不到定义,
所以这个符号的Ndx就被标注为了UND,表示这个符号被定义在了其它模块中,链接时要到其它模块中去找搜寻它的定义

其它模块:

  • 其它自己写的.c所对应的.o
  • 静态库
  • 动态库

如果是链接时,在其它目标文件中还找不到该符号的定义的话,链接程序就会报错,提示找不到这个符号。

出错的原因有两种:

  • 你忘了链接所需的目标文件
    比如gcc a.o b.o,结果写成gcc a.o,少了一个,肯定会找不到。

  • 符号名压根就写错了,不可能找得到

(d)Ndx=COM
表示是还未被分配空间(位置)的未初始化的数据目标,比如未初始化的全局变量。

int a;
int main(void)
{
...
}

未被初始化的全局变量都放在.bss中的,但是前面就说过,由于没有数据,
所以.bss没有实际空间,只有当程序运行时才有实际.bss节的空间