深入理解 ARMv8-A|处理器模式与寄存器
2026/6/1 6:43:13 网站建设 项目流程

本文主要从 ARM 汇编的使用者角度,系统地介绍 ARMv8-A (AArch64) 架构的核心知识。首先讲解异常级别(Exception Level)与执行状态的切换机制;接着深入介绍各种通用寄存器、特殊寄存器和系统寄存器,并在此基础上详解异常处理的完整流程;随后概述 A64 指令集的主要指令类别、AAPCS64 调用惯例以及汇编宏的使用方法;最后以一段裸机启动汇编代码的逐行分析结束全文。

参考:
《ARM Cortex-A Series Version: 1.0 Programmer’s Guide for ARMv8-A》
《Arm Architecture Reference Manual Armv8, for Armv8-A architecture profile》

1、ARMv8 处理器模式

在 ARMv8 架构中,程序的执行会发生在四个 Exception level(异常级别)之一。在 AArch64 状态下,Exception level 决定了执行的特权等级(privilege level),这与 ARMv7 中定义的特权等级非常相似。Exception level 直接决定了特权等级,因此在 ELn 上执行就对应着特权等级 PLn。同理,n 值越大的 Exception level,其特权等级越高;而 n 值较小的则被称为较低的 Exception level。

注意:并非所有处理器都会实现全部四个异常级别。例如,许多嵌入式或移动处理器可能只实现 EL0 ~ EL2;而简单的 RTOS 场景可能仅使用 EL0 和 EL1。具体实现情况需查阅对应处理器的 TRM(Technical Reference Manual)。

Exception level 提供了一种逻辑上的软件执行特权隔离,这种隔离适用于 ARMv8 架构的所有操作状态(operating states)。这非常类似于计算机科学中常见的“分层保护域(hierarchical protection domains)”概念,并且为其提供了底层支持。

下面是一个典型的各 Exception level 对应运行软件的示例:

  • EL0:普通用户应用程序(Normal user applications)。
  • EL1:操作系统内核,通常被称为特权模式(privileged)。
  • EL2:Hypervisor(虚拟机监控器)。
  • EL3:底层固件(Low-level firmware),包括 Secure Monitor。


通常情况下,一段软件(比如一个应用程序、操作系统内核或 Hypervisor)只运行在单一的 Exception level 中。不过也有例外,比如像 KVM 这种 in-kernel hypervisors,它们会同时跨越 EL2 和 EL1 运行。

ARMv8-A 提供了两种 security states(安全状态):Secure(安全)和 Non-secure(非安全)。Non-secure 状态通常也被称为 Normal World(正常世界)。这种设计使得操作系统(OS)能够与一个受信任的操作系统(trusted OS)在同一硬件上并行运行,并为抵御某些软件和硬件攻击提供了保护。ARM TrustZone 技术实现了系统在 Normal World 和 Secure World(安全世界)之间的隔离划分。与 ARMv7-A 架构类似,Secure Monitor 充当了在 Normal World 和 Secure World 之间切换的网关。

ARMv8-A 同样提供了对虚拟化的支持,不过仅限于 Normal world(正常世界)。这意味着系统上可以运行 Hypervisor(管理程序)或 VMM(虚拟机管理器)代码,并托管多个 Guest operating systems(客户操作系统)。每个客户操作系统本质上都是运行在一个虚拟机(Virtual Machine)之中。因此,每个操作系统都不会察觉到它正在与其他客户操作系统共享系统的运行时间。

补充:ARMv8.4-A 开始引入了 Secure EL2 特性,使得虚拟化也可以扩展到 Secure world 中。但作为 ARMv8-A 基础架构的学习,理解 Normal world 下的 EL0/EL1/EL2 三层结构依然是重点。

1.1 两种执行状态:AArch64 与 AArch32

ARMv8 架构定义了两种截然不同的“执行状态”(Execution States),分别是 AArch64 和 AArch32。这两种状态最直观的区别,就在于 CPU 干活时使用的通用寄存器分别是 64 位宽和 32 位宽。不过,这两种状态在“特权管理”上也有很大不同:

  • AArch32状态:为了照顾老设备,它保留了 ARMv7 时代那套老的特权定义。
  • AArch64状态:它启用了一套全新的规则,特权等级由“异常级别”(Exception Level)来决定。所以在这种状态下,运行在 ELn(异常级别 n)就对应着特权等级 PLn。

至于 CPU 能听懂什么“语言”(指令集),完全取决于它当前处在什么状态:

  • 当处于 AArch64 状态时,处理器执行的是A64 指令集(固定长度 32-bit 编码)。
  • 当处于 AArch32 状态时,处理器可以执行A32 指令集(在早期的架构版本里叫 ARM 指令)或者 T32 (Thumb )指令集。

截至目前,ARM 架构的指令集体系主要涵盖 A64(64 位)、A32(32 位)以及 T32/Thumb-2(混合长度 16/32 位)。SVE(Scalable Vector Extension)和 SVE2 在 ARMv8.2-A 中作为可选扩展引入,到了 ARMv9-A 中 SVE2 被提升为强制要求,以强化在高性能计算、AI 和安全领域的向量计算能力。

接下来的几张图展示了 AArch64 和 AArch32 中异常级别的具体组织架构。


1.2 执行状态的切换

有些时候,你必须改变系统的执行状态(execution state)。例如,当你运行一个 64 位的操作系统,但想要在 EL0 运行一个 32 位的应用程序时。为了实现这一点,系统必须切换到 AArch32。

execution state 的状态切换,只能在以下两种场景触发:

  • 复位(reset)
  • 异常切换(异常发生或异常返回时的硬件自动切换)

注意,ARMv8-A 不允许通过软件直接写寄存器来切换当前处理器的执行状态!执行状态的变更只能由异常捕获(exception entry)或异常返回(exception return)时,由硬件根据目标 EL 的配置寄存器自动完成。


捕获异常并进入更高的 Exception level 时,Execution state 会有以下两种选择:

  • 保持不变。
  • 从 AArch32 状态切换为 AArch64 状态。

异常进入目标 EL 后使用什么 Execution State,不是由异常来源决定,而是由更高一级 EL 的配置决定(具体来说,由目标 EL 对应的 SCR_EL3.RW 或 HCR_EL2.RW 等控制位决定)。

从异常返回并进入更低的 Exception level 时,Execution state 会有以下两种选择:

  • 保持不变。
  • 从 AArch64 状态切换为 AArch32 状态。

如果在捕获异常或从异常返回时,Exception level 保持不变,则 Execution state 无法发生改变。

当应用程序运行结束或执行流返回到操作系统时,系统可以切换回 AArch64。图 3-7 表明,反过来是不行的。也就是说,一个 AArch32 操作系统无法承载(host)一个 64 位的应用程序。核心原因在于:从 AArch32 EL1 返回 EL0 时,只能保持 AArch32;若要切换到 AArch64 必须伴随 EL 提升,而 EL0 无法进一步提升——因此 32-bit OS 无法为 64-bit 应用创建所需的 AArch64 EL0 环境。

若要在同一个 Exception level 切换执行状态,你必须先切换到一个更高的 Exception level,然后再返回到原来的 Exception level。例如,你可能在 64 位操作系统下同时运行 32 位和 64 位的应用程序。在这种情况下,32 位应用程序可以执行并触发一条 Supervisor Call (SVC) 指令,或者接收一个中断,从而引起向 EL1 和 AArch64 的切换。随后,操作系统可以进行任务切换,并返回到 AArch64 状态下的 EL0。


以下是在 AArch64 和 AArch32 执行状态之间切换时的一些要点总结

  • AArch64 和 AArch32 执行状态都拥有大体相似的 Exception levels,但在安全(Secure)与非安全(Non-secure)操作之间存在一些差异。
  • 切换到 AArch32 需要从较高的 Exception level 进入较低的 Exception level。这是通过执行ERET指令退出异常处理程序来实现的。
  • 切换到 AArch64 需要从较低的 Exception level 进入较高的 Exception level。该异常可能是由指令执行或外部信号引起的。
  • 如果在捕获异常或从异常返回时,Exception level 保持不变,则执行状态无法改变。
  • 当 ARMv8 处理器在特定的 Exception level 以 AArch32 执行状态运行时,它对于在该 Exception level 捕获的异常,使用与 ARMv7 相同的异常模型。而在 AArch64 执行状态下,它使用 AArch64 异常处理模型。

因此,两种状态之间的相互调用(Interworking)是在 Secure monitor、hypervisor 或操作系统级别执行的。运行在 AArch64 状态的 hypervisor 或操作系统可以支持在较低特权级别下的 AArch32 操作。这意味着,运行在 AArch64 的操作系统可以同时承载 AArch32 和 AArch64 应用程序。同样,AArch64 hypervisor 也可以同时承载 AArch32 和 AArch64 的 guest 操作系统。但是,一个 32 位操作系统无法承载 64 位应用程序,一个 32 位 hypervisor 也无法承载 64 位的 guest 操作系统。

对于系统所实现的最高异常级别(在 Cortex-A53 和 Cortex-A57 中为 EL3),该异常级别所使用的执行状态(AArch32 或 AArch64)是固定的,不能通过软件修改。处理器只有在复位(Reset)时,才可能改变该最高异常级别的执行状态配置。而较低异常级别(EL2、EL1)的执行状态则分别由SCR_EL3.RWHCR_EL2.RW控制。

1.3 异常级别的切换

在 ARMv7 架构中,处理器模式既可以在特权软件的管控下进行切换也可以在响应异常时自动切换。当异常发生时,处理器核心会保存当前的执行状态和返回地址,进入相应的模式,并可能会禁用硬件中断。

在 AArch64 中,处理器模式被映射到异常级别(Exception levels),如图 3-6 所示。与 ARMv7(AArch32)一样,当捕获异常时,处理器会切换到支持处理该异常的异常级别(模式)

异常级别之间的切换遵循以下规则:

  • 向更高的异常级别切换(例如从 EL0 到 EL1),意味着软件执行权限的提升。
  • 异常不能被路由(进入)到比当前更低的异常级别(Exception Level, EL)
  • EL0 级别本身不进行异常处理,异常必须在更高的异常级别上处理。
  • 异常会导致程序执行流的改变。异常处理程序的执行将从一个与该异常相关的、预定义的向量地址开始,且该处理程序运行在高于 EL0 的异常级别上。异常包括:
    • — 中断,例如 IRQ 和 FIQ。
    • — 内存系统中止(Memory system aborts)。
    • — 未定义指令(Undefined Instruction)。
    • — 系统调用(SVC / HVC / SMC)。这允许非特权软件向操作系统、Hypervisor 或 Secure Monitor 发起调用。
    • — Secure Monitor 或 Hypervisor 陷入(traps)。
  • 结束异常处理并返回到之前的异常级别,是通过执行ERET指令来完成的。
  • 从异常返回时,可以保持在同一个异常级别,也可以进入一个更低的异常级别,但不能移动到更高的异常级别。

2、ARMv8 Registers

AArch64 执行状态提供了 31 个 64 位的通用寄存器,这些寄存器在所有时刻和所有异常级别(Exception levels)下都可以被访问。

每个寄存器的宽度都是 64 位,通常被称为寄存器 X0 到 X30。


每个 AArch64 的 64 位通用寄存器(X0-X30),也都对应有一个 32 位的形态(W0-W30)。


32 位的 W 寄存器构成了对应 64 位 X 寄存器的低半部分。也就是说,W0 映射到 X0 的低字(lower word),W1 映射到 X1 的低字。

读取 W 寄存器时,会直接忽略对应 X 寄存器的高 32 位,并且不会对其做任何修改。而向 W 寄存器写入数据时,则会将 X 寄存器的高 32 位自动清零。也就是说,如果你向 W0 写入 0xFFFFFFFF,那么 X0 的值就会被设定为 0x00000000FFFFFFFF。

2.1 AArch64 特殊寄存器

除了31个核心寄存器外,还有几个特殊寄存器:


名为 X31 或 W31 的寄存器是不存在的。许多指令在编码时,会将数字 31 指定为零寄存器 ZR(即 WZR 或 XZR)。此外,还有一小部分特定的指令,会将其中一个或多个参数的编码 31 指定为堆栈指针 SP(Stack Pointer)。

当指令编码中 31 被解释为零寄存器(zero register)时,所有的写入操作都会被忽略,而所有的读取操作都会返回 0。需要注意的是,64 位形式的 SP 寄存器并没有使用 X 前缀。

补充说明:并非所有指令中编号 31 都会被解释为零寄存器/SP。具体指令编码中 31 的解释,取决于该指令的定义(如LDR中作为 base register 时表示 SP,而STR目标寄存器时表示 ZR 则写操作被忽略)。在阅读汇编代码时遇到 X31/W31 须结合指令上下文来判断。

在 ARMv8 架构中,当在 AArch64 状态下执行时,异常返回状态(exception return state)由每个异常级别(Exception level)的以下专用寄存器来保存:

  • 异常链接寄存器(Exception Link Register, ELR)。
  • 保存的处理器状态寄存器(Saved Processor State Register, SPSR)。

每个异常级别都有一个专用的 SP,但它并不用于保存异常返回状态。

2.1.1 堆栈指针(Stack Pointer)

在 ARMv8 架构中,选择使用哪个堆栈指针(stack pointer),在一定程度上与异常级别(Exception level)是分离的。默认情况下,捕获异常时会选择目标异常级别对应的堆栈指针,即SP_ELn。例如,捕获异常进入 EL1 时,就会选中 SP_EL1。每个异常级别都有自己专属的堆栈指针:SP_EL0SP_EL1SP_EL2SP_EL3

当处理器在除 EL0 以外的任意异常级别以 AArch64 状态运行时,它可以自由选择使用以下两种堆栈指针之一:

  • 与该异常级别绑定的专用 64 位堆栈指针(SP_ELn)。
  • 与 EL0 绑定的堆栈指针(SP_EL0)。

而 EL0 则只能访问SP_EL0

后缀 t 表示当前选中的是SP_EL0堆栈指针。后缀 h 则表示选中的是SP_ELn堆栈指针。

SP 无法被大多数指令直接引用。不过,某些特定形式的算术指令(例如 ADD 指令)可以读写当前的堆栈指针,以便在函数中调整栈的位置。例如:

ADD SP,SP,#0x10// Adjust SP to be 0x10 bytes before its current value
2.1.2 程序计数器(Program Counter)

原始 ARMv7 指令集的一个特点是,可以将 R15 即程序计数器(PC)作为通用寄存器来使用。PC 的这种特性虽然能实现一些非常巧妙的编程技巧,但也给编译器以及复杂流水线的设计带来了诸多麻烦。在 ARMv8 中取消对 PC 的直接访问,不仅让返回预测(return prediction)变得更加容易,也简化了 ABI(应用程序二进制接口)的规范。

PC 永远不会作为一个命名寄存器被直接访问。它的使用是隐式的,仅存在于某些特定的指令中,例如 PC 相对寻址的加载(PC-relative load)和地址生成指令(ADR / ADRP)。PC 不能被指定为数据处理指令或加载指令的目标寄存器。如果需要获取当前 PC 的值,通常使用 ADR 指令。

2.1.3 Exception Link Register (ELR)

当异常发生时,硬件会自动将返回地址保存到目标异常级别的 ELR_ELn 中。例如,从 EL0 触发异常进入 EL1,则返回地址被写入 ELR_EL1。当异常处理完毕、执行 ERET 指令时,处理器将 PC 设置为 ELR_ELn 的值,从而实现正确的返回。

对于同步异常(如 SVC、未定义指令等),ELR 保存的是触发异常的那条指令的地址,返回时需要重新执行该指令(SVC 调用返回时执行下一条)。对于异步异常(IRQ/FIQ/SError),ELR 保存的是尚未完成执行的指令地址,返回时应恢复执行。

更详细的 ELR 使用场景将在第 3 章"ARMv8 异常处理详解"中结合异常进入/退出流程进一步讲解。

2.1.4 Saved Process Status Register

当捕获异常时,处理器状态会被存储在相关的已保存程序状态寄存器(Saved Program Status Register, SPSR)中,这与 ARMv7 中的 CPSR 类似。SPSR 保存了捕获异常之前的 PSTATE 值,并用于在执行异常返回(exception return)时恢复 PSTATE 的值。

在 AArch64 中,各个独立的位代表以下数值:

  • N:负数结果(N 标志位)。
  • Z:零结果(Z 标志位)。
  • C:进位输出(C 标志位)。
  • V:溢出(V 标志位)。
  • SS:软件单步(Software Step)。指示在捕获异常时,软件单步是否处于启用状态。
  • IL:非法执行状态位(Illegal Execution State bit)。显示捕获异常前一刻的 PSTATE.IL 值。
  • D:进程状态调试屏蔽位(Process state Debug mask)。指示针对异常发生所在异常级别的、由观察点(watchpoint)、断点(breakpoint)和软件单步调试事件引发的调试异常,是否被屏蔽。
  • A:SError(系统错误)屏蔽位。
  • I:IRQ 屏蔽位。
  • F:FIQ 屏蔽位。
  • M[4]:捕获异常时所处的执行状态。值为 0 表示 AArch64。
  • M[3:0]:捕获异常时所处的模式或异常级别。

在 ARMv8 中,写入哪个 SPSR 取决于异常级别。如果异常在 EL1 捕获,则使用 SPSR_EL1。如果异常在 EL2 捕获,则使用 SPSR_EL2;如果异常在 EL3 捕获,则使用 SPSR_EL3。处理器核心会在捕获异常时由硬件自动填充 SPSR(填充是硬件过程,无需软件参与)。

2.2 处理器状态(Processor state, PSTATE)

AArch64 并没有 ARMv7 中当前程序状态寄存器(CPSR)的直接等价物。在 AArch64 中,传统 CPSR 的各个组成部分被拆分成了可以独立访问的字段。这些字段统称为处理器状态(Processor State, PSTATE)。AArch64 的处理器状态(PSTATE 字段)定义如下:

在 AArch64 中,通过执行ERET指令从异常返回,这会导致SPSR_ELn的值被复制到PSTATE中。这一操作会恢复算术逻辑单元(ALU)标志位、执行状态、异常级别,并且处理器会进行分支跳转。随后,将从ELR_ELn中的地址继续执行。

PSTATE.{N, Z, C, V}字段可以在 EL0 级别被访问。所有其他的 PSTATE 字段只能在 EL1 或更高级别执行访问,在 EL0 级别访问这些字段是未定义的(UNDEFINED)。

下面是 PSTATE 中几个值得特别关注的关键字段:

字段含义
N / Z / C / V条件标志位(ALU 运算结果标志)
D / A / I / F调试 / SError / IRQ / FIQ 屏蔽位(1 = 屏蔽)
SP当前选中的栈指针(0 = SP_EL0, 1 = SP_ELn)
EL当前异常级别(只读,由 CurrentEL 寄存器反映)
nRW当前执行状态(0 = AArch64, 1 = AArch32)
PANPrivileged Access Never(1 = 在 EL1/EL2 禁止访问用户页,除非显式使用 LDTR/STTR)
UAOUser Access Override(影响 LDTR/STTR 在 EL1/EL2 的行为)
SSBSSpeculative Store Bypass Safe(侧信道攻击缓解开关)
2.2.1 访问 PSTATE 字段

在 AArch64 状态下,PSTATE 字段可以通过专用寄存器(Special-purpose registers)来访问。这些寄存器可以使用MRS指令直接读取,也可以使用MSR(register) 指令直接写入。表 D1-3 展示了当处理器(PE)处于 AArch64 状态时,用于访问保存 AArch64 状态的 PSTATE 字段的专用寄存器。所有其他的 PSTATE 字段则无法被直接读写。


例如:

MRS x0, NZCV // 将条件标志位 N/Z/C/V 读入 x0 MRS x1, DAIF // 将中断屏蔽位 D/A/I/F 读入 x1 MRS x2, SPSel // 将当前栈指针选择状态读入 x2 MRS x3, CurrentEL // 将当前异常级别读入 x3

软件还可以使用 MSR (immediate) 指令,直接向PSTATE.{D, A, I, F, SP, PAN, UAO, SSBS, TCO}这些字段写入数据。表 D1-4 展示了当处理器(PE)处于 AArch64 状态时,能够直接向这些 PSTATE 字段写入数据的 MSR (immediate) 操作数。


例如:

MSR DAIFSet,#Imm4;Used to set any or all of DAIF to1MSR DAIFClr,#Imm4;Used to clear any or all of DAIF to0MSR SPSel,#Imm4;Used to select the Stack Pointer,between SP_EL0 and SP_ELx MSR UAO,#Imm4;Used to set the value of PSTATE.UAO MSR PAN,#Imm4;Used to set the value of PSTATE.PAN MSR DIT,#Imm4;Used to set the value of PSTATE.DIT MSR SSBS,#Imm4;Used to set the value of PSTATE.SSBS MSR TCO,#Imm4;Used to set the value of PSTATE.TCO

2.3 系统寄存器(System Registers)

在 AArch64 架构中,系统配置是通过 system registers(系统寄存器)来控制的,并使用MSRMRS指令进行访问。这与 ARMv7-A 形成了鲜明对比,在 ARMv7-A 中,这类寄存器通常是通过协处理器 15(CP15)的操作来访问的。寄存器的名称会直接告诉你,能够访问它的最低 Exception level(异常等级)是哪一个。

举个例子:

  • TTBR0_EL1 可以从 EL1、EL2 和 EL3 访问。
  • TTBR0_EL2 可以从 EL2 和 EL3 访问。

带有_ELn后缀的寄存器,在某些或所有异常等级中都有独立的 banked copy(分组副本/独立副本),不过通常不包括 EL0。很少有 system registers 能从 EL0 访问,但 Cache Type Register(CTR_EL0)就是其中一个可以在 EL0 访问的例子。

访问 system registers 的代码格式如下:

MRS x0,TTBR0_EL1// 将 TTBR0_EL1 的值移入 x0MSR TTBR0_EL1,x0// 将 x0 的值移入 TTBR0_EL1

ARM 架构的早期版本曾使用协处理器(coprocessors)来进行系统配置。不过,AArch64 已经不再支持 coprocessors 了。

2.3.1 常用系统寄存器速览

下表整理了 ARMv8-A 编程中经常遇到的系统寄存器及其功能描述,方便查阅:

寄存器最低访问 EL功能
VBAR_ELnEL1/2/3异常向量基址寄存器,指向当前 EL 的异常向量表
ELR_ELnEL1/2/3异常链接寄存器,保存异常返回地址
SPSR_ELnEL1/2/3保存的程序状态寄存器,异常进入时保存 PSTATE
ESR_ELnEL1/2/3异常综合寄存器,记录异常原因和分类信息
FAR_ELnEL1/2/3故障地址寄存器,保存触发同步异常的虚拟地址
SCTLR_ELnEL1/2/3系统控制寄存器(MMU、Cache、对齐检查等总体开关)
TCR_ELnEL1/2/3翻译控制寄存器(页表粒度、地址空间大小等 MMU 参数)
TTBR0_ELnEL1/2/3翻译表基址寄存器 0(用于低地址空间)
TTBR1_ELnEL1/2/3翻译表基址寄存器 1(用于高地址空间)
MAIR_ELnEL1/2/3内存属性间接寄存器(定义内存类型和设备内存属性)
SCR_EL3EL3安全配置寄存器(控制 Secure/Non-secure、FIQ 路由、AArch32/AArch64 选择)
HCR_EL2EL2Hypervisor 配置寄存器(控制 VM 行为、AArch32/AArch64 选择)
CPACR_EL1EL1协处理器访问控制寄存器(控制 EL0/EL1 对 SIMD/FP 寄存器的访问)
CTR_EL0EL0Cache 类型寄存器(只读,报告 Cache 的块大小等拓扑信息)
MPIDR_EL1EL1多处理器亲和寄存器(报告当前核心的 Cluster ID + Core ID)
DAIFEL0(读)中断屏蔽位(D/A/I/F)
CurrentELEL0(读)当前异常级别(只读)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询