Kernel Panic (Kdump) 解析实例之一
原创Red-White-Blue 最后发布于2013-09-27 23:02:29 阅读数 4256 收藏
网络上已经有一些介绍如何配置kexec已产生kdump的文章。这里不重复介绍配置方法,而是介绍如何进行kdump文件解析。
下面介绍的都是Linux内核产生的kdump,利用crash这一工具解析。关于crash这个工具支持哪些命令,请参考crash命令自带的help。
1) 用crash命令打开vmcore
刚打开文件后,会输出一些关于软件本身以及vmcore文件的信息,基本用处不大。
[root@node-8 log]# crash vmlinux vmcore
gdb本身的版本等信息,省略。
2) 用bt命令查看导致panic的函数调用栈及当时的寄存器内容
注意,我们只需要看到[exception RIP...]以及其调用者即可。比如,下面的例子中,[exception RIP]对应#5,那么#0至#4只是系统在执行到引起panic的代码后的例行操作,本身对于解panic问题没有帮助。
RIP是当前要执行的指令的地址。
从这里我们可以看到的是:系统在执行scst_do_job_active这个函数调用的list_del()时发生了panic。具体地址是list_del+27。
crash> bt
PID: 60146 TASK: ffff880175c80ab0 CPU: 2 COMMAND: "abd"
#0 [ffff8801759a3ba0] machine_kexec at ffffffff810368e9
#1 [ffff8801759a3c00] crash_kexec at ffffffff810b8ad8
#2 [ffff8801759a3cd0] oops_end at ffffffff814b9c60
#3 [ffff8801759a3d00] die at ffffffff8101732b
#4 [ffff8801759a3d30] do_general_protection at ffffffff814b97d2
#5 [ffff8801759a3d60] general_protection at ffffffff814b8fa5
[exception RIP: list_del+27]
RIP: ffffffff812680eb RSP: ffff8801759a3e10 RFLAGS: 00010046
RAX: dead000000100100 RBX: ffff880093ec5950 RCX: 0000000000000030
RDX: 000000000000c78b RSI: 0000000000000000 RDI: ffff880093ec5950
RBP: ffff8801759a3e20 R8: ffff880093ec5950 R9: 0000000000000080
R10: 0000000000000000 R11: 0000000000000010 R12: 0000000000000000
R13: ffff880177076bd0 R14: ffff880177076bc8 R15: 0000000000000000
ORIG_RAX: ffffffffffffffff CS: 0010 SS: 0018
#6 [ffff8801759a3e28] scst_do_job_active at ffffffffa0422516 [scst]
#7 [ffff8801759a3e68] scst_cmd_thread at ffffffffa042282a [scst]
#8 [ffff8801759a3ee8] kthread at ffffffff810916c6
#9 [ffff8801759a3f48] kernel_thread at ffffffff810141ca
3) 反汇编确定哪个寄存器是入口参数
从bt命令的结果可以看出,导致panic的指令是list_del+27。 list_del的源代码如下(kernel 2.6.32):
44 void list_del(struct list_head *entry)
45 {
46 WARN(entry->prev->next != entry,
47 "list_del corruption. prev->next should be %p, "
48 "but was %p\n", entry, entry->prev->next);
49 WARN(entry->next->prev != entry,
50 "list_del corruption. next->prev should be %p, "
51 "but was %p\n", entry, entry->next->prev);
52 __list_del(entry->prev, entry->next);
53 entry->next = LIST_POISON1;
54 entry->prev = LIST_POISON2;
55 }
我们用dis(disassemble)命令对list_del这个内核函数进行反汇编(绿色为注释,==>代表赋值。):
crash> dis list_del
0xffffffff812680d0 <list_del>: push %rbp
0xffffffff812680d1 <list_del+1>: mov %rsp,%rbp ;前两句是标准的函数入口操作
0xffffffff812680d4 <list_del+4>: push %rbx ;%rbx是 callee-save register,使用前先压栈保存.
0xffffffff812680d5 <list_del+5>: mov %rdi,%rbx ;%rdi ==> %rbx; 因为%rdi不是 callee-save register。%rdi的对应如可参数entry
0xffffffff812680d8 <list_del+8>: sub $0x8,%rsp ;栈顶减8,给WARN使用的局部变量预留空间
0xffffffff812680dc <list_del+12>: mov 0x8(%rdi),%rax ;entry->prev ==> %rax
0xffffffff812680e0 <list_del+16>: mov (%rax),%r8 ;entry->prev->next ==> %r8
0xffffffff812680e3 <list_del+19>: cmp %r8,%rdi ;比较 entry->prev->next 和 entry
0xffffffff812680e6 <list_del+22>: jne 0xffffffff81268121 <list_del+81> ; 如果二者不相等,则跳转,对应WARN(entry->prev->next != entry,...);
0xffffffff812680e8 <list_del+24>: mov (%rbx),%rax ;entry->next ==> %rax。注意,<list_del+5>这一行已经把entry赋值给%rbx。使用%rbx而不是%rdi,因为可能从别的地方跳转到这一行,%rdi已经失效。
0xffffffff812680eb <list_del+27>: mov 0x8(%rax),%r8; entry->next->prev ==> %r8,由于RIP显示就panic在这一句,而这一句对应了WARN(entry->next->prev != entry,...); 说明entry->next不是合法的指针。
根据上面的汇编分析,入口参数(entry)在rbx里面保存了。rbx的值为ffff880093ec5950。我们看看entry的内容,从而就能知道entry->next是什么。
crash> kmem ffff880093ec5950 ; 获取%rbx对应地址的内存分配信息,是一个scst_cmd结构体,与上面的bt信息吻合
CACHE NAME OBJSIZE ALLOCATED TOTAL SLABS SSIZE
ffff880195443240 scst_cmd 528 586 1155 165 4k
SLAB MEMORY TOTAL ALLOCATED FREE
ffff880093ec50c0 ffff880093ec5110 7 3 4
FREE / [ALLOCATED]
ffff880093ec5950 (cpu 4 cache)
PAGE PHYSICAL MAPPING INDEX CNT FLAGS
ffffea00024fb140 93ec5000 0 0 1 20000000000080
由于 scst_cmd的第一个成员就是list_head,直接把ffff880093ec5950作为list_head显示
crash> struct list_head ffff880093ec5950
struct list_head {
next = 0xdead000000100100,
prev = 0xffff880177076bd0
}
参见内核中的注释,#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
LIST_POISON1 即为 0xdead000000100100
从上面的内容可以看出,该list_head的内容是有问题的,其next表明它已经被从list上删除,但是其prev表明它还在list上。因此,可以认为程序没有保障list_head操作的原子性导致的。
猜测是list插入与删除并发引起的问题。具体的原因是由于锁的使用问题,就不在这里讨论了。