静态链接
想要理解静态链接这一过程,首先我们需要明白编译器驱动程序,它代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。
示例程序:
//main.c
int sum(int *a, int n);
int array[2] = {1, 2};
int main(){
int val = sum(array, 2);
return val;
}
//sum.c
int sum(int *a, int n){
int i, s = 0;
for(i=0; i<n; i++){
s += a[i];
}
return s;
}
当我们编辑完源代码需要运行时,大致过程如下图所示:
其中,标红的地方就是这篇文章要讲述的过程。
像Linux LD程序这样的静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可以加载和运行的可执行目标文件作为输出。
为了构造可执行文件,链接器必须完成两个主要任务:符号解析和重定位。
符号解析
目标文件中,会定义与引用符号。
符号包括:函数、全局变量、静态变量(在C语言中任何以static属性声明的变量)
注意:局部变量不属于符号。
符号解析的目的是将每个符号引用正好和一个符号定义关联起来。
重定位
编译器和汇编器生成从地址0开始的代码和数据节。
链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节。
然后修改所有对这些符号的引用,使它们准确指向定义的内存位置。
(简单来说,就是对符号定义进行重新定位,再修改符号引用指向的地址)
链接器使用汇编器产生的重定位条目的详细指令,不加甄别地执行这样的重定位。
重定位类型
当汇编器遇到对最终位置未知的目标引用时,会生成一个重定位条目:其目的是告诉链接器在将目标文件合并成可执行文件时应该如何修改这个引用。
ELF重定位条目的格式如下(offset、type、symbol、addend):
typedef struct{
long offset; //需要被修改的引用的偏移量,也就是入口点偏移量
long type:32, //声明重定位的类型(相对地址引用or绝对地址引用)
symbol:32; //标识被修改的符号,(例如sum函数,这里就是sum)
long addend; //有符号常数,有时候需要用该值做重定位偏移调整
}Elf64_Rela;
其中最基本的两种类型(type):
R_X86_64_PC32: 重定位一个使用32位PC相对地址的引用。将指令中编码的32位值加上PC的当前运行值作为有效地址。(PC值通常是下一条指令在内存中的地址)
R_X86_64_32: 重定位一个使用32位绝对地址的引用。CPU直接使用在指令中编码的32位值作为有效地址。
重定位符号引用
两种重定位算法如下:
foreach section s {
foreach relocation entry r {
refptr = s + r.offset; //s是每个节,r是相关联重定位条目
//相对寻址
if (r.type == R_X86_64_PC32) {
refaddr = ADDR(s) + r.offset;
//计算引用的运行时的地址。如果写题,题目会给出ADDR(s)
*refptr = (unsigned)(ADDR(r.symbol) + r.addend - refaddr);
//refaddr = ADDR(s) + r.offset
//*refptr是指令中编码的32位值(重定位引用值)。
//后续还需读出PC值。
//有效地址为:下一条指令地址(PC值)+ *refptr
}
//绝对寻址
if (r.type == R_X86_64_32){
*refptr = (unsigned)(ADDR(r.symbol) + r.addend);
//*refptr是指令中编码的32位值(重定位引用值),即有效地址。
}
}
}
接下来我们用以下示例图(反汇编代码)来演示相对寻址与绝对寻址。
已知:
ADDR(s) = 0x4004d0;
ADDR(sum) = 0x400e8;
ADDR(array) = 0x601018;
这里main函数引用了两个全局符号:array和sum。从图中我们可以得知:
array的type是R_X86_64_32(绝对寻址)
sum的type是R_X86_64_PC32(相对寻址)
重定位PC相对引用
第一步,我们需要列出上述示例中,sum的重定位条目:
从题目图中可以得知以下信息:
所以sum的重定位条目为:
r.offset = 0xf
r.symbol = sum
r.type = R_X86_64_PC32
r.addend = -0x4
且题目已告知:
ADDR(s) = 0x4004d0; ADDR(sum) = 0x400e8;
第二步,根据公式可做如下计算:
refaddr = ADDR(s) + r.offset
= 0x4004d0 + 0xf
= 0x4004df
*refptr = (unsigned)(ADDR(r.symbol) + r.addend - refaddr)
= 0x4004e8 + (-0x4) - 0x4004df
= 0x5
得到重定位引用值:0x5
重定位绝对引用
第一步,我们需要列出上述示例中,sum的重定位条目:
从题目图中可以得知以下信息:
所以array的重定位条目为:
r.offset = 0xa
r.symbol = array
r.type = R_X86_64_32
r.addend = 0
且题目已告知:
ADDR(array) = 0x601018;
第二步,根据公式可做如下计算:
*refptr = (unsigned)(ADDR(r.symbol) + r.addend)
= 0x601018 + 0
= 0x4601018
得到重定位引用值:0x4601018(同时也是有效地址)