title: 应用调试(三)oops

date: 2019/01/19 12:06:58

toc: true

应用调试(三)oops

引入

在驱动程序调试中,发生段错误后内核打印出oops信息,包括pc值,寄存器值和栈信息

  1. Unable to handle kernel paging request at virtual address 56000050
  2. .....

但是我们再应用程序故意引入一个错误(在地址0的地方写数据),只是提示段错误,没有信息打印,那么如何打开这个选项呢?

配置内核打开用户oops

搜索字符Unable to handle kernel,可以在arch\arm\mm\fault.c找到

  1. __do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
  2. struct pt_regs *regs)
  3. {
  4. ...
  5. printk(KERN_ALERT
  6. "Unable to handle kernel %s at virtual address %08lx\n",
  7. (addr < PAGE_SIZE) ? "NULL pointer dereference" :
  8. "paging request", addr);
  9. die("Oops", regs, fsr);
  10. ...
  11. }

也可以搜索下内核态的调用位置看到

  1. void do_bad_area(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
  2. {
  3. struct task_struct *tsk = current;
  4. struct mm_struct *mm = tsk->active_mm;
  5. /*
  6. * If we are in kernel mode at this point, we
  7. * have no context to handle this fault with.
  8. */
  9. if (user_mode(regs))
  10. __do_user_fault(tsk, addr, fsr, SIGSEGV, SEGV_MAPERR, regs);
  11. else
  12. __do_kernel_fault(mm, addr, fsr, regs);
  13. }

同时在下方就能看到打印用户态的oops

  1. static void
  2. __do_user_fault(struct task_struct *tsk, unsigned long addr,
  3. unsigned int fsr, unsigned int sig, int code,
  4. struct pt_regs *regs)
  5. {
  6. struct siginfo si;
  7. #ifdef CONFIG_DEBUG_USER
  8. if (user_debug & UDBG_SEGV) {
  9. printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n",
  10. tsk->comm, sig, addr, fsr);
  11. show_pte(tsk->mm, addr);
  12. show_regs(regs);
  13. }
  14. #endif
  15. tsk->thread.address = addr;
  16. tsk->thread.error_code = fsr;
  17. tsk->thread.trap_no = 14;
  18. si.si_signo = sig;
  19. si.si_errno = 0;
  20. si.si_code = code;
  21. si.si_addr = (void __user *)addr;
  22. force_sig_info(sig, &si, tsk);
  23. }

从代码上就可以看到需要两个处理

  • CONFIG_DEBUG_USER配置需要打开
  • user_debug变量的设置

CONFIG_DEBUG_USER

搜索内核这个配置项DEBUG_USER,已经打开了

  1. Symbol: DEBUG_USER [=y]
  2. Prompt: Verbose user fault messages
  3. Defined at arch/arm/Kconfig.debug:18
  4. Location:
  5. -> Kernel hacking

user_debug

搜索变量,可以看到如下,很明显可以通过设置启动参数arch\arm\kernel\traps.c

  1. #ifdef CONFIG_DEBUG_USER
  2. unsigned int user_debug;
  3. static int __init user_debug_setup(char *str)
  4. {
  5. get_option(&str, &user_debug);
  6. return 1;
  7. }
  8. __setup("user_debug=", user_debug_setup);
  9. #endif

设置启动参数测试

  1. set bootargs noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0 user_debug=0xff
  2. boot
  3. mount -t nfs -o nolock,vers=2 192.168.95.222:/home/book/stu /mnt

运行错误的程序,可以看到打印寄存器和pc值

  1. # ./test_debug
  2. a = 0x12
  3. pgd = c2cac000
  4. [00000000] *pgd=32cca031, *pte=00000000, *ppte=00000000
  5. Pid: 792, comm: test_debug
  6. CPU: 0 Not tainted (2.6.22.6 #9)
  7. PC is at 0x84ac
  8. LR is at 0x84d0
  9. pc : [<000084ac>] lr : [<000084d0>] psr: 60000010
  10. sp : bed7ae40 ip : bed7ae54 fp : bed7ae50
  11. r10: 4013365c r9 : 00000000 r8 : 00008514
  12. r7 : 00000001 r6 : 000085cc r5 : 00008568 r4 : bed7aec4
  13. r3 : 00000012 r2 : 00000000 r1 : 00001000 r0 : 00000000
  14. Flags: nZCv IRQs on FIQs on Mode USER_32 Segment user
  15. Control: c000717f Table: 32cac000 DAC: 00000015
  16. [<c002cd1c>] (show_regs+0x0/0x4c) from [<c0031a98>] (__do_user_fault+0x5c/0xa4)
  17. r4:c06c30a0
  18. [<c0031a3c>] (__do_user_fault+0x0/0xa4) from [<c0031d38>] (do_page_fault+0x1dc/0x20c)
  19. r7:c0026520 r6:c3333860 r5:c06c30a0 r4:ffffffec
  20. [<c0031b5c>] (do_page_fault+0x0/0x20c) from [<c002b224>] (do_DataAbort+0x3c/0xa0)
  21. [<c002b1e8>] (do_DataAbort+0x0/0xa0) from [<c002be48>] (ret_from_exception+0x0/0x10)
  22. Exception stack(0xc2ca1fb0 to 0xc2ca1ff8)
  23. 1fa0: 00000000 00001000 00000000 00000012
  24. 1fc0: bed7aec4 00008568 000085cc 00000001 00008514 00000000 4013365c bed7ae50
  25. 1fe0: bed7ae54 bed7ae40 000084d0 000084ac 60000010 ffffffff
  26. r8:00008514 r7:00000001 r6:000085cc r5:00008568 r4:c039bfc8
  27. Segmentation fault

这里其实是有stack的,但这个栈实际上寄存器的值而非程序的栈,可以在notepad++上双击数字看到高亮的

  1. Exception stack(0xc2ca1fb0 to 0xc2ca1ff8)
  2. r0
  3. 1fa0: 00000000 00001000 00000000 00000012
  4. 1fc0: bed7aec4 00008568 000085cc 00000001 00008514 00000000 4013365c bed7ae50
  5. psr
  6. 1fe0: bed7ae54 bed7ae40 000084d0 000084ac 60000010 ffffffff
  7. r8:00008514 r7:00000001 r6:000085cc r5:00008568 r4:c039bfc8

打印用户堆栈

可以看到内核态打印栈

  1. __do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
  2. struct pt_regs *regs)
  3. >die("Oops", regs, fsr);
  4. >show_stack_log_lvl(current, regs->sp, regs, KERN_EMERG)
  5. >dump_mem("Stack: ", log_lvl, sp,THREAD_SIZE + (unsigned long)tinfo);
  6. >show_trace_log_lvl(tsk, (unsigned long *)sp, regs, log_lvl);

试了下直接调用内核的打印栈的方式并不能成功,哈哈

  1. 添加代码为
  2. {
  3. printk("Stack by die: \n");
  4. die("Oops", regs, fsr);
  5. }
  6. 只会提示这个
  7. Stack by die:
  8. Internal error: Oops: 817 [#1]

我们这里就手动复制到用户态打印好了更改代码如下

  1. static void
  2. __do_user_fault(struct task_struct *tsk, unsigned long addr,
  3. unsigned int fsr, unsigned int sig, int code,
  4. struct pt_regs *regs)
  5. {
  6. struct siginfo si;
  7. #ifdef CONFIG_DEBUG_USER
  8. if (user_debug & UDBG_SEGV) {
  9. printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n",
  10. tsk->comm, sig, addr, fsr);
  11. show_pte(tsk->mm, addr);
  12. show_regs(regs);
  13. }
  14. #endif
  15. /////////////////////////////////////////////////////////////////////////////
  16. if(0)
  17. {
  18. printk("Stack by die: \n");
  19. die("Oops", regs, fsr);
  20. }
  21. if (1)
  22. {
  23. unsigned long i=0,val=0;
  24. printk("Stack: \n");
  25. while(i<1024)
  26. {
  27. /* copy_from_user()只是用来检测该地址是否有效,如有效,便获取地址数据,否则break */
  28. if(copy_from_user(&val, (const void __user *)(regs->ARM_sp+i*4), 4))
  29. break;
  30. printk("%08x ",val);
  31. i++;
  32. if(i%8==0) printk("\n");
  33. }
  34. printk("\n END of Stack\n");
  35. }
  36. /////////////////////////////////////////////////////////////////////////////
  37. tsk->thread.address = addr;
  38. tsk->thread.error_code = fsr;
  39. tsk->thread.trap_no = 14;
  40. si.si_signo = sig;
  41. si.si_errno = 0;
  42. si.si_code = code;
  43. si.si_addr = (void __user *)addr;
  44. force_sig_info(sig, &si, tsk);
  45. }

可以看到打印了栈了

  1. # /mnt/code/test_debug
  2. a = 0x12
  3. pgd = c2cc4000
  4. [00000000] *pgd=32c82031, *pte=00000000, *ppte=00000000
  5. Pid: 789, comm: test_debug
  6. CPU: 0 Not tainted (2.6.22.6 #6)
  7. PC is at 0x84ac
  8. LR is at 0x84d0
  9. pc : [<000084ac>] lr : [<000084d0>] psr: 60000010
  10. sp : be87ee50 ip : be87ee64 fp : be87ee60
  11. r10: 4013365c r9 : 00000000 r8 : 00008514
  12. r7 : 00000001 r6 : 000085cc r5 : 00008568 r4 : be87eed4
  13. r3 : 00000012 r2 : 00000000 r1 : 00001000 r0 : 00000000
  14. Flags: nZCv IRQs on FIQs on Mode USER_32 Segment user
  15. Control: c000717f Table: 32cc4000 DAC: 00000015
  16. [<c002cd1c>] (show_regs+0x0/0x4c) from [<c0031a9c>] (__do_user_fault+0x60/0x148)
  17. r4:c04a8800
  18. [<c0031a3c>] (__do_user_fault+0x0/0x148) from [<c0031ddc>] (do_page_fault+0x1dc/0x20c)
  19. [<c0031c00>] (do_page_fault+0x0/0x20c) from [<c002b224>] (do_DataAbort+0x3c/0xa0)
  20. [<c002b1e8>] (do_DataAbort+0x0/0xa0) from [<c002be48>] (ret_from_exception+0x0/0x10)
  21. Exception stack(0xc2cb1fb0 to 0xc2cb1ff8)
  22. 1fa0: 00000000 00001000 00000000 00000012
  23. 1fc0: be87eed4 00008568 000085cc 00000001 00008514 00000000 4013365c be87ee60
  24. 1fe0: be87ee64 be87ee50 000084d0 000084ac 60000010 ffffffff
  25. r8:00008514 r7:00000001 r6:000085cc r5:00008568 r4:c039bfc8
  26. Stack:
  27. 00000000 be87ee74 be87ee64 000084d0 000084a0 00000000 be87ee88 be87ee78
  28. 000084f0 000084c4 00000000 be87eea8 be87ee8c 00008554 000084e4 00000000
  29. 00000012 be87eed4 00000001 00000000 be87eeac 40034f14 00008524 00000000
  30. 00000000 0000839c 00000000 00000000 4001d594 000083c4 000085cc 4000c02c
  31. be87eed4 be87ef7f 00000000 be87ef94 be87ef9e be87efa5 be87efb0 be87efd3
  32. be87efe1 00000000 00000010 00000003 00000006 00001000 00000011 00000064
  33. 00000003 00008034 00000004 00000020 00000005 00000006 00000007 40000000
  34. 00000008 00000000 00000009 0000839c 0000000b 00000000 0000000c 00000000
  35. 0000000d 00000000 0000000e 00000000 00000017 00000000 0000000f be87ef7b
  36. 00000000 00000000 76000000 2f006c34 2f746e6d 65646f63 7365742f 65645f74
  37. 00677562 52455355 6f6f723d 4f480074 2f3d454d 52455400 74763d4d 00323031
  38. 48544150 62732f3d 2f3a6e69 2f727375 6e696273 69622f3a 752f3a6e 622f7273
  39. 53006e69 4c4c4548 69622f3d 68732f6e 44575000 2f002f3d 2f746e6d 65646f63
  40. 7365742f 65645f74 00677562 00000000
  41. END of Stack
  42. Segmentation fault

分析栈

同样可以先反汇编arm-linux-objdump -D test_debug > test_debug.dis

  1. PC is at 0x84ac
  2. LR is at 0x84d0
  3. Stack:
  4. 00000000 be87ee74 be87ee64 000084d0 000084a0 00000000 be87ee88 be87ee78
  5. C_sp ldr B_sp
  6. 000084f0 000084c4 00000000 be87eea8 be87ee8c 00008554 000084e4 00000000
  7. ldr A_sp ldr main_sp
  8. 00000012 be87eed4 00000001 00000000 be87eeac 40034f14 00008524 00000000
  9. ldr
  10. 00000000 0000839c 00000000 00000000 4001d594 000083c4 000085cc 4000c02c
  11. be87eed4 be87ef7f 00000000 be87ef94 be87ef9e be87efa5 be87efb0 be87efd3

这里的mani函数最后会返回40034f14这个动态库,动态库不太好分析,我们重新编译为静态链接分析下main被谁调用

main的调用

在动态库中,可以查看 /proc/pidxxx/maps,看到程序动态库的地址,但是这个函数我们直接段错误退出了所以无法查看,可以看下别的pid(shell)的

  1. # cat /proc/772/maps
  2. 00008000-000bf000 r-xp 00000000 1f:03 646 /bin/busybox
  3. 000c7000-000c8000 rw-p 000b7000 1f:03 646 /bin/busybox
  4. 000c8000-000ec000 rwxp 000c8000 00:00 0 [heap]
  5. 40000000-40015000 r-xp 00000000 1f:03 733 /lib/ld-2.3.6.so
  6. 40015000-40017000 rw-p 40015000 00:00 0
  7. 4001d000-4001e000 rw-p 00015000 1f:03 733 /lib/ld-2.3.6.so
  8. 4001e000-40023000 r-xp 00000000 1f:03 691 /lib/libcrypt-2.3.6.so
  9. 40023000-4002a000 ---p 00005000 1f:03 691 /lib/libcrypt-2.3.6.so
  10. 4002a000-4002b000 rw-p 00004000 1f:03 691 /lib/libcrypt-2.3.6.so
  11. 4002b000-40052000 rw-p 4002b000 00:00 0
  12. 40052000-400f9000 r-xp 00000000 1f:03 708 /lib/libm-2.3.6.so
  13. 400f9000-40101000 ---p 000a7000 1f:03 708 /lib/libm-2.3.6.so
  14. 40101000-40102000 rw-p 000a7000 1f:03 708 /lib/libm-2.3.6.so
  15. 40102000-4020d000 r-xp 00000000 1f:03 734 /lib/libc-2.3.6.so
  16. 4020d000-40215000 ---p 0010b000 1f:03 734 /lib/libc-2.3.6.so
  17. 40215000-40219000 rw-p 0010b000 1f:03 734 /lib/libc-2.3.6.so
  18. 40219000-4021b000 rw-p 40219000 00:00 0
  19. bed05000-bed1a000 rwxp bed05000 00:00 0 [stack]

那么我们可以静态编译这个文件,看看被谁调用

  1. arm-linux-gcc -static -o test_debug_static test_debug.c
  2. arm-linux-objdump -D test_debug_static > test_debug_static.dis

运行后打印了错误信息

  1. # /mnt/code/test_debug_static
  2. <Physical Layer error>
  3. <Physical Layer error>
  4. a = 0x12
  5. pgd = c2cd4000
  6. [00000000] *pgd=32c94031, *pte=00000000, *ppte=00000000
  7. Pid: 789, comm: test_debug_stat
  8. CPU: 0 Not tainted (2.6.22.6 #6)
  9. PC is at 0x81e0
  10. LR is at 0x8204
  11. pc : [<000081e0>] lr : [<00008204>] psr: 60000010
  12. .....
  13. Stack:
  14. 00000000 beeeac94 beeeac84 00008204 000081d4 00000000 beeeaca8 beeeac98
  15. C_Sp ldr B_Sp
  16. 00008224 000081f8 00000000 beeeacc8 beeeacac 00008288 00008218 00000000
  17. ldr A_Sp ldr main_Sp
  18. 00000012 beeeaec4 00000001 00000000 beeeaccc 000084ac 00008258 756e694c
  19. ldr
  20. 00000078 00000000 00000000 00000000 00000000 00000000 00000000 00000000
  21. 00000000 00000000 00000000 00000000 00000000 00000000 00000000 6f6e2800
  22. 0029656e 00000000 00000000 00000000 00000000 00000000 00000000 00000000
  23. 00000000 00000000 00000000 00000000 00000000 00000000 00000000 2e320000
  24. 32322e36 0000362e 00000000 00000000 00000000 00000000 00000000 00000000
  25. 00000000 00000000 00000000 00000000 00000000 00000000 00000000 23000000
  26. 61532036 614a2074 3931206e 3a333120 303a3630 53432032 30322054 00003931
  27. .......
  28. END of Stack
  29. Segmentation fault

跳过一些栈,直接看到

  1. 00008248 <main>:
  2. 可以看到main的返回 000084ac
  3. 0000829c <__libc_start_main>:
  4. 84a4: e1a0e00f mov lr, pc ;这个就是main
  5. 84ac: eb0000fc bl 88a4 <exit>

也就是最终的关系是

  1. __libc_start_main
  2. > main
  3. > exit

应用调试(三)oops的更多相关文章

  1. 驱动调试(三)oops确定函数PC

    目录 驱动调试(三)oops确定函数PC 什么是oops 流程简述 代码仓库 模块例子分析 找到PC值 判断是否属于模块 查看符号表 找到模块 反汇编模块 内核例子分析 找到PC值 判断是否属于模块 ...

  2. 驱动调试(四)oops确定调用树

    目录 驱动调试(四)oops确定调用树 内核开启调用树 栈指针分析 原理 寄存器别名 基础解释 例子分析 找到PC地址的位置 栈分析 附录:原文的excel title: 驱动调试(四)oops确定调 ...

  3. phone 调试三种工具

    1. Phonegap桌面开发工具 Phonegap Desktop-App与 手机客户端调试工具PhoneGap Developer App 此工具方便.快捷.自动.可以在真机中查看 无法设置断点. ...

  4. XE6 /XE8 & IOS开发之免证书真机调试三步走,生成iPA文件并安装到其它苹果设备上

    XE6 & IOS开发之免证书真机调试(1):颁发属于自己的App签名证书(有图有真相) XE6 & IOS开发之免证书真机调试(2):连接真机并运行App(有图有真相) XE6 &a ...

  5. 驱动调试-根据oops定位错误代码行

    1.当驱动有误时,比如,访问的内存地址是非法的,便会打印一大串的oops出来 1.1以LED驱动为例 将open()函数里的ioremap()屏蔽掉,直接使用物理地址的GPIOF,如下图所示: 1.2 ...

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

    上章链接入口: http://www.cnblogs.com/lifexy/p/8006748.html 在上章里,我们分析了oops的PC值在哪个函数出错的,那如何通过栈信息来查看出错函数的整个调用 ...

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

    上章链接入口: http://www.cnblogs.com/lifexy/p/8006748.html 在上章里,我们分析了oops的PC值在哪个函数出错的 本章便通过栈信息来分析函数调用过程 1. ...

  8. 36.Linux驱动调试-根据oops定位错误代码行

    1.当驱动有误时,比如,访问的内存地址是非法的,便会打印一大串的oops出来 1.1以LED驱动为例 将open()函数里的ioremap()屏蔽掉,直接使用物理地址的GPIOF,如下图所示: 1.2 ...

  9. gcc gdb调试 (三)

    编写代码过程中少不了调试.在windows下面,我们有visual studio工具.在linux下面呢,实际上除了gdb工具之外,你没有别的选择.那么,怎么用gdb进行调试呢?我们可以一步一步来试试 ...

随机推荐

  1. python模块(os,sys,hashlib,collections)

    列出目录下所有文件 os.listdir('dirname'):列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式返回. 创建文件夹: os.mkdir('文件夹')    创建文件夹 os ...

  2. 文件操作命令(move)

    move命令: // 描述: 将一个或多个文件从一个目录移动到另一个目录. // 语法: move [{/y | /-y}] [<Source>] [<Target>] // ...

  3. LeetCode算法题-License Key Formatting(Java实现)

    这是悦乐书的第241次更新,第254篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第108题(顺位题号是482).您将获得一个表示为字符串S的许可证密钥,该字符串仅包含字 ...

  4. FutureTask并发详解,通俗易懂

    最近做项目,使用到了FutureTask和主线程并发,应用到实际中还是挺实用的,特在此总结一下. 有不对之处,忘各位多多指出. package com.demo; import java.util.c ...

  5. Docker 镜像仓库

    仓库 仓库(Repository)是集中存放镜像的地方. 一个容易混淆的概念是注册服务器(Registry).实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜 ...

  6. org.springframework.web.context.support.XmlWebApplicationContext.refresh Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreatio

    错误异常: 11-Apr-2019 18:07:14.006 警告 [RMI TCP Connection(5)-127.0.0.1] org.springframework.web.context. ...

  7. LVS负载均衡集群

    回顾-Nginx反向代理型负载 负载均衡(load balance)集群,提供了一种廉价.有效.透明的方法,来扩展网络设备和服务器的负载.带宽.增加吞吐量.加强网络数据处理能力.提高网络的灵活性和可用 ...

  8. Linux-基础学习(五)-mariadb主从复制以及redis学习

    开始今日份整理 1.mariadb的主从复制 主从复制大致图示: 1.1 mysql基本命令复习 linux下的操作 .启动mysql systemctl start mariadb .linux客户 ...

  9. 【vue】移动端demo资料

    http://imzjh.com/inew/#/(移动端demo) https://github.com/liangxiaojuan/eleme(饿了么git地址) https://github.co ...

  10. ASP.NET Core 微服务初探[1]:服务发现之Consul

    ASP.NET Core 微服务初探[1]:服务发现之Consul   在传统单体架构中,由于应用动态性不强,不会频繁的更新和发布,也不会进行自动伸缩,我们通常将所有的服务地址都直接写在项目的配置文件 ...