引言
上篇文章,我们详细介绍了从源码到可执行程序的全过程,并详细介绍了目标文件的存储格式,ELF格式。这篇文章我们介绍静态链接的全过程,
一、多个目标文件如何合并
多个目标文件在链接过程中,采用相似段合并的方法。比如将所有目标文件的“.text”段合并到输出文件的“.text”段。
二、链接步骤
- 扫描所有的输入目标文件,拿到它们各个段的位置、长度和属性;将各个目标文件中的符号表中的符号定义和符号引用统一放到一个全局符号表中。将所有的段合并,重新计算各个段的位置和长度,并建立映射关系。
- 进行符号解析与重定位、调整代码中的地址。
三、举例说明链接的全过程
准备a.c 和 b.c 两个文件
a.c
#include <stdio.h> extern int shared; int main() { int a = 100; swap(&a, &shared); return 0; }
b.c
int shared = 1; void swap(int* a ,int* b){ *a ^= *b ^= *a ^= *b; }
gcc -c a.c b.c // 只编译不链接,生成a.o b.o gcc a.o b.o -o ab // 链接生成可执行文件 ab
图中的VMA就是虚拟地址,我们关注VMA和Size。可以看到在链接前,目标文件中的VMA都是0,因为还没有分配虚拟空间,默认为0。在链接后,我们使用objdump -h查看ab文件,“.text”段被分配到0x004000e8,大小是0x77字节,“.data”段被分配到0x00601000,大小是0x04字节。链接后的文件及程序虚拟地址如下图所示:
3.1 符号地址如何确定
经过上一步,输入目标文件的各个段在链接后的虚拟地址就确定了,“.text”段的起始地址为0x004000e8。有了起始地址,各个符号的虚拟地址通过偏移可以计算出来。
四、符号解析与重定位
因为a.c的中变量shared和函数swap都不在本文件中,所以这两个符号就需要重定位。在编译a.o目标文件的时候,因为编译器不知道这两个符号的地址,会先用一个虚假值代替,在链接的时候通过重定位进行地址修正。
重定位表
链接器就通过重定位表知道哪些符号需要重定位。每个需要重定位的段,都会有一个对应的重定位表。比如: “rela.text” 就是针对 “.text”段的重定位表,这是因为“.text”段中的“swap” 函数的调用和shared变量的引用;
可以通过:objdump -r 查看目标文件的重定位表。
每个需要重定位的地方都有一个重定位入口,从图中我们可以看到a.o有两个重定位的入口。“RELOCATION RECORDS FOR [.text]” 表示这个重定位表是代码段的重定位表。图中的OFFSET 0x14 和 0x21 分别对应通过a.o的反汇编结果中需要重定位的地方。
符号解析
如果只连接a.o,链接器就会找不到shared和sawp的定义而报错:undefined reference to “shared”和“swap”。就是链接时符号未定义。
链接时,链接器通过查找重定位表发现shared和swap符号需要重定位,就会查找所有目标文件建立的全局符号表,若是没找到就报未定义错误。
文章参考于<零声教育>的C/C++linux服务期高级架构,及书籍(程序员的自我修养)。