前言
我们终于走到了链接这一步,对于链接这一步,它是将多个输入目标文件链接后输出一个可执行文件。我们拿两个程序a.c和b.c来举例说明链接的过程。
a.c:
/* a.c */
extern int shared;
int main(){
int a = 100;
swap(&a,&shared);
}
b.c:
/* b.c */
int shared = 1;
void swap(int* a,int* b){
*a ^= *b ^= *a ^= *b;//这是位操作符异或, 二进制的数学运算。这是一种不需要临时变量就可以交换ab的方法
}
使用gcc将上述两个程序编译成目标文件a.o,b.o:
gcc -c a.c b.c
我们首先要关注的是链接后的输出文件的空间与地址分配问题。
1.按序叠加
一个最简单的方案就是将输入的目标文件按照次序叠加起来,如下图所示:
按序叠加的优点是做法很简单,直接将各个目标文件依次合并;缺点是在有很多输入文件的情况下,输出文件将会有很多零散的段,每个段都需要有一定的地址和空间对齐要求,会造成内存空间大量的内部碎片,非常浪费空间。
相似段合并
一个更实际的方法是将相同性质的段合并到一起,将所有输入文件的“.text”合并到输出文件的“.text”段,接着是“.data”段、“.bss”段等,如下图所示:
需要注意的是,“.bss”段在目标文件和可执行文件中并不占用文件的空间,但是它在装载时占用地址空间。所以链接器在合并各个段的同时,也将“.bss”合并,并且分配虚拟空间。现在的链接器空间分配的策略基本上都采用上述方法中的第二种,使用这种方法的链接器一般都采用一种叫两步链接(Two-pass Linking) 的方法。整个链接过程分两步。
第一步空间与地址分配: 扫描所有的输入目标文件,并且获得它们的各个段的长度、属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个全局符号表。这一步中,链接器将能够获得所有输入目标文件的段长度,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。
第二步符号解析与重定位: 使用上面第一步中收集到的所有信息,读取输入文件中段的数据、重定位信息,并且进行符号解析与重定位、调整代码中的地址等。事实上第二步是链接过程的核心,特别是重定位过程。这一步我们下一篇文章会详细介绍。
空间与地址分配
使用ld链接器将a.o和b.o链接起来:
ld a.o b.o -e main -o ab
-e main表示将main函数作为程序入口,ld链接器默认的程序入口为_start。
-o ab表示链接输出文件名为ab,默认为a.out。
使用objdump来查看链接前后地址的分配情况:
链接前后的程序中所使用的地址已经是程序在进程中的虚拟地址,即我们关心上面各个段中的VMA (Virtual Memory Address) 和Size,而忽略文件偏移(File off)。在链接之前,目标文件中的所有段的VMA都是0,因为虚拟空间还没有被分配,所以它们默认都为0。等到链接之后,可执行文件“ab”中的各个段都被分配到了相应的虚拟地址。“.text”段被分配到了地址0x08048094,大小为0x72字节,为a.o和b.o的“.text”大小之和;“.data”段从地址0x08049108开始,大小为4字节,为a.o和b.o的“.data”大小之和。整个链接过程前后,目标文件各段的分配、程序虚拟地址如下图所示:
可以看到链接器要将可执行文件“ab”的“.text”分配到0x08048094、将“.data”分配0x08049108。这涉及操作系统的进程虚拟地址空间的分配规则,在Linux 下,ELF可执行文件默认从地址0x08048000开始分配。
符号地址的确定
当空间地址分配完成后,链接器开始计算各个符号的虚拟地址,这时“main”、“shared”和“swap”的地址也已经是确定的了,链接器需要给每个符号加上一个偏移量,使它们能够调整到正确的虚拟地址。从前面“objdump”的输出看到,“main”位于“a.o”的“.text”段的最开始,也就是偏移为0,所以“main”这个符号在最终的输出文件中的地址应该是0x08048094+ 0,即0x08048094;而“swap”这个符号位于b.o的“.text”开始,偏移量也就是“a.o”的“.text”的大小0x00000034,所以“swap”这个符号在最终的输出文件中的地址应该是0x08048094+ 0x00000034,即0x080480c8;shared位于“.data”段的最开始,偏移量为0,所以“swap”这个符号在最终的输出文件中的地址应该是0x08048108+ 0,即0x08048108。所以链接器在更新全局符号表的符号地址以后,各个符号的最终地址如图所示: