ARM64异常处理
1. 异常级别
ARM64处理器定义了4个异常级别:0~3。通常ARM64处理器在异常级别0执行进程,在异常级别1执行内核。ARM64处理器的异常级别0就是我们常说的用户模式,异常级别1就是我们常说的内核模式。
2. 异常分类
在ARM64体系结构中,异常分为同步异常和异步异常。
同步异常:
(1)系统调用
(2)数据中止
(3)指令中止
(4)栈指针或指令地址没有对齐
(5)没有定义的指令
(6)调试异常
异步异常:
(1)中断(IRQ)
(2)快速中断(FIQ)
(3)系统错误
3. 异常向量表
当异常发生的时候,处理器需要执行异常的处理程序。存储异常处理程序的内存位置称为异常向量,通常把所有异常向量存放在一张表中,称为异常向量表。
对于ARM64处理器的异常级别1、2和3,每个异常级别都有自己的异常向量表,异常向量表的起始虚拟地址存放在寄存器VBAR_ELn(向量基准地址寄存器,Vector Based Address Register)中。
ARM64架构内核定义的异常级别1的异常向量表如下:
arch/arm64/kernel/entry.S
// 宏kernel_ventry
.macro kernel_ventry, el, label, regsize = 64
.align 7
sub sp, sp, #S_FRAME_SIZE
b el\()\el\()_\label
.endm
/*
* Exception vectors.
*/
.pushsection ".entry.text", "ax"
.align 11
ENTRY(vectors)
kernel_ventry 1, sync_invalid // 异常级别1生成的同步异常,使用栈指针寄存器SP_EL0
kernel_ventry 1, irq_invalid // 异常级别1生成的中断,使用栈指针寄存器SP_EL0
kernel_ventry 1, fiq_invalid // 异常级别1生成的快速中断,使用栈指针寄存器SP_EL0
kernel_ventry 1, error_invalid // 异常级别1生成的系统错误,使用栈指针寄存器SP_EL0
kernel_ventry 1, sync // 异常级别1生成的同步异常,使用栈指针寄存器SP_EL1
kernel_ventry 1, irq // 异常级别1生成的中断,使用栈指针寄存器SP_EL1
kernel_ventry 1, fiq_invalid // 异常级别1生成的快速中断,使用栈指针寄存器SP_EL1
kernel_ventry 1, error // 异常级别1生成的系统错误,使用栈指针寄存器SP_EL1
kernel_ventry 0, sync // 64位应用程序在异常级别0生成的同步异常
kernel_ventry 0, irq // 64位应用程序在异常级别0生成的中断
kernel_ventry 0, fiq_invalid // 64位应用程序在异常级别0生成的快速中断
kernel_ventry 0, error // 64位应用程序在异常级别0生成的系统错误
#ifdef CONFIG_COMPAT
kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0
kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0
kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0
kernel_ventry 0, error_compat, 32 // Error 32-bit EL0
#else
kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0
kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0
kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0
kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0
#endif
END(vectors)
把“kernel_ventry 1, sync”展开以后是:
.align 7 // 表示下一条指令的地址对齐到2的7次方,即对齐到128
sub sp, sp, #S_FRAME_SIZE
b el1_sync // 跳转到标号el1_sync
在启动过程中,0号处理器称为引导处理器,其他处理器称为从处理器。引导处理器在函数__primary_switched()中把寄存器VBAR_EL1设置为异常级别1的异常向量表的起始虚拟地址:
_head() -> stext() -> __primary_switch() -> __primary_switched()
arch/arm64/kernel/head.S
__primary_switched:
...
adr_l x8, vectors
msr vbar_el1, x8 // 把寄存器VBAL_EL1设置为异常向量表的起始虚拟地址
isb
...
从处理器在函数__secondary_switched()中把寄存器VBAR_EL1设置为异常级别1的异常向量表的起始虚拟地址。
secondary_entry() -> secondary_startup() -> __secondary_switched()
arch/arm64/kernel/head.S
__secondary_switched:
adr_l x5, vectors
msr vbar_el1, x5
isb
...
ENDPROC(__secondary_switched)
4.1.4 异常处理
当处理器取出异常处理的时候,自动执行下面的操作:
(1)把当前的处理器状态(Processor State,PSTATE)保存在寄存器SPSR_EL1(保存程序状态寄存器,Saved Program Status Register)中
(2)把返回地址保存在寄存器ELR_EL1(异常链接寄存器,Exception Link Register)中
- 如果是系统调用,那么返回地址是系统调用指令后面的指令
- 如果是除系统调用外的同步异常,那么返回地址是生成的异常指令,因为执行完异常处理程序以后需要重新执行生成异常的指令
- 如果是同步异常,那么返回地址是没有执行的第一条指令
(3)把处理器状态DAIF这4个异常掩码位都设置为1,禁止这4种异常,D是调试掩码位(Debug mask bit),A是系统错误掩码位(SError mask bit),I是中断掩码位(IRQ mask bit),F是快速中断掩码位(FIQ mask bit)
(4)如果是同步异常或系统错误异常,把生成异常的原因保存在寄存器ESR_EL1(异常症状寄存器,Exception Syndrome Register)中
(5)如果是同步异常,把错误地址保存在寄存器FAR_EL1(错误地址寄存器,Fault Address Register)中
(6)如果处理器处于用户模式(异常级别0),那么把异常级别提升到1
(7)根据向量基准地址寄存器VBAR_EL1、异常类型和生成异常的异常级别计算出异常向量的虚拟地址,执行异常向量
对于ARM64位应用程序在用户模式(异常级别0)下生成的同步异常,入口是el0_sync,其代码如下:
/*
* EL0 mode handlers.
*/
.align 6
el0_sync:
kernel_entry 0 // 把所有通用寄存器的值保存在当前进程的内核栈中
mrs x25, esr_el1 // read the syndrome register
lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class
cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state 系统调用
b.eq el0_svc
cmp x24, #ESR_ELx_EC_DABT_LOW // data abort in EL0 数据中止
b.eq el0_da
cmp x24, #ESR_ELx_EC_IABT_LOW // instruction abort in EL0 指令中止
b.eq el0_ia
cmp x24, #ESR_ELx_EC_FP_ASIMD // FP/ASIMD access 访问浮点或高级SIMD
b.eq el0_fpsimd_acc
cmp x24, #ESR_ELx_EC_SVE // SVE access 浮点或高级SIMD异常
b.eq el0_sve_acc
cmp x24, #ESR_ELx_EC_FP_EXC64 // FP/ASIMD exception
b.eq el0_fpsimd_exc
cmp x24, #ESR_ELx_EC_SYS64 // configurable trap
b.eq el0_sys
cmp x24, #ESR_ELx_EC_SP_ALIGN // stack alignment exception
b.eq el0_sp_pc
cmp x24, #ESR_ELx_EC_PC_ALIGN // pc alignment exception
b.eq el0_sp_pc
cmp x24, #ESR_ELx_EC_UNKNOWN // unknown exception in EL0
b.eq el0_undef
cmp x24, #ESR_ELx_EC_BREAKPT_LOW // debug exception in EL0
b.ge el0_dbg
b el0_inv