Oops信息及栈回溯(2011-10-2510:16)
标签:信息分类:linux
1.Oops信息来源及格式
Oops这个单词含义为“惊讶”,当内核出错时(比如访问非法地址)打印出来的信息被称为Oops信息。
Oops信息包含以下几部分内容:
(1)一段文本描述信息。
比如类似“UnabletohandlekernelNULLpointerdereferenceatvirtualaddress00000000"的信息,他说明
了发生的是哪类错误。
(2)Oops信息的序号。
比如是第几次等。这些信息与下面类似,括号内的数据表示序号。
1.Internalerror:Oops:806[#1]
(3)内核中加载的模块名称,也可能没有,以下面字样开头。
1.Moduleslinkedin:
(4)发生错误的CPU的序号,对于单处理器系统,序号为0,如:
1.CPU:0Nottainted(2.6.22.6#36)
(5)发生错误时CPU的各个寄存器值。
(6)当前进程的名字及进程ID,比如:
1.Processswapper(pid:1,stacklimit=0xc0480258)
这并不是说发生错误的是这个进程,而是表示发生错误时,当前进程是它。错误可能发生在内核代码、
驱动程序,也可能就是这个进程的错误。
(7)栈信息。
(8)栈回溯信息,可以从中看出函数调用关系,形式如下:
1.Backtrace:
2.[](s3c2410fb_probe+0x0/0x560)from[](platform_drv_probe+0x20/0x24)
3.......
(9)出错指令附近的指令机器码,比如(出错指令在小括号内):
1.Code:e24cb004e24dd010e59f34e0e3a07000(e5873000)
2.配置内核使Oops信息的栈回溯信息更直观
Linux2.26.32自身具备的调试功能,可以使打印出的Oops信息更直观。通过Oops信息中PC寄存器
的值可以知道出错指令的地址,通过栈回溯信息可以知道出错时的函数调用关系,根据这两点可以很
快定位错误。
要让内核出错时能够打印栈回溯信息,编译内核时要增加“-fno-omit-frame-pointer"选项,这可以通过
配置CONFIG_FRAME_POINTER来实现。查看内核目录下的配置文件.config,确保
CONFIG_FRAME_POINTER已经被定义,如果没有,执行“makemenuconfig”命令重新配置内核。
CONFIG_FRAME_POINTER有可能被其他配置项目自动选上。
3使用Oops信息调试内核的实例
(1)获得Oops信息
本小节故意修改LCD驱动程序drivers/video/s3c2410fb.c,加入错误代码:在s3c2410fb_
probe函数的开头增加下面两条代码:
1.intptest=NULL;
2.ptest=0x1234;
重新编译内核,启动后会出错并打印出如下Oops信息:
1.UnabletohandlekernelNULLpointerdereferenceatvirtualaddress00000000
2.pgd=c0004000
3.[00000000]pgd=00000000
4.Internalerror:Oops:805[#1]
5.lastsysfsfile:
6.Moduleslinkedin:
7.CPU:0Nottainted(2.6.32#24)
8.PCisats3c2410fb_probe+0xc/0x18
9.LRisatplatform_drv_probe+0x18/0x1c
10.pc:[]lr:[]psr:a0000013
11.sp:c3823f30ip:c3842e7cfp:00000000
12.r10:00000000r9:00000000r8:c0445008
13.r7:c38a0840r6:c0440d18r5:c042d178r4:00000000
14.r3:00001234r2:00000000r1:00000000r0:c042d170
15.Flags:NzCvIRQsonFIQsonModeSVC_32ISAARMSegmentkernel
16.Control:c000717fTable:30004000DAC:00000017
17.Processswapper(pid:1,stacklimit=0xc3822270)
18.Stack:(0xc3823f30to0xc3824000)
19.3f20:00000000c01d2e7cc0440d18c042d178
20.3f40:0000000cc042d178c042d1acc0440d18c38a0840c01d306800000000c01d300c
21.3f60:c0440d18c01d24e8c3804938c38473300000000000000000c0440d18c01d1d48
22.3f80:c03a1506c03a150600000001c0022dd400000000c0440d18c0018f8400000000
23.3fa0:00000000c01d3334c0022dd40000000000000000c0018f8400000000c0018f90
24.3fc0:c0022dd4c002e37cc0018f84c03c5ab3c0457880c0022fc8c0022dd400000000
25.3fe0:000000000000000000000000c00083f800000000c002fe540000000000000000
26.[](s3c2410fb_probe+0xc/0x18)from[](platform_drv_probe+0x18/0x1c)
27.[](platform_drv_probe+0x18/0x1c)from[](driver_probe_device+0x148/0x2d8)
28.[](driver_probe_device+0x148/0x2d8)from[](__driver_attach+0x5c/0x7c)
29.[](__driver_attach+0x5c/0x7c)from[](bus_for_each_dev+0x48/0x78)
30.[](bus_for_each_dev+0x48/0x78)from[](bus_add_driver+0xe0/0x288)
31.[](bus_add_driver+0xe0/0x288)from[](driver_register+0xa4/0x130)
32.[](driver_register+0xa4/0x130)from[](s3c2410fb_init+0xc/0x28)
33.[](s3c2410fb_init+0xc/0x28)from[](do_one_initcall+0x5c/0x1b4)
34.[](do_one_initcall+0x5c/0x1b4)from[](kernel_init+0x94/0x10c)
35.[](kernel_init+0x94/0x10c)from[](kernel_thread_exit+0x0/0x8)
36.Code:eafffe97e3a02000e59f3008e1a01002(e5823000)
37.---[endtraceda227214a82491b7]---
38.swapperusedgreateststackdepth:5792bytesleft
39.Kernelpanic-notsyncing:Attemptedtokill
40.[](unwind_backtrace+0x0/0x150)from[](panic+0x40/0x120)
41.[](panic+0x40/0x120)from[](do_exit+0x64/0x5f4)
42.[](do_exit+0x64/0x5f4)from[](die+0x15c/0x180)
43.[](die+0x15c/0x180)from[](__do_kernel_fault+0x64/0x74)
44.[](__do_kernel_fault+0x64/0x74)from[](do_page_fault+0x1b8/0x1cc)
45.[](do_page_fault+0x1b8/0x1cc)from[](do_DataAbort+0x34/0x94)
46.[](do_DataAbort+0x34/0x94)from[](__dabt_svc+0x40/0x60)
47.Exceptionstack(0xc3823ee8to0xc3823f30)
48.3ee0:c042d17000000000000000000000123400000000c042d178
49.3f00:c0440d18c38a0840c0445008000000000000000000000000c3842e7cc3823f30
50.3f20:c01d3f88c0018f78a0000013ffffffff
51.[](__dabt_svc+0x40/0x60)from[](s3c2410fb_probe+0xc/0x18)
52.[](s3c2410fb_probe+0xc/0x18)from[](platform_drv_probe+0x18/0x1c)
53.[](platform_drv_probe+0x18/0x1c)from[](driver_probe_device+0x148/0x2d8)
54.[](driver_probe_device+0x148/0x2d8)from[](__driver_attach+0x5c/0x7c)
55.[](__driver_attach+0x5c/0x7c)from[](bus_for_each_dev+0x48/0x78)
56.[](bus_for_each_dev+0x48/0x78)from[](bus_add_driver+0xe0/0x288)
57.[](bus_add_driver+0xe0/0x288)from[](driver_register+0xa4/0x130)
58.[](driver_register+0xa4/0x130)from[](s3c2410fb_init+0xc/0x28)
59.[](s3c2410fb_init+0xc/0x28)from[](do_one_initcall+0x5c/0x1b4)
60.[](do_one_initcall+0x5c/0x1b4)from[](kernel_init+0x94/0x10c)
61.[](kernel_init+0x94/0x10c)from[](kernel_thread_exit+0x0/0x8)
(2)分析Oops信息
?确出错原因。
由出错信息“UnabletohandlekernelNULLpointerdereferenceatvirtualaddress00000000”可知内核是
因为非法地址访问出错,使用了空指针。
?根据栈回溯信息找出函数调用关系。
内核崩溃时,可以从pc寄存器得知崩溃发生时的函数、出错指令。但是很多情况下,错误有可能是它
的调用者引入的,所以找出函数的调用关系也很重要。
部分栈回溯信息如下:
1.[](s3c2410fb_probe+0xc/0x18)from[](platform_drv_probe+0x18/0x1c)
这行信息分为两部分,表示后面的platform_drv_probe函数调用了前面的s3c2410fb_probe函数。
前半部含义为:“c0018f78”是s3c2410fb_probe函数首地址偏移0xc的地址,这个函数大小为0x18。
后半部含义为:“c01d3f88”是platform_drv_probe函数首地址偏移0x18的地址,这个函数大小为
0x1c。
另外,后半部的“[]”表示s3c2410fb_probe执行后的返回地址。
对于类似下面的栈回溯信息,其中是r8~r4表示driver_probe_device函数刚被调用时这
些寄存器的值。
从上面的栈回溯信息可以知道内核出错时的函数调用关系如下,最后在s3c2410fb_probe函数内部崩
溃。
1.do_exit->
2.kernel_init->
3.do_one_initcall->
4.s3c2410fb_init->
5.driver_register->
6.bus_add_driver->
7.bus_for_each_dev->
8.__driver_attach->
9.driver_probe_device->
10.platform_drv_probe->
11.s3c2410fb_probe
?根据pc寄存器的值确定出错位置。
上述Oops信息中出错时的寄存器值如下:
1.PCisats3c2410fb_probe+0xc/0x18
2.LRisatplatform_drv_probe+0x18/0x1c
3.pc:[]lr:[]psr:a0000013
4.sp:c3823f30ip:c3842e7cfp:00000000
5.r10:00000000r9:00000000r8:c0445008
6.r7:c38a0840r6:c0440d18r5:c042d178r4:00000000
7.r3:00001234r2:00000000r1:00000000r0:c042d170
"PCisats3c2410fb_probe+0xc/0x18"表示出错指令为s3c2410fb_probe函数中偏移为0xc的指令。
"pc:[]"表示出错指令的地址为c0018f78(十六进制)。
?结合内核源代码和反汇编代码定位问题。
先生成内核的反汇编代码vmlinux.dis,执行以下命令:
1.$cd~/Documents/myTest/kernel/linux-2.6.32
2.$arm-linux-objdump-Dvmlinux>vmlinux.dis
出错地址c0018f78附近的部分汇编代码如下:
1.c0018f64:
2.c0018f64:e3a01001movr1,#1;0x1
3.c0018f68:eafffe97bc00189cc
4.
5.c0018f6c:
6.c0018f6c:e3a02000movr2,#0;0x0
7.c0018f70:e59f3008ldrr3,[pc,#8];c0018f80
8.c0018f74:e1a01002movr1,r2
9.c0018f78:e5823000strr3,[r2]<===========出错指令
10.c0018f7c:eafffe92bc00189cc
11.c0018f80:00001234.word0x00001234
12.
13.c0018f84:
14.c0018f84:e92d4010push{r4,lr}
15.c0018f88:e59f0014ldrr0,[pc,#20];c0018fa4
16.c0018f8c:eb06ed06blc01d43ac
17.c0018f90:e3500000cmpr0,#0;0x0
18.c0018f94:18bd8010popne{r4,pc}
19.c0018f98:e59f0008ldrr0,[pc,#8];c0018fa8
20.c0018f9c:e8bd4010pop{r4,lr}
21.c0018fa0:ea06ed01bc01d43ac
22.c0018fa4:c0440d04.word0xc0440d04
出错指令为“strr3,[r2]”,它把r3寄存器的值放到内存中,内存地址为r2寄存器的值。
根据Oops信息中的寄存器值可知:r3为00001234,r2为0。0地址不可访问,所以出错。
1.staticint__inits3c2410fb_probe(structplatform_devicepdev)
2.{
3.//addforkernelpanic--begin
4.intptest=NULL;
5.ptest=0x1234;
6.//addforkernelpanic--end
7.returns3c24xxfb_probe(pdev,DRV_S3C2410);
8.}
结合反汇编代码,很容易知道是“ptest=0x1234;”导致错误,其中的ptest为空。对于大多数情况,从反
汇编代码定位到C代码并不会如此容易,这需要较强的阅读汇编程序的能力。通过栈回溯信息知道函
数的调用关系,这已经可以帮助定位很多问题了。
4.使用Oops的栈信息手工进行栈回溯
前面说过,从Oops信息的pc寄存器值可知得知崩溃发生时的函数、出错指令。但是错误有可能是
它的调用者引入的,所以还要找出函数的调用关系。由于内核配置了CONFIG_FRAME_POINTER,当
出现Oops信息时,会打印栈回溯信息。如果内核没有配置CONFIG_FRAME_POINTER,这时可以自
己分析栈信息,找到函数的调用关系。
(1)栈的作用
一个程序包含代码段、数据段、BSS段、堆、栈;其中数据段用来中存储初始值不为0的全局数据,BSS
段用来存储初始值为0的全局数据,堆用于动态内存分配,栈用于实现函数调用、存储局部变量。被调
用函数在执行之前,它会将一些寄存器的值保存在栈中,其中包括返回地址寄存器lr。如果知道了所保
存的lr寄存的值,那么就可以知道它的调用者是谁。在栈信息中,一个函数一个函数地往上找出所有
保存的lr值,就可以知道各个调用函数,这就是栈回溯的原理。
(2)栈回溯实例分析
仍以前面的LCD驱动程序为例,使用上面的Oops信息的栈信息进行分析,栈信息如下:
1.Stack:(0xc3823f30to0xc3824000)
2.3f20:00000000c01d2e7cc0440d18c042d178
3.3f40:0000000cc042d178c042d1acc0440d18c38a0840c01d306800000000c01d300c
4.3f60:c0440d18c01d24e8c3804938c38473300000000000000000c0440d18c01d1d48
5.3f80:c03a1506c03a150600000001c0022dd400000000c0440d18c0018f8400000000
6.3fa0:00000000c01d3334c0022dd40000000000000000c0018f8400000000c0018f90
7.3fc0:c0022dd4c002e37cc0018f84c03c5ab3c0457880c0022fc8c0022dd400000000
8.3fe0:000000000000000000000000c00083f800000000c002fe540000000000000000
?根据pc寄存器值找到第一个函数,确定它的栈大小,确定调用函数。
从Oops信息
1.PCisats3c2410fb_probe+0xc/0x18
2.LRisatplatform_drv_probe+0x18/0x1c
3.pc:[]lr:[]psr:a0000013
4.sp:c3823f30ip:c3842e7cfp:00000000
5.r10:00000000r9:00000000r8:c0445008
6.r7:c38a0840r6:c0440d18r5:c042d178r4:00000000
7.r3:00001234r2:00000000r1:00000000r0:c042d170
可知pc值为c0018f78,使用它在内核反汇编程序vmlinux.dis中可以知道它位于s3c2410fb_probe
函数内。
1.c0018f6c:
2.c0018f6c:e3a02000movr2,#0;0x0
3.c0018f70:e59f3008ldrr3,[pc,#8];c0018f80
4.c0018f74:e1a01002movr1,r2
5.c0018f78:e5823000strr3,[r2]<===========出错指令
6.c0018f7c:eafffe92bc00189cc
7.c0018f80:00001234.word0x00001234
lr存放的是函数返回值地址,为c01d3f88,根据这个地址,搜索内核反汇编程序vmlinux.dis,可知
它位于:
1.c01d3f70:
2.c01d3f70:e92d4010push{r4,lr}
3.c01d3f74:e1a03000movr3,r0
4.c01d3f78:e5933044ldrr3,[r3,#68]
5.c01d3f7c:e2400008subr0,r0,#8;0x8
6.c01d3f80:e1a0e00fmovlr,pc
7.c01d3f84:e513f014ldrpc,[r3,#-20]
8.c01d3f88:e8bd8010pop{r4,pc}
也就是说,函数s3c2410fb_probe()被platform_drv_probe()调用。再看platform_drv_probe()的反汇编
代码,期中
1.c01d3f70:e92d4010push{r4,lr}
栈中存放的是r4,lr
对应
1.Stack:(0xc3823f30to0xc3824000)
2.3f20:00000000c01d2e7cc0440d18c042d178
3.3f40:0000000cc042d178c042d1acc0440d18c38a0840c01d306800000000c01d300c
4.3f60:c0440d18c01d24e8c3804938c38473300000000000000000c0440d18c01d1d48
5.3f80:c03a1506c03a150600000001c0022dd400000000c0440d18c0018f8400000000
6.3fa0:00000000c01d3334c0022dd40000000000000000c0018f8400000000c0018f90
7.3fc0:c0022dd4c002e37cc0018f84c03c5ab3c0457880c0022fc8c0022dd400000000
8.3fe0:000000000000000000000000c00083f800000000c002fe540000000000000000
其中,lr对应的值为c01d2e7c,用此值检索vmlinux.dis,位于
1.c01d2d34:
2.c01d2d34:e92d40f7push{r0,r1,r2,r4,r5,r6,r7,lr}
3.c01d2d38:e5d13028ldrbr3,[r1,#40]
4.c01d2d3c:e1a05001movr5,r1
5.....
6.c01d2e7c:e2504000subsr4,r0,#0;0x0
7.c01d2e80:1a000016bnec01d2ee0
8.c01d2e84:e1a00005movr0,r5
9.c01d2e88:ebffff6eblc01d2c48
可知,platform_drv_probe()被driver_probe_device()调用,再用同样的方法就可以找出所有函数调用关
系。
|
|