在静态链接过程中主要发生了两件事。一是空间与地址分配,链接器扫描所有输入文件的段,合并相似段并且重新计算段长度和在虚拟内存中的映射关系,收集所有的符号放到全局符号表中。二是符号解析与重定位,链接器收集所有的段信息和重定位信息并且进行符号解析和重定位、调整代码中的地址等。
1. 空间与地址分配
我们用一个简单的例子来分析一下静态链接过程中的空间与地址分配情况。
/* 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;
}
gcc -c a.c b.c #获取a.o 和 b.o
ld a.o b.o -e main -o ab -lc #将a.o b.o链接生成ab
输入目标文件的同名段将会被合并
a.o的段结构--------------->
b.o 的段结构------------>
ab的段结构---------------->
对比一下a.o b.o和ab的.text .data段size,我们可以发现ab文件的段是a.o文件和b.o文件同名段的组合。
字段\文件 | a.o | b.o | ab |
---|---|---|---|
.text | size: 0x00000058 | sze: 0x0000004f | size: 0x000000a7 |
.data | size: 0x00000000 | size: 0x00000004 | size: 0x00000004 |
链接后文件才会有VMA
观察上述a.o b.o 和 ab 文件.text段的VMA地址可以发现,a.o和b.o文件中的.text的VMA地址都是0,只有ab文件的.text段的VMA地址不是0而是0x00401030。为什么是0x00401030?正常ELF可执行文件会被加载到0x400000开始的内存中,最开始加载的是程序头等段,然后才是代码数据段。
确定无需重定位的符号地址
在目标文件中,无需重定位的符号地址都是相对与段地址的偏移。那么扫描所有输入文件符号表的时候,会为所有的符号偏移加上重新计算出的段虚拟地址就是符号的新的虚拟地址。例如,上面的例子中a.o文件中main函数相对.text段的偏移是X,而经过计算后.text的虚拟地址是0x401030,那么main函数符号的虚拟地址就是0x401030+X。
2. 符号解析与重定位
链接器首先会扫描所有目标文件的符号表,将所有符号进行虚拟地址换算。然后扫描所有的重定位表,根据重定位表中的符号项名称在符号表中找到,再根据对应符号表中符号项的地址定位到需要替换的符号位置进行符号地址替换。