1.内核中的bug

  1. 内核中的bug表现得不像用户级程序中那么清晰——因为内核、用户以及硬件之间的交互会很微妙;
  2. 从隐藏在源代码中的错误到展现在目击者面前的bug,往往是经历一系列连锁反应的事件才可能触发的。

内核调试的难点

  1. 重现bug困难
  2. 调试风险比较大
  3. 定位bug的初始版本困难

2. 内核调试的工具和方法

2.1 输出 LOG

输出LOG不光是内核调试, 即使是在用户态程序的调试中, 也是经常使用的一个调试手段.

通过在可疑的代码周围加上一些LOG输出, 可以准确的了解bug发生前后的一些重要信息.

2.1.1 LOG等级

linux内核中输出LOG的函数是 printk (语法和printf几乎雷同, 唯一的区别是printk可以指定日志级别)

printk之所以好用, 就在与它随时都可以被调用, 没有任何限制条件.

printk的输出日志级别如下:

等级

描述

KERN_EMERG 一个紧急情况
KERN_ALERT 一个需要立即被注意到的错误
KERN_CRIT 一个临界情况
KERN_ERR 一个错误
KERN_WARNING 一个警告
KERN_NOTICE 一个普通的, 不过也有可能需要注意的情况
KERN_INFO 一条非正式的消息
KERN_DEBUG 一条调试信息--一般是冗余信息

输出示例:

printk(KERN_WARNING "This is a warning!\n");
printk(KERN_DEBUG "This is a debug notice!\n");
2.1.2 LOG记录

标准linux系统上, printk 输出log之后, 由用户空间的守护进程klogd从缓冲区中读取内核消息, 然后再通过syslogd守护进程将它们保存在系统日志文件中.

syslogd 将接受到的所有内核消息添加到一个文件中, 该文件默认为: /var/log/dmesg (系统Centos6.4 x86_64)

2.2 oops

oopss是个拟声词, 类似 "哎哟" 的意思. 它是内核通知用户有不幸发生的最常用方式.

触发一个oops很简单, 其实只要在上篇博客中的那些内核模块示例中随便找一个, 里面加上一段给未初始化的指针赋值的代码, 就能触发一个oops

oops中包含错误发生时的一些重要信息(比如, 寄存器上下文和回溯线索), 对调试bug很有帮助!

调试内核时, 还可以开启内核编译参数中的各种和内核调试相关的选项, 那样还可以给我们提供内核崩溃时的一些额外信息.

2.3 主动触发bug

调试中有时将某些情况下标记为bug, 执行到这些情况时, 提供断言并输出信息.

BUG 和 BUG_ON 就是2个可以主动触发oops的内核调用.

在不应该被执行到的地方使用 BUG 或者 BUG_ON 来捕获.

比如:

if (bad_thing)
BUG();
// 或者
BUG_ON(bad_thing);

如果想要触发更为严重的错误, 可以使用 panic() 函数

比如:

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

此外, 还有dump_stack 函数可以打印寄存器上下文和回溯信息.

比如:

if (!debug_check) {
printk(KERN_DEBUG "provide some information...\n");
dump_statck();
}

2.4 系统请求键

这个系统请求键可以在一个快挂了的系统上输出一些有用的信息.

这个按键一般就是标准键盘上的 [SysRq] 键 (就在 F12 键右边, 其实就是windows中截整个屏幕的按键)

单独按那个键相当于截屏, 按住 ALT + [SysRq] = [SysRq]的功能

启用这个键的功能有2个方法:

  • 开启内核编译选项 : CONFIG_MAGIC_SYSRQ
  • 动态启用: echo 1 > /proc/sys/kernel/sysrq

支持 SysRq 的命令如下:

主要命令

描述

SysRq-b 重新启动机器
SysRq-e 向init以外的所有进程发送SIGTERM信号
SysRq-h 在控制台显示SysRq的帮助信息
SysRq-i 向init以外的所有进程发送SIGKILL信号
SysRq-k 安全访问键:杀死这个控制台上的所有程序
SysRq-l 向包括init的所有进程发送SIGKILL信号
SysRq-m 把内存信息输出到控制台
SysRq-o 关闭机器
SysRq-p 把寄存器信息输出到控制台
SysRq-r 关闭键盘原始模式
SysRq-s 把所有已安装文件系统都刷新到磁盘
SysRq-t 把任务信息输出到控制台
SysRq-u 卸载所有已加载文件系统

2.5 内核调试器 gdb和kgdb

linux内核的调试器可以使用 gdb或者kgdb, 配置比较麻烦, 准备实际用调试的时候再去试试效果如何..

2.6 探测系统

下面一些方法是在修改内核后, 用来试探内核反应的小技巧.

2.6.1 用UID控制内核执行

比如在内核中加入了新的特性, 为了测试特性, 可以用UID来控制内核是否执行新特性.

if (current->uid != 7777) {
/* 原先的代码 */
} else {
/* 新的特性 */
}
2.6.2 用条件变量控制内核执行

也可以设置一些条件变量来控制内核是否执行某段代码.

条件变量可以像上篇博客中那样, 设置在 sys 文件系统的某个文件中. 当文件中的值变化时, 通知内核执行相应的代码.

2.6.3 使用统计量观察内核执行某段代码的频率

实现思路就是在内核中的设置一个全局变量, 比如 my_count, 当内核执行到某段代码时, 给 my_count + 1 就行.

同时还要将 my_count 打印出来(可以用printk), 便于随时查看它的值.

2.6.4 控制内核执行某段代码的频率

有时侯, 我们需要在内核发生错误时打印错误相关的信息, 如果这个错误不会导致内核崩溃, 并且这个错误每秒会发生几百次甚至更多.

那么, 用printk输出的信息会非常多, 给系统造成额外的负担.

这时, 我们就需要想办法控制错误输出的频率, 有2种方法:

方法1: 隔一段时间才输出一次错误

static unsigned long prev_jiffy = jiffies;  /* 频率限制 */

if (time_after(jiffies, prev_jiffy + 2*HZ)) {  /* 输出间隔至少 2HZ */
prev_jiffy = jiffies;
printk(KERN_ERR "错误信息....\n");
}

方法2: 输出 N 次之后, 不再输出(N是正整数)

static unsigned long limit = 0;

if (limit < 5) { /* 输出5次错误信息后就不再输出 */
limit++;
printk(KERN_ERR "错误信息....\n");
}

2.7 二分法查找bug发生的最初内核版本

Git 提供的二分搜索机制:

git bisect start  # 开始二分搜索
git bisect bad <bad_revision> # 指定一个bug出现的内核版本号
git bisect good <good_revision> # 指定一个没有bug的内核版本号, 此时git会检测2个版本直接的隐患 # 根据结果再次设置 bad 和 good 的版本号, 缩小Git检索范围, 直至找到可疑之处为止.

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

  1. Linux内核设计与实现第十周读书笔记

    第十七章 设备与模块 关于设备驱动与设备管理,我们讨论四种内核成分. 设备类型 模块 内核对象 sysfs 17.1设备类型 在Linux以及所有Unix系统中,设备被分为以下三种类型: 块设备,块设 ...

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

    <Linux内核设计与实现>Chapter 18 读书笔记 一.准备开始 一个bug 一个藏匿bug的内核版本 知道这个bug最早出现在哪个内核版本中. 相关内核代码的知识和运气 想要成功 ...

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

    <Linux内核设计与实现>Chapter 3 读书笔记 进程管理是所有操作系统的心脏所在. 一.进程 1.进程就是处于执行期的程序以及它所包含的资源的总称. 2.线程是在进程中活动的对象 ...

  4. 《Linux内核设计与实现》第四周读书笔记——第五章

    <Linux内核设计与实现>第四周读书笔记--第五章 20135301张忻 估算学习时间:共1.5小时 读书:1.0 代码:0 作业:0 博客:0.5 实际学习时间:共2.0小时 读书:1 ...

  5. 《Linux内核设计与实现》Chapter 1 读书笔记

    <Linux内核设计与实现>Chapter 1 读书笔记 一.Unix的特点 Unix从Multics中产生,是一个强大.健壮和稳定的操作系统. 特点 1.很简洁 2.在Unix系统中,所 ...

  6. 《Linux内核设计与实现》Chapter 2 读书笔记

    <Linux内核设计与实现>Chapter 2 读书笔记 一.获取内核源码 1.使用Git 我们曾经在以前的学习中使用过Git方法 $ git clone git://git.kernel ...

  7. 《Linux内核设计与实现》Chapter 5 读书笔记

    <Linux内核设计与实现>Chapter 5 读书笔记 在现代操作系统中,内核提供了用户进程与内核进行交互的一组接口,这些接口的作用是: 使应用程序受限地访问硬件设备 提供创建新进程与已 ...

  8. LINUX内核设计与实现第三周读书笔记

    LINUX内核设计与实现第三周读书笔记 第一章 LINUX内核简介 1.1 Unix的历史 1969年的夏天,贝尔实验室的程序员们在一台PDR-7型机上实现了Unix这个全新的操作系统. 1973年, ...

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

    1. 内核调试的难点 重现bug困难 调试风险比较大 定位bug的初始版本困难 2. 内核调试的工具和方法 2.1 输出 LOG 输出LOG不光是内核调试, 即使是在用户态程序的调试中, 也是经常使用 ...

  10. Linux内核设计与实现第五周读书笔记

    第十八章 调试 18.1准备开始 需要的只是: 一个确定的bug.大部分bug通常都不是行为可靠而且定义明确的. 一个藏匿bug的内核版本. 相关的内核代码的知识和运气. 18.2内核中的bug 内核 ...

随机推荐

  1. 记录:一个SQL SERVER奇怪的问题。

    今天遇到了一个奇怪的问题.始终没搞清楚是怎么回事.先记一下 1.首先有张表a,包含字段 编号.日期(varchar(250)),数值 发生日期字段有非正常日期字符串,有NULL,空字符串,可能是误触键 ...

  2. January 04th, 2018 Week 01st Thursday

    Just do what works for you, because there will always be someone who think differently. 就做你自己所能做的,因为 ...

  3. DFS普及组常用模板简单整理

    一些普及组会用到的DFS模板,其他的DFS我感觉普及组不会用到所以暂且搁着,等之后有时间了再细写w (至于我为什么最近不写TG相关只写最基础的PJ的内容,请戳这里了解) dfs各种模板big集合 1. ...

  4. 计算机基础-Ghost克隆

    Ghost硬盘克隆 1.主要功能 (1)创建硬盘镜像备份文件 (2)将备份恢复到原硬盘上 (3)磁盘备份可以在各种不同的存储系统间进行 (4)可以将备份复制到别的硬盘上 (5)在复制过程中自动分区并格 ...

  5. CyclicBarrier源码解读

    1. 简介 JUC中的CyclicBarrier提供了一种多线程间的同步机制,可以让多个线程在barrier等待其它线程到达barrier.正如其名CyclicBarrier含义就是可以循环使用的屏障 ...

  6. sql 查询重复行数据

    1.查找表中多余的重复记录,重复记录是根据单个字段(peopleId)来判断select * from peoplewhere peopleId in (select  peopleId  from  ...

  7. Android AbsListView Abs前缀

    Android AbsListView Abs abstract:抽象

  8. 如何在Windows平台下安装配置Memcached

    Memcached是一个自由开源的,高性能,分布式内存对象缓存系统. Memcached是以LiveJournal旗下Danga Interactive公司的Brad Fitzpatric为首开发的一 ...

  9. docker swarm英文文档学习-11-上锁你的集群来保护你的加密密钥

    Lock your swarm to protect its encryption key上锁你的集群来保护你的加密密钥 在Docker 1.13及更高版本中,默认情况下,群管理器使用的Raft日志在 ...

  10. Spring容器IOC解析及简单实现(转)

    文章转自http://blog.csdn.net/liushuijinger/article/details/35978965