1.在之前第36章里,我們學習了通過驅動的oops定位錯誤代碼行 第36章的oops代碼如下所示: 1.1那為什麼在上一章,我們用錯誤的應用程式,卻沒有列印oops,如下圖所示: 接下來,我們便來配置內核,從而列印應用程式的oops 2.首先來搜索oops里的:Unable to handle ke ...
1.在之前第36章里,我們學習了通過驅動的oops定位錯誤代碼行
第36章的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) //棧信息,從棧底0xc3fcbe80到棧頂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
1.1那為什麼在上一章,我們用錯誤的應用程式,卻沒有列印oops,如下圖所示:
接下來,我們便來配置內核,從而列印應用程式的oops
2.首先來搜索oops里的:Unable to handle kernel列印語句,看在哪個函數列印的
如下圖所示,找到位於__do_kernel_fault()函數中:
3.繼續找,發現__do_kernel_fault()被do_bad_area()調用
do_bad_area()函數,從字面上分析,表示代碼執行到錯誤段位置
其中user_mode(regs)函數,通過判斷CPSR寄存器若是用戶模式則返回0,否則返回正數.
所以我們上一章的錯誤的應用程式便會調用__do_user_fault()函數
4.__do_user_fault()函數如下所示:
從上圖來看,要想列印應用程式的錯誤信息,還需要:
3.1配置內核,設置巨集CONFIG_DEBUG_USER(只要巨集是以"CONFIG_"開頭,都是與配置相關)
1)在make menuconfig里搜索DEBUG_USER,如下圖所示:
所以將Kernel hacking-> Verbose user fault messages 置為Y,並重新燒內核
3.2使if (user_debug & UDBG_SEGV)為真
1)其中user_debug定義如下所示:
顯然當uboot傳遞進來的命令行字元里含有"user_debug="時,便會調用user_debug_setup()->get_option(),最終會將"user_debug="後面帶的字元串提取給user_debug變數.
比如:當命令行字元里含有"user_debug=0xff"時,則user_debug變數等於0xff
2)其中UDBG_SEGV定義如下所示:
#define UDBG_UNDEFINED (1 << 0) //用戶態的代碼出現未定義指令(UNDEFINED) #define UDBG_SYSCALL (1 << 1) //用戶態系統調用已過時(SYSCALL) #define UDBG_BADABORT (1 << 2) //用戶態數據錯誤已中止(BADABORT) #define UDBG_SEGV (1 << 3) //用戶態的代碼出現段錯誤(SEGV) #define UDBG_BUS (1 << 4) //用戶態訪問忙(BUS)
從上面的定義分析得出,我們只需要將user_debug設為0xff,上面的所有條件就都成立.
比如:當用戶態的代碼出現未定義指令時,由於user_debug最低位=1,所以列印出oops.
所以,進入uboot,在uboot命令行里添加: "user_debug=0xff"
4. 啟動內核,試驗
如下圖所示,執行錯誤的應用程式,只列印了各個寄存器值,以及函數調用關係,而沒有棧信息:
5.接下來,繼續修改內核,使應用程式的oops也列印棧信息出來
在驅動的oops里有"Stack: "這個欄位,搜索"Stack: "看看,位於哪個函數
5.1如下圖所示, 找到位於__die()函數中:
這個__die()會被die()調用,die()又會被__do_kernel_fault()調用,而我們應用程式調用的__do_user_fault()里沒有die()函數,所以沒有列印出Stack棧信息。
上圖裡dump_mem():
dump_mem("Stack: ", regs->ARM_sp,THREAD_SIZE + (unsigned long)task_stack_page(tsk)); //列印stack棧信息
主要是通過sp寄存器里存的棧地址,每列印一個棧地址里的32位數據, 棧地址便加4(一個地址存8位,所以加4)。
接下來我們便通過這個原理,來修改應用程式調用的__do_user_fault()
5.2 在__do_user_fault(),添加以下帶紅色的字:
static void __do_user_fault(struct task_struct *tsk, unsigned long addr,unsigned int fsr, unsignedint sig, int code,struct pt_regs *regs)
{
struct siginfo si;
unsigned long val ;
int i=0;
#ifdef CONFIG_DEBUG_USER
if (user_debug & UDBG_SEGV) {
printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n",
tsk->comm, sig, addr, fsr);
show_pte(tsk->mm, addr);
show_regs(regs);
printk("Stack: \n");
while(i<1024)
{
/* copy_from_user()只是用來檢測該地址是否有效,如有效,便獲取地址數據,否則break */
if(copy_from_user(&val, (const void __user *)(regs->ARM_sp+i*4), 4))
break;
printk("%08x ",val); //列印數據
i++;
if(i%8==0)
printk("\n");
}
printk("\n END of Stack\n");
}
#endif
tsk->thread.address = addr;
tsk->thread.error_code = fsr;
tsk->thread.trap_no = 14;
si.si_signo = sig;
si.si_errno = 0;
si.si_code = code;
si.si_addr = (void __user *)addr;
force_sig_info(sig, &si, tsk);
}
6.重新燒寫內核,試驗
如下圖所示:
接下來,便來分析PC值,Stack棧,到底如何調用的
7.首先來分析PC值,確定錯誤的代碼
1)生成反彙編:
arm-linux-objdump -D test_debug > test_debug.dis
2)搜索PC值84ac,如下圖所示:
從上面看出,主要是將0x12(r3)放入地址0x00(r2)中
而0x00是個非法地址,所以出錯
8.分析Stack棧信息,確定函數調用過程
參考: 37.Linux驅動調試-根據oops的棧信息,確定函數調用過程
8.1分析過程中,遇到main()函數的返回地址為:LR=40034f14
內核的虛擬地址是c0004000~c03cebf4,而反彙編里也沒有該地址,所以這是個動態庫的地址.
需要用到靜態鏈接方法,接下來重新編譯,反彙編,運行:
#arm-linux-gcc -o -static test_debug test_debug.c //-static 靜態鏈接,生成的文件會非常大, 好處在於不需要動態鏈接庫,也可以運行 #arm-linux-objdump -D test_debug > test_debug.dis
8.2最終, 找到main()函數的返回地址在__lobc_start_main()里
所以函數出錯時的調用過程:
__lobc_start_main()-> main()-> A()-> B()-> C() //將0x12(r3)放入地址0x00(r2)中