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;
每个重定位表项的作用是向装载器(静态或动态)提供四个信息:这种将数据切片成16位的方式与movk
(保持移动)、movz
(清零移动)和movn
(逻辑取反移动)相匹配。
其他指令,如b
、bl
、adrp
、adr
等,则有一个特别适合它们的重定位类型。
每当对于一条引用符号的给定指令只有一个明确的可能的重定位类型时,汇编器就可以生成相应的条目,而程序员无需显式指定它。
组重定位不适合此类别,它们存在是为了让程序员有一些灵活性,因此通常会被显式说明。
在一个组中,重定位类型可以指定汇编器是否必须执行溢出检查。
用于加载值低16位的G0重定位(除非明确禁止)会检查该值是否能够适合16位(有符号或无符号,取决于所使用的特定类型)。
如果加载31-16位,则G1也是如此检查该值是否适合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
是全局偏移表的地址。
GDAT(S+A)
表示地址S+A在GOT中的64位条目。该条目将在运行时使用重定位R_AARCH64_GLOB_DAT(S+A)进行重定位。
G(expr)
是表达式expr的GOT条目地址。
Delta(S)
解析为S
的静态链接地址与S
的执行地址之间的差异。如果S
是空符号(ELF符号索引0),则解析为P
的静态链接地址与P
的执行地址之间的差异。
Indirect(expr)
表示将expr
作为函数调用的结果。
[msb:lsb]
是一种位掩码操作,表示选择值中的位,边界是包括的。
为了简洁起见,重定位名称缺少前缀R_AARCH64_
。
形如|X|≤2^16的表达式意为-2^16 ≤ X < 2^16,请注意右侧的严格不等号。
这是一种符号滥用,由于表格格式的限制而产生。
组重定位
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位值,则在值本身中完全指定了符号。它们总是仅用于设置最高部分,因为符号仅在那里相关。这些重新定位根据值的符号将指令类型更改为movn
或movz
,从而有效地对值进行符号扩展。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。
将来我可能会扩展这个答案,包括汇编列表和重定位转储的示例。