链接
1.回顾一下
hello.c的编译过程:
①预处理器cpp gcc -E hello.c -o hello.i (ascii码中间文件)
将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码
②编译器ccl gcc -S hello.i -o hello.s (ascii码汇编语言文件)
“翻译”成汇编代码
③汇编器as gcc -c hello.s -o hello.o (可重定位目标文件)
汇编代码翻译成符合一定格式的机器代码,在Linux系统上一般表现位ELF目标文件(OBJ文件)
④链接器ld gcc hello.o -o hello (可执行目标文件)
将汇编生成的OBJ文件、系统库的OBJ文件、库文件链接起来,最终生成可以在特定平台运行的可执行程序
2.链接器
question1:为什么要用它?
①模块化
程序可以写成一组小规模的源代码,对于通用的功能写成库。例如Math library, standard C library。
②效率
时间上单独编译。一个源文件发生变化,单独编译,重新链接。
空间上:库。功能函数聚合为一个单独的文件,可执行文件仅包括其实际使用的代码。
question2:它做了什么?
①符号解析:
- 在这个程序里面定义或者引用的符号(函数、变量)。
- 符号表:保存符号定义(编译器干的),是一个结构数组,每个条目有符号名,大小,保存位置。
- 链接器将符号引用与符号定义关联起来。
②重定位
- 各自的代码和数据段合并。
- 符号的相对位置重定位到绝对存储位置。
- 更新所有符号引用到绝对位置。
a.目标文件有哪些
①可重定位目标文件
编译时可以与其他可重定位合并。
②可执行目标文件
可以直接复制到存储器并执行。
③共享目标文件
特殊的可重定位目标文件。可以在加载或运行时,动态地加载到存储器并链接。(windows内称为动态链接库)
b.可执行和可链接格式ELF
目标文件的标准二进制格式,统一的格式。
①ELF header (16 字节的序列)
描述了生成该文件的系统的字的大小和字节顺序,剩下的部包含帮助链接器语法分析和解释目标文件的信息。
ELF 头的大小、目标文件的类型 (如可重定位、可执行或者是共享的)、机器类型(如 IA32) 、节头部表 (section header table) 文件偏移,以及节头部表中的条目大小和数量。
Segment header table:页大小,虚地址内存段,段大小
②.text: 已编译程序的机器代码。
③.rodata: 只读数据,比如 printf 语句中的格式串和开关语句的跳转表。
④.data: 已初始化的全局变量。
⑤.bss: 未初始化的全局变量。目标文件种不需要占据任何实际的磁盘空间。
⑥.symtab:
- 符号表
- 函数和静态变量名 。
- Section名与位置 。
- 和编译器中的符号表不同不包含局部变量的条目。
⑦. rel .text:
- .text 节中位置的列表。
- 执行时需要修改的指令地址。
- 可执行目标文件中并不需要重定位信息因此通常省略。
⑧.rel.data:
- .data中重定位信息
- 被模块引用或定义的任何全局变量的重定位信息。
⑨.debug:
- 一个调试符号表
- 其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的源文件。
- gcc -g
⑩节头部表: 每个section的偏移与大小
c.符号和符号表
① 全局符号global 。本模块定义,外模块能引用(非静态c函数和全局变量)。
②外部符号external。 外模块定义本模块引用。
③本地符号local。本模块定义外模块不能引用。static。不是本地程序变量。
链接器的符号规则
strong:函数,已初始化的全局变量
weak: 未初始化的全局变量
①不允许多个强符号。
②一个强符号和多个弱符号:选择强
③多个弱符号:任意选择一个
*全局变量尽可能不使用,否则加上static或者初始化,外部extern。
symtab节中包含符号表,每个条目的格式:
d.与静态库链接
Unix 系统中,静态库以一种称为存档 (archive) 的特殊文件格式存放在磁盘中。 是一组连接起来的可重定位目标文件的集合。
使用静态库解析引用过程:
链接器从左到右按命令行上出现顺序,扫描可重定位目标文件和存档文件。
- E 可重定位目标文件的集合
- U 未解析符号集合
- D 前面输入文件中已经定义的符号集合
①f 是目标文件:f添加到E,修改U和D。
②f 是存档文件:匹配U和f中定义的符号,某个存档成员m匹配成功,m添加E,其他被丢弃,修改U和D。
③完成扫描后: U非空,报错,U空合并和重定位E中目标文件。
e.重定位
①重定位节和符号定义
- 将所有相同类型的节合并为同一类型的新的聚合节
- 将运行时存储器地址赋给新的聚合节
- 使每个指令和全局变量有唯一的运行时存储地址
②重定位节中的符号引用
- 修改代码节和数据节中对每个符号的引用
- 依赖于重定位条目
重定位条目:
- 无论何时汇编器遇到对最终位置未知的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。
- 放在.rel.text中,已初始化的在.rel.data中
- 两种最基本的重定位类型
- R_386_PC32: 重定位一个使用 32 PC 相对地址的引用,总是与函数调用有关。
- R_386_32: 重定位一个使用 32 位绝对地址的引用,总是与使用跨文件的全局变量有关。
重定位符号引用
①重定位pc相对引用
②重定位绝对引用
重定位代码与数据
f.可执行目标文件