zoukankan      html  css  js  c++  java
  • 应用层打印PC指针LR指针解决段错误总结【转】

    转自:https://blog.csdn.net/sdsszk/article/details/109765180?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

    1.需求的产生
    写程序难免会出现段错误的情况,这时候很想知道,到底在什么地方崩溃了,对于代码很少,或者你很有把握的时候,或许用二分法配合printf就可以搞定了;而对于非常复杂的代码,比如像Xserver这样的程序,可能就不太好定位了;
    (本文讨论的情况都是针对arm环境,并且gdb不方便使用的情况)
    
    2. 解决思路
    思路其实很简单,对于用户态段错误的原因,大约可以分为两种,
    
    a) 没有权限访问这个地址;
    b) 访问的地址没有映射,比如NULL地址;
    当出现这两种情况的时候,linux内核都会向用户态的程序发送SIGSEGV的信号,于是程序执行默认的信号处理函数,就退出了;
    所以有两个解决办法:
    A) 在内核里面把这些寄存器打印出来;
    B) 在上层程序里面把寄存器打印出来;
    下面来分别说明:
    
    3. 内核信息打印
    内核的执行路径如下:
    
    我们只需要在__do_user_fault的时候把打印信息打开就可以了,如下:
    
    #ifdef CONFIG_DEBUG_USER
    if (user_debug & UDBG_SEGV) {
    printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x
    ",
    tsk->comm, sig, addr, fsr);
    show_pte(tsk->mm, addr);
    show_regs(regs);
    }
    #endif
    改成
    printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x
    ",
    tsk->comm, sig, addr, fsr);
    show_pte(tsk->mm, addr);
    show_regs(regs);
    
    里面会打印出pc寄存器的值,有了这个就可以定位了。
    
    4. 用户态信息打印
    这个做法的主要思路就是先拦截SIGSEGV信号,然后在信号处理函数里面打印信息,信号拦截代码如下:
    
    static void catch_sigsegv()
    {
    struct sigaction action;
    memset(&action, 0, sizeof(action));
    action.sa_sigaction = sigsegv_handler;
    action.sa_flags = SA_SIGINFO;
    if(sigaction(SIGSEGV, &action, NULL) < 0)
    {
    perror("sigaction");
    }
    }
    
    只需要在main函数里面加入这个函数就可以了:
    
    main(…)
    {
    ….
    catch_sigsegv();
    …
    }
    
    下面来看看这个处理函数sigsegv_handler是怎么写的,代码如下:
    
    static void sigsegv_handler(int signum, siginfo_t* info, void*ptr)
    {
    static const char *si_codes[3] = {"", "SEGV_MAPERR", "SEGV_ACCERR"};
    int i;
    ucontext_t *ucontext = (ucontext_t*)ptr;
    void *bt[100];
    char **strings;
    
    printf("Segmentation Fault Trace:
    ");
    printf("info.si_signo = %d
    ", signum);
    printf("info.si_errno = %d
    ", info->si_errno);
    printf("info.si_code = %d (%s)
    ", info->si_code, si_codes[info->si_code]);
    printf("info.si_addr = %p
    ", info->si_addr);
    
    /*for arm*/
    printf("the arm_fp 0x%3x
    ",ucontext->uc_mcontext.arm_fp);
    printf("the arm_ip 0x%3x
    ",ucontext->uc_mcontext.arm_ip);
    printf("the arm_sp 0x%3x
    ",ucontext->uc_mcontext.arm_sp);
    printf("the arm_lr 0x%3x
    ",ucontext->uc_mcontext.arm_lr);
    printf("the arm_pc 0x%3x
    ",ucontext->uc_mcontext.arm_pc);
    printf("the arm_cpsr 0x%3x
    ",ucontext->uc_mcontext.arm_cpsr);
    printf("the falut_address 0x%3x
    ",ucontext->uc_mcontext.fault_address);
    
    **/*backtrace函数有的系统不支持,如hisi平台/FH平台等不支持*/**
    printf("Stack trace (non-dedicated):");
    int sz = backtrace(bt, 20);
    printf("the stack trace is %d
    ",sz);
    strings = backtrace_symbols(bt, sz);
    for(i = 0; i < sz; ++i)
    {
    printf("%s
    ", strings[i]);
    }
    _exit (-1);
    }
    
    测试代码如下:
    
    void test_segv()
    {
    char *i=0;
    *i=10;//产生段错误的位置
    }
    void cause_segv()
    {
    printf("this is the cause_segv
    ");
    test_segv();//调用函数
    }
    int main(int argc,char **argv)
    {
    catch_sigsegv();//初始化注册捕捉函数
    cause_segv();
    return 0;
    }
    
    编译方法:
    gcc segment_trace.c -g –rdynamic –o segment_trace
    执行:
    ./segment_trace
    输出如下:
    this is the catch_sigsegv
    Segmentation Fault Trace:
    info.si_signo = 11
    info.si_errno = 0
    info.si_code = 1 (SEGV_MAPERR)
    info.si_addr = (nil)
    the arm_fp 0xb7f8a3d4
    the arm_ip 0xb7f8a3d8
    the arm_sp 0xb7f8a3c0
    the arm_lr 0x8998
    the arm_pc 0x8974
    the arm_cpsr 0x60000010
    the falut_address 0x 0
    Stack trace (non-dedicated):the stack trace is 5
    ./segment_trace(backtrace_symbols+0x1c8) [0x8844]
    /lib/libc.so.6(__default_rt_sa_restorer+0) [0xb5e22230]
    ./segment_trace(cause_segv+0x18) [0x8998]
    ./segment_trace(main+0x20) [0x89c0]
    /lib/libc.so.6(__libc_start_main+0x108) [0xb5e0c10c]
    
    5. 输出信息分析
    根据上面的输出可以看出一些端倪:
    
    根据栈信息,可以看出是在cause_segv里面出了问题,但是最后一层栈信息是看不到的,另外需要根据pc寄存器的值来定位:
    addr2line -f -e segment_trace 0x8974
    test_segv
    /home/wf/test/segment_trace.c:55
    可以看到说是在55行,一看:
    刚好是
    *i=10;
    这一行,
    而且可以看出,函数名是test_segv,
    所以基本上不需要打印栈信息,也可以定位了。
    
    6. 注意
    这个方法最好不要用在多线程环境里面;
    如果打印不出栈信息,需要在内核中去掉:
    -fomit-frame-pointer编译选项;
    ————————————————
    版权声明:本文为CSDN博主「sdsszk」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/sdsszk/article/details/109765180
    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    阅读 Android源码的一些姿势
    Unity3d UGUI 界面适配 实例解析 三种适配方式
    Unity3D Android手机开发环境配置
    DOTween教程
    DoTween 部分中文文档
    C# 委托、事件,lamda表达式
    EditText中输入信息的限制的方法
    Android中shape中的属性大全
    Android 高版本API方法在低版本系统上的兼容性处理
    python 绘制f(x)=x^2
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/15118326.html
Copyright © 2011-2022 走看看