内核段错误跟踪

转载自:

http://blog.chinaunix.net/u2/67984/showart_2166165.html

 

段错误跟踪

1. 需求的产生

写程序难免会出现段错误的情况,这时候很想知道,到底在什么地方崩溃了,对于代码很少,或者你很有把握的时候,或许用二分法配合 printf 就可以搞定了;而对于非常复杂的代码,比如像 Xserver 这样的程序,可能就不太好定位了;

(本文讨论的情况都是针对 arm 环境,并且 gdb 不方便使用的情况)

2.     解决思路

思路其实很简单,对于用户态段错误的原因,大约可以分为两种 ,

<!-- [if !supportLists]-->a)       <!-- [endif]-->没有权限访问这个地址;

<!-- [if !supportLists]-->b)      <!-- [endif]-->访问的地址没有映射,比如 NULL 地址;

当出现这两种情况的时候, linux 内核都会向用户态的程序发送 SIGSEGV 的信号,于是程序执行默认的信号处理函数,就退出了;

所以有两个解决办法:

<!-- [if !supportLists]-->A) <!-- [endif]-->在内核里面把这些寄存器打印出来;

<!-- [if !supportLists]-->B) <!-- [endif]-->在上层程序里面把寄存器打印出来;

下面来分别说明:

3.          内核信息打印

内核的执行路径如下:

       3.0 段错误时内核执行路径

我们只需要在 __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\n",

                     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\n",

                     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 是怎么写的,代码如下:

#include <memory.h>

#include <stdlib.h>

#include <stdio.h>

#include <unistd.h>

#include <signal.h>

#include <ucontext.h>

#include <dlfcn.h>

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:\n");

        printf("info.si_signo = %d\n", signum);

        printf("info.si_errno = %d\n", info->si_errno);

        printf("info.si_code  = %d (%s)\n", info->si_code, si_codes[info->si_code]);

        printf("info.si_addr  = %p\n", info->si_addr);

 

        /*for arm*/

        printf("the arm_fp 0x%3x\n",ucontext->uc_mcontext.arm_fp);

        printf("the arm_ip 0x%3x\n",ucontext->uc_mcontext.arm_ip);

        printf("the arm_sp 0x%3x\n",ucontext->uc_mcontext.arm_sp);

        printf("the arm_lr 0x%3x\n",ucontext->uc_mcontext.arm_lr);

         printf("the arm_pc 0x%3x\n",ucontext->uc_mcontext.arm_pc);

        printf("the arm_cpsr 0x%3x\n",ucontext->uc_mcontext.arm_cpsr);

        printf("the falut_address 0x%3x\n",ucontext->uc_mcontext.fault_address);

 

        printf("Stack trace (non-dedicated):");

        int sz = backtrace(bt, 20);

        printf("the stack trace is %d\n",sz);

        strings = backtrace_symbols(bt, sz);

        for(i = 0; i < sz; ++i){

                printf("%s\n", strings[i]);

        }

    _exit (-1);

}

 

 

测试代码如下:

void test_segv()

{

        char *i=0;

        *i=10;

 

}

 

void cause_segv()

{

        printf("this is the cause_segv\n");

        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 编译选项;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值