符号-链接的接口
l 符号表
链接过程的本质就是把多个不同的目标文件之间相互粘在一起。在链接中,目标文件之间相互拼合实际上是目标文件之间对地址的引用,即对函数和变量(函数,变量统称符号)的地址的引用。
符号是链接过程中的粘合剂,整个链接过程是基于符号引用完成的,链接过程中很关键的一部分就是符号的管理,每一个目标文件都会有一个相应的符号表(Symbol Table),这个表里面记录了目标文件中用到的所有符号。每个定义的符号有一个对应的值,叫做符号值,对于变量和函数来说,符号值就是它们的地址。
符号主要包括定义在本文件内的全局符号,在本文件中引用的全局符号(但定义在其它文件),段名,局部符号,行号信息(目标文件指令与源代码中代码行的对应关系)等,对于链接来说,段名,局部符号,行号信息等对其它文件不可见的信息并不重要,关键处理全局符号之间的引用。
ELF符号表的结构如下:
typedef struct {
Elf32_Word st_name; /* 符号名,符号名在字符串表中的下标 */
Elf32_Addr st_value; /* 符号值 */
Elf32_Word st_size; /* 符号大小,为0时表示大小为0或大小未知 */
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx; /* 符号所在的段 */
} Elf32_Sym;
l 特殊符号
使用ld作为链接器来链接生成可执行文件时,它会定义很多特殊的符号,程序员可以直接声明并且引用这些特殊符号,常见的特殊符号包括:
__executable_start,该符号为程序的起始地址。
__etext或etext,该符号为代码段的结束地址。
__edata或edata,该符号为数据段的结束地址。
__end或end,该符号为程序的结束地址。
在程序中可先声明这些符号,并引用,如:
extern char __executable_start[];
printf(“%x\n”, __executable_start);
l 弱符号与强符号
如果目标文件A,B都定义了某变量global,并进行了初始化,当ld链接目标文件A和B时会报错,提示multiple definition of “global”,类似于global(多次定义链接器报错)的这种符号成为强符号,强符号相对于弱符号而言,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。可通过__attribute__((weak))来定义任何一个强符号为弱符号。
针对强弱符号,链接器通常按如下规则处理与选择被多次定义的全局符号:
1. 不允许强符号被多次定义(即不同目标文件中不能有同名的强符号);如果有多个强符号定义,则链接器会报错。
2. 如果一个符号在某个目标中为强符号,则其它文件中都是弱符号,则选择强符号。
3. 如果一个符号在所有目标文件中都是弱符号,则选择占用空间最大的一个(但我做了个小测试,貌似并非如此)。
l 弱引用与强引用
目标文件中对外部符号的引用在被链接时会被正确的决议,如果没有找到该符号的定义,链接器会报符号未定义错误,这种引用被称为强引用,与之相对的称为弱引用,对于弱引用,链接器在链接时如果找不到符号,链接器并不报错可通过__attribute__((weakref))来声明对一个外部符号的引用为弱引用。
弱符号和弱引用对于库来说十分有用,如库中定义的弱符号可以被用户定义的强符号所覆盖,从而使得程序可以使用自定义版本的库函数;或者程序可以对某些扩展功能模块的引用定义为弱引用,当将扩展模块与程序链接在一起时,功能模块就可以正常使用;如果去掉了某些功能模块,那么程序也能正确的链接,只是缺少了相应的功能,这使得程序的功能更加容易裁剪和组合。