Oops信息及棧回溯

1. Oops信息來源及格式
Oops這個單詞含義爲「驚訝」,當內核出錯時(好比訪問非法地址)打印出來的信息被稱爲Oops信息。
Oops信息包含如下幾部份內容:
(1)一段文本描述信息。
      好比相似「Unable to handle kernel NULL pointer dereference at virtual address 00000000"的信息,他說明了發生的是哪類錯誤。
(2)Oops信息的序號。
      好比是第幾回等。這些信息與下面相似,括號內的數據表示序號。

linux

  1. Internal error: Oops: 806 [#1]
(3)內核中加載的模塊名稱,也可能沒有,如下面字樣開頭。
  1. Modules linked in:
(4)發生錯誤的CPU的序號,對於單處理器系統,序號爲0,如:
  1. CPU: 0 Not tainted (2.6.22.6 #36)
(5)發生錯誤時CPU的各個寄存器值。
(6)當前進程的名字及進程ID,好比:

  1. Process swapper (pid: 1, stack limit = 0xc0480258)
這並非說發生錯誤的是這個進程,而是表示發生錯誤時,當前進程是它。錯誤可能發生在內核代碼、驅動程序,也可能就是這個進程的錯誤。
(7)棧信息。
(8)棧回溯信息,能夠從中看出函數調用關係,形式以下:

  1. Backtrace:
  2. [<c001a6f4>] (s3c2410fb_probe+0x0/0x560) from [<c01bf4e8>] (platform_drv_probe+0x20/0x24)
  3. ......
(9)出錯指令附近的指令機器碼,好比(出錯指令在小括號內):
  1. Code: e24cb004 e24dd010 e59f34e0 e3a07000 (e5873000)
2. 配置內核使Oops信息的棧回溯信息更直觀
Linux 2.26.32自身具有的調試功能,可使打印出的Oops信息更直觀。經過Oops信息中PC寄存器的值能夠知道出錯指令的地址經過棧回溯信息能夠知道出錯時的函數調用關係,根據這兩點能夠很快定位錯誤。
要讓內核出錯時可以打印棧回溯信息,編譯內核時要增長「-fno-omit-frame-pointer"選項,這能夠經過配置CONFIG_FRAME_POINTER來實現。查看內核目錄下的配置文件.config,確保
CONFIG_FRAME_POINTER已經被定義,若是沒有,執行「make menuconfig」命令從新配置內核。 CONFIG_FRAME_POINTER有可能被其餘配置項目自動選上。

3使用Oops信息調試內核的實例
(1)得到Oops信息
本小節故意修改 LCD 驅動程序 drivers/video/s3c2410fb.c,加入錯誤代碼:在 s3c2410fb_
probe 函數的開頭增長下面兩條代碼:
  1. int *ptest = NULL;
  2. *ptest = 0x1234;
從新編譯內核,啓動後會出錯並打印出以下 Oops 信息:
  1. Unable to handle kernel NULL pointer dereference at virtual address 00000000
  2. pgd = c0004000
  3. [00000000] *pgd=00000000
  4. Internal error: Oops: 805 [#1]
  5. last sysfs file:
  6. Modules linked in:
  7. CPU: 0 Not tainted (2.6.32 #24)
  8. PC is at s3c2410fb_probe+0xc/0x18
  9. LR is at platform_drv_probe+0x18/0x1c
  10. pc : [<c0018f78>] lr : [<c01d3f88>] psr: a0000013
  11. sp : c3823f30 ip : c3842e7c fp : 00000000
  12. r10: 00000000 r9 : 00000000 r8 : c0445008
  13. r7 : c38a0840 r6 : c0440d18 r5 : c042d178 r4 : 00000000
  14. r3 : 00001234 r2 : 00000000 r1 : 00000000 r0 : c042d170
  15. Flags: NzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
  16. Control: c000717f Table: 30004000 DAC: 00000017
  17. Process swapper (pid: 1, stack limit = 0xc3822270)
  18. Stack: (0xc3823f30 to 0xc3824000)
  19. 3f20: 00000000 c01d2e7c c0440d18 c042d178
  20. 3f40: 0000000c c042d178 c042d1ac c0440d18 c38a0840 c01d3068 00000000 c01d300c
  21. 3f60: c0440d18 c01d24e8 c3804938 c3847330 00000000 00000000 c0440d18 c01d1d48
  22. 3f80: c03a1506 c03a1506 00000001 c0022dd4 00000000 c0440d18 c0018f84 00000000
  23. 3fa0: 00000000 c01d3334 c0022dd4 00000000 00000000 c0018f84 00000000 c0018f90
  24. 3fc0: c0022dd4 c002e37c c0018f84 c03c5ab3 c0457880 c0022fc8 c0022dd4 00000000
  25. 3fe0: 00000000 00000000 00000000 c00083f8 00000000 c002fe54 00000000 00000000
  26. [<c0018f78>] (s3c2410fb_probe+0xc/0x18) from [<c01d3f88>] (platform_drv_probe+0x18/0x1c)
  27. [<c01d3f88>] (platform_drv_probe+0x18/0x1c) from [<c01d2e7c>] (driver_probe_device+0x148/0x2d8)
  28. [<c01d2e7c>] (driver_probe_device+0x148/0x2d8) from [<c01d3068>] (__driver_attach+0x5c/0x7c)
  29. [<c01d3068>] (__driver_attach+0x5c/0x7c) from [<c01d24e8>] (bus_for_each_dev+0x48/0x78)
  30. [<c01d24e8>] (bus_for_each_dev+0x48/0x78) from [<c01d1d48>] (bus_add_driver+0xe0/0x288)
  31. [<c01d1d48>] (bus_add_driver+0xe0/0x288) from [<c01d3334>] (driver_register+0xa4/0x130)
  32. [<c01d3334>] (driver_register+0xa4/0x130) from [<c0018f90>] (s3c2410fb_init+0xc/0x28)
  33. [<c0018f90>] (s3c2410fb_init+0xc/0x28) from [<c002e37c>] (do_one_initcall+0x5c/0x1b4)
  34. [<c002e37c>] (do_one_initcall+0x5c/0x1b4) from [<c00083f8>] (kernel_init+0x94/0x10c)
  35. [<c00083f8>] (kernel_init+0x94/0x10c) from [<c002fe54>] (kernel_thread_exit+0x0/0x8)
  36. Code: eafffe97 e3a02000 e59f3008 e1a01002 (e5823000)
  37. ---[ end trace da227214a82491b7 ]---
  38. swapper used greatest stack depth: 5792 bytes left
  39. Kernel panic - not syncing: Attempted to kill
  40. [<c00352ac>] (unwind_backtrace+0x0/0x150) from [<c0311c54>] (panic+0x40/0x120)
  41. [<c0311c54>] (panic+0x40/0x120) from [<c0046cbc>] (do_exit+0x64/0x5f4)
  42. [<c0046cbc>] (do_exit+0x64/0x5f4) from [<c0032f28>] (die+0x15c/0x180)
  43. [<c0032f28>] (die+0x15c/0x180) from [<c00360a0>] (__do_kernel_fault+0x64/0x74)
  44. [<c00360a0>] (__do_kernel_fault+0x64/0x74) from [<c0036268>] (do_page_fault+0x1b8/0x1cc)
  45. [<c0036268>] (do_page_fault+0x1b8/0x1cc) from [<c002e2c0>] (do_DataAbort+0x34/0x94)
  46. [<c002e2c0>] (do_DataAbort+0x34/0x94) from [<c002ea20>] (__dabt_svc+0x40/0x60)
  47. Exception stack(0xc3823ee8 to 0xc3823f30)
  48. 3ee0: c042d170 00000000 00000000 00001234 00000000 c042d178
  49. 3f00: c0440d18 c38a0840 c0445008 00000000 00000000 00000000 c3842e7c c3823f30
  50. 3f20: c01d3f88 c0018f78 a0000013 ffffffff
  51. [<c002ea20>] (__dabt_svc+0x40/0x60) from [<c0018f78>] (s3c2410fb_probe+0xc/0x18)
  52. [<c0018f78>] (s3c2410fb_probe+0xc/0x18) from [<c01d3f88>] (platform_drv_probe+0x18/0x1c)
  53. [<c01d3f88>] (platform_drv_probe+0x18/0x1c) from [<c01d2e7c>] (driver_probe_device+0x148/0x2d8)
  54. [<c01d2e7c>] (driver_probe_device+0x148/0x2d8) from [<c01d3068>] (__driver_attach+0x5c/0x7c)
  55. [<c01d3068>] (__driver_attach+0x5c/0x7c) from [<c01d24e8>] (bus_for_each_dev+0x48/0x78)
  56. [<c01d24e8>] (bus_for_each_dev+0x48/0x78) from [<c01d1d48>] (bus_add_driver+0xe0/0x288)
  57. [<c01d1d48>] (bus_add_driver+0xe0/0x288) from [<c01d3334>] (driver_register+0xa4/0x130)
  58. [<c01d3334>] (driver_register+0xa4/0x130) from [<c0018f90>] (s3c2410fb_init+0xc/0x28)
  59. [<c0018f90>] (s3c2410fb_init+0xc/0x28) from [<c002e37c>] (do_one_initcall+0x5c/0x1b4)
  60. [<c002e37c>] (do_one_initcall+0x5c/0x1b4) from [<c00083f8>] (kernel_init+0x94/0x10c)
  61. [<c00083f8>] (kernel_init+0x94/0x10c) from [<c002fe54>] (kernel_thread_exit+0x0/0x8)
(2)分析 Oops 信息
  • 確出錯緣由。
由出錯信息「Unable to handle kernel NULL pointer dereference at virtual address 00000000」 可知內核是由於非法地址訪問出錯,使用了空指針。
  • 根據棧回溯信息找出函數調用關係。
內核崩潰時,能夠從 pc 寄存器得知崩潰發生時的函數、出錯指令。可是不少狀況下,錯誤有多是它的調用者引入的,因此找出函數的調用關係也很重要。
部分棧回溯信息以下:

  1. [<c0018f78>] (s3c2410fb_probe+0xc/0x18) from [<c01d3f88>] (platform_drv_probe+0x18/0x1c)
這行信息分爲兩部分,表示後面的 platform_drv_probe 函數調用了前面的 s3c2410fb_probe函數。
前半部含義爲:「c0018f78」是 s3c2410fb_probe 函數首地址偏移 0xc 的地址,這個函數大小爲 0x18。
後半部含義爲:「c01d3f88」是 platform_drv_probe 函數首地址偏移 0x18 的地址,這個函數大小爲 0x1c
另外,後半部的「[<c01d3f88>]」表示 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. PC is at s3c2410fb_probe+0xc/0x18
  2. LR is at platform_drv_probe+0x18/0x1c
  3. pc : [<c0018f78>] lr : [<c01d3f88>] psr: a0000013
  4. sp : c3823f30 ip : c3842e7c fp : 00000000
  5. r10: 00000000 r9 : 00000000 r8 : c0445008
  6. r7 : c38a0840 r6 : c0440d18 r5 : c042d178 r4 : 00000000
  7. r3 : 00001234 r2 : 00000000 r1 : 00000000 r0 : c042d170
"PC is at s3c2410fb_probe+0xc/0x18"表示出錯指令爲 s3c2410fb_probe 函數中偏移爲0xc 的指令。
"pc : [<c0018f78>] "表示出錯指令的地址爲 c0018f78(十六進制)。
  • 結合內核源代碼和反彙編代碼定位問題。
先生成內核的反彙編代碼 vmlinux.dis,執行如下命令:
  1. $ cd ~/Documents/myTest/kernel/linux-2.6.32
  2. $ arm-linux-objdump -D vmlinux > vmlinux.dis
出錯地址 c0018f78 附近的部分彙編代碼以下:
  1. c0018f64 <s3c2412fb_probe>:
  2. c0018f64: e3a01001 mov r1, #1 ; 0x1
  3. c0018f68: eafffe97 b c00189cc <s3c24xxfb_probe>

  4. c0018f6c <s3c2410fb_probe>:
  5. c0018f6c: e3a02000 mov r2, #0 ; 0x0
  6. c0018f70: e59f3008 ldr r3, [pc, #8] ; c0018f80 <s3c2410fb_probe+0x14>
  7. c0018f74: e1a01002 mov r1, r2
  8. c0018f78: e5823000 str r3, [r2]         <===========出錯指令
  9. c0018f7c: eafffe92 b c00189cc <s3c24xxfb_probe>
  10. c0018f80: 00001234 .word 0x00001234

  11. c0018f84 <s3c2410fb_init>:
  12. c0018f84: e92d4010 push {r4, lr}
  13. c0018f88: e59f0014 ldr r0, [pc, #20] ; c0018fa4 <s3c2410fb_init+0x20>
  14. c0018f8c: eb06ed06 bl c01d43ac <platform_driver_register>
  15. c0018f90: e3500000 cmp r0, #0 ; 0x0
  16. c0018f94: 18bd8010 popne {r4, pc}
  17. c0018f98: e59f0008 ldr r0, [pc, #8] ; c0018fa8 <s3c2410fb_init+0x24>
  18. c0018f9c: e8bd4010 pop {r4, lr}
  19. c0018fa0: ea06ed01 b c01d43ac <platform_driver_register>
  20. c0018fa4: c0440d04 .word 0xc0440d04
出錯指令爲「 str r3, [r2] 」,它把 r3 寄存器的值放到內存中,內存地址爲 r2 寄存器的值。
根據 Oops 信息中的寄存器值可知:r3 爲
00001234 , r2 爲 0。0 地址不可訪問,因此出錯。
  1. static int __init s3c2410fb_probe(struct platform_device *pdev)
  2. {
  3.     // add for kernel panic --begin
  4.     int *ptest = NULL;
  5.     *ptest = 0x1234;
  6.     // add for kernel panic --end
  7.     return s3c24xxfb_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: (0xc3823f30 to 0xc3824000)
  2. 3f20: 00000000 c01d2e7c c0440d18 c042d178
  3. 3f40: 0000000c c042d178 c042d1ac c0440d18 c38a0840 c01d3068 00000000 c01d300c
  4. 3f60: c0440d18 c01d24e8 c3804938 c3847330 00000000 00000000 c0440d18 c01d1d48
  5. 3f80: c03a1506 c03a1506 00000001 c0022dd4 00000000 c0440d18 c0018f84 00000000
  6. 3fa0: 00000000 c01d3334 c0022dd4 00000000 00000000 c0018f84 00000000 c0018f90
  7. 3fc0: c0022dd4 c002e37c c0018f84 c03c5ab3 c0457880 c0022fc8 c0022dd4 00000000
  8. 3fe0: 00000000 00000000 00000000 c00083f8 00000000 c002fe54 00000000 00000000
  • 根據 pc 寄存器值找到第一個函數,肯定它的棧大小,肯定調用函數。
從 Oops 信息
  1. PC is at s3c2410fb_probe+0xc/0x18
  2.     LR is at platform_drv_probe+0x18/0x1c
  3.     pc : [<c0018f78>] lr : [<c01d3f88>] psr: a0000013
  4.     sp : c3823f30 ip : c3842e7c fp : 00000000
  5.     r10: 00000000 r9 : 00000000 r8 : c0445008
  6.     r7 : c38a0840 r6 : c0440d18 r5 : c042d178 r4 : 00000000
  7.     r3 : 00001234 r2 : 00000000 r1 : 00000000 r0 : c042d170
可知 pc 值爲 c0018f78,使用它在內核反彙編程序 vmlinux.dis 中能夠知道它位於 s3c2410fb_probe 函數內。
  1. c0018f6c <s3c2410fb_probe>:
  2. c0018f6c: e3a02000 mov r2, #0 ; 0x0
  3. c0018f70: e59f3008 ldr r3, [pc, #8] ; c0018f80 <s3c2410fb_probe+0x14>
  4. c0018f74: e1a01002 mov r1, r2
  5. c0018f78: e5823000 str r3, [r2] <===========出錯指令
  6. c0018f7c: eafffe92 b c00189cc <s3c24xxfb_probe>
  7. c0018f80: 00001234 .word 0x00001234
lr存放的是函數返回值地址,爲 c01d3f88,根據這個地址,搜索內核反彙編程序 vmlinux.dis ,可知它位於:
  1. c01d3f70 <platform_drv_probe>:
  2. c01d3f70: e92d4010 push {r4, lr}
  3. c01d3f74: e1a03000 mov r3, r0
  4. c01d3f78: e5933044 ldr r3, [r3, #68]
  5. c01d3f7c: e2400008 sub r0, r0, #8 ; 0x8
  6. c01d3f80: e1a0e00f mov lr, pc
  7. c01d3f84: e513f014 ldr pc, [r3, #-20]
  8. c01d3f88: e8bd8010 pop {r4, pc}
也就是說,函數 s3c2410fb_probe() 被 platform_drv_probe()調用。再看 platform_drv_probe()的反彙編代碼,期中
  1. c01d3f70: e92d4010 push {r4, lr}
棧中存放的是 r4, lr
對應
  1. Stack: (0xc3823f30 to 0xc3824000)
  2. 3f20:                                                                            00000000 c01d2e7c c0440d18 c042d178
  3. 3f40: 0000000c c042d178 c042d1ac c0440d18 c38a0840 c01d3068 00000000 c01d300c
  4. 3f60: c0440d18 c01d24e8 c3804938 c3847330 00000000 00000000 c0440d18 c01d1d48
  5. 3f80: c03a1506 c03a1506 00000001 c0022dd4 00000000 c0440d18 c0018f84 00000000
  6. 3fa0: 00000000 c01d3334 c0022dd4 00000000 00000000 c0018f84 00000000 c0018f90
  7. 3fc0: c0022dd4 c002e37c c0018f84 c03c5ab3 c0457880 c0022fc8 c0022dd4 00000000
  8. 3fe0: 00000000 00000000 00000000 c00083f8 00000000 c002fe54 00000000 00000000
其中,lr對應的值爲 c01d2e7c,用此值檢索vmlinux.dis,位於
  1. c01d2d34 <driver_probe_device>:
  2. c01d2d34: e92d40f7 push {r0, r1, r2, r4, r5, r6, r7, lr}
  3. c01d2d38: e5d13028 ldrb r3, [r1, #40]
  4. c01d2d3c: e1a05001 mov r5, r1
  5. ....
  6. c01d2e7c: e2504000 subs r4, r0, #0 ; 0x0
  7. c01d2e80: 1a000016 bne c01d2ee0 <driver_probe_device+0x1ac>
  8. c01d2e84: e1a00005 mov r0, r5
  9. c01d2e88: ebffff6e bl c01d2c48 <driver_bound>
可知, platform_drv_probe()被 driver_probe_device()調用 ,再用一樣的方法就能夠找出全部函數調用關係。
相關文章
相關標籤/搜索