转载自:
http://blog.chinaunix.net/u2/67984/showart_2166165.html
段错误跟踪
写程序难免会出现段错误的情况,这时候很想知道,到底在什么地方崩溃了,对于代码很少,或者你很有把握的时候,或许用二分法配合 printf 就可以搞定了;而对于非常复杂的代码,比如像 Xserver 这样的程序,可能就不太好定位了;
(本文讨论的情况都是针对 arm 环境,并且 gdb 不方便使用的情况)
思路其实很简单,对于用户态段错误的原因,大约可以分为两种 ,
<!-- [if !supportLists]-->a) <!-- [endif]-->没有权限访问这个地址;
<!-- [if !supportLists]-->b) <!-- [endif]-->访问的地址没有映射,比如 NULL 地址;
当出现这两种情况的时候, linux 内核都会向用户态的程序发送 SIGSEGV 的信号,于是程序执行默认的信号处理函数,就退出了;
所以有两个解决办法:
<!-- [if !supportLists]-->A) <!-- [endif]-->在内核里面把这些寄存器打印出来;
<!-- [if !supportLists]-->B) <!-- [endif]-->在上层程序里面把寄存器打印出来;
下面来分别说明:
内核的执行路径如下:
图 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 寄存器的值,有了这个就可以定位了,定位办法后面再讲;
这个做法的主要思路就是先拦截 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]
根据上面的输出可以看出一些端倪:
根据栈信息,可以看出是在 cause_segv 里面出了问题,但是最后一层栈信息是看不到的,另外需要根据 pc 寄存器的值来定位:
addr2line -f -e segment_trace 0x8974
test_segv
/home/wf/test/segment_trace.c:55
可以看到说是在 55 行,一看 :
刚好是
*i=10;
这一行,
而且可以看出,函数名是 test_segv ,
所以基本上不需要打印栈信息,也可以定位了。
这个方法最好不要用在多线程环境里面;
如果打印不出栈信息,需要去掉:
-fomit-frame-pointer 编译选项;