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

2023-11-17

简介:

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

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 移远EC20模组MQTT连接阿里云平台

    一 实现原理 在开始操作前说一下MQTT的实现的原理 MQTT协议 Message Queuing Telemetry Transport 消息队列遥测传输 是IBM开发的一个即时通讯协议 是为大量计算能力有限 且工作在低带宽 不可靠的网络
  • 使用 gvm 来快速安装或者升级 golang 版本

    gvm 是 golang 的版本管理工具 有点类似于 python 的 pyenv 一 安装 gvm bash lt lt curl s S L https raw githubusercontent com moovweb gvm mas
  • 自然对数e的来历

    e是自然对数的底数 是一个无限不循环小数 其值是2 71828 是这样定义的 当n gt 时 1 1 n n的极限 注 x y表示x的y次方 随着n的增大 底数越来越接近1 而指数趋向无穷大 那结果到底是趋向于1还是无穷大呢 其实 是趋向于
  • win10与Linux虚拟机进行文件共享

    由于工作需要使用到linux虚拟机 为了实现win10与Linux虚拟机进行文件共享 分享教程如下 1 在windows系统中设置文件共享 设置过程参考我之前的文章 局域网内共享文件夹 2 虚拟机设置共享文件夹 1 设置 gt 共享文件夹
  • openGLAPI之glPolygonOffset

    openGL系列文章目录 文章目录 openGL系列文章目录 前言 一 glPolygonOffset官方文档 二 翻译 前言 openGLAPI之glPolygonOffset函数详解 一 glPolygonOffset官方文档 glPo
  • Android Dalvik VM GC options 命令控制参数

    else if strncmp argv i Xgc 5 0 In VM thread there is a register map for marking each stack item s status whether it is a
  • YOLOv3的交通灯检测,ROS下实现交通灯检测一样只需要相应文件夹下面修改之后编译即可

    YOLOv3的交通灯检测 效果 只是需要修改源码image c即可修改如下 这里的0和9就是只检测行人和交通灯 对应的数字设置自己想检测的类型 可以查看coco names文件下 完成修改之后 make clean make j 重新编译即
  • MySQL常用的数据类型

    MySQL的常用数据类型包括 整型 Int TINYINT SMALLINT MEDIUMINT INT BIGINT 浮点型 Float FLOAT DOUBLE DECIMAL 字符串类型 String CHAR VARCHAR TEX
  • SpringBoot项目用 jQuery webcam plugin实现调用摄像头拍照并保存图片

    参考博客 http www voidcn com article p oigngyvb kv html 自定义样式
  • visual studio附加选项/Tc、/Tp、/TC、/TP(指定源文件类型)

    Tc 选项指定 filename 为 C 源文件 即使它没有 c 扩展名 Tp 选项指定 filename 为 C 源文件 即使它没有 cpp 或 cxx 扩展名 选项和 filename 之间的空格是可选的 每个选项指定一个文件 若要指定
  • 多元函数可微性知识点总结

    这节的知识点也挺多 主要就是可微和偏导数存在的关系 偏导数 z f x y z对x或者y的偏导数就是把另一个当做常数求导 还算简单 判断可微性 必要条件 可以写成偏导数都存在 且可以写成 d z f x d
  • 【Matlab】simlink自动生成嵌入式代码配置教程

    1 简介 最近在学习模型开发 相较于普通嵌入式代码开发来说 其能够进行仿真 并且能够使各模块之间的逻辑更加清晰 simlink自动生成代码过程较多 因此记录下来 方便日后参考 2 搭建simlink相关模块 3 进行求解器等相关配置 配置求
  • C++的四种强制转换

    1 static cast 基本等价于隐式转换的一种类型转换运算符 以前是编译器自动隐式转换 static cast可使用于需要明确隐式转换的地方 c 中用static cast用来表示明确的转换 可以用于低风险的转换 整型和浮点型 字符与
  • 量化交易的历史

    学习目标 了解量化交易的发展历史 量化交易全球的发展历史 量化投资的产生 60年代 1969年 爱德华 索普利用他发明的 科学股票市场系统 实际上是一种股票权证定价模型 成立了第一个量化投资基金 索普也被称之为量化投资的鼻祖 量化投资的兴起
  • bWAPP通关记录(A1)

    安装 这里使用的是phpstudy进行搭建的 先下载bWAPP 打开解压之后bWAPP目录下的admin中的settings php 将数据库连接名和密码改为与phpmyadmin相同的 保存 浏览器访问 点击here 点击Login 默认
  • kuka机器人焊接编程入门教程_焊接机器人操作编程与应用教学.pptx

    ABB MOTOMAN FANUC KUKA OTC机器人 第1章 机器人基础知识 第1章 机器人基础知识 第1章 机器人基础知识 第1章 机器人基础知识 第1章 机器人基础知识 第1章 机器人基础知识 第1章 机器人基础知识 第1章 机器
  • 小程序的文件结构

    小程序的文件结构 我们利用微信开发者工具创建一个新的小程序 会有包含一个描述整体程序的app和多个描述各自页面的page 一个小程序的主体部分由三个文件组成 必须放在项目的根目录 gt app js 小程序的主要逻辑判断 gt gt app
  • 重新学防抖debounce和节流throttle,及react hook模式中防抖节流的实现方式和注意事项

    重学节流和防抖 防抖 概念理解 防抖就是指触发事件后在 n 秒内函数只能执行一次 如果在 n 秒内又触发了事件 则会重新计算函数执行时间 举例说明 坐电梯的时候 如果电梯检测到有人进来 触发事件 就会多等待 10 秒 此时如果又有人进来 1
  • 300 倍的性能提升!PieCloudDB Database 优化器「达奇」又出新“招”啦

    随着数字化进程的加快 全球数据量呈指数级增长 对数据库系统的性能提出了巨大的挑战 优化器作为数据库管理系统中的关键技术 对数据库性能和效率具有重要影响 它通过对用户的查询请求进行解析和优化来生成高效的执行计划 进而快速返回查询结果 然而 同
  • 嵌入式Linux——oops:根据oops信息,找到错误的产生位置以及函数的调用关系

    简介 本文主要介绍通过oops信息找到程序中出错位置的方法 并结合自己代码中的错误来讲解如何找到出错位置 同时还会介绍使用栈信息来推到函数间的调用关系 Linux内核 linux 2 6 22 6 所用开发板 JZ2440 V3 S3C24