20135320赵瀚青LINUX第十八章读书笔记

概述:调试工作艰难是内核级开发区别于用户级开发的一个显著特点

18.1准备开始

内核调试往往是一个令人挠头不已的漫长过程。幸运的是,在这些费劲的问题中也有不少比较简单而且容易消灭的小bug,运气好你可能面对的是些简单的小bug。

开始做―些调查之前,不会清楚到底面对的是什么现在,需要的只是:
一个bug。听起来很可笑,但确实需要一个确定的bug。如果错误总是能够重现的话,那对我们会有很大的帮助(有一部分错误确实如此)。然而不幸的是,大部分bug通常都不是行为可靠而且定义明确的。
一个藏匿bug的内核版本。如果你知道这个bug最早出现在哪个内核版本中那就再理想不过了。
相关内核代码的知识和运气。调试内核其实是一个棘手的问题。不过对周围的代码理解得越多调试起来也就越轻松。

18.2 内核中的bug

内核中的bug多种多样,它们的产生可以有无数的原因,同时它们的表象也变化多端

  • 明白无误的错误代码(比如,没有把正确的值存放在恰当的位置)
  • 同步时发生的错误(比如共享变量锁定不当)
  • 错误地管理硬件(比如,给错误的控制寄存器发送错误的指令)。
  • 降低所有程序的运行性能到毁坏数据再到使得系统处于死锁状态,都可能是bug发作时的症状。

18.3通过打印来调试

printk():内核的格式化打印函数

1. 健壮性

弹性极佳的函数:任何时候、任何地方都能调用它

可以在中断上下文和进程上下文中被调用

可以在任何持有锁时被调用

可以在多处理器上同时被调用

除非再启动过程中的初期就要在终端上输出

2. 日志等级

printk()和printf()在使用上最主要的区别就是前者可以指定一个日志级别。内核通过这个级别来判断是否在终端上打印消息。

内核把级别比某个特定值低的所有消息显示在终端上。

  • 0 KERN_EMERG 最重要
  • 7 KERN_DEBUG 最不重要

调试信息, 有两种赋予记录等级的方法:

  • 保持终端的默认记录等级不变,给所有调试信息KERN_CRIT或更低的等级。
  • 给所有调试信息KERN_DEBUG等级,调整终端的默认记录等级。

3. 记录缓冲区

内核消息保存在一个LOG_BUF_LEN大小的环形队列中,读写都是按照环形队列方式操作的。

大小是可以在编译时通过CONFIG_LOG_BUF_SHIFT进行调整。

在单处理器的系统上默认值是16kb,即内核在同一时间只能保存16kb的内核消息,再多的话新消息就会覆盖老消息。

优点:

  • 同步问题易解决
  • 记录维护容易

    缺点:
  • 可能会丢失消息

18.4、oops

1. 概述

oops是内核告知用户有不幸发生的最常用的方式。

内核很难自我修复,也不能将自己杀死,只能发布oops,过程:

  • 向终端上输出错误消息
  • 输出寄存器中保存的信息
  • 输出可供跟踪的回溯线索

    通常发送完oops之后,内核会处于一种不稳定状态。

寄存器上下文信息也很有用,比如帮助冲进引发问题的现场

2. ksymoops

调用ksymoops命令:将回溯线索中的地址需要转化成有意义的符号名称

并且还必须提供编译内核时产生的System.map。如果用的是模块,还需要调用一些模块信息:kysmoop saved_oops.txt

3. kallsyms

现在的版本中不需要使用kysmoops这个工具,因为可能会发生很多问题,新版本中引入了kallsyms,可以通过定义CONFIG_KALLSYMS配置选项启用。

18.5 内核调试配置选项

在编译的时候,为了方便调试和测试内核代码,内核提供了许多配置选项。在内核配置编辑器的内核开发菜单。这些选项中,它们都依赖于CONFIG_DEBUG_KERNEL。当开发内核的时候,作为一种练习,不妨打开所有这些选项。

有些选项确实有用,这些选项确实能完成不少调试工作。

18.6 引发bug并打印信息

一些内核调用可以用来方便标记bug方便标记bug提供断言并输出信息。最常用的两个是BUG()和些声明BUG_ON()。当被调用的时候,它们会引发oops,导致栈的回溯和错误信息的打印。大部分体系结构把BUG()和BUG_ON()定义成某种会导致oops跟硬件的体系结构是相关的非法操作,这样自然会产生需要的oops。可以把这些调用当做断言使用,想要断言某种情况不该发生。

18.7 神奇的系统请求键

神奇的系统请求键是另外一根救命稻草,该功能可以通过定义CONFIG_MAGIC_SYSRQ配置选项来启用。

当该功能被启用的时候,无论内核处于什么状态,都可以通过特殊的组合键跟内核进行通信。这种功能可以让你在面对一台奄奄一息的系统时能完成一些有用的工作。除了配置选项以外,还要通过一个sysctl用来标记该特性的开或关。

需要启用它时使用如下命令:echo 1> /proc/sys/kernel/sysrq

18.8 内核调试器的传奇

很多内核开发者一直以来都希望能拥有一个用于内核的调试器.不幸的是,Linus不愿意在它的内核源代码树中加入一个调试器。他认为调试器会误导开发者,从而致引入不良的修正,没有人能对他的逻辑提出异议从真正理解代码出发,确实更能保证修正的正确性。然而,许多内核开发者们还是希望有一个官方发布的、用于内核的调试器。因为这个要求看起来不会马上被满足,所以许多补丁应运而生了,它们为标准内核附加上了内核调试的支持,虽然这都是―些不被官方认可的附加补丁,但它们确实功能完善,十分强大。在我们深入这些解决方案之前,先看看标准的调试器gdb能够给我们一些什么帮助是―个不错的选择。

18.9 探测系统

如果对内核调试有丰富的经验的话,那么你会掌握一些诀窍来帮助你更进一步地探测系统从而找到想要的答案。内核调试很有挑战性,即使是一点小的暗示或者技巧都能给你很大的帮助我们最好把它们联系起来。

18.9.1 用UID作为选择条件

假设为了加入一个激动人心的新特性,你重写了fork()系统调用。除非第一次的尝试就完美无缺,否则系统调试就是―场噩梦。如fork()系统调用不正常的话,压根就不用指望整个系统还能正常工作。当然,和任何时候一样,希望总是存在的,一般情况下,只要保留原有的算法而把你的新算法加入到其他位置上,基本就能保证安全:可以利用把用户id作为选择条件来实现这种功能,通过这种选择条件,可以安排到底执行哪种算法。

18.9.2 使用条件变量

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

18.9.3 使用统计量

有些时候你需要掌握某个特定事件的发生规律。有些时候需要比较多个事件并从中得出规律。通过创建统计量并提供某种机制访问其统计结果,很容易就能满足这种需求。举个例子,假设我们希望得到foo和bar的发生频率,那么在某个文件中,当然最好是在定义该事件的那个文件里定义两个全局变量。

18.9.4 重复频率限制

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

18.11 使用Git进行二分搜索

Git源码管理工具提供了一个有用的二分搜索机制。如果你使用Git来控制Linux源码树的副本,那么Git将自动运行二分搜索进程。此外,Git会在修订版本中进行二分搜索,这样可以找到具体哪次提交的代码引发了bug。很多Git相关的任务比较繁杂,但使用Git进行二分搜索并不那么的困难。
上一篇:20135337朱荟潼 Linux第五周学习总结——扒开系统调用的三层皮(下)


下一篇:Bzoj 2038---[2009国家集训队]小Z的袜子(hose) 莫队算法