ARM架构
ARM是一种精简指令集计算机架构(处理器架构)。
ARMv8-A
Armv8-A架构支持处理器同时运行64位和32位程序,处理器在AArch64执行状态下运行64位程序,在AArch32执行状态下运行32位程序。
指令集架构
AArch64(64位模式)
引入了新的指令集,A64指令集,由32位宽的指令组成。
AArch32(32位模式)
向后兼容32位的ARMv7-A指令集。
支持ARM指令集(固定32位指令长度)和Thumb指令集(16位和32位混合指令长度),称为A32和T32指令集 。
ARMv8-A的双模式设计实现了从32位到64位的过渡。
AArch64执行状态
寄存器
A64共有32个64位寄存器(X0到X30)。
每个寄存器均支持 64 位和 32 位访问(例如
X0
和W0
分别表示寄存器的高 64 位和低 32 位部分)。
Arm架构程序调用标准
- X0~X7是参数寄存器,传递参数并返回结果。
- X8用于传递间接结果的地址位置。
- X9~X15是调用者保存的临时寄存器。
- X16~X18是子程序内调用临时寄存器。
- X19~X28是被调用者保存的寄存器。
- X29用作栈帧寄存器(FP),跟踪栈帧。
- X30用作链接寄存器(LR),保存函数返回地址。
寄存器 | 名称 | 寄存器宽度 |
---|---|---|
PC | 程序计数器 | 64位 |
SP | 栈指针寄存器 | 64位 |
WSP | 栈指针寄存器 | 32位 |
XZR | 零寄存器 | 64位 |
WZR | 零寄存器 | 32位 |
LR(X30) | 链接寄存器 | 64位 |
ELR | 异常链接寄存器 | 64位 |
PSTATE | 程序状态寄存器 | 64位 |
SPSR_ELx | 保存进程状态寄存器 | 32位 |
- 程序计数器
PC保存当前指令的地址。所有指令在加载之前都必须从PC引用的内存位置加载。
- 栈指针
SP用于跟踪当前线程的栈位置,通常指向该线程栈的逻辑“顶部”。
- 零寄存器
它被定义为始终保存0值。
- 链接寄存器
它主要用于存储调用函数时的返回地址。
- 栈帧指针
跟踪栈帧。
AArch32执行状态
寄存器
16 个 32 位通用寄存器(
R0
到R15
)。PC(程序计数器)占用
R15
,R14
通常作为链接寄存器(LR),R13
通常作为栈指针(SP)。
数据处理指令
指令的基本语法
1 | <操作码> <目标寄存器>, <操作数1>, <操作数2> |
- 在A32/T32指令中目标寄存器表示为Rd
- 在A64指令中目标寄存器表示为Xd或Wd
- 在A32/T32指令中输入寄存器表示为Rm、Rn或Ra
- 在A64指令中输入寄存器表示为Xn或Xm
条件标志位
N(Negative):
结果为负数,则值为1;
结果是正数或零,则值为0。
Z(Zero):
结果为 0,则值为1。
否则,值为0。
C(Carry):
如果结果产生了进位条件,则值为1;
否则,值为0。
V(Overflow):
如果指令结果导致溢出条件,则值为1。
算术指令
add
加法
1 | add x0,x1,x2 ;x0=x1+x2 |
adds
加法运算并更新条件标志
1 | adds x0,x1,x2 ;x0=x1+x2 and set flags |
adr
计算 标签(Label)的地址 并将其加载到目标寄存器。
1 | ADR <Rd>, <label> |
sub
减法
mul
MUL
指令用于无符号乘法。它将两个寄存器的值相乘,并将结果存储在目标寄存器中。
1 | MUL destination, operand1, operand2 |
muls
MULS
与 MUL
指令类似,但是它还会更新条件码(N, Z, C, V),即用于设置 ARM 标志寄存器的状态。用于无符号乘法,格式和 MUL
类似。
mla
MLA
指令用于乘法累加操作,即将两个数相乘后将结果加到一个寄存器上。
1 | MLA destination, operand1, operand2, operand3 |
sdiv/udiv
除法
移位和循环移位
lsl(Logical Shift Left)
逻辑左移,左移后相当于乘以 2 的若干次方。
将一个值的二进制位整体向左移动,用 0 填充右边空出的位,丢弃左边溢出的位。
1 | lsl r0,r1,#2 ;r0 = r1 << 2 |
1 | add r0,r1,r2,lsl #2 ;r0 = r1 + (r2<<2) |
lsr(Logical Shift Right)
逻辑右移,逻辑右移后,每位的值变成原来数值的 1/2。
左侧空出的位用 0 填充,右侧移出的位被丢弃。
asr(Arithmetic Shift Left)
算术右移
将一个值的二进制位整体向右移动。它与逻辑右移的主要区别在于,左侧空出的位用符号位填充(最高位的值,0 或 1),以保持数值的符号不变。
rrx
带扩展的循环右移
在普通 ROR
的基础上,利用 进位标志位(C) 的值作为循环右移的高位,右移后最右侧的位会填入进位标志位(C
)
- 始终右移 1 位
- 进位标志位参与操作
逻辑运算
AND
位与
ORR
将两个操作数的每一位进行逻辑或,结果写入目标寄存器。
1 | ORR <Wd>, <Wn>, <Wm>{, <shift> #<amount>} // 32位操作 |
EOR(Exclusive OR)
按位异或
LSR(Logical Shift Right)
逻辑右移指令,用于将寄存器中的数值向右移动指定的位数,高位补零。
1 | LSR{S}{cond} Rd, Rm, #imm @ 立即数移位 |
示例
1 | MOV R0, #16 @ R0 = 16 |
UBFIZ (Unsigned Bitfield Insert and Zero)
是 ARM 架构中的一个位域操作指令。
1 | UBFIZ Wd, Wn, #lsb, #width // 32位寄存器版本 |
将源寄存器 Wn/Xn 的低 width 位向左移动 lsb 位
将结果放入目标寄存器 Wd/Xd
目标寄存器中的其他位都设置为 0
条件指令
cmp
比较
TST
用于按位测试两个操作数的逻辑与(AND)结果,并根据结果更新条件标志(Condition Flags),但不会修改任何寄存器的值。
1 | TST{cond} Rn, Operand2 |
根据 AND
操作的结果更新以下标志位:
- N (Negative):结果的最高位(符号位)为1时置位。
- Z (Zero):结果为0时置位。
- C (Carry) 和 V (Overflow):不受影响(除非
Operand2
包含移位操作)。
移动指令
mov
用于将值从一个寄存器传递到另一个寄存器,或者将一个立即数加载到目标寄存器。
movz
将一个 16 位立即数 加载到目标寄存器的指定位置,并将其他位清零。
movk
将一个 16 位立即数 加载到目标寄存器的指定位置,但保持寄存器中其他位的原值不变。
movn
将一个 16 位立即数的按位取反值 加载到目标寄存器的指定位置,并将其他位清零。
MVN
将 源操作数(Source Operand) 的每一位取反(0→1,1→0),并将结果存入 目标寄存器。
1 | MVN{<cond>}{S} <Rd>, <Operand> |
内存访问指令
加载和存储指令
LDR(Load Register)
从内存地址加载一个字(通常是32位)到指定的寄存器中。
1 | LDR R0, [R1] ; 将内存地址R1中的值加载到寄存器R0 |
LDRB(Load Register Byte)
从内存中加载一个字节(8位)到寄存器,并将该字节零扩展(Zero-Extend)为32位。
1 | LDRB{cond}{B} Rt, [Rn, Operand2]{!} |
1 | LDR R1, =buffer @ R1指向缓冲区首地址 |
LDP(Load Pair of Registers)
一条高效的双寄存器加载指令,用于从内存中连续加载两个寄存器的值
LDUR(Load Unscaled Register)
是一条用于从内存加载数据到寄存器的指令。它与常见的 LDR
(Load Register)指令类似,但在偏移量范围和寻址模式上有特殊设计。
从 基址寄存器 (Xn
或 SP
) + 偏移量 (simm
) 的内存地址加载数据到目标寄存器。
1 | SUB SP, SP, #32 // 分配栈空间 |
LDRSW(Load Register Signed Word)
用于从内存中加载 32 位有符号整数,并将其符号扩展至 64 位目标寄存器。
1 | LDRSW <Xd>, [<Xn|SP>{, #<pimm>}] ; 立即数偏移模式 |
STR(Store Register)
将寄存器中的数据存储到内存地址中。
1 | STR R0, [R1] ; 将寄存器R0的值存储到R1指向的内存地址 |
STRB(Store Register Byte)
将寄存器中的低8位数据(一个字节)存储到内存中。
1 | STRB{cond}{B} Rt, [Rn, Operand2]{!} |
1 | MOV R0, #'A' @ R0 = ASCII字符'A'(0x41) |
STP(Store Pair)
将 两个寄存器 的值存储到 连续的内存地址 中。
1 | STP <寄存器1>, <寄存器2>, [<基址寄存器>{, #偏移量}] |
示例
1 | STP X0, X1, [X2] ; 将X0存入X2,X1存入X2+8 |
EXTR(Extract Register)
是一条用于 提取寄存器位域 的指令。它可以从两个寄存器的组合位域中提取指定范围的位,并将结果存入目标寄存器。
控制流
分支指令
B(Branch)
B指令用于无条件跳转到一个由立即数偏移指定的目标地址。
执行该指令时,程序计数器(PC)被更新为目标地址,从而实现流程的跳转。
1 | ;无条件跳转到标签label |
BX(Branch and Exchange)
BX 指令用于从一个寄存器中取得目标地址并跳转,并且可以用于 ARM 和 Thumb 模式之间的切换。
它会检查目标地址的最低位:
- 如果最低位为 1,则表示需要进入 Thumb 模式;
- 如果最低位为 0,则保持在 ARM 模式。
常用于子程序返回(例如:BX LR
);
1 | BX LR ; 使用链接寄存器 LR 跳转,通常用于返回。 |
BL(Branch with Link)
用于调用子程序。
BL 指令除了进行跳转外,还会将返回地址(即 BL 指令下一条指令的地址)保存到链接寄存器 LR中。
1 | BL function ; 调用 function 子程序,并将返回地址保存到 LR |
BR
BR 指令出现在 ARMv8 的 AArch64 指令集中,用于实现从寄存器中取得目标地址进行无条件跳转。
与 BX 指令类似,BR 指令也是基于寄存器的跳转,但不涉及状态交换(因为 AArch64 不存在 ARM/Thumb 模式之分)。
1 | // 示例1:通过寄存器 X1 中的地址跳转 |
CBZ(Compare and Branch on Zero)
根据寄存器的值是否为零来决定是否跳转到目标地址。
1 | CBZ Rn, label |
- 当
Rn == 0
时:程序跳转到label
处执行。 - 当
Rn ≠ 0
时:继续顺序执行下一条指令。
CBNZ(Compare and Branch on Non-Zero)
检测寄存器是否非零并决定是否跳转。
1 | CBNZ Rn, label |
- 当
Rn ≠ 0
时:跳转到label
执行。 - 当
Rn = 0
时:顺序执行下一条指令。
B.NE
条件分支指令,当处理器状态寄存器(PSTATE)中的 Z(Zero)标志为0 时跳转到指定地址。
1 | B.NE label |
- 当 Z = 0 时:跳转到
label
执行。 - 当 Z = 1 时:顺序执行下一条指令。
指令大全
MVN(Move Not)
是一条逻辑运算指令,用于对源操作数进行 按位取反 操作,并将结果存入目标寄存器。
1 | MVN X0, X1 ; X0 = ~X1 |
ROR(Rotate Right)
循环右移
将一个二进制数的位向右移动指定的位数,并将被“移出”的位重新填充到左侧。
固定位宽循环右移
操作数num
为 n
位的整数,循环右移 k
位:
1 | def ror(num: int, k: int, bits: int = 32) -> int: |
EON(Exclusive OR Not)
是一条按位逻辑运算指令,用于执行 异或非(XOR-NOT)操作。
1 | Xn ^ (~Xm) |
UXTW(Unsigned Extend Word to Doubleword)
UXTW
是一个用于 无符号扩展(Unsigned Extend) 的操作符
它的作用是将一个 32 位寄存器(W寄存器) 的值无符号扩展到 64 位(X寄存器),并可用于地址计算或算术运算。
核心功能
- 无符号扩展:将 32 位
W
寄存器的值 无符号扩展 为 64 位,高位补零。 - 移位操作:可对扩展后的值进行左移(如
UXTW #2
表示左移 2 位)。 - 典型用途:在地址计算或算术运算中,将 32 位索引或偏移量扩展为 64 位。
SXTW(Sign Extend Word)
是一种 扩展修饰符,用于将 32 位寄存器(W
寄存器)的值 有符号扩展** 为 64 位,通常与其他指令(如 ADD
、LDR
等)结合使用。
核心功能
- 有符号扩展:将 32 位
W
寄存器的值 有符号扩展 为 64 位,高位补符号位(0
或1
)。 - 移位操作:可对扩展后的值进行左移(如
SXTW #2
表示左移 2 位)。 - 典型用途:在地址计算或算术运算中,将 32 位有符号索引或偏移量扩展为 64 位。
BFXIL(Bit Field Extract and Insert Low)
是一条 位域操作指令,用于从源寄存器中提取一个位域,并将其插入到目标寄存器的 低有效位部分。
- 从源寄存器
<Xn>
的[lsb, lsb + width - 1]
位提取一个位域。 - 将该位域插入到目标寄存器
<Xd>
的[0, width - 1]
位(低有效位部分)。 - 目标寄存器的高位(
[width, 63]
或[width, 31]
)保持不变。
1 | Xd[width-1:0] = Xn[lsb+width-1:lsb] // 低 width 位被替换 |
示例
1 | "bfxil w30, w24, #3, #5" w30=0xffff9480 w24=0xfffffca4 => w30=0xffff9494 |
Python实现
1 | def bfxil_visual(dest, src, lsb, width, is_64bit=False): |
BFI(Bit Field Insert)
是 ARM AArch64 架构中的 位域插入指令,用于将源寄存器的指定位域插入到目标寄存器的任意位置,同时保持目标寄存器其他位不变。
- 从源寄存器提取
width
位(最低有效位开始)。 - 插入到目标寄存器的
[lsb, lsb + width - 1]
位。 - 目标寄存器其他位保持不变。
1 | Xd[lsb + width - 1 : lsb] = Xn[width - 1 : 0] |
示例
1 | 0x120a0684: "bfi w30, w24, #6, #8" w30=0x2 w24=0xa => w30=0x282 |
Python实现
1 | def bfi_visual(dest, src, lsb, width, is_64bit=False): |