我注意到用于ARM 64位汇编的GNU asm relocation syntax。那些#:abs_g0_nc:
和:pg_hi21:
是什么?他们在哪里解释?他们有模式吗?还是在忙碌中弥补?在哪里可以了解更多?
最佳答案
介绍
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
指定的位置,以计算要从加数(r_addend
用于RELA)和符号地址(可从r_info
的上部获取)。实际上,与其他架构相反,在其他架构中,指令的立即数字段通常与用于对操作进行编码的字节完全分开,从而简化了写入部分。在ARM中,即时值与其他编码信息混合在一起。
因此,加载器应该知道要尝试重定位的指令类型(如果完全是一条指令)1,而是由汇编程序根据指令设置重定位类型,而不是让它分解重定位位置。
每个重定位符号只能重定位一个或两个等价的编码指令。
在特定情况下,重定位本身甚至会更改指令的类型。
在重定位期间计算的值计算将隐式扩展为64位,根据选择的重定位类型有符号或无符号。
AArch64搬迁
作为ARM一种具有固定指令大小的RISC体系结构,立即将全宽度(即64位)加载到寄存器中是很重要的,因为没有指令可以具有全宽度立即数字段。
AArch64中的重定位也必须解决这个问题,它实际上是两个方面的问题:首先,找到程序员打算使用的实际价值(这是问题的纯重定位部分);其次,找到一种将其放入寄存器的方法,因为没有指令具有64位立即数字段。
第二个问题通过使用组重定位解决,组中的每个重定位类型都用于计算64位值的16位部分,因此,组中只能有四个重定位类型(范围从G0到G3)。
此切片分为16位,以适应
movk
(移动保持),movz
(移动归零)和movn
(在逻辑上进行求反)。其他说明,例如
b
,bl
,adrp
,adr
等,具有特别适合他们的重定位类型。只要给定指令的引用符号只有一种(因此毫不含糊)可能的重定位类型,汇编器就可以生成相应的条目,而无需程序员明确指定它。
组重定位不属于此类,它们的存在是为了使程序员有一定的灵活性,因此通常进行明确说明。
在一个组中,重定位类型可以指定汇编程序是否必须执行溢出检查。
除非明确抑制,否则用于加载值的低16位的G0重定位,请检查该值是否适合16位(带符号或无符号,具体取决于所使用的特定类型)。
对于G1也是如此,加载第31-16位检查值是否适合32位。
结果,G3总是不检查,因为每个值都适合64位。
最后,重定位可用于将整数值加载到寄存器中。
实际上,符号的地址不过是一个任意的整数常量。
请注意,
r_addend
为64位宽。1如果
r_offset
指向数据节中的某个站点,则计算出的值将以64位字的形式写入所指示的位置。搬迁公司
首先,一些参考:
描述ELF64格式的重定位类型的ARM文档为here,第4.6节
可能包含GAS可用的所有重定位运算符的AArch64测试程序集文件为here here
约定
遵循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位值,则必须在值本身中明确指定符号。
它们始终仅用于设置最高部分,因为符号仅在此处相关。
它们始终在检查,因为有符号值的溢出会使该值的含义变小。
这些重定位根据值的符号将指令的类型更改为
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。
将来,我可能会扩展此答案,以包括程序集列表和重定位转储的示例。
关于assembly - AArch64重定位前缀,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38570495/