回溯线索显示了导致错误发生的函数调用链。这样我们就可以观察究竟发生了什么:机器处于空闲状态,正在执行idle循环,由cpu_idle()反复调用default_idle()。此时定时器中断产生了,它引起了对定时器的处理。tulip_timer()这个定时器处理函数被调用,而就是它引用了空指针。你甚至可以通过偏移量(像0x128/0x1c4这些出现在函数左侧的数字)找出导致问题的语句。
寄存器上下文信息可能同样有用,尽管使用起来不那么方便。如果你有函数的汇编代码,这些寄存器数据可以帮助你重建引发问题的现场。在寄存器中发现一个本不应该出现的数值可能会在黑暗中给你带来第一丝光明。在上面的例子中,我们可以查看是哪个寄存器包含了NULL(一个所有位都为零的数值)进而找出是函数的哪个变量的值不正常。一般在这种情况下问题往往是竞争引起的——本例中,是定时器和这块网卡驱动的其它部分之间的竞争。调试一个竞争条件往往很有挑战性。
ksymoops前面列举的oops可以说是一个经过解码的oops,因为内存地址都已经被转换成了它们对应的函数。下面是其未解码版本:
NIP C013A7F0 LR:C013A7F0 SP:C0685E00 REGS: c0905d10 TRAP 0700
Not tainted
MSR : 0089037 EE:1 PR: 0 FP:0 ME: 1 IR/DR : 11
TASK = c0712530[0] ‘swapper’ last syscall :120
GPR00:C013A7C0 C0295E00 C0231530 0000002F 00000001 C0380CB8 C0291B80 C02D0000
GPR08:000012A0 00000000 00000000 C0292AA0 4020A088 00000000 00000000 00000000
GPR16:00000000 0000000000000000 00000000 00000000 00000000 00000000 00000000
GPR24:00000000 00000005 00000000 00001032 C3F7C000 00000032 FFFFFFFF C3F7C1C0
call trace: [c013ab30] [c0020744] [c001b864] [c0007e80] [c00033c4] [c0007b84] [c0007bf8] [c0003ae8]
回溯线索中的地址需要被转化成有意义的符号名称才方便使用。这需要调用ksymoops命令,并且还必须提供编译内核时产生的System.map。如果你使用的是模块,你还需要一些模块信息。ksymoops通常会自行解析这些信息,所以一般你可以这样调用它:
ksymoops saved_oops.txt
然后该程序就会吐出解码版的oops。如果ksymoops无法找到默认位置上的信息,或者你想提供不同信息,该程序可以接受许多参数。它的使用手册上提供了许多说明信息,使用之前你最好先行查阅。
ksymoops一般会随你得到的Linux发行提供。
Kallsyms
谢天谢地,现在已经无须使用ksymoops工具了,这是一个了不起的工作。因为尽管开发者使用它的时候一般很少出现问题,但是最终用户常常会错误地匹配System.map文件或错误地对oops进行解码。
开发版的2.5内核引入了kallsyms特性,它可以通过定义CONFIG_KALLSYMS配置选项启用。该选项可以载入内核镜像对应的内存地址的符号的名称,所以内核可以打印解码好的跟踪线索。相应的,解码oops也不再需要System.map或者ksymoops工具了。硬币总有另一面,这样做会使内核变大一些,因为地址对应的符号名称必须始终驻留在内核所在的内存上。占用这些内存是值得的,至少在开发过程中如此。
