简介
ELF64定义了两种类型的重定位条目,分别称为REL和RELA:
typedef struct
{
Elf64_Addr r_offset; /* Address of reference */
Elf64_Xword r_info; /* Symbol index and type of relocation */
} Elf64_Rel;
typedef struct
{
Elf64_Addr r_offset; /* Address of reference */
Elf64_Xword r_info; /* Symbol index and type of relocation */
Elf64_Sxword r_addend; /* Constant part of expression */
} Elf64_Rela;
每个重定位条目的范围是给加载器(静态或动态)四个信息:
要修补的指令的虚拟地址或偏移量。
这是由r_offset 提供的。
所访问符号的运行时地址。
这是由r_info 的较高部分给出的。
称为 addend 的自定义值
该值最终将作为表达式中的操作数,用于计算将写入修补指令的值。
RELA 条目在r_addend 中有这个值,REL 条目从重定位站点中提取它。
重定位类型
这决定了用于计算修补指令值的表达式类型。这是在r_info 的下部编码的。
搬迁
在重定位阶段,加载器遍历所有重定位条目并写入每个r_offset指定的位置,使用r_info的下半部分选择的公式从addend(r_addend for RELA)和符号地址(可从r_info 的上半部分获得)。
实际上写部分已经被简化了,与其他体系结构相反,指令的立即字段通常与用于编码操作的字段完全分开,在 ARM 中,立即值与其他编码信息混合在一起。
所以加载器应该知道试图重定位的指令是什么类型的,如果它是一条指令1,而不是让它反汇编重定位的位置,而是设置重定位类型的汇编器按照说明。
每个重定位符号只能重定位一个或两个编码等效的指令。
在特定情况下,重定位本身甚至会改变指令的类型。
在重定位期间计算的值被隐式扩展到 64 位,根据选择的重定位类型有符号或无符号。
AArch64 重定位
作为 ARM 一个具有固定指令大小的 RISC 架构,将全宽(即 64 位)立即加载到寄存器中并非易事,因为没有指令可以具有全宽立即字段。
AArch64 中的重定位也必须解决这个问题,这实际上是一个双重问题:首先,找到程序员打算使用的真实值(这是问题的纯重定位部分);其次,找到一种方法将其放入寄存器,因为没有指令具有 64 位立即数字段。
第二个问题是通过使用group relocation来解决的,group中的每个relocation type都用于计算64 bits value的16 bits,因此一个relocation type只能有4个relocation type组(范围从 G0 到 G3)。
这种切片为 16 位适合 movk(移动保持)、movz(移动归零)和 movn(移动逻辑否定)。
其他指令,如b、bl、adrp、adr 等,都有专门适合它们的重定位类型。
只要给定指令引用符号的重定位类型只有一种,因此是明确的,汇编程序可以生成相应的条目,而程序员无需显式指定它。
组重定位不属于此类别,它们的存在是为了给程序员一些灵活性,因此通常会明确说明。
在组中,重定位类型可以指定汇编器是否必须执行溢出检查。
G0 重定位,用于加载值的低 16 位,除非明确禁止,否则检查该值是否适合 16 位(有符号或无符号,取决于使用的特定类型)。
G1 也是如此,加载位 31-16 会检查这些值是否适合 32 位。
因此,G3 总是不检查,因为每个值都适合 64 位。
最后,重定位可用于将整数值加载到寄存器中。
实际上,符号的地址只不过是一个任意整数常量。
请注意,r_addend 是 64 位宽。
1 如果r_offset 指向数据段中的某个站点,则计算值将作为 64 位字写入指定位置。
重定位运算符
首先,一些参考资料:
约定
遵循我们的 ARM 文档约定:
S 是被重定位符号的运行时地址。
A 是
搬迁加数。
P是搬迁地点地址
(源自r_offset)。
X 是重定位的结果
操作,在应用任何掩码或位选择操作之前。
Page(expr) 是表达式 expr 的页地址,定义为
expr & ~0xFFF,即expr,低 12 位清零。
GOT 是
Global Offset
Table.
GDAT(S+A) 的地址表示 GOT 中用于地址 S+A 的 64 位条目。这
条目将在运行时通过重定位重定位
R_AARCH64_GLOB_DAT(S+A).
G(expr)是GOT的地址
表达式 expr.
Delta(S) 的条目解析差异
S的静态链接地址和
S。如果S 是空符号(ELF 符号索引 0),则解析为
P的静态链接地址与执行的区别
P的地址。
Indirect(expr)代表调用结果
expr 作为函数。
[msb:lsb] 是位掩码操作
表示值中位的选择,包括边界。
运营商
为了简洁起见,重定位名称缺少前缀R_AARCH64_。
|X|≤2^16 类型的表达式旨在 -2^16 ≤ X ,注意右边的严格不等式。
这是一种对符号的滥用,被格式化表格的约束所调用。
集团搬迁
Operator | Relocation name | Operation | Inst | Immediate | Check
------------+-----------------+-----------+------+-----------+----------
:abs_g0: | MOVW_UABS_G0 | S + A | movz | X[15:0] | 0≤X≤2^16
------------+-----------------+-----------+------+-----------+----------
:abs_g0_nc: | MOVW_UABS_G0_NC | S + A | movk | X[15:0] |
------------+-----------------+-----------+------+-----------+----------
:abs_g1: | MOVW_UABS_G1 | S + A | movz | X[31:16] | 0≤X≤2^32
------------+-----------------+-----------+------+-----------+----------
:abs_g1_nc: | MOVW_UABS_G1_NC | S + A | movk | X[31:16] |
------------+-----------------+-----------+------+-----------+----------
:abs_g2: | MOVW_UABS_G2 | S + A | movz | X[47:32] | 0≤X≤2^48
------------+-----------------+-----------+------+-----------+----------
:abs_g2_nc: | MOVW_UABS_G2_NC | S + A | movk | X[47:32] |
------------+-----------------+-----------+------+-----------+----------
:abs_g3: | MOVW_UABS_G3 | S + A | movk | X[64:48] |
| | | movz | |
------------+-----------------+-----------+------+-----------+----------
:abs_g0_s: | MOVW_SABS_G0 | S + A | movz | X[15:0] | |X|≤2^16
| | | movn | |
------------+-----------------+-----------+------+-----------+----------
:abs_g1_s: | MOVW_SABS_G1 | S + A | movz | X[31:16] | |X|≤2^32
| | | movn | |
------------+-----------------+-----------+------+-----------+----------
:abs_g2_s: | MOVW_SABS_G2 | S + A | movz | X[47:32] | |X|≤2^48
| | | movn | |
------------+-----------------+-----------+------+-----------+----------
表中显示ABS版本,汇编器可以选择PREL(PC相关)或GOTOFF(GOT相关)版本取决于引用的符号和输出格式的类型。
这种重定位运算符的典型用法是
Unsigned 64 bits Signed 64 bits
movz x1,#:abs_g3:u64 movz x1,#:abs_g3_s:u64
movk x1,#:abs_g2_nc:u64 movk x1,#:abs_g2_nc:u64
movk x1,#:abs_g1_nc:u64 movk x1,#:abs_g1_nc:u64
movk x1,#:abs_g0_nc:u64 movk x1,#:abs_g0_nc:u64
通常使用一对一检查运算符,即设置最高部分的运算符。
这就是为什么检查版本仅重定位 movz,而非检查版本重定位 movk(部分设置寄存器)。
G3 重定位两者是因为它本质上是非检查的,因为没有值可以超过 64 位。
签名版本以_s 结尾,并且它们一直在检查。
没有 G3 版本,因为如果使用 64 位值,则在值本身中指定符号 if sully。
它们始终仅用于设置最高部分,因为该标志仅在此处相关。
他们总是检查有符号值中的溢出,使值的意义减少。
这些重定位根据值的符号将指令的类型更改为movn 或movz,这有效地扩展了值。
团体搬迁,也可以
PC 相关的 19、21、33 位地址
Operator | Relocation name | Operation | Inst | Immediate | Check
------------+-----------------+-----------+------+-----------+----------
[implicit] | LD_PREL_LO19 | S + A - P | ldr | X[20:2] | |X|≤2^20
------------+-----------------+-----------+------+-----------+----------
[implicit] | LD_PREL_LO21 | S + A - P | adr | X[20:0] | |X|≤2^20
------------+-----------------+-----------+------+-----------+----------
[implicit] | LD_PREL_LO21 | S + A - P | adr | X[20:0] | |X|≤2^20
------------+-----------------+-----------+------+-----------+----------
:pg_hi21: | ADR_PREL_PG | Page(S+A) | adrp | X[31:12] | |X|≤2^32
| _HI21 | - Page(P) | | |
------------+-----------------+-----------+------+-----------+----------
:pg_hi21_nc:| ADR_PREL_PG | Page(S+A) | adrp | X[31:12] |
| _HI21_NC | - Page(P) | | |
------------+-----------------+-----------+------+-----------+----------
:lo12: | ADD_ABS_LO12_NC | S + A | add | X[11:0] |
------------+-----------------+-----------+------+-----------+----------
:lo12: | LDST8_ABS_LO12 | S + A | ld | X[11:0] |
| _NC | | st | |
------------+-----------------+-----------+------+-----------+----------
:lo12: | LDST16_ABS_LO12 | S + A | ld | X[11:1] |
| _NC | | st | |
------------+-----------------+-----------+------+-----------+----------
:lo12: | LDST32_ABS_LO12 | S + A | ld | X[11:2] |
| _NC | | st | |
------------+-----------------+-----------+------+-----------+----------
:lo12: | LDST64_ABS_LO12 | S + A | prfm | X[11:3] |
| _NC | | | |
------------+-----------------+-----------+------+-----------+----------
:lo12: | LDST128_ABS | S + A | ? | X[11:4] |
| _LO12_NC | | | |
:lo12: 根据指令处理的数据大小改变含义(例如,ldrb 使用 LDST8_ABS_LO12_NC,ldrh 使用 LDST16_ABS_LO12_NC)。
这些重定位的 GOT 相对版本也存在,汇编器会选择正确的。
控制流重定位
Operator | Relocation name | Operation | Inst | Immediate | Check
------------+-----------------+-----------+------+-----------+----------
[implicit] | TSTBR14 | S + A - P | tbz | X[15:2] | |X|≤2^15
| | | tbnz | |
------------+-----------------+-----------+------+-----------+----------
[implicit] | CONDBR19 | S + A - P | b.* | X[20:2] | |X|≤2^20
------------+-----------------+-----------+------+-----------+----------
[implicit] | JUMP26 | S + A - P | b | X[27:2] | |X|≤2^27
------------+-----------------+-----------+------+-----------+----------
[implicit] | CALL26 | S + A - P | bl | X[27:2] | |X|≤2^27
------------+-----------------+-----------+------+-----------+----------
结语
我找不到官方文档。
上面的表格是根据 GAS 测试用例和 ARM 文档重新构建的,该文档解释了可用于符合 AArch64 的 ELF 的重定位类型。
这些表并未显示 ARM 文档中存在的所有重定位,因为它们中的大多数是补充版本,由汇编程序自动获取。
带有示例的部分会很棒,但我没有 ARM GAS。
将来我可能会扩展此答案以包括装配列表和重定位转储的示例。