轉自:https://blog.csdn.net/walkingman321/article/details/6230334
本文簡要分析了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_<exception> (parent PC) and spsr_<exception>
@ (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函數,進入死循環。