arm平台linux异常处理流程

异常处理

异常向量的位置

arch/arm/kernel/traps.c中的early_trap_init函数将异常向量复制到某特定位置,这样当异常发生时,arm就能找到异常向量。
	/*
	 * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
	 * into the vector page, mapped at 0xffff0000, and ensure these
	 * are visible to the instruction stream.
	 */
	memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
	memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
	memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
如果看过异常向量,就会发现异常向量总是在函数地址后面加了一个偏移:“+ stubs_offset”。
	// 这里定义 stubs_offset
	// 这个值是一个固定的值
	.equ	stubs_offset, __vectors_start + 0x200 - __stubs_start
为什么要 + 0x200?因为early_trap_init函数中在copy的时候也是 + 0x200!就不能用一个宏代替0x200吗!
 

异常处理入口

异常向量以及异常处理入口在arch/arm/kernel/entry-armv.S中实现
// 这里是中断向量表
	.globl	__vectors_start
__vectors_start:
 ARM(	swi	SYS_ERROR0	)
 THUMB(	svc	#0		)
 THUMB(	nop			)
	W(b)	vector_und + stubs_offset
	W(ldr)	pc, .LCvswi + stubs_offset
	W(b)	vector_pabt + stubs_offset
	W(b)	vector_dabt + stubs_offset
	W(b)	vector_addrexcptn + stubs_offset
	W(b)	vector_irq + stubs_offset
	W(b)	vector_fiq + stubs_offset

	.globl	__vectors_end
__vectors_end:
 
以data abot为例,vector_dabt是这样实现的:
 // 这里定义vector_dabt
	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是一个宏,在本源代码文件前面定义,它展开后是一系列操作,然后根据当前模式进行查表,进而找到__dabt_svc。(当然如果是user模式下则找到__dabt_usr。)
 
__dabt_usr这样实现:
// 下面定义svc模式下的异常处理
	.align	5
__dabt_svc:
	svc_entry
	mov	r2, sp
	dabt_helper
	svc_exit r5				@ return from exception
 UNWIND(.fnend		)
ENDPROC(__dabt_svc)
这里不展开描述svc_entry和svc_exit,他们一个在entry_armv.S中定义,一个在entry_header.S中定义。顾名思义,他们用来保存现场和还原现场。
dabt_helper的实现:
	.macro	dabt_helper

	@
	@ Call the processor-specific abort handler:
	@
	@  r2 - pt_regs
	@  r4 - aborted context pc
	@  r5 - aborted context psr
	@
	@ The abort handler must return the aborted address in r0, and
	@ the fault status register in r1.  r9 must be preserved.
	@
#ifdef MULTI_DABORT
	ldr	ip, .LCprocfns
	mov	lr, pc
	ldr	pc, [ip, #PROCESSOR_DABT_FUNC]
#else
	bl	CPU_DABORT_HANDLER // 走这个分支,v7_early_abort,./arch/arm/mm/abort-ev7.S
#endif
	.endm
abort-ev7.S中实现了v7_early_abort:
	.align	5
ENTRY(v7_early_abort)
	/*
	 * The effect of data aborts on on the exclusive access monitor are
	 * UNPREDICTABLE. Do a CLREX to clear the state
	 */
	 // 清除局部处理器独占标记
	clrex

	 // 设置r0和r1,即设置do_DataAbort的参数
	 // 其实,这里就是从 cp15 获取异常的状态和类型
	 // 然后 do_DataAbort 会根据不同的类型和状态做不同的处理
	mrc	p15, 0, r1, c5, c0, 0		@ get FSR
	mrc	p15, 0, r0, c6, c0, 0		@ get FAR
	......
	b	do_DataAbort
ENDPROC(v7_early_abort)	......
	b	do_DataAbort
ENDPROC(v7_early_abort)
do_DataAbort是一个C语言实现的函数。
 

 

do_DataAbort

 

下面是一个svc模式下data abort的dump stack,我根据它分析其处理流程:

 

Kernel panic - not syncing: Fatal exception
Backtrace:
[<c00569b0>] (dump_backtrace+0x0/0x12c) from [<c088e71c>] (dump_stack+0x18/0x1c)
r6:d04d5780 r5:c120c78c r4:c004c070 r3:00000000
[<c088e704>] (dump_stack+0x0/0x1c) from [<c088eaa0>] (panic+0x80/0x1b4)
[<c088ea20>] (panic+0x0/0x1b4) from [<c0056eec>] (die+0x1d4/0x210)
r3:c120cbc0 r2:c95d1a60 r1:00000001 r0:c0af3ee8
r7:c0af3ebc
[<c0056d18>] (die+0x0/0x210) from [<c088e788>] (__do_kernel_fault.part.2+0x68/0x88)
r8:00000008 r7:c95d1c10 r6:d25b6e00 r5:00000005 r4:00000008
[<c088e720>] (__do_kernel_fault.part.2+0x0/0x88) from [<c005aa00>] (do_page_fault+0x204/0x210)
r7:d04d5780 r3:c95d1c10
[<c005a7fc>] (do_page_fault+0x0/0x210) from [<c005ab40>] (do_translation_fault+0xa8/0xb0)
[<c005aa98>] (do_translation_fault+0x0/0xb0) from [<c004c7b4>] (do_DataAbort+0x40/0xac)
r7:c95d1c10 r6:00000008 r5:c1079244 r4:00000005
[<c004c774>] (do_DataAbort+0x0/0xac) from [<c00522f4>] (__dabt_svc+0x54/0x80)

 

结合源码可知其处理流程如下:

1、  首先是空指针触发data abort异常。

2、  data abort异常处理调用的第一个C函数是do_DataAbort.

3、  do_DataAbort函数根据fsr的不同调用不同的处理函数,如果处理成功则返回了。

4、  如果处理处理结果不为0,则会看到“Unhandled fault: ...”这样的log。

5、  然后调用arm_notify_die,系统关机或重启了。
 

 

asmlinkage void __exception
do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{
		// fsr_info数组使用的是fsr-2level.c中的,见本文件518行
	const struct fsr_info *inf = fsr_info + fsr_fs(fsr); // 根据异常类型找到处理方法
	struct siginfo info;

	if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs)) // 用找到的方法进行处理
		return; // 如果处理成功,就返回了

	printk(KERN_ALERT "Unhandled fault: %s (0x%03x) at 0x%08lx\n",
		inf->name, fsr, addr);

	info.si_signo = inf->sig;
	info.si_errno = 0;
	info.si_code  = inf->code;
	info.si_addr  = (void __user *)addr;
	arm_notify_die("", regs, &info, fsr, 0);
}
 

 

do_DataAbort的三个参数:

第一个参数addr和第二个参数fsr是这样来的:

	mrc	p15, 0, r1, c5, c0, 0		@ get FSR
	mrc	p15, 0, r0, c6, c0, 0		@ get FAR
要想知道它们的具体含义,须查阅arm手册。
 

第三个参数regs是通过r2传过来的,那么下面看r2是怎么赋值的:

rch/arm/kernel/entry-armv.S中

	.align	5
__dabt_svc:
	svc_entry
	mov	r2, sp
	dabt_helper
	svc_exit r5				@ return from exception
 UNWIND(.fnend		)
ENDPROC(__dabt_svc)

 

可见r2中保存的是sp的值。注意,这个sp应该不是usr态或svc态的sp,而是dabt态的sp。别忘了svc_entry这个宏,它将必要的信息压栈了或做了其它处理。

 

do_DataAbort函数首先根据fsr在fsr_info数组中找到对应到fsr的结构体并执行其回调函数。如果回调函数返回值为0,表示处理完成,返回。如果回调函数返回值不为0,则继续后续处理。

 

arm_notify_die 函数会调用 die 函数。

 

die 函数定义在arch/arm/kernel/traps.c 中。

然后 die 函数又会调用 __die 函数。

__die 函数又会调用print_modules、__show_regs、dump_mem、dump_backtrace、dump_instr 等函数。对照这些函数的源码,相信能够从一大堆log中提取更多有用的信息。
 

其他类型异常处理

Linux kernel对其他几种异常的处理流程与data abort异常处理流程类似。一旦找到源码后,就很容易看懂了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值