编译和连接-静态连接
静态链接是将两个文件(他们之间存在引用)合并为一个文件,并将他们的引用地址进行重定位。主要分为两步:1.空间与地址分配。2.符号解析和重定位。
我们将源代码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){
int t=*a;
*a=*b;
*b=t;
}
编译后获得目标文件a.o和b.o,连接是将这两个目标文件作为输入,生成最终的可执行文件。
空间和地址分配
我们已经介绍了目标文件的格式,那么,如何将多个目标文件声称可执行文件呢,当前的方法是将相同性质的段合并到一起。如下图所示。
对于空间分配,其实有两个含义:一是在输出的可执行文件中的空间。二是装载后的虚拟地址中的虚拟地址空间。对于有实际数据的段如.text和.data,文件中和虚拟地址中都要分配空间;而对于.bbs这种段,文件中没有内容,分配空间的意义在于虚拟地址空间。实际上,我们谈到的空间分配只关注与虚拟地址空间的分配,因为这关系到后面的连接器的地址计算过程。
综上所述,这一步的功能和步骤如下:扫描所有的输入目标文件,获得他们每个段的长度、属性和位置,并将输入目标文件中的符号表中的所有符号定义和符号引用收集起来,统一放到一个全局符号表。这一步连接器合并所有输入文件的段,并计算输出文件中各段合并后的长度和位置。
简单地说,就是:基于多个输入的目标文件生成一个新的ELF格式可执行文件,要求该文件符合语义要求。
连接器所使用的地址已经是程序在进程中的虚拟地址(用于重定位),而忽略了文件偏移。连接生成的执行文件ab各个段被分配到相应的虚拟地址,如上图.text段分配到0X08048094,大小为0X72字节。.data段地址为0x08049108,大小为4字节。如下图所示。具体的分配地址策略和操作系统有关,如Linux下ELF可执行文件默认从地址0x08048000可是分配。
在确定了每个段的的虚拟地址之后,各个符号的位置就固定了,如main、shared、swap等,只要将他们相对于段的偏移量加上段的起始位置即可。
符号解析和重定位
在编译完成后,连接开始前,在目标文件a.o中虽然有引用了shared和swap,但是它不知道它们具体的实际地址,因此就先用未定义数字填充这些地址,如使用0看作变量shared的地址,将-4看为函数swap的地址,这只是一个临时地址。
在连接以后的ab文件中,shared地址和swap的地址都已经确定,因此链接将这些临时地址替换为真实的实际地址。
在目标文件中,有一个重定位表的结构来保存那些需要重定位的符号,每一个段中如果有需要重定位的符号都会有一个重定位表段,如.tetx段中存在需要重定位的信息,就会有一个.rel.text的段保存重定位表。相应的,.data对应.rel.data。
重定位表中存放两个信息:偏移量和信息。偏移量表示重定位的入口再被重定位的段中的位置,而信息存放重定位的类型和符号。