ARM异常处理分为同步(synchronous)和异步异常(asynchronous)
满足下面条件为同步异常:
1. 异常是由于直接执行或尝试执行指令而生成的。
2. 提供给异常处理程序的返回地址确定保存着指示引起异常的指令。
3. 异常是精确的。
(一)同步异常分类及可能产生的原因
(1) 未定义异常:UNDEFINED exceptions
产生的原因:a)在不当的exception level执行指令;b)尝试执行未定义的指令位模式;
(2)非法执行状态异常:Illegal Execution State exceptions
产生的原因:尝试执行指令的时候,而PSTATE.IL 被设置为 1,PSTATE.IL为非法执行标志位;
(3)未对齐异常:misaligned Stack Pointer/ PC
产生的原因: SP和PC在执行使用中,未对齐;
(4)系统调用异常:SVC, HVC, or SMC
产生的原因:SVC, HVC, or SMC 指令产生的异常;SVC通常被EL0(user mode)的软件用来申请 操作系统上EL1(OS service)请求特权操作或访问系统资源。HVC主要被guest OS用来请求hypervisor的服务; SMC表示:Secure monitor Call用于secure与none-secure切换;
(5)陷阱执行异常:Traps execute Exception
产生的原因:陷阱试图执行系统控制寄存器定义的指令导致被困到更高等级EL的异常。
(6)指令数据终止异常: Instruction/Data abort Exception
产生的原因:指令异常:CPU根据一个地址预取指令,发现地址取不出数据或者无法访问,就会触发预取指异常;数据异常:当程序试图读或者写一个不合法的内存地址时发生(没有权限访问或者不存在的地址)
(7)debug异常:debug exception
产生的原因:打开调试模式时候,软件断点指令/断点/观察点/向量捕获/软件单步 等Debug产生的异常;
异步异常分类
异步异常分为外部物理异常和虚拟异常;SError or vSError:系统错误类型,包括外部数据终止;IRQ or vIRQ:外部中断 or 虚拟外部中断;
FIQ or vFIQ:快速中断 or 虚拟快速中断;
异常处理过程
- 保存PSTATE 数据到SPSR_ELx,(x = 1,2,3),在返回异常现场的时候,可以使用SPSR_ELx来恢复PE的状态
- 保存异常进入地址到ELR_ELx,同步异常(und/abt等)是当前地址,而异步异常(irq/fiq等)是下一条指令地址,在返回异常现场的时候,可以使用ELR_ELx来恢复PC值
- 保存异常原因信息到ESR_ELx;
- PE根据目标EL的异常向量表中定义的异常地址强制跳转到异常处理程序;
- 堆栈指针SP的使用由目标EL决定;
用户态(EL0)不能处理异常,当异常发生在用户态时,异常级别(EL)会发生切换,默认切换到EL1(内核态),所以大部分的异常都被路由到EL1来处理;
(二)异常向量表
arch/arm64/kernel/entry.S:
199 /*
200 * Exception vectors.
201 */
202
203 .align 11
204 ENTRY(vectors)
205 ventry el1_sync_invalid // Synchronous EL1t
206 ventry el1_irq_invalid // IRQ EL1t
207 ventry el1_fiq_invalid // FIQ EL1t
208 ventry el1_error_invalid // Error EL1t
209
210 ventry el1_sync // Synchronous EL1h 常发生在内核态(EL1)并且系统配置为内核处理这些异常(这些异常导致PE迁移到EL1)时候的异常向量;
211 ventry el1_irq // IRQ EL1h
212 ventry el1_fiq_invalid // FIQ EL1h
213 ventry el1_error_invalid // Error EL1h
214
215 ventry el0_sync // Synchronous 64-bit EL0异常发生在了用户态(EL0)并且该异常需要在内核态(EL1)中处理 ;
216 ventry el0_irq // IRQ 64-bit EL0
217 ventry el0_fiq_invalid // FIQ 64-bit EL0
218 ventry el0_error_invalid // Error 64-bit EL0
219
220 #ifdef CONFIG_COMPAT
221 ventry el0_sync_compat // Synchronous 32-bit EL0
222 ventry el0_irq_compat // IRQ 32-bit EL0
223 ventry el0_fiq_invalid_compat // FIQ 32-bit EL0
224 ventry el0_error_invalid_compat // Error 32-bit EL0
225 #else
226 ventry el0_sync_invalid // Synchronous 32-bit EL0
227 ventry el0_irq_invalid // IRQ 32-bit EL0
228 ventry el0_fiq_invalid // FIQ 32-bit EL0
229 ventry el0_error_invalid // Error 32-bit EL0
230 #endif
231 END(vectors)
align 11:EL1的异常向量表保存在VBAR_EL1寄存器中(Vector Base Address Register (EL1)),该寄存器的低11bit是reserve的,11~63表示了Vector Base Address,因此这里的异常向量表是2K对齐的。
各个exception level的Vector Base Address Register (VBAR)寄存器,该寄存器保存了各个exception level的异常向量表的基地址。该寄存器有三个,分别是VBAR_EL1,VBAR_EL2,VBAR_EL3。
具体的exception handler是通过vector base address + offset得到
根据上面的异常向量表可以分为4组:
1. SError
2. FIQ
3. IRQ
4. Synchronous
4个组的分类根据发生异常时是否发生异常级别切换、和使用的堆栈指针来区别。分别对应于如下4组:
异常发生在当前级别且使用SP_EL0(EL0级别对应的堆栈指针),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL0级别对应的SP。 这种情况在Linux内核中未进行实质处理,直接进入bad_mode()流程。
异常发生在当前级别且使用SP_ELx(ELx级别对应的堆栈指针,x可能为1、2、3),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL1级别对应的SP。 这是比较常见的场景。
异常发生在更低级别且在异常处理时使用AArch64模式。可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch64执行模式(非AArch32模式)。 这也是比较常见的场景。
异常发生在更低级别且在异常处理时使用AArch32模式。可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch32执行模式(非AArch64模式)。 这种场景基本未做处理。
比如el1_error_invalid:异常发生在EL1内核态,EL1t使用SP_EL0(用户态栈),EL1h使用SP_EL1(内核态栈);而el0_error_invalid:异常发生在用户态System Error ,使用SP_EL1(内核态栈);
(三)invalid类异常处理函数接口
带invalid后缀的向量都是Linux做未做进一步处理的向量,默认都会进入bad_mode()流程,说明这类异常Linux内核无法处理,只能上报给用户进程(用户态,sigkill或sigbus信号)或die(内核态)
带invalid后缀的向量最终都调用了inv_entry,inv_entry实现如下:
233 /*
234 * Invalid mode handlers
235 */
236 .macro inv_entry, el, reason, regsize = 64
//用.MACRO伪指令定义一个宏,可以把需要重复执行的一段代码或者是一组指令缩写成一个宏;
237 kernel_entry el, \regsize //(a)
238 mov x0, sp
239 mov x1, #\reason
240 mrs x2, esr_el1
//保存三个参数到x0,x1,x2;
241 b bad_mode //(b)
242 .endm
244 el0_sync_invalid:
245 inv_entry 0, BAD_SYNC
246 ENDPROC(el0_sync_invalid)
(a)异常进入压栈准备 kernel_entry
59 /*
60 * Bad Abort numbers
61 *-----------------
62 */
63 #define BAD_SYNC 0
64 #define BAD_IRQ 1
65 #define BAD_FIQ 2
66 #define BAD_ERROR 3
67
68 .macro kernel_entry, el, regsize = 64
69 sub sp, sp, #S_FRAME_SIZE - S_LR // room for LR, SP, SPSR, ELR //SP指针满递减;
70 .if \regsize == 32
71 mov w0, w0 // zero upper 32 bits of x0
72 .endif
73 push x28, x29
74 push x26, x27
75 push x24, x25
76 push x22, x23
77 push x20, x21
78 push x18, x19
79 push x16, x17
80 push x14, x15
81 push x12, x13
82 push x10, x11
83 push x8, x9
84 push x6, x7
85 push x4, x5
86 push x2, x3
87 push x0, x1 //pair 寄存器存放
88 .if \el == 0
89 mrs x21, sp_el0 //根据EL,取出相应的栈指针;
90 get_thread_info tsk // Ensure MDSCR_EL1.SS is clear,
91 ldr x19, [tsk, #TI_FLAGS] // since we can unmask debug
92 disable_step_tsk x19, x20 // exceptions when scheduling.
93 .else
94 add x21, sp, #S_FRAME_SIZE
//如果异常级不是el0,把sp指针指向的地方加上pt_regs大小后的地址放入x21,//即指向没进入kernel_entry函数钱的sp指向的位置;
95 .endif
96 mrs x22, elr_el1 //把el1的elr寄存器内容给x22;
97 mrs x23, spsr_el1 //把el1的spsr寄存器内容给x23;
98 stp lr, x21, [sp, #S_LR]
99 stp x22, x23, [sp, #S_PC]
100 //把sp_el0,elr,lr,spsr这些内容都压入栈,用于异常返回;
101 /*
102 * Set syscallno to -1 by default (overridden later if real syscall).
103 */
104 .if \el == 0
105 mvn x21, xzr
106 str x21, [sp, #S_SYSCALLNO]
107 .endif
108
109 /*
110 * Registers that may be useful after this macro is invoked:
111 *
112 * x21 - aborted SP
113 * x22 - aborted PC
114 * x23 - aborted PSTATE
115 */
116 .endm
S_FRAME_SIZE表示sizeof(structpt_regs);S_LR表示offsetof(structpt_regs, regs[30]即31号寄存器在结构体pt_regs中的偏移量;两者相减我们就知道SP,PC,PSTATE的所占字节的大小了;
107 struct pt_regs {
108 union {
109 struct user_pt_regs user_regs;
110 struct {
111 u64 regs[31];
112 u64 sp;
113 u64 pc;
114 u64 pstate;
115 };
116 };
117 u64 orig_x0;
118 u64 syscallno;
119 };
arrch64当中也不存在pop和push命令,而是通过宏来定义stp和ldp来实现,所以push x0, x1进行pair 存放到栈中;
33 .macro push, xreg1, xreg2
34 stp \xreg1, \xreg2, [sp, #-16]!
35 .endm
36
37 .macro pop, xreg1, xreg2
38 ldp \xreg1, \xreg2, [sp], #16
39 .endm
(b) arch/arm/kernel/traps.c : bad_mode()
494 /*
495 * bad_mode handles the impossible case in the vectors. If you see one of
496 * these, then it's extremely serious, and could mean you have buggy hardware.
497 * It never returns, and never tries to sync. We hope that we can at least
498 * dump out some state information...
499 */
500 asmlinkage void bad_mode(struct pt_regs *regs, int reason)
501 {
502 console_verbose(); //设置console log level等级为最高;
503
504 printk(KERN_CRIT "Bad mode in %s handler detected\n", handler[reason]);
505
506 die("Oops - bad mode", regs, 0);//通知内核die
507 local_irq_disable();//disable中断
508 panic("bad mode");
509 }
(四)其他类异常处理函数接口
其他类的处理函数入口,还有:
210 ventry el1_sync // Synchronous EL1h
211 ventry el1_irq // IRQ EL1h
215 ventry el0_sync // Synchronous 64-bit EL0
216 ventry el0_irq // IRQ 64-bit EL0
以el1_sync为例:
286 /*
287 * EL1 mode handlers.
288 */
289 .align 6
290 el1_sync:
291 kernel_entry 1 //把寄存器信息压栈
292 mrs x1, esr_el1 // read the syndrome register 读异常类型寄存器
293 lsr x24, x1, #ESR_EL1_EC_SHIFT // exception class
294 cmp x24, #ESR_EL1_EC_DABT_EL1 // data abort in EL1 如果是el1的数据中止(data_abort)异常,跳转到el1_da标号处
295 b.eq el1_da
296 cmp x24, #ESR_EL1_EC_SYS64 // configurable trap
297 b.eq el1_undef
298 cmp x24, #ESR_EL1_EC_SP_ALIGN // stack alignment exception
299 b.eq el1_sp_pc
300 cmp x24, #ESR_EL1_EC_PC_ALIGN // pc alignment exception
301 b.eq el1_sp_pc
302 cmp x24, #ESR_EL1_EC_UNKNOWN // unknown exception in EL1
303 b.eq el1_undef
304 cmp x24, #ESR_EL1_EC_BREAKPT_EL1 // debug exception in EL1
305 b.ge el1_dbg
306 b el1_inv
307 el1_da:
308 /*
309 * Data abort handling
310 */
311 mrs x0, far_el1
312 enable_dbg
313 // re-enable interrupts if they were enabled in the aborted context
314 tbnz x23, #7, 1f // PSR_I_BIT 测试位比较非 0 跳转
315 enable_irq
316 1:
317 mov x2, sp //structpt_regs,sp中存储的是执行完kernel_entry后的值,其指向压栈后的栈顶,作为参数传给函数do_mem_abort()
318 bl do_mem_abort
319 //传给该函数的x0发生异常的地址信息,x1是异常类型,x2就是压入栈中的寄存器堆首地址。
320 // disable interrupts before pulling preserved data off the stack
321 disable_irq
322 kernel_exit 1
发生在EL1的同步异常,然后还会esr_el1读取出来的值,判断具体是哪一类的同步异常类型;以data bort异常的el1_da处理函数接口,do_mem_abort():
468 /*
469 * Dispatch a data abort to the relevant handler.
470 */
471 asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr,
472 struct pt_regs *regs)
473 {
474 const struct fault_info *inf = fault_info + (esr & 63);//取esr所有有效位,用于选择fault_info数组中的相应处理函数,该数组定义在后面;
475 struct siginfo info;
476
477 if (!inf->fn(addr, esr, regs))//如果处理成功(返回0),则直接返回,否则继续执行。
478 return;
479 //异常处理不成功,打印出错信息,进一步处理,不做分析。这里假设异常处理正常返回。
480 pr_alert("Unhandled fault: %s (0x%08x) at 0x%016lx\n",
481 inf->name, esr, addr);
482
483 info.si_signo = inf->sig;
484 info.si_errno = 0;
485 info.si_code = inf->code;
486 info.si_addr = (void __user *)addr;
487 arm64_notify_die("", regs, &info, esr); //通知内核die;
488 }
arch/arm64/mm/fault.c:
390 static struct fault_info {
391 int (*fn)(unsigned long addr, unsigned int esr, struct pt_regs *regs);
392 int sig;
393 int code;
394 const char *name;
395 } fault_info[] = {
396 { do_bad, SIGBUS, 0, "ttbr address size fault" },
397 { do_bad, SIGBUS, 0, "level 1 address size fault" },
398 { do_bad, SIGBUS, 0, "level 2 address size fault" },
399 { do_bad, SIGBUS, 0, "level 3 address size fault" },
400 { do_translation_fault, SIGSEGV, SEGV_MAPERR, "input address range fault" },
401 { do_translation_fault, SIGSEGV, SEGV_MAPERR, "level 1 translation fault" },
402 { do_translation_fault, SIGSEGV, SEGV_MAPERR, "level 2 translation fault" },
403 { do_page_fault, SIGSEGV, SEGV_MAPERR, "level 3 translation fault" },
404 { do_bad, SIGBUS, 0, "reserved access flag fault" },
405 { do_page_fault, SIGSEGV, SEGV_ACCERR, "level 1 access flag fault" },
406 { do_page_fault, SIGSEGV, SEGV_ACCERR, "level 2 access flag fault" },
407 { do_page_fault, SIGSEGV, SEGV_ACCERR, "level 3 access flag fault" },
408 { do_bad, SIGBUS, 0, "reserved permission fault" },
409 { do_page_fault, SIGSEGV, SEGV_ACCERR, "level 1 permission fault" },
410 { do_page_fault, SIGSEGV, SEGV_ACCERR, "level 2 permission fault" },
411 { do_page_fault, SIGSEGV, SEGV_ACCERR, "level 3 permission fault" },
412 { do_bad, SIGBUS, 0, "synchronous external abort" },
413 { do_bad, SIGBUS, 0, "asynchronous external abort" },
414 { do_bad, SIGBUS, 0, "unknown 18" },
415 { do_bad, SIGBUS, 0, "unknown 19" },
416 { do_bad, SIGBUS, 0, "synchronous abort (translation table walk)" },
417 { do_bad, SIGBUS, 0, "synchronous abort (translation table walk)" },
418 { do_bad, SIGBUS, 0, "synchronous abort (translation table walk)" },
419 { do_bad, SIGBUS, 0, "synchronous abort (translation table walk)" },
420 { do_bad, SIGBUS, 0, "synchronous parity error" },
421 { do_bad, SIGBUS, 0, "asynchronous parity error" },
422 { do_bad, SIGBUS, 0, "unknown 26" },
423 { do_bad, SIGBUS, 0, "unknown 27" },
424 { do_bad, SIGBUS, 0, "synchronous parity error (translation table walk" },
......
结语
这一节主要介绍异常异常类型分同步异常和异步异常,当异常发生的时候通过异常向量表找到相应的异常entry,然后调用相应的处理函数,执行寄存器压栈操作然后调用具体处理函数,至此异常模型处理接口已经很清晰,后面还会分析通知内核die然后panic过程及do_bad()和缺页异常do_page_fault()处理函数的分析;