跳转至

AArch64 架构基础知识

Pwn 速查

  • 函数参数:x0-x7,返回值:x0;浮点参数/返回值走 v0-v7 / v0
  • Linux 系统调用:调用号放 x8,参数放 x0-x5,指令为 svc #0
  • 栈指针 sp 在调用边界必须保持 16 字节对齐。

易错点

x18 是平台寄存器/临时寄存器,部分平台 ABI 会保留它;跨平台 shellcode 或 ROP 里不要默认随便破坏。

AArch64 寄存器详解

通用寄存器 (X0-X30, W0-W30)

寄存器 用途 备注
X0-X7 参数寄存器 / 返回值寄存器 64 位视图
W0-W7 参数寄存器 / 返回值寄存器 对应 X0-X7 的低 32 位视图
X8 间接结果位置寄存器 / Linux syscall number 调用者保存
X9-X15 临时寄存器 调用者保存
X16-X17 程序内调用临时寄存器 IP0, IP1
X18 平台寄存器 / 临时寄存器 平台 ABI 可能保留
X19-X28 保存寄存器 被调用者保存
X29 帧指针 FP
X30 链接寄存器 LR
SP 栈指针 16 字节对齐
PC 程序计数器 不可作为普通通用寄存器直接读写

寄存器详细用途

寄存器 别名 主要用途 调用约定 大小
X0 - 第 1 个参数 / 返回值 调用者保存 64 位
X1 - 第 2 个参数 / 第 2 个返回值 调用者保存 64 位
X2 - 第 3 个参数 调用者保存 64 位
X3 - 第 4 个参数 调用者保存 64 位
X4 - 第 5 个参数 调用者保存 64 位
X5 - 第 6 个参数 调用者保存 64 位
X6 - 第 7 个参数 调用者保存 64 位
X7 - 第 8 个参数 调用者保存 64 位
X8 - 间接结果位置 / Linux syscall number 调用者保存 64 位
X9-X15 - 临时寄存器 调用者保存 64 位
X16 IP0 程序内调用临时寄存器 调用者保存 64 位
X17 IP1 程序内调用临时寄存器 调用者保存 64 位
X18 - 平台寄存器 / 临时寄存器 平台相关 64 位
X19-X28 - 保存寄存器 被调用者保存 64 位
X29 FP 帧指针 被调用者保存 64 位
X30 LR 链接寄存器 特殊 64 位
SP - 栈指针 特殊 64 位
PC - 程序计数器 特殊 64 位

W 寄存器(32 位视图)

Note

W0-W30X0-X30 的低 32 位视图:写入 Wn 会自动清零对应 Xn 的高 32 位;读取 Wn 只访问低 32 位。

SIMD / 浮点寄存器

寄存器视图 大小 说明
V0-V31 128 位 SIMD / 浮点完整视图
D0-D31 64 位 双精度浮点视图
S0-S31 32 位 单精度浮点视图
H0-H31 16 位 半精度浮点视图
B0-B31 8 位 字节视图

特殊寄存器

寄存器 作用
NZCV 条件标志寄存器(Negative, Zero, Carry, oVerflow)
FPCR 浮点控制寄存器
FPSR 浮点状态寄存器
TPIDR_EL0 用户态线程标识寄存器

Linux AArch64 系统调用表

常用系统调用号

常用系统调用号(展开查看)
C
#define __NR_openat                     56
#define __NR_close                      57
#define __NR_lseek                      62
#define __NR_read                       63
#define __NR_write                      64
#define __NR_readv                      65
#define __NR_writev                     66
#define __NR_pread64                    67
#define __NR_pwrite64                   68
#define __NR_preadv                     69
#define __NR_pwritev                    70
#define __NR_sendfile                   71
#define __NR_execve                     221
#define __NR_mmap                       222

系统调用约定

系统调用查询

项目 约定
系统调用号 x8
参数 1-6 x0, x1, x2, x3, x4, x5
返回值 x0
触发指令 svc #0

Note

Linux AArch64 失败时通常在 x0 返回 -errno(例如 -1-4095)。

系统调用示例

GAS
// write(1, "Hello", 5)
mov x0, #1          // fd = 1 (stdout)
adr x1, hello_str   // buf = "Hello"
mov x2, #5          // count = 5
mov x8, #64         // __NR_write
svc #0              // 系统调用

// exit(0)
mov x0, #0          // status = 0
mov x8, #93         // __NR_exit
svc #0

函数调用约定 (AAPCS64)

参数传递

项目 AAPCS64 约定
前 8 个整数 / 指针参数 x0-x7
前 8 个浮点 / SIMD 参数 v0-v7(如 s0-s7, d0-d7
更多参数 通过栈上传参区域传递
整数 / 指针返回值 x0
浮点返回值 v0
大型结构体返回 通过 x8 传递结果地址

Warning

调用边界处 sp 必须 16 字节对齐;ROP 链里手动 pivot 栈时也要注意。

栈帧结构

Text Only
高地址
+------------------+
| 第n个参数        |  <- 超过x0-x7的参数
| ...              |
| 第9个参数        |
+------------------+
| 返回地址 (LR)    |  <- 函数调用时保存
| 老的帧指针(x29)  |  <- 可选但推荐
| 局部变量         |
| 保存的寄存器     |  <- x19-x28需要保存
+------------------+  <- SP (当前栈指针,16字节对齐)
低地址

函数调用流程

GAS
// 调用者 (Caller)
stp x29, x30, [sp, #-16]!   // 保存帧指针和返回地址
mov x29, sp                 // 设置新的帧指针
stp x19, x20, [sp, #-16]!   // 保存其他需要的寄存器
mov x0, #arg1               // 设置参数1
mov x1, #arg2               // 设置参数2
bl function                 // 调用函数
// x0 包含返回值
ldp x19, x20, [sp], #16     // 恢复寄存器
ldp x29, x30, [sp], #16     // 恢复帧指针和返回地址
ret                         // 返回

// 被调用者 (Callee)
function:
stp x29, x30, [sp, #-16]!   // 保存帧指针和返回地址
mov x29, sp                 // 设置帧指针
stp x19, x20, [sp, #-16]!   // 保存需要使用的寄存器
// 函数体
mov x0, #return_val         // 设置返回值
ldp x19, x20, [sp], #16     // 恢复寄存器
ldp x29, x30, [sp], #16     // 恢复帧指针和返回地址
ret                         // 返回

指令集合

AArch64 指令字长固定为 4 字节;AArch32 的 ARM 状态为 4 字节,Thumb/Thumb-2 状态则可能是 2 字节或 4 字节。

指令格式

一般格式如下:

GAS
MNEMONIC {目的寄存器}, 操作符1, 操作符2
助记符 {目的寄存器}, 操作符1, 操作符2

由于AArch64指令的灵活性,不是全部的指令都满足这个模板,不过大部分都满足了。下面来说说模板中的含义:

GAS
1
2
3
4
MNEMONIC     - 指令的助记符如ADD
{目的寄存器} - 存储结果的目的寄存器
操作符1      - 第一个操作数,寄存器或者是一个立即数
操作符2      - 第二个(可变的)操作数,可以是一个立即数或者寄存器或者有偏移量的寄存器

和 AArch32 不同,AArch64 没有“几乎所有指令都能加条件后缀”的模型;条件主要体现在 b.condcselccmp 等专门指令上。

条件执行

AArch64通过条件分支指令实现条件执行:

条件码 助记符 含义 NZCV标志位条件
0000 EQ 相等 Z = 1
0001 NE 不相等 Z = 0
0010 CS/HS 进位/无符号大于等于 C = 1
0011 CC/LO 无进位/无符号小于 C = 0
0100 MI 负数 N = 1
0101 PL 正数或零 N = 0
0110 VS 溢出 V = 1
0111 VC 无溢出 V = 0
1000 HI 无符号大于 C=1且Z=0
1001 LS 无符号小于等于 C=0或Z=1
1010 GE 有符号大于等于 N = V
1011 LT 有符号小于 N ≠ V
1100 GT 有符号大于 Z=0且N=V
1101 LE 有符号小于等于 Z=1或N≠V
1110 AL 总是执行 任何
1111 NV 保留/特殊编码;多数普通场景不要依赖 不建议使用

示例:

GAS
1
2
3
4
cmp x0, #5         // 比较x0和5
b.gt greater       // 如果x0 > 5,跳转到greater
b.le less_equal    // 如果x0 <= 5,跳转到less_equal
csel x1, x2, x3, gt // 如果条件为真选择x2,否则选择x3

操作数类型

第二操作数是一个可变操作数,可以以各种形式使用:

GAS
1
2
3
4
5
6
7
#123                    // 立即数
Xn                      // 64位寄存器
Wn                      // 32位寄存器
Xn, LSL #n              // 对寄存器中的值进行逻辑左移n位后的值
Xn, LSR #n              // 对寄存器中的值进行逻辑右移n位后的值
Xn, ASR #n              // 对寄存器中的值进行算术右移n位后的值
Xn, ROR #n              // 对寄存器中的值进行循环右移n位后的值

基本示例:

GAS
1
2
3
4
ADD   x0, x1, x2         // 将第一操作数x1的内容与第二操作数x2的内容相加,将结果存储到x0中
ADD   x0, x1, #2         // 将第一操作数x1的内容与第二操作数一个立即数相加,将结果存到x0中
MOV   x0, #5             // 将立即数5移动到x0中
LSL   x0, x1, #1         // 将x1寄存器中的值逻辑左移1位后存入x0

指令分类

AArch64指令可以分为以下几类:

数据处理指令

指令 含义 示例 描述
MOV 移动数据 mov x0, x1 将x1的值复制到x0
MVN 取反码移动数据 mvn x0, x1 将x1按位取反后移动到x0
ADD 数据相加 add x0, x1, x2 x0 = x1 + x2
SUB 数据相减 sub x0, x1, x2 x0 = x1 - x2
MUL 数据相乘 mul x0, x1, x2 x0 = x1 * x2
UDIV 无符号除法 udiv x0, x1, x2 x0 = x1 / x2 (无符号)
SDIV 有符号除法 sdiv x0, x1, x2 x0 = x1 / x2 (有符号)
AND 比特位与 and x0, x1, x2 x0 = x1 & x2
ORR 比特位或 orr x0, x1, x2 x0 = x1 | x2
EOR 比特位异或 eor x0, x1, x2 x0 = x1 ^ x2
BIC 位清除 bic x0, x1, x2 x0 = x1 & (~x2)
CMP 比较操作 cmp x0, x1 比较x0和x1,设置标志位
CMN 负数比较 cmn x0, x1 比较x0和-x1,设置标志位
TST 测试 tst x0, x1 执行x0 & x1,仅设置标志位

移位操作指令

指令 含义 示例 描述
LSL 逻辑左移 lsl x0, x1, #2 将x1左移2位后存入x0
LSR 逻辑右移 lsr x0, x1, #2 将x1逻辑右移2位后存入x0
ASR 算术右移 asr x0, x1, #2 将x1算术右移2位后存入x0
ROR 循环右移 ror x0, x1, #2 将x1循环右移2位后存入x0

分支和跳转指令

指令 含义 示例 描述
B 分支跳转 b label 无条件跳转到标签
BL 链接分支跳转 bl function 跳转并保存返回地址到x30
BR 分支跳转寄存器 br x0 跳转到x0地址
BLR 链接分支跳转寄存器 blr x0 跳转到x0地址并保存返回地址
RET 返回 ret 从x30返回
CBZ 零分支 cbz x0, label 如果x0为0则跳转
CBNZ 非零分支 cbnz x0, label 如果x0不为0则跳转
TBZ 测试位零分支 tbz x0, #1, label 如果x0的第1位为0则跳转
TBNZ 测试位非零分支 tbnz x0, #1, label 如果x0的第1位为1则跳转

加载/存储指令

指令 含义 示例 描述
LDR 加载寄存器 ldr x0, [x1] 从x1地址加载64位数据到x0
LDRB 加载字节 ldrb w0, [x1] 从x1地址加载8位数据到w0
LDRH 加载半字 ldrh w0, [x1] 从x1地址加载16位数据到w0
LDRSB 加载有符号字节 ldrsb x0, [x1] 加载8位有符号数据并扩展
LDRSH 加载有符号半字 ldrsh x0, [x1] 加载16位有符号数据并扩展
LDRSW 加载有符号字 ldrsw x0, [x1] 加载32位有符号数据并扩展
STR 存储寄存器 str x0, [x1] 将x0的64位数据存储到x1地址
STRB 存储字节 strb w0, [x1] 将w0的低8位存储到x1地址
STRH 存储半字 strh w0, [x1] 将w0的低16位存储到x1地址

多寄存器加载/存储指令

指令 含义 示例 描述
LDP 加载寄存器对 ldp x0, x1, [x2] 从x2地址加载两个64位数据
STP 存储寄存器对 stp x0, x1, [x2] 将两个64位数据存储到x2地址

Note

AArch64 没有 AArch32 那种 LDM/STM {多寄存器列表} 指令,常见批量保存/恢复通常由多条 LDP/STP 完成。

特殊指令

指令 含义 示例 描述
MRS 系统寄存器读取 mrs x0, nzcv 将NZCV内容复制到x0
MSR 系统寄存器写入 msr nzcv, x0 将x0内容复制到NZCV
SVC 系统调用 svc #0 触发系统调用中断
NOP 空操作 nop 不执行任何操作
WFI 等待中断 wfi 进入低功耗模式等待中断
DMB 数据内存屏障 dmb sy 数据内存屏障
DSB 数据同步屏障 dsb sy 数据同步屏障
ISB 指令同步屏障 isb 指令同步屏障

重点指令详解

算术运算指令

指令 计算公式 示例 备注
ADD Xd, Xn, Xm Xd = Xn + Xm add x0, x1, x2 加法运算
ADD Xd, Xn, #imm Xd = Xn + #imm add x0, x1, #5 立即数加法
SUB Xd, Xn, Xm Xd = Xn - Xm sub x0, x1, x2 减法
SUB Xd, Xn, #imm Xd = Xn - #imm sub x0, x1, #5 立即数减法
MUL Xd, Xn, Xm Xd = Xn * Xm mul x0, x1, x2 64位乘法
MADD Xd, Xn, Xm, Xa Xd = Xa + (Xn * Xm) madd x0, x1, x2, x3 乘加运算
MSUB Xd, Xn, Xm, Xa Xd = Xa - (Xn * Xm) msub x0, x1, x2, x3 乘减运算
SMULL Xd, Wn, Wm Xd = Wn * Wm smull x0, w1, w2 有符号乘法扩展
UMULL Xd, Wn, Wm Xd = Wn * Wm umull x0, w1, w2 无符号乘法扩展
UDIV Xd, Xn, Xm Xd = Xn / Xm udiv x0, x1, x2 无符号除法
SDIV Xd, Xn, Xm Xd = Xn / Xm sdiv x0, x1, x2 有符号除法

数据传输指令示例

指令 目的 描述
MOV x0 x1 将 x1 里面的数据复制到 x0 中
MOV x0 #0x1234 将立即数0x1234加载到x0中
MRS x0 nzcv 将特殊寄存器 NZCV 里面的数据复制到 x0 中
MSR nzcv x1 将 x1 里面的数据复制到特殊寄存器 NZCV 中
LDR x0 =0x1234567890abcdef 将地址/常量加载到 x0 中
LDR x0 [x1] 从x1指向的地址加载数据到x0
LDR x0 [x1, #8] 从x1+8地址加载数据到x0
LDR x0 [x1, x2] 从x1+x2地址加载数据到x0
LDR x0 [x1], #8 从x1地址加载数据到x0,然后x1=x1+8
LDR x0 [x1, #8]! x1=x1+8,然后从x1地址加载数据到x0
STR x1 [x0] 将 x1 中的值写入到 x0 中所保存的地址中

栈操作指令

指令 操作数 描述 等价指令
STP x0, x1, [sp, #-16]! 将 x0,x1 压栈并预递减 str x0,[sp,#-16]!; str x1,[sp,#8]
LDP x0, x1, [sp], #16 从栈中弹出到 x0,x1 并后递增 ldr x0,[sp]; ldr x1,[sp,#8]; add sp,sp,#16
STP x29, x30, [sp, #-16]! 保存帧指针和返回地址 标准函数序言
LDP x29, x30, [sp], #16 恢复帧指针和返回地址 标准函数结尾

寻址模式详解

AArch64支持多种灵活的寻址模式:

立即数寻址
GAS
mov x0, #100        // 将立即数100加载到x0
add x1, x2, #4      // x1 = x2 + 4
寄存器寻址
GAS
mov x0, x1          // 将x1的值复制到x0
add x0, x1, x2      // x0 = x1 + x2
寄存器间接寻址
GAS
ldr x0, [x1]        // 从x1指向的地址加载数据到x0
str x0, [x1]        // 将x0的数据存储到x1指向的地址
基址加偏移寻址
GAS
1
2
3
ldr x0, [x1, #8]    // 从x1+8地址加载数据,x1不变
ldr x0, [x1, x2]    // 从x1+x2地址加载数据,x1不变
ldr x0, [x1, x2, lsl #3]  // 从x1+(x2<<3)地址加载数据
前索引寻址(预递增/递减)
GAS
ldr x0, [x1, #8]!   // x1 = x1 + 8, 然后从x1地址加载数据
// AArch64 的预索引写回只支持立即数偏移;寄存器偏移不能写回
后索引寻址(后递增/递减)
GAS
ldr x0, [x1], #8    // 从x1地址加载数据,然后x1 = x1 + 8
// AArch64 的后索引写回只支持立即数偏移;寄存器偏移不能写回

重要说明:

  • ldr x3, [x1, x2, lsl #3] - 不会改变寄存器x1的值
  • ldr x3, [x1, #8]! - 感叹号代表事先更新,会改变x1的值为x1+8
  • ldr x3, [x1], #8 - 事后更新,先加载数据,然后改变x1的值为x1+8

数据类型支持

AArch64支持多种数据类型的操作:

数据类型表
数据类型 大小 后缀 有符号后缀 描述
字节 (Byte) 8位 B SB 无符号/有符号字节
半字 (Halfword) 16位 H SH 无符号/有符号半字
字 (Word) 32位 W SW 无符号/有符号32位数据
双字 (Doubleword) 64位 X 64位数据
加载指令示例
GAS
1
2
3
4
5
6
7
ldrb  w0, [x1]      // 加载无符号字节,高位清零
ldrsb x0, [x1]      // 加载有符号字节,符号扩展到64位
ldrh  w0, [x1]      // 加载无符号半字,高位清零
ldrsh x0, [x1]      // 加载有符号半字,符号扩展到64位
ldr   w0, [x1]      // 加载32位字,x0高32位清零
ldrsw x0, [x1]      // 加载有符号32位字,符号扩展到64位
ldr   x0, [x1]      // 加载64位双字
存储指令示例
GAS
1
2
3
4
strb  w0, [x1]      // 存储w0的低8位
strh  w0, [x1]      // 存储w0的低16位
str   w0, [x1]      // 存储w0的32位
str   x0, [x1]      // 存储x0的64位
数据类型范围
类型 范围 用途
无符号字节 0 ~ 255 字符、小整数
有符号字节 -128 ~ 127 有符号小整数
无符号半字 0 ~ 65535 较大整数、Unicode
有符号半字 -32768 ~ 32767 有符号整数
无符号字 0 ~ 4294967295 32位整数、地址
有符号字 -2147483648 ~ 2147483647 32位有符号整数
无符号双字 0 ~ 18446744073709551615 64位整数、地址

常用编程模式

循环结构

GAS
// for循环示例: for(int i=0; i<10; i++)
mov x0, #0          // i = 0
loop:
    cmp x0, #10     // 比较 i 和 10
    b.ge loop_end   // 如果 i >= 10 跳出循环
    // 循环体代码
    add x0, x0, #1  // i++
    b loop          // 跳回循环开始
loop_end:

// while循环示例: while(condition)
while_loop:
    // 检查条件的代码
    cmp x0, #0      // 检查条件
    b.eq while_end  // 条件为假则退出
    // 循环体代码
    b while_loop    // 继续循环
while_end:

条件判断

GAS
// if-else 结构
cmp x0, #5          // 比较 x0 和 5
b.lt else_branch    // 如果 x0 < 5 跳到 else
    // if 分支代码
    mov x1, #1
    b endif
else_branch:
    // else 分支代码
    mov x1, #0
endif:

// switch-case 结构
cmp x0, #1
b.eq case1
cmp x0, #2
b.eq case2
cmp x0, #3
b.eq case3
b default_case

case1:
    // case 1 代码
    b switch_end
case2:
    // case 2 代码
    b switch_end
case3:
    // case 3 代码
    b switch_end
default_case:
    // 默认情况代码
switch_end:

函数调用模板

GAS
// 标准函数模板
function_name:
    stp x29, x30, [sp, #-16]!   // 保存帧指针和返回地址
    mov x29, sp                 // 设置帧指针
    stp x19, x20, [sp, #-16]!   // 保存其他需要的寄存器
    sub sp, sp, #32             // 为局部变量分配栈空间

    // 函数体
    // 参数在 x0-x7 中
    // 局部变量使用栈空间

    mov x0, #return_val         // 设置返回值
    add sp, sp, #32             // 释放栈空间
    ldp x19, x20, [sp], #16     // 恢复寄存器
    ldp x29, x30, [sp], #16     // 恢复帧指针和返回地址
    ret                         // 返回

// 调用函数
    mov x0, #arg1       // 第一个参数
    mov x1, #arg2       // 第二个参数
    mov x2, #arg3       // 第三个参数
    mov x3, #arg4       // 第四个参数
    bl function_name    // 调用函数
    // 返回值在 x0 中

数组操作

GAS
// 数组遍历示例
adr x0, array_base      // 数组基地址
mov x1, #0              // 索引 i = 0
mov x2, #array_size     // 数组大小

array_loop:
    cmp x1, x2          // 比较 i 和 size
    b.ge array_end      // i >= size 则结束

    ldr x3, [x0, x1, lsl #3]  // 加载 array[i] (假设64位数组)
    // 处理 x3 中的数据

    add x1, x1, #1      // i++
    b array_loop
array_end:

// 字符串长度计算
adr x0, string_ptr      // 字符串指针
mov x1, #0              // 长度计数器

strlen_loop:
    ldrb w2, [x0, x1]   // 加载当前字符
    cbz w2, strlen_end  // 检查是否为'\0',是则结束
    add x1, x1, #1      // 长度++
    b strlen_loop
strlen_end:
    // x1 包含字符串长度

位操作技巧

GAS
// 检查第n位是否为1
mov x1, #1
lsl x1, x1, x0      // x1 = 1 << n
tst x2, x1          // 测试 x2 的第n位
b.ne bit_is_set     // 如果位为1则跳转

// 设置第n位为1
mov x1, #1
lsl x1, x1, x0      // x1 = 1 << n
orr x2, x2, x1      // x2 |= (1 << n)

// 清除第n位
mov x1, #1
lsl x1, x1, x0      // x1 = 1 << n
bic x2, x2, x1      // x2 &= ~(1 << n)

// 切换第n位
mov x1, #1
lsl x1, x1, x0      // x1 = 1 << n
eor x2, x2, x1      // x2 ^= (1 << n)

// 计算2的幂次
mov x1, #1
lsl x1, x1, x0      // x1 = 2^x0

// 除以2的幂次(无符号)
lsr x1, x2, x0      // x1 = x2 / (2^x0)

// 除以2的幂次(有符号)
asr x1, x2, x0      // x1 = x2 / (2^x0) (保持符号)

内存对齐和优化

GAS
// 8字节对齐检查
tst x0, #7          // 检查低3位
b.ne not_aligned    // 如果不为0则未对齐

// 向上对齐到8字节边界
add x0, x0, #7      // x0 += 7
bic x0, x0, #7      // x0 &= ~7

// 向下对齐到8字节边界
bic x0, x0, #7      // x0 &= ~7

// 快速清零内存块
mov x1, #0          // 清零值
mov x2, x0          // 保存起始地址
add x3, x0, x3      // 计算结束地址
clear_loop:
    cmp x2, x3
    b.ge clear_end
    str x1, [x2], #8  // 存储0并递增地址
    b clear_loop
clear_end:

// 使用SIMD加速内存操作
movi v0.16b, #0     // 设置128位全零向量
clear_simd_loop:
    cmp x2, x3
    b.ge clear_simd_end
    str q0, [x2], #16 // 存储128位0并递增地址
    b clear_simd_loop
clear_simd_end:

条件选择指令

GAS
// 条件选择指令的使用
cmp x0, x1
csel x2, x3, x4, gt  // 如果 x0 > x1,x2 = x3,否则 x2 = x4
csinc x2, x3, x4, eq // 如果相等,x2 = x3,否则 x2 = x4 + 1
csinv x2, x3, x4, ne // 如果不等,x2 = x3,否则 x2 = ~x4
csneg x2, x3, x4, lt // 如果 x0 < x1,x2 = x3,否则 x2 = -x4

// 实现 max(a, b)
cmp x0, x1
csel x2, x0, x1, gt  // x2 = max(x0, x1)

// 实现 min(a, b)
cmp x0, x1
csel x2, x0, x1, lt  // x2 = min(x0, x1)

// 实现 abs(a)
cmp x0, #0
csneg x1, x0, x0, ge // x1 = (x0 >= 0) ? x0 : -x0

这些编程模式涵盖了AArch64汇编中最常用的结构和技巧,可以作为编写AArch64汇编程序的参考模板。AArch64相比ARMv7提供了更加规整的指令集、更大的寄存器、更强的性能和更好的扩展性。