arm64异常处理

985 阅读5分钟

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、异常处理

  1. 把当前处理器状态保存在寄存器SPSR_EL1(Saved Program Status Register)中;
  2. 把返回地址保存在寄存器ELR_EL1(Exception Link Register)中;
  • 如果是系统调用,那么返回地址就是系统调用指令后面的指令(系统调用执行完后一条指令继续执行)
  • 如果是除系统调用外的同步异常,那么返回地址是生成异常的指令(例如page_fault,内存映射完再一次执行内存访问指令)
  • 如果是异步异常,那么返回地址是没有执行的第一条指令
  1. 如果是同步异常,或者系统错误,把生成异常的原因保存在ESR_EL1(Exception Syndrome Register)
  2. 如果是同步异常,把错误地址保存在寄存器FAR_EL1(Fault Address Register)
  3. 根据向量基准地址寄存器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