闲聊System Error(SError) in AArch64
by HappySeeker
闲话
自己的AArach64(ARMv8)环境中,由出现了SError异常,于是又做了一番了解和研究,再次记录,权当闲聊。
什么SError?
SError全称为:System Error,是ARM架构中的一种类型的异常,从字面上看,好像太宽泛,无法得知更多的信息。
AArch64(ARM64)架构中,主要包括如下4中类型的异常:
- Synchronous exception(同步异常),“同步”可以理解为:发生异常的指令为导致异常的指令,即当导致异常发生的指令执行时能立即触发异常。 包括ARM架构中定义的所有Aborts异常,如:指令异常、数据异常、对齐异常等。
- SError,System Error,是一种异步异常,后面再仔细说明。
- IRQ,普通的中断,是异步异常。
- FIQ,高优先级的中断,是异步异常。
SError本质上是一种异步外部abort(asynchronous external abort)。所谓异步,就说是发生异常时硬件(相关的寄存器)不能提供有效信息用于分析定位,异常发生时的指令,并不是导致异常的指令。外部意味着异常来自于外部存储系统(相较于CPU来说,MMU是内部的)。
通常,软件不太可能导致这样的问题,通常是硬件触发,但也不绝对。
SError产生的原因?
到了大家最关心的问题,什么原因可能导致SError。 这个名字实在太宽泛,根据Arm相关手册的说明,其可能原因有很多,比较常见的原因包括:
- asynchronous Data Aborts,异步数据异常,数据异常即CPU读写内存数据时产生的异常。 比如:如果误将ROM对应的区域的页表项设置为RW,同时该内存熟悉为write-back cacheable,当尝试写这段区域内存时,数据会先写入cache,而在后面的某个时间点,当其他操作触发相应的脏的cacheline回写内存时,此时内存系统就会返回错误(因为ROM是只读的),从而触发一个异步数据异常。 在Armv8环境中,这就是一个标准的SError。
- 外部引脚触发。 部分处理器,比如Cortex-A5x,拥有独立的引脚来触发SError,也就是说此时的SError是由SoC设计者自己定义的,比如:ECC和奇偶校验错误就常作为SError来触发。具体应该参考SoC的参考手册来确认SError可能的对应的错误类型。
内核如何处理SError?
Linux内核中,对SError进行了捕获,设置了相应的中断向量,当并未做实际的处理,只是上报异常,并终止进程会内核,因为对于内核来说,SError是致命的,内核自身无法做相应的修复操作,内核不知道具体原因,也不知道如何修复。
内核中相应的处理具体包括:
-
设置了相应的中断向量,对应的中断向量设置在entry.S汇编代码中,AArch64对应的四种状态下的SError对应中断处理函数都一样:
/\*el1代表内核态,el0代表用户态*/ ENTRY(vectors) ventry el1_sync_invalid // Synchronous EL1t ventry el1_irq_invalid // IRQ EL1t ventry el1_fiq_invalid // FIQ EL1t /*内核态System Error ,使用SP_EL0(用户态栈)*/ ventry el1_error_invalid // Error EL1t ventry el1_sync // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry el1_fiq_invalid // FIQ EL1h /*内核态System Error ,使用SP_EL1(内核态栈)*/ 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 /*用户态System Error ,使用SP_EL1(内核态栈)*/ ventry el0_error_invalid // Error 64-bit EL0 ... END(vectors)
el0_error_invalid实现:
el0_error_invalid:
inv_entry 0, BAD_ERROR
ENDPROC(el0_error_invalid)
el1_error_invalid实现:
el1_error_invalid:
inv_entry 1, BAD_ERROR
ENDPROC(el1_error_invalid)
可见,最终都调用了inv_entry,inv_entry实现如下:
/*
* Invalid mode handlers
*/
/*Invalid类异常都在这里处理,统一调用bad_mode函数*/
.macro inv_entry, el, reason, regsize = 64
kernel_entry el, \regsize
/*传入bad_mode的三个参数*/
mov x0, sp
/*reason由上一级传入*/
mov x1, #\reason
/*esr_el1是EL1(内核态)级的ESR(异常状态寄存器),用于记录异常的详细信息,具体内容解析需要参考硬件手册*/
mrs x2, esr_el1
/*调用bad_mode函数*/
b bad_mode
.endm
-
调用bad_mode,是C函数,通知用户态进程或者panic。
/*
- bad_mode handles the impossible case in the exception vector. / asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr) { siginfo_t info; /获取异常时的PC指针/ void __user *pc = (void __user *)instruction_pointer(regs); console_verbose(); /打印异常信息,messages中可以看到。/ pr_crit(“Bad mode in %s handler detected, code 0x%08x – %s\n”, handler[reason], esr, esr_get_class_string(esr)); /打印寄存器内容/ __show_regs(regs); /如果发生在用户态,需要向其发送信号,这种情况下,发送SIGILL信号,所以就不会有core文件产生了/ info.si_signo = SIGILL; info.si_errno = 0; info.si_code = ILL_ILLOPC; info.si_addr = pc; /给用户态进程发生信号,或者die然后panic*/ arm64_notify_die(“Oops - bad mode”, regs, &info, 0); }
arm64_notify_die:
void arm64_notify_die(const char *str, struct pt_regs *regs,
struct siginfo *info, int err)
{
/*如果发生异常的上下文处于用户态,则给相应的用户态进程发送信号*/
if (user_mode(regs)) {
current->thread.fault_address = 0;
current->thread.fault_code = err;
force_sig_info(info->si_signo, info, current);
} else {
/*如果是内核态,则直接die,最终会panic*/
die(str, regs, err);
}
}
Subscribe via RSS