内核宕机不要panic,我们有gdb,通过dump出来信息可以快速定位出出错的地方。下面就以一个实际遇到的例子描述一下怎么通过gdb找到实际出错的代码行。
Unable to handle kernel paging request for data at address 0x000001d0
Faulting instruction address: 0xc0220820
Oops: Kernel access of bad area, sig: 11 [#1]
MPC85xx CDS
Modules linked in: hsl linux_bcm_diag_full(P) linux_uk_proxy(P) linux_kernel_bde(P) wdtDrv pciDrv i2cDrv flashDrv cpuDrv
NIP: c0220820 LR: c0220818 CTR: c02209a4
REGS: dde67b20 TRAP: 0300 Tainted: P (2.6.27.21)
MSR: 00029000 <EE,ME> CR: 24000824 XER: 00000000
DEAR: 000001d0, ESR: 00000000
TASK = decea500[940] 'nsm' THREAD: dde66000
GPR00: 00000001 dde67bd0 decea500 00000001 00000001 decea538 00000078 00000000
GPR08: deceae80 00000000 83d0da00 c02209a4 e8669fbb 10219d44 00000290 ffffffff
GPR16: 00000001 00000000 100d4464 100e7db4 00000000 00000008 dde67ddc dde67dfc
GPR24: dde67ddc dde67dfc dde67dc0 e3970000 00000001 00000000 ddac8018 00000000
NIP [c0220820] rollback_registered+0x2c/0x130
LR [c0220818] rollback_registered+0x24/0x130
Call Trace:
[dde67bd0] [00000008] 0x8 (unreliable)
[dde67be0] [c022094c] unregister_netdevice+0x28/0x80
[dde67bf0] [c02209c4] unregister_netdev+0x20/0x38
[dde67c10] [e393661c] hsl_eth_drv_destroy_netdevice+0x10/0x24 [hsl]
[dde67c20] [e3938490] hsl_os_l3_if_unconfigure+0x20/0x34 [hsl]
[dde67c30] [e393aa94] hsl_ifmgr_L3_delete2+0xf0/0x2b4 [hsl]
[dde67c50] [e394103c] hsl_ifmgr_L3_unregister+0x60/0xfc [hsl]
[dde67c70] [e3926004] hsl_msg_recv_if_delete_svi+0x54/0x15c [hsl]
[dde67c90] [e391feb8] hsl_sock_process_msg+0x260/0x7a8 [hsl]
[dde67ca0] [e392046c] _hsl_sock_sendmsg+0x6c/0xac [hsl]
[dde67cc0] [c0211fc0] sock_sendmsg+0xac/0xe4
[dde67db0] [c02121cc] sys_sendmsg+0x1d4/0x284
[dde67f00] [c0212d40] sys_socketcall+0xe8/0x200
[dde67f40] [c000df5c] ret_from_syscall+0x0/0x3c
Instruction dump:
4bffff48 9421fff0 7c0802a6 3d20c039 93e1000c 7c7f1b78 90010014 800954d8
0f000000 4800a101 2f830000 419e00e4 <813f01d0> 2f890000 409e0034 3c60c033
---[ end trace fc1818f8d9535ae2 ]---
首先介绍一下ppc(powerpc)的寄存器:
通用寄存器:
r0 在函数开始(function prologs)时使用。
r1 堆栈指针,相当于ia32架构中的esp寄存器,idapro把这个寄存器反汇编标识为sp。
r2 内容表(toc)指针,idapro把这个寄存器反汇编标识为rtoc。系统调用时,它包含系统调用号。
r3 作为第一个参数和返回地址。
r4-r10 函数或系统调用开始的参数。
r11 用在指针的调用和当作一些语言的环境指针。
r12 它用在异常处理和glink(动态连接器)代码。
r13 保留作为系统线程ID。
r14-r31 作为本地变量,非易失性。
专用寄存器:
lr 链接寄存器,它用来存放函数调用结束处的返回地址。
ctr 计数寄存器,它用来当作循环计数器,会随特定转移操作而递减。
xer 定点异常寄存器,存放整数运算操作的进位以及溢出信息。
msr 机器状态寄存器,用来配置微处理器的设定。
cr 条件寄存器,它分成8个4位字段,cr0-cr7,它反映了某个算法操作的结果并且提供条件分支的机制。
寄存器r1、r14-r31是非易失性的,这意味着它们的值在函数调用过程保持不变。寄存器r2也算非易失性,但是只有在调用函数在调用后必须恢复它的值时才被处理。
ppc使用了LR寄存器(Link Register)来完成:在bl指令跳转前,下条指令的地址会被保存到LR,函数返回时候调用 blr,系统会跳转到LR所表示的地址,完成返回。
------------------------------分割线--------------------------------------------------
我们开始通过gdb查找宕机的地方:
1. 首先进入内核的编译目录,保证代码与出错的内核一直,没有编译的话要先编译,编译完之后,在当前目录就会有vmlinux,这个是未压缩过的内核bin文件,用gdb运行它(这里是powerpc平台):
gdb4ppc ./vmlinux
2. NIP显示的下一条指令地址(0xc0220820),使用list 指令显示出对应的函数:
(gdb) list *0xc0220820
0xc0220820 is in rollback_registered (net/core/dev.c:4051).
4046 {
4047 BUG_ON(dev_boot_phase);
4048 ASSERT_RTNL();
4049
4050 /* Some devices call without registering for initialization unwind. */
4051 if (dev->reg_state == NETREG_UNINITIALIZED) {
4052 printk(KERN_DEBUG "unregister_netdevice: device %s/%p never "
4053 "was registered\n", dev->name, dev);
4054
4055 WARN_ON(1);
3. 反汇编这个函数 (disassemble /r表示原始的16进制格式的汇编)
(gdb) disassemble /r rollback_registered
Dump of assembler code for function rollback_registered:
0xc02207f4 <+0>: 94 21 ff f0 stwu r1,-16(r1)
0xc02207f8 <+4>: 7c 08 02 a6 mflr r0
0xc02207fc <+8>: 3d 20 c0 39 lis r9,-16327
0xc0220800 <+12>: 93 e1 00 0c stw r31,12(r1)
0xc0220804 <+16>: 7c 7f 1b 78 mr r31,r3
0xc0220808 <+20>: 90 01 00 14 stw r0,20(r1)
0xc022080c <+24>: 80 09 54 d8 lwz r0,21720(r9)
0xc0220810 <+28>: 0f 00 00 00 twnei r0,0
0xc0220814 <+32>: 48 00 a1 01 bl 0xc022a914 <rtnl_is_locked>
0xc0220818 <+36>: 2f 83 00 00 cmpwi cr7,r3,0
0xc022081c <+40>: 41 9e 00 e4 beq cr7,0xc0220900 <rollback_registered+268>
0xc0220820 <+44>: 81 3f 01 d0 lwz r9,464(r31)
0xc0220824 <+48>: 2f 89 00 00 cmpwi cr7,r9,0
0xc0220828 <+52>: 40 9e 00 34 bne cr7,0xc022085c <rollback_registered+104>
可以看到汇编与dump信息里面的Instruction dump完全对得上,地址0xc022082
对应汇编 81 3f 01 d0, dump信息里面特意用尖括号标识出来了,看来就死在这一条指令了。
4. 找到对应的代码行(disassemble /m 将源码嵌入到汇编里面),分析原因。
(gdb) disassemble /m rollback_registered
Dump of assembler code for function rollback_registered:
4046 {
0xc02207f4 <+0>: stwu r1,-16(r1)
0xc02207f8 <+4>: mflr r0
0xc0220800 <+12>: stw r31,12(r1)
0xc0220804 <+16>: mr r31,r3
0xc0220808 <+20>: stw r0,20(r1)
4047 BUG_ON(dev_boot_phase);
0xc02207fc <+8>: lis r9,-16327
0xc022080c <+24>: lwz r0,21720(r9)
0xc0220810 <+28>: twnei r0,0
4048 ASSERT_RTNL();
0xc0220814 <+32>: bl 0xc022a914 <rtnl_is_locked>
0xc0220818 <+36>: cmpwi cr7,r3,0
0xc022081c <+40>: beq cr7,0xc0220900 <rollback_registered+268>
0xc0220900 <+268>: lis r4,-16333
0xc0220904 <+272>: lis r3,-16334
0xc0220908 <+276>: addi r4,r4,-16240
0xc022090c <+280>: li r5,4048
0xc0220910 <+284>: addi r3,r3,-3816
0xc0220914 <+288>: crclr 4*cr1+eq
0xc0220918 <+292>: bl 0xc0027ca0 <printk>
0xc022091c <+296>: bl 0xc0006cfc <dump_stack>
0xc0220920 <+300>: b 0xc0220820 <rollback_registered+44>
4049
4050 /* Some devices call without registering for initialization unwind. */
4051 if (dev->reg_state == NETREG_UNINITIALIZED) {
0xc0220820 <+44>: lwz r9,464(r31)
0xc0220824 <+48>: cmpwi cr7,r9,0
0xc0220828 <+52>: bne cr7,0xc022085c <rollback_registered+104>
这样我们就找到对应的代码语句是 rollback_registered中
if (dev->reg_state == NETREG_UNINITIALIZED) {
这一行,指令是lwz r9,464(r31),R31存放的是dev指针,我们先确认一下偏移464的地方就是
dev->reg_state
(gdb) p &((struct net_device *)0)->reg_state
$1 = (enum {...} *) 0x1d0
0x1d0也就是十进制的464,dump信息里的R31寄存器是0,即dev是NULL,这也就是这条指令导致产生访问非法地址0x000001d0的原因:
Unable to handle kernel paging request for data at address 0x000001d0