根据oops信息,通过addr2line找问题

转:
https://blog.csdn.net/W1107101310/article/details/80611880?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase

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 中找到:

在这里插入图片描述
5 发生错误时 CPU 的各个寄存器值
PC is at first_drv_open+0x18/0x3c [first_drv]
LR is at chrdev_open+0x14c/0x164
pc : [] lr : [] 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 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:
[] (first_drv_open+0x0/0x3c [first_drv]) from [] (chrdev_open+0x14c/0x164)
[] (chrdev_open+0x0/0x164) from [] (__dentry_open+0x100/0x1e8)
r8:c3e79724 r7:c0474f20 r6:c008d73c r5:c3e700c0 r4:c049a300
[] (__dentry_open+0x0/0x1e8) from [] (nameidata_to_filp+0x34/0x48)
[] (nameidata_to_filp+0x0/0x48) from [] (do_filp_open+0x40/0x48)
r4:00000002
[] (do_filp_open+0x0/0x48) from [] (do_sys_open+0x54/0xe4)
r5:bed00edc r4:00000002
[] (do_sys_open+0x0/0xe4) from [] (sys_open+0x24/0x28)
[] (sys_open+0x0/0x28) from [] (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,所以不是内核函数引起的错误,而是加载模块引起的错误,同时在内核模型中一般有:
在这里插入图片描述
下一步我们就要确定是哪个驱动模块引起的错误,当然我们上面的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值与反汇编文件了:
在这里插入图片描述
通过上面的分析我们就可以找到出错的行在哪里了:

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)

从上面可以看出这两个是对应的。也证明我们的推导是正确的。

addr2line 使用

Addr2line 工具(它是标准的 GNU Binutils 中的一部分)是一个可以将指令的地址和可执行映像转换成文件名、函数名和源代码行数的工具。这种功能对于将跟踪地址转换成更有意义的内容来说简直是太棒了。

要了解这个过程是怎样工作的,我们可以试验一个简单的交互式的例子。(我直接从 shell 中进行操作,因为这是最简单地展示这个过程的方法,如清单 4 所示。)这个示例 C 文件(test.c)是通过 cat 一个简单的应用程序实现的(也就是说,将标准输出的文本重定向到一个文件中)。然后使用 gcc 来编译这个文件,它会传递一些特殊的选项。首先,要(使用 -Wl 选项)通知链接器生成一个映像文件,并(使用 -g 选项)通知编译器生成调试符号。最终生成可执行文件 test。得到新的可执行应用程序之后,您就可以使用grep 工具在映像文件中查找 main 来寻找它的地址了。使用这个地址和 Addr2line 工具,就可以判断出函数名(main)、源文件(/home/mtj/test/test.c)以及它在源文件中的行号(4)。

在调用 Addr2line 工具时,要使用 -e 选项来指定可执行映像是 test。通过使用 -f 选项,可以告诉工具输出函数名。

				
$ cat >> test.c
#include <stdio.h>
int main()
{
  printf("Hello World\n");
  return 0;
}
<ctld-d>
$ gcc -Wl,-Map=test.map -g -o test test.c
$ grep main test.map
	0x08048258		__libc_start_main@@GLIBC_2.0
	0x08048258		main
$ addr2line 0x08048258 -e test -f
main
/home/mtj/test/test.c:4
$
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_kerneler

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值