ARMv8-异常处理

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 虚拟快速中断;

异常处理过程

  1. 保存PSTATE 数据到SPSR_ELx,(x = 1,2,3),在返回异常现场的时候,可以使用SPSR_ELx来恢复PE的状态
  2. 保存异常进入地址到ELR_ELx,同步异常(und/abt等)是当前地址,而异步异常(irq/fiq等)是下一条指令地址,在返回异常现场的时候,可以使用ELR_ELx来恢复PC值
  3. 保存异常原因信息到ESR_ELx;
  4. PE根据目标EL的异常向量表中定义的异常地址强制跳转到异常处理程序;
  5. 堆栈指针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组:

  1. 异常发生在当前级别且使用SP_EL0(EL0级别对应的堆栈指针),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL0级别对应的SP。 这种情况在Linux内核中未进行实质处理,直接进入bad_mode()流程。

  2. 异常发生在当前级别且使用SP_ELx(ELx级别对应的堆栈指针,x可能为1、2、3),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL1级别对应的SP。 这是比较常见的场景。

  3. 异常发生在更低级别且在异常处理时使用AArch64模式。可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch64执行模式(非AArch32模式)。 这也是比较常见的场景。

  4. 异常发生在更低级别且在异常处理时使用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()处理函数的分析;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章