在前面,我们了解了可重定位目标文件的文件格式以及符号链接的过程。
一旦完成了符号解析,它就把代码中的每个符号引用和确定的一个符号定义联系起来。此时,链接器就知道它的输入目标模块中的代码节和数据节的大小。现在就可以开始重定位了。
在重定位的过程中,将合并输入模块,并为每个符号分配运行时地址。
重定位由两步组成
- 重定位段和符号定义
链接器将所有相同类型的段合并为同一类型的新的聚合段。
然后将运行时存储器地址赋给新的聚合段,赋给输入模块定义的每个段,以及赋给输入模块定义的每个符号。
此时程序中的每个指令和全局变量都有唯一的运行时存储器地址。 - 重定位段中的符号引用
链接器修改代码段和数据段中对每个符号的引用,使他们指向正确的运行时地址。
这一步链接器依赖于重定位条目,使得他们指向正确的运行时地址。
首先我们需要了解什么是重定位条目:
重定位条目:当汇编器遇到对最终位置未知的目标引用,它就会生成一个重定位条目,用来告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。
代码的重定位条目放在 .rel.text 中,已初始化数据的重定位条目放在 .rel.data 中。
重定位条目格式
typedef struct{
int offset; // 需要被修改的引用的段偏移
int symbol:24, // 被修改的引用应指向的符号
type:8; // 修改方式
}ELF32_REL;
在 ELF 中定义了 11 种不同的重定位类型。
跟随深入理解计算机系统第七章重定位的讲解
1.R_386_PC32 相对地址引用
当 CPU 执行一条使用 PC 相对寻址的指令时,它将在指令中编码的 32 位值上加上 PC 的当前运行值,从而得到有效地址。
2.R_386_32 绝对地址引用
通过绝对地址引用,CPU 直接使用指令中编码的 32 位值作为有效地址。
重定位符号引用
以深入理解计算机系统第七章的解析为例
重定位算法的伪代码
其中 段运行时地址 ADDR(s),符号运行时地址 ADDR(r.symbol)
foreach section s{
foreach relocation entry r{
refptr = s + r.offset; //段偏移
if(r.type == R_386_PC32){ //间接引用寻址
refaddr = ADDR(s) + r.offset; // 引用运行时地址
*refptr = (unsigned) (ADDR(r.symbol) + *refptr - refaddr);
}
if(r.type == R_386_32){ // 绝对地址引用
*refptr = (unsigned) (ADDR(r.symbol) + *refptr);
}
- 重定位 PC 相对引用
此时我们引用之前的 main.c 函数
main.o 的 .text 段中的 main 程序调用 swap 程序,该程序在 swap.o 中定义
反汇编列表 其中地址以小端字节顺序存储
6: e8 fc ff ff ff call 7<main+7>
7: R_386_PC32 swap
call 指令开始于段偏移 0x6 处,下一行显示的是这个引用的重定位条目,其中
r.offset = 0x7
r.symbol = swap
r.type = R_386_PC32
这些字段告诉连接器修改开始于偏移量 0x7 处的 32 位 PC 相对引用,假设连接器已确定
ADDR(s) = ADDR(.text) = 0x080483b4 - - - 段运行时地址
ADDR(s.symbol) = ADDR(swap) = 0x080483c8 - - - 符号运行时地址
使用前面的算法,链接器首先计算出引用的运行时地址
refaddr = ADDR(s) + r.offset
= 0x080483b4 + 0x7
= 0x080483bb
然后他将引用的当前值修改,使得它在运行时指向 swap 程序
*refptr = (unsigned)(ADDR(r.symbol) + *refptr - refaddr)
= (unsigned)(0x080483c8 + (-4) - 0x080483bb)
= (unsigned)(0x9)
在得到可执行文件中,call 指令有如下重定位形式
0x080483ba: e8 09 00 00 00 call 080483c8 <swap>
在运行时 call 指令存放在地址 0x080483ba 处,当 CPU 执行 call 指令时, PC 的值为 0x080483bf,执行这条指令 CPU 执行:
push PC onto stack
PC <- PC + 0x9 = 0x080483bf + 0x9 = 0x080483c8
此时要执行的下一条指令就i是 swap 程序的第一条指令。
2. 重定位绝对引用直接调用即可,这里不再做演示。