GCC的编译链接过程包括:
1. C预处理器(cpp),处理所有的宏定义,生成文本文件.i
2. C编译器(cc1),生成汇编语言文件.s
3. 汇编器(as)生成可重定位的目标文件.o
4. 链接器(ld)把所有的.o和库组合成一个可执行的目标文件
a) 符号解析:将每个符号的引用和一个符号定义联系起来
b) 重定位:编译(cc1和as)生成的代码(.text)和数据(.data)节的起始地址是0,需要把每个符号的定义和一个存储器位置联系起来,然后修改所有对这些符号的引用。
典型的ELF包含以下几个节:
1. .text:指令
2. .rodata:只读数据,例如printf的格式串
3. .data:已初始化的全局变量
4. .bss:未初始化的全局变量
5. .symtab:符号表,每个可重定位目标文件都有,不包含局部变量(和-g编译选项区别)
6. .rel.text:.text节的重定位信息,所有引用外部函数或全局变量的指令都需要修改
7. .rel.data:.data节的重定位信息,例如已初始化全局变量的值是全局变量或外部定义函数的地址
8. .strtab:以null结尾的字符串列表,包含.symtab和.debug等节的字符串信息(符号名字)
每个可重定位目标模块m都有一个符号表,包含:
1. 由m定义并能被其他模块引用的全局符号,非静态的函数和非静态的全局变量
2. 由其他模块定义并被模块m引用的全局符号,也就是外部符号
3. 只被模块m定义和引用的本地符号,静态的函数和静态的全局变量
符号表条目定义:
typedef struct {
int name;
int value;
int size;
char type:4,
binding:4;
char reserved;
char section;
} Elf_Symbol;
1. name:是.strtab表中的偏移量,指向符号的名字
2. value:对于可重定位模块,value是距离定义该符号的节的起始位置的偏移;对于可执行文件,该值是一个绝对运行时地址
3. size:目标的大小
4. type:函数或者数据
5. binding:全局或者本地
6. section:定义该符号的节的索引(节头表)。有3个伪节:
a) ABS:不能被重定位的符号
b) UNDEF:未定义的符号
c) COMMON:未被分配位置的未初始化的数据目标,value给出对齐的要求。最终链接时将作为一个.bss目标分配
静态库(.a)
1. 所有的标准C函数放在一个单独的可重定位目标模块中(libc.o),缺点是:每个可执行程序都包含libc.o的完整拷贝;对标准函数的任何改变都需要重新编译整个源文件。
2. 为每个标准函数创建分离的可重定位文件(printf.o strcpy.o…),缺点是:要求程序员显式的指定所用到的所有目标文件。
把各个独立的目标模块封装成一个单独的静态库文件,在链接时,链接器只拷贝被引用的目标模块。
Linux的静态库是由ar工具创建的一组可重定位目标文件的集合。
ar rcs out.a in1.o in2.o
假设链接器定义了3个集合:
1. 可重定位目标文件的集合E
2. 未解析符号的集合U
3. 前面输入文件中已定义符号的集合D
初始的E、U和D都是空。
链接器的规则:
1. 命令行输入的文件f是目标文件,把f添加到E,修改U和D来反映f中的符号定义和引用
2. 命令行输入的文件f是静态库
a) 如果其中的模块m,定义了一个符号解析U中的一个引用,把m加入E,修改U和D来反映m中的符号定义和引用
b) 静态库中的每个模块都重复动作a,直到U和D不再变化为止。
c) 没被包含进E的所有静态库的模块被丢弃
3. 最终,假如U非空,链接器报错。否则合并和重定位E中所有文件,生成可执行文件。
可推导出2个结论:
1. 构建静态库时的目标文件顺序不重要
2. 链接时的库和目标文件之间的顺序很重要,假如定义一个符号的库出现在引用这个符号的目标文件之前,那么引用就不能被解析,链接失败。
链接器完成符号解析后进行重定位过程:
1. 重定位节和符号定义:链接器将所有同类型的节合并,把运行时地址赋给新的聚合节,同时修改符号表,把输入模块定义的每个节和每个符号的地址都被更新。
2. 重定位节中的符号引用:根据重定位表目修改代码节和数据节中对每个符号的引用,指向正确的运行时地址。
汇编器遇到对最终位置未知的目标引用,就生成一个重定位表目,告诉链接器在合并生成可执行文件时如何修改这个引用。
重定位表目定义:
typedef {
int offset;
int symbol:24,
type:8;
}Elf32_Rel;
1. offset:需要被修改的引用的节偏移
2. symbol:被修改引用所指向的符号
3. type:如何修改
a) R_386_PC32:使用32位PC相关的地址引用
b) R_386_32:使用32位绝对地址的引用