1、异常分类
在arm64体系结构中,异常分为同步异常与异步异常。
同步异常是试图执行指令时产生的异常,或是作为指令的执行结果生成的异常。
- 系统调用:异常级别0使用svc指令陷入异常级别1
- 数据中止:访问数据时的页错误异常,虚拟地址没有映射到物理地址,或者没有写权限
- 指令中止:取指令时的页错误异常,虚拟地址没有映射到物理地址,或者没有执行权限
- 栈指针或指令没有对齐
- 没有定义的指令
- 调试异常
异步异常不是由正在执行的指令生成的,和正在执行的指令没有关联。
- 中断:普通优先级中断
- 快速中断:高优先级中断
- 系统错误:SError,由硬件错误触发的异常,最常见的是把脏数据从缓存行写回内存时触发异步的数据中止异常
2、异常向量表
//https://elixir.bootlin.com/linux/v4.12/source/arch/arm64/kernel/entry.S#L317
/*
* Exception vectors.
*/
.pushsection ".entry.text", "ax"
.align 11
ENTRY(vectors)
ventry el1_sync_invalid // Synchronous EL1t
ventry el1_irq_invalid // IRQ EL1t
ventry el1_fiq_invalid // FIQ EL1t
ventry el1_error_invalid // Error EL1t
ventry el1_sync // Synchronous EL1h
ventry el1_irq // IRQ EL1h
ventry el1_fiq_invalid // FIQ EL1h
ventry el1_error_invalid // Error EL1h
ventry el0_sync // Synchronous 64-bit EL0
ventry el0_irq // IRQ 64-bit EL0
ventry el0_fiq_invalid // FIQ 64-bit EL0
ventry el0_error_invalid // Error 64-bit EL0
#ifdef CONFIG_COMPAT
ventry el0_sync_compat // Synchronous 32-bit EL0
ventry el0_irq_compat // IRQ 32-bit EL0
ventry el0_fiq_invalid_compat // FIQ 32-bit EL0
ventry el0_error_invalid_compat // Error 32-bit EL0
#else
ventry el0_sync_invalid // Synchronous 32-bit EL0
ventry el0_irq_invalid // IRQ 32-bit EL0
ventry el0_fiq_invalid // FIQ 32-bit EL0
ventry el0_error_invalid // Error 32-bit EL0
#endif
END(vectors)
- 对于内核模式(异常级别1)生成的异常,linux内核选择使用异常级别1的栈指针寄存器SP_EL1
- 对于内核模式(异常级别1),同步异常:el1_sync
- 对于内核模式(异常级别1),中断:el1_irq
- 对于64位应用程序,在用户模式(异常级别0),同步异常:el0_sync
- 对于64位应用程序,在用户模式(异常级别0),中断:el0_irq
- 对于32位应用程序,在用户模式(异常级别0),同步异常:el0_sync_compact
- 对于64位应用程序,在用户模式(异常级别0),中断:el0_irq_compact
3、异常处理
- 把当前处理器状态保存在寄存器SPSR_EL1(Saved Program Status Register)中;
- 把返回地址保存在寄存器ELR_EL1(Exception Link Register)中;
- 如果是系统调用,那么返回地址就是系统调用指令后面的指令(系统调用执行完后一条指令继续执行)
- 如果是除系统调用外的同步异常,那么返回地址是生成异常的指令(例如page_fault,内存映射完再一次执行内存访问指令)
- 如果是异步异常,那么返回地址是没有执行的第一条指令
- 如果是同步异常,或者系统错误,把生成异常的原因保存在ESR_EL1(Exception Syndrome Register)
- 如果是同步异常,把错误地址保存在寄存器FAR_EL1(Fault Address Register)
- 根据向量基准地址寄存器VBAR_EL1、异常类型、生成异常的异常级别,计算中异常向量的虚拟地址,执行异常向量。
/*
* 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
b.eq el0_fpsimd_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
.macro kernel_entry, el, regsize = 64
sub sp, sp, #S_FRAME_SIZE
.if \regsize == 32
mov w0, w0 // zero upper 32 bits of x0
.endif
//寄存器存入sp
stp x0, x1, [sp, #16 * 0]
stp x2, x3, [sp, #16 * 1]
stp x4, x5, [sp, #16 * 2]
stp x6, x7, [sp, #16 * 3]
stp x8, x9, [sp, #16 * 4]
stp x10, x11, [sp, #16 * 5]
stp x12, x13, [sp, #16 * 6]
stp x14, x15, [sp, #16 * 7]
stp x16, x17, [sp, #16 * 8]
stp x18, x19, [sp, #16 * 9]
stp x20, x21, [sp, #16 * 10]
stp x22, x23, [sp, #16 * 11]
stp x24, x25, [sp, #16 * 12]
stp x26, x27, [sp, #16 * 13]
stp x28, x29, [sp, #16 * 14]
......
.endm
el0_da:
/*
* Data abort handling
*/
mrs x26, far_el1
// enable interrupts before calling the main handler
enable_dbg_and_irq
ct_user_exit
//x0保存数据的虚拟地址,far_el1
clear_address_tag x0, x26
//x1保存生成异常的原因寄存器esr_el1的值
mov x1, x25
//x2保存内核栈里面pt_regs的起始地址,保存通用寄存器的值
mov x2, sp
//进行页错误处理
bl do_mem_abort
//返回用户模式,恢复当前进程的寄存器,重新执行页错误异常的指令
b ret_to_user
/*
* Dispatch a data abort to the relevant handler.
*/
asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr,
struct pt_regs *regs)
{
const struct fault_info *inf = esr_to_fault_info(esr);
struct siginfo info;
if (!inf->fn(addr, esr, regs))
return;
pr_alert("Unhandled fault: %s (0x%08x) at 0x%016lx\n",
inf->name, esr, addr);
info.si_signo = inf->sig;
info.si_errno = 0;
info.si_code = inf->code;
info.si_addr = (void __user *)addr;
arm64_notify_die("", regs, &info, esr);
}
/*
* EL1 mode handlers.
*/
.align 6
el1_sync:
kernel_entry 1
mrs x1, esr_el1 // read the syndrome register
lsr x24, x1, #ESR_ELx_EC_SHIFT // exception class
cmp x24, #ESR_ELx_EC_DABT_CUR // data abort in EL1
b.eq el1_da
cmp x24, #ESR_ELx_EC_IABT_CUR // instruction abort in EL1
b.eq el1_ia
cmp x24, #ESR_ELx_EC_SYS64 // configurable trap
b.eq el1_undef
cmp x24, #ESR_ELx_EC_SP_ALIGN // stack alignment exception
b.eq el1_sp_pc
cmp x24, #ESR_ELx_EC_PC_ALIGN // pc alignment exception
b.eq el1_sp_pc
cmp x24, #ESR_ELx_EC_UNKNOWN // unknown exception in EL1
b.eq el1_undef
cmp x24, #ESR_ELx_EC_BREAKPT_CUR // debug exception in EL1
b.ge el1_dbg
b el1_inv
el1_ia:
/*
* Fall through to the Data abort case
*/
el1_da:
/*
* Data abort handling
*/
mrs x3, far_el1
enable_dbg
// re-enable interrupts if they were enabled in the aborted context
tbnz x23, #7, 1f // PSR_I_BIT
enable_irq
1:
clear_address_tag x0, x3
mov x2, sp // struct pt_regs
bl do_mem_abort
// disable interrupts before pulling preserved data off the stack
disable_irq
kernel_exit 1