重定位PC相对引用(PC-relative reference relocation)

本文通过实例详细介绍了链接过程中的重定位操作,包括重定位条目的各部分含义及重定位PC相对引用的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

 

链接符号引用重定位

简介

我们知道一个.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函数的地址赋值给操作数,但是一般用于外部变量数据的引用中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值