20135323符运锦----第十八章知识点总结

第十八章 调试

18.1 准备开始

需要的只是:

①一个bug。大部分bug 通常都不是行为可靠而且定义明确的。②一个藏匿bug 的内核版本。③相关内核代码的知识和运气。

许多时候,当可以精确地重现一个bug 的时候,就已经成功了一大半了。

18.2 内核中的bug

从隐藏在源代码中的错误到展现在目击者面前的bug,往往是经历一系列连锁反应的事件才可能触发的。内核确实有一些独特的问题需要考虑,像定时限制和竞争条件等,它们都是允许多个线程在内核中同时运行产生的结果。

18.3 通过打印来调试

内核提供的打印函数printk()和C 库提供的printf()函数功能几乎相同。

18.3.1 健壮性

健壮性是printk() 函数最容易让人们接受的一个特质..可以在中断上下文和进程上下中被调用,可以在任何持有锁时被调用,可以在多处理器上同时被调用,而且调用者连锁都不必使用。

解决的办单是提供一个printk() 的变体函数一一early_printk(),这个函数在启动过程的初期就具有在终端上打印的能力,它的功能与prink()完全相同,区别仅仅在于名字和能够更早地工作,不过,由于该函数在一些内核支持的硬件体系结构上无法实现,所以这种办法峡少可移植性。

18.3.2 日志等级

printk()和printf()在使用上最主要的区别就是前者可以指定一个日志级别, 内核根据这个级别来判断是否在终端上打印消息。内核把级别比某个特定值低的所有消息显示在终端上。

一种选择是保持终端的默认记录等级不变,给所有调试信息KERN_CRIT 或更低的等级。相反,也可以给所有调试信息KERN_DEBUG 等级,而调整终端的默认记录等级。

18.3.3 记录缓冲区

内核消息都被保存在一个LOG_BUF_LEN 大小的环形队列中。该缓冲区大小可以在编译时通过设置CONFIG_LOG_BUF_SHIFf 进行调整。在单处理器的系统上其默认值是16。如果消息队列已经达到最大值,那么如果再有printk()调用,新消息将覆盖队列中的老消息。这个记录缓冲区之所以称为环形,是因为它的读写都是按照环形队列方式进行操作的。

环形缓冲区的唯一缺点一一可能会丢失消息

18.3.4 syslogd 和klogd

syslogd 守护进程把它接收到的所有消息添加进一个文件中,该文件默认是/var/log/messages.也可以通过/etc/syslog.conf 配置文件重新指定。在启动klogd 的时候,可以通过指定-c 标志来改变终端的记录级。

18.4 oops

oops 是内核告知用户有不幸发生的最常用的方式。oops 的产生有很多可能原因,其中包括内存访问越界或者非法的指令等。

回溯线索显示了导致错误发生的函数调用链。机器处于空闲状态,正在执行idle循环, 由cpu_idle()循环调用default_idle()。

18.4.1 ksymoops

如果使用的是模块, 还需要一些模块信息。ksymoops 通常会自行解析这些信息,所以一般可以这样调用它:

ksymoops saved_ oops. txt

然后该程序就会吐出解码版的oops。

18.4.2 kallsyms

它可以通过定义CONFIG_KALLS YMS 配置选项启用。该选项存放着内核镜像中相应函数地址的符号名称,所以内核可以打印解码好的跟踪线索

18.5 内核调试配置选项

内核开发( Kernel hacking)菜单项中,它们都依赖于CONFIG_DEBUG_KERNEL。。其中最有用的一个是sleep-inside-spinlock-checking (自旋锁内睡眠选项)。使用锁时睡眠是引发死锁的元凶。所以,包括正使用锁的时候调用,正使用锁的时候以阻塞方式请求分配内存和在引用单CPU 数据时睡眼在内,各种潜在的bug 都能够被探测到。

18.6 引发bug 并打印信息

最常用的两个是BUG() 和BUG_ON()。当披调用的时候,它们会引发oops ,导致栓的回溯和错误信息的打印可以把这些调用当做断言使用,想要断言某种情况不该发生:

if (bad_thing)
BUG();

或者使用更好的形式:

BUG_ON (bad_thing);

多数内核开发者相信BUG_ON()比BUG()更清晰、更可读, 而且BUG_ON()会将其声明作为一个语句放入unlikely()中。

可以用panic()引发更严重的错误。调用panic()不但会打印错误消息,而且还会挂起整个系统。显然,只应该在最糟糕的情况下使用它

if (terrible_thing)
panic ( ” terrible thing is %ld\n ”, terrible_thing);

18.7 神奇的系统请求键

该功能可以通过定义CONFIG MAGIC _SYSRQ 配置选项来启用。SysRq (系统请求)键在大多数键盘上都是标准键。

除了配置选项以外,还要通过一个sysctl 用来标记该特性的开或关。需要启用它时使用如下命令:

echo 1 > /proc/sys/kernel/sysrq

18.8 肉核调试器的传奇

18.8.1 gdb

可以使用标准的GNU 调试器对正在运行的内核进行查看。针对内核启动调试器的方站与针对进程的方栋大致相同:

gdb vml inux /proc/kcore

其中vmlinux 文件是未经压缩的内核映像,不是压缩过的zlmage 或bzlmage,它存放在源代码树的根目录上。

/proc/kcore 作为一个参数选项,是作为core 文件来用的,通过它能够访问到内核驻留的高端内存。只有超级用户才能读取此文件的数据。可以使用gdb 的所有命令来获取信息。如果编译内核的时候使用了-g 参数(在内核的Makefile 文件的CFLAGS 变量中加入-g),gdb 还可以提供更多的信息。

18.8.2 kgdb

kgdb 是一个补丁,它可以让我们在远端主机上通过串口利用gdb 的所有功能对内核进行调试。这需要两台计算机:第一台运行带有kgdb补丁的内核,第二台通过行线(不通过modem,直接连接两台机器的电缆)使用gdb对第一台进行调试。

18.9 探测系统

18.9.1 用UID 作为选择条件

if (current - >uid ! = 7777 ) {
/*老算法...*/
} else {
/*新算法...*/
}

18.9.2 使用条件变量

如果代码与进程无关,或者希望有一个针对所有情况都能使用的机制来控制某个特性,可以使用条件变量。这比使用UID 还来得简单,只需要创建一个全局变量作为一个条件选择开关。如果该变量为零,就使用一个分支上的代码。如果它不为零,就选择另外一个分支。

18.9.3 使用统计量

有些时候你需要掌握某个特定事件的发生规律。定义两个全局变量:

unsigned long foo_stat : O;
unsigned long bar_stat : O;

每当事件发生的时候,就让相应的变量加l,然后在觉得合适的地方输它。

18.9.4 量复频率限制

在内核中,有些函数每秒都要被调用很多次。有两种相关的技巧可以用来防止此类问题的发生。第一种是重复频率限制,如果某种事件发生的非常频繁,而又需要观察它的整体进展情况,就可以让这种技巧施展身手了。

static unsigned long prev _jiffy = jiffies;
if (time_after(jiffies, prev_jiffy + 2*HZ)) {
prev_ jiffy ; jiffies;
printk(KERN_ERR ” blah blah blah\n”) ;
}

另一种棘手的问题是你如何确认在特定情况下某段代码确实被执行了。

18.10 用二分查找法找出引发罪恶的变更

一开始,需要一个可靠的可复制的错误,最好是系统一启动就能查证的bug. 接下来,需要一个能确保没问题的内核。如果发现问题,说明那时就存了,那就找更早的。

18.11 使用Git 进行二分搜索

一开始,你得告诉Git 你要进行二分搜索:

$ git bisect start

然后再为Git 提供一个出现问题的最早内核版本:

$ git bisect bad <revision>

如果当前的内核版本就是引发bug的罪魁祸首,那么就不必提供内核版本:

$ git bisect bad

然后,还得为Git 提供一个最新的可正常运行的内核版本:

$ git bisect good v2.6.28

如果这个版本一切正常,可以运行下面的命令:

$ git bisect good

如果这个版本运行有异常一一也就是说,如果证明这个给定的内核版本有bug,可以运行:

$ git bisect bad

如果你已经知道引发bug 的源(比如, x86 机型的启动代码),你可以指定git仅仅在与错模相关的目录列表中去二分搜索提交的补丁。

$ git bisect start - arch/x86

18.12 当所有的努力都失败时:社区

posted @ 2016-03-27 14:34  20135323符运锦  阅读(245)  评论(0编辑  收藏  举报