白乐天

道阻且长,行则将至。

ARM汇编

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位混合指令长度),称为A32T32指令集 。

ARMv8-A的双模式设计实现了从32位到64位的过渡。

AArch64执行状态

寄存器

  • A64共有32个64位寄存器(X0到X30)。

  • 每个寄存器均支持 64 位和 32 位访问(例如 X0W0 分别表示寄存器的高 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 位通用寄存器(R0R15)。

  • PC(程序计数器)占用 R15R14 通常作为链接寄存器(LR),R13 通常作为栈指针(SP)。

数据处理指令

指令的基本语法

1
2
3
4
5
<操作码> <目标寄存器>, <操作数1>, <操作数2>

<操作码>:指令的操作代码,决定了具体的功能(如加法、减法、逻辑操作等)。
<目标寄存器>:结果存储的目标寄存器。
<操作数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

MULSMUL 指令类似,但是它还会更新条件码(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
2
ORR <Wd>, <Wn>, <Wm>{, <shift> #<amount>}  // 32位操作
ORR <Xd>, <Xn>, <Xm>{, <shift> #<amount>} // 64位操作

EOR(Exclusive OR)

按位异或

LSR(Logical Shift Right

逻辑右移指令,用于将寄存器中的数值向右移动指定的位数,高位补零。

1
2
LSR{S}{cond} Rd, Rm, #imm      @ 立即数移位
LSR{S}{cond} Rd, Rm, Rs @ 寄存器指定移位量

示例

1
2
MOV R0, #16         @ R0 = 16
LSR R1, R0, #2 @ R1 = 16 / 4 = 4

UBFIZ (Unsigned Bitfield Insert and Zero)

是 ARM 架构中的一个位域操作指令。

1
2
3
4
5
6
7
UBFIZ Wd, Wn, #lsb, #width   // 32位寄存器版本
UBFIZ Xd, Xn, #lsb, #width // 64位寄存器版本

Wd/Xd:目标寄存器
Wn/Xn:源寄存器
lsb:目标中的最低有效位位置(左移量)
width:要插入的位数
  • 将源寄存器 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
2
LDR R0, [R1]      ; 将内存地址R1中的值加载到寄存器R0
LDR R2, [R3, #4] ; 将R3 + 4地址中的值加载到寄存器R2

LDRB(Load Register Byte)

从内存中加载一个字节(8位)到寄存器,并将该字节零扩展(Zero-Extend)为32位。

1
2
LDRB{cond}{B} Rt, [Rn, Operand2]{!}
LDRB{cond}{B} Rt, [Rn], Operand2
1
2
LDR R1, =buffer     @ R1指向缓冲区首地址
LDRB R0, [R1] @ 从缓冲区加载第一个字节到R0(零扩展为32位)

LDP(Load Pair of Registers)

一条高效的双寄存器加载指令,用于从内存中连续加载两个寄存器的值

LDUR(Load Unscaled Register)

是一条用于从内存加载数据到寄存器的指令。它与常见的 LDR(Load Register)指令类似,但在偏移量范围和寻址模式上有特殊设计。

从 基址寄存器 (XnSP) + 偏移量 (simm) 的内存地址加载数据到目标寄存器。

1
2
3
4
SUB SP, SP, #32       // 分配栈空间
STR X0, [SP, #16] // 存储变量到栈
LDUR X1, [SP, #16] // 从栈加载变量(偏移量为正)
LDUR X2, [SP, #-8] // 加载 SP 之前的内存数据(偏移量为负)

LDRSW(Load Register Signed Word)

用于从内存中加载 32 位有符号整数,并将其符号扩展至 64 位目标寄存器。

1
2
LDRSW <Xd>, [<Xn|SP>{, #<pimm>}]   ; 立即数偏移模式
LDRSW <Xd>, [<Xn|SP>, <Xm>{, <extend> {#<amount>}}] ; 寄存器偏移模式

STR(Store Register)

将寄存器中的数据存储到内存地址中。

1
2
STR R0, [R1]      ; 将寄存器R0的值存储到R1指向的内存地址
STR R2, [R3, #8] ; 将R2的值存储到R3 + 8的内存地址

STRB(Store Register Byte)

将寄存器中的低8位数据(一个字节)存储到内存中。

1
2
STRB{cond}{B} Rt, [Rn, Operand2]{!}
STRB{cond}{B} Rt, [Rn], Operand2
1
2
3
MOV R0, #'A'        @ R0 = ASCII字符'A'(0x41)
LDR R1, =buffer @ R1指向缓冲区首地址
STRB R0, [R1] @ 将'A'存储到缓冲区

STP(Store Pair)

将 两个寄存器 的值存储到 连续的内存地址 中。

1
STP <寄存器1>, <寄存器2>, [<基址寄存器>{, #偏移量}]

示例

1
2
3
STP X0, X1, [X2]        ; 将X0存入X2,X1存入X2+8
STP W3, W4, [SP, #-8]! ; 前变址:SP减8,存入W3和W4(32位,占8字节)
STP X5, X6, [X7, #16] ; 将X5存入X7+16,X6存入X7+24

EXTR(Extract Register)

是一条用于 提取寄存器位域 的指令。它可以从两个寄存器的组合位域中提取指定范围的位,并将结果存入目标寄存器。

控制流

分支指令

B(Branch)

B指令用于无条件跳转到一个由立即数偏移指定的目标地址。

执行该指令时,程序计数器(PC)被更新为目标地址,从而实现流程的跳转。

1
2
;无条件跳转到标签label
B label ; 无条件跳转到标签label指定的 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
2
3
4
5
6
7
// 示例1:通过寄存器 X1 中的地址跳转
MOV X1, #0x4000 // 将目标地址 0x4000 存入 X1
BR X1 // 跳转到 X1 中的地址

// 示例2:从子程序返回(在 AArch64 中常用 RET 指令,但也可使用 BR)
// 假设链接寄存器在 X30 中
BR X30 // 跳转到 X30 保存的返回地址

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)

循环右移

将一个二进制数的位向右移动指定的位数,并将被“移出”的位重新填充到左侧。

固定位宽循环右移

操作数numn 位的整数,循环右移 k 位:

1
2
3
4
5
6
7
def ror(num: int, k: int, bits: int = 32) -> int:
k = k % bits # 处理 k ≥ bits 的情况
mask = (1 << bits) - 1 # 生成指定位数的掩码(如 32 位掩码 0xFFFFFFFF)
num &= mask # 确保数值在指定位宽范围内
right = num >> k # 右移 k 位
left = num << (bits - k) # 左移补位
return (left | right) & mask

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 位,通常与其他指令(如 ADDLDR 等)结合使用。

核心功能

  • 有符号扩展:将 32 位 W 寄存器的值 有符号扩展 为 64 位,高位补符号位(01)。
  • 移位操作:可对扩展后的值进行左移(如 SXTW #2 表示左移 2 位)。
  • 典型用途:在地址计算或算术运算中,将 32 位有符号索引或偏移量扩展为 64 位。

BFXIL(Bit Field Extract and Insert Low)

是一条 位域操作指令,用于从源寄存器中提取一个位域,并将其插入到目标寄存器的 低有效位部分。

  1. 从源寄存器 <Xn>[lsb, lsb + width - 1] 位提取一个位域。
  2. 将该位域插入到目标寄存器 <Xd>[0, width - 1] 位(低有效位部分)。
  3. 目标寄存器的高位([width, 63][width, 31])保持不变。
1
2
Xd[width-1:0] = Xn[lsb+width-1:lsb]  // 低 width 位被替换
Xd[63:width] 保持不变 // 高位不变

示例

1
2
3
4
5
6
7
8
9
10
11
12
"bfxil w30, w24, #3, #5" w30=0xffff9480 w24=0xfffffca4 => w30=0xffff9494

W30 = 0xFFFF9480 bin:11111111 11111111 10010100 10000000
W24 = 0xFFFFFCA4 bin:11111111 11111111 11111100 10100100

起始位 lsb = 3, 宽度 width = 5
提取 W24 的 [3:7] 位
从右向左数,最低位为 0 : 10100

插入目标位域:[4:0]
低 8 位更新为:10010100(0x94)
w30 = 0xffff9494

Python实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def bfxil_visual(dest, src, lsb, width, is_64bit=False):
"""
更具可视化的BFXIL实现,展示中间步骤

参数:
dest - 目标值
src - 源值
lsb - 源中的起始位位置
width - 提取的位宽
is_64bit - 是否为64位操作,默认为False(32位)

返回:
更新后的dest值
"""
# 确定操作位宽
max_bits = 64 if is_64bit else 32

# 显示输入值
print(f"源值(src) = {bin(src)[2:].zfill(max_bits)}")
print(f"目标值(dest) = {bin(dest)[2:].zfill(max_bits)}")
print(f"lsb = {lsb}")
print(f"width = {width}")

# 步骤1: 创建源掩码并提取位
src_mask = ((1 << width) - 1) << lsb
print(f"源掩码 = {bin(src_mask)[2:].zfill(max_bits)}")

extracted_with_position = src & src_mask
print(f"提取的位(带位置) = {bin(extracted_with_position)[2:].zfill(max_bits)}")

extracted = (extracted_with_position >> lsb)
print(f"提取的位(移位后) = {bin(extracted)[2:].zfill(max_bits)}")

# 步骤2: 创建目标掩码并清除低位
dest_mask = ~((1 << width) - 1)
if not is_64bit:
dest_mask &= 0xFFFFFFFF
print(f"目标掩码 = {bin(dest_mask)[2:].zfill(max_bits)}")

dest_cleared = dest & dest_mask
print(f"目标(清除低位) = {bin(dest_cleared)[2:].zfill(max_bits)}")

# 步骤3: 合并结果
result = dest_cleared | extracted
print(f"最终结果 = {bin(result)[2:].zfill(max_bits)}")

if not is_64bit:
result &= 0xFFFFFFFF

return result

def bfxil(dest, src, lsb, width, is_64bit=False):
max_bits = 64 if is_64bit else 32
src_mask = ((1 << width) - 1) << lsb
extracted_with_position = src & src_mask
extracted = (extracted_with_position >> lsb)
dest_mask = ~((1 << width) - 1)
if not is_64bit:
dest_mask &= 0xFFFFFFFF
dest_cleared = dest & dest_mask
result = dest_cleared | extracted
if not is_64bit:
result &= 0xFFFFFFFF
return result

print(hex(bfxil(0xffff9480,0xfffffca4,3,5,False)))

BFI(Bit Field Insert)

是 ARM AArch64 架构中的 位域插入指令,用于将源寄存器的指定位域插入到目标寄存器的任意位置,同时保持目标寄存器其他位不变。

  1. 从源寄存器提取 width(最低有效位开始)。
  2. 插入到目标寄存器的 [lsb, lsb + width - 1]
  3. 目标寄存器其他位保持不变
1
2
Xd[lsb + width - 1 : lsb] = Xn[width - 1 : 0]
Xd[其他位] 保持不变

示例

1
2
3
4
5
6
7
8
0x120a0684: "bfi w30, w24, #6, #8" w30=0x2 w24=0xa => w30=0x282

从 W24 提取 8 位(#8)
提取最低 8 位([7:0]):00001010(0xA)

插入到 W30 的 [6:13] 位(#6 + #8)
目标寄存器 W30 = 0x2(00000010)
插入后:1010000010 (0x282)

Python实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def bfi_visual(dest, src, lsb, width, is_64bit=False):
"""
更具可视化的BFI实现,展示中间步骤

参数:
dest - 目标值
src - 源值
lsb - 目标中的起始位位置
width - 插入的位宽
is_64bit - 是否为64位操作,默认为False(32位)

返回:
更新后的dest值
"""
# 确定操作位宽
max_bits = 64 if is_64bit else 32

# 显示输入值
print(f"源值(src) = {bin(src)[2:].zfill(max_bits)}")
print(f"目标值(dest) = {bin(dest)[2:].zfill(max_bits)}")
print(f"lsb = {lsb}")
print(f"width = {width}")

# 步骤1: 创建源掩码并提取位
src_mask = (1 << width) - 1
print(f"源掩码 = {bin(src_mask)[2:].zfill(max_bits)}")

src_bits = src & src_mask
print(f"提取的源位 = {bin(src_bits)[2:].zfill(max_bits)}")

# 步骤2: 创建目标掩码
insert_mask = src_mask << lsb
print(f"插入位置掩码 = {bin(insert_mask)[2:].zfill(max_bits)}")

dest_mask = ~insert_mask
if not is_64bit:
dest_mask &= 0xFFFFFFFF
print(f"目标掩码 = {bin(dest_mask)[2:].zfill(max_bits)}")

# 步骤3: 清除目标中对应位置
dest_cleared = dest & dest_mask
print(f"目标(已清除位) = {bin(dest_cleared)[2:].zfill(max_bits)}")

# 步骤4: 将源位移到正确位置
src_positioned = src_bits << lsb
print(f"源位(定位后) = {bin(src_positioned)[2:].zfill(max_bits)}")

# 步骤5: 合并结果
result = dest_cleared | src_positioned
print(f"最终结果 = {bin(result)[2:].zfill(max_bits)}")

if not is_64bit:
result &= 0xFFFFFFFF

return result

def bfi(dest, src, lsb, width, is_64bit=False):
max_bits = 64 if is_64bit else 32
src_mask = (1 << width) - 1
src_bits = src & src_mask
insert_mask = src_mask << lsb
dest_mask = ~insert_mask
if not is_64bit:
dest_mask &= 0xFFFFFFFF
dest_cleared = dest & dest_mask
src_positioned = src_bits << lsb
result = dest_cleared | src_positioned
if not is_64bit:
result &= 0xFFFFFFFF
return result

print(hex(bfi(0x2,0xa,6,8,True)))