全部博文(175)
分类: LINUX
2013-06-16 11:35:54
必修实验3--内核异常分析(2)
为了能看到oops的全部信息,可以利用超级终端将打印信息输出到文件。通过超级终端菜单【传送(T)】|【捕获文字(C)】选择输出的文本文件。下面是插入模块时捕获到的全部oops信息。
- oops init
- Function A
- Function B
- Function C
- Function D
- Unable to handle kernel NULL pointer dereference at virtual address 00000000
- pgd = c39d8000
- [00000000] *pgd=339cf031, *pte=00000000, *ppte=00000000
- Internal error: Oops: 817 [#1]
- last sysfs file: /sys/devices/platform/soc-audio/sound/card0/mixer/dev
- Modules linked in: oops(+)
- CPU: 0 Not tainted (2.6.32.2-GQ2440 #18)
- PC is at func_D+0x1c/0x28 [oops]
- LR is at release_console_sem+0x1bc/0x214
- pc : [<bf000038>] lr : [<c004b7a4>] psr: 60000013
- sp : c39d5ee0 ip : c39d5e10 fp : c39d5eec
- r10: 00000000 r9 : c05096cc r8 : bf0000a4
- r7 : c39d4000 r6 : 001dbfd8 r5 : bf000134 r4 : 000009c9
- r3 : 00000000 r2 : 0000000b r1 : 00004e5e r0 : 0000000e
- Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user
- Control: c000717f Table: 339d8000 DAC: 00000015
- Process insmod (pid: 716, stack limit = 0xc39d4270)
- Stack: (0xc39d5ee0 to 0xc39d6000)
- 5ee0: c39d5efc c39d5ef0 bf00005c bf00002c c39d5f0c c39d5f00 bf00007c bf000054
- 5f00: c39d5f1c c39d5f10 bf00009c bf000074 c39d5f2c c39d5f20 bf0000bc bf000094
- 5f20: c39d5f7c c39d5f30 c003132c bf0000b4 00000000 00000000 00000000 00000000
- 5f40: 00000000 00000000 bf000134 001dbfd8 00000000 000009c9 bf000134 001dbfd8
- 5f60: 00000000 c0032068 c39d4000 00000000 c39d5fa4 c39d5f80 c0070f24 c0031300
- 5f80: c009ba9c c009b998 00000000 bec59e68 00000000 00000080 00000000 c39d5fa8
- 5fa0: c0031ec0 c0070e68 00000000 bec59e68 001dbfd8 000009c9 001927dc 00000000
- 5fc0: 00000000 bec59e68 00000000 00000080 bec59e68 001927dc 00000000 00000002
- 5fe0: 00000069 bec597b4 0002bff8 00009494 60000010 001dbfd8 00000000 00000000
- Backtrace:
- [<bf00001c>] (func_D+0x0/0x28 [oops]) from [<bf00005c>] (func_C+0x18/0x20 [oops])
- [<bf000044>] (func_C+0x0/0x20 [oops]) from [<bf00007c>] (func_B+0x18/0x20 [oops])
- [<bf000064>] (func_B+0x0/0x20 [oops]) from [<bf00009c>] (func_A+0x18/0x20 [oops])
- [<bf000084>] (func_A+0x0/0x20 [oops]) from [<bf0000bc>] (init_module+0x18/0x24 [oops])
- [<bf0000a4>] (init_module+0x0/0x24 [oops]) from [<c003132c>] (do_one_initcall+0x3c/0x1d0)
- [<c00312f0>] (do_one_initcall+0x0/0x1d0) from [<c0070f24>] (sys_init_module+0xcc/0x200)
- [<c0070e58>] (sys_init_module+0x0/0x200) from [<c0031ec0>] (ret_fast_syscall+0x0/0x28)
- r7:00000080 r6:00000000 r5:bec59e68 r4:00000000
- Code: e59f0010 eb412fb6 e3a0200b e3a03000 (e5832000)
- ---[ end trace 4e70d5baadf4ccd0 ]---
- Segmentation fault
一下子出来这么多信息可能会让人感到头痛,但仔细看一下这些信息就会发现它们仅仅是将出错前的寄存器、栈、内存的情况简单的罗列出来。分析oops信息,应该首先弄明白一个完整的oops是怎么构成的。首先,分析最开头的部分信息。
可以看出,前面5行是加载模块时,由模块中的printk打印出来的语句。但发现在打印"Function D"后,内核提示"Unable to handle kernel NULL pointer dereference at virtual address 00000000"。很显然,这说明在打印出"Function D"之后,模块因为访问了空指针而出错。如果这个时候进入参考代码会发现,在调用printk("Function D")之后,仅有一条语句"*p =a+5",由此可以很轻松地确定,非法的地址访问就出现在那里了。当然,在实际开发中,不会有这么简单的情况,因此,还得找出其他更有效的定位方案。
- oops init
- Function A
- Function B
- Function C
- Function D
- Unable to handle kernel NULL pointer dereference at virtual address 00000000
接下来的这些信息,和这个模块的调试没多大关系,它们是虚拟内存页目录、页表信息、oops错误号以及最后访问的sysfs文件等。
再接下来是寄存器信息,这部分信息比较重要,其中最可能帮助定位错误的寄存器当然是PC。在这部分信息中,下面这句最为关键。
- pgd = c39d8000
- [00000000] *pgd=339cf031, *pte=00000000, *ppte=00000000
- Internal error: Oops: 817 [#1]
- last sysfs file: /sys/devices/platform/soc-audio/sound/card0/mixer/dev
- Modules linked in: oops(+)
- PC is at func_D+0x1c/0x28 [oops]
它直接地告诉了我们,oops出错时,PC是位于func_D函数标号之后的0x1c处(怎么去寻找它?后面会进行分析。另外请思考后面的0x28代表什么?)。
寄存器信息之后是栈信息,但这里还用不上,先略过。
最后的部分,也就是Backtrace标号开始的地方,它是oops的精华。它表示回溯信息,也告诉调试者在oops出错之前,模块调用了那些函数。当然,在本实例中,可以看到模块调用了func_D后就出错了,显然错误就在func_D中了。
结尾部分还有一点信息请注意。
- 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语言中的语句呢?回忆
一下在<
(点击查看大图)图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步的分析结论,精确定位出错点的位置。