嵌入式Linux——oops:根據oops信息,找到錯誤的產生位置以及函數的調用關係

簡介:

    本文主要介紹通過oops信息找到程序中出錯位置的方法。並結合自己代碼中的錯誤來講解如何找到出錯位置。同時還會介紹使用棧信息來推到函數間的調用關係。

 Linux內核:linux-2.6.22.6

 所用開發板:JZ2440 V3(S3C2440A)

聲明:

    本文主要是對韋東山老師視頻的總結,同時看了一些網友的博文來對這方面的信息進行補充。希望通過我的文章讓你對oops信息有更好的瞭解。

oops信息介紹:

    我們先以一個例子來介紹oops中都含有什麼信息。我們看下面這幾部分:

1  一段文本描述信息,用於描述程序出錯的原因:
Unable to handle kernel paging request at virtual address 56000050

2  Oops 信息的序號
Internal error: Oops: 5 [#1]
 bit 0 如果第0位被清0,則異常是由一個不存在的頁所引起的;否則是由無效的訪問權限引起的。
 bit 1 如果第1位被清0,則異常由讀訪問或者執行訪問所引起;否則異常由寫訪問引起。
 bit 2 如果第2位被清0,則異常發生在內核態;否則異常發生在用戶態。
Oops中的 [#1] crash發生次數。
Oops中的 PREEMPT 是指系統支持搶佔模式,有時會還會輸出SMP(多核) ARM/THUMB(指令集)等信息。

3 內核中加載的模塊名稱,也可能沒有,以下面字樣開頭。
Modules linked in: first_drv

4 發生錯誤的 CPU 的序號,對於單處理器的系統,序號爲 0,比如:
CPU: 0    Not tainted  (2.6.22.6 #1)

其中Tainted的表示可以從內核中 kernel/panic.c 中找到:

Tainted描述
‘G’if all modules loaded have a GPL or compatible license
‘P’if any proprietary module has been loaded. Modules without a MODULE_LICENSE or with a MODULE_LICENSE that is not recognised by insmod as GPL compatible are assumed to be proprietary.
‘F’if any module was force loaded by “insmod -f”.
‘S’if the Oops occurred on an SMP kernel running on hardware that hasn’t been certified as safe to run multiprocessor. Currently this occurs only on various Athlons that are not SMP capable.
‘R’if a module was force unloaded by “rmmod -f”.
‘M’if any processor has reported a Machine Check Exception.
‘B’if a page-release function has found a bad page reference or some unexpected page flags.
‘U’if a user or user application specifically requested that the Tainted flag be set.
‘D’if the kernel has died recently, i.e. there was an OOPS or BUG.
‘W’if a warning has previously been issued by the kernel.
‘C’if a staging module / driver has been loaded.
‘I’if the kernel is working around a sever bug in the platform’s firmware (BIOS or similar).
5 發生錯誤時 CPU 的各個寄存器值。
PC is at first_drv_open+0x18/0x3c [first_drv]
LR is at chrdev_open+0x14c/0x164
pc : [<bf000018>]    lr : [<c008d888>]    psr: a0000013
sp : c3ed5e88  ip : c3ed5e98  fp : c3ed5e94
r10: 00000000  r9 : c3ed4000  r8 : c049a300
r7 : 00000000  r6 : 00000000  r5 : c3e700c0  r4 : c06a4540
r3 : bf000000  r2 : 56000050  r1 : bf000964  r0 : 00000000

不同的系統中提示的可能有所不同, 不同架構對 PC/IP 寄存器的叫法不同

PC is at first_drv_open+0x18/0x3c [first_drv]

或者

EIP : first_drv_open+0x18/0x3c [first_drv]
告訴我們內核是執行到first_drv_open+0x18/0x3c [first_drv] 這個地址處出錯的, 那麼我們所需要做的就是找到這個地址對應的代碼格式爲 函數+偏移/長度
first_drv_open指示了在first_drv_open中出現的異常
0x18表示出錯的偏移位置
0x3c 表示first_drv_open函數的大小

6 當前進程的名字及進程 ID,比如:
Process firstdrvtest (pid: 783, stack limit = 0xc3ed4258)
這並不是說發生錯誤的是這個進程,而是表示發生錯誤時,當前進程是它。錯誤可能發生在內核代碼、驅動程序,也可能就是這個進程的錯誤。

7 棧信息。
Stack: (0xc3ed5e88 to 0xc3ed6000)
5e80:                   c3ed5ebc c3ed5e98 c008d888 bf000010 00000000 c049a300 
5ea0: c3e700c0 c008d73c c0474f20 c3e79724 c3ed5ee4 c3ed5ec0 c0089e48 c008d74c 
5ec0: c049a300 c3ed5f04 00000003 ffffff9c c002c044 c3cf4000 c3ed5efc c3ed5ee8 
5ee0: c0089f64 c0089d58 00000000 00000002 c3ed5f68 c3ed5f00 c0089fb8 c0089f40
5f00: c3ed5f04 c3e79724 c0474f20 00000000 00000000 c3edd000 00000101 00000001

8 棧回溯信息,可以從中看出函數調用關係,形式如下:
Backtrace: 
[<bf000000>] (first_drv_open+0x0/0x3c [first_drv]) from [<c008d888>] (chrdev_open+0x14c/0x164)
[<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8)
 r8:c3e79724 r7:c0474f20 r6:c008d73c r5:c3e700c0 r4:c049a300
[<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:bed00edc 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)

要讓內核出錯時能夠打印棧回溯信息,編譯內核時要增加“-fno-omit-frame-pointer”選項,這可以通過配置 CONFIG_FRAME_POINTER 來實現。查看內核目錄下的配置文件.config,確保 CONFIG_FRAME_POINTER 已經被定義,如果沒有,執行“make menuconfig”命令重新配置內核。CONFIG_FRAME_POINTER 有可能被其他配置項自動選上。

9 出錯指令附近的指令的機器碼,比如(出錯指令在小括號裏)

Code: e24cb004 e59f1024 e3a00000 e5912000 (e5923000) 

根據PC/IP值找到出錯的位置:

    我們主要通過PC/IP值來找到出錯的位置。這裏我們還需要其他的信息輔助。我們先將判斷位置的方法寫出,然後我們使用這個方法從上面舉例的錯誤中找到導致錯誤的出處。

判斷步驟:

一   先通過PC/IP值判斷這個錯誤是內核函數中的錯誤還是使用insmod加載的驅動程序的錯誤:

二   假設是加載驅動程序引起的錯誤

    2.1  是加載驅動程序引起的錯誤,那麼就要確定是哪個驅動程序引起的錯誤。

    2.2  確定是哪個驅動引起的錯誤之後我們就要反彙編這個驅動模塊的ko文件,得到dis文件。

    2.3  分析反彙編得到的dis文件,來確定引起錯誤的語句。

    2.4  結合上面各個寄存器的值來確定具體是那條C語言語句引起的錯誤。

三   假設是內核函數引起的錯誤

    3.1   是內核函數引起的錯誤,那麼反彙編內核文件,得到dis文件

    3.2   在內核的反彙編文件中以PC/IP值進行搜索,得到出錯的函數和出錯的語句。

    3.3    結合上面各個寄存器的值來確定具體是那條C語言語句引起的錯誤。

    好了,有了上面的方法我們現在就以上一個錯誤的oops信息爲例,來找出是哪裏出了錯誤。

# ./firstdrvtest   
Unable to handle kernel paging request at virtual address 56000050
pgd = c3edc000
[56000050] *pgd=00000000
Internal error: Oops: 5 [#1]
Modules linked in: first_drv
CPU: 0    Not tainted  (2.6.22.6 #1)
PC is at first_drv_open+0x18/0x3c [first_drv]
LR is at chrdev_open+0x14c/0x164
pc : [<bf000018>]    lr : [<c008d888>]    psr: a0000013
sp : c3ed5e88  ip : c3ed5e98  fp : c3ed5e94
r10: 00000000  r9 : c3ed4000  r8 : c049a300
r7 : 00000000  r6 : 00000000  r5 : c3e700c0  r4 : c06a4540
r3 : bf000000  r2 : 56000050  r1 : bf000964  r0 : 00000000
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment user
Control: c000717f  Table: 33edc000  DAC: 00000015
Process firstdrvtest (pid: 783, stack limit = 0xc3ed4258)
Stack: (0xc3ed5e88 to 0xc3ed6000)
5e80:                   c3ed5ebc c3ed5e98 c008d888 bf000010 00000000 c049a300
5ea0: c3e700c0 c008d73c c0474f20 c3e79724 c3ed5ee4 c3ed5ec0 c0089e48 c008d74c
5ec0: c049a300 c3ed5f04 00000003 ffffff9c c002c044 c3cf4000 c3ed5efc c3ed5ee8 
5ee0: c0089f64 c0089d58 00000000 00000002 c3ed5f68 c3ed5f00 c0089fb8 c0089f40
5f00: c3ed5f04 c3e79724 c0474f20 00000000 00000000 c3edd000 00000101 00000001
5f20: 00000000 c3ed4000 c046de08 c046de00 ffffffe8 c3cf4000 c3ed5f68 c3ed5f48 
5f40: c008a16c c009fc70 00000003 00000000 c049a300 00000002 bed00edc c3ed5f94 
5f60: c3ed5f6c c008a2f4 c0089f88 00008520 bed00ed4 0000860c 00008670 00000005 
5f80: c002c044 4013365c c3ed5fa4 c3ed5f98 c008a3a8 c008a2b0 00000000 c3ed5fa8 
5fa0: c002bea0 c008a394 bed00ed4 0000860c 00008720 00000002 bed00edc 00000001 
5fc0: bed00ed4 0000860c 00008670 00000001 00008520 00000000 4013365c bed00ea8 
5fe0: 00000000 bed00e84 0000266c 400c98e0 60000010 00008720 4021a2cc 4021a2dc 
Backtrace: 
[<bf000000>] (first_drv_open+0x0/0x3c [first_drv]) from [<c008d888>] (chrdev_open+0x14c/0x164)
[<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8)
 r8:c3e79724 r7:c0474f20 r6:c008d73c r5:c3e700c0 r4:c049a300
[<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:bed00edc 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: e24cb004 e59f1024 e3a00000 e5912000 (e5923000) 

    從中我們發現這裏的pc 爲 bf000018,那麼我們如何判斷他是屬於內核還是屬於加載模塊引發的錯誤那?

    我們要通過內核目錄下的System.map文件來確定內核函數的地址範圍。如果上面的PC值在System.map文件中內核函數的範圍之內,那麼這個錯誤就是有內核函數引起的,否則就是由加載模塊引起的。而我的System.map中內核函數的範圍是:

c0004000 A swapper_pg_dir
c0008000 T __init_begin
c0008000 T _sinittext
c0008000 T stext
c0008000 T _stext
·············
c03cdb44 b ratelimit.1
c03cdb48 b registered_mechs_lock
c03cdb48 b rsi_table
c03cdc48 b rsc_table
c03cec48 B krb5_seq_lock
c03cec4c b i.0
c03cec54 B _end

    c0004000~c03cec54,而我的PC值爲bf000018,所以不是內核函數引起的錯誤,而是加載模塊引起的錯誤,同時在內核模型中一般有:

地址
作用
說明
>=0xc000 0000
內核虛擬存儲器
用戶代碼不可見區域
<0xc000 0000
Stack(用戶棧)
ESP指向棧頂
 
 
 
空閒內存
>=0x4000 0000
文件映射區
 
<0x4000 0000
 
 
 
空閒內存
 
 
Heap(運行時堆)
通過brk/sbrk系統調用擴大堆,向上增長。
 
.data、.bss(讀寫段)
從可執行文件中加載
>=0x0804 8000
.init、.text、.rodata(只讀段)
從可執行文件中加載
<0x0804 8000
保留區域
 

    上面的表格來自於:linux的內存模

    下一步我們就要確定是哪個驅動模塊引起的錯誤,當然我們上面的oops信息中已經告訴我們:

PC is at first_drv_open+0x18/0x3c [first_drv]

    是first_drv驅動引入的錯誤,那如果我們沒有上面的提示信息該怎麼辦啊?

   我們要去看內核的/proc/kallsyms 文件,從中找到與PC值相近的值的所在的函數,最好是比PC值要小一些。我們使用命令:cat /proc/kallsyms > /kallsyms.txt將/proc/kallsyms文件的內容放到一個TXT文件中。我們看這個文件中的信息:


    從上面紅色邊框中我們看出,這個bf000000與我們的PC值bf000018十分相近。同時我們觀察發現PC值包含在first_drv的函數值中,所以在此可以確定我們出錯的模塊爲first_drv模塊。同時我們從紅色方框中發現bf000000位置對應的是first_drv_open函數。

    下面我們就要進行下一步:反彙編該模塊的ko文件。在這裏我們使用:arm-linux-objdump -D first_drv.ko > first_drv.dis 命令來將first_drv的ko文件轉化爲其反彙編的dis文件。

    那麼下面我們就要對比PC值與反彙編文件了:

dis文件中的函數和地址iinsmod加載文件中的函數和地址
00000000 <first_drv_open>:bf000000 t first_drv_open
00000018 (出錯的行)bf000018

    通過上面的分析我們就可以找到出錯的行在哪裏了:

00000000 <first_drv_open>:
   0:	e1a0c00d 	mov	ip, sp
   4:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
   8:	e24cb004 	sub	fp, ip, #4	; 0x4
   c:	e59f1024 	ldr	r1, [pc, #36]	; 38 <__mod_vermagic5>
  10:	e3a00000 	mov	r0, #0	; 0x0
  14:	e5912000 	ldr	r2, [r1]
  18:	e5923000 	ldr	r3, [r2]  //這裏出錯了

    不過這裏是彙編語句,我們要想將這個這裏的彙編語句對應到我們的C語句就要藉助於其他的信息了。例如我們這時各個寄存器中的值:

pc : [<bf000018>]    lr : [<c008d888>]    psr: a0000013
sp : c3ed5e88  ip : c3ed5e98  fp : c3ed5e94
r10: 00000000  r9 : c3ed4000  r8 : c049a300
r7 : 00000000  r6 : 00000000  r5 : c3e700c0  r4 : c06a4540
r3 : bf000000  r2 : 56000050  r1 : bf000964  r0 : 00000000

    通過上面的寄存器值,並結合我們的彙編語言知識我們不難將錯誤找出。而我們這裏其實就是在對開發板寄存器操作時,沒有使用ioremap函數引起的。

內核引起錯誤:

    我將上面這個有錯誤的文件放到內核驅動中的char文件夾下,並修改相應的Makefile文件 vi /drivers/char/Makefile。在其中加入: obj-y += first_drv.o 。然後我們重新編譯內核得到新的uImage,我們加載新的uImage。這時候這個錯誤就是內核引起的了。

    這時候我們看oops信息發現這時候的PC值變爲了:c014e6c0了。而我們System.map中內核函數的範圍是:c0004000 ~ c03cec54。而我們這個時候要使用命令:arm-linux-objdump -D vmlinux > vmlinux.dis 來得到內核的反彙編文件。然後我們在文件中找PC值對應的行,從中我們就可以確定是在哪裏出處,我們結合寄存器相關的信息就可以找出出錯的位置了。

通過棧信息確定函數調用關係:

    我們以上面驅動模塊中出錯的oops信息來分析函數的調用關係。我們知道其實上面已經有了這個函數調用關係,那就是

Backtrace: 
[<bf000000>] (first_drv_open+0x0/0x3c [first_drv]) from [<c008d888>] (chrdev_open+0x14c/0x164)
[<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8)
 r8:c3e79724 r7:c0474f20 r6:c008d73c r5:c3e700c0 r4:c049a300
[<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:bed00edc 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: e24cb004 e59f1024 e3a00000 e5912000 (e5923000) 

    從上面我們可以看出出錯函數的調用關係,但是如果我們的oops中沒有Backtrace怎麼辦啊?這時候我們就要通過棧信息自己來推出函數調用關係了。這裏我們需要內核的反彙編文件,因爲我們是內核中的函數調用驅動模塊中的函數。

    而在講解如何推出調用關係之前,我想先介紹一下我們要用到的寄存器:

1. 寄存器R14被稱爲鏈接寄存器(LR),存放每種模式下,當前子程序的返回地址或者發生異常中斷的時候,將R14設置成異常模式將要返回的地址。

2. 寄存器R13(SP),通常用作堆棧指針,每一種模式都有自己的物理R13,程序初始化R13。當進入該模式時,可以將要使用的寄存器保存在R13所指的棧中,當退出時,將彈出,從而實現了現場保護。

    我們現在假設有三個函數A函數,B函數,C函數。他們的調用關係爲C函數調用B函數,而B函數調用A函數。我們可以以僞代碼的形式表達爲:

C(void){
	B();
	其他代碼;
}

B(void){
	A();
	其他代碼;
}

A(void){
	其他代碼;
}

    而他對應的調用關係圖爲:


    從上面的圖中我們知道,我們的C函數調用B函數,在進入B函數後,B函數會將C函數的LR壓入棧中,當B函數運行完成後,他會調用LR的值返回到C函數中。同樣在B函數中調用A函數,進入A函數後先將B函數的LR壓入棧中,當A函數完成後,他會調用LR的值返回B函數。

    而現在我們知道了A函數,並且知道了A函數棧中B函數的LR值。那麼我們就可以找到B函數。而找到B函數後我們知道B函數棧中LR的值我們就可以找到C函數。我們以此類推就可以找到函數的調用關係了。我們就是利用這個原理來從這些棧信息中找到調用關係。

    好了,有了上面的知識我們現在再看我們oops中的棧信息:

Stack: (0xc3ed5e88 to 0xc3ed6000)
5e80:                   c3ed5ebc c3ed5e98 c008d888 bf000010 00000000 c049a300 
5ea0: c3e700c0 c008d73c c0474f20 c3e79724 c3ed5ee4 c3ed5ec0 c0089e48 c008d74c 
5ec0: c049a300 c3ed5f04 00000003 ffffff9c c002c044 c3cf4000 c3ed5efc c3ed5ee8 
5ee0: c0089f64 c0089d58 00000000 00000002 c3ed5f68 c3ed5f00 c0089fb8 c0089f40 
5f00: c3ed5f04 c3e79724 c0474f20 00000000 00000000 c3edd000 00000101 00000001 
5f20: 00000000 c3ed4000 c046de08 c046de00 ffffffe8 c3cf4000 c3ed5f68 c3ed5f48 
5f40: c008a16c c009fc70 00000003 00000000 c049a300 00000002 bed00edc c3ed5f94 
5f60: c3ed5f6c c008a2f4 c0089f88 00008520 bed00ed4 0000860c 00008670 00000005 
5f80: c002c044 4013365c c3ed5fa4 c3ed5f98 c008a3a8 c008a2b0 00000000 c3ed5fa8 
5fa0: c002bea0 c008a394 bed00ed4 0000860c 00008720 00000002 bed00edc 00000001 
5fc0: bed00ed4 0000860c 00008670 00000001 00008520 00000000 4013365c bed00ea8 
5fe0: 00000000 bed00e84 0000266c 400c98e0 60000010 00008720 4021a2cc 4021a2dc 

    我們通過上面的分析知道我們加載模塊的出錯函數爲first_drv_open,所以我們看他的反彙編代碼:

00000000 <first_drv_open>:
   0:	e1a0c00d 	mov	ip, sp
   4:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
   8:	e24cb004 	sub	fp, ip, #4	; 0x4
   c:	e59f1024 	ldr	r1, [pc, #36]	; 38 <__mod_vermagic5>
  10:	e3a00000 	mov	r0, #0	; 0x0
  14:	e5912000 	ldr	r2, [r1]
  18:	e5923000 	ldr	r3, [r2]

    從上面的代碼我們看出對棧的操作只有:e92dd800 stmdb sp!, {fp, ip, lr, pc},而在棧裏面他是高寄存器在高位,所以PC值在最高位,而LR次之,接下來依次爲IP和FP。而他佔用四個棧信息值。而在其中LR爲倒數第二個。所以在上面的棧信息中有:

5e80:                   c3ed5ebc c3ed5e98 c008d888 bf000010 00000000 c049a300 
			<first_drv_open>: lr		    caller'sp

    同時我們知道LR中存放的就是上一個調用函數的返回地址,所以我們可以通過first_drv_open的LR值c008d888找到他的調用函數。我們在內核的反彙編文件中搜c008d888這個地址,看他屬於那個函數:

c008d73c <chrdev_open>:
c008d73c:	e1a0c00d 	mov	ip, sp
c008d740:	e92dd9f0 	stmdb	sp!, {r4, r5, r6, r7, r8, fp, ip, lr, pc}
c008d744:	e24cb004 	sub	fp, ip, #4	; 0x4
c008d748:	e24dd004 	sub	sp, sp, #4	; 0x4
c008d74c:	e59040e4 	ldr	r4, [r0, #228]
·······
c008d874:	0a000006 	beq	c008d894 <chrdev_open+0x158>
c008d878:	e1a00005 	mov	r0, r5
c008d87c:	e1a01008 	mov	r1, r8
c008d880:	e1a0e00f 	mov	lr, pc
c008d884:	e1a0f003 	mov	pc, r3
c008d888:	e2507000 	subs	r7, r0, #0	; 0x0
c008d88c:	11a00004 	movne	r0, r4
······

    從上面我們知道first_drv_open的調用函數爲chrdev_open函數,同時我們還從上面的反彙編代碼中看出在chrdev_open函數中對棧SP的操作只有:

c008d740:	e92dd9f0 	stmdb	sp!, {r4, r5, r6, r7, r8, fp, ip, lr, pc}
c008d748:	e24dd004 	sub	sp, sp, #4	; 0x4

    這裏面一共移動棧10個位置,其中下面sub sp, sp, #4 ; 0x4 因爲這是32位的系統,所以4字節表示一個棧地址。同時LR信息同樣在倒數第二位。而對應到上面的棧信息中:

5e80:                   c3ed5ebc c3ed5e98 c008d888 bf000010 00000000 c049a300 
			<first_drv_open>: lr		    <chrdev_open>

5ea0: c3e700c0 c008d73c c0474f20 c3e79724 c3ed5ee4 c3ed5ec0 c0089e48 c008d74c 
                                                             lr
5ec0: c049a300 c3ed5f04 00000003 ffffff9c c002c044 c3cf4000 c3ed5efc c3ed5ee8 
      caller'sp

    下面我們就要在內核的反彙編文件中找c0089e48對應的函數了:

c0089d48 <__dentry_open>:
c0089d48:	e1a0c00d 	mov	ip, sp
c0089d4c:	e92dddf0 	stmdb	sp!, {r4, r5, r6, r7, r8, sl, fp, ip, lr, pc}
c0089d50:	e24cb004 	sub	fp, ip, #4	; 0x4
······
c0089e40:	e1a0e00f 	mov	lr, pc
c0089e44:	e1a0f006 	mov	pc, r6
c0089e48:	e250a000 	subs	sl, r0, #0	; 0x0
c0089e4c:	1a000019 	bne	c0089eb8 <__dentry_open+0x170>
c0089e50:	e5943018 	ldr	r3, [r4, #24]
······

    從上面看chrdev_open函數由__dentry_open函數調用,同時我們還知道了棧操作信息:

c0089d4c:	e92dddf0 	stmdb	sp!, {r4, r5, r6, r7, r8, sl, fp, ip, lr, pc}

    那麼通過上面的信息我們就可以知道__dentry_open的調用函數了。這樣以此類推我們就知道內核中對first_drv_open函數的調用關係了。這裏我將他們全部的關係貼出:

Stack: (0xc3ed5e88 to 0xc3ed6000)
5e80:                   c3ed5ebc c3ed5e98 c008d888 bf000010 00000000 c049a300 
		        <first_drv_open>: lr		    <chrdev_open>
5ea0: c3e700c0 c008d73c c0474f20 c3e79724 c3ed5ee4 c3ed5ec0 c0089e48 c008d74c 
							     lr
5ec0: c049a300 c3ed5f04 00000003 ffffff9c c002c044 c3cf4000 c3ed5efc c3ed5ee8 
      <__dentry_open>
5ee0: c0089f64 c0089d58 00000000 00000002 c3ed5f68 c3ed5f00 c0089fb8 c0089f40
       lr		<nameidata_to_filp>		     lr
5f00: c3ed5f04 c3e79724 c0474f20 00000000 00000000 c3edd000 00000101 00000001
      <do_filp_open>
5f20: 00000000 c3ed4000 c046de08 c046de00 ffffffe8 c3cf4000 c3ed5f68 c3ed5f48 
5f40: c008a16c c009fc70 00000003 00000000 c049a300 00000002 bed00edc c3ed5f94 
5f60: c3ed5f6c c008a2f4 c0089f88 00008520 bed00ed4 0000860c 00008670 00000005 
		lr		 <do_sys_open>:
5f80: c002c044 4013365c c3ed5fa4 c3ed5f98 c008a3a8 c008a2b0 00000000 c3ed5fa8 
   					   lr	 	    <sys_open> 
5fa0: c002bea0 c008a394 bed00ed4 0000860c 00008720 00000002 bed00edc 00000001 
       lr								
5fc0: bed00ed4 0000860c 00008670 00000001 00008520 00000000 4013365c bed00ea8 
5fe0: 00000000 bed00e84 0000266c 400c98e0 60000010 00008720 4021a2cc 4021a2dc 

    而棧回溯信息爲:

Backtrace: 
[<bf000000>] (first_drv_open+0x0/0x3c [first_drv]) from [<c008d888>] (chrdev_open+0x14c/0x164)
[<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8)
 r8:c3e79724 r7:c0474f20 r6:c008d73c r5:c3e700c0 r4:c049a300
[<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:bed00edc 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)
    從上面可以看出這兩個是對應的。也證明我們的推導是正確的。


參考文章:

Linux Kernel PANIC(三)--Soft Panic/Oops調試及實例分析
Oops中的error code解釋
linux的內存模型
linux中Oops信息的調試及棧回溯—Linux人都知道,這是好東西!
36.Linux驅動調試-根據oops定位錯誤代碼行
37.Linux驅動調試-根據oops的棧信息,確定函數調用過程


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章