ARMv8 AArch64异常处理机制概览
1 处理机制概述
相对于ARMv7中的异常向量表(Exception Vector Table),ARMv8异常处理机制更为复杂,涉及处理器的异常等级(Exception Levels, ELn)、运行状态(Execution States)和安全模式(Secure Mode)。
这种特性意味着我们在描述异常时需要说明异常是在什么异常等级下发生的,返回时处理器的异常等级、运行状态和安全模式发生了什么变化,比如“在当前异常等级下发生Sync异常,异常等级从EL2切换到EL1,使处理器的运行状态从AArch64转到AArch32”。
ARMv8共有4个异常等级ELn(n=0,1,2,3),EL3最高,EL2最低。复位处理(Reset Handler)和异常向量表分开,支持为每个异常等级提供独立的异常向量表和栈指针寄存器(Stack Pointer,SP)。异常等级只能在复位、异常发生和退出时变更,且进入异常不能降低异常等级、退出异常不能提升异常等级。
2 异常向量表
ARMv8的异常向量表也有固定的区域划分,不过更大——单张异常向量表起始地址0x800字节对齐,内部每段0x80字节对齐,如图2-1。
图2-1 ARMv8异常向量表的结构
上图只是一个异常等级的异常向量表,一般来说需要为EL3、EL2和EL1各提供一张异常向量表。不同异常等级ELn的异常向量表起始地址可以通过寄存器VBAR_ELn(n=1,2,3)设置。
向量表可分为两部分,偏移地址0x000-0x400的用于相应当前异常等级的异常,偏移地址0x400-0x780的向量用于接收低一等级的异常。比如EL2的异常向量表,这部分就用于处理EL1的异常。
ARMv8提供了4个堆栈指针寄存器,不同异常等级ELn(n=1,2,3)可通过寄存器SPSel选择SP_ELn或者EL0作为堆栈指针,这将决定在当前异常等级下发生异常时PC跳转的位置。比如当前异常等级(假设为EL2)下发生IRQ中断,如果当前选择的堆栈指针为SP0,PC就会跳转到异常向量表的0x080偏移位置,如果当前选择的堆栈为SP_EL2,PC就会跳转到异常向量表的偏移地址0x280处。
值得一提的是,堆栈指针寄存器的使用和ARMv7没啥差别。向SPSel值选择了当前异常等级的堆栈指针后,SP寄存器和选定的SP_ELn会产生关联,二者的值会保持一致。同步机制还没有在手册里看到,暂且可以认为SP是引用了SP_ELn的值。
此外,习惯上用后缀t表示当前异常等级选择的堆栈是SP_EL0,比如SP_EL1t,用后缀h表示选择的是ELn,比如SP_EL1h。
3 异常处理
处于AArch64运行状态的ARMv8处理器根据异常的类型、发生异常的异常等级、当前使用的堆栈指针(SP in use)、寄存器组的状态(state of the register file)。
3.1 Taken 和 Return
Taken关乎异常的处理,Return关乎异常的返回。
Taken指的是处理器对异常的响应(Responds),即因异常做出动作。Taken from描述异常的来源,指处理器在Taken发生前一刻的状态。Taken to则描述异常的去向,指的是处理器在Taken发生后一刻的状态。
在异常处理的语境下,Return指的是,额,使用异常返回指令(Exception return instruction)触发的动作?Return form描述异常从何退出,指的是Return前处理器的状态。Return to描述异常退出结果,指的是Return后处理器的状态。
3.2 异常处理入口
产生异常的指令有HVC/SMC/SVC。
如果异常taken to一个使用AArch64运行状态的异常等级ELn,那么处理器在将PC跳转到异常向量表的对应位置前,将执行以下动作:
将Taken from的PSTATE保存到SPSR_ELn寄存器
将优先返回地址(preferred exception return address)保存到ELR_ELn寄存器
配置Taken to的PSTATE寄存器
如果是Synchronous或者SError异常,异常综合信息写入ESR_ELn寄存器
跳转到异常向量表对应位置
上述Taken to的PSTATE配置遵循以下规则:
PSTATE.EL 设置为ELn。
PSTATE.{D, A, I, F, SP, TCO} 设置为 1
PSTATE.SSBS 设置为 SCTLR_ELn.DSSBS 的值
PSTATE.{IL, nRw, UAO} 设置为 0
PSTATE.BTYPE 设置为 0b00
PSTATE.SS 根据v8手册Chapter D2 AArch64 Self-hosted Debug中的规则设置
对于以下任何一种情况,PSTATE.PAN 设置为 1:
—— 目标异常级别为 EL1并且SCTLR_EL1为 0
—— Taken from EL0,Taken to AArch64状态且打开安全模式的EL2,HCR_EL2.{TGE, E2H}是{1, 1},并且SCTLR_EL2.SPAN是0.
PSTATE.ALLINT 设置为SCTLR_ELx.SPINTMASK的值取反
3.3 退出异常
退出异常的指令有ERET/ERETAA/ERETAB,异常等级EL0不支持异常退出指令。退出时切换到的目标异常等级可通过当前异常等级ELn的SPSR_ELn设置。
图3-3-1 通过SPSR_ELn选择Target EL
.global _el3_to_el1
.type _el3_to_el1, "function"
_el3_to_el1:
// Initialize the SCTLR_EL1 register before entering EL1
MSR SCTLR_EL1, XZR
// Determine the EL1 Execution state
MRS X0, SCR_EL3
ORR X0, X0, #(1<<10) // RW, EL2 Execution state is AArch64
// Determine the EL1 Security Mode
AND X0, X0, #0xFFFFFFFFFFFFFFFE // NS, EL1 is Secure world
MSR SCR_EL3, x0
MOV X0, #0b00101
MSR SPSR_EL3, X0 // M[4:0]=01001 EL1h, must match SCR_EL3.RW
// Determine EL2 entry.
ADR X0, el2_entry
MSR ELR_EL3, X0
ERET
图3-3-2 SCR_EL3寄存器与异常返回相关的位
SCR_EL3[10] 控制低等级的运行状态,置0时切换到低等级的运行状态为32位模式,置1时低等级的运行状态则为64位模式。
SCR_EL3[0] 控制低等级的安全模式,置0时低等级位安全模式,置1时低等级为非安全模式。
图3-3-3 SPSR_EL3 寄存器与异常返回相关的位
SPSR_EL3[4] 与 RETURN 后的运行模式相关,必须和 SCR_EL3[10] 保持一致。
SPSR_EL3[3:0] 用于选择异常等级和堆栈寄存器。在 RETURN 时,SPSR_EL3[3:2] 的值会被复制到 PSTATE.EL,SPSR_EL3[0] 的值会被复制到 PSTATE.SP。
图3-3-4 ELR_
3.4 异常等级切换
ARMv8有EL3、EL2、EL1、EL0共四个异常等级,用户可通过产生或退出异常切换异常等级。切换异常等级需注意配置目标异常等级的栈指针寄存器、运行状态和安全模式。
EL0,EL1有安全模式和非安全模式的区别。 EL2是虚拟机管理级别并且只有非安全模式。 EL3是最高优先级并且只存在安全模式。
此外,EL3直接切换到EL1只能选择安全模式,因此EL3切换到EL1只有两个路径,一种是EL3(S)->EL2(NS)->EL1(S),另一种是EL3(S)->EL1(S)。
3.4.1 异常等级切换基本流程
- 触发异常,跳转到 VBR_EL3 中保存的异常向量表起始地址
- 顺序执行向量表中指令
- 配置 SCR_EL3/SPSR_EL3/ELR_EL3 等寄存器相关位段
- 执行 ERET 指令,退出异常,跳转到 ELR_EL3 寄存器中保存的地址执行
3.4.2 异常等级切换实例
下面以一个汇编程序为例子说明这个过程。
.align 0x4
reset_handler:
// Set exception table base addr
LDR X0, =el3_exception_table_start
MSR VBAR_EL3 X0
// Enabling exception
MRS X0, SCR_EL3
ORR X0, X0, #(1<<1) // IRQ
ORR X0, X0, #(1<<2) // FIQ
ORR X0, X0, #(1<<3) // Abort
MSR SCR_EL3, X0
MSR DAIFClr, 0x7 // Enable SError, IRQ & FIQ
// Set SP
MSR SPSel, #0x01
LDR X0, =0x60000000
MOV SP, X0
SVC 0
/
exception_level_1_entry:
b exception_level_1_entry
swithc2el1:
// Initialize the SCTLR_EL1 register before entering EL1
MSR SCTLR_EL1, XZR
// Determine the EL1 Execution state
MRS X0, SCR_EL3
ORR X0, X0, #(1<<10) // RW, EL2 Execution state is AArch64
// Determine the EL1 Security Mode
AND X0, X0, #0xFFFFFFFFFFFFFFFE // NS, EL1 is Secure world
MSR SCR_EL3, x0
MOV X0, #0b00101
MSR SPSR_EL3, X0 // M[4:0]=01001 EL1h, must match SCR_EL3.RW
// Determine EL1 entry.
ADR X0, exception_level_1_entry
MSR ELR_EL3, X0
ERET
/
sloop:
b sloop
.align 0x800
el3_et:
// Current exception level, sp_el0
.align 0x80
el3_cur_spel0_sync:
b swithc2el1
.align 0x80
el3_cur_spel0_irq:
b sloop
.align 0x80
el3_cur_spel0_fiq:
b sloop
.align 0x80
el3_cur_spel0_serror:
b sloop
// Current exception level, sp_elx
.align 0x80
el3_cur_spelx_sync:
b swithc2el1
.align 0x80
el3_cur_spelx_irq:
b sloop
.align 0x80
el3_cur_spelx_fiq:
b sloop
.align 0x80
el3_cur_spelx_serror:
b sloop
// Lower exception level, sp_el0
.align 0x80
el3_lower_spel0_sync:
b sloop
.align 0x80
el3_lower_spel0_irq:
b sloop
.align 0x80
el3_lower_spel0_fiq:
b sloop
.align 0x80
el3_lower_spel0_serror:
b sloop
// Lower exception level, sp_elx
.align 0x80
el3_lower_spelx_sync:
b sloop
.align 0x80
el3_lower_spelx_irq:
b sloop
.align 0x80
el3_lower_spelx_fiq:
b sloop
.align 0x80
el3_lower_spelx_serror:
b sloop
应用的场景预设为上电复位后处理器异常等级为 EL3,运行于 AARCH64 模式,这个程序的功能是将处理器的异常等级切换到 EL1,下面我们逐行分析这段汇编代码。
.align 0x4
reset_handler:
第一行用 .align提示编译器此后的指令地址4字节对齐。
而后是一个符号 reset_handler,这是程序的起始位置,也是处理器复位后执行的第一条指令的位置。通常用来配置处理器的异常处理、中断使能、堆栈等功能相关的寄存器,为执行 C 语言等高级语言编写的代码准备条件。
// Set exception table base addr
LDR X0, =el3_exception_table_start
MSR VBAR_EL3 X0
第一小段指令是配置 VBAR_EL3 寄存器。这个寄存器用于保存 EL3 异常等级下的异常向量表的起始地址,当异常被捕获后,处理器将从这个寄存器保存的地址处取指执行。el3_exception_table_start 也是一个符号,可以在后面找到,对应异常向量表的起始地址。
// Enabling exception
MRS X0, SCR_EL3
ORR X0, X0, #(1<<1) // IRQ
ORR X0, X0, #(1<<2) // FIQ
ORR X0, X0, #(1<<3) // Abort
MSR SCR_EL3, X0
MSR DAIFClr, 0x7 // Enable SError, IRQ & FIQ
然后通过 SCR_EL3 寄存器使能中断路由,并通过 DAIFClr 清除 SError、IRQ和FIQ的屏蔽位。
// Set SP
MSR SPSel, #0x01
LDR X0, =0x60000000
MOV SP, X0
紧接着就配置堆栈寄存器,ARMv8 处理器每个异常等级都有独立的堆栈指针,另有一个公用的堆栈指针(每个异常等级都可以访问),这里 SPSel 设置为 1,选择使用 EL3 独有的 SP。
SVC 0
如前所述,要切换异常等级首先要触发异常,这里我们使用 SVC 指令触发异常。
// Current exception level, sp_elx
.align 0x80
el3_cur_spelx_sync:
b swithc2el1
当前异常等级触发的异常,且使用当前异常等级特有的 SP 寄存器,于是跳转到异常向量表的这个位置,执行 b switch2el1 跳转到 switch2el1 处。
swithc2el1:
// Initialize the SCTLR_EL1 register before entering EL1
MSR SCTLR_EL1, XZR
// Determine the EL1 Execution state
MRS X0, SCR_EL3
ORR X0, X0, #(1<<10) // RW, EL2 Execution state is AArch64
// Determine the EL1 Security Mode
AND X0, X0, #0xFFFFFFFFFFFFFFFE // NS, EL1 is Secure world
MSR SCR_EL3, x0
MOV X0, #0b00101
MSR SPSR_EL3, X0 // M[4:0]=01001 EL1h, must match SCR_EL3.RW
// Determine EL2 entry.
ADR X0, exception_level_1_entry
MSR ELR_EL3, X0
ERET
切换过程前面已经介绍过了,就是配置 SCR_EL3、SPSR_EL3 和 ELR_EL3 寄存器,这里在配置 ELR_EL3 寄存器之后使用 ERET 指令从异常退出,跳转到 ELR_EL3 寄存器保存的地址处(即 exception_level_1_entry 处)执行,跳转前会将异常等级切换为 EL1。
exception_level_1_entry:
b exception_level_1_entry
exception_level_1_entry 里什么也没做,只是原地跳转,死循环。
4 相关的指令和寄存器速查
4.1 ERET
用于从异常返回,执行时从当前异常等级的 SPSR 寄存器恢复 PSTATE 寄存器,根据而后当前异常等级的 ELR 跳转。
4.2 SCR_EL3
4.3 SPSR_ELn.DAIF
4.4 ELR_ELn
捕获异常时,保存返回地址。
5 架构相关的补充信息
5.1 运行状态
运行状态指的是处理单元的运行环境,包括寄存器位宽、指令集、异常模型、虚拟内存系统架构 VMSA 和编程模型。
AARCH64 模式特性如下:
提供 31 个 64 位的通用寄存器(X0-X30),X30 用作链接寄存器
提供 64 位的程序计数器(PC)、堆栈指针(SP)和异常链接寄存器(ELRs)
提供 32 个128 位的 SIMD 寄存器,用于支持向量/标量浮点操作
提供单一指令集 A64
使用支持运行特权架构(execution privilege hierarchy)的 ARMv8 异常模型,支持最多 4 个异常等级(EL0-EL3)
支持 64 位虚拟地址
支持多种处理器状态,保存在PSTATE 寄存器中,此寄存器可用汇编指令修改
寄存器命名带有后缀,表明此寄存器可在哪个/哪些异常等级访问
AARCH64 即 64 位运行模式,此模式地址寄存器位宽为 64,基本指令集中的指令可以使用 64 位寄存器进行处理。 AARCH64 模式支持 A64 指令集。
AARCH32 即 32 位模式,此模式下地址寄存器位宽为 32, 处理过程使用 32 位的寄存器,支持 A32 和 T32 指令集。
5.2 架构系列
ARM架构每版本有3个系列,ARMv8 架构 A 系列的实现可以称作 AArchv8-A,而Armv9 架构 A 系列的实现可以称为 AArchv9-A。
A系列(应用系列):
支持基于内存管理单元(MMU)的虚拟内存系统架构 VMSA
支持 A64、A32 和 T32 指令集
R系列(实时系列):
支持基于内存保护单元(MPU)的内存保护系统架构
可选支持 VMSA
支持 A64、A32 和 T32 指令集
M系列(微控制器系列):
实现为低延迟中断处理(low-latency interrupt processing)设计的编程模型,支持硬件对寄存器进行出入栈操作、用高级语言编写中断处理程序
实现 R 系列的 PMSA 的变体
支持 T32 指令的变体