Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1290564
  • 博文数量: 175
  • 博客积分: 2743
  • 博客等级: 少校
  • 技术积分: 4024
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-30 01:41
文章分类

全部博文(175)

文章存档

2015年(1)

2013年(53)

2012年(71)

2011年(50)

分类: LINUX

2013-06-16 11:35:54

必修实验3--内核异常分析(2)

为了能看到oops的全部信息,可以利用超级终端将打印信息输出到文件。通过超级终端菜单【传送(T)】|【捕获文字(C)】选择输出的文本文件。下面是插入模块时捕获到的全部oops信息。

	
  1. oops init  
  2. Function A  
  3. Function B  
  4. Function C  
  5. Function D  
  6. Unable to handle kernel NULL pointer dereference at virtual address 00000000  
  7. pgd = c39d8000 
  8. [00000000] *pgd=339cf031, *pte=00000000, *ppte=00000000 
  9. Internal error: Oops: 817 [#1]  
  10. last sysfs file: /sys/devices/platform/soc-audio/sound/card0/mixer/dev  
  11. Modules linked in: oops(+)  
  12. CPU: 0    Not tainted  (2.6.32.2-GQ2440 #18)  
  13. PC is at func_D+0x1c/0x28 [oops]  
  14. LR is at release_console_sem+0x1bc/0x214  
  15. pc : [<bf000038>]    lr : [<c004b7a4>]    psr: 60000013  
  16. sp : c39d5ee0  ip : c39d5e10  fp : c39d5eec  
  17. r10: 00000000  r9 : c05096cc  r8 : bf0000a4  
  18. r7 : c39d4000  r6 : 001dbfd8  r5 : bf000134  r4 : 000009c9  
  19. r3 : 00000000  r2 : 0000000b  r1 : 00004e5e  r0 : 0000000e  
  20. Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user  
  21. Control: c000717f  Table: 339d8000  DAC: 00000015  
  22. Process insmod (pid: 716, stack limit = 0xc39d4270)  
  23. Stack: (0xc39d5ee0 to 0xc39d6000)  
  24. 5ee0: c39d5efc c39d5ef0 bf00005c bf00002c c39d5f0c c39d5f00 bf00007c bf000054  
  25. 5f00: c39d5f1c c39d5f10 bf00009c bf000074 c39d5f2c c39d5f20 bf0000bc bf000094  
  26. 5f20: c39d5f7c c39d5f30 c003132c bf0000b4 00000000 00000000 00000000 00000000  
  27. 5f40: 00000000 00000000 bf000134 001dbfd8 00000000 000009c9 bf000134 001dbfd8  
  28. 5f60: 00000000 c0032068 c39d4000 00000000 c39d5fa4 c39d5f80 c0070f24 c0031300  
  29. 5f80: c009ba9c c009b998 00000000 bec59e68 00000000 00000080 00000000 c39d5fa8  
  30. 5fa0: c0031ec0 c0070e68 00000000 bec59e68 001dbfd8 000009c9 001927dc 00000000  
  31. 5fc0: 00000000 bec59e68 00000000 00000080 bec59e68 001927dc 00000000 00000002  
  32. 5fe0: 00000069 bec597b4 0002bff8 00009494 60000010 001dbfd8 00000000 00000000  
  33. Backtrace:   
  34. [<bf00001c>] (func_D+0x0/0x28 [oops]) from [<bf00005c>] (func_C+0x18/0x20 [oops])  
  35. [<bf000044>] (func_C+0x0/0x20 [oops]) from [<bf00007c>] (func_B+0x18/0x20 [oops])  
  36. [<bf000064>] (func_B+0x0/0x20 [oops]) from [<bf00009c>] (func_A+0x18/0x20 [oops])  
  37. [<bf000084>] (func_A+0x0/0x20 [oops]) from [<bf0000bc>] (init_module+0x18/0x24 [oops])  
  38. [<bf0000a4>] (init_module+0x0/0x24 [oops]) from [<c003132c>] (do_one_initcall+0x3c/0x1d0)  
  39. [<c00312f0>] (do_one_initcall+0x0/0x1d0) from [<c0070f24>] (sys_init_module+0xcc/0x200)  
  40. [<c0070e58>] (sys_init_module+0x0/0x200) from [<c0031ec0>] (ret_fast_syscall+0x0/0x28)  
  41.  r7:00000080 r6:00000000 r5:bec59e68 r4:00000000  
  42. Code: e59f0010 eb412fb6 e3a0200b e3a03000 (e5832000)   
  43. ---[ end trace 4e70d5baadf4ccd0 ]---  
  44. Segmentation fault  

一下子出来这么多信息可能会让人感到头痛,但仔细看一下这些信息就会发现它们仅仅是将出错前的寄存器、栈、内存的情况简单的罗列出来。分析oops信息,应该首先弄明白一个完整的oops是怎么构成的。首先,分析最开头的部分信息。

	
  1. oops init  
  2. Function A  
  3. Function B  
  4. Function C  
  5. Function D  
  6. Unable to handle kernel NULL pointer dereference at virtual address 00000000  
可以看出,前面5行是加载模块时,由模块中的printk打印出来的语句。但发现在打印"Function D"后,内核提示"Unable to handle kernel NULL pointer dereference at virtual address 00000000"。很显然,这说明在打印出"Function D"之后,模块因为访问了空指针而出错。如果这个时候进入参考代码会发现,在调用printk("Function D")之后,仅有一条语句"*p =a+5",由此可以很轻松地确定,非法的地址访问就出现在那里了。当然,在实际开发中,不会有这么简单的情况,因此,还得找出其他更有效的定位方案。

6.4 必修实验3--内核异常分析(3)


接下来的这些信息,和这个模块的调试没多大关系,它们是虚拟内存页目录、页表信息、oops错误号以及最后访问的sysfs文件等。

  1. pgd = c39d8000 
  2. [00000000] *pgd=339cf031, *pte=00000000, *ppte=00000000 
  3. Internal error: Oops: 817 [#1]  
  4. last sysfs file: /sys/devices/platform/soc-audio/sound/card0/mixer/dev  
  5. Modules linked in: oops(+)  
再接下来是寄存器信息,这部分信息比较重要,其中最可能帮助定位错误的寄存器当然是PC。在这部分信息中,下面这句最为关键。
  1. PC is at func_D+0x1c/0x28 [oops] 

它直接地告诉了我们,oops出错时,PC是位于func_D函数标号之后的0x1c处(怎么去寻找它?后面会进行分析。另外请思考后面的0x28代表什么?)。

寄存器信息之后是栈信息,但这里还用不上,先略过。

最后的部分,也就是Backtrace标号开始的地方,它是oops的精华。它表示回溯信息,也告诉调试者在oops出错之前,模块调用了那些函数。当然,在本实例中,可以看到模块调用了func_D后就出错了,显然错误就在func_D中了。

结尾部分还有一点信息请注意。

  1. Code: e59f0010 eb412fb6 e3a0200b e3a03000 (e5832000) 

Code标号开始的字段记录了模块出错前最后几条机器码,其中被括号括起来的就是oops出错对应的机器码。

(4)根据上面的分析,可以使用反汇编来确定出错的位置。在RHEL5中,使用命令:arm-linux-objdump -D -S oops.ko >log,将模块文件反汇编到log中,使用vim打开该文件log,直接找到func_D标号处,如图6-17所示。

 
(点击查看大图)图6-17  反汇编结果

根据前面的信息,出错位置应该在func_D+0x1c处,func_D在0x1c,所以出错地址应该是0x38。看看这句汇编代码,前面的语句将 寄存器r3赋值为0,然后这句又试图将寄存器r2的值存入到r3指向的地址处,也就是向0地址写。因此出错。再来看看这句出错代码对应的机器码 e5832000,显然就是之前在opps的Code字段中看到的被括起来的那个。

(5)通过反汇编程序,定位了汇编代码中的错误位置,但对于用C语言编写的内核模块而言,这样还不够。如何才能准确地定位到C语言中的语句呢?回忆 一下在<>中学习gdb调试时,能够在调试中看到对应的源码。当时为了进行gdb调试,在编译时加 入了-g选项,这样可以将调试信息加入到目标文件中。由此得到灵感,在这里也加入-g试试。进入实验代码目录2-3-1中的内核源码目录,修改其顶层 Makefile,如图6-18所示。

 
(点击查看大图)图6-18  内核调试信息开关

提示 可见编译模块时加入调试信息,需要定义CONFIG_DEBUG_INFO这个宏,由于它默认是关闭的,所以内核并没有启用-g选项,可以暂时把这个宏开关注释掉,使KBUILD_CFLAGS标识拥有-g选项。

(6)修改内核Makefile后,再次编译模块。然后将新得到的oops.ko反汇编,使用命令:arm-linux-objdump -D -S oops.ko >log。用vim打开log文件。这时,通过汇编混合C语言调试信息的结果,结合前面的分析,可以很轻松的定位到C语言的错误语句就出现在"*p = a + 5"处,如图6-19所示。

 
(点击查看大图)图6-19  定位出错的C语言语句

5.总结

通过本节实验,可以学会oops信息的分析方法,掌握利用oops信息调试内核模块的基本方法。下面列出利用oops定位出错点的基本步骤。

(1)oops出错时,首先搜集到所有oops打印信息,如果模块中本身有很多printk打印语句,首先根据oops开头的打印信息分析出错点的大概位置。

(2)通过Backtrace字段,分析发生oops错误前模块程序的执行路径,将范围缩小到某个函数中。

(3)如果通过前面两步仍无法定位出错点,那么就直接通过PC来定位。查看出错时

oops信息中打印出的PC的值并记录下来。在内核Makefile中加入-g选项,重新编译模块。

(4)通过objdump反汇编该模块。在反汇编得出的汇编混合C语言调试信息的代码中,结合前3步的分析结论,精确定位出错点的位置。
阅读(2889) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~