1 .symtab符号表
.symtab中记录的符号是从.s文件来的,所以.s这个汇编文件很关键。
1.1 .symtab所记录符号的种类
a.c
extern int a_va1 = 100; static float a_va2 = 200.0; static void a_fun2(void) { a_va2 += 100; } extern int a_fun1(int a) { b_va1 = b_va1+100; b_fun1(); a_fun2(); }
b.c
int b_va1 = 300; int b_fun2(void) { ... } int main(void) { fun1(100); a_va1 = a_va1 + 200; }
extern:表示定义的函数和全局变量可以被其它模块引用,如果不写extern,默认就是extern,为了不麻烦,一般都不会明确的写extern。
static:表示定义的函数和全局变量,只能被本模块有效。
.symtab中所以记录的符号,严格说起来就两种:
(1)全局符号:某模块定义以后,除了自己外,其它所有模块也可以引用我们以a.o模块为例来讲。
1)由a.o模块定义,可由其它模块引用的全局符号
a_fun1:a.o定义,但是其它模块(b.o)可以引用
a_va1:a.o定义,但是其它模块(b.o)可以引用
2)由a.o模块引用,但是由其它模块定义的全局符号
b_fun1:a.o引用,但是由b.o定义
b_va1:a.o引用,但是由b.o定义
(2)本地符号:模块自己定义,而且只能由自己引用的符号
a.o的a_fun2和a_va2,由于加了static修饰,表示符号只在模块内有效,所以a_fun2和a_va2属于典型的由本模块定义、而且只能由本模块引用的本地符号。
旁注:有关static
在写c代码时,对于那些只在本模块定义和引用的函数与全局变量来说,我们要养成使用static的好习惯,这样子符号就是只在本模块有效的本地符号。
如果不写static的话,会带来命名冲突、错误调用等等一些列可能的不良后果,特别是对于大型c工程文件来说,出现这类不良后果的概率非常高。
1.2 .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
符号类型,有如下几种类型。
(a)FUNC:符号代表的是函数
(b)OBJECT:符号代表的是全局变量
(c)FILE:符号是源文件的名字
5)bind
就两种情况,LCOAL、GLOBAL
(a)bind=LOCAL
表示符号是本地的:符号在模块中定义后,只能由本模块引用,static修饰的全局变量和函数就是这种情况。
比如:
a.o:
static int a = 100; static int fun(int arg) { ... }
有关static、extern等关键字,后面章节会还会详细讲,不过如果你能在这里就先理解他,后面章节再讲到时,你会有一种完全不一样的感觉。
(b)bind=CLOBAL(全局符号)
表示符号在本模块定义,但是可以被其它模块引用(使用),extern修饰的全局变量和函数就是这种情况。
比如:
a.o:
int a = 100; int fun(int arg) { ... }
定义a和fun时没有显式的写出extern,那么默认就是extern的,为了不麻烦,一般不会显式写出。
6)section
section的值有四种情况,节索引号、ABS、UNDEF、COM
(a)section=节索引号
说明符号所对应的空间在哪个节里面。
比如,
· 如果section == 1
符号代表的空间在.text节,说明符号代表的是函数,因为只有函数指令才会保存在.text中。
· 如果section == 3
符号代表的空间在.data中,说明符号是初始化了的全局变量,因为只有初始化了的全局变量才会在.data节。
(b)section=ABS
表示该符号不需要被“链接程序”处理。
比如,如果符号名是***.c,这个符号不是全局变量、不是函数,只是一个源文件名而已,链接器(ld/collect2)在链接“可重定位目标文件”时,这个符号不需要被处理。
(c)section=UNDEF
表示这个符号,只是在本模块中被引用了,这个符号并不是由本模块定义的,在本某块找不到定义,所以这个符号的section就被标注为了UNDEF,表示这个符号被定义在了其它模块中,链接时要到其它模块中去找搜寻它的定义。
其它模块:
· 其它自己写的.c所对应的.o
· 静态库
· 动态库
如果是链接时,在其它目标文件中还找不到该符号的定义的话,链接程序就会报错,提示找不到这个符号。
出错的原因有两种:
· 你忘了链接所需的目标文件
比如gcc a.o b.o,结果写成gcc a.o,少了一个,肯定会找不到。
· 符号名压根就写错了,不可能找得到
(d)section=COM
表示是还未被分配空间(位置)的未初始化的数据目标,比如未初始化的全局变量。
int a; int main(void) { ... }
未被初始化的全局变量都放在.bss中的,但是前面就说过,由于没有数据,所以.bss没有实际空间,只有当程序运行时才有实际.bss节的空间。
1.2 查看目标文件的.symtab符号表信息
目标文件三种:
可重定位目标文件
可执行目标文件
共享目标文件
这几类目标文件的.symtab符号表都可以被查看,目前我们这里只查看.o可重定位目标文件的符号表,查看程序(命令)依然是readelf。
readelf -h:查看ELF头中的信息
readelf -s:查看.symtab符号表
比如:helloworld.c ——————> helloworld.o,查看helloworld.o的.symtab
helloworld.c
unsigned long a = 100; static int b = 100; int c; int main(void) { time(&a); printf("hello world\n"); return 0; }
演示:readelf -s helloworld.o
Num: Value Size Type Bind Vis Ndx(section) 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
回顾.symtab中每个条目(符号)的基本信息
int name;
int value;
int size;
char type;
char bind;
char section;
(1)举例分析
1)符号main
Num: Value Size Type Bind Vis Ndx(section) Name 12: 0000000000000000 31 FUNC GLOBAL DEFAULT 1 main
(a)符号名:main
(b)类型:函数
(c)本地符号/全局符号:全局
被extern修饰的全局变量和函数,都会被标记为GLOBAL
(d)符号对应空间所在位置
· 所在节:函数指令存在了编号为1的.text节
· 节中位置:偏移为0,表示main指令从.text第一个字节处开始存放
· 大小:从.text的第一个字节往后,占31字节
符号对应的空间有明确的定义位置(.text),表明符号是在本模块定义的。
2)符号time
Num: Value Size Type Bind Vis Ndx(section) Name 13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND time
(a)符号名:time
(b)类型:因为在本模块中找不到time的定义,所以不清楚符号的类型
(c)本地符号/全局符号:全局
UDN表示符号只是在本模块引用,但是不在本模块定义(在其它模块定义),属于全局符号。
(d)符号对应空间所在位置
UDN表示符号没有在本模块中定义,因此value/size都没有意义,所以都是0。
3)符号b
Num: Value Size Type Bind Vis Ndx(section) Name 5: 0000000000000008 4 OBJECT LOCAL DEFAULT 3 b
(a)符号名:b
(b)类型:全局变量(静态变量)
(c)本地符号/全局符号:本地的
被static修饰的全局变量和函数,都会被标记为LOCAL本地符号
(d)符号对应空间所在位置
· 所在节:第3节.data,放在了.data中,说明是初始化了的
· 节中位置:.data节的第8个字节处(偏移)
· 大小:4个字节(int类型,肯定是4个字节)
符号对应空间有明确的定义位置,表明符号是在本模块中定义的。
4)符号c
Num: Value Size Type Bind Vis Ndx(section) Name 11: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM c
(a)符号名:c
(b)类型:(静态变量(全局变量))
COM表示未初始化,未被初始化在.bss节中,不过.bss也是空的。
(c)本地符号/全局符号:全局的
(d)符号对应空间所在位置
· 所在节:未分配空间的都在.bss节,也就是第4节,这不过第4节在编译阶段其实并不存在
· 节中位置:.bss的第4字节处,这只是理论上的,因为在.o中还没有.bss的实际空间
· 大小:4个字节
符号对应空间有明确的定义位置,表明符号是在本模块定义的
5)符号helloworld.c
Num: Value Size Type Bind Vis Ndx(section) Name 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS helloworld.c
ABS表示这个符号并不需要链接处理。
FILE表示这个符号是源文件名。
LOCAL表示这个符号时本地符号
对于源文件名来说,并没有对应的空间,所以value/size没有意义,所以值为0。
5)其它哪些没有符号名的是什么情况
专门给链接器链接时使用的本地符号。