linux学习中
分类: LINUX
2010-06-10 14:46:39
3.5.1 ELF符号表结构(1)
ELF文件中的符号表往往是文件中的一个段,段名一般叫".symtab"。符号表的结构很简单,它是一个Elf32_Sym结构(32位ELF文件)的数组,每个Elf32_Sym结构对应一个符号。这个数组的第一个元素,也就是下标0的元素为无效的"未定义"符号。Elf32_Sym的结构定义如下:
这几个成员的定义如表3-14所示。
表3-14
st_name |
符号名。这个成员包含了该符号名在字符串 表中的下标(还记得字符串表吧?) |
st_value |
符号相对应的值。这个值跟符号有关,可能 是一个绝对值,也可能是一个地址等,不同的符号, 它所对应的值含义不同,见下文“符号值” |
st_size |
符号大小。对于包含数据的符号,这个值是该数据 类型的大小。比如一个double型的符号它占用8个字节。 如果该值为0,则表示该符号大小为0或未知 |
st_info |
符号类型和绑定信息,见下文“符号类型与绑定信息” |
st_other |
该成员目前为0,没用 |
st_shndx |
符号所在的段,见下文“符号所在段” |
符号类型和绑定信息(st_info) 该成员低4位表示符号的类型(Symbol Type),高28位表示符号绑定信息(Symbol Binding),如表3-15、表3-16所示。
表3-15
符号绑定信息 | ||
宏定义名 |
值 |
说明 |
STB_LOCAL |
0 |
局部符号,对于目标文件的外部不可见 |
STB_GLOBAL |
1 |
全局符号,外部可见 |
STB_WEAK |
2 |
弱引用,详见“弱符号与强符号” |
表3-16
符号类型 | ||
宏定义名 |
值 |
说明 |
STT_NOTYPE |
0 |
未知类型符号 |
STT_OBJECT |
1 |
该符号是个数据对象,比如变量、数组等 |
STT_FUNC |
2 |
该符号是个函数或其他可执行代码 |
STT_SECTION |
3 |
该符号表示一个段,这种符号必须是 STB_LOCAL的 |
STT_FILE |
4 |
该符号表示文件名,一般都是该目标文 件所对应的源文件名,它一定是 STB_LOCAL类型的,并且它的 st_shndx一定是SHN_ABS |
符号所在段(st_shndx)如果符号定义在本目标文件中,那么这个成员表示符号所在的段在段表中的下标;但是如果符号不是定义在本目标文件中,或者对于有些特殊符号,sh_shndx的值有些特殊,如表3-17所示。
表3-17
符号所在段特殊常量 | ||
宏定义名 |
值 |
说明 |
SHN_ABS |
0xfff1 |
表示该符号包含了一个绝对的值。 比如表示文件名的符号就属于这种类型的 |
SHN_COMMON |
0xfff2 |
表示该符号是一个“COMMON块” 类型的符号,一般来说,未初始化的全局 符号定义就是这种类型的,比如 SimpleSection.o里面的global_uninit_var。 有关“COMMON”详见“深入静态链接” 之“COMMON块” |
SHN_UNDEF |
0 |
表示该符号未定义。这个符号表示该符号 在本目标文件被引用到,但是定义在其他目标文件中 |
3.5.1 ELF符号表结构(2)
符号值(st_value) 我们前面已经介绍过,每个符号都有一个对应的值,如果这个符号是一个函数或变量的定义,那么符号的值就是这个函数或变量的地址,更准确地讲应该按下面这几种情况区别对待。
在目标文件中,如果是符号的定义并且该符号不是"COMMON块"类型的(即st_shndx不为SHN_COMMON,具体请参照"深入静态链接"一章中的"COMMON块"),则st_value表示该符号在段中的偏移。即符号所对应的函数或变量位于由st_shndx指定的段,偏移st_value的位置。这也是目标文件中定义全局变量的符号的最常见情况,比如SimpleSection.o中的"func1"、"main"和"global_init_var"。
在目标文件中,如果符号是"COMMON块"类型的(即st_shndx为SHN_COMMON),则st_value表示该符号的对齐属性。比如SimpleSection.o中的"global_uninit_var"。
在可执行文件中,st_value表示符号的虚拟地址。这个虚拟地址对于动态链接器来说十分有用。我们将在第3部分讲述动态链接器。
根据上面的介绍,我们对ELF文件的符号表有了大致的了解,接着将以SimpleSection.o里面的符号为例子,分析各个符号在符号表中的状态。这里使用readelf工具来查看ELF文件的符号,虽然objdump工具也可以达到同样的目的,但是总体来看readelf的输出格式更为清晰:
readelf的输出格式与上面描述的Elf32_Sym的各个成员几乎一一对应,第一列Num表示符号表数组的下标,从0开始,共15个符号;第二列Value就是符号值,即st_value;第三列Size为符号大小,即st_size;第四列和第五列分别为符号类型和绑定信息,即对应st_info的低4位和高28位;第六列Vis目前在C/C++语言中未使用,我们可以暂时忽略它;第七列Ndx即st_shndx,表示该符号所属的段;当然最后一列也最明显,即符号名称。从上面的输出可以看到,第一个符号,即下标为0的符号,永远是一个未定义的符号。对于另外几个符号解释如下。
func1和main函数都是定义在SimpleSection.c里面的,它们所在的位置都为代码段,所以Ndx为1,即SimpleSection.o里面,.text段的下标为1。这一点可以通过readelf -a或objdump -x得到验证。它们是函数,所以类型是STT_FUNC;它们是全局可见的,所以是STB_GLOBAL;Size表示函数指令所占的字节数;Value表示函数相对于代码段起始位置的偏移量。
再来看printf这个符号,该符号在SimpleSection.c里面被引用,但是没有被定义。所以它的Ndx是SHN_UNDEF。
global_init_var是已初始化的全局变量,它被定义在.bss段,即下标为3。
global_uninit_var是未初始化的全局变量,它是一个SHN_COMMON类型的符号,它本身并没有存在于BSS段;关于未初始化的全局变量具体请参见"COMMON块"。
static_var.1533和static_var2.1534是两个静态变量,它们的绑定属性是STB_LOCAL,即只是编译单元内部可见。至于为什么它们的变量名从"static_var"和"static_var2"变成了现在这两个"static_var.1533"和"static_var2.1534",我们在下面一节"符号修饰"中将会详细介绍。
对于那些STT_SECTION类型的符号,它们表示下标为Ndx的段的段名。它们的符号名没有显示,其实它们的符号名即它们的段名。比如2号符号的Ndx为1,那么它即表示.text段的段名,该符号的符号名应该就是".text"。如果我们使用"objdump -t"就可以清楚地看到这些段名符号。
"SimpleSection.c"这个符号表示编译单元的源文件名。