ARMv8的异常机制
运行态
这里的运行态指特权级别,非执行状态(AArch32/AArch64),ARMv8-A 架构使用异常级别(Exception levels)表特权级别,从 EL0 到 EL3权限依次提升,其中:
- EL0 级别执行称为非特权执行(unprivileged execution),通常为用户态。
- EL1 通常执行操作系统内核及其相关功能,通常称为内核态。
- EL2 提供了对非安全操作虚拟化的支持,通常跑虚拟机监控程序(Hypervisor)。
- EL3 提供了在两种安全状态(Secure state 和 Non-secure state)之间切换的支持,即跑安全监控程序(Secure monitor)。
一个实现必须包含 EL0 和 EL1,而EL2 和 EL3 是可选的。 当在 AArch64 状态下执行时,执行只能在发生异常或从异常返回时在异常级别之间移动: - 在发生异常时,异常级别只能增加或保持不变。 - 在从异常返回时,异常级别只能减少或保持不变。
在发生异常时,执行切换到的或保持的异常级别称为该异常的目标异常级别(target Exception level)。 每种异常类型都有一个目标异常级别,该级别要么是:
-
由异常的性质隐含决定。
-
由系统寄存器中的配置位定义。
异常不能以 EL0 为目标。 异常级别存在于特定的安全状态(Security state)中。当在某个异常级别执行时,处理单元(PE)可以访问以下两者:
-
当前异常级别和当前安全状态组合下可用的资源。
-
所有较低异常级别可用的资源,前提是这些资源对当前安全状态可用。
这意味着,如果实现包含 EL3,则当执行在 EL3 时,PE 可以访问所有异常级别下可用的资源,适用于两种安全状态。
除了 EL0 之外,每个异常级别都有自己的转换机制(translation regime)和相关的控制寄存器。

注:具体跳到哪一级,由控制寄存器来决定,比如默认EL0是跳到EL1,但设置了HCR就可以直接跳到EL2,具体可见异常相关的伪代码。
异常类型
主要分同步和异步,判断标准很复杂,粗略来说就是异常是否由于直接执行或尝试执行指令而生成的。
中断(异步异常)
| 类型 | 名称 | 描述 |
|---|---|---|
| 物理中断 | SError | 系统错误中断,通常由内存系统或其他硬件错误引发。 |
| IRQ | 普通中断请求,优先级低于 FIQ,用于处理一般外设中断。 | |
| FIQ | 快速中断请求,优先级高于 IRQ,用于处理需要快速响应的外设中断。 | |
| 虚拟中断 | vSError | 虚拟系统错误中断,由 EL2 软件启用并挂起。 |
| vIRQ | 虚拟普通中断请求,由 EL2 软件启用并挂起。 | |
| vFIQ | 虚拟快速中断请求,由 EL2 软件启用并挂起。 |
异常(同步异常)
它存在如下的同步异常:
| 异常类型 | 描述 |
|---|---|
| 未定义指令异常 | 尝试执行未定义的指令时触发,如在不适当的异常级别(Exception Level)执行指令、在指令被禁用时尝试执行、尝试执行未分配的指令位模式。 |
| 非法执行状态异常 | 当 PSTATE.IL 为 1 时,尝试执行指令会触发此异常,通常是由于非法返回事件,可以见异常返回那儿。 |
| 栈指针(SP)未对齐异常 | 使用未对齐的栈指针(SP)时触发。 |
| 程序计数器(PC)未对齐异常 | 尝试执行未对齐的程序计数器(PC)指向的指令时触发。 |
| 异常生成指令异常 | 执行以下异常生成指令时触发:
|
| 指令陷阱异常 | 尝试执行被系统寄存器配置为陷阱的指令时触发,某些指令被配置为触发陷阱,以便在更高异常级别处理。 |
| 指令中止异常(Instruction Abort) | 内存地址转换系统在尝试执行指令时发生故障时触发,如访问的内存区域生成故障(如页表无效或权限不足)。 |
| 数据中止异常(Data Abort) | 内存地址转换系统在尝试读取或写入内存时发生故障时触发,如访问的内存区域生成故障(如页表无效或权限不足)。 |
| 未对齐地址数据中止异常 | 尝试访问未对齐的内存地址时触发。 |
| 调试异常 | 与调试相关的异常,包括:
|
| 浮点异常 | 在支持浮点异常捕获的实现中,IEEE 浮点异常会触发此异常,如浮点运算中发生异常(如除以零或溢出)。 |
| 外部中止异常(External Abort) | 内存访问失败时触发,包括地址转换期间的内存系统部分访问失败。ARMv8 架构允许但不要求实现将此类异常视为同步异常。 |
复位
略略略~
寄存器
ELR(Exception Link Register)
异常链接寄存器用于保存异常返回地址,每个目标异常级别会有一个ELR,即有ELR_EL1~ELR_EL3,在异常处理完成后返回到该地址继续执行。
SPSR(Saved Program Status Register)
保存的程序状态寄存器用于在发生异常时保存处理单元(PE)的状态,在 AArch64 状态下,每个可以目标异常级别都有一个 SPSR,即存在SPSR_EL1~SPSR_EL3三个,在异常返回时用来恢复PSTATE,根据异常发生时两种执行态它们的域有差别:


下面是详细解释:
| 位域 | 描述 | 详细作用 |
|---|---|---|
| N, Z, C, V, bits[31:28] | 异常发生前 PSTATE.{N, Z, C, V} 条件标志的值。 | 这些是条件标志位,用于记录上一条指令的执行结果:
|
| Bits[27:24], 从 AArch64 状态发生异常 | 保留,RES0。 | 这些位保留未使用,通常设置为 0(RES0)。 |
| Q, bit[27], 从 AArch32 状态发生异常 | 异常发生前 PSTATE.Q 的值。 | Q 位用于指示 SIMD 或浮点指令是否发生了溢出或饱和。在 AArch32 状态下,它记录异常发生前的 SIMD 溢出状态。 |
| IT[1:0], bits[26:25], 从 AArch32 状态发生异常 | IT 位用于 AArch32 状态下的条件执行指令(如 IT 块)。IT[1:0] 与 IT[7:2] 一起表示条件执行的状态。 | |
| J, bit[24], 从 AArch32 状态发生异常 | 显示异常发生前 PSTATE.J 的值。此位为 RES0。 | J 位在 AArch32 状态下用于指示 Jazelle 执行状态。在 AArch64 状态下未使用,通常为 0(RES0)。 |
| UAO, bit[23], 从 ARMv8.2 开始 | 异常发生前 PSTATE.UAO 的值。 | UAO(User Access Override)位用于控制用户模式下的内存访问权限。当置位时,允许用户模式访问某些特权内存区域。 |
| PAN, bit[22], 从 ARMv8.1 开始 | 异常发生前 PSTATE.PAN 的值。 | PAN(Privileged Access Never)位用于防止特权模式(如内核)访问用户空间内存。当置位时,特权模式访问用户内存会触发异常。 |
| SS, bit[21] | 软件步进位。 | SS(Software Step)位用于调试。当置位时,PE 会在执行下一条指令后触发调试异常,用于单步调试。 |
| IL, bit[20] | 非法执行状态位。 | IL(Illegal Execution)位指示是否发生了非法执行状态。例如,尝试从 AArch64 状态返回到 AArch32 状态时,此位置位。 |
| Bits[19:10], 从 AArch64 状态发生异常 | 保留,RES0。 | 这些位保留未使用,通常设置为 0(RES0)。 |
| GE[3:0], bits[19:16], 从 AArch32 状态发生异常 | 显示异常发生前 PSTATE.GE 的值。 | GE(Greater than or Equal)位用于 AArch32 状态下的 SIMD 指令,表示每个字节或半字的比较结果。 |
| IT[7:2], bits[15:10], 从 AArch32 状态发生异常 | 与 IT[1:0] 一起,显示异常发生前 PSTATE.IT 的值。 | IT 位用于 AArch32 状态下的条件执行指令(如 IT 块)。IT[7:2] 与 IT[1:0] 一起表示条件执行的状态。 |
| D, bit[9], 从 AArch64 状态发生异常 | 调试异常屏蔽位。 | D 位用于屏蔽调试异常。当置位时,调试异常被禁用。 |
| E, bit[9], 从 AArch32 状态发生异常 | 数据访问的字节序。 | E 位用于指示数据访问的字节序(大端或小端)。当置位时,表示大端模式;清零时,表示小端模式。 |
| A, bit[8] | SError 中断屏蔽位。 | A 位用于屏蔽 SError(系统错误)中断。当置位时,SError 中断被禁用。 |
| I, bit[7] | IRQ 屏蔽位。 | I 位用于屏蔽 IRQ(普通中断)。当置位时,IRQ 中断被禁用。 |
| F, bit[6] | FIQ 屏蔽位。 | F 位用于屏蔽 FIQ(快速中断)。当置位时,FIQ 中断被禁用。 |
| Bit[5], 从 AArch64 状态发生异常 | 保留,RES0。 | 此位保留未使用,通常设置为 0(RES0)。 |
| T, bit[5], 从 AArch32 状态发生异常 | 异常发生前 PSTATE.T 的值。 | T 位用于指示 AArch32 状态下的 Thumb 执行状态。当置位时,表示当前执行 Thumb 指令集。 |
| M[4:0], bits[4:0] | 此字段的名称继承自 ARMv7,其中 M 字段指定了 PE 模式。 | M M[4]表示异常发生时的执行状态,如1为AArch32,0为AArch64,剩下的根据它含义有所不同:
|
SP(Stack Pointer)
每个实现的异常级别都有一个专用的栈指针寄存器,除了各级别专有的SP,EL1~EL3还能用EL0的SP,这里用t表示使用SP_EL0,h表示使用SP_ELx:
| 异常级别 | AArch64 栈指针选项 |
|---|---|
| EL0 | EL0t |
| EL1 | EL1t, EL1h |
| EL2 | EL2t, EL2h |
| EL3 | EL3t, EL3h |
在每次异常切换时,硬件会自动使用对应目标异常级别的SP,而后异常处理程序可通过设置PSTATE.SP来手动切换。
ESR(Exception Syndrome Register)
异常综合信息寄存器保存同步异常和系统错误的综合信息,每个目标级别有一个,即ESR_EL1~ESR_EL3:

它的含义如下:
- EC:Exception Class表示异常的类别。
- IL:Instruction Length用于同步异常,表示造成异常的指令长度。
- ISS:Instruction Specific Syndrome保存EC类下更详细的信息,根据EC不同而编码不同内容。
这一块内容很多,可以直接见手册的D1.10.4 Exception classes and the ESR_ELx syndrome registers部分,不再额外列了~
FAR(Fault Address Register)
它是64位的,存储导致故障或异常的虚拟地址,在处理页面故障(以便在地址有效时调入页面)和检测内核内存操作(内核文本中的故障)时非常重要。
VBAR(Vector Base Address Register)
向量基址寄存器存储异常向量表的基址,每个目标及异常级别有一个,即VBAR_EL1~VBAR_EL3,该表是12位对齐的,由于地址只有48/52位宽,因此需注意若未使用指针认证则高位未使用部分必须相同(全0/1):

注:有个类似的RVBAR_ELn,即Reset Vector Base Address Register,存储复位后从哪里开始执行,它是硬件设置的软件无法修改的物理地址,并且只有最高异常级别有一个。
机制
先说异常向量表,每个目标异常级别一张,每张表根据异常来源可分为4类,而每类又分为4部分,每部分0x80字节:
| 异常来源 | 同步异常 | IRQ 或 vIRQ | FIQ 或 vFIQ | SError 或 vSError |
|---|---|---|---|---|
| 当前异常级别,使用 SP_EL0 | 0x000 | 0x080 | 0x100 | 0x180 |
| 当前异常级别,使用 SP_ELx(x > 0) | 0x200 | 0x280 | 0x300 | 0x380 |
| 较低异常级别,且目标级别的下一级实现为 AArch64 | 0x400 | 0x480 | 0x500 | 0x580 |
| 较低异常级别,且目标级别的下一级实现为 AArch32 | 0x600 | 0x680 | 0x700 | 0x780 |
值得注意的是它里面存储的并不是异常处理例程的地址,而是指令,即每部分能存32条指令,当异常发生时,根据如下伪代码跳到异常向量表对应位置去执行:
aarch64/exceptions/takeexception/AArch64.TakeException
// AArch64.TakeException()
// =======================
// Take an exception to an Exception Level using AArch64.
AArch64.TakeException(bits(2) target_el, ExceptionRecord exception, bits(64) preferred_exception_return, integer vect_offset)
// 检查目标异常级别是否有效,且当前状态为 AArch64,且目标异常级别不低于当前异常级别(合法)
assert HaveEL(target_el) && !ELUsingAArch32(target_el) && UInt(target_el) >= UInt(PSTATE.EL);
from_32 = UsingAArch32(); // 检查当前是否处于 AArch32 状态
// 如果是 AArch32 状态,清零 X[] 寄存器的高位,确保 AArch64 寄存器的正确性
if from_32 then AArch64.MaybeZeroRegisterUppers();
if UInt(target_el) > UInt(PSTATE.EL) then // 判断目标异常级别是否高于当前异常级别
boolean lower_32;
if target_el == EL3 then // 处理 EL3 的特殊情况
// 如果当前是非安全状态且支持 EL2,则检查 EL2 是否使用 AArch32
if !IsSecure() && HaveEL(EL2) then // 非安全状态下,检查 EL2 的状态
lower_32 = ELUsingAArch32(EL2); // 如果 EL2 使用 AArch32,则标记为 true
else
lower_32 = ELUsingAArch32(EL1); // 如果 EL1 使用 AArch32,则标记为 true
// 如果当前在 Host 模式,且当前异常级别是 EL0,目标异常级别是 EL2 (处理 Host 模式下 EL0 到 EL2 的异常)
elsif IsInHost() && PSTATE.EL == EL0 && target_el == EL2 then
lower_32 = ELUsingAArch32(EL0); // 如果 EL0 使用 AArch32,则标记为 true
else
lower_32 = ELUsingAArch32(target_el - 1); // 检查目标异常级别的下一级是否使用 AArch32
// 如果下一级是 AArch32,偏移量增加 0x600,否则增加 0x400
vect_offset = vect_offset + (if lower_32 then 0x600 else 0x400); //
// 如果当前使用 SP_ELx(x > 0)作为栈指针,调整异常向量表的偏移量,增加 0x200
elsif PSTATE.SP == '1' then
vect_offset = vect_offset + 0x200;
spsr = GetPSRFromPSTATE(); // 保存当前处理器状态到 SPSR,用于异常返回时恢复状态
// 如果支持 UAO 扩展,则清零 PSTATE.UAO,禁用用户访问覆盖功能
if HaveUAOExt() then PSTATE.UAO = '0';
// 如果异常类型不是 IRQ 或 FIQ,即为同步异常则报告异常
if !(exception.type IN {Exception_IRQ, Exception_FIQ}) then
AArch64.ReportException(exception, target_el);
PSTATE.EL = target_el; PSTATE.nRW = '0'; PSTATE.SP = '1'; // 更新 PSTATE 的值,设置目标异常级别、状态为 AArch64、使用 SP_ELx 作为栈指针
SPSR[] = spsr; // 将当前状态保存到 SPSR,用于异常返回时恢复
ELR[] = preferred_exception_return; // 设置异常返回地址,通常是异常发生时的下一条指令地址
PSTATE.SS = '0'; // 禁用单步调试功能
PSTATE.<D,A,I,F> = '1111'; // 禁用调试、异步异常、IRQ 和 FIQ,确保异常处理期间不会被中断
PSTATE.IL = '0'; // 清零 PSTATE.IL(指令长度),确保指令长度为正常模式
// 如果当前是从 AArch32 状态进入异常,则清零 PSTATE.IT 和 PSTATE.T(Thumb 状态),确保 AArch64 状态正确
if from_32 then
PSTATE.IT = '00000000'; PSTATE.T = '0';
// 如果支持 PAN 扩展,并且当前异常级别是 EL1 或 EL2(Host 模式),且 SCTLR.SPAN 为 0,则启用 PAN,限制特权模式对用户内存的访问
if HavePANExt() && (PSTATE.EL == EL1 || (PSTATE.EL == EL2 && ELIsInHost(EL0))) && SCTLR[].SPAN == '0' then
PSTATE.PAN = '1';
// 跳转到异常向量表的指定偏移量处执行异常处理程序
BranchTo(VBAR[]<63:11>:vect_offset<10:0>, BranchType_EXCEPTION);
// 如果支持 RAS 扩展,并且 SCTLR.IESB 为 1,则执行错误同步屏障,确保错误处理的正确性
if HaveRASExt() && SCTLR[].IESB == '1' then
ErrorSynchronizationBarrier(MBReqDomain_FullSystem, MBReqTypes_All);
iesb_req = TRUE;
TakeUnmaskedPhysicalSErrorInterrupts(iesb_req); // 处理未屏蔽的物理 SError 中断
EndOfInstruction(); // 结束当前指令的执行,准备进入异常处理程序
*OS的运行态切换
总览
xnu支持4类系统调用:
- unix syscall:
- mach syscall:
- mdep syscall:
- diag syscall:这是x86独有的,开发调试用,忽略~


学习中....
参考
- ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture profile:D1 The AArch64 System Level Programmers’ Model
- 《*OS Internals Volume II:Kernel Internals》chapter 4 -- Jonathan Levin
- 《*OS Internals Volume I:User Space》chapter 2 -- Jonathan Levin