接着上一篇来分析一下Oops的栈
s3c2440平台
关于调试源码和整个Oops信息请参考上一篇博文,这里只再次贴出关于栈的信息
Stack: (0xc3a61e30 to 0xc3a62000)
1e20: c3a61e64 c3a61e40 c00a8580 bf0d7010
1e40: c00adba8 00000000 00000000 c3b46100 c3ab84b0 c00a84b4 c3a61e8c c3a61e68
1e60: c00a3a7c c00a84c4 c3b46100 c2c0ae40 00000003 c3af0000 00000026 c3a61ed8
1e80: c3a61eac c3a61e90 c00a3d14 c00a39bc 00000000 c2c0ae40 00000000 00000000
1ea0: c3a61f64 c3a61eb0 c00b0c80 c00a3cc0 c3a61f7c c3a61ec0 c004b714 c006f8b8
1ec0: c3a61efc beb5ad9c 00000000 00000000 c3a63000 c048070c c394bc80 c34b7600
1ee0: c048077c c3a61fb0 00000000 00000101 00000001 00000000 c00441e0 c004b548
1f00: 08100875 c39568a0 c3a7ec00 0000001c 00000000 00001000 00000003 00000003
1f20: 00000000 c3b46100 00000000 c3a60000 c3a61f64 c3a61f40 c00b99b8 00000003
1f40: c3af0000 00000002 beb5ad9c ffffff9c c3a60000 00000000 c3a61f94 c3a61f68
1f60: c00a38d8 c00b0aa0 00000000 40025000 c3a61f9c 0000850c 00000000 000083e0
1f80: 00000005 c0045008 c3a61fa4 c3a61f98 c00a3988 c00a3878 00000000 c3a61fa8
1fa0: c0044e60 c00a3974 0000850c 00000000 00008590 00000002 beb5ad9c 00000001
1fc0: 0000850c 00000000 000083e0 00000005 00000000 00000000 40025000 beb5ac44
1fe0: 00000000 beb5ac28 000084b8 400efd9c 60000010 00008590 00000000 00000000
那么这一堆栈指示的是什么信息?
指示的是函数的回溯信息,我们在上一节看到了函数的回溯信息,就是函数的调用关系,栈信息不仅包含了回溯信息,
还包含了各个函数相关的寄存器,当函数切换时,会把相关的寄存器保存在自己的堆栈中,出错时,会层层打印出来。
这样会有助于我们调试,因为在内核函数中,很多数据不是在当前函数中确定的,而是由上一个函数传过来的,所以
有必要知道这么一种调用关系
下面说一下这种调用关系:
A()
{
……
B();
……
}
B()
{
……
C();
……
}
C()
{
……
}
在上面的函数调用关系中,A函数会调用B函数,B函数会调用C函数,C函数执行完之后会返回B函数,B函数执行完之后返回A函数。
那么为什么能够返回去呢?
肯定是每次发生调用切换函数时,在自己的栈中保存了一个寄存器,在函数执行完后,根据这个寄存器能够返回到调用这个函数的
代码中,这个寄存器就是lr寄存器。
每个函数都有自己的堆栈区来保存相关的寄存器,通过sp寄存器找到这个地址。如下图所示:
假如在上面的调用中,C函数出错了,会打印出相关的堆栈信息供我们分析。按什么顺序打印呢?
先打印C函数堆栈中的寄存器,在根据C堆栈中的lr寄存器找出调用它的函数,这里是B函数,然后打印出B函数
堆栈中的寄存器,同样根据B的lr寄存器找到A函数并打印,就是这样一层一层往上推的。
现在来分析栈的打印信息
首先我们知道错误处在了segment_test_open函数中,来看一下这个函数的汇编代码,确定一下它的堆栈中存了那些
寄存器
00000000 :
0: e1a0c00d mov ip, sp
4: e92dd800 push {fp, ip, lr, pc}
看到这里,由第二行知道它的堆栈中存了四个寄存器,出错时会把这个函数堆栈中这四个寄存器打印出来,就是我们看到的
前四个栈的前四个数据:
c3a61e64 c3a61e40 c00a8580 bf0d7010
这里栈地址是向上增长的,即最低地址存的是fp寄存器,fp = c3a61e64,pc = bf0d7010。如下图所示
我们根据lr寄存器来找调用它的函数,这里 lr = c00a8580,怎么根据这个地址去查找是在哪个函数呢?
首先反汇编内核(arm-none-linux-gnueabi-objdump -D vmlinux > vmlinux.dis),然后在内核的汇编代码中查找这个地址。
找到这个地址之后,向上翻看,可以看到这个地址是在 c00a84b4 :里面,也就是chrdev_open函数,就是说下一个
要打印的堆栈信息将是这个函数的。看一下这个函数的堆栈大小及寄存器
c00a84b8: e92dd8f0 push {r4, r5, r6, r7, fp, ip, lr, pc}
……
c00a84c0: e24dd008 sub sp, sp, #8 ; 0x8
这里看出chrdev_open函数的堆栈大小是 40字节,会打印出十个寄存器值(每个寄存器占4字节),lr寄存器是倒数第二个,
接下来的十个寄存器是:
c00adba8 00000000 00000000 c3b46100 c3ab84b0 c00a84b4 c3a61e8c c3a61e68 c00a3a7c c00a84c4
可以看出 lr = c00a3a7c
再去查找这个地址,知道是在 __dentry_open函数中,这个函数的反汇编:
c00a39b0: e92dddf0 push {r4, r5, r6, r7, r8, sl, fp, ip, lr, pc}
同样是十个寄存器,接下来是:
c3b46100 c2c0ae40 00000003 c3af0000 00000026 c3a61ed8 c3a61eac c3a61e90 c00a3d14 c00a39bc
lr = c00a3d14,这个地址在nameidata_to_filp函数中,然后再根据它的堆栈信息,层层网上查找,
最后会根据c0044e60地址到ret_fast_syscall 函数,再之后的地址
0000850c 00000000 00008590 …………
已经不是内核函数了,这个函数就是发生在内核调用中的最上层函数。
谁调用ret_fast_syscall函数的?
根据我们之前对系统调用的分析知道,当发生系统调用open时,会产生swi异常,陷入到内核,这个函数就是陷入内核时
首先执行的函数,然后层层调用找到我们要调用的segment_test_open函数,通过这个分析我们也可以看到系统调用open的
调用过程。
这篇博文用到了寄存器的知识,有时间再介绍
阅读(4146) | 评论(0) | 转发(2) |