Chapter 18 调 试

18.1 准备开始

1.准备工作:

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

2.执行foo就会让程序立即产生核心信息转储(dump core)。

18.2 内核中的bug

往往是经由连锁反应触发的。##

1.内核bug的原因:

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

2.内核bug发作的症状可能有

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

18.3 通过打印来调试

内核提供的打印函数printk(),与C库提供的printf()类似。但是也有一些自身特殊的功能。

18.3.1 健壮性(随时随地可被调用)

  • 在中断上下文和进程上下文中被调用
  • 在任何持有锁时被调用
  • 在多处理器上同时被调用,并且不必使用锁。

    1.在系统启动过程中,终端还没有初始化之前,在某些地方不能使用它。

2.setup_arch():负责执行硬件体系结构相关的初始化动作。

3.核心硬件部分的黑客依靠某时刻能工作的硬件设备(如串口)与外界通信。解决的办法就是提供一个printk()的变体函数——early_printk(),这个函数在启动过程的初期就具有在终端上打印的能力。它的功能与printk()完全相同,区别仅仅在于名字和能够更早地工作。除非在启动初期就要在终端上输出,否则可以认为printk()在什么情况下都能工作。

18.3.2 日志等级

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

enter description here

如果没有特别特别指定,函数会选用默认的DEFAULT_MESSAGE_LOGLEVEL,在当前来看是KERN_WARNING,即一个警告。最好还是给自己的消息指定一个记录等级。内核会把这些记录等级转化为"",n指等级,从0-7,对应表中从上到下,数字越小越重要

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

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

18.3.3 记录缓冲区

  1. 内核消息是保存在一个环形队列中,这个环形队列就是它的记录缓冲区。

  2. 大小是可以在编译时进行调整的,但是在单处理器的系统上默认值是16kb。也就是说内核在同一时间只能保存16kb的内核消息,再多的话新消息就会覆盖老消息,读写都是按照环形队列方式操作的。

  3. 优点:

  • 健壮性:在中断上下文中也可以方便的使用。
  • 简单性:使记录维护起来更容易。
  1. 缺点:可能会丢失消息。

18.3.4 syslogd和klogd(这是两个用户空间的守护进程,klogd从记录缓冲区中获取内核消息,再通过syslogd守护进程将他们保存在系统日志文件中。)

(1)klogd

  • 既可以从/proc/kmsg文件中,也可以通过syslog()系统调用读取这些消息。
  • 默认是/proc方式。
  • 两种情况klogd都会阻塞,知道有新的内核消息可供读出,唤醒之后默认处理是将消息传给syslogd。
  • 可以通过-c标志来改变终端的记录等级

(2)syslogd

将它接收到的所有消息添加到一个文件中,默认是/var/log/messages。

18.4 oops

1.oops是内核告知用户有不幸发生的最常用的方式。内核很难自我修复,也不能将自己杀死,只能发布oops,过程包括:

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

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

3.oops发生的时机:

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

    4.oops发生的可能原因:内存访问越界、非法的指令等

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

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

18.4.1 ksymoops

1.调用ksymoops命令使回溯线索中的地址转化成有意义的符号名称,提供编译内核时产生的System.map。如果用的是模块,还需要一些模块信息。

kysmoop saved_oops.txt

18.4.2 kallsyms

现在的版本中不需要使用sysmoops这个工具,而在新版本中引入了kallsyms特性。

通过定义CONFIG_KALLSYMS配置选项启用。

18.5 内核调试配置选项

1.位于内核配置编辑器的内核开发菜单项中,都依赖于CONFIG_DEBUG_KERNEL。

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,导致栈的回溯和错误信息的打印。可以把这些调用当做断言使用,想要断言某种情况不该发生:

或者:

  • BUG_ON(bad_thing);
  • BUILD_BUG_ON():与BUG_ON()作用相同,仅在编译时调用。
  • panic():可以引发更严重的错误,不但会打印错误信息,还会挂起整个系统。
  • dump_stack():只在终端上打印寄存器上下文和函数的跟踪线索。

18.7 神奇的系统请求键

这个功能可以通过定义CONFIG_MAGIC_SYSRQ配置选项来启用。SysRq(系统请求)键在大多数键盘上都是标准键。该功能被启用时,无论内核出于什么状态,都可以通过特殊的组合键和内核进行通信。除了配置选项以外,还要通过一个sysctl用来标记该特性的开或关,启动命令如下:

echo 1 > /proc/sys/kernel/sysrq

相关命令如下:

enter description here

注意:在一行内发送这三个键的组合可以重新启动濒临死亡的系统。

  • SysRq-s:将“脏”缓冲区跟硬盘交换分区同步
  • SysRq-u:卸载所有的文件系统
  • SysRq-b:重启设备

    内核代码中的Documentation/sysrq.txt有更详细说明,实际的实现在drivers/char/sysrq.c中。

18.8 内核调试器的传奇

18.8.1 gdb

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

    gdb vmlinux /proc/kcore

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

  2. 使用gdb的所有命令来获取信息。

例如:打印一个变量的值:

p global_variable

反汇编一个函数:

disassemble function

使用-g参数还可以提供更多的信息。

  1. 局限性:
  • 没有办法修改内核数据

    -不能单步执行内核代码

18.8.2 kgdb

  1. 用于在远程主机上通过串口利用gdb的所有功能对内核进行调试。
  2. 需要两台计算机。一台运行带有kgdb补丁的内核,另一台通过串行线使用gdb对第一台进行调试。
  3. 通过kgdb,gdb的所有功能都能使用:
  • 读取和修改变量值
  • 设置断点
  • 设置关注变量
  • 单步执行

18.9 探测系统

18.9.1 使用uid作为选择条件

  1. 一般情况下,加入特性时,只要保留原有的算法而把新算法加入到其他位置上,基本就能保证安全。

  2. 可以把用户id(UID)作为选择条件来实现这种功能:

    通过某种选择条件,安排到底执行哪种算法。

18.9.2 使用条件变量

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

2.这种方式比使用UID更简单,只需要创建一个全局变量作为一个条件选择开关:如果该变量为0,就使用某一个分支上的代码;否则,选择另外一个分支。

3.操控方式:某种接口,或者调试器。

18.9.3 使用统计量

1.用于使用者需要掌握某个特定事件的发生规律的时候。方法是创建统计量,并提供某种机制访问其统计结果。

2.操作:定义全局变量-->在/proc目录中创建一个文件/新建一个系统调用/通过调试器直接访问(最直接)。

3.不是SMP安全的,更好的方式是用原子操作。

18.9.4 重复频率限制

当系统的调试信息过多的时候,可以用重复频率限制、发生次数限制防止这类问题发生。

①重复频率限制:限制调试信息,最多几秒打印一次,可以根据自己的需要调节频率。

例如printk()函数的调节频率,可以用printk_ratelimit()函数限制。

②发生次数限制:调试信息至多输出几次,超过次数限制后就不能再输出。

这种方法可以用来确认在特定情况下某段代码的确被执行了。

注:不是SMP安全,不是抢占安全,更好的方式是用原子操作。

SMP(Symmetric Multi-Processing),对称多处理结构的简称,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。在这种技术的支持下,一个服务器系统可以同时运行多个处理器,并共享内存和其他的主机资源。

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

运用二分查找将问题局限在两个相继发行的版本之间,一个包含错误而另一个不包含,这样就能够对引发bug的代码变更进行定位。

这样的方法较为高效。

18.11 使用Git进行二分搜索

1.Git源码管理工具提供了一个有用的二分搜索机制。若使用Git来控制Linux源码树的副本,Git将自动运行二分搜索进程。

2.操作命令如下:

git bisect start    # 告知git要进行二分搜索
git bisect bad <revision> # 已知出现问题的最早内核版本
git bisect bad # 当前版本就是引发bug的最初版本的情况下使用这条命令
git bisect good <revision> # 最新的可正常运行的内核版本

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

①若版本正常,则:

git bisect good

②若版本异常,则:

git bisect bad

4.对于每一个命令,GIT将在每一个版本的基础上反复二分搜索源码树,并且返回所查的下一个内核版本,一直到不能再进行二分搜索位置,最终git会打印出有问题的版本号。

18.12

如果bug是内核的主流部分中出现的,可以在内核开发社区中寻求其他开发者的帮助。

《Linux内核设计与实现》读书笔记三的更多相关文章

  1. R语言实战读书笔记(三)图形初阶

    这篇简直是白写了,写到后面发现ggplot明显更好用 3.1 使用图形 attach(mtcars)plot(wt, mpg) #x轴wt,y轴pgabline(lm(mpg ~ wt)) #画线拟合 ...

  2. R实战读书笔记四

    第三章 图形入门 本章概要 1 创建和保存图形 2 定义符号.线.颜色和坐标轴 3 文本标注 4 掌控图形维数 5 多幅图合在一起 本章所介绍内容概括例如以下. 一图胜千字,人们从视觉层更易获取和理解 ...

  3. iPhone与iPad开发实战读书笔记

    iPhone开发一些读书笔记 手机应用分类1.教育工具2.生活工具3.社交应用4.定位工具5.游戏6.报纸和杂志的阅读器7.移动办公应用8.财经工具9.手机购物应用10.风景区相关应用11.旅游相关的 ...

  4. <<Java RESTful Web Service实战>> 读书笔记

    <<Java RESTful Web Service实战>> 读书笔记 第一章   JAX-RS2.0入门 REST (Representational State ransf ...

  5. 机器学习实战 - 读书笔记(13) - 利用PCA来简化数据

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第13章 - 利用PCA来简化数据. 这里介绍,机器学习中的降维技术,可简化样品数据. ...

  6. 机器学习实战 - 读书笔记(12) - 使用FP-growth算法来高效发现频繁项集

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第12章 - 使用FP-growth算法来高效发现频繁项集. 基本概念 FP-growt ...

  7. 机器学习实战 - 读书笔记(11) - 使用Apriori算法进行关联分析

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第11章 - 使用Apriori算法进行关联分析. 基本概念 关联分析(associat ...

  8. 机器学习实战 - 读书笔记(07) - 利用AdaBoost元算法提高分类性能

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习笔记,这次是第7章 - 利用AdaBoost元算法提高分类性能. 核心思想 在使用某个特定的算法是, ...

  9. 【转载】MDX Step by Step 读书笔记(三) - Understanding Tuples (理解元组)

    1. 在 Analysis Service 分析服务中,Cube (多维数据集) 是以一个多维数据空间来呈现的.在Cube 中,每一个纬度的属性层次结构都形成了一个轴.沿着这个轴,在属性层次结构上的每 ...

  10. Spring实战读书笔记

    Spring实战读书笔记 Spring-core Spring之旅 - DI 和 AOP 概念 spring 的Bean容器 spring 的 核心模块 Spring的核心策略 POJO 最小侵入式编 ...

随机推荐

  1. 11LaTeX学习系列之---LaTeX的特殊字符

    目录 目录 前言 (一)源代码 (二)输出效果 目录 本系列是有关LaTeX的学习系列,共计19篇,本章节是第11篇. 前一篇:10LaTeX学习系列之---Latex的文档结构 后一篇:12LaTe ...

  2. tkinter内嵌Matplotlib系列(二)之函数曲线绘制

    目录 目录 前言 (一)对matplotlib画布的封装: (二)思路分析: 1.需求说明: 2.框架的设置: 3.文件说明: (三)各文件的源代码 1.main.py 2.widget.py 3.f ...

  3. java.sql.SQLSyntaxErrorException: ORA-00904: "column": 标识符无效

    java.sql.SQLSyntaxErrorException: ORA-00904: "column": 标识符无效 首先查看无效的列是不是orcale关键字 , 如果不是 , ...

  4. MySql/Oracle和SQL Server的分页查

    假设当前是第PageNo页,每页有PageSize条记录,现在分别用Mysql.Oracle和SQL Server分页查询student表. 1.Mysql的分页查询: 1 SELECT 2 * 3 ...

  5. 描述整体程序的 app

    一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下 app.js文件 App() 函数用来注册一个小程序.接受一个 object 参数,其指定小程序的生命周期函数等.object参数说明如下 ...

  6. 说明split()与join()函数的区别?

    前者是切割成数组的形式,后者是将数组转换成字符串join函数获取一批字符串,然后用分隔符字符串将它们连接起来,从而返回一个字符串.Split函数获取一个字符串,然后再分隔符处将其断开,从而返回一批字符 ...

  7. Python高级网络编程系列之终极篇---自己实现一个Web框架

    通过前面几个小节的学习,现在我们想要把之前学到的知识点给串联起来,实现一个很小型的Web框架.虽然很小,但是用到的知识点都是比较多的.如Socket编程,装饰器传参在实际项目中如何使用.通过这一节的学 ...

  8. 转载 js函数声明和函数表达式

    在js中函数有两种表达方式.1 函数声明 2 函数表达式 函数声明 function sayname(){ alert("li lei"); } 函数表达式 var sayname ...

  9. MetaMask/metamask-inpage-provider

    https://github.com/MetaMask/metamask-inpage-provider Used to initialize the inpage ethereum provider ...

  10. js删除数组元素、清空数组的简单方法

    一.清空数组 ? 1 2 3 var ary = [1,2,3,4]; ary.splice(0,ary.length);//清空数组 console.log(ary); // 输出 [],空数组,即 ...