【图片+代码】:GCC 链接过程中的【重定位】过程分析

道哥分享
关注


符号表信息

指令:readelf -s main

黄色矩形框中的SubData属于数据段,长度是 4 个字节,虚拟地址是 0x0804_9138,与段信息中的值是一致的。

红色矩形框中的SubFunc属于代码段,长度是 12 个字节,虚拟地址是 0x0804_80c6。

因为main中的代码段包括 2 部分内容:

1. main.o 中的代码段 main 函数;

2. sub.o 中的代码段 SubFunc 函数;

所以,可执行文件main中的代码段,先存放的是main函数,虚拟地址:0x0804_8094,长度是0x32(50 个字节);

紧接着存放的是SubFunc函数,虚拟地址:0x0804_80c6,长度是0x0c(12 个字节)。

如下图所示:

链接器在第一遍扫描所有的目标文件时,把所有相同类型的段进行合并,安排到相应的虚拟地址,如上图所示。

所谓的安排虚拟地址,就是指定这块内容被加载到虚拟内存的什么地方。当可执行文件被执行的时候,加载器就把每一块内容复制到虚拟内存相应的地址处。

同时,链接器还会建立一个全局符号表,把每一个目标文件中的符号信息都复制到这个全局符号表中。

对于我们的实例程序,全局符号表中包括:

SubData: 属于 sub.o 文件,数据段,安排在虚拟地址 0x0804_9138;

SubFunc: 属于 sub.o 文件,代码段,安排在虚拟地址 0x0804_80c6;

其它符号信息...

绝对地址重定位

然后,链接器第二遍扫描所有的目标文件,检查哪些目标文件中的符号需要进行重定位。

对于我们的示例程序,首先来看一下main.o中使用的外部变量SubData的重定位。

从main.o的重定位表中可知:SubData符号需要进行重定位,需要把这个符号在执行时刻的绝对寻址(虚拟地址),写入到 main可执行文件中代码段中偏移0x12字节处。

也就是说需要解决 2 个问题:

1. 需要计算出在执行文件 main 中的什么位置来填写绝对地址(虚拟地址);

2. 填写的绝对地址(虚拟地址)的值是多少;

首先来解决第一个问题。

从可执行文件的段表中可以看出:目标文件main.o和sub.o中的代码段被存放到可执行文件main中代码段的开始位置,先放main.o代码段,再放sub.o代码段。

代码段的开始地址距离文件开始的偏移量是0x94,再加上偏移量0x12,结果就是0xa6。

也就是说:需要在main文件中偏移0xa6处填入SubData在执行时刻的绝对地址(虚拟地址)。

再来解决第二个问题。

链接器从全局符号表中发现:SubData符号属于sub.o文件,已经被安排在虚拟地址0x0804_9138处,因此只需要把0x0804_9138填写到可执行文件main中偏移0xa6的地方。

我们来读取main文件,验证一下这个位置处的虚拟地址是否正确:

指令:od -Ax -t x1 -j 166 -N 4 main

-Ax: 显示地址的时候,用十六进制来表示。如果使用 -Ad,意思就是用十进制来显示地址;

-t -x1: 显示字节码内容的时候,使用十六进制(x),每次显示一个字节(1);

-j 166: 跨过 166 个字节(十六进制 0xa6);

-N 4:只需要读取 4 个字节;

注意:显示的是小端格式。

相对地址重定位

从上面描述的重定位表中看出:main.o代码段中的SubFunc符号也需要重定位,而且是相对寻址。

链接器需要把SunFunc符号在执行时刻的绝对地址(虚拟地址),减去call指令的下一条指令(PC 寄存器) 之后的差值,填写到执行文件main中的main.o代码段偏移0x1b的地方。

同样的道理,需要解决 2 个问题:

1. 需要计算出在执行文件 main 中的什么位置来填写相对地址;

2. 填写的相对地址的值是多少;

首先来解决第一个问题。

从main.o的重定位表中可知:需要修正的位置距离main.o中代码段的偏移量是0x1b字节。

可执行文件main中代码段的开始地址距离文件开始的偏移量是0x94,再加上偏移量0x1b就是0xaf。

也就是说:需要在main文件中0xaf偏移处填入一个相对地址,这个相对地址的值就是SubFunc在执行时刻的绝对地址(虚拟地址)、距离call指令的下一条指令的偏移量。

再来解决第二个问题。

链接器在第一遍扫描的时候,已经把sub.o中的符号SubFunc记录到全局符号表中了,知道SubFunc函数被安排在虚拟地址0x0804_80c6的地方。

但是不能把这个绝对地址直接填写进去,因为 call 指令需要的是相对地址(偏移地址)。

链接器把main代码段起始位置安排在 0x0804_8094,那么偏移0x1b处的虚拟地址就是:0x0804_80af,然后还需要再跨过4个字节(因为执行call指令时,PC的值自动增加到下一条指令的开始地址)才是此刻PC寄存器的值,即:0x0804_80b3,如下图中红色部分:

两个虚拟地址都知道了,计算一下差值就可以了:0x0804_80c6 - 0x0804_80b3 = 0x13。

也就是说:在可执行文件main中偏移为0xaf的地方,填入相对地址0x0000_0013就完成了SubFunc符号的重定位。

还是用od指令来读取main文件的内容来验证一下:

指令:od -Ax -t x1 -j 175 -N 4 main

总结

经过以上两个重定位操作,main.c中使用的两个外部符号就解决了地址重定位问题。

再来看一下可执行文件main的反汇编代码:

从黄色和红色的矩形框可以看出,二进制指令中的地址值与上面的分析是一致的。

以上就是静态链接过程中地址重定位的基本过程,与动态链接相比,静态链接还是相对简单很多。

以后有机会的话,我们再继续聊一下动态链接中的一些操作,谢谢!

       原文标题 : 【图片+代码】:GCC 链接过程中的【重定位】过程分析

声明: 本文由入驻OFweek维科号的作者撰写,观点仅代表作者本人,不代表OFweek立场。如有侵权或其他问题,请联系举报。
侵权投诉

下载OFweek,一手掌握高科技全行业资讯

还不是OFweek会员,马上注册
打开app,查看更多精彩资讯 >
  • 长按识别二维码
  • 进入OFweek阅读全文
长按图片进行保存