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

原文來源:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=29401328&id=4923529, 感謝原著。


接着上一篇來分析一下Oops的棧


s3c2440平臺

關於調試源碼和整個Oops信息請參考上一篇博文,這裏只再次貼出關於棧的信息

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 

那麼這一堆棧指示的是什麼信息?
指示的是函數的回溯信息,我們在上一節看到了函數的回溯信息,就是函數的調用關係,棧信息不僅包含了回溯信息,
還包含了各個函數相關的寄存器,當函數切換時,會把相關的寄存器保存在自己的堆棧中,出錯時,會層層打印出來。
這樣會有助於我們調試,因爲在內核函數中,很多數據不是在當前函數中確定的,而是由上一個函數傳過來的,所以
有必要知道這麼一種調用關係

下面說一下這種調用關係:

A()
{
……
B();
……
}

B()
{
……
C();
……
}

C()
{
……
}

在上面的函數調用關係中,A函數會調用B函數,B函數會調用C函數,C函數執行完之後會返回B函數,B函數執行完之後返回A函數。
那麼爲什麼能夠返回去呢?
肯定是每次發生調用切換函數時,在自己的棧中保存了一個寄存器,在函數執行完後,根據這個寄存器能夠返回到調用這個函數的
代碼中,這個寄存器就是lr寄存器。

每個函數都有自己的堆棧區來保存相關的寄存器,通過sp寄存器找到這個地址。如下圖所示:


假如在上面的調用中,C函數出錯了,會打印出相關的堆棧信息供我們分析。按什麼順序打印呢?
先打印C函數堆棧中的寄存器,在根據C堆棧中的lr寄存器找出調用它的函數,這裏是B函數,然後打印出B函數
堆棧中的寄存器,同樣根據B的lr寄存器找到A函數並打印,就是這樣一層一層往上推的。

現在來分析棧的打印信息

首先我們知道錯誤處在了segment_test_open函數中,來看一下這個函數的彙編代碼,確定一下它的堆棧中存了那些
寄存器
00000000 :
   0:   e1a0c00d        mov     ip, sp
   4:   e92dd800        push    {fp, ip, lr, pc}
看到這裏,由第二行知道它的堆棧中存了四個寄存器,出錯時會把這個函數堆棧中這四個寄存器打印出來,就是我們看到的
前四個棧的前四個數據:
c3a61e64 c3a61e40 c00a8580 bf0d7010

這裏棧地址是向上增長的,即最低地址存的是fp寄存器,fp = c3a61e64,pc = bf0d7010。如下圖所示


我們根據lr寄存器來找調用它的函數,這裏 lr = c00a8580,怎麼根據這個地址去查找是在哪個函數呢?
首先反彙編內核(arm-none-linux-gnueabi-objdump -D vmlinux > vmlinux.dis),然後在內核的彙編代碼中查找這個地址。

找到這個地址之後,向上翻看,可以看到這個地址是在 c00a84b4 :裏面,也就是chrdev_open函數,就是說下一個
要打印的堆棧信息將是這個函數的。看一下這個函數的堆棧大小及寄存器

c00a84b8:       e92dd8f0        push    {r4, r5, r6, r7, fp, ip, lr, pc}
……
c00a84c0:       e24dd008        sub     sp, sp, #8      ; 0x8

這裏看出chrdev_open函數的堆棧大小是 40字節,會打印出十個寄存器值(每個寄存器佔4字節),lr寄存器是倒數第二個,
接下來的十個寄存器是:
c00adba8 00000000 00000000 c3b46100 c3ab84b0 c00a84b4 c3a61e8c c3a61e68 c00a3a7c c00a84c4

可以看出 lr = c00a3a7c

再去查找這個地址,知道是在 __dentry_open函數中,這個函數的反彙編:
c00a39b0:       e92dddf0        push    {r4, r5, r6, r7, r8, sl, fp, ip, lr, pc}

同樣是十個寄存器,接下來是:
c3b46100 c2c0ae40 00000003 c3af0000 00000026 c3a61ed8 c3a61eac c3a61e90 c00a3d14 c00a39bc

lr = c00a3d14,這個地址在nameidata_to_filp函數中,然後再根據它的堆棧信息,層層網上查找,
最後會根據c0044e60地址到ret_fast_syscall 函數,再之後的地址
0000850c 00000000 00008590 …………
已經不是內核函數了,這個函數就是發生在內核調用中的最上層函數。

誰調用ret_fast_syscall函數的?
根據我們之前對系統調用的分析知道,當發生系統調用open時,會產生swi異常,陷入到內核,這個函數就是陷入內核時
首先執行的函數,然後層層調用找到我們要調用的segment_test_open函數,通過這個分析我們也可以看到系統調用open的
調用過程。

這篇博文用到了寄存器的知識,有時間再介紹
發佈了34 篇原創文章 · 獲贊 4 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章