1. ARM64 基础与开发环境搭建
ARM64 这门课,咱们从最基础的东西开始。说实话,很多搞嵌入式或系统编程的朋友,一上来就盯着反汇编和优化看,结果连当前芯片跑的是 AArch32 还是 AArch64 都没搞清楚——我见过不止一次这样的翻车现场。所以第一章,咱们先把地基打牢。
1.1 ARM64 架构演进史
ARM 架构从诞生到现在,走了快四十年。我个人习惯把它的演进分成三个阶段:
- ARMv1–ARMv7(经典时代):从 Acorn 电脑的 BBC Micro 开始,到手机时代大放异彩的 Cortex-A8/A9。这个阶段指令集一直是 32 位的,也就是我们常说的 AArch32。
- ARMv8-A(转折点):2011 年发布,首次引入 64 位指令集。注意,它不是完全抛弃 32 位,而是同时支持 AArch64 和 AArch32 两种执行状态。
- ARMv9-A(未来):2021 年发布,在 ARMv8 基础上强化了安全(CCA)、AI(SVE2)和虚拟化。目前高端手机芯片基本都基于 ARMv9。
为什么说 ARMv8 是转折点?因为从它开始,ARM 真正杀入了服务器和数据中心市场。我在 2016 年参与过一个基于 Cavium ThunderX 的存储项目,那是我第一次在服务器上写 64 位 ARM 汇编——嗯,当时踩了不少坑,后面会慢慢聊。
核心认知:ARM64 不是凭空冒出来的,它是 ARMv8-A 架构引入的 AArch64 执行状态的通俗叫法。你听到的 "AArch64"、"ARM64"、"ARMv8-A 64位" 基本是一回事。
1.2 AArch64 与 AArch32 对比
很多初学者会问:64 位不就是寄存器变宽了吗?其实远不止这么简单。我整理了一个对比表,你可以直观感受一下差异:
| 对比项 | AArch32 | AArch64 |
|---|---|---|
| 通用寄存器 | 16 个(r0–r15),32 位 | 31 个(x0–x30),64 位 |
| PC 寄存器 | r15 是 PC,可读写 | PC 独立,不可直接访问 |
| SP 寄存器 | r13 是 SP,多模式共享 | 每个异常等级有独立 SP |
| 指令长度 | 32 位(ARM)/ 16 位(Thumb) | 固定 32 位 |
| 条件执行 | 几乎所有指令可带条件码 | 仅少数指令(如 B.cond)支持 |
| 加载/存储寻址 | 3 种基本寻址模式 | 9 种寻址模式,支持前/后变址 |
| 异常模型 | 7 种处理器模式 | 4 个异常等级(EL0–EL3) |
你看,AArch64 的寄存器数量翻了一倍,而且每个都是 64 位宽。这意味着什么?说白了,函数调用时参数传递可以多用寄存器,少压栈——性能自然就上去了。
另外,AArch64 去掉了 Thumb 指令集,所有指令固定 32 位。你想想看,这对反汇编来说有多友好?不需要像 32 位时代那样先判断当前模式是 ARM 还是 Thumb,再决定怎么解码。我当年做 32 位 ARM 反汇编工具时,最头疼的就是模式切换问题——AArch64 直接把这个坑填平了。
避坑指南:AArch64 的 PC 寄存器不能像 AArch32 那样直接读写。如果你想获取当前指令地址,需要用 ADR 或 ADRL 指令。我曾经在移植一段 32 位汇编代码时,直接用了 MOV PC, #addr——结果编译都过不了。
1.3 交叉编译工具链安装
咱们做 ARM64 开发,通常不是在 ARM 设备上直接编译——毕竟你的开发机大概率是 x86 的。所以需要交叉编译工具链。我个人推荐两种方式:
方式一:使用发行版包管理器(推荐新手)
# Ubuntu / Debian
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
# Fedora / CentOS
sudo dnf install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
# macOS(使用 Homebrew)
brew install aarch64-elf-gcc
装完之后,你的工具链前缀是 aarch64-linux-gnu-。比如编译一个 C 文件:
aarch64-linux-gnu-gcc -o hello hello.c
file hello
# 输出:hello: ELF 64-bit LSB executable, ARM aarch64, ...
方式二:从 ARM 官方下载(适合需要特定版本)
ARM 官方提供了预编译的 GNU 工具链,地址在 developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain。我一般用这个方式,因为可以固定版本,避免团队环境不一致。
注意:交叉编译时,链接器默认会找目标系统的库。如果你编译的是裸机程序(没有操作系统),需要加 -nostdlib 选项。我曾经帮一个同事排查问题,他编译出来的程序在 QEMU 上一运行就段错误——就是因为链接了宿主机的 glibc。
1.4 QEMU 模拟器配置
没有实体开发板怎么办?QEMU 就是你的救星。它不仅能模拟 ARM64 的 CPU,还能模拟完整的开发板(比如树莓派 3/4)。
安装 QEMU
# Ubuntu / Debian
sudo apt install qemu-system-arm qemu-user
# macOS
brew install qemu
QEMU 有两种模式:
- User 模式(qemu-aarch64):直接运行单个 ARM64 二进制文件,不需要模拟整个系统。适合快速测试。
- System 模式(qemu-system-aarch64):模拟完整的 ARM64 机器,可以启动 Linux 内核。适合做系统级调试。
快速测试:User 模式
# 编译一个静态链接的 ARM64 程序
aarch64-linux-gnu-gcc -static -o hello hello.c
# 用 QEMU 运行
qemu-aarch64 ./hello
就这么简单。你甚至可以用 -g 参数开启 GDB 调试端口:
qemu-aarch64 -g 1234 ./hello
启动完整系统:System 模式
这个稍微复杂一点,你需要一个内核镜像和一个根文件系统。我一般用 Buildroot 或 Yocto 生成。这里给一个最小示例:
# 下载 ARM64 内核和根文件系统(以 Debian 为例)
wget https://people.debian.org/~gio/dqib/aarch64/.../kernel
wget https://people.debian.org/~gio/dqib/aarch64/.../rootfs.qcow2
# 启动 QEMU
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a72 \
-smp 2 \
-m 2G \
-kernel kernel \
-drive file=rootfs.qcow2,if=none,id=drive0 \
-device virtio-blk-device,drive=drive0 \
-netdev user,id=net0 \
-device virtio-net-device,netdev=net0 \
-nographic
启动后你就有了一个完整的 ARM64 Linux 环境。我个人习惯在 System 模式下做性能调优,因为可以控制 CPU 频率、中断等硬件细节。
小技巧:QEMU 的 -cpu 参数可以指定具体的 ARM 核心型号,比如 cortex-a72、cortex-a76、neoverse-n1。不同核心的流水线深度、缓存大小都不一样——这对性能调优来说至关重要。
1.5 GDB 调试器入门
GDB 是咱们反汇编和调试的利器。ARM64 下的 GDB 用法和 x86 基本一致,但有一些架构特有的命令。
安装 ARM64 版 GDB
# 安装支持 ARM64 的 GDB
sudo apt install gdb-multiarch
# 或者安装 ARM 专用版
sudo apt install gdb-aarch64-linux-gnu
基本调试流程
假设我们有一个简单的 C 程序:
// test.c
int add(int a, int b) {
return a + b;
}
int main() {
int x = 10;
int y = 20;
int z = add(x, y);
return z;
}
编译并调试:
# 编译,加 -g 保留调试信息
aarch64-linux-gnu-gcc -g -o test test.c
# 启动 GDB
gdb-multiarch ./test
# 在 GDB 中设置架构
(gdb) set architecture aarch64
# 设置断点
(gdb) break main
# 运行
(gdb) run
# 反汇编当前函数
(gdb) disassemble
# 查看寄存器
(gdb) info registers x0 x1 x2
# 单步执行
(gdb) stepi
# 查看内存
(gdb) x/8gx $sp
你看,disassemble 命令会直接输出 ARM64 汇编。我个人在分析性能热点时,最常用的组合是 perf record 抓采样,然后用 GDB 的 disassemble /m 看源码和汇编的对应关系。
关键命令速查:
info registers— 查看所有寄存器disassemble— 反汇编当前函数stepi— 单步执行一条指令nexti— 单步跳过函数调用layout asm— 打开汇编视图(TUI 模式)
1.6 本章知识体系
下面这张图总结了本章的核心脉络,你可以把它当作后续学习的导航图: