assembly-arm
ARM / AArch64 汇编
用途
指导代理学习 AArch64(64 位)和 ARM(32 位 Thumb)汇编:寄存器、调用约定、内联汇编,以及 NEON/SVE SIMD 模式。
触发场景
- "如何阅读 ARM64 汇编输出?"
- "AArch64 的寄存器和调用约定是什么?"
- "如何为 ARM 编写内联汇编?"
- "AArch64 和 ARM Thumb 有什么区别?"
- "如何使用 NEON 内联函数?"
工作流程
1. 生成 ARM 汇编
# AArch64(本机或交叉编译)
aarch64-linux-gnu-gcc -S -O2 foo.c -o foo.s
# 32 位 ARM Thumb
arm-linux-gnueabihf-gcc -S -O2 -mthumb foo.c -o foo.s
# 使用 objdump
aarch64-linux-gnu-objdump -d -S prog
# 在目标设备上使用 GDB
(gdb) disassemble /s main
2. AArch64 寄存器(AAPCS64)
| 寄存器 | 别名 | 用途 |
|---|---|---|
x0–x7 |
— | 参数 1–8 及返回值 |
x8 |
xr |
间接结果位置(结构体返回) |
x9–x15 |
— | 调用者保存的临时寄存器 |
x16–x17 |
ip0, ip1 |
过程内调用临时寄存器(链接器使用) |
x18 |
pr |
平台寄存器(某些 OS 上保留) |
x19–x28 |
— | 被调用者保存 |
x29 |
fp |
帧指针(被调用者保存) |
x30 |
lr |
链接寄存器(返回地址) |
sp |
— | 栈指针(调用时必须 16 字节对齐) |
pc |
— | 程序计数器(不可直接访问) |
xzr |
wzr |
零寄存器(读取恒为 0,写入被丢弃) |
v0–v7 |
q0–q7 |
FP/SIMD 参数和返回值 |
v8–v15 |
— | 被调用者保存的 SIMD(仅低 64 位) |
v16–v31 |
— | 调用者保存的临时寄存器 |
宽度变体:x0(64 位)、w0(32 位,零扩展到 64 位)、h0(16 位)、b0(8 位)。
3. AAPCS64 调用约定
整数/指针参数: x0–x7
浮点/SIMD 参数: v0–v7
返回值: x0(整数)、x0+x1(128 位)、v0(浮点/SIMD)
被调用者保存: x19–x28、x29(fp)、x30(lr)、v8–v15(低 64 位)
调用者保存: 其余所有寄存器
在任何 bl 或 blr 指令处,栈必须 16 字节对齐。
4. 常用 AArch64 指令
| 指令 | 效果 |
|---|---|
mov x0, x1 |
复制寄存器 |
mov x0, #42 |
加载立即数 |
movz x0, #0x1234, lsl #16 |
零扩展移位后载入 |
movk x0, #0xabcd |
保留移位后载入(部分更新) |
ldr x0, [x1] |
从 x1 所指地址加载 64 位 |
ldr x0, [x1, #8] |
从 x1+8 加载 |
str x0, [x1, #8] |
将 x0 存储到 x1+8 |
ldp x0, x1, [sp, #16] |
加载一对寄存器 |
stp x29, x30, [sp, #-16]! |
存储一对寄存器,sp 预减 |
add x0, x1, x2 |
x0 = x1 + x2 |
add x0, x1, #8 |
x0 = x1 + 8 |
sub x0, x1, x2 |
x0 = x1 - x2 |
mul x0, x1, x2 |
x0 = x1 * x2 |
sdiv x0, x1, x2 |
有符号除法 |
udiv x0, x1, x2 |
无符号除法 |
cmp x0, x1 |
为 x0 - x1 设置标志位 |
cbz x0, label |
若 x0 == 0 则跳转 |
cbnz x0, label |
若 x0 != 0 则跳转 |
bl func |
带链接跳转(调用) |
blr x0 |
带链接跳转到 x0 中的地址 |
ret |
返回(跳转到 x30) |
ret x0 |
返回到 x0 中的地址 |
adrp x0, symbol |
PC 相对页地址 |
add x0, x0, :lo12:symbol |
符号偏移的低 12 位 |
5. 典型函数序言/尾声
// 非叶子函数
stp x29, x30, [sp, #-32]! // 保存 fp、lr;分配 32 字节
mov x29, sp // 设置帧指针
stp x19, x20, [sp, #16] // 保存被调用者保存的寄存器
// ... 函数体 ...
ldp x19, x20, [sp, #16] // 恢复
ldp x29, x30, [sp], #32 // 恢复 fp、lr;释放栈帧
ret
// 叶子函数(无调用,无需保存被调用者保存寄存器)
// AArch64 没有红区(red zone)
sub sp, sp, #16 // 分配局部变量空间
// ... 函数体 ...
add sp, sp, #16
ret
6. 内联汇编(GCC/Clang)
// 内存屏障
__asm__ volatile ("dmb ish" ::: "memory");
// 带获取语义的加载(load acquire)
static inline int load_acquire(volatile int *p) {
int val;
__asm__ volatile ("ldar %w0, %1" : "=r"(val) : "Q"(*p));
return val;
}
// 带释放语义的存储(store release)
static inline void store_release(volatile int *p, int val) {
__asm__ volatile ("stlr %w1, %0" : "=Q"(*p) : "r"(val));
}
// 读取系统计数器
static inline uint64_t read_cntvct(void) {
uint64_t val;
__asm__ volatile ("mrs %0, cntvct_el0" : "=r"(val));
return val;
}
AArch64 特定约束:
"Q"— 适合独占/获取/释放指令的内存操作数"r"— 任意通用寄存器"w"— 任意 FP/SIMD 寄存器
7. NEON SIMD 内联函数
#include <arm_neon.h>
// 同时对 4 个浮点数求和
float32x4_t a = vld1q_f32(arr_a); // 加载 4 个浮点数
float32x4_t b = vld1q_f32(arr_b);
float32x4_t c = vaddq_f32(a, b);
vst1q_f32(result, c);
// 水平求和
float32x4_t sum = vpaddq_f32(c, c);
sum = vpaddq_f32(sum, sum);
float total = vgetq_lane_f32(sum, 0);
命名约定:v<op><q>_<type>
q后缀:128 位(quad)向量_f32:float32,_s32:int32,_u8:uint8,等等
关于寄存器参考,请参阅 references/reference.md。
相关技能
- 使用
skills/low-level-programming/assembly-x86了解 x86-64 汇编 - 使用
skills/compilers/cross-gcc了解交叉编译工具链 - 使用
skills/debuggers/gdb配合 gdbserver 调试 ARM 代码
More from killvxk/low-level-dev-skills-zh
binutils
GNU binutils 二进制操作与分析技能。适用场景:使用 ar 管理静态库、使用 strip 或 objcopy 处理二进制文件、使用 addr2line 将地址转换为源码位置、使用 strings 提取文本、或使用 c++filt 对 C++ 名称进行反混淆。触发条件:涉及 ar、strip、objcopy、addr2line、strings、c++filt、ranlib 或二进制后处理任务的查询。
1ebpf
Linux 可观测性和网络的 eBPF 技能。适用场景:使用 libbpf 或 bpftrace 编写 eBPF 程序、挂载 kprobe/tracepoint/XDP 钩子、调试验证器错误、使用 eBPF map,或实现跨内核版本的 CO-RE 可移植性。触发条件:查询 eBPF、bpftool、bpftrace、XDP 程序、libbpf、验证器错误、eBPF map 或使用 BPF 进行内核追踪相关问题。
1clang
C/C++ 项目的 Clang/LLVM 编译器技能。适用场景:使用 clang 或 clang++ 进行诊断、sanitizer 插桩、优化备注、通过 clang-tidy 进行静态分析、通过 lld 实现 LTO,或从 GCC 迁移到 Clang。触发条件:涉及 clang 标志、clang-tidy、clang-format、更好的错误信息、Apple/FreeBSD 工具链或 LLVM 特定优化的查询。涵盖标志选择、诊断调优及与 LLVM 工具的集成。
1gcc
C/C++ 项目的 GCC 编译器技能。适用场景:选择优化级别、警告标志、调试构建、LTO、sanitizer 插桩,或诊断 GCC 编译错误。涵盖调试与发布构建的标志选择、ABI 问题、预处理器宏、配置引导优化(PGO)及与构建系统的集成。触发条件:涉及 gcc 标志、编译错误、性能调优、警告抑制或跨标准编译的查询。
1cmake
C/C++ 项目的 CMake 构建系统技能。适用场景:编写或重构 CMakeLists.txt、配置源外构建、选择生成器(Ninja、Make、VS)、使用 target_link_libraries 管理目标和依赖、通过 find_package 或 FetchContent 集成外部包、启用 Sanitizer、为交叉编译配置工具链文件,或导出 CMake 包。触发条件:涉及 CMakeLists.txt、cmake 配置错误、目标属性、安装规则、CPack 或 CMake Presets 的查询。
1cpp-modules
现代 C++ 项目的 C++20 模块技能。适用场景:使用命名模块、模块分区、头文件单元、CMake MODULE_SOURCES、Clang -fmodules-ts、BMI 缓存问题,或从头文件迁移到模块。触发条件:涉及 C++20 模块、import 语句、模块接口单元、头文件单元或 BMI 文件的查询。
1