ARM Linux异常处理之data abort(一)

本文简要分析了ARM Linuxdata abort异常处理过程,内核版本2.6.28s3c6410平台。

异常向量与程序跳转

data abortARM体系定义的异常之一。异常发生时,ARM会自动跳转到异常向量表中,通过向量表中的跳转命令跳转到相应的异常处理中去。

ARM的异常处理向量表在entry-armv.S文件中:

       .globl      __vectors_start

__vectors_start:

       swi   SYS_ERROR0

       b     vector_und + stubs_offset

       ldr   pc, .LCvswi + stubs_offset

       b     vector_pabt + stubs_offset

       b     vector_dabt + stubs_offset

       b     vector_addrexcptn + stubs_offset

       b     vector_irq + stubs_offset

       b     vector_fiq + stubs_offset

对于data abort,对应的跳转地址是vector_dabt + stubs_offset。这个地址的指令定义也在entry-armv.S

       vector_stub     dabt, ABT_MODE, 8

       .long       __dabt_usr                       @  0  (USR_26 / USR_32)

       .long       __dabt_invalid                     @  1  (FIQ_26 / FIQ_32)

       .long       __dabt_invalid                     @  2  (IRQ_26 / IRQ_32)

       .long       __dabt_svc                       @  3  (SVC_26 / SVC_32)

       .long       __dabt_invalid                     @  4

       .long       __dabt_invalid                     @  5

       .long       __dabt_invalid                     @  6

       .long       __dabt_invalid                     @  7

       .long       __dabt_invalid                     @  8

       .long       __dabt_invalid                     @  9

       .long       __dabt_invalid                     @  a

       .long       __dabt_invalid                     @  b

       .long       __dabt_invalid                     @  c

       .long       __dabt_invalid                     @  d

       .long       __dabt_invalid                     @  e

       .long       __dabt_invalid                     @  f

vector_stub是一个宏定义:

       .macro     vector_stub, name, mode, correction=0

       .align      5

vector_/name:

       .if /correction

       sub  lr, lr, #/correction

       .endif

 

       @

       @ Save r0, lr_ (parent PC) and spsr_

       @ (parent CPSR)

       @

       stmia       sp, {r0, lr}             @ save r0, lr

       mrs  lr, spsr                          @ 保存跳转之前的CPSRlr寄存器

       str    lr, [sp, #8]                    @ save spsr

 

       @

       @ Prepare for SVC32 mode.  IRQs remain disabled.

       @

       mrs  r0, cpsr

       eor   r0, r0, #(/mode ^ SVC_MODE)

       msr  spsr_cxsf, r0                 @ 准备进入svc模式

 

       @

       @ the branch table must immediately follow this code

       @

       and  lr, lr, #0x0f                    @ 得到跳转前所处的模式(usrsvr等)

       mov r0, sp

       ldr   lr, [pc, lr, lsl #2]            @ 根据模式跳转到相应的data abort指令,并进入svc模式

       movs       pc, lr                     @ branch to handler in SVC mode

ENDPROC(vector_/name)

       .endm

由代码中红色标注部分可看出,对于同一个异常,根据进入异常之前所处的模式,会跳转到不同的指令分支,这些指令分支紧跟在vector_stub宏定义的后面。如果进入data abort之前处于usr模式,那么跳转到__dabt_usr;如果处于svc模式,那么跳转到__dabt_svc;否则跳转到__dabt_invalid

实际上,进入异常向量前Linux只能处于usr或者svc两种模式之一。这时因为irq等异常在跳转表中都要经过vector_stub宏,而不管之前是哪种状态,这个宏都会将CPU状态改为svc模式。

usr模式即Linux中的用户态模式,svc即内核模式。

下面看一下在不同模式下进入data abort时的处理过程。

svc模式进入data abort

svc模式进入data abort,也就是Linux的内核模式进入data aboart时,会跳转到__dabt_svc

__dabt_svc:

       svc_entry               @ 保护寄存器现场

 

       mrs  r9, cpsr

       tst    r3, #PSR_I_BIT            @ 检查是否要开中断

       biceq       r9, r9, #PSR_I_BIT

       bl    CPU_DABORT_HANDLER  @ 处理异常之前的准备工作

 

       msr  cpsr_c, r9

       mov r2, sp

       bl    do_DataAbort        @ 主要操作都在这里,本文暂不研究

 

       disable_irq

 

       ldr   r0, [sp, #S_PSR]

       msr  spsr_cxsf, r0

       ldmia      sp, {r0 - pc}^                @ load r0 - pc, cpsr

ENDPROC(__dabt_svc)

CPU_DABORT_HANDLER的定义在glue.h

#define CPU_DABORT_HANDLER v6_early_abort

对于s3c6410v6_early_abort的定义在abort-ev6.S中,里面涉及到很多ARM的细节操作,但对我们来说,只需要了解下面这两句即可:

       mrc  p15, 0, r1, c5, c0, 0              @ get FSR

       mrc  p15, 0, r0, c6, c0, 0              @ get FAR

这两句用于读取协处理器CP15C5C6寄存器。当data abort异常发生时,C5寄存器中保存的值指明了是哪种原因导致的异常,具体原因可在介绍arm的资料中找到。C6寄存器中保存的是导致data abort的存储地址。

usr模式进入data abort

usr模式进入data abort,也就是Linux的用户模式进入data bort时,会跳转到__dabt_usr

__dabt_usr:

       usr_entry                                    @ 保护寄存器现场

       kuser_cmpxchg_check

 

       bl    CPU_DABORT_HANDLER  @ svc模式时处理过程一样

 

       enable_irq                                  @ 开中断

       mov r2, sp

       adr  lr, ret_from_exception            @ 重设返回地址

       b     do_DataAbort                      @ svc模式时处理过程一样

ENDPROC(__dabt_usr)

由代码可知,用户模式和内核模式的data abort处理过程类似,区别在于:

l         用户模式下data abort处理一定是开中断的;内核模式下则由具体情况决定。

l         用户模式下异常处理返回地址被设为ret_from_exception (entry-armv.S文件);内核模式下则返回到出现异常的那条语句。

下面看一下ret_from_exception

ENTRY(ret_from_exception)

       get_thread_info tsk

       mov why, #0

       b     ret_to_user

ENDPROC(__pabt_usr)

ret_to_user会判断是否需要进行进程调度,并最终返回到用户空间。用户空间data abort时可能产生进程调度的原因就在这里。

未定义状态的data abort

除了usrsvc模式之外其它模式下发生data abort都会调用__dabt_invalid函数。这里所说的其它模式在linux正常运行过程中是不应该存在的,所以如果进入__dabt_invalid函数,那就代表Linux内核应该崩溃了。

__dabt_invalid:

       inv_entry BAD_DATA

       b     common_invalid

ENDPROC(__dabt_invalid)

inv_entry宏做的主要工作是保存寄存器现场(压栈)。

common_invalid做一些必要的设置,最终调用C函数bad_mode (traps.c)

asmlinkage void bad_mode(struct pt_regs *regs, int reason)

{

       console_verbose();

 

       printk(KERN_CRIT "Bad mode in %s handler detected/n", handler[reason]);

 

       die("Oops - bad mode", regs, 0);

       local_irq_disable();

       panic("bad mode");

}

由代码可知,bad_mode主要是输出一些必要的信息,然后调用panic函数,进入死循环。

 

你可能感兴趣的:(Linux)