第十八章、调试
18.1 准备开始
如果bug能重现的话,将会有很大的帮助。
18.2 内核中的bug
Bug多种多样,产生的原因可以有无数的原因,表象也变化多端。
从隐藏在源代码中的错误到展现在目击者面前的bug,往往是经历一系列连锁反应的事件才可能触发的。
18.3 通过打印来调试
内核提供的打印函数是printk()
(1)健壮性:
健壮性是printk()函数最容易让人接受的一个特质。任何时候,任何地方都能调用它,所以很有用。
漏洞:在系统启动过程中,终端还没有初始化之前,在某些地方不能使用它。
解决方法:early_printk()
(2)日志等级:
Printk()可以指定一个日志级别而printf()不能。内核可以通过这个日志级别来判断是否在终端上打印消息。内核把级别比某个特定值低的所有消息显示在终端上。
(3)记录缓冲区:
内核消息都被保存在一个LOG_BUF_LEN大小的环形队列中。这个记录缓冲区之所以称为环形,是因为它的读写都是按照环形队列方式进行操作的。
(4)Syslogd和klogd
在标准的Linux系统上,用户空间的守护进程klogd从记录缓冲区中获取内核消息,再通过syslogd守护进程将它们保存在系统日志文件中。
(5)从printf()到printk()的转换
二者不要搞混了
18.4 oops:
Oops是内核告知用户有不幸发生的最常用方式。由于内核是整个系统的管理者,所以它不能采取像在用户空间出现运行错误时使用的那些简单手段,
因为它很难自行修复,它也不能将自己杀死。
内核只能发布oops。这个过程包括向终端上输出错误消息,输出寄存器中保存的信息并输出可供跟踪的回溯信息。
Oops的产生原因可能有很多,其中包括内存访问越界或者非法的指令等。
Oops中包含的重要信息对于所有体系结构都是完全相同的:寄存器上下文和回溯线索。
(1)ksymoops:
回溯线索中的地址需要转化成有意义的符号名称才方便使用。这需要调用ksymoops命令,并且还必须提供编译内核时产生的System.map。如果
使用的是模块,还需要一些模块信息。Ksymoops通常会自行解析这些信息,所以一般可以这样调用它:
Ksymoops saved_oops.txt
然后该程序就会吐出解码板的oops。
18.5 kallsyms:
可以通过定义CONFIG_KALLSYMS配置选项启用。
编译时为了调试和测试内核代码。
18.6 引发bug并打印信息:
BUG()和BUG_ON()
If(bad thing)
BUG();
或:
BUG_ON(bad_thing);
18.7 神奇系统请求键
该功能通过定义CONFIG_MAGIC_SYSRQ配置选项来启动。
通过sysctl用来标记该特性的开或关。
18.8 内核调试器的传奇
(1)gdb:
Gdb vmlinux /proc/kcroe
如果编译内核的时候使用了-g参数,gdb还可以提供更多的信息。
(2)kgdb:
Kgdb是一个补丁,它可以让我们在远端主机上通过串口利用gdb的所有功能对内核进行调试。这需要两台计算机:第一台运行带有kgdb
补丁的内核,第二台通过串行线使用gdb对第一台进行调试。
18.9 探测系统
(1)用UID作为选择条件
(2)使用条件变量
(3)使用统计量
(4)重复频率限制
18.10 用二分查找法找出引发罪恶的变更
18.11 使用Git进行二分搜索
18.12 社区