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

第36章的oops代码如下所示:

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

41.Linux应用调试-修改内核来打印用户态的oops的更多相关文章

  1. linux驱动调试--修改系统时钟终端来定位僵死问题【转】

    本文转载自:http://blog.chinaunix.net/uid-20671208-id-4940381.html 原文地址:linux驱动调试--修改系统时钟终端来定位僵死问题 作者:枫露清愁 ...

  2. linux中如何修改文件夹的用户权限 chown命令

    linux中,可以使用chown命令来修改文件夹的用户权限. 1.  以普通用户 A 登录linux,利用su -切换到root用户 2. 在root用户下,可以看到文件夹内容 3. 但通过文件系统, ...

  3. 例说linux内核与应用数据通信(四):映射设备内核空间到用户态

    [版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet.文章仅供学习交流,请勿用于商业用途]         一个进程的内存映象由以下几部分组成:代码段.数据段.BSS段和 ...

  4. linux mce的一些相关内容和用户态监控的设计方法

    之所以想起写一点关于mce的东西,倒不是因为遇到mce的异常了,之前遇到过很多mce的异常,内存居多,但没有好好记录下来,写这个是因为参加2018 clk南京会议的一点想法. void __init ...

  5. Linux CentOS 7修改内核启动默认顺序

    1.查看系统有几个内核 a.进入grub2.cfg文件中进行查看 b.通过grub界面查看 3.设置默认启动内核 grub2-set-default "内核版本" 配置默认内核4. ...

  6. Linux 内核态与用户态通信 netlink

    参考资料: https://blog.csdn.net/zqixiao_09/article/details/77131283 https://www.cnblogs.com/lopnor/p/615 ...

  7. 在linux系统中实现各项监控的关键技术(2)--内核态与用户态进程之间的通信netlink

    Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 ...

  8. v79.01 鸿蒙内核源码分析(用户态锁篇) | 如何使用快锁Futex(上) | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(用户态锁篇) | 如何使用快锁Futex(上) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...

  9. 42.Linux应用调试-初步制作系统调用(用户态->内核态)

    1首先来讲讲应用程序如何实现系统调用(用户态->内核态)? 我们以应用程序的write()函数为例: 1)首先用户态的write()函数会进入glibc库,里面会将write()转换为swi(S ...

随机推荐

  1. 基于Vue.js的大型报告页项目实现过程及问题总结(一)

    今年5月份的时候做了一个测评报告项目,需要在网页正常显示的同时且可打印为pdf,当时的技术方案采用jquery+template的方式,因为是固定模板所以并没有考虑报告的模块化区分,九月底产品提出新的 ...

  2. 快速配置vs2012+opencv

    关于OpenCV+Windows+VS配置的文章网上有很多,多是类似 OpenCV中文网 上的安装方法. 不管什么方法,配置的步骤毫无疑问是: 1. 配置环境变量, 2. 配置VS. 在这个过程中,令 ...

  3. Java求555 555的约数中最大的三位数。

    package org.llh.test; /** * 求555 555的约数中最大的三位数 * @author llh * */ public class Car { //整数j除以整数i(i≠0) ...

  4. shell 组合新的变量名

    shell 组合新的变量名 普通变量 name=yushuang var=name # 要获取到yushuang res=`eval echo '$'"$var"` echo $r ...

  5. VS2012 C#生成DLL并调用

    1.创建一个C#工程生成DLL 新建->项目->Visual C#->类库->MyMethods 项目建好后,为了理解,将项目中的Class1.cs 文件 重命名为 MySwa ...

  6. JavaNIO阻塞IO添加服务器反馈

    package com.java.NIO; import java.io.IOException; import java.net.InetSocketAddress; import java.nio ...

  7. Mongoose之 SchemaTypes 数据类型

    SchemaTypes 数据类型 SchemaTypes handle definition of path defaults, validation, getters, setters, field ...

  8. 【机器学习实战】第13章 利用 PCA 来简化数据

    第13章 利用 PCA 来简化数据 降维技术 场景 我们正通过电视观看体育比赛,在电视的显示器上有一个球. 显示器大概包含了100万像素点,而球则可能是由较少的像素点组成,例如说一千个像素点. 人们实 ...

  9. Java设计模式相关面试

    1.接口是什么?为什么要使用接口而不是直接使用具体类? 接口用于定义 API.它定义了类必须得遵循的规则.同时,它提供了一种抽象,因为客户端只使用接口,这样可以有多重实现,如 List 接口,你可以使 ...

  10. javascript第二章--变量、作用域和内存问题

    ① 基本类型和引用类型的值 ② 执行环境及作用域 ③ 垃圾收集