1.當驅動有誤時,比如,訪問的記憶體地址是非法的,便會列印一大串的oops出來 1.1以LED驅動為例 將open()函數里的ioremap()屏蔽掉,直接使用物理地址的GPIOF,如下圖所示: 1.2然後編譯裝載26th_segmentfault並執行測試程式後,內核便列印了oops出來,如下圖所示 ...
1.當驅動有誤時,比如,訪問的記憶體地址是非法的,便會列印一大串的oops出來
1.1以LED驅動為例
將open()函數里的ioremap()屏蔽掉,直接使用物理地址的GPIOF,如下圖所示:
1.2然後編譯裝載26th_segmentfault並執行測試程式後,內核便列印了oops出來,如下圖所示:
2.接下來,我們便來分析oops:
Unable to handle kernel paging request at virtual address 56000050 //無法處理內核頁面請求的虛擬地址56000050 pgd = c3850000 [56000050] *pgd=00000000 Internal error: Oops: 5 [#1] //內部錯誤oops Modules linked in: 26th_segmentfault //表示內部錯誤發生在26th_segmentfault.ko驅動模塊里 CPU: 0 Not tainted (2.6.22.6 #2) PC is at first_drv_open+0x78/0x12c [26th_segmentfault] //PC值:程式運行成功的最後一次地址,位於first_drv_open()函數里,偏移值0x78,該函數總大小0x12c LR is at 0xc0365ed8 //LR值 /*發生錯誤時的各個寄存器值*/ pc : [<bf000078>] lr : [<c0365ed8>] psr: 80000013 sp : c3fcbe80 ip : c0365ed8 fp : c3fcbe94 r10: 00000000 r9 : c3fca000 r8 : c04df960 r7 : 00000000 r6 : 00000000 r5 : bf000de4 r4 : 00000000 r3 : 00000000 r2 : 56000050 r1 : 00000001 r0 : 00000052 Flags: Nzcv IRQs on FIQs on Mode SVC_32 Segment user Control: c000717f Table: 33850000 DAC: 00000015 Process 26th_segmentfau (pid: 813, stack limit = 0xc3fca258) //發生錯誤時,進程名稱為26th_segmentfault Stack: (0xc3fcbe80 to 0xc3fcc000) //棧信息 be80: c06d7660 c3e880c0 c3fcbebc c3fcbe98 c008d888 bf000010 00000000 c04df960 bea0: c3e880c0 c008d73c c0474e20 c3fb9534 c3fcbee4 c3fcbec0 c0089e48 c008d74c bec0: c04df960 c3fcbf04 00000003 ffffff9c c002c044 c380a000 c3fcbefc c3fcbee8 bee0: c0089f64 c0089d58 00000000 00000002 c3fcbf68 c3fcbf00 c0089fb8 c0089f40 bf00: c3fcbf04 c3fb9534 c0474e20 00000000 00000000 c3851000 00000101 00000001 bf20: 00000000 c3fca000 c04c90a8 c04c90a0 ffffffe8 c380a000 c3fcbf68 c3fcbf48 bf40: c008a16c c009fc70 00000003 00000000 c04df960 00000002 be84ce38 c3fcbf94 bf60: c3fcbf6c c008a2f4 c0089f88 00008588 be84ce84 00008718 0000877c 00000005 bf80: c002c044 4013365c c3fcbfa4 c3fcbf98 c008a3a8 c008a2b0 00000000 c3fcbfa8 bfa0: c002bea0 c008a394 be84ce84 00008718 be84ce30 00000002 be84ce38 be84ce30 bfc0: be84ce84 00008718 0000877c 00000003 00008588 00000000 4013365c be84ce58 bfe0: 00000000 be84ce28 0000266c 400c98e0 60000010 be84ce30 30002031 30002431 Backtrace: //回溯信息 [<bf000000>] (first_drv_open+0x0/0x12c [26th_segmentfault]) from [<c008d888>] (chrdev_open+0x14c/0x164) r5:c3e880c0 r4:c06d7660 [<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8) r8:c3fb9534 r7:c0474e20 r6:c008d73c r5:c3e880c0 r4:c04df960 [<c0089d48>] (__dentry_open+0x0/0x1e8) from [<c0089f64>] (nameidata_to_filp+0x34/0x48) [<c0089f30>] (nameidata_to_filp+0x0/0x48) from [<c0089fb8>] (do_filp_open+0x40/0x48) r4:00000002 [<c0089f78>] (do_filp_open+0x0/0x48) from [<c008a2f4>] (do_sys_open+0x54/0xe4) r5:be84ce38 r4:00000002 [<c008a2a0>] (do_sys_open+0x0/0xe4) from [<c008a3a8>] (sys_open+0x24/0x28) [<c008a384>] (sys_open+0x0/0x28) from [<c002bea0>] (ret_fast_syscall+0x0/0x2c) Code: bf000094 bf0000b4 bf0000d4 e5952000 (e5923000) Segmentation fault
2.1上面的回溯信息,表示了函數的整個調用過程
比如上面的回溯信息表示:
- sys_open()->do_sys_open()->do_filp_open()->nameidata_to_filp()->chrdev_open()->first_drv_open();
最終錯誤出在了first_drv_open();
若內核沒有配置回溯信息顯示,則就不會列印函數調用過程,可以修改內核的.config文件,添加:
//CONFIG_FRAME_POINTER,表示幀指針,用fp寄存器表示
內核里,就會通過fp寄存器記錄函數的運行位置,並存到棧里,然後當出問題時,從棧里調出fp寄存器,查看函數的調用關係,就可以看到回溯信息.
(PS:若不配置,也可以直接通過棧來分析函數調用過程,在下章會分析到:http://www.cnblogs.com/lifexy/p/8011966.html)
2.2而有些內核的環境不同,opps也可能不會列印出上面的:
Modules linked in: 26th_segmentfault PC is at first_drv_open+0x78/0x12c [26th_segmentfault]
這些相關信息, 只列印PC值,就根本無法知道,到底是驅動模塊出的問題,還是內核自帶的函數出的問題?
所以oops里的最重要內容還是這一段: pc : [<bf000078>]
2.3那麼如何來確定,該PC值地址位於內核的函數,還是我們裝載的驅動模塊?
答:
可以在內核源碼的根目錄下通過的“vi System.map”來查看,該文件保存了內核里所有(符號、函數)的虛擬地址映射,比如下圖的內核函數root_dev_setup():
通過vi命令的:0和:$命令行,可以看到內核的虛擬地址是c0004000~c03cebf4
所以,pc值bf000078為的驅動模塊的地址值
2.4當有多個驅動裝載時,又如何區分PC值是哪個驅動的函數的地址值?
答:通過/proc/kallsyms來查看:
#cat /proc/kallsyms //(kernel all symbols)查看所有的內核標號(包括內核函數,裝載的驅動函數,變數符號等)的地址值
或者:
#cat /proc/kallsyms> /kallsyms.txt //將地址值放入kallsyms.txt中
如下圖所示,在kallsyms.txt里,找到pc值bf000078位於26th_segmentfault驅動里first_drv_open()函數下的bf000000+0x78中
2.5然後將驅動生成反彙編:
arm-linux-objdump -D 26th_segmentfault.ko >26th_segmentfault.dis //反彙編
2.6打開反彙編:
如下圖所示,左邊是kallsyms.txt,右邊是26th_segmentfault.dis反彙編
顯然pc值bf000078,就位於反彙編的78地址處:
Disassembly of section .text: //.text段起始地址為0x00 00000000 <first_drv_open>: 38: e59fc0e8 ldr ip, [pc, #232]; 128 <.text+0x128> //ip=.text段+0x128里的內容 ... ... 50: e585c000 str ip, [r5] //r5=.text段+0x128里的內容 ... ... 74: e5952000 ldr r2, [r5] //r2=.text段+0x128里的內容 78: e5923000 ldr r3, [r2] // r3=.text段+0x128里的內容 7c: e3c33c3f bic r3, r3, #16128 ;0x3f00 //清除0x56000050的bit8~13 ... ... 128: 56000050 undefined //.text段+0x128里的內容=0x56000050
(PS:其中, pc值78表示的是最後運行成功的地址,所以出錯地址在78+4上)
所以,找到了出錯地方位於first_drv_open ()函數下:
3.若發生錯誤的驅動位於內核的地址值時
3.1還是以26th_segmentfault.c為例,首先加入內核:
#cp 26th_segmentfault.c /linux-2.6.22.6/drivers/char/ //將有問題的驅動複製到字元驅動目錄下
#vi Makefile
添加:
obj-y += 26th_segmentfault.o //y:將該驅動放入內核中
3.2然後make uImage裝載新內核後,再運行測試程式,便會列印出opps信息
3.3在內核源碼的根目錄下通過:
# arm-none-linux-gnueabi-objdump -D vmlinux > vmlinux.dis
將整個內核反彙編, vmlinux:未壓縮的內核
3.4 vi vmlinux.dis,然後通過oops信息的PC值直接來查找地址即可
接下來下章便通過棧信息來分析函數調用過程:http://www.cnblogs.com/lifexy/p/8011966.html