ARM64汇编寻址、汇编指令、指令编码方式
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
ARM64汇编寻址
1. 立即数寻址(Immediate Addressing)
这种方式直接将立即数作为操作数,适合小数据或常量。ARM64的立即数在指令中直接编码。
MOV X0, #10 ; 将常数10加载到寄存器X0中
2. 寄存器间接寻址(Register Indirect Addressing)
使用寄存器中的地址作为内存地址。适合基于寄存器值进行偏移的简单访问方式。
LDR X1, [X0] ; 将地址X0指向的内存内容加载到X1中
3. 偏移寻址(Offset Addressing)
在基地址寄存器的基础上添加一个偏移量来确定目标地址,偏移量可以是立即数或寄存器值。
LDR X1, [X0, #8] ; 从地址X0 + 8的位置加载数据到X1中
4. 预索引寻址(Pre-indexed Addressing)
使用基地址加上偏移量来访问内存,访问完成后,将偏移量更新到基地址寄存器中。
LDR X1, [X0, #8]! ; 从地址X0 + 8加载数据到X1中,同时更新X0为X0 + 8
5. 后索引寻址(Post-indexed Addressing)
先使用基地址来访问内存,再将偏移量加到基地址寄存器中。这样偏移的效果在读取数据后才生效。
LDR X1, [X0], #8 ; 先从X0指向的地址加载数据到X1中,随后X0增加8
6. 寄存器偏移寻址(Register Offset Addressing)
偏移量用另一个寄存器指定,便于灵活的偏移操作,特别适合对数据结构的访问。
LDR X1, [X0, X2] ; 从X0 + X2指向的内存地址加载数据到X1中
7. 带移位的寄存器偏移寻址(Scaled Register Offset Addressing)
将偏移寄存器的值进行左移,然后加到基地址中。这个方式常用于访问数组中的元素,因为它可以按数据大小自动调整偏移。
LDR X1, [X0, X2, LSL #3] ; 计算X0 + (X2 * 8)的地址,并从中加载数据到X1
LSL:逻辑左移,将寄存器的值向左移位指定的位数,右侧用 0 填充。
LSR:逻辑右移,将寄存器的值向右移位指定的位数,左侧用 0 填充。
ASR:算术右移,和逻辑右移类似,但左侧用符号位填充。即,如果值为负,左侧填充 1,如果为正,填充 0。
UXTX:执行零扩展,同时可以左移指定的位数。
SXTW/SXTX:执行符号扩展,并可以左移指定的位数。这些用于在 32 位和 64 位数据之间进行转换的场合,特别适合混合宽度数据操作。
ROR:循环右移,将寄存器的值向右旋转指定的位数,即右移的位会从左侧填入。
8. 栈寻址
使用栈指针 SP 作为基地址,加上偏移量来访问栈中的数据。例如,存储和加载局部变量
存储数据到栈
STP X29, X30, [SP, #0x70 + var_s0] ; 将 X29 和 X30 的值存储到 SP 基址加 #0x70 + var_s0 偏移量的内存地址上
从栈加载数据
LDP X29, X30, [SP, #0x70 + var_s0] ; 从 SP 基址加偏移 #0x70 + var_s0 处加载数据到 X29 和 X30
STP(Store Pair) 指令用于将两个寄存器的值存储到内存中(通常是栈上)。
LDP(Load Pair)指令用于从内存中同时加载两个寄存器的值。
指令中的寄存器类型决定了要读取或存储的数据大小。在上面的例子中,由于使用的是 64 位的 X 寄存器,每个寄存器占 8 字节(64 位),所以 STP 和 LDP 指令会操作两个 8 字节的数据。
MOV 和 LDR 指令的区别
MOV:用于寄存器和常量间的数据传递,不涉及内存访问。适合在不需要访问内存的情况下使用,如加载常数或在寄存器之间传递数据。
LDR:用于从内存中加载数据到寄存器,需要指定内存地址。支持多种寻址方式,例如偏移寻址、基地址寄存器加偏移等,非常适合访问内存中的数据,如全局变量、数组元素等。
两者主要区别在于是否需要访问内存。
ARM64汇编指令
ARM64 指令集具有固定长度(每条指令均为 32 位)。ARM64 包含 31 个通用寄存器(X0-X30),用于存储数据和地址;一个零寄存器(XZR),用于返回零值;以及专门的栈指针(SP)和程序计数器(PC)寄存器,用于管理函数调用和指令执行。
跳转指令:
B:无条件跳转到指定的地址。
BL:无条件跳转到指定地址并将返回地址存储在链接寄存器(LR)中,用于函数调用。
BR:跳转到寄存器中存储的地址。
CBZ/CBNZ:根据寄存器值是否为零进行跳转。
TBZ/TBNZ:根据寄存器中指定位的值(0或1)进行跳转。
数据处理指令:
MOV:将一个值或寄存器的内容传送到另一个寄存器中。
MVN:将寄存器值按位取反,并传送到目标寄存器。
AND/ORR/EOR:对寄存器数据进行按位与、或、异或运算。
LSL/LSR:逻辑左移或右移寄存器内容。
BIC:对寄存器值进行按位与取反运算,用于清除特定位。
算术指令:
ADD/ADDS:对两个寄存器内容相加,ADDS会更新条件标志。
SUB/SUBS:对两个寄存器内容相减,SUBS会更新条件标志。
MUL:对两个寄存器内容相乘。
UDIV/SDIV:无符号/有符号除法,将结果存储在目标寄存器中。
NEG:对寄存器中的值取反并加1,相当于求负。
内存访问指令:
LDR:从内存加载一个值到寄存器。
STR:将寄存器的值存储到内存中。
LDUR/STUR:使用偏移量从内存加载或存储数据,不会影响基址寄存器。
LDXR/STXR:用于原子操作的加载和存储指令,用于处理并发数据。
LDP/STP:从内存加载或存储两个连续的寄存器内容,常用于函数调用保存/恢复寄存器。
如何查看ARM官方文档
打开 ARM 官方网站 ,路径 Home / Documentation / Architectures / CPU Architecture 找到 ARM64 指令集文档
可以在线查看,也可以点击左边的下载按钮下载 pdf 到本地
ARM64指令编码方式
以 MOV(bitmask immediate) 指令为例,文档中搜索 MOV 可以找到该指令的编码格式说明。
MOV(bitmask immediate) 指令的 32 位二进制格式,其中各字段的含义如下:
sf(位 31):size flag,指定操作数的位宽。sf=0 表示 32 位操作,sf=1 表示 64 位操作。
opc(位 29-23):操作码字段,用于区分不同的操作类型。对于 MOV 指令,其操作码固定为 0110010。
N(位 22):与立即数编码相关,用于调整立即数的生成规则。
immr 和 imms(位 21-10):立即数字段,用于构造一个特定的位掩码。
Rn(位 9-5):源寄存器字段。在 MOV(bitmask immediate)中,该字段固定为 11111,表示使用零寄存器(WZR 或 XZR),以便执行立即数到寄存器的移动操作。
Rd(位 4-0):目标寄存器字段,指定立即数写入的目标寄存器编号。
以下面汇编指令为例
mov w0, #1
32 位指令编码中的各字段定义如下
位 | 字段 | 说明 |
---|---|---|
31 | sf | 设为 0,表示 32 位操作 |
30-29 | 固定位 | 固定为 01 |
28-23 | opc | 操作码,固定为 100100 |
22 | N | 设为 0 |
21-16 | immr | 000000,意味着掩码宽度为 1,即只生成一个 1,其他位全部为 0。 |
15-10 | imms | 000000,表示不进行任何旋转。 |
9-5 | Rn | 固定为 11111,表示使用零寄存器(XZR) |
4-0 | Rd | 目标寄存器 X0 的编码为 00000 |
将以上字段组合,我们得到完整 32 位二进制编码如下
0 01 100100 0 000000 000000 11111 00000
十六进制值为 320003E0,转换为小端模式值为 E0030032
在 https://armconverter.com/?disasm 验证一下是否正确
相关文章:详解ARM64可执行程序的生成过程