嵌入式Linux——oops:根据oops信息,找到错误的产生位置以及函数的调用关系

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/W1107101310/article/details/80611880

简介:

    本文主要介绍通过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的栈信息,确定函数调用过程


猜你喜欢

转载自blog.csdn.net/W1107101310/article/details/80611880