目录
链接符号引用重定位
简介
我们知道一个.c文件可以被编译为.o文件,即目标文件,而假如一个.c中引用了别的.c中的函数或者是变量,这时候的.o其实是不知道引用函数实际的内存位置的,也就无法跳转,这就需要【重定位】的操作了,而针对函数名(也是符号)的重定位
例子
我们编写两个.c文件,分别是main.c和func.c,main.c引用了func.c中的func函数
// main.c
int func(int x, int y);
int main()
{
int c = func(1, 2);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
// func.c
int func(int x, int y)
{
return 2*x + y;
}
- 1
- 2
- 3
- 4
- 5
我们使用gcc来编译出两个.o文件,使用如下命令将会生成main.o与func.o
gcc -c main.c func.c
- 1
然后我们对main.o与func.o
反汇编,使用如下命令可以生成main_dump.txt与func_dump.txt
的反汇编文件
objdump -d main.o > main_dump.txt
objdump -d func.o > func_dump.txt
- 1
- 2
因为main.c调用func.c而func.c并未调用其他函数,所以我们查看main.o反汇编结果,如下图蓝色区域,因为在链接之前每个.c相对是独立的,并不知道如何跳转到其他文件的函数,所以无法正确的写成跳转指令
但是我们将两个.o文件进一步编译成可执行文件,并查看其反汇编。执行以下命令做进一步的编译,将main.o和func.o链接进而生成exe可执行的二进制文件
gcc -o exe main.o func.o
- 1
如图
我们对exe反汇编,得到exe的汇编代码
objdump -d exe > exe_dump.txt
- 1
发现可以正确的跳转到func所指定的地址,这是我们想要的,也是链接中重定位所做的功劳
重定位条目
简介
----引用自《深入理解计算机系统》
但是书上讲的很抽象,而且没有例子,接下来简单解释以下这些条目中各个符号的意义
offset
因为在链接之前,每个.c相当于一个独立的部分,我们称之为【节】,而offset表明这个符号引用相对于【节】起始处的偏移(其实就是相对地址)
值得注意的是,这个offset指向的是callq指令中需要被修改的操作数,而不是callq指令本身
如图 main.c 的节
type
是何种重定位方式,因为编译的时候可以有很多种重定位方式,但是接下来只讨论书上描述的比较难懂的【重定位PC相对引用】
symbol
即这个引用指向的是哪个符号,比如main.c引用func函数那么就指向func函数
addend
在PC+偏移跳转计算实际地址时用到的计算偏移,待会会讲解
重定位PC相对引用
这里是书上讲的比较晦涩的地方,书上直接给出了公式
这个公式很难懂,但是我们可以翻译一下。仍然以main.c引用func.c为例子
符号 | 意义 |
---|---|
ADDR(s) | main节在实际的可执行文件中,位于内存的实际地址 |
ADDR(r.symbol) | func函数在实际可执行文件中,位于内存的实际地址 |
r.offset | 上文提到的offset,计算出需要修改的引用的操作数相对main节的偏移 |
r.addend | 上文提到的addend,计算实际callq语句操作数时需要用到的计算偏移 |
结合exe可执行文件的反汇编代码,我们进一步理解:
现在再来看这个公式,其中offset为0x13,根据上面offset定义推得
refaddr = ADDR(s) + r.offset
要修改的引用的实际地址 = main函数实际地址 + 要修改的引用的相对地址
0x60d = 0x5fa + 0x13
- 1
- 2
- 3
addend是由命令查看精灵 ELF readelf -r main.o
查询得的
*refptr = ADDR(r.symbol) + r.addend - refaddr
修改后的内容x = func函数实际地址 + 计算偏移 - 要修改的引用的实际地址
0xa = 0x61b + -0x4 - 0x60d
注:
因为是【PC+偏移】寻址,修改后的内容x就是call的操作数
要想使跳转到func函数,就需要【PC+x = func函数实际地址】
PC + 0xa = 0x611 + 0xa = 0x61b 确实是func函数的地址,达到效果
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
注:此处r.addend=-4,是因为要修改的操作数地址(refaddr)和该操作数所在指令的下一条指令地址(即PC)相差4字节。因为操作数是4字节的,填充操作数之后,才是下一条指令的地址(PC),所以做差之后,要补偿操作数占用的4字节地址回去。
换句话说,call的操作数有 x 字节,r.addend的就等于 -x
可以看到编译后的结果,也就是链接的时候修改后的结果,确实是0xa(小端表示牢记于心)
附:图解,以main函数调用sum函数为例
现在再看书上的公式是否变简单了?
重定位PC绝对引用
这个简单,没有那么多做差和偏移,直接将实际的func函数的地址赋值给操作数,但是一般用于外部变量数据的引用中