捕捉信号SIGSEGV并回溯栈帧
来源:互联网 发布:手机网页游戏平台源码 编辑:程序博客网 时间:2024/06/03 17:19
引出问题
在嵌入式应用程序开发过程中,调试一直是个老大难问题 -- 由于环境的限制,当程序发生段错误时不能很好的定位到底是哪里出现了错误,如果在程序发生段错误时能够清晰明了地看到程序的栈帧链,那无疑是雪中送炭。本文就捕捉信号SIGSEGV并在该信号的处理函数中打印出函数栈帧链来帮助我们调试程序。
本文的程序适合ARM和X86平台。
回溯栈帧原理
理解函数栈帧的布局后,那么自然明白回溯栈帧的原理了,这里不多解释了,直接上图(来自网络):
x86函数栈帧结构
ARM函数栈帧结构
代码sigsegv.c
#ifndef _GNU_SOURCE#define _GNU_SOURCE#endif#include <stdio.h>#include <dlfcn.h>#include <stdlib.h>#include <signal.h>#include <unistd.h>#include <string.h>#include <ucontext.h>/* 纯C环境下,不定义宏NO_CPP_DEMANGLE */#if (!defined(__cplusplus)) && (!defined(NO_CPP_DEMANGLE))#define NO_CPP_DEMANGLE#endif#ifndef NO_CPP_DEMANGLE #include <cxxabi.h> #ifdef __cplusplus using __cxxabiv1::__cxa_demangle; #endif#endif#ifdef HAS_ULSLIB#include <uls/logger.h>#define sigsegv_outp(x)sigsegv_outp(, gx)#else#define sigsegv_outp(x, ...) fprintf(stderr, x"\n", ##__VA_ARGS__)#endif#if (defined __x86_64__)#define REGFORMAT "%016lx"#elif (defined __i386__)#define REGFORMAT "%08x"#elif (defined __arm__)#define REGFORMAT "%lx"#endifstatic void print_reg(ucontext_t *uc) {#if (defined __x86_64__) || (defined __i386__)int i;for (i = 0; i < NGREG; i++) {sigsegv_outp("reg[%02d]: 0x"REGFORMAT, i, uc->uc_mcontext.gregs[i]);}#elif (defined __arm__)sigsegv_outp("reg[%02d]= 0x"REGFORMAT, 0, uc->uc_mcontext.arm_r0);sigsegv_outp("reg[%02d]= 0x"REGFORMAT, 1, uc->uc_mcontext.arm_r1);sigsegv_outp("reg[%02d]= 0x"REGFORMAT, 2, uc->uc_mcontext.arm_r2);sigsegv_outp("reg[%02d]= 0x"REGFORMAT, 3, uc->uc_mcontext.arm_r3);sigsegv_outp("reg[%02d]= 0x"REGFORMAT, 4, uc->uc_mcontext.arm_r4);sigsegv_outp("reg[%02d]= 0x"REGFORMAT, 5, uc->uc_mcontext.arm_r5);sigsegv_outp("reg[%02d]= 0x"REGFORMAT, 6, uc->uc_mcontext.arm_r6);sigsegv_outp("reg[%02d]= 0x"REGFORMAT, 7, uc->uc_mcontext.arm_r7);sigsegv_outp("reg[%02d]= 0x"REGFORMAT, 8, uc->uc_mcontext.arm_r8);sigsegv_outp("reg[%02d]= 0x"REGFORMAT, 9, uc->uc_mcontext.arm_r9);sigsegv_outp("reg[%02d]= 0x"REGFORMAT, 10, uc->uc_mcontext.arm_r10);sigsegv_outp("FP= 0x"REGFORMAT, uc->uc_mcontext.arm_fp);sigsegv_outp("IP= 0x"REGFORMAT, uc->uc_mcontext.arm_ip);sigsegv_outp("SP= 0x"REGFORMAT, uc->uc_mcontext.arm_sp);sigsegv_outp("LR= 0x"REGFORMAT, uc->uc_mcontext.arm_lr);sigsegv_outp("PC= 0x"REGFORMAT, uc->uc_mcontext.arm_pc);sigsegv_outp("CPSR= 0x"REGFORMAT, uc->uc_mcontext.arm_cpsr);sigsegv_outp("Fault Address= 0x"REGFORMAT, uc->uc_mcontext.fault_address);sigsegv_outp("Trap no= 0x"REGFORMAT, uc->uc_mcontext.trap_no);sigsegv_outp("Err Code= 0x"REGFORMAT, uc->uc_mcontext.error_code);sigsegv_outp("Old Mask= 0x"REGFORMAT, uc->uc_mcontext.oldmask);#endif}static void print_call_link(ucontext_t *uc) {int i = 0;void **frame_pointer = (void **)NULL;void *return_address = NULL;Dl_infodl_info = { 0 };#if (defined __i386__)frame_pointer = (void **)uc->uc_mcontext.gregs[REG_EBP];return_address = (void *)uc->uc_mcontext.gregs[REG_EIP];#elif (defined __x86_64__)frame_pointer = (void **)uc->uc_mcontext.gregs[REG_RBP];return_address = (void *)uc->uc_mcontext.gregs[REG_RIP];#elif (defined __arm__)/* sigcontext_t on ARM: unsigned long trap_no; unsigned long error_code; unsigned long oldmask; unsigned long arm_r0; ... unsigned long arm_r10; unsigned long arm_fp; unsigned long arm_ip; unsigned long arm_sp; unsigned long arm_lr; unsigned long arm_pc; unsigned long arm_cpsr; unsigned long fault_address;*/frame_pointer = (void **)uc->uc_mcontext.arm_fp;return_address = (void *)uc->uc_mcontext.arm_pc;#endifsigsegv_outp("\nStack trace:");while (frame_pointer && return_address) {if (!dladdr(return_address, &dl_info))break;const char *sname = dl_info.dli_sname;#ifndef NO_CPP_DEMANGLEint status;char *tmp = __cxa_demangle(sname, NULL, 0, &status);if (status == 0 && tmp) {sname = tmp;}#endif/* No: return address <sym-name + offset> (filename) */sigsegv_outp("%02d: %p <%s + %lu> (%s)", ++i, return_address, sname, (unsigned long)return_address - (unsigned long)dl_info.dli_saddr, dl_info.dli_fname);#ifndef NO_CPP_DEMANGLEif (tmp)free(tmp);#endifif (dl_info.dli_sname && !strcmp(dl_info.dli_sname, "main")) {break;}#if (defined __x86_64__) || (defined __i386__)return_address = frame_pointer[1];frame_pointer = frame_pointer[0];#elif (defined __arm__)return_address = frame_pointer[-1];frame_pointer = (void **)frame_pointer[-3];#endif}sigsegv_outp("Stack trace end.");}static void sigsegv_handler(int signo, siginfo_t *info, void *context){if (context) {ucontext_t *uc = (ucontext_t *)context; sigsegv_outp("Segmentation Fault!"); sigsegv_outp("info.si_signo = %d", signo); sigsegv_outp("info.si_errno = %d", info->si_errno); sigsegv_outp("info.si_code = %d (%s)", info->si_code, (info->si_code == SEGV_MAPERR) ? "SEGV_MAPERR" : "SEGV_ACCERR"); sigsegv_outp("info.si_addr = %p\n", info->si_addr);print_reg(uc);print_call_link(uc);}_exit(0);}#define SETSIG(sa, sig, fun, flags) \ do { \ sa.sa_sigaction = fun; \ sa.sa_flags = flags; \ sigemptyset(&sa.sa_mask); \ sigaction(sig, &sa, NULL); \ } while(0)static void __attribute((constructor)) setup_sigsegv(void) {struct sigaction sa;SETSIG(sa, SIGSEGV, sigsegv_handler, SA_SIGINFO); #if 0memset(&sa, 0, sizeof(struct sigaction));sa.sa_sigaction = sigsegv_handler;sa.sa_flags = SA_SIGINFO;if (sigaction(SIGSEGV, &sa, NULL) < 0) {perror("sigaction: ");}#endif}#if 1void func3(void){char *p = (char *)0x12345678;*p = 10;}void func2(void){func3();}void func1(void){func2();}int main(int argc, const char *argv[]){func1();exit(EXIT_SUCCESS);}#endif编译时请加上-rdynamic -ldl选项!
代码地址:https://github.com/astrotycoon/sigsegv/blob/master/sigsegv.c
2016-1-17 18:29:33补充知识如下:
今天无意中发现原来gcc
相关阅读:
《 linux下利用backtrace追踪函数调用堆栈以及定位段错误》
《Stack Backtracing Inside Your Program》
《Obtaining a stack trace in C upon SIGSEGV》
参考链接:
《利用堆栈回溯、addr2line和Graphviz生成运行时函数调用图》
《Stack backtrace 的实现》
《linux backtrace()详细使用说明,分析Segmentation fault》
《Backtracing from code in ARM》(需要翻墙)
《Programmatic access to the call stack in C++》
《Stack unwinding (stack trace) with GCC》
《浅析ARM汇编语言子例程设计方法》
《自己动手实现arm函数栈帧回溯》
《Pre-mortem Backtracing》
《C++ Code Snippet - Print Stack Backtrace Programmatically with Demangled Function Names》
《内存泄露调试代码 可以解析C++ symbol》
《将信号用作 Linux 调试工具》
《Get an exception from a segfault on linux (x86 and x86_64), using some black magic !》
《broadcom6838开发环境实现函数栈追踪》
- 捕捉信号SIGSEGV并回溯栈帧
- 捕捉信号SIGSEGV并回溯栈帧
- 捕捉信号SIGSEGV并回溯栈帧backtrace
- SIGSEGV 信号捕捉,setjmp/longjmp记录上下文跳转
- SIGSEGV信号
- linux SIGSEGV 信号捕捉,保证发生段错误后程序不崩溃
- 信号捕捉
- 阻塞信号和捕捉信号
- Linux 信号捕捉trap
- signal信号捕捉
- signal信号捕捉
- QTextEdit捕捉回车信号
- signal() 捕捉信号
- QTextEdit捕捉回车信号
- signal 函数捕捉信号
- signal信号捕捉
- 漂亮的信号捕捉
- trap捕捉信号学习
- Apache安装
- 8步从Python白板到专家,从基础到深度学习
- 走进软考(1)— 完成看视频和看教程的时代
- php 引用变量机制
- Webview && 浏览器
- 捕捉信号SIGSEGV并回溯栈帧
- 获取json数据的属性名
- 改正 抢占式camera的可能带来死锁问题
- SDUT 2084 DOTA-人王之战(博弈论)
- Java基本输入输出
- iOS网络开发——解析与封装JSON数据
- 01背包
- VS报错:Missing type map configuration or unsupported mapping.
- SDUT 2044 Number Sequence(循环)