内核异常分析(3)
接下来的这些信息,和这个模块的调试没多大关系,它们是虚拟内存页目录、页表信息、oops错误号以及最后访问的sysfs文件等。
- 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。在这部分信息中,下面这句最为关键。
- 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语言中的语句呢?回忆一下在<<Linux应用程序开发班>>中学习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步的分析结论,精确定位出错点的位置。