第十八章    调试

18.1 准备开始

1、在用户级的程序里,bug表现比较直接;在内核中却不清晰。

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

3、准备工作需要的是:

  (1)一个bug

  (2)一个藏匿bug的内核版本

  (3)相关内核代码的知识和运气

18.2 内核中的bug

1、内核中的bug多种多样。

2、引用空指针会产生一个oops;垃圾数据会导致系统崩溃。

3、定时限制和竞争条件都允许多个线程在内核中同时运行产生的结果。

18.3 通过打印来调试

一、健壮性

1、健壮性——在任何时候,任何地方都能调用它。

2、内核中的printk()比比皆是:

  (1)在中断上下文和进程上下文中被调用

  (2)在任何持有锁时被调用

  (3)在多处理器上同时被调用,并且不必使用锁。

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

二、日志等级

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

2、KERN_WARING和KERN_DEGUG是<linux/kernel.h>中的简单宏定义。内核用指定的记录等级和当前终端的记录等级console_loglevel来决定是不是向终端上打印。

3、默认级别:KERN_WARNING。

4、内核将最重要的内核等级KERN_EMERG定为“<0>”,将无关紧要的记录等级KERN_DEBUG定为“<7>”。

5、有两种赋予记录等级的方法:

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

  (2)给所有调试信息KERN_DEBUG等级,调整终端的默认记录等级。

三、记录缓冲区

1、内核消息都被保存在一个LOG_BUF_LEN大小的环形队列中。在单处理器的系统上其默认值是16KB。

2、优点:在中断上下文中也可以方便的使用。

      使记录维护起来更容易。

缺点:可能会丢失消息。

四、syslogd和klogd

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

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

3、syslogd守护进程把它接收到的所有消息添加进一个文件中,该文件默认是/var/log/messages。也可以通过/etc/syslog.conf配置文件重新指定。

五、从printf()到printk()的转换

1、反复出现的错误很快就会让你开始培养新的习惯。

18.4 oops

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

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

  (1)向终端上输出错误消息

  (2)输出寄存器中保存的信息

  (3)输出可供跟踪的回溯线索

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

4、oops发生时:

  (1)在中断上下文时发生:内核无法继续,会陷入混乱,导致系统死机

  (2)在idle进程(pid为0)或init进程(pid为1)时发生,结果同样是系统陷入混乱。

  (3)在其他进程运行时发生,内核会杀死该进程并尝试着继续执行。

5、oops的产生有很多可能原因:内存访问越界、非法的指令等。

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

  (1)回溯线索:显示了导致错误发生的函数调用链。

  (2)寄存器上下文信息也很有用,比如帮助冲进引发问题的现场

一、ksymoops

1、回溯线索中的地址需要转化成有意义的符号名称才方便使用,这需要调用ksymoops命令,并且还需要提供编译内核时产生的System.map。如果用的是模块,还需要一些模块信息。一般可以这样调用它:ksymoops saved_oops.txt。

二、kallsyms

1、CONFIG_KALLSYMS 定义配置选项启用。

2、CONFIG_KALLSYMS_ALL 不仅存放函数名称;还存放所有符号名称。

3、CONFIG_KALLSYMS_EXTRA_PASS 会引起内核构建过程中再次忽略内核的目标代码。

18.5 内核调试配置选项

1、在内核配置编辑器的内核开发菜单项中,依赖CONFIG_DEBUG_KERNEL。

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

18.6 引发bug并打印信息

1、常用BUG()和BUG_ON()。

2、被调用时会引发oops,导致栈的回溯和错误信息的打印。可以把这些调用当做断言使用,想要断言某种情况不该发生:

  if (bad_thing)

    BUG();

  或:

  BUG_ON(bad_thing);

3、调用panic()不但会打印错误信息,而且还会挂起整个系统。

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

18.7 神奇的系统请求键

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

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

3、除了配置选项以外,还要通过一个sysctl用来标记该特性的开或关,启动命令如下:echo 1 > /proc/sys/kernel/sysrq

4、Sysrq的几个命令:

5、在一行内发送这三个键的组合可以重新启动濒临死亡的系统,这比直接按下机器的Reset键要安全一些。

18.8 内核调试器的传奇

一、gdb

1、可以使用标准的GNU调试器对正在运行的内核进行查看。针对内核启动调试器的方法与针对进程的方法大致相同:gdb vmlinux /proc/kcore

2、vmlinx文件是未经压缩的内核映像,不是压缩过的zImage或bImage,它存放于源代码树的根目录上。

3、/proc/kcore作为一个参数选项,是作为core文件来用的,通过它能够访问到内核驻留的高端内存。只有超级用户才能读取此文件的数据

4、可以使用gdb的所有命令来获取信息。例如:打印一个变量的值:p global_variable

                      反汇编一个函数:disassemble function

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

5、局限性:

  (1)没有办法修改内核数据

  (2)不能单步执行内核代码,不能加断点。

二、kgdb

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

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

3、通过kgdb,gdb的所有功能都能使用:读取和修改变量值、设置断点、设置关注变量、单步执行等。

18.9 探测系统

一、用UID作为选择条件

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

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

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

  if
(current-> uid !=7777) {

  /* 老算法…… */

  }
else {

    /* 新算法…… */

  }

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

二、使用条件变量

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

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

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

三、使用统计量

1、需要掌握某个特定事件的发生规律,需要比较多个事件并从中得出规律通过创建统计量并提供某种机制访问其统计结果。

2、定义两个全局变量:

  unsigned  long 
foo_stat = 0;

  unsigned  long 
bar_stat = 0;

  每当事件发生的时候,就让相应的变量加1.然后在觉得合适的地方输出它。可以在/proc目录中创建一个文件,还可以新建一个系统调用。最简单的办法还是通过调试器直接访问它们。

3、注意,这种实现并非是SMP安全的,更好的方法是通过原子操作进行实现。

四、重复频率限制

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

  (1)重复频率限制

  (2)发生次数限制

2、为了避免调试信息发生井喷,可以每隔几秒执行一次操作。

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

1、知道bug是什么时候引入内核源代码的通常都是很有用的。

18.11 使用Git进行二分搜索

1、具体哪次提交的代码引发了bug,可以使用Git进行二分搜索。

  $ git  bisect  start   
//告知git要进行二分搜索

  $ git  bisect  bad  <revision>   //已知出现问题的最早内核版本

  $ git  bisect  bad  //当前版本就是引发bug的最初版本的情况下使用这条命令

  $ git  bisect  good  v2.6.28 
//最新的可正常运行的内核版本

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

  如果这个版本正常:$ git
 bisect  good

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

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

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

1、应该向内核邮件列表发送一份电子邮件,对bug进行完整而又简洁的描述。

2、社区和它最重要的论坛——Linux内核邮件列表(LKML)。

总结:

  本章讨论了内核的调试——调试过程其实是一种寻求实现与目标偏差的行为。其中考察了几种技术:从内核内置的调试架构到程序调试,从记录日志到用git二分法查找。

  本章的内容对于试图在内核代码中牛刀小试的任何人都至关重要。

Linux内核分析——第十八章 调试的更多相关文章

  1. Linux内核分析第十八章读书笔记

    第十八章 调试 调试工作艰难是内核级开发区别于用户级开发的一个显著特点. 18.1 准备开始 我们需要什么? 一个bug 一个藏匿bug的内核版本 思路:假定能够让bug重现 在用户级程序中,bug直 ...

  2. Linux内核分析 - 网络[十四]:IP选项

    Linux内核分析 - 网络[十四]:IP选项 标签: linux内核网络structsocketdst 2012-04-25 17:14 5639人阅读 评论(1) 收藏 举报  分类: 内核协议栈 ...

  3. Linux内核分析-使用gdb跟踪调试内核从start_kernel到init进程启动

    姓名:江军 ID:fuchen1994 实验日期:2016.3.13 实验指导 使用实验楼的虚拟机打开shell cd LinuxKernel/ qemu -kernel linux-3.18.6/a ...

  4. Linux内核分析 读书笔记 (第十八章)

    第十八章 调试 18.1 准备开始 1. 需要的只是: 一个bug 一个藏匿bug的内核版本 相关内核代码的知识和运气 2. 在跟踪bug的时候,掌握的信息越多越好. 18.2 内核中的bug 1.  ...

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

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

  6. Linux内核分析课程总结

    Linux内核分析课程总结 By 20135203齐岳 知识梳理 (思维导图地址http://mindmap.4ye.me/mkxM0cFh/1) 从start _ kernel构造一个新的Linux ...

  7. Linux内核分析——期末总结

    Linux内核学习总结 首先非常感谢网易云课堂这个平台,让我能够在课下学习,课上加强,体会翻转课堂的乐趣.孟宁老师的课程循序渐进,虽然偶尔我学习地不是很透彻,但能够在后续的课程中进一步巩固学习,更加深 ...

  8. linux内核分析 课程总结

    Linux内核分析 链接汇总 Linux内核分析第一周学习总结--计算机是如何工作的 Linux内核分析第二周学习总结--操作系统是如何工作的 Linux内核分析第三周学习总结--构造一个简单的Lin ...

  9. 【课程总结】Linux内核分析课程总结

    程涵  原创博客 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 每周实验报告: 反汇编一个简单的C程序 ...

随机推荐

  1. 5.2Python函数(二)

    目录 目录 前言 (一)偏函数 ==1.说明== ==2.原代码== ==3.显示效果== (二)高阶函数 ==1.说明== ==2.源代码== ==3.运行效果== (三)返回值函数 ==1.说明= ...

  2. IO流_演示键盘录入

    读取一个键盘录入的数据,打印到控制台上 键盘本身就是一个标准的输入设备,对于java而言,对于这种输入设备都有相应的对象在System类中 import java.io.IOException; im ...

  3. 深入分析escape()、encodeURI()、encodeURIComponent()的区别及示例

    JavaScript中有三个可以对字符串编码的函数,分别是: escape,encodeURI,encodeURIComponent,相应3个解码函数:unescape,decodeURI,decod ...

  4. P3265 [JLOI2015]装备购买(高斯消元+贪心,线性代数)

    题意; 有n个装备,每个装备有m个属性,每件装备的价值为cost. 小哥,为了省钱,如果第j个装备的属性可以由其他准备组合而来.比如 每个装备属性表示为, b1, b2.......bm . 它可以由 ...

  5. metamask源码学习-inpage.js

    The most confusing part about porting MetaMask to a new platform is the way we provide the Web3 API ...

  6. mysql 创建表格 AUTO_INCREMENT

    CREATE TABLE `t_user` ( `USER_ID` int(11) NOT NULL AUTO_INCREMENT, `USER_NAME` char(30) NOT NULL, `U ...

  7. Jenkins忘记密码解决方案

    # 当jenkins忘记了管理用户的密码时,只能通过修改配置文件并重启的方式初始化设置用户名及密码,操作如下: 找到jenkins的配置目录,笔者的jenkins是下载的war包直接丢在tomcat下 ...

  8. 代码编辑器monaco-editor之基础使用

    1.下载安装monaco-editor npm install monaco-editor 我的安装目录在 C://Windows//SystemApps//Microsoft.MicrosoftEd ...

  9. 在Qt项目中如何添加一个已有的项目作为子项目

    新建一个子目录项目(具体方法参见<类似Visual Studio一样,使用Qt Creator管理多个项目,创建子项目>),然后需要添加的项目移动到该子目录项目目录下,再在其pro文件中添 ...

  10. ssh test

    本文以以下需求为背景,介绍详细的做法: 需在同一台服务器同时部署两个不同的 Github 仓库(对 Bitbucket 等 git 服务同样适用) root 用户可在远程登录 SSH 后附上预期的 S ...