信息存储系统教育部重点实验室

祖文强同学分享核态调试经验


一般程序的错误有三种:编译错误、运行时错误和逻辑错误。其中编译错误是最容易解决的,根据编译时的提示即可解决,其次是运行时错误,最后是逻辑错误。本文主要是谈论解决运行时错误和逻辑错误的经验。

解决一个bug的步骤一般有三步:定位、分析和解决。首先是定位到程序出错时发生的位置,如定位内存出错时哪个函数中哪个指针非法引起的、定位数据错误是由哪个操作引起等;其次是在定位的基础上分析错误原因,bug发生的地方很多时候并不是引起错误的原因,在出错的位置简单修改只是治标不治本的方法,我们需要找到错误的根本原因;根本原因确定之后,就要去解决问题,解决问题时需要根据实际情况进行,不在本文谈论之列。在一处bug修改之后,需要思考下程序中是否还有类似的地方,将这些类似的地方逐一修改之。定位bug常用的方法是printk、strace、反汇编、崩溃转储等方法;分析bug原因并进行求证常用的方法是printk、分析函数调用栈等。

printk是内核中最好用,最易用,最常用的调试方法,其使用方式类似于用户态下的printf函数。printk打印的信息可用dmesg命令查看,其输出信息是保存在内核日志文件/var/log/messages中。printk在定位错误和分析原因中都十分有用。定位错误时,一般最开始是在错误发生前后的较大范围内添加多个printk,程序出错时,若前一个printk打印,而后一个未打印,则可确定错误地方在这对printk之间。然后在这对printk之间再次添加printk,直到确定代码出错在哪一行为止。在分析错误原因时要分析程序执行流程,此时可在出错函数中调用dump_stack()函数打印函数调用栈或者是根据内核崩溃时的oops信息中的函数调用栈,分析有哪些可能导致出错的原因,然后可在程序中添加printk打印相关信息以确定到底是什么原因。

strace命令可以跟踪打印用户态程序所使用的系统调用,包括系统调用的参数与返回值,还可以统计系统调用所使用的时间。如果是在调试一个文件系统时,在该文件系统上的一个用户态程序在执行时返回错误,而内核没有报运行时错误,则很可能是文件系统有逻辑错误,此时可用strace xxxx来执行xxxx程序,根据trace信息找到是哪个系统调用出错。然后借助printk来对文件系统中该系统调用实现的错误进行定位分析等。如果trace信息中某个系统调用未返回或返回错误,则可知文件系统中与该系统调用相关的实现有问题。strace的具体用法可查阅相关手册。

反汇编可以使用命令objdump,objdump –S xxxx.o是将xxxx.c在编译时生成的xxxx.o文件反汇编成AT&T格式的汇编代码,若想转换成intel格式的汇编代码,可使用命令objdump –S –M intel xxxx.o。AT&T与intel格式的汇编代码略有不同,主要表现在汇编命令中目的地址和源地址的顺序刚好相反,AT&T中寄存器名前加%等。Linux内核代码中使用的是AT&T格式的汇编代码。使用反汇编主要是用来定位错误,在内核崩溃时打印的oops信息中有出错函数及出错的汇编代码行,分析源代码及反汇编代码来确定到底是哪一行代码中哪个指针出错。

如果遇到内核崩溃重启后,在日志文件中找不到崩溃前的一部分打印信息及崩溃时的oops信息,那么此时比较有用的方法是崩溃转储。在内核崩溃时会在/var/crash目录下生成一个vmcore文件,在系统重启后可使用crash命令,如crash /usr/lib/debug/lib/modules/2.6.32-279.el6.x86_64/vmlinux /var/crash/127.0.0.1-2013-11-10-14\:10\:16/vmcore。在crash命令中可使用类似gdb的方式来查看崩溃时的信息,如bt;但最有用的信息还是可以使用dmesg查看崩溃前的打印信息及崩溃时的oops信息,oops信息是对定位系统bug非常有帮助。

除了这些我使用的调试核态代码的方法外,还有诸如kgdb或使用探针等较复杂的方法,由于我也不熟悉,此处不再赘谈。

注:本文为原创,如转载请注明出处。

分享文章

Share