.symtab: Symbol Table¶
概述¶
每个目标文件都会有一个符号表,熟悉编译原理的就会知道,在编译程序时,必须有相应的结构来管理程序中的符号以便于对函数和变量进行重定位。
此外,链接本质就是把多个不同的目标文件相互“粘”在一起。实际上,目标文件相互粘合是目标文件之间对地址的引用,即函数和变量的地址的相互引用。而在粘合的过程中,符号就是其中的粘合剂。
目标文件中的符号表包含了一些通用的符号,这部分信息在进行了 strip
操作后就会消失。这些符号信息可能包括变量名、函数名。
符号表可以被视为一个数组,数组中的每一个元素都是一个结构体,具体如下
typedef struct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;
每个字段的含义如下
字段 | 说明 |
---|---|
st_name | 符号在字符串表中对应的索引。如果该值非 0,则它表示了给出符号名的字符串表索引,否则符号表项没有名称。 注:外部 C 符号在 C 语言和目标文件的符号表中具有相同的名称。 |
st_value | 给出与符号相关联的数值,具体取值依赖于上下文,可能是一个正常的数值、一个地址等等。 |
st_size | 给出对应符号所占用的大小。如果符号没有大小或者大小未知,则此成员为0。 |
st_info | 给出符号的类型和绑定属性。之后会给出若干取值和含义的绑定关系。 |
st_other | 目前为 0,其含义没有被定义。 |
st_shndx | 如果符号定义在该文件中,那么该成员为符号所在节在节区头部表中的下标;如果符号不在本目标文件中,或者对于某些特殊的符号,该成员具有一些特殊含义。 |
其中,符号表中下标 0 存储了符号表的一个元素,同时这个元素也相对比较特殊,作为所有未定义符号的索引,具体如下
名称 | 取值 | 说明 |
---|---|---|
st_name | 0 | 无名称 |
st_value | 0 | 0 值 |
st_size | 0 | 无大小 |
st_info | 0 | 无类型,局部绑定 |
st_other | 0 | 无附加信息 |
st_shndx | 0 | 无节区 |
st_value¶
在 Linux 的 ELF 文件中,具体说明如下
- 该符号对应着一个变量,那么表明该变量在内存中的偏移。我们可由这个值获取其文件偏移
- 获取该符号对应的
st_shndx
,进而获取到相关的节区。 - 根据节区头元素可以获取节区的虚拟基地址和文件基地址。
- value-内存基虚拟地址=文件偏移-文件基地址
- 获取该符号对应的
- 该符号对应着一个函数,那么表明该函数在文件中的起始地址。
st_info¶
st_info 中包含符号类型和绑定信息,这里给出了控制它的值的方式具体信息如下
#define ELF32_ST_TYPE(i) ((i)&0xf)
#define ELF32_ST_INFO(b, t) (((b)<<4) + ((t)&0xf))
Symbol Type¶
可以看出 st_info 的低 4 位表示符号的类型,具体定义如下
名称 | 取值 | 说明 |
---|---|---|
STT_NOTYPE | 0 | 符号的类型没有定义。 |
STT_OBJECT | 1 | 符号与某个数据对象相关,比如一个变量、数组等等。 |
STT_FUNC | 2 | 符号与某个函数或者其他可执行代码相关。 |
STT_SECTION | 3 | 符号与某个节区相关。这种类型的符号表项主要用于重定位,通常具有 STB_LOCAL 绑定。 |
STT_FILE | 4 | 一般情况下,符号的名称给出了生成该目标文件相关的源文件的名称。如果存在的话,该符号具有 STB_LOCAL 绑定,其节区索引是 SHN_ABS 且优先级比其他STB_LOCAL 符号高。 |
STT_LOPROC ~STT_HIPROC | 13~15 | 保留用于特定处理器 |
共享目标文件中的函数符号有比较特殊,当另一个目标文件从共享目标文件中引用一个函数时,链接器自动为被引用符号创建过程链接表项。共享目标中除了STT_FUNC
, 其它符号将不会通过过程链接表自动被引用。
如果一个符号的值指向节内的特定位置,则它的节索引号 st_shndx
,包含了它在节头表中的索引。当一个节在重定位过程中移动时,该符号值也做相应改变,对该符号的引用继续指向程序中的相同位置。有些特定节索引值具有其他语义。
Symbol Binding¶
根据 #define ELF32_ST_BIND(i) ((i)>>4)
可以看出 st_info 的高 4 位表示符号绑定的信息。而这部分信息确定了符号的链接可见性以及其行为,具体的取值如下
名称 | 取值 | 说明 |
---|---|---|
STB_LOCAL | 0 | 表明该符号为局部符号,在包含该符号定义的目标文件以外不可见。相同名称的局部符号可以存在于多个文件中,互不影响。 |
STB_GLOBAL | 1 | 表明该符号为全局符号,对所有将被组合在一起的目标文件都是可见的。一个文件中对某个全局符号的定义将满足另一个文件对相同全局符号的未定义引用。我们称初始化非零变量的全局符号为强符号,只能定义一次。 |
STB_WEAK | 2 | 弱符号与全局符号类似,不过它们的定义优先级比较低。 |
STB_LOPROC ~STB_HIPROC | 13 | 这个范围的取值是保留给处理器专用语义的。 |
在每个符号表中,所有具有 STB_LOCAL 绑定的符号都优先于弱符号和全局符号。符号表节区中的 sh_info 项所对应的头部的成员包含第一个非局部符号的符号表索引。
此外,全局符号与弱符号的主要区别如下:
- 当链接器在链接多个可重定位目标文件时,不允许定义多个相同名字的
STB_GLOBAL
符号。另一方面,如果存在一个已定义全局符号,则同名的弱符号的存在不会引起错误。链接器会优先选择全局定义,忽略弱符号定义。类似的,如果一个公共符号存在(st_shndx
域为SHN_COMMON
的符号),则同名的弱符号的存在不会引起错误。链接器会选择公共定义,忽略弱符号定义。 - 当链接器寻找文件库时,它会提取包含未定义全局符号的成员,可能是一个全局符号或者弱符号。链接器不会为了解决未定义的弱符号问题而提取文件,未定义的弱符号的值为0。
符号取值¶
不同的目标文件类型对符号表项中 st_value 成员的解释不同:
- 在可重定位文件中,st_value 保存了节区索引为 SHN_COMMON 的符号的对齐约束。
- 在可重定位文件中,st_value 保存了已定义符号的节区偏移。也就是说,st_value保留了st_shndx 所标识的节区的头部到符号位置的偏移。
- 在可执行和共享目标文件中,st_value 包含一个虚地址。为了使得这些文件的符号对动态链接器更有用,节区偏移(针对文件的解释)给出了与节区号无关的虚拟地址(针对内存的解释)。
符号表取值在不同的目标文件中具有相似的含义,可以有适当的程序可以采取高效的方法来访问数据。
st_shndx¶
特殊的索引及其意义如下
- SHN_ABS: 符号的取值具有绝对性,不会因为重定位而发生变化。
- SHN_COMMON: 符号标记了一个尚未分配的公共块。符号的取值给出了对齐约束,与节区的 sh_addralign 成员类似。就是说,链接编辑器将在地址位于 st_value 的倍数处为符号分配空间。符号的大小给出了所需要的字节数。
- SHN_UNDEF: 此索引值表示符号没有定义。当链接编辑器将此目标文件与其他定义了该符号的目标文件进行组合时,此文件中对该符号的引用将被链接到实际定义的位置。
如何定位¶
那么对于一个符号来说如何定位其对应字符串的地址呢?具体步骤如下
- 根据 Section Header Table 中符号节头中的
sh_link
获取该符号节中对应符号字符串节在Section Header Table
中的下标。进而我们就可以获取对应符号节的地址。 - 根据该符号的定义中的 st_name 获取该符号的偏移,即在对应符号节中的偏移。
- 根据上述两者就可以定位一个符号对应的字符串的地址了。