ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

37.Linux应用调试-修改内核来打印用户态的oops

2021-04-16 16:52:40  阅读:414  来源: 互联网

标签:__ do 00000000 37 user Linux debug open oops


1.在之前第32章里,我们学习了通过驱动的oops定位错误代码行

oops代码如下所示:

Unable to handle kernel paging request at virtual address 56000050      //无法处理内核页面请求的虚拟地址56000050pgd = c3850000[56000050] *pgd=00000000Internal error: Oops: 5 [#1]        //内部错误oopsModules linked in: 26th_segmentfault
        //表示内部错误发生在26th_segmentfault.ko驱动模块里CPU: 0    Not tainted  (2.6.22.6 #2)PC is at first_drv_open+0x78/0x12c [26th_segmentfault]        //PC值:程序运行成功的最后一次地址,位于first_drv_open()函数里,偏移值0x78,该函数总大小0x12cLR is at 0xc0365ed8             //LR值/*发生错误时的各个寄存器值*/pc : [<bf000078>]    lr : [<c0365ed8>]    psr: 80000013sp : c3fcbe80  ip : c0365ed8  fp : c3fcbe94
r10: 00000000  r9 : c3fca000  r8 : c04df960
r7 : 00000000  r6 : 00000000  r5 : bf000de4  r4 : 00000000r3 : 00000000  r2 : 56000050  r1 : 00000001  r0 : 00000052Flags: Nzcv  IRQs on  FIQs on  Mode SVC_32  Segment user
Control: c000717f  Table: 33850000  DAC: 00000015Process 26th_segmentfau (pid: 813, stack limit = 0xc3fca258)            //发生错误时,进程名称为26th_segmentfaultStack: (0xc3fcbe80 to 0xc3fcc000)        //栈信息,从栈底0xc3fcbe80到栈顶0xc3fcc000be80: c06d7660 c3e880c0 c3fcbebc c3fcbe98 c008d888 bf000010 00000000 c04df960
bea0: c3e880c0 c008d73c c0474e20 c3fb9534 c3fcbee4 c3fcbec0 c0089e48 c008d74c
bec0: c04df960 c3fcbf04 00000003 ffffff9c c002c044 c380a000 c3fcbefc c3fcbee8
bee0: c0089f64 c0089d58 00000000 00000002 c3fcbf68 c3fcbf00 c0089fb8 c0089f40
bf00: c3fcbf04 c3fb9534 c0474e20 00000000 00000000 c3851000 00000101 00000001bf20: 00000000 c3fca000 c04c90a8 c04c90a0 ffffffe8 c380a000 c3fcbf68 c3fcbf48
bf40: c008a16c c009fc70 00000003 00000000 c04df960 00000002 be84ce38 c3fcbf94
bf60: c3fcbf6c c008a2f4 c0089f88 00008588 be84ce84 00008718 0000877c 00000005bf80: c002c044 4013365c c3fcbfa4 c3fcbf98 c008a3a8 c008a2b0 00000000 c3fcbfa8
bfa0: c002bea0 c008a394 be84ce84 00008718 be84ce30 00000002 be84ce38 be84ce30
bfc0: be84ce84 00008718 0000877c 00000003 00008588 00000000 4013365c be84ce58
bfe0: 00000000 be84ce28 0000266c 400c98e0 60000010 be84ce30 30002031 30002431Backtrace:                                        //回溯信息[<bf000000>] (first_drv_open+0x0/0x12c [26th_segmentfault]) from [<c008d888>] (chrdev_open+0x14c/0x164)
 r5:c3e880c0 r4:c06d7660[<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8)
 r8:c3fb9534 r7:c0474e20 r6:c008d73c r5:c3e880c0 r4:c04df960[<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:be84ce38 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: bf000094 bf0000b4 bf0000d4 e5952000 (e5923000)Segmentation fault

1.1那为什么在上一章,我们用错误的应用程序,却没有打印oops,如下图所示:

在这里插入图片描述
  接下来,我们便来配置内核,从而打印应用程序的oops

2.首先来搜索oops里的:Unable to handle kernel打印语句,看在哪个函数打印的

  如下图所示,找到位于__do_kernel_fault()函数中:
在这里插入图片描述

3.继续找,发现__do_kernel_fault()被do_bad_area()调用

在这里插入图片描述
  do_bad_area()函数,从字面上分析,表示代码执行到错误段位置

  其中user_mode(regs)函数,通过判断CPSR寄存器若是用户模式则返回0,否则返回正数.

  所以我们上一章的错误的应用程序便会调用__do_user_fault()函数

4.__do_user_fault()函数如下所示:

在这里插入图片描述
  从上图来看,要想打印应用程序的错误信息,还需要:

3.1配置内核,设置宏CONFIG_DEBUG_USER(只要宏是以"CONFIG_"开头,都是与配置相关)

  1)在make menuconfig里搜索DEBUG_USER,如下图所示:
在这里插入图片描述
  所以将Kernel hacking-> Verbose user fault messages 置为Y,并重新烧内核

3.2使if (user_debug & UDBG_SEGV)为真

  1)其中user_debug定义如下所示:
在这里插入图片描述
  显然当uboot传递进来的命令行字符里含有"user_debug="时,便会调用user_debug_setup()->get_option(),最终会将"user_debug="后面带的字符串提取给user_debug变量.

  比如:当命令行字符里含有"user_debug=0xff"时,则user_debug变量等于0xff

  2)其中UDBG_SEGV定义如下所示:

#define UDBG_UNDEFINED  (1 << 0)        //用户态的代码出现未定义指令(UNDEFINED)#define UDBG_SYSCALL (1 << 1)           //用户态系统调用已过时(SYSCALL)     #define UDBG_BADABORT    (1 << 2)       //用户态数据错误已中止(BADABORT) #define UDBG_SEGV     (1 << 3)         //用户态的代码出现段错误(SEGV)#define UDBG_BUS       (1 << 4)        //用户态访问忙(BUS)

  从上面的定义分析得出,我们只需要将user_debug设为0xff,上面的所有条件就都成立.

  比如:当用户态的代码出现未定义指令时,由于user_debug最低位=1,所以打印出oops.

所以,进入uboot,在uboot命令行里添加: “user_debug=0xff”
4. 启动内核,试验

  如下图所示,执行错误的应用程序,只打印了各个寄存器值,以及函数调用关系,而没有栈信息:
在这里插入图片描述

5.接下来,继续修改内核,使应用程序的oops也打印栈信息出来

  在驱动的oops里有"Stack: "这个字段,搜索"Stack: "看看,位于哪个函数

5.1如下图所示, 找到位于__die()函数中:

在这里插入图片描述
  这个__die()会被die()调用,die()又会被__do_kernel_fault()调用,而我们应用程序调用的__do_user_fault()里没有die()函数,所以没有打印出Stack栈信息。

  上图里dump_mem():

dump_mem("Stack: ", regs->ARM_sp,THREAD_SIZE + (unsigned long)task_stack_page(tsk));    //打印stack栈信息

   主要是通过sp寄存器里存的栈地址,每打印一个栈地址里的32位数据, 栈地址便加4(一个地址存8位,所以加4)。

  接下来我们便通过这个原理,来修改应用程序调用的__do_user_fault()

5.2 在__do_user_fault(),添加以下部分 :

static void  __do_user_fault(struct task_struct *tsk, unsigned long addr,unsigned int fsr, unsignedint sig, int code,struct pt_regs *regs){   struct siginfo si;   unsigned long val ;   int i=0;#ifdef CONFIG_DEBUG_USER   if (user_debug & UDBG_SEGV) {  printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n", tsk->comm, sig, addr, fsr);  show_pte(tsk->mm, addr);  show_regs(regs);printk("Stack: \n");while(i<1024){   /* copy_from_user()只是用来检测该地址是否有效,如有效,便获取地址数据,否则break */   if(copy_from_user(&val, (const void __user *)(regs->ARM_sp+i*4), 4))   break;printk("%08x ",val);    //打印数据i++;if(i%8==0)printk("\n");}printk("\n END of Stack\n");   }#endif   tsk->thread.address = addr;   tsk->thread.error_code = fsr;   tsk->thread.trap_no = 14;   si.si_signo = sig;   si.si_errno = 0;   si.si_code = code;   si.si_addr = (void __user *)addr;   force_sig_info(sig, &si, tsk);}

6.重新烧写内核,试验

  如下图所示:
在这里插入图片描述
  接下来,便来分析PC值,Stack栈,到底如何调用的

7.首先来分析PC值,确定错误的代码

  1)生成反汇编:

arm-linux-objdump -D test_debug > test_debug.dis

  2)搜索PC值84ac,如下图所示:
在这里插入图片描述
  从上面看出,主要是将0x12(r3)放入地址0x00(r2)中

而0x00是个非法地址,所以出错

8.分析Stack栈信息,确定函数调用过程

  参考: 37.Linux驱动调试-根据oops的栈信息,确定函数调用过程

8.1分析过程中,遇到main()函数的返回地址为:LR=40034f14

  内核的虚拟地址是c0004000~c03cebf4,而反汇编里也没有该地址,所以这是个动态库的地址.

  需要用到静态链接方法,接下来重新编译,反汇编,运行:

#arm-linux-gcc -o -static  test_debug test_debug.c  //-static   静态链接,生成的文件会非常大, 好处在于不需要动态链接库,也可以运行#arm-linux-objdump -D test_debug > test_debug.dis

8.2最终, 找到main()函数的返回地址在__lobc_start_main()里

  所以函数出错时的调用过程:
__lobc_start_main()->
main()->
   A()->
B()->
C() //将0x12(r3)放入地址0x00(r2)中

标签:__,do,00000000,37,user,Linux,debug,open,oops
来源: https://blog.51cto.com/u_11934066/2711497

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有