《linux内核设计与实现》读书笔记第十八章

Posted java_stx

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《linux内核设计与实现》读书笔记第十八章相关的知识,希望对你有一定的参考价值。

第18章 调试

18.1 准备开始

准备工作需要的是:

  • 一个bug
  • 一个藏匿bug的内核版本
  • 相关内核代码的知识和运气

18.2 内核中的bug

内核中bug的产生原因

  • 从明白无误的错误代码——没有把正确的值存放在恰当的位置
  • 同步时发生的错误——共享变量锁定不当
  • 错误地管理硬件——错误的控制寄存器发送错误的指令

危害:

  • 从降低所有程序的运行性能到毁坏数据
  • 使得系统处于死锁状态

18.3 通过打印来调试

prink()是内核的格式化打印程序。

18.3.1 健壮性

prink()函数健壮性——任何时候,任何地方都能调用它。

  • 可以在中断上下文和进程上下文中被调用
  • 可以在任何持有锁时被调用
  • 可以在多处理器上同时被调用

prink()变体函数——early_printk()。这个函数在启动过程的初期就具有在终端上打印的能力。

除非在启动过程的初期就要在终端上输出,否则可以认为prink()在什么情况下都能工作。

18.3.2 日志等级

printk()和printf()在使用上最主要的区别就是前者可以指定一个日志级别

内核根据记录等级和当前终端的记录等级console_loglevel来决定是否在终端上打印消息。

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

控制调试信息显示的方法:

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

18.3.3 记录缓冲区

内核消息保存在一个LOGBUFLEN大小的环形队列中,该缓冲区大小可以在编译时通过CONFIG_LOGBUFSHIFT进行调整。

使用环形队列的好处:

  • 由于同时读写环形缓冲区时,其同步问题很容易解决,所以即使在中断上下文中也可以方便地使用printfk()
  • 记录维护起来也更容易。

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

18.3.4 syslogd和klogd

用户空间的守护进程klogd从记录缓冲区中获取内核消息,再通过syslogd守护进程将它们保存在系统日志文件中

klogd程序:

  • 既可以从/proc/kmsg文件中,也可以通过syslog()系统调用读取这些消息;默认情况下,它选择读取/proc方式实现
  • 在被唤醒之后,它会读取出新的内核消息并进行处理,默认情况下,它就是把消息传给syslogd守护进程。

syslogd守护进程:

  • 把它接收到的所有消息添加进一个文件中,该文件默认是/var/log/messages。可以通过配置文件重新指定。

在启动klogd的时候,可以通过指定-c标志来改变终端的记录等级。

18.3.5 从printf()到printk()的转换

当你把printk()用的像printf()一样溜的时候,你才能算的上是一个内核黑客。——前路漫漫,任重而道远啊。。。

18.4 oops

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

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

oops发生的时机:

  • 中断上下文:内核无法继续,会陷入混乱,导致系统死机
  • 在idle进程或init进程(0号进程和1号进程):内核无法继续,会陷入混乱,导致系统死机
  • 在其他进程运行时,内核会杀死该进程并尝试着继续执行

oops发生的可能原因:

内存访问越界 
非法的指令

oops中包含的重要信息:寄存器上下文和回溯线索

  • 回溯线索:显示了导致错误发生的函数调用链。
  • 寄存器上下文信息也很有用——帮助你重建引发问题的现场

18.4.1 ksymoops

回溯线索中的地址需要转化成有意义的符号名称才方便使用,这需要调用ksymoops命令。并且还必须提供编译内核时产生的System.map。

调用ksymoops的方法:

ksymoops saved_opps.txt

18.4.2 kallsyms

2.5内核引入了kallsyms特性,可以通过定义CONFIG_KALLSYMS配置选项启用。 从函数的地址到符号名称的映射必须永久地驻久地驻留在内核所映射的内存地址

  • 配置选项CONFIG_KALLSYMS_ALL 表示不仅存放函数名称,还存放所有的符号名称。
  • CONFIG_KALLSYMSEXTRAPASS选项会引起内核构建过程中再次忽略内核的目标代码。——只有在调试kallsyms本身时才会用。

18.5 内核调试配置选项

在编译的时候,为了方便调试和测试内核代码,内核提供了许多配置选项。在内核配置编辑器的内核开发菜单。这些选项中,它们都依赖于CONFIGDEBUGKERNEL。

常见选项有:

  • slab layer debugging slab层调试选项
  • high-memory debugging 高端内存调试选项
  • I/O mapping debugging I/O映射调试选项
  • spin-lock debugging 自旋锁调试选项
  • stack-overflow debugging 栈溢出检查选项
  • sleep-inside-spinlock checking 自旋锁内睡眠选项

原子操作:指那些能够不分隔执行的东西;在执行时不能中断否则就是完不成的代码。

原子操作包括:

  • 正在使用一个自旋锁
  • 禁止抢占的代码

使用锁时睡眠是引发死锁的元凶。

内核提供了一个原子操作计数器。

18.6 引发bug并打印信息

1、BUG()和BUG_ ON()

被调用时会引发oops,导致栈的回溯和错误信息的打印。

2、BUILD_ BUG_ ON()

与BUG_ ON()作用相同,仅在编译时调用。

3、panic()

可以引发更严重的错误,不但会打印错误信息,还会挂起整个系统。

4、dump_ stack()

只在终端上打印寄存器上下文和函数的跟踪线索。

18.7 神奇的系统请求键

1、系统请求键可以通过定义CONFIGMAGICSYSRQ配置选项来启用。SysRq(系统请求)键在大多数键盘上都是标准键。

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

echo 1 > /proc/sys/kernel/sysrq

3、常见的命令

  • SysRq-b:重启设备
  • SysRq-o:关闭机器
  • SysRq-u:卸载所有的文件系统
  • SysRq-s:把所有已安装的文件系统都刷新到磁盘
  • SysRq-k:安全访问键,杀死这个控制台上的所有程序

18.8 内核调试器的传奇

18.8.1 gdb

1、针对内核启动调试器的方法与针对进程的方法大致相同:

gdb vmlinux /proc/kcore

  • vmlinx:未经压缩的内核映像,它存放于源代码树的根目录上。
  • /proc/kcore作为一个参数选项,是作为core文件来用的,通过它能够访问到内核驻留的高端内存。只有超级用户才能读取此文件的数据

2、可以使用gdb的所有命令来获取信息。

  • 打印一个变量的值:

p global_variable

  • 反汇编一个函数:

disassemble function

3、局限性:

  • 没有任何办法修改内核数据
  • 不能单步执行内核代码

18.8.2 kgdb

1、是一个补丁 ,可以让我们在远程主机上通过串口利用gdb的所有功能对内核进行调试。

2、通过kgdb,gdb的所有功能都能使用:

  • 读取和修改变量值
  • 设置断点
  • 设置关注变量
  • 单步执行

18.9 探测系统

18.9.1 用UID作为选择条件

利用用户id(UID)作为选择条件来实现这种功能:

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

18.9.2 使用条件变量

1、如果代码与进程无关,或者希望有一个针对所有情况都能使用的机制来控制某个特性,可以使用条件变量。

2、创建一个全局变量作为一个条件选择开关:

  • 如果该变量为0,就使用某一个分支上的代码;
  • 反之,选择另外一个分支。

18.9.3 使用统计量

当要掌握某个特定事件的发生规律的时候。

  • 定义全局变量 输出全局变量的位置:

  • 在/proc目录中创建一个文件

  • or新建一个系统调用
  • or通过调试器直接访问(最直接)

该方式不是SMP安全的,理想的方式是用原子操作实现。

18.9.4 重复频率限制

系统的调试信息过多的解决方法:

  • 重复频率限制
  • 发生次数限制

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

这部分的精华就是,使用二分法查找版本号,以确定某个漏洞最开始出现在哪个版本里。

18.11 使用Git进行二分搜索

告诉git要进行二分搜索:

git bisect start

提供已知出现问题的最早内核版本:

git bisect bad

若当前版本就是引发bug的最初版本,则使用:

git bisect bad

最新的可正常运行的内核版本:

git bisect good

之后,git就会利用二分搜索法在Linux源码树中,自动检测正常的版本内核和有bug的内核版本之间那个版本有隐患,然后再编译、运行以及测试正被检测的版本。

如果这个版本正常:

git bisect good

如果这个版本运行有异常:

git bisect bad

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

在社区上求助时,要把问题描述的完整而简洁

小结

1、调试过程其实是一种寻求实现与目标偏差的行为。

2、涉及到的几种技术:从内核内置的调试架构到调试程序,从记录日志到用git二分法查找

以上是关于《linux内核设计与实现》读书笔记第十八章的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核设计与实现 第十八章

第十八章读书笔记

《Linux内核设计与实现》读书笔记 18

linux内核设计与实现一书阅读整理 之第十八章

Linux内核分析——第十八章 调试

读书笔记