嵌入式(驱动-基础):29---调试技术之(系统故障调试(oops消息)、系统挂起)
一、调试系统故障
- 系统故障通常是指系统中某个进程崩溃,而系统仍会继续运行
- 系统故障可能带来的损失是:进程被终止时为进程上下文分配的一些内存可能会丢失(例如:驱动程序通过kmalloc分配的动态链表可能丢失)
- 系统故障产生的消息称为“oops”消息,oops消息并不会导致整个系统崩溃,但是如果发现了这种情况时最好要重新引导系统
- 一个有缺陷的驱动程序可能导致硬件不可用,或者导致内核资源处于不一致的状态,最坏的情况可能会随即破坏内核内存。通常,我们可以在看到oops之后卸载自己有缺陷的驱动程序,然后重试。但是,如果我们看到任何说明系统整体出现问题的信息后,最好的办法就是立即重新引导系统
- oops消息的格式相当的复杂,不过处理器在出错时转储处得我这些数据包含了很多值得关注的信息,通过它们足够我们查明程序错误,而无须额外的测试
oops消息
- 产生oops消息的原因:大部分错误都是因为对NULL指针取值或因为使用了其他不正确的指针值。这些错误通常会导致一个oops消息
- 产生oops消息的底层实现机制:由处理器使用的地址几乎都是虚拟地址,这些地址(除了内存管理子系统本身所使用的物理内存之外)通过一个复杂的被称为“页表”的结构被映射为物理地址。当引用一个非法指针时,分页机制无法将该地址映射到物理地址,此时处理器就会向操作系统发出一个“页面失效”的信号。如果地址非法,内核就无法“换入(page in)”缺失页面;这时,如果处理器恰好处于超级用户模式,系统就会产生一个oops
- oops显示的消息内容:oops显示发生错误时处理器的状态,比如CPU存储器的内容以及其他看上去无法理解的信息。这些消息由失效处理函数(arch/*(各种平台)/kernel/traps.c)中的printk语句产生
oops消息内容格式
- ①查看oops,首先观察发生的问题所在的位置,这可通过查看调用栈信息得到,例如:
- 我们可以看到,故障发生的函数是faulty_write,该函数是属于faulty模块的([]括号显示的);十六进制的数据表明指令指针再该函数的4字节处,而函数本身是10(十六进制)字节长
- ②如果需要更多信息,调试栈可以告诉我们系统是如何达到故障点的。栈本身以十六进制形式打印,通过一些工作,我们可通过栈清单确定局部变量和函数参数的值。有经验的开发人员通过此类模式可有效地发现问题所在。例如,我们观察faulty_read产生的oops栈清单:
- 栈顶部的ffffffff就是导致故障产生的字符串的一部分
- 在x86架构上,用户空间的栈默认自0xc0000000向下。因此很容易联想到0xbfffda70可能是用户空间的栈地址,即传递给read调用的缓冲区地址,这个地址会在内核的调用链上重复向下传递
- 在x86结构上(默认情况下),内核空间起始于0xc0000000,故大于0xc0000000的值几乎肯定是内核空间的地址,等等
- ③最后,观察oops清单时还要记得前面讨论过的“slab毒剂”值。例如,如果我们获得的内核oops中包含有0xa5a5a5a5这样的地址,那几乎可以肯定的是,我们在某处忘记了初始化动态分配的内存
- ④CONFIG_KALLSYMS内核选项:只有在构造内核时打开了CONFIG_KALLSYMS选项,我们才能看到符号化的调用栈(就像上面那样);否则,我们只能看到裸的、十六进制的清单,因而只有通过其他途径解开这些数字的含义,才能弄清楚真正的调用栈
演示案例(faulty模块)
- faulty模块专为演示出错而编写的,模块中很多方法都提供了产生oops消息的案例
①write方法
- 下面是一台运行Linux 2.6内核的PC机上使用一个NULL指针时,就会导致下面这些信息被显示出来(这里最关心的就是指令指针(EIP),即出错指令的地址)
- 从图中可以看到这个消息是因为faulty模块的一个设备进行写操作而产生的,,faulty.c的write方法如下(可以看到这里引用了一个NULL指针,因为0绝不会是个合法的指针值,所以产生了错误,内核进入上面的oops消息状态,然后这个调用进程接着就被杀掉了):
②read方法
- 在faulty模块的read实现中,该模块还展示了更多有意思的错误状态,见下:
- 上面的read实现将一个字符串复制到一个局部变量,但不幸的是,字符串要比目标数组长。这样就会在该函数返回时因为缓冲区溢出而导致一个oops的产生。然而,由于return指令把指令指针带到了无法预期的地方,所以这种错误很难跟踪,所能获得的仅是如下的错误:
- 从上图我们只能看到调用栈的部分信息(无法看到vfs_read和faulty_read),内核说遇到一条“错误的EIP值(Bad EIP value)”,以及开头处列出的明显错误的地址(ffffffff)均说明内核栈已经被破坏