在讨论内核调试方法之前,首先需要强调一点的是,软件的根本质量是认认真真地设计出来的,扎扎实实地写出来的,而不是靠辛辛苦苦调试出来的。
这里总体上系统地梳理一下我们实际工作中可能用到软件调试手段,并重点描述常用的调试手段;至于硬件手段(如:调试仿真器、示波器、专用子系统辅助仪器(比如USB分析仪)等)本文不会提及,请参考相关硬件调试器使用手册。
由于对Linux内核的调试因调试的对象本身要提供调试运行的环境,在这个层面上说,Linux内核的调试相对于应用程序调试要困难得多。为此,除了常规的调试手段外,许多非常规的手段都需要被考虑和利用:
首先,需要提及的手段是人工逻辑代码走查,这个手段容易在概念上被大家忽略而经常会用到(尤其是模块开发人员习惯性的第一思维“我的软件在逻辑上是否存在这方面漏洞的可能”),这里头有些通用性的东西;
其次,若人工逻辑代码走查还是发现不了问题,我们就需要程序能否自明地输出一些关于自己运行的信息,于是大名鼎鼎的printk就派上用场了,就像大家知道的一样,理想情况下你程序运行到哪了,printk就能证明你在哪里了,这看似十分完美,故内核层面的调试基本上以printk为主了;但printk是有缺陷的(尤其是先天性缺陷即输出信息只能是预期的,而错误往往并非你能预期的),它无法全面胜任;
接下来,就是非预期问题在内核层面如何想办法尽可能地展示,那就是常见的oops以及panic机制,oops是内核告知用户有不幸发生的最常用的方式,而panic则是内核检测到内部致命错误而无法安全恢复的情形的诊断信息输出;oops发生时不一定导致内核panic,但一般都会导致内核panic,视情况而定;
再者,从调试手段来,当内核发生panic后,一般系统不能再交互,而SysRq"魔术组合键"的方式(一般用在PC上,不过我们也可以借鉴此方式来帮助我们更好地调试内核),可提供命令交互的方式控制内核或打印内核信息的功能,从这点上看,比单纯的panic后系统输出信息后呆在原地不动或reboot要更进一步;
另外,内核自身还会有许多关于自身运行情况的动态信息可以查看,以帮助内核开发者分析问题的可能原因,这就是proc和sysfs;
还有,就是Linux系统提供一些通用工具也会帮助我们分析内核或系统的问题很有帮助,比如strace(它就是很好地跟踪内核界面的输入输出的交互情况,作为驱动开发者,可以站在用户的角度以黑箱的方式查看驱动运行的结果)和top(能够知道系统问题当前负载分布情况,就很能说明问题,因为很多问题都是负载过很高引起的问题,而高负载的对象往往就是问题宏观所在)等等;
我们把内核自身的主要自明的调试手段描述完外(也是现实中内核调试的主要手段),再来看借助调试器来调试内核调试方法:
首先,我们来看kdb,它通过就在运行的被调试的内核上提供检查内存和数据结构的方法来调试内核,包括单步运行一个处理器、在指定的指令执行处理暂停等等;
其次,我们来看kgdb,它是Linux内核的源代码级调试器,与gdb配合使用可以调试Linux内核。它需要两台连接的机器(一个目标机,一个主机)的模式(而kdb只需单机,这也导致kdb调试会受限),在Linux内核的kgdb配合下,内核开发者可以用类似于调试应用程序的方式通过gdb调试内核;
最后,我们还会提及UML(User Mode Linux),UML为研究linux内核代码提供一种便利的方式,整个linux系统完全是一个用户进程,你可以像调试普通用户进程一样调试它。这种调试思路还是挺诱人,不过此方法有许多局限性,比如无法调试硬件驱动。