arm平台linux异常处理流程

时间:2023-02-04 12:20:09

异常处理

异常向量的位置

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
// 这个值是一个固定的值
.equstubs_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(swiSYS_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_stubdabt, 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模式下的异常处理
.align5
__dabt_svc:
svc_entry
movr2, 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的实现:
.macrodabt_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
ldrip, .LCprocfns
movlr, pc
ldrpc, [ip, #PROCESSOR_DABT_FUNC]
#else
blCPU_DABORT_HANDLER // 走这个分支,v7_early_abort,./arch/arm/mm/abort-ev7.S
#endif
.endm
abort-ev7.S中实现了v7_early_abort:
.align5
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 会根据不同的类型和状态做不同的处理
mrcp15, 0, r1, c5, c0, 0@ get FSR
mrcp15, 0, r0, c6, c0, 0@ get FAR
......
bdo_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是这样来的:

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

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

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

.align5
__dabt_svc:
svc_entry
movr2, 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异常处理流程类似。一旦找到源码后,就很容易看懂了。