程序员的自我修养(3)

静态链接

上节是讲述目标文件的构成与格式,即elf文件格式的描述。接下来的问题是我们如何将这些文件链接起来,成为一个可执行文件?这个过程发生了什么。
以以下a.c与b.c作为例子展开分析
a.c

extern int shared;
int main()
{
	int a = 100;
	swap(&a,&shared);
}

b.c

int shared = 1;
void swap(int *a,int *b)
{
	*a ^= *b ^= *a ^= *b;
}

假设程序只有这两个模块a.c与b.c(因为程序的运行,需要很多的模块支持,链接器已经省略)
生成a.o与b.o,即目标文件,

gcc -c a.c b.c

这个程序目的很明显,a.c使用了b.c的shared变量和swap函数,swap函数是进行两数交换。
1.空间地址的分配
因为可执行文件同样是为elf文件,故如何将多个.o文件的段合成为一个可执行文件的对应段,或者说输出的可执行文件的空间如何分配,接下来有两种方法,按序叠加和相似段合并。
2.按序叠加
最简单的方法,将输入的目标文件按照次序叠加起来。
如下

object file A                       
File Header                 File Header    
.text						.text
.data						.data
.bss						.bss
                    -.text  
object file B				.data
File Header					.bss
.text						
.data
.bss

很简单,就是直接将各个目标文件依次合并,但是这样做会导致会有很多个.text和.data等段,会很浪费看空间,因为每一个段都需要一定大小的地址和对齐的要求,那么对齐太多,肯定会产生很多空间碎片,这并不是一个很好的方案。
3.相似段合并
一个更实际的方法是将相同性质的段合并到一起,比如将所有的输入文件的.text合并到一个段中,接着是.data,.bss段。现在的链接器空间分配基本使用这种,使用这种方法一般采用二步链接的方法,即整个链接的过程分为两步。
4.相似段合并的两步链接法
第一步 空间与地址分配
扫描所有的输入目标文件,获取各个段的长度,属性和位置。并且将输入的目标文件的符号表中所有的符号定义与符号引用收集,统一放入一个全局符号表。获得的段长度,将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。
第二部 符号解析与重定位
使用上面一步收集到的所有信息,读取输入文件中段的数据,,重定位信息,并且进行符号解析与重定位,调整代码中的地址,这一步是程序的和核心,特别是重定位过程。
下列代码进行链接
-e表示以main函数作为程序入口,通常程序是以_start作为程序入口
-o表示更改目标执行文件名字为ab,默认a.out

ld a.o b.o -e main -o ab

5.链接前后各个段属性
使用objdump工具查询

objdump -h 目标文件

a.o
程序员的自我修养(3)_第1张图片

b.o
程序员的自我修养(3)_第2张图片
ab
程序员的自我修养(3)_第3张图片
VAM表示虚拟地址,由上面可以知道,程序没有链接前.o文件VAM地址都为0,代表没有分配,链接过后VAM地址都存在了,.text被分配到了0x400405,大小为0x0202
6.符号地址的确定
在第一步链接过程中,虚拟地址就已经确定了,比如.text段的起始位置是0x400405,.data是0x601028。完成这一步后,链接器开始计算各个符号的虚拟地址,因为各个符号在段中的相对地址是确定的,故我们直接使用对应段的虚拟地址起始加上相对地址即可求出对应符号的虚拟地址。
就是假设main函数在a.o的偏移量是X
那么合并后的虚拟地址就位0x400405+X
7.符号解析与重定位
在符号地址确认后,链接器就进入了符号解析与重定位步骤。这也是链接的核心内容,a.c为什么要与b.c链接,是因为要使用b.c的shared变量和swap函数,链接器主要就是执行这个重要过程。
反汇编a.o可以看出,swap函数与shared函数的地址是被赋值为0的,就是没有找到,而ab可执行文件是存在地址的。
反汇编命令

objdump -d 目标文件

那么输出的可执行文件能够获取到正确的地址,因为在目标文件.o中存在重定位表,我们在前一节提到过,它会记录每个段中需要重定位的的地方,.text段存放在.rel.text段中,.data段存放在.rel.data中。
观察要重定位的符号命令

objdump -r 目标文件

程序员的自我修养(3)_第4张图片
明显看出shared变量与swap函数是需要重定位的,确认了要重定位的符号,如何重定位,之前不是说了吗,第一步链接的时候会生成一个全局符号表,而且已经更新了虚拟地址,从该表就能找到相对应的符号进行重定位。

你可能感兴趣的:(程序员的自我修养,底层原理)