linux驅動調試--段錯誤之oops信息分析

原文地址

http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=29401328&id=4923447

發生段錯誤原因就是訪問了不該訪問的地址,例如訪問了不存在的內存地址、訪問了系統保護的內存地址、訪問了只讀的內存地址等。

下面根據Oops信息來分析一下段錯誤

first_drv.c


    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/delay.h>
    #include <asm/uaccess.h>
    #include <asm/irq.h>
    #include <asm/io.h>
    #include <linux/device.h>

    static struct class *segment_class;
    static struct device *segment_class_dev;
    unsigned char *c = NULL;
    int major;

    static int segment_test_open(struct inode *inode, struct file *file)
    {
        *c = 0x34;
        //printk("segment_test_open success!\n");
        return 0;
    }

    static struct file_operations segment_test_fops = {
        .owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
        .open = segment_test_open,
    };

    static int segment_drv_init(void)
    {
        major = register_chrdev(0, "segment_test", &segment_test_fops); // 註冊, 告訴內核
        segment_class = class_create(THIS_MODULE, "segment_test");
        segment_class_dev = device_create(segment_class, NULL, MKDEV(major, 0), NULL, "segment");

        c = (unsigned char *)0x48000000;

        printk("segment_drv_init success!\n");

        return 0;
    }

    static void segment_drv_exit(void)
    {    
        device_destroy(segment_class, MKDEV(major,0));
        class_destroy(segment_class);

        unregister_chrdev(major, "segment_test");
    }

    module_init(segment_drv_init);
    module_exit(segment_drv_exit);


    MODULE_LICENSE("GPL");


firstdrvtest.c

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>

    int main(int argc, char **argv)
    {
        int fd;
        int val = 1;
        fd = open("/dev/segment", O_RDWR);
        if (fd < 0)
        {
            printf("can't open!\n");
            return -1;
        }

        close(fd);

        return 0;
    }


# insmod first_drv.ko
# ./firstdrvtest
Unable to handle kernel paging request at virtual address 48000000     // 內核使用48000000來訪問時發生了錯誤
pgd = c3b4c000
[48000000] *pgd=00000000
Internal error: Oops: 805 [#1]
Modules linked in: first_drv rt5370sta zd1211rw mac80211
CPU: 0    Not tainted  (2.6.30.4-EmbedSky #1)
PC is at segment_test_open+0x1c/0x28 [first_drv]                       // PC值
LR is at chrdev_open+0xcc/0x170
pc : []    lr : []    psr: a0000013                // 發生錯誤時各寄存器的值(下面五行)
sp : c3a61e30  ip : c3a61e40  fp : c3a61e3c
r10: c394bc80  r9 : 00000002  r8 : c34b7600
r7 : c3b46100  r6 : c3ab84b0  r5 : c3a62180  r4 : 00000000
r3 : 00000034  r2 : 48000000  r1 : c3b46100  r0 : 00000000
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: c000717f  Table: 33b4c000  DAC: 00000015
Process firstdrvtest (pid: 637, stack limit = 0xc3a60268)              // 發生錯誤時當前進程的名稱是firstdrvtest
Stack: (0xc3a61e30 to 0xc3a62000)                                                 // 棧
1e20:                                     c3a61e64 c3a61e40 c00a8580 bf0d7010 
1e40: c00adba8 00000000 00000000 c3b46100 c3ab84b0 c00a84b4 c3a61e8c c3a61e68 
1e60: c00a3a7c c00a84c4 c3b46100 c2c0ae40 00000003 c3af0000 00000026 c3a61ed8 
1e80: c3a61eac c3a61e90 c00a3d14 c00a39bc 00000000 c2c0ae40 00000000 00000000 
1ea0: c3a61f64 c3a61eb0 c00b0c80 c00a3cc0 c3a61f7c c3a61ec0 c004b714 c006f8b8 
1ec0: c3a61efc beb5ad9c 00000000 00000000 c3a63000 c048070c c394bc80 c34b7600 
1ee0: c048077c c3a61fb0 00000000 00000101 00000001 00000000 c00441e0 c004b548 
1f00: 08100875 c39568a0 c3a7ec00 0000001c 00000000 00001000 00000003 00000003 
1f20: 00000000 c3b46100 00000000 c3a60000 c3a61f64 c3a61f40 c00b99b8 00000003 
1f40: c3af0000 00000002 beb5ad9c ffffff9c c3a60000 00000000 c3a61f94 c3a61f68 
1f60: c00a38d8 c00b0aa0 00000000 40025000 c3a61f9c 0000850c 00000000 000083e0 
1f80: 00000005 c0045008 c3a61fa4 c3a61f98 c00a3988 c00a3878 00000000 c3a61fa8 
1fa0: c0044e60 c00a3974 0000850c 00000000 00008590 00000002 beb5ad9c 00000001 
1fc0: 0000850c 00000000 000083e0 00000005 00000000 00000000 40025000 beb5ac44 
1fe0: 00000000 beb5ac28 000084b8 400efd9c 60000010 00008590 00000000 00000000 
Backtrace:                                                                          // 回溯信息
[] (segment_test_open+0x0/0x28 [first_drv]) from [] (chrdev_open+0xcc/0x170)
[] (chrdev_open+0x0/0x170) from [] (__dentry_open+0xd0/0x270)
 r7:c00a84b4 r6:c3ab84b0 r5:c3b46100 r4:00000000
[] (__dentry_open+0x0/0x270) from [] (nameidata_to_filp+0x64/0x6c)
[] (nameidata_to_filp+0x0/0x6c) from [] (do_filp_open+0x1f0/0x7e8)
 r5:00000000 r4:00000000
[] (do_filp_open+0x0/0x7e8) from [] (do_sys_open+0x70/0xe8)
[] (do_sys_open+0x0/0xe8) from [] (sys_open+0x24/0x28)
 r8:c0045008 r7:00000005 r6:000083e0 r5:00000000 r4:0000850c
[] (sys_open+0x0/0x28) from [] (ret_fast_syscall+0x0/0x2c)
Code: e59f3010 e3a00000 e5932000 e3a03034 (e5c23000) 
---[ end trace d31b8aee70b25c9c ]---
Segmentation fault

上面的這些調試信息包含了很多內容,我們可以根據其中的一部分就可以定位出問題,下面逐一介紹一下。


一、直接確定發生錯誤的函數
看到這句 “PC is at segment_test_open+0x1c/0x28 [first_drv]”,出現錯誤時我們最關注的就是PC值,因爲它就是發生錯誤
的指令的地址,這裏我們可以看到錯誤發生在函數 segment_test_open 的0x1c處,0x28代表這個函數的總長度(彙編代碼)


二、根據PC值確定發生錯誤的函數

有時候不會直接告訴你發生在哪個函數,而是隻把PC值告訴你:
pc : []
這時你要根據PC值自己找到發生錯誤的地方,怎麼找呢?

現在我們知道發生錯誤時 PC = 0xbf0d701c,我們首先要確定發生的錯誤位置是在內核中還是在外面的模塊裏,
然後根據PC值找出發生的函數及指令。怎麼確定?

1. 進入到我們內核源碼的根目錄下,找到System.map,這個文件指示了所有的內核函數的地址範圍,
我們可以觀察,發生錯誤時PC值是不是在這個文件的地址範圍內,例如我的這個文件的地址範圍是:
c0004000 A swapper_pg_dir  ~~~  c04ec044 B _end

如果不屬於System.map裏的範圍,則它屬於insmod加載的驅動程序,這裏可以看到bf0d701c屬於模塊地址

2.知道錯誤在模塊裏了,那麼怎麼確定是哪一個驅動程序?

在開發板上查看:
# cat /proc/kallsyms    // 內核函數、加載的函數的地址,t是靜態函數,T是全局函數

從這些信息裏找到一個與PC值相近的地址
比如找到了:
00000000 a first_drv.c  [first_drv]                       
bf0d7000 t $a   [first_drv]                       
bf0d7000 t segment_test_open    [first_drv]       
bf0d7024 t $d   [first_drv]                       
bf0d7028 t $a   [first_drv]                       
bf0d7028 t segment_drv_exit     [first_drv]     

這裏可以看出來,PC=bf0d701c 是屬於segment_test_open函數

其實,我們只通過“cat /proc/kallsyms”就可以知道是哪個函數發生了錯誤,步驟1只是讓我們知道這個函數是屬於內核的還是模塊的


三、通過回溯信息確定發生錯誤的函數

Backtrace: 
[] (segment_test_open+0x0/0x28 [first_drv]) from [] (chrdev_open+0xcc/0x170)
省略好幾行

這部分是回溯信息,從最後調用的發生錯誤的函數層層打印出函數的調用關係,上一行的函數被下一行的調用。
注意:在配置內核時,需要選擇 FRAME_POINTER = y 纔會有回溯信息,如果沒有,可以根據棧信息分析


四、定位發生錯誤的代碼(需要彙編閱讀能力)

上面幾種方法都只定位了發生錯誤的函數,怎麼定位到是哪一句代碼發生了錯誤呢?

1. 如果發生的錯誤函數是屬於模塊的,如我們的這個實例
segment_test_open+0x1c/0x28
這裏的0x1c是指彙編代碼的地址,所以我們要把這個模塊反彙編,然後定位。

# arm-none-linux-gnueabi-objdump -D first_drv.ko > first_drv.dis
打開first_drv.dis有下面這一段:

00000000 :
   0:   e1a0c00d        mov     ip, sp
   4:   e92dd800        push    {fp, ip, lr, pc}
   8:   e24cb004        sub     fp, ip, #4      ; 0x4
   c:   e59f3010        ldr     r3, [pc, #16]   ; 24
  10:   e3a00000        mov     r0, #0  ; 0x0
  14:   e5932000        ldr     r2, [r3]
  18:   e3a03034        mov     r3, #52 ; 0x34
  1c:   e5c23000        strb    r3, [r2]
  20:   e89da800        ldm     sp, {fp, sp, pc}
  24:   00000000        .word   0x00000000

這裏代碼的實際地址都要加上偏移地址 bf0d7000,發生錯誤的那句代碼是:
1c:   e5c23000        strb    r3, [r2]
根據我們的C語言代碼可以看出這裏是把0x34賦給變量時產生錯誤,產生錯誤的原因是加載模塊初始化時賦給的一個地址非法:
c = (unsigned char *)0x48000000;

我們這裏的程序比較短,可以一眼看出來,如果代碼很長,就可以根據發生錯誤的位置,大概確定代碼的位置,
然後再去看代碼和彙編,這裏要求比較高的彙編閱讀能力


2. 如果發生的錯誤函數是屬於內核的

這個時候和發生在模塊裏類似,不過這裏要反彙編整個內核:
# arm-none-linux-gnueabi-objdump -D vmlinux > vmlinux.dis
打開vmlinux.dis,然後直接查找地址bf0d7000,接下來像上面一樣分析代碼

關於上面的打印信息,還有棧那一段沒有講,放到下一篇博文說












發佈了34 篇原創文章 · 獲贊 4 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章