ARM Exception Model
时间轴
2025-10-16
init
ARM64 的异常处理(Exception Model)
参考文档:
ARM64 的异常等级(Exception Level)
- EL0 非特权模式,例如应用程序
- EL1 特权模式,例如 OS 内核
- EL2 虚拟化监控程序,例如 hypervisor
- EL3 安全模式,例如 secure monitor
术语(Exception terminology)
- Taking an exception: 正在处理一个异常
- Returning from an exception: 从一个异常中返回
- Exception levels: 异常等级
- Precise exception: 精准异常
- Synchronous and asynchronous exception: 同步异常与异步异常
- 同步异常
- 系统调用, svc, hvc, SMC 等
- MMU 引发的异常
- SP 和 PC 对齐检查
- 未分配的指令
- 未分配的指令操作码
- 需要比当前异常级别更高级别特权的指令。
- 已禁用的指令。
- PSTATE.IL 域被设置时的任何指令
- 调试异常
- 异步异常
- IRQ 中断
- FIQ 中断
- SError(系统错误)
- 同步异常
异常入口(Exception Entry)
Process state or PSTATE is an abstraction of process state information
- 当异常发生时,CPU 硬件都做了哪些事情
- PSTATE 保存到 SPSR_ELx(The Saved Program Status Register)
- 返回地址保存 ELR_ELx
- PSTATE 寄存器的 DAIF 域都设置为 1,相当于把调试异常,系统错误(SError),IRQ 中断以及 FIQ 中断都关闭了
- 更新了 ESR_ELx(Exception Syndrome Register)寄存器,里面包含了同步异常或SError发生的原因
- SP 执行 SP_ELx
- 切换到对应的 EL,然后跳转到异常向量表里执行
- 当异常发生后,操作系统需要做哪些事情
操作系统需要设置异常向量表,这样CPU根据异常发生的类型,会跳转到合适的异常向量表的表项
异常向量表的每个表项会保存一个异常处理的跳转函数,然后跳转到恰当的异常处理函数并处理异常
异常状态寄存器(Exception Syndrome Register, ESR_ELn)包含允许异常处理程序确定异常原因的信息。它只
对针对同步异常和 SError 做更新,不为 IRQ 或 FIQ 更新,因为这些中断处理程序通常从通用中断控制
器 (GIC) 的寄存器中获取状态信息。
异常的返回
- 操作系统执行一条eret语句
- 从 ELR_ELx(Exception Link Register)寄存器中恢复 PC 指针
- 从 SPSR_ELx(The Saved Program Status Register)寄存器恢复处理器的状态

异常返回地址
- 返回地址两个寄存器
- x30:子函数的返回地址。使用 ret 指令来返回
- ELR_ELx:异常返回地址。使用 eret 指令来返回
- ELR_ELx寄存器保存了异常返回地址
- 对于异步异常,它的返回地址是中断发生时的下一条指令,或者没有执行的第一条指令
- 对于不是 system call 的同步异常,返回的是触发同步异常的那一条指令
- 对于 system call,它返回的是 svc 指令的下一条指令
异步异常(中断)
硬件已经把 ELR 设置为 下一条要执行的指令,
eret就直接回去继续执行,不用额外改
同步异常(非 system call)
硬件把 ELR 设置为 触发异常的那条指令
如果你修复了异常原因(比如缺页异常),直接eret就会重新执行这条指令
如果你想跳过它(不重试),就要手动ELR_ELx += 4
system call(svc)
硬件把 ELR 设置为 svc 的下一条指令,所以
eret会直接返回到下一条
异常处理的路由
当在一个特定的“异常等级(Exception Level, EL)”发生异常时,CPU 应该跳转到哪个异常等级去处理它
- 异常发生时,可以在当前 EL 处理也可以在更高 EL 处理
- EL0 不能用来处理异常
- 同步异常是可以在当前 EL 里处理的,比如在 EL1 发生了同步异常
- 对于异步异常,可以路由到 EL1,EL2,EL3 处理,需要配置HCR(Hypervisor Configuration Register)以及SCR(Secure Configuration Register)相关寄存器
- 如果异常是由于在 EL0 处取指令而产生的,则将其视为 EL1 的异常,除非 HCR_EL2.TGE 位设置为非安全状态,在这种情况下将其视为 EL2。

参考 armv8.6 的 tableD1-10
- SCR_EL3: Secure Configuration Register
- HCR_EL2: Hyervisor Cofiguration Register
该图片中的表格(Table D1-10)是ARM 架构下 EL3(安全监控模式)与 EL2(虚拟化监控模式)同时实现时的中断 / 异常路由规则表,核心用于定义不同系统状态(寄存器配置)下,中断(IRQ/FIQ)或异常(如 Abt 中止异常)触发后,处理器最终跳转的目标特权级别(EL0~EL3)。

- 第 1 行:SCR=0,NS EEL2a=0,EA IRQ FIQ=0,RW=0
- 输入条件:安全配置为默认(SCR=0)、非安全 EL2 异常入口关闭(NS EEL2a=0)、无活跃中断 / 异常(EA IRQ FIQ=0)、读写权限为 0;HCR TGE、E2H 为任意值(X)。
- 路由结果:
- 从 EL0 触发:跳转到 “FIQ IRQ Abt”(即触发对应中断 / 异常的原生处理入口,未被高 EL 拦截);
- 从 EL1 触发:同 EL0,跳转到 “FIQ IRQ Abt”;
- 从 EL2 触发:
n/a(不适用,因 NS EEL2a=0 时 EL2 未使能该场景); - 从 EL3 触发:跳转到 “C”(通常指 “安全态下的异常处理入口”,ARM 架构中 “C” 常代表安全相关的默认目标)。
- 第 2 行:SCR=0,NS EEL2a=0,EA IRQ FIQ=0,RW=1
- 输入条件:仅 RW(读写权限)从 0 改为 1,其他与第 1 行一致。
- 路由结果:
- 从 EL0/EL1 触发:跳转到 “EL1”(即异常被 EL1(内核态)拦截处理,而非原生中断入口,因 RW=1 对应更高权限的内核处理逻辑);
- 从 EL2 触发:
n/a(同第 1 行,EL2 未使能); - 从 EL3 触发:跳转到 “C”(安全态入口不变)。
- 第 3 行:SCR=0,NS EEL2a=0,EA IRQ FIQ=1
- 输入条件:EA IRQ FIQ=1(有活跃的中断 / 异常触发),其他配置同前两行;HCR TGE、E2H 为任意值。
- 路由结果:
- 从 EL0/EL1 触发:跳转到 “EL3”(活跃中断 / 异常被最高安全级 EL3 拦截,符合 ARM 安全架构中 “高优先级异常由 EL3 处理” 的逻辑);
- 从 EL2 触发:
n/a(EL2 未使能); - 从 EL3 触发:跳转到 “EL3”(EL3 自身触发的异常,在 EL3 内部处理,不跳转其他级别)。
- 第 4 行:SCR=0,NS EEL2a=1,EA IRQ FIQ=0,HCR TGE=0,E2H=0,RW=0
- 输入条件:NS EEL2a=1(非安全 EL2 异常入口开启)、无活跃中断 / 异常(EA IRQ FIQ=0)、虚拟化模式关闭(HCR TGE=0)、EL2 不拦截 EL1 异常(E2H=0)。
- 路由结果:
- 从 EL0 触发:跳转到 “FIQ IRQ Abt”(原生中断入口);
- 从 EL1 触发:跳转到 “FIQ IRQ Abt”(EL2 未拦截,因 E2H=0);
- 从 EL2 触发:跳转到 “C”(非安全 EL2 触发的异常,路由到安全态入口 “C”);
- 从 EL3 触发:跳转到 “C”(安全态内部处理,目标不变)。
栈的选择
- 每个异常等级 EL 都有对应栈指针寄存器 SP_ELx
- SP_EL0, SP_EL1, SP_EL2, SP_EL3
- 栈必须 16 字节对齐。硬件可以检测栈指针是否对齐
- 当异常发生时,并跳转到目标异常等级时,硬件会自动选择 SP_ELx
- 操作系统负责和分配保证每个异常等级 EL 对应的栈,是可用的
执行模式
异常处理的执行模式
- 当异常发生时,切换到高级别的 EL,这个 EL 运行在哪个模式?AArch64 or AArch32
- HCR_EL2.RW(Hypervisor Configuration Register)记录了 EL1 要运行在哪个模式
- 1 表示 aarch64
- 0 表示 aarch32
- HCR_EL2.RW(Hypervisor Configuration Register)记录了 EL1 要运行在哪个模式

- 当异常发生后,执行模式可以发生改变
- 一个 aarch32 的应用程序正在运行,这时候来了一个中断,它可能会跑到 aarch64 执行状态下的 EL1 里处理这个中断
异常返回的执行模式
- 从一个异常返回,SPSR 寄存器(Saved Program Status Register)记录了:
- 返回到哪个 EL? SPSR.M[3:0]
- 返回目标 EL 的执行模式?SPSR.M[4]
- 0 表示 aarch64
- 1 表示 aarch32

实验 1:切换到 EL1 中运行

提示
从 EL2 切换到 EL1,需要做如下几件事情
设置 HCR_EL2(Hypervisor Configuration Register)寄存器,最重要的是 Bit 31 的 RW 域,表示 EL1 要运行在哪个执行环境里,aarch32 或 aarch64(相似地对于EL2的执行状态由SCR_EL3决定)
- HCR_EL2 属于 General System Control Register


设置 SCTLR_EL1(System Control Register),要设置大小端和关闭 MMU



设置 SPSR_EL2(Saved Program Status Register)寄存器,设置模式 M 域为 EL1h,另外需要关闭所有 PSTATE 的 DAIF
- SPSR_ELx属于Special-purpose Register





| M[3:0] | 目标模式 | 说明 |
|---|---|---|
0000 |
EL0t | EL0,使用 SP_EL0 |
0100 |
EL1t | EL1,使用 SP_EL0 |
0101 |
EL1h | EL1,使用 SP_EL1 |
1000 |
EL2t | EL2,使用 SP_EL0 |
1001 |
EL2h | EL2,使用 SP_EL2 |
1100 |
EL3t | EL3,使用 SP_EL0 |
1101 |
EL3h | EL3,使用 SP_EL3 |

设置异常返回寄存器 elr_el2,让其返回到 el1_entry 汇编函数里
执行eret
(3,4 其实是一个异常返回的经典步骤,从 EL2 返回到 EL1)
首先根据当前异常等级判断要跳到哪个异常等级

注意执行 eret 前要设置 elr_el2 寄存器

异常向量表(Exception vectors)
每个异常等级 EL 都有自己的异常向量表,EL0 除外
异常向量表的基地址需要设置到VBAR_ELx(Vector Base Address Register)寄存器中
VBAR_EL1 寄存器:

0~10 是 reserve,因此地址是 2k 对齐

行(Exception taken from)
定义异常触发时的 “当前执行状态”,分 4 类场景:
- Current Exception level with SP_EL0:当前异常级别使用
SP_EL0栈指针(如在 EL1 下用用户级栈指针时触发异常 )。 - Current Exception level with SP_ELx, x>0:当前异常级别使用
SP_ELx(x≥1,如 EL1 用SP_EL1、EL2 用SP_EL2等内核级栈指针)时触发异常。 - Lower Exception level(AArch64):从更低异常级别触发,且 “更低级别” 处于 AArch64 执行状态(如从 EL0/EL1 切换到更高 EL 时,且原级别是 64 位模式 )。
- Lower Exception level(AArch32):从更低异常级别触发,且 “更低级别” 处于 AArch32 执行状态(如从 32 位模式的 EL0/EL1 切换到更高 EL )。
- Current Exception level with SP_EL0:当前异常级别使用
列(Offset for exception type)
按异常类型分类,对应不同偏移:
- Synchronous:同步异常(如指令执行错误、软件触发异常等,与指令流同步发生 )。
- IRQ or vIRQ:普通中断(IRQ)或虚拟中断(vIRQ,虚拟化场景下)。
- FIQ or vFIQ:快速中断(FIQ,优先级通常更高)或虚拟快速中断(vFIQ,虚拟化场景 )。
- SError or vSError:系统错误(SError,如总线错误等严重错误)或虚拟系统错误(vSError,虚拟化场景 )。
偏移值(如 0x000、0x080 等):基于行(触发状态)和列(异常类型)的组合,给出相对于向量表基地址的偏移,处理器通过基地址 + 偏移,就能找到异常处理程序的入口地址 。

- 异常向量表的整体结构
在 AArch64 EL1 及以上的执行级别中,异常向量表(Vector Table)存放在 VBAR_ELx(Vector Base Address Register)寄存器指向的地址。
这个表里一共包含 4 个“区块” (blocks),分别对应不同的异常来源:
- 从当前的 SP (SP0) 触发的异常
- 从当前的 SP (SPx) 触发的异常(x≠0,比如 SP_EL1)
- 来自下一个更低特权级(比如 EL0) 的异常
- 来自当前特权级(比如 EL1 内部) 的异常
每个区块又包含 4 种异常类型:
- Synchronous exception (同步异常,比如 SVC 指令、数据访问异常)
- IRQ (普通中断)
- FIQ (快速中断)
- SError (系统错误,通常是硬件异常)
所以总共:
4 个区块 × 4 种异常 = 16 个表项
每个表项的大小
ARMv8-A 规定:
- 每个表项的大小固定为 128 字节(0x80)(32条指令大小)。
- 16 个表项 × 128 字节 = 2048 字节 = 2KB。
- 所以整个向量表的大小是 2KB。
也就是说,异常向量表必须是 2KB 对齐的区域。
异常向量表表实际上由 4 组 4 项组成
Linux5.0 内核的异常处理

.align 11 就是让异常向量表按2k 大小对齐
kernel_ventry 是一个宏,简化代码如下

align 7 表示按 2 的 7 次方大小来对齐,2 的 7 次方是 128 个字节
sub 指令是让栈指针 sp 减去一个 S_FRAME_SIZE,其中 S_FRAME_SIZE 称为寄存器框架大小,也就是 struct pt_regs 数据结构的大小
“\()”表示连接
举例:在发生 EL1 的 IRQ 中断时,这条语句变成了”b el1_irq”
kernel_entry 是异常向量表跳转到的第一级入口,它的职责是:
- 保存上下文:把 CPU 现场(通用寄存器)保存到栈上。
- 建立栈帧:为异常处理函数(C 代码)准备好栈空间。
- 切换栈(如果需要):例如从用户态进入内核态时,栈要换成内核栈。
- 调用 C 层的异常分发函数。
以发生在 EL1 的 FIQ 为例


保存异常上下文
- 栈框:Linux 内核中定义了一个struct pt_regs的数据结构来描述内核栈上保存寄存器的排列信息,通常用于保存中断上下文等信息

orig_x0, syscallno, orig_addr_limit, unused, stackfram[0], stackframe[1]都是软件定义保存的东西

实验 2:建立一个异常向量表并创造一个同步异常

FAR Fault Address Register (at EL1)
它的作用是:
- 当 发生同步异常(Synchronous Exception) 时,处理器会把导致异常的 虚拟地址写入到
FAR_ELx对应寄存器中。 EL1就对应内核态(操作系统层),所以FAR_EL1保存的是 EL1 下异常发生时的虚拟地址。
常见场景
- Page Fault(缺页异常)
如果 CPU 在访问一个不存在或非法的虚拟地址时触发异常,FAR_EL1会保存这个地址,内核就可以根据它决定是否分配新页、还是杀掉进程。 - Alignment Fault(未对齐访问异常)
如果访问地址不符合对齐规则,FAR_EL1也会保存那个地址。 - Watchpoint/Breakpoint 异常
FAR_EL1也可能被用来存储触发异常的地址。
相关寄存器
ESR_EL1(Exception Syndrome Register)
保存异常的 原因码(比如是缺页、权限错误、未对齐等)。FAR_EL1
保存导致异常的 具体虚拟地址。
entry.S
1 | #define BAD_SYNC 0 |
kernel.c中定义

在kernel_main中触发trigger_alignment

在boot.S中设置异常向量表

实验 3:解 bug:寻找树莓派 4 上触发异常的指令


这条指令会加载当前 PC+MY_LABEL 为地址取值到 x6
相当于:
1 | x6 = *(PC-relative 地址 + MY_LABEL) |
对于 LDR Xt 来说目标地址必须是 8 字节对齐的(因为 Xt 是 64 位寄存器)
可以改成 w6,只需要四个字节对齐,因为 MY_LABEL 定义为 0x20
异常的解析
ESR_ELx(异常综合信息寄存器 Excepion Syndrome Register)
ESR_ELx它只对针对同步异常和 SError 做更新,不为 IRQ 或 FIQ 更新,因为这些中断处理程序通常从通用中断控制器 (GIC) 的寄存器中获取状态信息。

- ESR 寄存器一共包含四个字段(域)
- Bit 32~63 保留比特位
- Bit 26~31,是异常类型(Exception Class,简称 EC),这个字段指示发生异常的类型,同时用来索引 ISS 域
- Bit 25,IL,表示同步异常指令的指令长度,Instruction Length
- Bit 0~24,ISS(Instruction Specific Syndrome)具体的异常指令编码。这个异常指令编码表依赖不同的异常类型,不同的异常类型有不同的编码格式

常见的异常类型对应编码

第一个表示从低级异常等级的指令异常,第二个表示当前异常等级的指令异常

ISS 字段的编码方式




FAR(失效地址寄存器)

FAR 寄存器保存了发生异常时刻的虚拟地址
实验四:解析数据异常的信息


esr.h
1 |
|
作用:解析 ESR_ELx 寄存器的值,方便异常处理。
结构:
- 高 6 位 → EC(Exception Class)
- IL、ISV → 指令长度/有效性
- ISS → Instruction Specific Syndrome
- FSC → 数据访问异常代码
- WNR、EA、CM → 访问属性
kernel.c
1 | static const char *const bad_mode_handler[] = {"Sync Abort", "IRQ", "FIQ", |
触发数据异常的代码:





