1. 程式人生 > >linux中Oops資訊的除錯及棧回溯—Linux人都知道,這是好東西

linux中Oops資訊的除錯及棧回溯—Linux人都知道,這是好東西

               

=============================================================================

看後感想:這點比 ldd3上講的都仔細

2012年11月29日11:24:17:有BUG_ON就不用反彙編了。。。

2012年11月30日11:14:13:回撥函式跟丟了

=============================================================================

Oops 資訊來源及格式Oops 這個單詞含義為“驚訝”,當核心出錯時(比如訪問非法地址)打印出來的資訊被稱為 Oops 資訊。Oops 資訊包含以下幾部分內容。

1 一段文字描述資訊。比如類似“Unable to handle kernel NULL pointer dereference at virtual address 00000000”的資訊,它說明了發生的是哪類錯誤。2 Oops 資訊的序號。比如是第 1 次、第 2 次等。這些資訊與下面類似,中括號內的資料表示序號。Internal error: Oops: 805 [#1]3 核心中載入的模組名稱,也可能沒有,以下面字樣開頭。Modules linked in:4 發生錯誤的 CPU 的序號,對於單處理器的系統,序號為 0,比如:CPU: 0Not tainted (2.6.22.6 #36)5 發生錯誤時 CPU 的各個暫存器值。6 當前程序的名字及程序 ID,比如:Process swapper (pid: 1, stack limit = 0xc0480258)這並不是說發生錯誤的是這個程序,而是表示發生錯誤時,當前程序是它。錯誤可能發生在核心程式碼、驅動程式,也可能就是這個程序的錯誤。7 棧資訊。8 棧回溯資訊,可以從中看出函式呼叫關係,形式如下:Backtrace:[<c001a6f4>] (s3c2410fb_probe+0x0/0x560) from [<c01bf4e8>] (platform_drv_probe+0x20/0x24)...9 出錯指令附近的指令的機器碼,比如(出錯指令在小括號裡):Code: e24cb004 e24dd010 e59f34e0 e3a07000 (e5873000)

配置核心使 Oops 資訊的棧回溯資訊更直觀Linux 2.6.22 自身具備的除錯功能,可以使得打印出的 Oops 資訊更直觀。通過 Oops 資訊中 PC 暫存器的值可以知道出錯指令的地址,通過棧回溯資訊可以知道出錯時的函式呼叫關係,根據這兩點可以很快定位錯誤。要讓核心出錯時能夠列印棧回溯資訊,編譯核心時要增加“-fno-omit-frame-pointer”選項,這可以通過配置 CONFIG_FRAME_POINTER 來實現。檢視核心目錄下的配置檔案.config,確保 CONFIG_FRAME_POINTER 已經被定義,如果沒有,執行“make menuconfig”命令重新配置核心。CONFIG_FRAME_POINTER 有可能被其他配置項自動選上。18.3.3使用 Oops 資訊除錯核心的例項1.獲得 Oops 資訊本小節故意修改 LCD 驅動程式 drivers/video/s3c2410fb.c,加入錯誤程式碼:在 s3c2410fb_probe 函式的開頭增加下面兩條程式碼:int *ptest = NULL;*ptest = 0x1234;重新編譯核心,啟動後會出錯並打印出如下 Oops 資訊:Unable to handle kernel NULL pointer dereference at virtual address 00000000pgd = c0004000[00000000] *pgd=00000000Internal error: Oops: 805 [#1]Modules linked in:CPU: 0Not tainted (2.6.22.6 #36)PC is at s3c2410fb_probe+0x18/0x560LR is at platform_drv_probe+0x20/0x24pc : [<c001a70c>]lr : [<c01bf4e8>]psr: a0000013sp : c0481e64 ip : c0481ea0 fp : c0481e9cr10: 00000000 r9 : c0024864 r8 : c03c420cr7 : 00000000 r6 : c0389a3c r5 : 00000000 r4 : c036256cr3 : 00001234 r2 : 00000001 r1 : c04c0fc4 r0 : c0362564Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment kernelControl: c000717f Table: 30004000 DAC: 00000017Process swapper (pid: 1, stack limit = 0xc0480258)Stack: (0xc0481e64 to 0xc0482000)1e60:c02b1f70 00000020 c03625d4 c036256c c036256c 00000000 c0389a3c1e80: c0389a3c c03c420c c0024864 00000000 c0481eac c0481ea0 c01bf4e8 c001a7041ea0: c0481ed0 c0481eb0 c01bd5a8 c01bf4d8 c0362644 c036256c c01bd708 c0389a3c1ec0: 00000000 c0481ee8 c0481ed4 c01bd788 c01bd4d0 00000000 c0481eec c0481f141ee0: c0481eec c01bc5a8 c01bd718 c038dac8 c038dac8 c03625b4 00000000 c0389a3c1f00: c0389a44 c038d9dc c0481f24 c0481f18 c01bd808 c01bc568 c0481f4c c0481f281f20: c01bcd78 c01bd7f8 c0389a3c 00000000 00000000 c0480000 c0023ac8 000000001f40: c0481f60 c0481f50 c01bdc84 c01bcd0c 00000000 c0481f70 c0481f64 c01bf5fc1f60: c01bdc14 c0481f80 c0481f74 c019479c c01bf5a0 c0481ff4 c0481f84 c0008c141f80: c0194798 e3c338ff e0222423 00000000 00000001 e2844004 00000000 000000001fa0: 00000000 c0481fb0 c002bf24 c0041328 00000000 00000000 c0008b40 c00476ec1fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 000000001fe0: 00000000 00000000 00000000 c0481ff8 c00476ec c0008b50 c03cdf50 c0344178Backtrace:[<c001a6f4>] (s3c2410fb_probe+0x0/0x560) from [<c01bf4e8>] (platform_drv_probe+0x20/0x24)[<c01bf4c8>] (platform_drv_probe+0x0/0x24) from [<c01bd5a8>] (driver_probe_device+0xe8/0x18c)[<c01bd4c0>] (driver_probe_device+0x0/0x18c) from [<c01bd788>] (__driver_attach+0x80/0xe0)r8:00000000 r7:c0389a3c r6:c01bd708 r5:c036256c r4:c0362644[<c01bd708>] (_ _driver_attach+0x0/0xe0) from [<c01bc5a8>] (bus_for_each_dev+0x50/0x84)r5:c0481eec r4:00000000[<c01bc558>] (bus_for_each_dev+0x0/0x84) from [<c01bd808>] (driver_attach+0x20/0x28)r7:c038d9dc r6:c0389a44 r5:c0389a3c r4:00000000[<c01bd7e8>] (driver_attach+0x0/0x28) from [<c01bcd78>] (bus_add_driver+0x7c/0x1b4)[<c01bccfc>] (bus_add_driver+0x0/0x1b4) from [<c01bdc84>] (driver_register+0x80/0x88)[<c01bdc04>] (driver_register+0x0/0x88) from [<c01bf5fc>] (platform_driver_register+0x6c/0x88)r4:00000000[<c01bf590>] (platform_driver_register+0x0/0x88) from [<c019479c>] (s3c2410fb_init+0x14/0x1c)[<c0194788>] (s3c2410fb_init+0x0/0x1c) from [<c0008c14>] (kernel_init+0xd4/0x28c)[<c0008b40>] (kernel_init+0x0/0x28c) from [<c00476ec>] (do_exit+0x0/0x760)Code: e24cb004 e24dd010 e59f34e0 e3a07000 (e5873000)Kernel panic - not syncing: Attempted to kill init!分析 Oops 資訊

(1)明確出錯原因。由出錯資訊“Unable to handle kernel NULL pointer dereference at virtual address 00000000”可知核心是因為非法地址訪問出錯,使用了空指標。(2)根據棧回溯資訊找出函式呼叫關係。核心崩潰時,可以從 pc 暫存器得知崩潰發生時的函式、出錯指令。但是很多情況下,錯誤有可能是它的呼叫者引入的,所以找出函式的呼叫關係也很重要。部分棧回溯資訊如下:[<c001a6f4>] (s3c2410fb_probe+0x0/0x560) from [<c01bf4e8>] (platform_drv_probe+0x20/0x24)這行資訊分為兩部分,表示後面的 platform_drv_probe 函式呼叫了前面的 s3c2410fb_probe函式。前半部含義為:“c001a6f4”是 s3c2410fb_probe 函式首地址偏移 0 的地址,這個函式大小為 0x560。後半部含義為:“c01bf4e8”是 platform_drv_probe 函式首地址偏移 0x20 的地址,這個函式大小為 0x24。另外,後半部的“[<c01bf4e8>]”表示 s3c2410fb_probe 執行後的返回地址。對於類似下面的棧回溯資訊,其中是 r8~r4 表示 driver_probe_device 函式剛被呼叫時這些暫存器的值。[<c01bd4c0>] (driver_probe_device+0x0/0x18c) from [<c01bd788>] (__driver_attach+0x80/0xe0)r8:00000000 r7:c0389a3c r6:c01bd708 r5:c036256c r4:c0362644從上面的棧回溯資訊可以知道核心出錯時的函式呼叫關係如下,最後在 s3c2410fb_probe函式內部崩潰。do_exit ->kernel_init ->s3c2410fb_init ->platform_driver_register ->driver_register ->bus_add_driver ->driver_attach ->bus_for_each_dev ->__driver_attach ->driver_probe_device ->platform_drv_probe ->s3c2410fb_probe(3)根據 pc 暫存器的值確定出錯位置。上述 Oops 資訊中出錯時的暫存器值如下:PC is at s3c2410fb_probe+0x18/0x560LR is at platform_drv_probe+0x20/0x24pc : [<c001a70c>]lr : [<c01bf4e8>]psr: a0000013...“PC is at s3c2410fb_probe+0x18/0x560”表示出錯指令為 s3c2410fb_probe 函式中偏移為0x18 的指令。“pc : [<c001a70c>]”表示出錯指令的地址為 c001a70c(十六進位制)。(4)結合核心原始碼和反彙編程式碼定位問題。先生成核心的反彙編程式碼 vmlinux.dis,執行以下命令:$ cd /work/system/linux-2.6.22.6$ arm-linux-objdump -D vmlinux > vmlinux.dis出錯地址 c001a70c 附近的部分彙編程式碼如下:c001a6f4 <s3c2410fb_probe>:c001a6f4: e1a0c00d mov ip, spc001a6f8: e92ddff0 stmdbc001a6fc: e24cb004 sub fp, ip, #4 ; 0x4c001a700: e24dd010 sub sp, sp, #16 ; 0x10c001a704: e59f34e0 ldr r3, [pc, #1248] ; c001abec <.init+0x1284c>c001a708: e3a07000 mov r7, #0c001a70c: e5873000 str r3, [r7]c001a710: e59030fc ldr r3, [r0, #252]sp!, {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}; 0x0<===========出錯指令出錯指令為“str r3, [r7]”,它把 r3 暫存器的值放到記憶體中,記憶體地址為 r7 暫存器的值。根據 Oops 資訊中的暫存器值可知:r3 為 0x00001234,r7 為 0。0 地址不可訪問,所以出錯。s3c2410fb_probe 函式的部分 C 程式碼如下:static int __init s3c2410fb_probe(struct platform_device *pdev){struct s3c2410fb_info *info;struct fb_info*fbinfo;struct s3c2410fb_hw *mregs;int ret;int irq;int i;u32 lcdcon1;int *ptest = NULL;*ptest = 0x1234;mach_info = pdev->dev.platform_data;結合反彙編程式碼,很容易知道是“*ptest = 0x1234;”導致錯誤,其中的 ptest 為空。對於大多數情況,從反彙編程式碼定位到 C 程式碼並不會如此容易,這需要較強的閱讀彙編程式的能力。通過棧回溯資訊知道函式的呼叫關係,這已經可以幫助定位很多問題了。

使用 Oops 的棧資訊手工進行棧回溯前面說過,從 Oops 資訊的 pc 暫存器值可知得知崩潰發生時的函式、出錯指令。但是錯誤有可能是它的呼叫者引入的,所以還要找出函式的呼叫關係。由於核心配置了 CONFIG_FRAME_POINTER,當出現 Oops 資訊時,會列印棧回溯資訊。如果核心沒有配置 CONFIG_FRAME_POINTER,這時可以自己分析棧資訊,找到函式的呼叫關係。1.棧的作用一個程式包含程式碼段、資料段、BSS 段、堆、棧;其中資料段用來中儲存初始值不為 0的全域性資料,BSS 段用來儲存初始值為 0 的全域性資料,堆用於動態記憶體分配,棧用於實現函式呼叫、儲存區域性變數。被呼叫函式在執行之前,它會將一些暫存器的值儲存在棧中,其中包括返回地址暫存器lr。如果知道了所儲存的 lr 寄存的值,那麼就可以知道它的呼叫者是誰。在棧資訊中,一個函式一個函式地往上找出所有儲存的 lr 值,就可以知道各個呼叫函式,這就是棧回溯的原理。2.棧回溯例項分析仍以前面的 LCD 驅動程式為例,使用上面的 Oops 資訊的棧資訊進行分析,棧資訊如下:Stack: (0xc0481e64 to 0xc0482000)1e60:c02b1f70 00000020 c03625d4 c036256c c036256c 00000000 c0389a3c1e80: c0389a3c c03c420c c0024864 00000000 c0481eac c0481ea0 c01bf4e8 c001a7041ea0: c0481ed0 c0481eb0 c01bd5a8 c01bf4d8 c0362644 c036256c c01bd708 c0389a3c1ec0: 00000000 c0481ee8 c0481ed4 c01bd788 c01bd4d0 00000000 c0481eec c0481f141ee0: c0481eec c01bc5a8 c01bd718 c038dac8 c038dac8 c03625b4 00000000 c0389a3c...1 根據 pc 暫存器值找到第一個函式,確定它的棧大小,確定呼叫函式。從 Oops 資訊可知 pc 值為 c001a70c,使用它在核心反彙編程式 vmlinux.dis 中可以知道它位於 s3c2410fb_probe 函式內。根據這個函式開始部分的彙編程式碼可以知道棧的大小、lr 返回值在棧中儲存的位置,程式碼如下:c001a6f4 <s3c2410fb_probe>:c001a6f4:e1a0c00dmov ip, spc001a6f8: e92ddff0 stmdbc001a6fc: e24cb004 sub fp, ip, #4 ; 0x4sp!, {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}c001a700: e24dd010 sub sp, sp, #16 ; 0x10e5873000 str r3, [r7]...c001a70c:// pc 值 c001a70c 對應的指令...{r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}這 11 個暫存器都儲存在棧中,指令“sub sp, sp, #16”又使得棧向下擴充套件了 16 位元組,所以本函式的棧大小為(11 × 4+16)位元組,即 15 個雙字。棧資訊開始部分的 15 個數據就是本函式的棧內容,下面列出了它們所儲存的暫存器。1e60:c02b1f70 00000020 c03625d4 c036256c c036256c 00000000 c0389a3cr4r5r61e80: c0389a3c c03c420c c0024864 00000000 c0481eac c0481ea0 c01bf4e8 c001a704r7r8r9slfpiplrpc其中 lr 值為 c01bf4e8,表示函式 s3c2410fb_probe 執行完後的返回地址,它是呼叫函式中的地址。下面使用 lr 值再次重複本步驟的回溯過程。2 根據 lr 暫存器值找到呼叫函式,確定它的棧大小,確定上一級呼叫函式。根據上步得到的 lr 值(c01bf4e8)在核心反彙編程式 vmlinux.dis 中可以知道它位於platform_drv_probe 函式內。根據這個函式開始部分的反彙編程式碼可以知道棧的大小、lr 返回值在棧中儲存的位置。程式碼如下:c01bf4c8 <platform_drv_probe>:c01bf4c8: e1a0c00d mov ip, spc01bf4cc: e92dd800 stmdb sp!, {fp, ip, lr, pc}e89da800 ldmia sp, {fp, sp, pc}...c01bf4e8:// lr 值(c01bf4e8)對應的指令{fp, ip, lr, pc}這 4 暫存器都儲存在棧中,本函式的棧大小為 4 個雙字。Oops 棧資訊中,前一個函式 s3c2410fb_probe 的棧下面的 4 個數據就是函式 platform_drv_probe 的棧內容,如下所示:1ea0: c0481ed0 c0481eb0 c01bd5a8 c01bf4d8fpiplrpc其中 lr 值為 c01bd5a8,表示函式 platform_drv_probe 執行完後的返回地址,它是上一級呼叫函式中的地址。使用 lr 值,重複本步驟的查詢過程,直到棧資訊分析完畢或者再也無法分析,這樣就可以找出所有的函式呼叫關係。有些函式很簡單,沒有使用棧(sp 值在這個函式中沒有變化),或者沒有在棧中儲存 lr值。這些情況需要讀者靈活處理,較強的彙編程式閱讀能力是關鍵。