调试内核艰难且风险高,关键在于对内核的深入理解。linux
须要的是:git
内核中的bug不是很清晰,调试成功的关键点在于精确的重现一个bug。算法
内核bug多种多样,表象也变化无穷:sass
bug展示出来每每是经历一系列的连锁反应,内核调试没有特别难,但也有一些独特的问题:如定时限制和竞争条件,这些是容许多个线程在内核中同时运行产生的结果。安全
内核提供的打印函数printk()和C库提供的printf()功能几乎相同。app
printk()特质:健壮性:任什么时候候任何地方都能调用。编辑器
日志等级ide
printk()指定一个日志级别,内核把级别比某个特定值低的消息显示在终端上。函数
有7个等级:KERN_EMERG~KERN_DEBUG(宏),对应<0>~<7> 默认是DEFAULT_MESSAGE_LOGLEVEL(如今是KERN_WARNING,可能改)
记录缓冲区工具
内核消息被保存在一个LOG_BUF_LEN大小的环形队列中,大小可在编译时设置CONFIG_LOG_BUF_SHIFT进行调整。 单处理器系统上默认值是16KB,超过最大值时,新消息将覆盖老消息。
环形队列好处:
缺点:可能会丢失信息
syslogd和klogd是用户空间守护进程。
klogd从记录缓冲区获取内核信息(会先堵塞,到有新信息可读),传给syslogd,syslogd把它们保存在系统日志文件中(默认是/var/log/messages,可经过/etc/syslog.conf配置文件从新指定)。
读取两种方式:
启动klogd时,可经过-c改变终端的记录等级。
oops是内核告知用户有不幸发生的最经常使用的方式,oops包括:
发送完oops后,内核会处于一种不稳定状态,若是oops在idle进程(pid=0)或init进程(pid=1)时发生,系统将陷入混乱,如果其余进程,内核会杀死该进程并尝试继续执行。
ksymoops命令:将回溯线索中的地址转化为有意义的符号名称,必须提供编译内核时产生的System.map,ksymoops会自行解析,获得解码版:
ksymoops saved_oops.txt
kallsyms特性:内核引入kallsyms特性,经过定义CONFIG_KALLSYMS配置选项启用,存放着内核镜像中相应函数地址的符号名称,内核能够打印解码好的跟踪线索。再也不须要System.map或ksymoops了。
为了方便调试和测试内核代码,内核提供了许多配置选项:
位于内核配置编辑器的内核开发菜单项中,依赖于CONFIG_DEBUG_KERNEL。
如:
原子操做:指那些可以不分隔执行的东西;在执行时不能中断不然就是完不成的代码。这时睡眠就是引起死锁的元凶。
解决:内核提供了一个原子操做计数器,在进程要进入睡眠时打印警告信息并提供追踪线索。
BUG()和BUG_ON():用来方便标记bug,提供断言并输出信息,会引起oops,致使栈的回溯和错误信息的打印。能够把这些调用当作断言使用,想要断言某种状况不应发生:
if (bad_thing) BUG(); 或 BUG_ON(bad_thing);(更清晰更可读,会将其声明做为一个语句放入unlikely()中) 另:BUILD_BUG_ON():与BUG_ON()做用相同,仅在编译时调用。
panic():引起更严重的错误,不但会打印错误信息,还会挂起整个系统。
dump_stack():只在终端上打印寄存器上下文和函数的跟踪线索。
神奇的系统请求键是调试和挽救垂危系统所必需的一种工具:该功能被启用时,不管内核出于什么状态,均可以经过特殊的组合键和内核进行通讯。
经过定义CONFIG_MAGIC_SYSRQ配置选项来启用。 除了配置选项之外,还要经过一个sysctl用来标记该特性的开或关,启动命令以下: echo 1 > /proc/sys/kernel/sysrq
输入Sysrq-h可获取可用的选项列表,三键组合可从新启动濒临死亡的系统:
SysRq-s:将“脏”缓冲区跟硬盘交换分区同步 SysRq-u:卸载全部的文件系统 SysRq-b:重启设备
因为没有用于内核的调试器,许多补丁应运而生,为标准内核附加上了内核调试的支持,这些补丁功能完善,十分强大。
gdb
可使用标准的GNU调试器对正在运行的内核进行查看。针对内核启动调试器的方法与针对进程的方法大体相同:
gdb vmlinux /proc/kcore vmlinx:未经压缩的内核映像,不是压缩过的zImage或bImage,它存放于源代码树的根目录上。 /proc/kcore:做为一个参数选项,是做为core文件来用的,经过它可以访问到内核驻留的高端内存。只有超级用户才能读取此文件的数据。
可使用gdb的命令来获取信息。如:
打印一个变量的值:p global_variable 反汇编一个函数:disassemble function 编译内核时使用了-g参数gdb还能够提供更多的信息。(不能当习惯,这样编译的内核会很大)
局限性:没有办法修改内核数据,不能单步执行内核代码,不能加断点
kgdb
是一个补丁 ,可让咱们在远程主机上经过串口利用gdb的全部功能对内核进行调试。这须要两台计算机:第一台运行带有kgdb补丁的内核,第二台经过串行线使用gdb对第一台进行调试。
这样经过kgdb,gdb的全部功能都能使用:
使用uid做为选择条件
提供替代物的同时不打破原有代码的可执行性:把用户id(UID)做为选择条件来实现:
if (current-> uid !=7777) { //新建立的uid=7777的用户,专门用来测试新算法。 /* 老算法…… */ } else { /* 新算法…… */ }
使用条件变量
若是代码与进程无关,或者但愿有一个针对全部状况都能使用的机制来控制某个特性,可使用条件变量。
只须要建立一个全局变量做为一个条件选择开关:若是该变量为0,就使用某一个分支上的代码;不然,选择另一个分支。经过某种接口或者调试器直接操控。
使用统计量
须要掌握某个特定事件的发生规律时使用:建立统计量,并提供某种机制访问其统计结果。
重复频率限制
当系统要显示的调试信息过多的时候,有两种技巧能够防止这类问题发生:
另:用到的变量都应该是静态的,并限制在函数局部范围之内。
注意:以上都不是SMP(对称多处理结构)安全的,理想的方式是用原子操做。
找到bug是何时引入内核源代码的,就是从哪个版本开始有bug的。
在一个确保没有问题的内核和一个确定有问题的内核之间使用二分法,重复筛选,将问题局限在两个相继发行的版本之间,对引起bug的代码变动进行定位。
Git可自动进行二分搜索进程找到具体哪次提交的代码引起了bug:
git bisect start //告诉git要进行二分搜索 git bisect bad <revision> //提供出现问题的最先内核版本,若是就是当前版本:git bisect bad git bisect good <revision> //提供最新的可正常运行的内核版本 git利用二分搜索法在Linux源码树中,自动检测正常的版本内核和有bug的内核版本之间那个版本有隐患,再编译、运行以及测试正被检测的版本。 若是这个版本正常:git bisect good 若是这个版本运行有异常:git bisect bad 注意:对于每个命令,git将在一个版本的基础上反复二分搜索源码树,而且返回所查的下一个内核版本。反复执行直到不能再进行二分搜索为止,最终git会打印出有问题的版本号。 可指定git仅在与错误相关的目录列表中(这里是arch/x86)去二分搜索提交的补丁:git bisect start - arch/x86
在内核开发社区中寻找其余开发者的帮助。