链接
- 基本概念
- 分类:静态链接、动态链接。
- 工作步骤:
- 符号解析:每个目标文件都带有符号表,链接器将每个符号引用与所有目标文件中恰好一个符号定义关联。
- 重定位:把所有目标文件合并,并更新符号引用的位置。
- ELF 文件
- 分类
- 可重定位目标文件:
.o,与一个.c对应。 - 可执行目标文件:如
a.out。 - 共享目标文件:
.so,在加载或运行时动态链接。
- 可重定位目标文件:
- 节
.text:存放代码,可执行,不可写。.rodata:只读数据,如字符串常量、全局常量。.data:数据段,其中的变量为全局变量或静态变量,且被初始化为非 0 值。.bss:数据段,与.data类似,但是变量未初始化或以 0 初始化。- 如果
COMMON存在,则未初始化全局变量优先存放在其中。
- 如果
COMMON:未初始化的全局变量。.symtab:符号表,链接时使用。.rel.text:.text节的重定位信息。.rel.data:.data节的重定位信息。.debug:调试符号。
- 分类
- 链接器符号
- 分类
- 全局符号:当前模块定义、其他模块可引用的符号。
- 外部符号:当前模块引用、其他模块定义的符号。
- 局部符号:只在当前模块可用的符号,用
static修饰,不是局部变量。
- 符号解析
- 会成为符号的 C 语言元素:
- 函数的声明、定义。
- 全局/静态变量的声明、定义。
- 函数内静态变量的声明、定义。
- 符号强弱:
- 强符号:初始化过的全局符号,函数定义一定是强符号。
- 根据是函数还是变量、初始化的值,符号进入
.text、.data或.bss。
- 根据是函数还是变量、初始化的值,符号进入
- 弱符号:未初始化的全局符号。在编译
.c为.o后,这些符号进入 COMMON。
- 强符号:初始化过的全局符号,函数定义一定是强符号。
- 解析规则:
- 只有一个强符号可以存在,多个强符号互相冲突。
- 强符号比同名的多个弱符号都优先,即
COMMON中会被覆盖。 - 无强符号时,同名的多个弱符号随机选一个。
- 选择符号不考虑 C 语言的类型,只与强弱、随机性相关。
- 会成为符号的 C 语言元素:
- 重定位
- 重定位条目
typedef struct { long offset; long type:32, symbol:32; long addend; } Elf64_Rela;- 每一处符号引用都会被转换为
Elf64_Rela,供链接器做重定位处理。 offset表示此引用相对于段起始地址的偏移量。type表示重定位的类型:R_X86_64_32:32 位绝对地址引用。R_X86_64_PC32:32 位 PC 相对地址引用,偏移量是目标相对于指令末端地址。
symbol表示被引用的符号在符号表中的索引。addend与指令长度相关,用于处理计算指令末端。
- 32 位绝对地址引用
- 此时一定有
addend == 0。 - 重定位结果
refptr = ADDR(symbol) + addend = ADDR(symbol)。
- 此时一定有
- 32 位 PC 相对地址引用
- 首先计算符号引用本身的位置
refaddr = ADDR(.text) + offset。 - 再计算重定位结果
refptr = ADDR(symbol) + addend - refaddr = ADDR(symbol) - (refaddr - addend)。 refaddr - addend即当前指令的末端的地址。
- 首先计算符号引用本身的位置
- 重定位条目
- 分类
- 加载目标文件
- 静态链接
- 静态库为
lib<name>.a。 - 编译时
gcc命令用-l<name>完成链接。 gcc命令需要注意顺序,符号解析过程只按照命令行顺序。- 每链接一个目标文件或静态库,则尝试用其中的符号解析现有的未定义符号,但符号不会用于后续的解析。
- 所有文件都处理后,若还有未定义符号,则报错。
- 所以要把被依赖的库文件放在依赖的目标后面链接。
- 静态库为
- 加载时动态链接
- 编译时在
gcc命令参数中添加动态库,对动态库的引用被加入可执行文件。 - 运行可执行文件时,在加载阶段由动态链接器加载所有动态库。
- 动态链接器一般为
ld-linux.so或各种变体。
- 编译时在
- 运行时动态链接
- 不需要在编译时引用动态库,而是在代码中用
dlopen()动态加载动态库。 - 只有在执行到需要的时候才会按需加载。
- 不需要在编译时引用动态库,而是在代码中用
- 静态链接