来自:ytuglt > 馆藏分类
配色: 字号:
Oops信息及栈回溯
2013-01-23 | 阅:  转:  |  分享 
  
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()调用,再用同样的方法就可以找出所有函数调用关

系。



献花(0)
+1
(本文系ytuglt首藏)