本文简要分析了ARM Linux的data
abort异常处理过程,内核版本2.6.28,s3c6410平台。
异常向量与程序跳转
data abort是ARM体系定义的异常之一。异常发生时,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 @
保存跳转之前的CPSR到lr寄存器
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
@ 得到跳转前所处的模式(usr、svr等)
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
对于s3c6410,v6_early_abort的定义在abort-ev6.S中,里面涉及到很多ARM的细节操作,但对我们来说,只需要了解下面这两句即可:
mrc p15, 0, r1, c5, c0, 0 @ get
FSR
mrc p15, 0, r0, c6, c0, 0 @ get
FAR
这两句用于读取协处理器CP15的C5、C6寄存器。当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
除了usr和svc模式之外,其它模式下发生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函数,进入死循环。