参考:《程序员的自我修养》第4章
链接器:把目标文件按一定的方式链接到一起,形成一个可执行文件。
链接器命令
ld file1.o file2.o -e main -o exc // 将目标文件file1.o,file2.o以main函数作为程序入口,合成可执行文件exc。
注:如果在链接的时候出现 undefined reference to "__stack_chk_fail"的错误,可在编译的时候加上选项 -fno-stack-protector。
即gcc -c -fno-stack-protector file1.cfile2.c
objdump -r file.o // 查看文件的重定位表
ar -t libfile.a // 查看库文件中包含了哪些目标文件
ld -verbose // 查看ld默认链接脚本
1. 链接
通常按照相似段合并的方式来分配空间。该链接过程分为两步:
- 空间和地址分配:扫描所有目标文件,获取各个段的长度、属性和位置,并将输入目标文件中的符号表中的所有符号定义和符号引用收集起来,放到全局符号表。该过程链接器获得了所有目标文件的段长度,将其合并,计算出输出文件中各个段合并后的长度和位置,并建立映射关系。
- 符号解析与重定位:根据上一步收集的段信息、重定位信息,进行符号解析和重定位,调整代码的地址。
2. 空间与地址分配
链接前后地址分配情况:
- VMA:Virtual Memory Address,虚拟地址。在链接前,由于未分配虚拟地址,所以虚拟地址全是0。
- LMA:Load Memory Address,加载地址。
3. 符号解析与重定位
目标文件反汇编
可执行文件反汇编
重定位表:记录了重定位相关信息
- 重定位入口:每个要被重定位的地方。
- 重定位入口偏移:对于可重定位文件而言,表示该重定位入口要修正的位置的第一个字节相对于段的起始偏移。对于可执行或共享文件而言,表示该重定位入口要修正的位置的第一个字节的虚拟地址。
- 重定位入口的类型和符号
4. COMMON块
由于编译器允许不同类型的弱符号存在,但是链接器无法判断各个符号类型是否一致,因此需要COMMON块机制。
- COMMON块机制:当不同的目标文件需要的COMMON块大小不一致时,以最大块为准。
- COMMON块机制是针对弱符号而言的。若其中一个符号为强符号,按照强弱符号规则,则符号所占空间与强符号相同。如果连接过程中有弱符号大小大于强符号,则链接器会报warning。
编译器为什么不直接把未初始化的全局变量当作未初始化的局部静态变量,为它在BSS段分配空间,而是标记为COMMON类型的变量?
- 因为编译器无法确定弱符号的所占空间的大小。
如何将未初始化的全局变量以非COMMON块处理?
- gcc选项 -fno-common
- 使用__attribute__,即__attribute__((nocommon))
5. 静态库链接
静态库,多个目标文件经过压缩打包后形成的文件。
链接器在链接静态库时,是以目标文件为单位。
6. 链接过程控制
链接器默认链接规则是对目标文件进行链接。但对于某些特殊的程序,如操作系统内核、没有操作系统的程序(引导程序Boot Loader,嵌入式程序)等,需要指定输出文件各个段的虚拟地址、段名称、段存放顺序等。
可使用链接脚本控制链接过程。
- ld -T link.script:指定某个脚本为连接脚本。
- ld使用手册