嵌入式arm开发C语言调用栈回溯实战
-
代码
#define _GNU_SOURCE #endif #ifndef __USE_GNU #define __USE_GNU #endif #include <execinfo.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ucontext.h> #include <unistd.h> /* This structure mirrors the one found in /usr/include/asm/ucontext.h (arm: arm-linux-gnueabihf/libc/usr/include/asm-generic/ucontext.h) */ typedef struct _sig_ucontext { unsigned long uc_flags; struct ucontext *uc_link; stack_t uc_stack; struct sigcontext uc_mcontext; sigset_t uc_sigmask; } sig_ucontext_t; void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) { void * array[50]; void * caller_address; char ** messages; int size, i; sig_ucontext_t * uc; uc = (sig_ucontext_t *)ucontext; /* Get the address at the time the signal was raised */ #if defined(__i386__) // gcc specific caller_address = (void *) uc->uc_mcontext.eip; // EIP: x86 specific #elif defined(__x86_64__) // gcc specific caller_address = (void *) uc->uc_mcontext.rip; // RIP: x86_64 specific #else //#error Unsupported architecture. // TODO: Add support for other arch. caller_address = (void *) uc->uc_mcontext.arm_pc; // ARM #endif fprintf(stderr, "signal %d (%s), address is %p from %p\n", sig_num, strsignal(sig_num), info->si_addr, (void *)caller_address); size = backtrace(array, 50); /* overwrite sigaction with caller's address */ array[1] = caller_address; messages = backtrace_symbols(array, size); /* skip first stack frame (points here) */ for (i = 1; i < size && messages != NULL; ++i) { fprintf(stderr, "[bt]: (%d) %s\n", i, messages[i]); } free(messages); exit(EXIT_FAILURE); } int crash() { char * p = NULL; *p = 0; return 0; } int foo4() { crash(); return 0; } int foo3() { foo4(); return 0; } int foo2() { foo3(); return 0; } int foo1() { foo2(); return 0; } int main(int argc, char ** argv) { struct sigaction sigact; sigact.sa_sigaction = crit_err_hdlr; sigact.sa_flags = SA_RESTART | SA_SIGINFO; if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0) { fprintf(stderr, "error setting signal handler for %d (%s)\n", SIGSEGV, strsignal(SIGSEGV)); exit(EXIT_FAILURE); } foo1(); exit(EXIT_SUCCESS); }
-
编译
arm-linux-gnueabihf-gcc -o test -rdynamic -mapcs-frame -funwind-tables -ffunction-sections backtrace.c
-
运行
signal 11 (Segmentation fault), address is (nil) from 0x10a32 [bt]: (1) ./Test(crash+0xd) [0x10a32] [bt]: (2) ./Test(crash+0xd) [0x10a32] [bt]: (3) ./Test(foo4+0x7) [0x10a4c] [bt]: (4) ./Test(foo3+0x7) [0x10a5c] [bt]: (5) ./Test(foo2+0x7) [0x10a6c] [bt]: (6) ./Test(foo1+0x7) [0x10a7c] [bt]: (7) ./Test(main+0x5d) [0x10ae2] [bt]: (8) /lib/libc.so.6(__libc_start_main+0x9b) [0xb6ec9334]
-
回溯
arm-linux-gnueabihf-addr2line -e ./test -C -f 0x10a4c arm-linux-gnueabihf-objdump -s -d ./test --start-address=0x10a00 --stop-address=0x10a80
-
更多参考
-
- 用的uclib版本较低,没有这些函数,但又需要,只能自己实现了(较高的版本应该有这些函数,换版本很麻烦),而且可以加深自己对这方面的理解
-
利用backtrace和ucontex定位segment错误
- 上面通过backtrace可以大体得到”segmentfault”错误时的函数调用栈,然而仅凭backtrace还是不能得到引起异常的指令地址(甚至连引起异常的函数也无法得到)。在Redis的源码中,看到了打印指令地址的方法。使用ucontext_t结构,打印出指令寄存器的内容。