介绍
第四章讲调试技术,事实上除了本章介绍的几种调试技术以外,还有利用Qemu、利用vmcore+crash的调试技术,后面会单独出博文来分别介绍。
内核调试配置项
内核提供了一些配置选项来支持内核调试,不知道这些选项在最新的内核还有用没有,用得时候最好再查一下。
选项名称 | 功能 |
---|---|
CONFIG_DEBUG_KERNEL | 内核调试总开关,要调试使用内核调试功能,该选项就必须打开 |
CONFIG_DEBUG_SLAB | 用于检测SLAB内存泄漏,在内存被申请前和释放后,内核会分别填充0xa5和0x6b。如果内核踩内存,发生oops,则可以通过这些特殊值来判断踩踏位置和原因。 |
CONFIG_DEBUG_PAGEALLOC | 当内存被释放的时候,将其完全移出内核地址空间。会降低内核性能,但也能快速定位内存错误。 |
CONFIG_DEBUG_SPINLOCK | 开启该选项,内核将捕获对未初始化的自旋锁的使用,也会捕捉重复开锁等错误。 |
CONFIG_DEBUG_SPINLOCK_SLEEP | 该选项让内核检查内核代码是否在持有锁的时候企图睡眠,哪怕是有“可能会睡眠”也报错。 |
CONFIG_INIT_DEBUG | 标记为__init(或者__initdata)的那些代码和数据应该在初始化完成后被内核丢掉,开启该选项可以让内核检查对那些本该丢掉的内容的访问。 |
CONFIG_DEBUG_INFO | 让内核代码包含完整的调试信息(调试符号表),如果内核将用gdb调试,还要打开CONFIG_DEBUG_INFO |
CONFIG_MAGIC_SYSRQ | Enables the “magic SysRq” key. We look at this key in the section “System Hangs,” later in this chapter. // TODO 讲到再说 |
CONFIG_DEBUG_STACKOVERFLOW | 和下面的配置项一起使用 |
CONFIG_DEBUG_STACK_USAGE | 上下两个选项,前一个选项打开内核栈溢出检查,这个选项打开内核栈用量统计。可以和SysRq配合使用,输出统计信息。 |
CONFIG_KALLSYMS | This option (under “General setup/Standard features”) 默认是开启的,决定oops的时候打印的trace信息是函数名称还是地址数据。 |
CONFIG_IKCONFIG | 和下面的配置项一起使用 |
CONFIG_PROC | These options (found in the “General setup” menu) 将内核配置选项导出到/proc中,方便查看。 |
CONFIG_ACPI_DEBUG | Under “Power management/ACPI.” This option turns on verbose ACPI (Advanced Configuration and Power Interface) debugging information, which can be useful if you suspect a problem related to ACPI.高级电源管理相关。 |
CONFIG_DEBUG_DRIVER | Under “Device drivers.” 该选项使能driver core中的debug信息,该信息可以用于分析底层代码。 |
CONFIG_SCSI_CONSTANTS | This option, found under “Device drivers/SCSI device support,” 详细记录SCSI错误信息,编写SCSI驱动可以启用该选项。 |
CONFIG_INPUT_EVBUG | This option (under “Device drivers/Input device support”) 原封不动的记录输入内容,包括密码,可以用来调试输入设备的驱动代码。 |
CONFIG_PROFILING | This option is found under “Profiling support.” ,用于系统性能调节,对跟踪内核挂死问题也有用。 |
打印
事实上,内核打印函数printk是很高级的,在之前的调试过程中都没有用到它的log等级的概念,在这里专门梳理一下它的功能。
log等级 | 对应事件类型 |
---|---|
KERN_EMERG | 系统要崩了用这个打印 |
KERN_ALERT | 该情况下最好立即采取行动 |
KERN_CRIT | 紧迫状态,通常涉及严重的硬件或者软件失败 |
KERN_ERR | 出现了错误,一般是驱动报告硬件错误时使用 |
KERN_WARNING | 类比于编程的warning,可以跑,但最好不这么搞得时候的打印 |
KERN_NOTICE | 正常的提醒,许多安全相关的状态用这个log等级,告知用户需要注意的东西 |
KERN_INFO | 信息,比如驱动跑到某某阶段了,打印一下某些关键的内容 |
KERN_DEBUG | Used for debugging messages. |
其实上表中的宏,从上往下对应0-7的数字,数字越小优先级越高。当printk不写log等级的时候,它用的其实是DEFAULT_MESSAGE_LOGLEVEL这个等级,这个等级其实就对应于上表中的某个log等级,具体是啥可以读代码看。注意,这些标志后不加逗号。用法是printk(KERN_DEBUG “hello,world”),printk不支持浮点打印,内核态不随便用浮点单元。
log
复杂的内核log机制
在内核用有两个内核服务守护程序,一个是klogd,负责监听来自内核的各种log数据,另一个是syslogd,负责监听来自各种进程的log数据,包括klogd的。syslog自1980就开始开发, 其定义了log协议。在syslog的基础上又衍生出了syslog-ng和rsyslog。三个项目现在是并行状态,CentOS8默认装的是rsyslog。事实上,很多发行版的klogd和syslogd都合并了,对于其中的细节不需要太深究。下图是内核各种日志打印的关系图示,dmesg打印的是/proc/kmsg的内容,cat该文件可以看到内核的实时日志输出。

控制台log
控制台也可以作为内核日志的输出口,但是它有个console_loglevel变量来控制哪些内容能输出。只有内核log等级高于(数值小于)console_loglevel的printk才会显示。该变量通过/proc/sys/kernel/printk来修改。
1 | cat /proc/sys/kernel/printk # 输出4个数字对应下面的代码 |
对STDIN_FILENO文件描述符执行TIOCLINUX 参数的ioctl以修改默认接受printk打印的终端。TIOCLINUX 仍然是复合命令,还需要指定参数选定子功能
1 | char bytes[2] = {11,0}; /* 11 is the TIOCLINUX cmd number */ |
printk辅助函数
printk不停的刷可能导致缓冲区溢出,内核提供了一个函数来控制打印速率
1 | if (printk_ratelimit() != 0) |
打印设备号常用,但写起来麻烦。内核提供下面两个函数将设备号转码到字符串buffer中
1 |
|
其他调试方法
/proc
可以将内核中的一些运行信息导出到/proc文件系统中,注意不要滥用这个导出功能,必要时考虑导出到/sysfs。该接口以单次一个Page的粒度,将内核中的数据传输到用户态。
seq_file
同/proc类似的一组接口,也是将内核数据导出到/proc中,不过对大块输出输出更友好。
ioctl
就是驱动的自定义命令,也可以用来作为响应式调试。相对于/proc导出的好处是,可以将调试接口对用户隐藏。
strace
strace可以记录用户态程序的每一个系统调用,可以打印出系统调用的入参和返回值。如果用来调试驱动程序,可以借助用户态程序。让用户态程序对驱动进行操作,观察驱动的返回值等。
SysRq魔法键
对于某些系统假死的情况(看起来卡死了,其实调度器还能调度),可以通过SysRq魔法键来调试,例如打印当前寄存器信息、保存磁盘数据等。开启该功能可能需要重编内核,然后代开sysfs中的开关。SysRq魔法键也提供其他功能,具体可看内核文档。
利用性能调优工具
如果SysRq处理假死问题行不通,还可以用性能调优工具。本书描述的调优工具已经废弃了,但是思路是不变的。可以通过调优工具来分析内核假死的时候将时间消耗在了哪个位置。
GDB调试
除了利用Qemu的GDB调试以外,还有一种内核自己用GDB调试自己的技术。自己调试自己的时候更像是只读,因为不能加断点、不能改数据、不能单步调试,但是比用Qemu调试要方便快捷。
1 | CONFIG_PROC_KCORE // 使能/proc/kcore必须开启的编译选项 |