18.1 准备开始
需要:
1.一个确定的bug。但是,大部分bug通常都不是行为可靠定义明确的。
2.一个藏匿bug的内核版本。
18.2 内核中的bug
bug发作时的症状:
- 明白无误的错误代码(没有把正确的值存放在恰当的位置);
- 同步时发生的错误(共享变量锁定不当);
- 错误地管理硬件(给错误的控制寄存器发送错误的指令)。
- … …
18.3 通过打印来调试
18.3.1健壮性
Printk()函数最容易让人们接受的一个特质。任何时候任何地方都能调用它。
- 可以在中断上下文和进程上下中被调用;
- 可以在任何持有锁时被调用;
- 可以在多处理器上同时被调用,而且调用不需要使用锁。
Early_printk()
在启动过程的初期就具有在终端上打印的能力。
18.3.2日志等级
printk()和printf()区别:
printk()可以指定一个日志级别,内核根据这个级别来判断是否在终端上打印消息。内核把级别比某个特定值低的所有消息显示在终端上。
内核将最重要的记录等级KERN_EMERG定位"<0>",将无关紧要的记录等级KERN_DEBUG定位"<7>"。
18.3.3 记录缓冲区
内核消息都被保存在一个LOG_BUF_LEN大小的环形队列中。
大小可以在编译时通过设置CONFIG_LOG_BUF_SHIFT进行调整。在单处理器的系统上其默认值是16KB。(内核在同一时间只能保存16KB的内核消息)
环形缓冲区的唯一缺点:可能会丢失消息。
18.3.4 syslogd和klogd
用户空间的守护进程klogd从记录缓冲区中获得内核消息,再通过syslogd守护进程将它们保存在系统日志文件中。
默认情况下,klogd选择读取/proc方式实现系统调用。
在启动klogd时,可以通过指定-c标志来改变终端的记录等级。
18.4 oops
内核告知用户有不幸发生的最常用方式。
oops在idle进程或init进程时发生,系统陷入混乱;
在其他进程运行时发生,内核会杀死该进程并尝试着继续执行。
oops中包含的重要信息对于所有体系结构都是完全相同的:寄存器上下文和回溯线索。
18.4.1 ksymoops
调用ksymoops:
Ksmoops saved_oops.txt
18.4.2 kallsyms
通过定义CONFIG_KALLSYMS配置选项启用。
配置选项CONFIG_KALLSYMS_ALL表示:不仅存放函数名称,还存放所有的符号名称。
18.5 内核调试配置选项
在内和配置编辑器的内核开发菜单项中,依赖于CONFIG_DEBUG_KERNEL。
一些可以利用的选项
18.6 引发bug并打印信息
BUG()和BUG_ON()被调用时,会引发oops,导致栈的回溯和错误信息的打印。
断言:
18.7 系统请求键
通过定义CONFIG_MAGIC_SYSRQ配置选项来启用。
当该功能被启用,无论内核出于什么状态,都可以通过特殊的组合键和内核进行通信。
sysctl标记特性的开或关:
18.8 内核调试器的传奇
18.8.1 gdb
使用标准的GNU调试器对正在运行的内核进行查看。
针对内核启动调试器的方法与针对进程的方法大致相同:
gdb vmlinux /proc/kcore
gdb局限性:
- 不能修改内核数据
- 不能单步执行内核代码
- 不能加断点
18.8.2 kgdb
补丁。在远端主机上通过串口利用gdb的所有功能对内核进行调试。
需要两台计算机:
运行带有kgdb补丁的内核;
通过串行线使用gdb对第一台进行调试。
18.9 探测系统
18.9.1 用UID作为选择条件
创建一个UID为7777的用户,专门来测试新算法。
18.9.2 使用条件变量
创建一个全局变量作为一个条件选择开关。
18.9.3 使用统计量
掌握某个特定事件的发生规律。比较多个事件并从中得出规律。通过创建统计量并提供某种机制访问其统计结果。
18.10 用二分法查找出引发罪恶的变更
1.一个可靠的可复制的错误,最好是系统一启动就能查证的bug。
2.一个能确保没问题的内核。
3.一个有问题的内核。应该从已知最早出现该问题的内核开始。
4.在问题内核和良好内核之间使用二分法了。