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 本章知识体系

下面这张图总结了本章的核心脉络,你可以把它当作后续学习的导航图:

第1章 知识体系:ARM64基础与开发环境搭建 ARM64 开发基础 ARM64 架构演进史 AArch64 vs AArch32 交叉编译工具链 QEMU 模拟器配置 GDB 调试器入门 ARMv7 → ARMv8 → ARMv9 寄存器 / 指令 / 异常模型 gcc-aarch64 / 官方工具链 User模式 / System模式 断点 / 反汇编 / 寄存器查看 💡 本章是后续29章的基础,建议动手搭建完整环境 后续章节将基于本章环境进行反汇编与性能调优实战