深入理解计算机系统篇之链接(4):重定位
1.摘要
本文讲解链接过程中最核心的步骤之一:重定位。介绍重定位的工作原理。
2.重定位的主要工作
链接器完成符号解析后,就把符号引用与符号定义关联起来了。然后就可以开始重定位的步骤了,重定位的目的就是==确定每个符号定义的运行时内存地址,然后修改符号引用,使之指向符号定义的运行时内存地址==。
重定位由两步组成:
- 重定位节与符号定义:首先链接器会将输入文件的所有相同类型的节聚合在一起,形成新的聚合节。然后链接器为新的聚合节赋予运行时内存地址,以及给输入模块定义的每个节,定义的每个符号都赋予内存地址。
- 重定位节中的符号引用:然后链接器修改代码节和数据节中的符号引用,使得它们指向正确的运行时内存地址。
第二步的执行链接器依赖于可重定位目标文件中的重定位条目,接下来介绍一下重定位条目这个数据结构。
3.重定位条目
首先查看一下可重定位目标文件中的重定位信息:
% greadelf -r main.o
Relocation section '.rel.text' at offset 0x22c contains 4 entries:
Offset Info Type Sym.Value Sym. Name
00000024 00000d1c R_ARM_CALL 00000000 add
00000034 00000e1c R_ARM_CALL 00000000 subtract
00000058 00000028 R_ARM_V4BX
0000005c 00000b02 R_ARM_ABS32 00000000 global_sum
Relocation section '.rel.data' at offset 0x24c contains 1 entry:
Offset Info Type Sym.Value Sym. Name
00000000 00000b02 R_ARM_ABS32 00000000 global_sum
介绍一下各字段的含义:
| 字段 | 含义 | 备注 |
|---|---|---|
| Offset | 需要被修改的引⽤的节偏移 | 对于可重定位目标文件,该字段表示需要修改的符号引用的起始位置在目标 section 中的偏移量(字节) |
| Info | 表示符号表索引和重定位类型(Type+Symbol) | 符号索引表示需要修改的符号在.symtab里的索引。Sym.Value与Sym. Name只是将符号的信息打印在控制台重定位类型指示链接器如何修改该符号引用的值。 |
| Addend | 表示一个有符号常数,一些重定位类型要使用它对被修改符号引用的值做偏移调整。 | 重定位条目Rel和Rela之间的唯一区别:Rel中没有Addend字段。 |
有了重定位条目,链接器就可以根据它对一些符号引用的地方进行重定位了。比如.rel.text中可以定位到从text字段偏移24字节后就是需要修改的add的地方,然后链接器可以去查找已经解析完成的全局符号表,并根据之前合并节区时赋予的地址来确定其运行时内存地址。
4.重定位符号引用
介绍两种嵌入式常见类型的重定位符号引用的算法:R_ARM_CALL与R_ARM_ABS32。R_ARM_CALL类似于相对PC地址,R_ARM_ABS32是绝对地址。
绝对地址计算比较简单:
result = S + A
其中:
- S = 符号的最终地址(链接之后的真实地址)
- A = addend(来自机器码中的原始值)
而相对地址需要计算相对PC的偏移:
offset = S - (P + 4)
其中:
- S = 符号的最终地址(链接之后的真实地址)
- P = 需要修改的符号地址(来自重定位表中的Offset字段)
- 4是函数调用后的下一个取指地址(32位机器上地址长度为4)
5.总结
至此,链接部分的内容算是告一段落,但是要注意的是链接并不是静态的打包好之后再放到目标主机去运行的,或者说静态的链接可能只打包了一部分,其余部分留给动态链接以及加载器去完成,但这也是链接的一部分。符号解析、重定位不一定都要走静态阶段全部完成,有些需要动态加载的场景下就不可能在动态执行之前就计算好重定位地址的。下面我汇总一张表说明一下链接的静态与动态情况。
| 链接器功能 | 静态阶段 | 动态阶段 |
|---|---|---|
| 合并节区 | 必须完成 | 不会再合并,因为共享库不会被合并进执行文件中 |
| 符号解析 | 部分或全部完成 | 剩余部分继续解析 |
| 重定位 | 部分或全部完成或不进行重定位 | 剩余部分继续重定位或者全部动态阶段重定位 |