内核回溯

上一节里面我们看到出错信息还包括回溯信息,通过回溯信息我们可以知道是哪一条调用路径出现了错误。但是要想显示回溯信息的话,在内核配置文件.config文件里面必须要有这么一句:CONFIG_FRAME_POINTER=y


但是如果我们的内核没有配置CONFIG_FRAME_POINTER=y这句话,是不是就意味着我们无法知道回调关系了呢!其实并非如此,根据上一节里面的内容我们还知道,出错信息里面还包括栈信息,其实从栈信息里面我们就可以分析出回调关系,它的原理也很简单,我们知道当A调用B的时候,会将A的返回地址入栈,B在调用C的时候,又会将B的返回地址入栈,这样栈中的信息就表示了一种调用关系。这样当调用函数返回时,就可以从栈中取出返回地址,一次返回B、A。同样,在出错的时候,也可以通过一次打印栈中的信息,来显示调用关系。
下面我们就来具体地分析一下:
这里是我们故意写的一个出错的模块:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

int *i;

void a()
{
    *i=1;
}

static int first_drv_init(void)
{
       i=0;
       a();
return 0;
}

static void first_drv_exit(void)
{
    
}

module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

加载的时候出现了下面的打印信息:

Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = c3c44000
[00000000] *pgd=3001b031, *pte=00000000, *ppte=00000000
Internal error: Oops: 817 [#1]
Modules linked in: test
CPU: 0    Not tainted  (2.6.22.6 #16)
PC is at a+0x18/0x24 [test]
LR is at first_drv_init+0x1c/0x28 [test]
pc : [<bf000018>]    lr : [<bf000040>]    psr: a0000013
sp : c3c27eb4  ip : c3c27ec4  fp : c3c27ec0
r10: c486e000  r9 : c06d6f14  r8 : 00000018
r7 : c4879624  r6 : bf0003c0  r5 : bf0003c0  r4 : 00000000
r3 : 00000001  r2 : 00000000  r1 : 00000001  r0 : 00000000
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment user
Control: c000717f  Table: 33c44000  DAC: 00000015
Process insmod (pid: 747, stack limit = 0xc3c26258)
Stack: (0xc3c27eb4 to 0xc3c28000)
7ea0:                                              c3c27ed4 c3c27ec4 bf000040 
7ec0: bf000010 00000000 c3c27fa4 c3c27ed8 c006285c bf000034 00000000 00000398 
7ee0: c02a9f20 c02a9f20 000000fc 0000001c 00000018 c3cce128 c3c27f18 00000000 
7f00: 0000002b 0000002b 0000005c 00000058 00000014 c3c26000 00000000 00000000 
7f20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
7f40: 00000003 00000000 00000005 00000000 00000000 00000000 00000017 00000016 
7f60: c487ce14 c3e16240 c4879550 000cb060 be8a0918 000c7f7c c3c27f9c 0000eea9 
7f80: 000ca1b0 000cb050 00000080 c002b044 c3c26000 000c7f7c 00000000 c3c27fa8 
7fa0: c002aea0 c0061448 000ca1b0 000cb050 00900080 000cb248 0000eea9 000cb070 
7fc0: 0000eea9 000ca1b0 000cb050 00000000 000cb060 be8a0918 000c7f7c 00000002 
7fe0: be89efb8 be89efac 00052354 401b7a00 60000010 00900080 00000000 00000000 
Backtrace: 
[<bf000000>] (a+0x0/0x24 [test]) from [<bf000040>] (first_drv_init+0x1c/0x28 [test])
[<bf000024>] (first_drv_init+0x0/0x28 [test]) from [<c006285c>] (sys_init_module+0x1424/0x1514)
 r4:00000000
[<c0061438>] (sys_init_module+0x0/0x1514) from [<c002aea0>] (ret_fast_syscall+0x0/0x2c)
Code: e24cb004 e59f300c e5932000 e3a03001 (e5823000) 
Segmentation fault

分析:
1、找到pc=bf000018
2、打开System.map文件,发现内核模块函数的地址范围是:
      c0004000~c03be254
      所以出错位置不是内核模块
3、查看开发板上:/proc/kallsyms文件
      cat /proc/kallsyms > /kallsyms.txt
      vi /kallsyms.txt
      找到离bf000018比较近的函数,发现如下信息:
      bf000000 t $a   [test]
      于是知道错误出现在:加载模块test,出错函数是:a
4、我们将模块test反汇编:arm-linux-objdump -D test.ko > test.dis
      在test.dis找到如下信息:
00000000 <a>:
   0:   e1a0c00d        mov     ip, sp
   4:   e92dd800        stmdb   sp!, {fp, ip, lr, pc}
   8:   e24cb004        sub     fp, ip, #4      ; 0x4
   c:   e59f300c        ldr     r3, [pc, #12]   ; 20 <.text+0x20>//到20处取到0给了r3
  10:   e5932000        ldr     r2, [r3]                                      //将0地址的内容放入r2
  14:   e3a03001        mov     r3, #1  ; 0x1                          //将1放入0地址处
  18:   e5823000        str     r3, [r2]                                     //将1放入0地址内容表示的位置,出错了
  1c:   e89da800        ldmia   sp, {fp, sp, pc}
  20:   00000000        andeq   r0, r0, r0

由于之前我们已经知道出错位置相对于函数a的偏移是18,所以这里我们很容易找到了出错位置就是上面蓝色标识处!
通过分析,我们很容易就知道是对指向0地址的i指针赋值的时候出现了错误!

下面我们根据栈内容分析调用关系:

Stack: (0xc3c27eb4 to 0xc3c28000)
7ea0:                                              c3c27ed4 c3c27ec4 bf000040 
7ec0: bf000010 00000000 c3c27fa4 c3c27ed8 c006285c bf000034 00000000 00000398 
7ee0: c02a9f20 c02a9f20 000000fc 0000001c 00000018 c3cce128 c3c27f18 00000000 
7f00: 0000002b 0000002b 0000005c 00000058 00000014 c3c26000 00000000 00000000 
7f20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
7f40: 00000003 00000000 00000005 00000000 00000000 00000000 00000017 00000016 
7f60: c487ce14 c3e16240 c4879550 000cb060 be8a0918 000c7f7c c3c27f9c 0000eea9 
7f80: 000ca1b0 000cb050 00000080 c002b044 c3c26000 000c7f7c 00000000 c3c27fa8 
7fa0: c002aea0 c0061448 000ca1b0 000cb050 00900080 000cb248 0000eea9 000cb070 
7fc0: 0000eea9 000ca1b0 000cb050 00000000 000cb060 be8a0918 000c7f7c 00000002 
7fe0: be89efb8 be89efac 00052354 401b7a00 60000010 00900080 00000000 00000000 

首先我们要知道返回地址在寄存器lr中,而返回指令为调用指令的下一条指令,好的下面开始分析:
上面我们已经得知错误出现在a函数里面,我们贴出a函数的反汇编:
00000000 <a>:
   0:e1a0c00d  movip, sp
   4:e92dd800  stmdbsp!, {fp, ip, lr, pc}
   8:e24cb004  subfp, ip, #4 ; 0x4
   c:e59f300c  ldrr3, [pc, #12] ; 20 <.text+0x20>
  10:e5932000  ldrr2, [r3]
  14:e3a03001  movr3, #1 ; 0x1
  18:e5823000  strr3, [r2]
  1c:e89da800  ldmiasp, {fp, sp, pc}
  20:00000000  andeqr0, r0, r0

得出lr=bf000040,回溯一下发现调用语句在init_module函数里面

贴出init_module的反汇编:
00000024 <init_module>:
  24:e1a0c00d  movip, sp
  28:e92dd810  stmdbsp!, {r4, fp, ip, lr, pc}
  2c:e24cb004  subfp, ip, #4 ; 0x4
  30:e59f3010  ldrr3, [pc, #16] ; 48 <.text+0x48>
  34:e3a04000  movr4, #0 ; 0x0
  38:e5834000  strr4, [r3]
  3c:ebfffffe  bl3c <init_module+0x18>
  40:e1a00004  movr0, r4
  44:e89da810  ldmiasp, {r4, fp, sp, pc}
  48:00000000  andeqr0, r0, r0

得出lr=c006285c ,我们看到这是个内核模块的函数地址,我们还需要内核模块的反汇编:
arm-linux-objdump -D vmlinux > vmlinux.dis
 vmlinux.dis文件里面搜索地址:c006285c 
发现地址为c006285c的代码在函数:sys_init_module里面

由此我们得出调用关系为:
sys_init_module----->init_module------>a
由于init_module就相当于执行first_drv_init,所以调用关系是:
sys_init_module----->init_module(first_drv_init)------>a

到此为止,我们通过栈来分析回溯调用成功了!根据真正的回溯函数我们可以验证其正确性!

https://blog.csdn.net/bytxl/article/details/38292791

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Linux内核中,每个进程都有自己的内核堆栈,用于保存当前进程的执行状态、寄存器、局部变量等信息。当内核执行系统调用、中断处理、异常处理等操作时,会使用内核堆栈来保存相关信息。 以下是Linux内核堆栈解析方法: 1. 确定内核堆栈地址:在Linux中,内核堆栈通常位于进程控制块(PCB)中的内核栈指针(kernel stack pointer)所指向的地址。可以通过查看进程的PCB结构体中的kernel_stack成员来获取内核堆栈地址。 2. 获取堆栈帧指针:堆栈帧指针(frame pointer)是指向当前堆栈帧底部的指针,用于确定当前堆栈帧的大小和位置。可以通过读取当前CPU寄存器ebp(x86架构)或者r29(ARM架构)中的值来获取堆栈帧指针。 3. 解析堆栈帧:在堆栈帧中,局部变量、函数参数、返回值等信息都保存在栈中。可以通过指针运算和类型转换等方法来访问这些信息。需要注意的是,由于内核堆栈是内核态的栈,因此在解析过程中需要特别小心,防止出现悬垂指针、越界访问等问题。 4. 调试工具:除了手动解析堆栈外,还可以使用调试工具来辅助解析。例如,可以使用gdb调试器的bt命令来打印当前进程的堆栈回溯信息,或者使用系统调用ptrace来跟踪进程的堆栈信息。 总之,在Linux内核中解析堆栈需要一定的经验和技能,需要特别小心,以避免出现问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值