ARM64汇编基础
ARM64(也称 AArch64)有如下寄存器:
- 64 位通用寄存器:
X0~X30 - 栈指针寄存器:
SP - 链接寄存器(返回地址):
LR(X30) - 程序计数器:
PC(不可直接读写)
注1:x0~x7 是 ARM64 ABI 规定的函数参数传递寄存器,这里是将原始参数存入栈,后续可用于回调或者做参数修改
注2:x29 是 ARM64 典型的frame pointer,一般用来标记当前栈帧的开始,便于调用栈复杂操作和回溯。例如:mov x29, sp 将当前sp存入x29寄存器
函数参数传递
ARM64函数参数是通过寄存器优先传递的(寄存器传递效率高,无需栈操作),参数数量超出寄存器或大对象参数,才用栈传递(多余的参数会压到栈上,由被调函数通过栈来读取)
(1) 前 8 个整数类型或指针类型参数,分别用 x0 ~ x7 这 8 个寄存器传递
(2) 如果参数是浮点类型(float、double),用 v0 ~ v7 这 8 个浮点寄存器传递
void foo(int a, int b, int c, int d, int e, int f, int g, int h, int i) { ... } // a,b,c,d,e,f,g,h通过x0,x1...,x7寄存器传递,第9个参数i通过栈传递
(3) 如果参数是小的结构体或数组,ABI 会试图通过一组寄存器传递;如果参数很大(结构体等),可能通过栈或指针传递
函数返回值
返回值一般用 x0(整数/指针) 或 v0(浮点) 返回。如果多个返回值(如结构体),会用 x0/x1 等或栈。
数据处理类指令
| 指令 | 作用 | 示例 |
|---|---|---|
MOV |
数据搬移(赋值) |
MOV X0, X1 // X0 = X1; |
ADD |
加法运算 |
ADD X0, X1, X2 // X0 = X1 + X2; |
SUB |
减法运算 |
SUB X3, X3, #1 // X3 = X3 - 1; |
MUL |
乘法 |
MUL X0, X1, X2 // X0 = X1 * X2; |
DIV |
除法 |
SDIV X0, X2, #5 // X0 = X2 / 5; |
NEG |
取反 |
NEG X1, X2 |
CMP |
比较(设置条件标志位) |
CMP X4, #100 |
TST |
按位与并根据结果设置标志位 |
TST X0, X1 |
逻辑类指令
| 指令 | 作用 | 示例 |
|---|---|---|
AND |
按位与 |
AND X0, X1, X2 |
ORR |
按位或 |
ORR X0, X1, X2 |
EOR |
按位异或 |
EOR X0, X1, X2 |
LSL |
左移 |
LSL X1, X2, #3 |
LSR |
逻辑右移 |
LSR X3, X4, #2 |
ASR |
算术右移 |
ASR X5, X6, #1 |
分支与跳转类指令
| 指令 | 作用 | 示例 |
|---|---|---|
B |
无条件跳转 |
B loop_start // 跳转到loop_start标签 |
BL |
跳转到子程序(带返回地址) |
BL functionA // 调用functionA函数。会保存返回地址到X30寄存器 |
RET |
返回 |
RET |
CBZ |
等于零则跳 |
CBZ X0, label // if (X0==0) goto label; |
CBNZ |
不为零则跳 |
CBNZ W1, label // if (w1!=0) goto label; |
BEQ/ BNE |
条件跳转 (等于/不等于) |
BEQ label |
BGT/ BLT |
条件跳转 (大于/小于) |
BGT label |
内存访问类(读写)指令
| 指令 | 作用 | 示例 |
|---|---|---|
LDR |
加载(从内存到寄存器) | LDR X0, [X1, #8] // 加载地址为X1+8的数据到X0寄存器 |
STR |
存储(从寄存器到内存) | STR W2, [X3] // 将W2数据写到为X3的地址的内存中 |
LDUR |
非对齐加载 | LDUR W5, [X6, #2] |
STUR |
非对齐存储 | STUR X7, [X8, #4] |
栈操作类指令
| 指令 | 作用 | 示例 |
|---|---|---|
PUSH * |
压栈(用 STP 模拟) |
STP X1, X2, [SP, #-16]! // 将当前的帧指针(x1)和返回地址(x2)压入栈,同时为当前函数开辟栈空间(这里是 16字节),[sp, #-16]! 表示先把 SP 指针向下移动 16 字节(为局部变量等腾空间),再把 x1/x2 存到新位置 stp x0, x1, [sp, #0x90] // 将x0、x1的值存储到栈的特定偏移[sp, #0x90] 上 |
POP * |
出栈(用 LDP 模拟) |
LDP X0, X1, [X17, #0x10] // 把 X17+0x10 处的两个 64 位内容装载到 X0、X1。这一般用于取"调用接口参数"(CIF,一种 FFI 调用描述结构)和"函数指针(fn)" ldp x17, x16, [x16] // 以 x16 为地址,从 [x16] 取出 16 字节数据,前8字节放到 x17,后8字节放到 x16 自己 |
其它常见指令
| 指令 | 作用 | 示例 |
|---|---|---|
NOP |
空操作(无结果) | NOP |
ADR |
计算地址寄存到寄存器 | adr x16, #-0x4000 // 把当前指令地址减去 0x4000,存到 x16 |
BR |
跳转到寄存器地址 | br x16 // 无条件跳转到 x16 中保存的地址处继续执行代码 |