第十八章 调试

内核级开发的调试工作远比用户级开发艰难的多。

一、准备开始

准备工作需要的是:

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

在这一章里,调试的主要思想是让bug重现,但是在内核中这并不是很容易做到的。因此,在跟踪bug的时候,掌握的信息越多越好。

二、内核中的bug

内核bug的原因可能有:

- 错误代码
- 同步时发生的错误,例如共享变量锁定不当
- 错误的管理硬件
- ……

内核bug发作的症状可能有:

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

内核开发比起用户开发要多考虑一些独特的问题,比如:

- 定时限制
- 竞争条件
- ……
原因是允许多个线程在内核中同时运行。

三、通过打印来调试

这里说的打印是指的内核的格式化打印函数printk(),因为它有自己的一些特殊的功能:

1.健壮性

健壮性的意思是,在任何时候,任何地方都能调用它。

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

弹性极佳

唯一的例外是【在系统启动过程的初期就要输出】这种情况,需要用early_printk()代替,两者功能完全相同。

2.日志等级

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



如果没有特别特别指定,函数会选用默认的DEFAULT_MESSAGE_LOGLEVEL,在当前来看是KERN_WARNING,即一个警告。最好还是给自己的消息指定一个记录等级。

内核会把这些记录等级转化为"",n指等级,从0-7,对应表中从上到下,数字越小越重要,也就是说:

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

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

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

3.记录缓冲区

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

大小是可以在编译时进行调整的,但是在单处理器的系统上默认值是16kb。

也就是说内核在同一时间只能保存16kb的内核消息,再多的话新消息就会覆盖老消息,读写都是按照环形队列方式操作的。

优点:

  1. 健壮性:在中断上下文中也可以方便的使用。
  2. 简单性:使记录维护起来更容易。

缺点:

可能会丢失消息。

4.syslogd和klogd

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

(1)klogd

既可以从/proc/kmsg文件中,也可以通过syslog()系统调用读取这些消息。

默认是/proc方式。

两种情况klogd都会阻塞,知道有新的内核消息可供读出,唤醒之后默认处理是将消息传给syslogd。

可以通过-c标志来改变终端的记录等级

(2)syslogd

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

四、oops

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

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

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

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

关于oops发生的时机:

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

oops发生的可能原因:

  1. 内存访问越界
  2. 非法的指令
  3. ……

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

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

1.ksymoops

回溯线索中的地址需要转化成有意义的符号名称

——需要调用ksymoops命令。

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

kysmoop saved_oops.txt

2.kallsyms

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

五、内核调试配置选项

位于内核配置编辑器的内核开发菜单项中,都依赖于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 自旋锁内睡眠选项
  • ……

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

是完不成的代码。

例如;正在使用一个自旋锁或禁止抢占的代码。

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

六、引发bug并打印信息

1.BUG()和BUG_ON()

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

可以把这些调用当做断言使用,想要断言某种情况不该发生:

if (bad_thing)
BUG();
或:
BUG_ON(bad_thing);

2.BUILD_BUG_ON()

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

3.panic()

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

4.dump_stack()

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

七、神奇的系统请求键

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

该功能被启用时,无论内核出于什么状态,都可以通过特殊的组合键和内核进行通信。

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

echo 1 > /proc/sys/kernel/sysrq

Sysrq的几个命令:

SysRq-s:将“脏”缓冲区跟硬盘交换分区同步

SysRq-u:卸载所有的文件系统

SysRq-b:重启设备

……参见以下表格:

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

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

八、内核调试器的传奇

1.gdb

可以使用标准的GNU调试器对正在运行的内核进行查看。

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

gdb vmlinux /proc/kcore

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

可以使用gdb的所有命令来获取信息。例如:

打印一个变量的值:
p global_variable 反汇编一个函数:
disassemble function -g参数还可以提供更多的信息。

局限性:

  1. 没有办法修改内核数据
  2. 不能单步执行内核代码

2.kgdb

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

需要两台计算机:仪态运行带有kgdb补丁的内核,第二胎通过串行线使用gdb对第一台进行调试。

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

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

九、探测系统

1.使用uid作为选择条件

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

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

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

例如:

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

即,除了uid=7777的用户以外,其他所有的用户都是用的老算法,所以这个7777用户可以专门用来测试新算法。

2.使用条件变量

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

这种方式比使用UID更简单,只需要创建一个全局变量作为一个条件选择开关:

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

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

3.使用统计量

这种方法常用于使用者需要掌握某个特定事件的发生规律的时候。

方法是创建统计量,并提供某种机制访问其统计结果。

定义全局变量

在/proc目录中创建一个文件
or新建一个系统调用
or通过调试器直接访问(最直接)

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

4.重复频率限制

当系统的调试信息过多的时候,有两种方式可以防止这类问题发生:

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

(1)重复频率限制

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

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

(2)发生次数限制

这种方法是要调试信息至多输出几次,超过次数限制后就不能再输出。

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

※用到的变量需要是**静态的、局部的。

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

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

十、二分搜索的妙用

很多时候,内核的更新会带来bug,那么是哪一个内核版本带来的bug?可以使用二分法搜索。

我们可以利用GIT来实现这一步骤。

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

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

如果这个版本正常:
git bisect good 如果这个版本运行有异常:
git bisect bad

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

总结:

这一章中 ,我学到了有关内核bug和调试的很多知识,只是就目前看来它离我还比较远,需要在今后的学习过程中不断加深认识和理解。

20135202闫佳歆--week5 课本18章学习笔记的更多相关文章

  1. 20135202闫佳歆--week3 课本1-2章学习笔记

    第一章 Linux内核简介 一.Unix Unix是一个强大.健壮和稳定的操作系统. 简洁 绝大部分东西都被当做文件对待.这种抽象使对数据和对设备的操作都是通过一套相同的系统调用借口来进行的:open ...

  2. 20135202闫佳歆--week7 可执行程序的装载--学习笔记

    此为个人学习笔记存档 week 7 可执行程序的装载 一.预处理.编译.链接和目标文件的格式 可执行文件的创建--预处理.编译和链接 cd Code vi hello.c gcc -E -o hell ...

  3. 20135202闫佳歆--week4 课本第5章学习笔记

    第五章 系统调用 一.与内核通信 系统调用在用户控件进程和硬件设备之间添加了一个中间层,作用如下" 为用户空间提供了一种硬件的抽象接口 系统调用保证了系统的稳定和安全 每个进程都运行在虚拟系 ...

  4. 20135202闫佳歆--week5 系统调用(下)--学习笔记

    此为个人笔记存档 week 5 系统调用(下) 一.给MenuOS增加time和time-asm命令 这里老师示范的时候是已经做好的了: rm menu -rf 强制删除 git clone http ...

  5. 20135202闫佳歆--week6 课本第三章学习笔记

    第三章 进程管理 一.进程 1.进程 进程就是处于执行期的程序. 进程就是正在执行的程序代码的实时结果. 进程是处于执行期的程序以及相关的资源的总称. 进程包括代码段和其他资源. 2.线程 执行线程, ...

  6. 20135202闫佳歆--week5 分析system_call中断处理过程--实验及总结

    week 5 实验:分析system_call中断处理过程 一.使用gdb跟踪分析一个系统调用内核函数(上周选择那一个系统调用)--getpid 复习视频: 如何实现? - 更新menu代码到最新版 ...

  7. 20135202闫佳歆--week 7 Linux内核如何装载和启动一个可执行程序--实验及总结

    week 7 实验:Linux内核如何装载和启动一个可执行程序 1.环境搭建: rm menu -rf git clone https://github.com/megnning/menu.git c ...

  8. 20135202闫佳歆--week 9 期中总结

    期中总结 前半学期的主要学习内容是学习mooc课程<Linux内核分析>以及课本<Linux内核设计与实现>. 所涉及知识点总结如下: 1. Linux内核启动的过程--以Me ...

  9. 20135202闫佳歆--week3 跟踪分析Linux内核的启动过程--实验及总结

    实验三:跟踪分析Linux内核的启动过程 一.调试步骤如下: 使用gdb跟踪调试内核 qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd r ...

随机推荐

  1. NoSQL——not onlySQL不仅仅是SQL

    数据有很大一部分是由关系数据库管理系统(RDBMS)来处理. 1970年 E.F.Codd's提出的关系模型的论文 "A relational model of data for large ...

  2. 在Linux服务器上运行Jupyter notebook server教程

    在Linux服务器上运行Jupyter notebook server教程 很多deep learning教程都推荐在jupyter notebook运行python代码,方便及时交互.但只在本地运行 ...

  3. Android常见UI组件之ListView(一)

    使用ListView显示一个长的项列表 1.新建一个名为"BasicView5"的Android项目. 2.改动BasicView5.java文件.改动后的程序例如以下: pack ...

  4. tomcat 使用 cronolog 切割日志

    1. 下载 cronolog 软件 wget http://cronolog.org/download/cronolog-1.6.2.tar.gz cronolog-.tar.gz tar zxvf ...

  5. VA_X安装

    1,下载VA_X安装包: 地址:https://pan.baidu.com/s/16v5RFX2apjqvDy1Jw8A6Dg 密码:tmv0 解压后显示: 第一步: 第二步:将这个解压文件下的VA_ ...

  6. C#控件中的KeyDown、KeyPress 与 KeyUp事件浅谈

    研究了一下KeyDown,KeyPress 和 KeyUp 的学问.让我们带着如下问题来说明: 1.这三个事件的顺序是怎么样的? 2.KeyDown 触发后,KeyUp是不是一定触发? 3.三个事件的 ...

  7. ORA-02291: 违反完整约束条件 - 未找到父项关键字

    由于大意,在设置数据库表时将外键字段的类型与外键表的主键字段类型不一致,造成此错误. 我的情况是: 1.将一个为number(10)的外键设置成了number(19) 2.将外键字段对应的主键表设置成 ...

  8. C# read write ini file

    [DllImport("kernel32")] private static extern long WritePrivateProfileString(string sectio ...

  9. vb用createprocess启动其他应用程序

    Option Explicit Private Type PROCESS_INFORMATION hProcess As Long hThread As Long dwProcessId As Lon ...

  10. 20155204《网络对抗》Exp7 网络欺诈防范

    20155204<网络对抗>Exp7 网络欺诈防范 一.基础问题回答 1.通常在什么场景下容易受到DNS spoof攻击 在不安全的网络环境下访问网站. 2.在日常生活工作中如何防范以上两 ...