转:
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怎么办啊?这时候我们就要通过栈信息自己来推出函数调用关系了。这里我们需要内核的反汇编文件,因为我们是内核中的函数调用驱动模块中的函数。
而在讲解如何推出调用关系之前,我想先介绍一下我们要用到的寄存器:
-
寄存器R14被称为链接寄存器(LR),存放每种模式下,当前子程序的返回地址或者发生异常中断的时候,将R14设置成异常模式将要返回的地址。
-
寄存器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
$