链接器需要完成2个任务,以便来生成可执行文件:
解析符号(symbol resolution)
。每个符号对应一个函数、一个全局变量或一个静态变量。符号解析的目的,是为将每个符号引用和符号定义关联起来。
重定位(relocation)
。编译器和汇编器生成的代码和数据节都是从地址为0开始的。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。
有三种目标文件:
(1) 可重定位目标文件,包含二进制的代码和数据。可以在编译时与其它目标文件合并起来,创建一个可执行的目标文件。
(2) 可执行目标文件,包含二进制代码和数据,可以直接执行。
(3) 共享目标文件,一种特殊的可重定位目标文件,可以在加载或运行时被动态地加载进内存并链接。
ELF可重定位目标文件格式
名称 | 含义 |
---|---|
ELF头 | 描述字节序、机器类型等信息 |
.text | 代码段 |
.rodata | 只读数据 |
.data | 已初始化的全局和静态变量 |
.bss | 未初始化的/或被初始化为0的全局和静态变量 |
.symtab | 符号表,存放程序中定义和引用的函数和全局变量的信息 |
.rel.text | 一个.text段中位置的列表。当链接器把这个目标文件和其它文件组合时,需要修改这些位置信息 |
.rel.data | 被模块引用或定义的所有全局变量的重定位信息 |
.debug | 调试符号表. 包含程序中定义的局部变量和类型. 需-g选项来编译 |
.line | 原始C源程序的行号和.text段中机器指令间的映射. 需-g选项来编译 |
.strtab | 一个字符串从表,包含.symtab和.debug段中的符号表 |
节头部表 |
每个可重定位模块都有一个符号表
,包含本模块中定义和引用的符号信息。
全局符号: 本模块定义的,能被其它模块引用的全局函数和全局变量. (全局、非静态的函数和变量)
外部符号: 其它模块定义的,被本模块引用的全局函数和全局变量.
内部符号: 本模块定义的,只被本模块引用的。带static属性的全局函数和全局变量. (全局的、static的函数和变量)
函数内部定义的非static局部变量,运行时在栈中被管理,链接器忽略这些符号。
readelf
查看目标文件详细信息
名称 | 含义 |
---|---|
ABS | 不被重定位的符号 |
COMMON | 未分配位置的未初始化的数据目标 |
UNDEF | 未定义的符号(在本模块引用的,但在其它模块定义的符号) |
value: 字段对其要求
size : 最小的大小
编译器的输出,便是链接器的输入。
编译器输出的全局符号,要么强(strong)要么弱(weak),汇编器把这些信息隐含的编码在可重定位目标文件的符号表里。函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。有以下规则:
不允许有多个同名的强符号, 否则链接器会提示: "multiple definition of 'xx'".
若有一个强符号和一个若符号同名,选择强符号.
若有多个同名的弱符号,则从这些弱符号中任意选择一个.
若有多个弱符号,链接器会随机的选择一个,会带来潜在的error。
可用使用 -fno-common
标志,该选项告诉链接器,在遇到多重定义的全局符号时,触发错误。
在符号解析阶段,链接器会根据编译命令行中库的顺序(从左到右),来重定位目标文件和.a/.so文件。