NO IMAGE

轉自:http://blog.chinaunix.net/uid-27159438-id-3280213.html

出現Oops訊息的大部分錯誤時因為對NULL指標取值或者因為用了其他不正確的指標值。
Oops如何產生的解釋如下:
    由於處理器使用的地址幾乎都是虛擬地址,這些地址通過一個被稱為“頁表”的結構被對映為實體地址。當引入一個非法指標的時候,分頁機制無法將該地址對映到實體地址,此時處理器就會向作業系統發出一個“頁面失效(page fault)”的訊號。如果地址非法“換入(page in)”缺失頁面;這時,如果處理器恰好處於超級使用者模式,系統就會產生一個Oops。
Oops的格式:
更加詳細的解釋參考下面程式碼,這裡對輸出資訊做個簡要介紹:
arch/arm/mm/fault.c
arch/arm/kernel/traps.c
Unable to handle kernel NULL pointer dereference at virtual address 00000000
//一段文字資訊,提示表明是什麼錯誤型別
pgd = 80004000
[00000000] *pgd=00000000
Internal error: Oops: 5 [#1] PREEMPT//錯誤序號,中括號中藍色
last sysfs file: 
Modules linked in://被連線進的模組
CPU: 0    Not tainted  (2.6.35.3-00054-g8deb747-dirty #1)
//發生錯誤的CPU序號,藍色表示編譯了10次。
PC is at mutex_lock 0xc/0x28
LR is at alc5633_reg_write 0x20/0x5c
pc : [<80375de8>]    lr : [<801ce2d0>]    psr: a0000013
sp : 9b029e50  ip : 9b029e60  fp : 9b029e5c
r10: 9b22c2d0  r9 : 9b22c2d0  r8 : 00000000
r7 : 804c0c10  r6 : 9b22c208  r5 : 00000000  r4 : 00000000
r3 : 00000001  r2 : 00000000  r1 : 00000000  r0 : 00000000
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment kernel
Control: 10c5387d  Table: 90004019  DAC: 00000017
//發生錯誤時候CPU中各個暫存器的值,即當時CPU暫存器快照
Process swapper (pid: 1, stack limit = 0x9b0282e8)
//當前程序的名字和程序ID。但是這並不表示該程序中發生了該錯誤,而是表示發生錯誤時候,當前的程序是它。錯誤可能發生在核心程式碼、驅動程式,也可能就是這個程序的錯誤。
Stack: (0x9b029e50 to 0x9b02a000)
9e40:                                     9b029e7c 9b029e60 801ce2d0 80375de8
9e60: 804c0c10 00000000 9b22c200 00000000 9b029eac 9b029e80 802c043c 801ce2bc
9e80: 804e6b8c 804c0c10 804c0c44 804e6b8c 804e6b8c 00000000 00000000 00000000
9ea0: 9b029ebc 9b029eb0 801c2cac 802c0328 9b029edc 9b029ec0 801c1c48 801c2c98
9ec0: 804c0c10 804c0c44 804e6b8c 00000000 9b029efc 9b029ee0 801c1d6c 801c1b84
9ee0: 804e6b8c 9b029f00 801c1d04 00000000 9b029f24 9b029f00 801c13ac 801c1d10
9f00: 9b0068b8 9b07f8d0 804e6b8c 9b1ec5a0 804d9670 00000000 9b029f34 9b029f28
9f20: 801c1a8c 801c1364 9b029f64 9b029f38 801c0c78 801c1a78 80452a97 80023f1c
9f40: 804e6b8c 80023f1c 00000001 00000013 00000000 00000000 9b029f8c 9b029f68
9f60: 801c20a0 801c0be0 8001d024 80023f1c 00000001 00000013 00000000 00000000
9f80: 9b029f9c 9b029f90 801c313c 801c1ffc 9b029fac 9b029fa0 8001d038 801c30fc
9fa0: 9b029fdc 9b029fb0 800273b4 8001d030 800515f0 00000013 9b029fdc 9b029fc8
9fc0: 80023e64 80023f1c 800515f0 00000013 9b029ff4 9b029fe0 800084ac 8002735c
9fe0: 00000000 800083f4 00000000 9b029ff8 800515f0 80008400 ffefdb9e db31f16d
//上面是棧資訊
Backtrace: 
[<80375ddc>] (mutex_lock 0x0/0x28) from [<801ce2d0>] (alc5633_reg_write 0x20/0x5c)
[<801ce2b0>] (alc5633_reg_write 0x0/0x5c) from [<802c043c>] (alc5633_codec_probe 0x120/0x224)
 r5:00000000 r4:9b22c200
[<802c031c>] (alc5633_codec_probe 0x0/0x224) from [<801c2cac>] (platform_drv_probe 0x20/0x24)
[<801c2c8c>] (platform_drv_probe 0x0/0x24) from [<801c1c48>] (driver_probe_device 0xd0/0x18c)
[<801c1b78>] (driver_probe_device 0x0/0x18c) from [<801c1d6c>] (__driver_attach 0x68/0x8c)
 r7:00000000 r6:804e6b8c r5:804c0c44 r4:804c0c10
[<801c1d04>] (__driver_attach 0x0/0x8c) from [<801c13ac>] (bus_for_each_dev 0x54/0x94)
 r7:00000000 r6:801c1d04 r5:9b029f00 r4:804e6b8c
[<801c1358>] (bus_for_each_dev 0x0/0x94) from [<801c1a8c>] (driver_attach 0x20/0x28)
 r7:00000000 r6:804d9670 r5:9b1ec5a0 r4:804e6b8c
[<801c1a6c>] (driver_attach 0x0/0x28) from [<801c0c78>] (bus_add_driver 0xa4/0x224)
[<801c0bd4>] (bus_add_driver 0x0/0x224) from [<801c20a0>] (driver_register 0xb0/0x140)
[<801c1ff0>] (driver_register 0x0/0x140) from [<801c313c>] (platform_driver_register 0x4c/0x60)
 r9:00000000 r8:00000000 r7:00000013 r6:00000001 r5:80023f1c
r4:8001d024
[<801c30f0>] (platform_driver_register 0x0/0x60) from [<8001d038>] (alc5633_init 0x14/0x1c)
[<8001d024>] (alc5633_init 0x0/0x1c) from [<800273b4>] (do_one_initcall 0x64/0x1bc)
[<80027350>] (do_one_initcall 0x0/0x1bc) from [<800084ac>] (kernel_init 0xb8/0x174)
 r7:00000013 r6:800515f0 r5:80023f1c r4:80023e64
[<800083f4>] (kernel_init 0x0/0x174) from [<800515f0>] (do_exit 0x0/0x674)
 r5:800083f4 r4:00000000
//上面是棧回溯的資訊,可以從中看出呼叫關係。我們配置CONFIG_FRAME_POINTER這個選項也就是為了出這些資訊。
Code: e89dadf0 e1a0c00d e92dd800 e24cb004 (e1903f9f) 
//出錯指令附近的指令機器碼,比如(出錯指令在小括號內)。
—[ end trace ae0d0d75681e1941 ]—
上面的核心Oops就是當時除錯ALC5633過程中發生的錯誤,正是通過Oops資訊回溯發現了問題點。
明確出錯原因
“Unable to handle kernel NULL pointer dereference at virtual address 00000000”可知核心因為非法地址訪問出錯,使用了空指標。
根據棧回溯資訊找出函式呼叫關係。
核心崩潰時,可以從pc暫存器得知崩潰發生時的函式,出錯指令。但是很多情況下是它的調入者引入的,所以找出呼叫關係很重要,這就是引入棧回溯的目的。
從棧回溯資訊我們可以得到清晰的函式呼叫關係,以及最後在mutex_lock函式內部崩潰。
do_exit()
 kernel_init()
   do_one_initcall()
      alc5633_init()
        platform_driver_register()
           driver_register()
               bus_add_driver()
                  driver_attach()
                     bus_for_each_dev()
                        __driver_attach()
                          driver_probe_device()
                             platform_drv_probe()
                                alc5633_codec_probe()
                                   alc5633_reg_write()
                                      mutex_lock()
根據PC暫存器的值確定出錯位置。
PC is at mutex_lock 0xc/0x28
LR is at alc5633_reg_write 0x20/0x5c
pc : [<80375de8>]    lr : [<801ce2d0>]    psr: a0000013
sp : 9b029e50  ip : 9b029e60  fp : 9b029e5c
r10: 9b22c2d0  r9 : 9b22c2d0  r8 : 00000000
r7 : 804c0c10  r6 : 9b22c208  r5 : 00000000  r4 : 00000000
r3 : 00000001  r2 : 00000000  r1 : 00000000  r0 : 00000000
上面標示出錯指令在mutex_lock偏移在0xc處的指令。pc : [<80375de8>]表示出錯地址的指令為80375de8
反彙編我們的核心,採用如下指令:
./prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin/arm-eabi-objdump
 -D vmlinux > vmlinux.dis
結合反彙編我們定位到函式:
80375ddc :
80375ddc:       e1a0c00d        mov     ip, sp  
80375de0:       e92dd800        push    {fp, ip, lr, pc} 
80375de4:       e24cb004        sub     fp, ip, #4      ; 0x4   
80375de8:       e1903f9f        ldrex   r3, [r0]
80375dec:       e2433001        sub     r3, r3, #1      ; 0x1   
80375df0:       e1802f93        strex   r2, r3, [r0]
80375df4:       e1923003        orrs    r3, r2, r3
80375df8:       089da800        ldmeq   sp, {fp, sp, pc} 
80375dfc:       ebffff9f        bl      80375c80 <__mutex_lock_slowpath>
80375e00:       e89da800        ldm     sp, {fp, sp, pc} 
上面可以看到r0裡面的值為0x00000000。這裡知道了我們給mutex_lock傳的引數為空指標。
【這裡需要知道ARM函式引數傳遞規則,根據該規則r0存有函式傳過來的第一引數,超過4個引數,要進行壓棧動作了。】
下面是muetx_lock上一級的呼叫程式碼:
 96 int alc5633_reg_write(struct alc5633 *alc5633, unsigned short reg,
 97                      unsigned short val)
 98 {
 99         int ret;
100 
101         mutex_lock(&alc5633->io_lock);
102 
103         ret = alc5633_write(alc5633, reg, 2, &val);
104 
105         mutex_unlock(&alc5633->io_lock);
106 
107         return ret;
108 }
109 EXPORT_SYMBOL_GPL(alc5633_reg_write);
進而發現alc5633->io_lock為空。
在檢視再上級的程式碼,在alc5633_codec_probe()程式碼如下:
779 static int alc5633_codec_probe(struct platform_device *pdev)
780 {
818   alc5633_reg_write(codec->control_data,ALC5633_RESET,0);
853 }
藍色部分我們沒有初始化具體的struct alc5633 *型別的值過來,導致了空指標的出現,定位了問題的出處,就好解決了。