Kernel Oops
当我们编写的Linux内核程序含有Bug时会出现下面所示的情况,屏幕输出内核错误信息,我们称之为Oops。此时系统需要重启才能继续使用。
注意,这里有三行十分重要。BUG指出了错误原因,IP寄存器的值则指出了错误位置相对于函数开始位置的偏移量。比如下面的出错位置在距离函数hydra_hook_func_out开始位置偏移量为0xb8的机器代码处。后文还会提到它们。Oops指出了错误代码,含义如下:
- bit 0 == 0 means no page found, 1 means protection fault
- bit 1 == 0 means read, 1 means write
- bit 2 == 0 means kernel, 1 means user-mode
- bit 3 == 0 means data, 1 means instruction
这里的错误代码 0000 意思是内核数据读错误。 #1 表示这个错误发生一次。
|
|
获取调试信息
使用printk函数和dmesg命令
这是最基本的用法,把怀疑出错的地方打印出来再检验。假如没有内核没有出错或者已经注释掉可能出错的部分,可以用此法。
使用一台额外的机器通过IPMI获取被调试机器的Oops信息
我们最好把Oops输出的信息保存下来,因为信息太多可能会导致整个屏幕显示不全,最上面的BUG、IP等最重要的信息就看不见了。虽然可以在不切换控制台窗口的前提下使用shift + pageup,shift + pagedown调整阅读位置,但我认为保存错误信息本来就有它的意义所在。
具体方法参考我的CSDN博文。我们机房的DELL R730的iDrac支持IPMI。做到此文的3.6节后,被调试机屏幕输出的信息都将出现在调试机屏幕上。
调试虚拟机中的内核
以VMware为例,虚拟机是被调试机,我们可以给它创建一个虚拟串口设备,并指出它的输出为宿主机的文本文件。
虚拟机内核的配置参见我的CSDN博文第3.3节,注意搞清设备名是ttyS[?]。配置完成后的原理其实和IPMI一致:虚拟机屏幕上显示的内容同时也会经过串口输出到宿主机的文本文件中。
调试内核模块
得到内核Oops信息后,我们要诊断错误原因。
调试内核模块之GDB
我们可以利用gdb对内核模块进行调试,反汇编内核模块。
第一步
进入生成模块目录,注意系统崩溃的内核模块环境需要与调试环境一致。如下调试tyz_memfs.ko。这里需要开启内核调试功能,否则gdb无法进行调试。
第二步
利用gdb命令进入调试
第三步
在gdb下,反汇编某个函数disassemble,加 /m选项可以同时显示c源码和汇编代码
相对地址后面给出了相对函数开始位置偏移量<+x>,结合Oops中IP寄存器的值,我们可以定位错误位置。
其它
另外,我们可以使用list加地址进行调试。在能直接得到错误地址的情况下很有用。
一般来说在list后面可以跟以下这们的参数:
调试内核模块之objdump
使用objdump -S 内核模块名
同样可以反汇编调试并定位错误位置,这里不展开讲解。