第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内核设计与实现》读书笔记第十八章的更多相关文章

  1. Linux内核设计与实现 读书笔记 转

    Linux内核设计与实现  读书笔记: http://www.cnblogs.com/wang_yb/tag/linux-kernel/ <深入理解LINUX内存管理> http://bl ...

  2. 《Linux内核设计与实现》课本第十八章自学笔记——20135203齐岳

    <Linux内核设计与实现>课本第十八章自学笔记 By20135203齐岳 通过打印来调试 printk()是内核提供的格式化打印函数,除了和C库提供的printf()函数功能相同外还有一 ...

  3. Linux内核设计与实现 读书笔记

    第三章 进程管理 1. fork系统调用从内核返回两次: 一次返回到子进程,一次返回到父进程 2. task_struct结构是用slab分配器分配的,2.6以前的是放在内核栈的栈底的:所有进程的ta ...

  4. Linux内核设计与实现读书笔记(8)-内核同步方法【转】

    转自:http://blog.chinaunix.net/uid-10469829-id-2953001.html 1.原子操作可以保证指令以原子的方式执行——执行过程不被打断.内核提供了两组原子操作 ...

  5. Linux内核设计与实现——读书笔记2:进程管理

    1.进程: (1)处于执行期的程序,但不止是代码,还包括各种程序运行时所需的资源,实际上进程是正在执行的 程序的实时结果. (2)程序的本身并不是进程,进程是处于执行期的程序及其相关资源的总称. (3 ...

  6. Linux内核设计与实现——读书笔记1:内核简介

    内核:有的时候被称管理者或者操作系统核心,通常内核负责响应中断的中断服务程序, 负责管理多个进程从而分享处理器时间的调度程序,负责管理进程地址空间德内存管理程序 和网络,进程间通信等系统服务程序共同组 ...

  7. Linux内核设计与实现 总结笔记(第八章)下半部和推后执行的工作

    上半部分的中断处理有一些局限,包括: 中断处理程序以异步方式执行,并且它有可能打断其他重要代码的执行. 中断会屏蔽其他程序,所以中断处理程序执行的越快越好. 由于中断处理程序往往需要对硬件进行操作,所 ...

  8. 初探内核之《Linux内核设计与实现》笔记上

    内核简介  本篇简单介绍内核相关的基本概念. 主要内容: 单内核和微内核 内核版本号 1. 单内核和微内核   原理 优势 劣势 单内核 整个内核都在一个大内核地址空间上运行. 1. 简单.2. 高效 ...

  9. 《Linux内核设计与实现》读书笔记 第十八章 调试

    第十八章调试 18.1 准备开始          需要准备的东西: l  一个bug:大部分bug通常都不是行为可靠而且定义明确的 l  一个藏匿bug的内核版本:找出bug首先出现的版本 l  相 ...

  10. Linux内核架构与底层--读书笔记

    linux中管道符"|"的作用 命令格式:命令A|命令B,即命令1的正确输出作为命令B的操作对象(下图应用别人的图片) 1. 例如: ps aux | grep "tes ...

随机推荐

  1. three.js运动

    <!DOCTYPE html> <html> <head> <title>Example 01.04 - Materials, light and an ...

  2. Codeforces 607B Zuma(区间DP)

    题目大概说,有n个颜色的宝石,可以消除是回文串的连续颜色序列,问最少要几下才能全部消除. 自然想到dp[i][j]表示序列i...j全部消除的最少操作数 有几种消除的方式都能通过枚举k(i<=k ...

  3. EF框架step by step(4)—DBcontext应用于已存在数据库

    EF4.1有三种方式来进行数据操作及持久化.分别是Database-First,Model-First,Code-first,前面都已经简单介绍过了.下面简单小结一下:1.Database First ...

  4. 解决嵌入WinForm的WPF控件无法显示图片问题

    解决办法是在控件初始化时,通过下面方法再次加载图片: ucCanvas.CreateCoordinateImage.Source = GetImageIcon(global::MainApplicat ...

  5. samba 挂载 问题

    link: http://www.minunix.com/2013/04/linux-mount-samba/ http://my.oschina.net/laopiao/blog/161648 最近 ...

  6. Android NumberPicker 修改分割线颜色和高度及字体颜色大小

    (1)重写NumberPicker已达到修改显示字体颜色大小 public class TextColorNumberPicker extends NumberPicker { public Text ...

  7. linux修改系统编码

    Windows的默认编码为GBK,Linux的默认编码为UTF-8.在Windows下编辑的中文,在Linux下显示为乱码.一种方法是在windows进行转码,比如使用ue工具在文件-->转换 ...

  8. Selenium_获取相对坐标

    <html> <head> <title>位置</title> <style> .test { background: url(" ...

  9. 转 Datatables中文API——基本参数

    鉴于自己一直在使用datatables,发现这是个很不错的表格插件,但是好的东西都是英文的,所以我结合自己的使用经验,把官网的英文api做下简单的翻译,同时也希望大家把自己的使用经验一起分享出来,让我 ...

  10. 使用plsql创建用户并授权(图形化界面)

    使用sys用户登录数据库(或者有dba权限的[还不知道具体的区别,但是能用]) 在左边的对象列表中找到USERS,右键点击USERS,选择“新建用户”选项 其他安装下面的图片步骤来即可: OK!