kernel panic , Oops 等cpu异常的分析与定位

一、kernel panic

二、mips异常机制

三、linuxkernel 对mips异常的处理

四、kernel panic 实例分析

Kernel  panic

内核代码,相比用户层代码更难以调试,在内核程序开发上更要加倍小心和注意:有的BUG在内核或内核模块运行时会导致系统崩溃。当崩溃发生时,收集尽可能多的信息将有助于问题的解决。这就是内核OOPS诞生的目的。

OOPS会显示出CPU在崩溃时的状态,包括CPU寄存器和其它一些看起来难懂的信息。其实现代码在内核的arch\xxx\kernel\traps.c中。

在linux中对mips体系结构代码中,主要是通过异常处理的方式,在产生异常时将当前的寄存器状态,线程状态,加载的模块及函数调用信息,打印处理,为内核debug提供尽可能多的信息。

mips异常机制

图1
图2

Exception 2/3:TLB Miss Load/Write

Miss Load/Write,如果试图访问没有在MMU的TLB中映射的内存地址,会触发这个异常。在支持虚拟内存的操作系统中,这会触发内存的页面倒换,系统的Exception Handler会将所需要的内存页从虚拟内存中调入物理内存,并更新相应的TLB表项。

Exception 4/5:Address Error Load/Write

如果试图访问一个非对齐的地址,例如lw/sw指令的地址非4字节对齐,或lh/sh的地址非2字节对齐,就会触发这个异常。一般地,操作系统在Exception Handler中对这个异常的处理,是分开两次读取/写入这个地址。虽然一般的操作系统内核都处理了这个异常,最后能够完成期待的操作,但是由于会引起用户态到内核态的切换,以及异常的退出,当这样非对齐操作较多时会严重影响程序的运行效率。因此,编译器在定义局部和全局变量时,都会自动考虑到对齐的情况,而程序员在设计数据结构时,则需要对对齐做特别的斟酌。

Exception 9:Break Point

绝对断点指令。和syscall指令类似,它也是由专用指令break触发的。它指示了系统的一些异常情况,编程人员可以在某些不应当出现的异常分支里面加入这个指令,便于及早发现问题和调试。我们可以用高级语言中的assert机制来类比理解它。最常见的Break异常的子类型为0x07,它是编译器在编译除法运算时自动加入的。如果除数为0则执行一条break 0x07指令。这样,当出现被0除的情况时,系统就会抛出一个异常,并执行Coredump,以便于程序员定位除0错误的根因。

内核中的BUG()函数使用break指指令产生断点异常

Mips 通用寄存器定义

图3

Linux kernel 对mips异常的处理

/arch/mips/kernel/trap.c

/* 异常向量初始化 */
void __init trap_init(void)
/* 设置异常向量 */
void *set_except_vector(int n, void *addr)

异常向量的处理函数:

TLBL/TLBS:

do_page_fault()
{
        printk(KERN_ALERT "CPU %d Unable to handle kernel paging request at ""virtual address %0*lx, epc ==                      %0*lx, ra == %0*lx\n",raw_smp_processor_id(), field, address, field, regs->cp0_epc,      field,  regs-                    >regs[31]);
        die("Oops", regs);

ADEL/ADES

Arch/mips/unaligned.c
do_ade(struct pt_regs *regs)
 {     …
       sigbus:
       die_if_kernel("Kernel unaligned instruction access", regs);
       force_sig(SIGBUS, current);
        …
}

BP

Arch/mips/kernel/trap.c
do_bp(struct pt_regs *regs)
 {
     do_trap_or_bp(regs, bcode, "Break");
}
do_trap_or_bp()
 {
     case BRK_BUG:
     die_if_kernel("Kernel bug detected", regs);
     force_sig(SIGTRAP, current);
      break;
}

die_if_kernel

 static inline void die_if_kernel(const char *str, const struct pt_regs *regs)
 {         
       if (unlikely(!user_mode(regs)))
                    die(str, regs);

}

die()

图4

show_register

图5

kernel panic 实例分析

epc:产生异常时的PC指针的值

ra:子程序的返回地址

cp0 cause

BadVA

我们重点看下epc的分析过程,工具链中提供了一系列的工具可以用于二进制的文件的分析,通过这些工具的使用我们可以定位到产生异常的代码:

内核需要开启CFG_KALLSYM,在产生异常时,能够打印出对应的函数及相应的偏移。函数地址加上偏移就是对应的汇编指令地址。

由于EPC和堆栈中的函数地址信息都是装载之后的地址,所以可以使用objdump -S 将相应的.o文件反汇编获取到函数在编译时的静态地址,从而确定函数地址。

addr2line 命令可以从.o文件中通过指令在代码段中的偏移来确定对应的C程序所在的行(需要在gcc编译时加上-g选项)。

实例(实际信息比这多,当前截取一部分):

图6

我们看到当前epc指针指向的地址为:xxx_state_context+0xb0/0x5624(偏移为0xb0),使用上面步骤:

1)反汇编命令:你的编译工具链路径/objdump -S 你的内核vmlinx或模块.ko >dump.txt

2)获取函数地址:在dump.txt中查找xxx_state_context,找到该函数地址,假如为00000034

3)获取pc指针地址:00000034+0xb0=0x000000e4

4)定位到具体函数某一行,命令:

你的编译工具链路径/addr2line 0x000000e4 -e 你的内核vmlinx或模块.ko -f xxx_state_context

后续

1)找到产生异常的代码仅仅只是开始,因为产生异常的原因有很多,kernel panic打印处理的系统信息能提供的仅仅是当前的一些状态,更具体的分析需要结合代码进行分析。

2)假如编译工具链中有gdb的话,可以更快捷的定位到具体代码,执行命令:

#你的编译工具链gdb路径/gdb 你的内核vmlinx或模块.ko

(gdb) b *xxx_state_context+0xb0

该指令将同样可以定位到具体文件的某一行,有兴趣的朋友可以试一下。

3)该文章提到的具体命令用法,请自行查阅文档。

你可能感兴趣的:(kernel panic , Oops 等cpu异常的分析与定位)