堆栈回溯实践
堆栈回溯实践
总结
目前堆栈回溯存在两套代码:
- 代码1使用系统库函数
backtrace()
自动进行堆栈回溯,在x86,mips,arm架构上实践证明有效. - 代码2手动进行堆栈回溯,通过
fp
,ra
等指针,一层一层找到调用的函数地址,并通过函数dladdr()
得到函数名,使用此函数时链接选项添加-ldl
,头文件为dlfcn.h
。这种方案在x86,arm架构上有效,mips的堆栈帧太过复杂,目前没有手动回溯成功。
通过工具链自定义的宏控,可以将两套代码合并,使用在我们的平台下。
编译时用到的小知识
- 使用
gcc -E -dM - </dev/null
查看工具链预定义的宏,如x86工具链预定义了__x86_64__
,类似可以知道arm,mips预定义的类似宏(在方案二的代码中用到)。 - 使用
gcc -E -v - </dev/null
查看交叉工具链的头文件与库文件搜索路径,方便我们使用交叉工具链进行编译。
编译注意事项
- 使用
-g -dynamic
产生必要的调试信息 - 若手动进行符号解析,需要使用
dladdr
函数,链接选项添加-ldl
- arm下编译选项需要带有
-mapcs-frame
:对所有函数都生成一个遵从ARM程序调用标准的堆栈帧,即使在正确执行代码无需严格这么做时。使用此开关时指定“-fomit-frame-pointer”将不产生叶函数的堆栈帧。缺省情况下是“-mno-apcs-frame”。使用此编译选项,方案2即可有效。参考https://blog.csdn.net/weixin_30270561/article/details/94854486 - arm下编译选项需要带有
-mapcs-frame -fexceptions
方案1才有效。 - mips下可能需要编译选项
-fexceptions -fno-omit-frame-pointer -O0
(没有严格验证,只是为防止意外)
代码1(mips,arm,x86下有效)
#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
void handler(int sig) {
void *array[10];
size_t size;
// get void*'s for all entries on the stack
size = backtrace(array, 10);
// print out all the frames to stderr
fprintf(stderr, "Error: signal %d:\n", sig);
backtrace_symbols_fd(array, size, STDERR_FILENO);
exit(1);
}
void baz() {
//int *foo = (int*)-1; // make a bad pointer
int *foo =(int*)123;
printf("%d\n", *foo); // causes segfault
}
void bar() { baz(); }
void foo() { bar(); }
int main(int argc, char **argv) {
signal(SIGSEGV, handler); // install our handler
foo(); // this will call foo, bar, and baz. baz segfaults.
}
注意事项
- 注释掉的
int *foo=(int*)-1;
使用时,程序运行的错误为Bus error
而不是段错误; - 信号处理函数中使用
backtrace_symbols_fd()
而不是backtrace_symbols()
,实践显示使用后一个函数堆栈回溯并不正确,只显示了一层,即使编译选项添加了-fexceptions
也只显示了错误的三层。看到的说法是backtrace_symbols()
函数使用了malloc
不能在信号处理函数中调用(信号处理函数中不能使用不可重入函数)。
参考链接
https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes?answertab=votes#tab-top
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
https://sourceware.org/git/?p=glibc.git;a=tree;f=debug
代码2(x86,arm下有效,mips下无效)
#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"
#endif
static 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_info dl_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;
#endif
sigsegv_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_DEMANGLE
int 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_DEMANGLE
if (tmp) free(tmp);
#endif
if (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 0
memset(&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 1
void 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