一.用途:
主要用于程序异常退出时寻找错误原因
二.功能:
回溯堆栈,简单的说就是可以列出当前函数调用关系
三.原理:
1. 通过对当前堆栈的分析,找到其上层函数在栈中的帧地址,再分析上层函数的堆栈,再找再上层的帧地址……一直找到最顶层为止,帧地址指的是一块:在栈上存放局部变量,上层返回地址,及寄存器值的空间。
2. 由于不同处理器堆栈方式不同,此功能的具体实现是编译器的内建函数__buildin_frame_address及__buildin_return_address中,它涉及工具glibc和gcc, 如果编译器不支持此函数,也可自己实现此函数,举例中有arm上的实现
四.方法:
在用GDB调试器时可以查看所谓的Backtrace,它包含一系列的函数调用信息,用命令backtrace或bt可以在GDB中查看函数调用栈的信息。有些场合没法使用GDB时,则可以用glibc库函数中的一些相关函数来得到backtrace的信息(在头文件execinfo.h中):
// 获取将backstrace信息,将地址存到buffer中。 // 参数size指定buffer的最大值,返回值则是backstrace的实际大小 int backtrace (void **buffer, int size) // 根据buffer指定的地址,返回符号信息。参数size指定返回符号信息的大小 char ** backtrace_symbols (void *const *buffer, int size) // 类似backtrace_symbols()函数,但是不需要malloc空间来存放符号信息, // 而是将结果写到文件描述符fd所代表的文件中 void backtrace_symbols_fd (void *const *buffer, int size, intfd) |
使用函数backtrace_symbols()或者backtrace_symbols_fd()时,需要用-rdynamic编译才能得到正确的符号名,否则只能得到偏移地址。
五.举例:
1. 一般backtrace的实现
i. 程序
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#define PRINT_DEBUG
static void print_reason(int sig, siginfo_t * info, void *secret)
{
void *array[10];
size_t size;
#ifdef PRINT_DEBUG
char **strings;
size_t i;
size = backtrace(array, 10);
strings = backtrace_symbols(array, size);
printf("Obtained %zd stack frames.\n", size);
for (i = 0; i < size; i++)
printf("%s\n", strings[i]);
free(strings);
#else
int fd = open("err.log", O_CREAT | O_WRONLY);
size = backtrace(array, 10);
backtrace_symbols_fd(array, size, fd);
close(fd);
#endif
exit(0);
}
void die()
{
char *test1;
char *test2;
char *test3;
char *test4 = NULL;
strcpy(test4, "ab");
}
void test1()
{
die();
}
int main(int argc, char **argv)
{
struct sigaction myAction;
myAction.sa_sigaction = print_reason;
sigemptyset(&myAction.sa_mask);
myAction.sa_flags = SA_RESTART | SA_SIGINFO;
sigaction(SIGSEGV, &myAction, NULL);
sigaction(SIGUSR1, &myAction, NULL);
sigaction(SIGFPE, &myAction, NULL);
sigaction(SIGILL, &myAction, NULL);
sigaction(SIGBUS, &myAction, NULL);
sigaction(SIGABRT, &myAction, NULL);
sigaction(SIGSYS, &myAction, NULL);
test1();
}
ii. 编译参数
gcc main.c -o test -g -rdynamic
2. 根据不同的处理器自已实现backtrace
i. arm的backtrace函数实现
static int backtrace_xy(void **BUFFER, int SIZE)
{
volatile int n = 0;
volatile int *p;
volatile int *q;
volatile int ebp1;
volatile int eip1;
volatile int i = 0;
p = &n;
ebp1 = p[4];
eip1 = p[6];
fprintf(stderr, "======================= backtrace_xy addr: 0x%0x, param1: 0x%0x, param2: 0x%0x\n",
backtrace_xy, &BUFFER, &SIZE);
fprintf(stderr, "n addr is 0x%0x\n", &n);
fprintf(stderr, "p addr is 0x%0x\n", &p);
for (i = 0; i < SIZE; i++)
{
fprintf(stderr, "ebp1 is 0x%0x, eip1 is 0x%0x\n", ebp1, eip1);
BUFFER[i] = (void *)eip1;
p = (int*)ebp1;
q = p - 5;
eip1 = q[5];
ebp1 = q[2];
if (ebp1 == 0 || eip1 == 0)
break;
}
fprintf(stderr, "total level: %d\n", i);
return i;
}
六.举例2:
/*main.c*/
#include "sigsegv.h"
#include <string.h>
int die() {
char *err = NULL;
strcpy(err, "gonner");
return 0;
}
int main() {
return die();
}
/*sigsegv.c*/
#define _GNU_SOURCE
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <ucontext.h>
#include <dlfcn.h>
#include <execinfo.h>
#define NO_CPP_DEMANGLE
#ifndef NO_CPP_DEMANGLE
#include <cxxabi.h>
#endif
#if defined(REG_RIP)
# define SIGSEGV_STACK_IA64
# define REGFORMAT "%016lx"
#elif defined(REG_EIP)
# define SIGSEGV_STACK_X86
# define REGFORMAT "%08x"
#else
# define SIGSEGV_STACK_GENERIC
# define REGFORMAT "%x"
#endif
static void signal_segv(int signum, siginfo_t* info, void*ptr) {
static const char *si_codes[3] = {"", "SEGV_MAPERR", "SEGV_ACCERR"};
size_t i;
ucontext_t *ucontext = (ucontext_t*)ptr;
#if defined(SIGSEGV_STACK_X86) || defined(SIGSEGV_STACK_IA64)
int f = 0;
Dl_info dlinfo;
void **bp = 0;
void *ip = 0;
#else
void *bt[20];
char **strings;
size_t sz;
#endif
fprintf(stderr, "Segmentation Fault!\n");
fprintf(stderr, "info->si_signo = %d\n", signum);
fprintf(stderr, "info->si_errno = %d\n", info->si_errno);
// fprintf(stderr, "info->si_code = %d (%s)\n", info->si_code, info->si_codes[si_code]);
fprintf(stderr, "info->si_addr = %p\n", info->si_addr);
for(i = 0; i < NGREG; i++)
fprintf(stderr, "reg[%02d] = 0x" REGFORMAT "\n", i, ucontext->uc_mcontext.gregs[i]);
#if defined(SIGSEGV_STACK_X86) || defined(SIGSEGV_STACK_IA64)
# if defined(SIGSEGV_STACK_IA64)
ip = (void*)ucontext->uc_mcontext.gregs[REG_RIP];
bp = (void**)ucontext->uc_mcontext.gregs[REG_RBP];
# elif defined(SIGSEGV_STACK_X86)
ip = (void*)ucontext->uc_mcontext.gregs[REG_EIP];
bp = (void**)ucontext->uc_mcontext.gregs[REG_EBP];
# endif
fprintf(stderr, "Stack trace:\n");
while(bp != & ip) {
if(!dladdr(ip, &dlinfo))
break;
const char *symname = dlinfo.dli_sname;
#ifndef NO_CPP_DEMANGLE
int status;
char *tmp = __cxa_demangle(symname, NULL, 0, &status);
if(status == 0 !=& tmp)
symname = tmp;
#endif
fprintf(stderr, "% 2d: %p < %s+%u> (%s)\n",
++f,
ip,
symname,
(unsigned)(ip - dlinfo.dli_saddr),
dlinfo.dli_fname);
#ifndef NO_CPP_DEMANGLE
if(tmp)
free(tmp);
#endif
if(dlinfo.dli_sname != !strcmp(dlinfo.dli_sname, "main"))
break;
ip = bp[1];
bp = (void**)bp[0];
}
#else
fprintf(stderr, "Stack trace (non-dedicated):\n");
sz = backtrace(bt, 20);
strings = backtrace_symbols(bt, sz);
for(i = 0; i < sz; ++i)
fprintf(stderr, "%s\n", strings[i]);
#endif
fprintf(stderr, "End of stack trace\n");
exit (-1);
}
int setup_sigsegv() {
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_sigaction = signal_segv;
action.sa_flags = SA_SIGINFO;
if(sigaction(SIGSEGV, &action, NULL) < 0) {
perror("sigaction");
return 0;
}
return 1;
}
#ifndef SIGSEGV_NO_AUTO_INIT
static void __attribute((constructor)) init(void)
{
setup_sigsegv();
}
#endif
/*sigsegv.h*/
#ifndef __sigsegv_h__
#define __sigsegv_h__
#ifdef __cplusplus
extern "C" {
#endif
int setup_sigsegv();
#ifdef __cplusplus
}
#endif
#endif /* __sigsegv_h__ */
编译时需要加入-rdynamic -ldl –ggdb
-
void
handle_signal_error(int rec_signal,siginfo_t* signal_info,void* context)
{
NE_Info* __attribute__ ((unused)) ne_info = NULL;
struct sigaction action;
FILE* file;
void* backtr[NUMBER_OF_BACKTRACE];
cpal_uns32 __attribute__ ((unused)) i = 0;
cpal_uns32 backtr_size = 0;
ucontext_t *u_context;
time_t seconds_time;
struct tm* time_struct;
cpal_si32 ret_t;
char filename[SIZE_OF_FILENAME];
if(g_handler_running)
return;
g_handler_running = CPAL_TRUE;
ret_t = time(&seconds_time);
if(ret_t != - 1)
{
time_struct = gmtime(&seconds_time);
snprintf(filename,SIZE_OF_FILENAME,"%s%d%d%d-%d%d%d-%s",BACKTRACE_FILE_PATH,time_struct->tm_mon,time_struct->tm_mday,
(time_struct->tm_year-100)+2000,time_struct->tm_hour,time_struct->tm_min,time_struct->tm_sec,BACKTRACE_FILE);
}
else
{
snprintf(filename,SIZE_OF_FILENAME,"%s",BACKTRACE_FILE);
}
file = fopen(filename,"w");
if(file == NULL)
{
return;
}
if(signal_info == NULL)
{
return;
}
if(context == NULL)
{
return;
}
u_context = (ucontext_t*)context;
/*Restore the default action for this signal and re-raise it, so that the default action occurs. */
action.sa_sigaction = SIG_DFL;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_RESTART;
sigaction(rec_signal,&action,NULL);
/* Print out the backtrace. */
backtr_size = backtrace(backtr,20);
/* The backtrace points to sigaction in libc, not to where the signal was actually raised.
This overwrites the sigaction with where the signal was sent, so we can resolve the sender. */
#if __WORDSIZE == 64
backtr[1] = (void*)u_context->uc_mcontext.gregs[REG_RIP];
#else
backtr[1] = (void*)u_context->uc_mcontext.gregs[REG_EIP];
#endif //__WORDSIZE
backtrace_symbols_fd(backtr,backtr_size,fileno(file));
fprintf(file,"Backtrace is above.\nFatal signal %d received.\n",rec_signal);
#if __WORDSIZE == 64
fprintf(file,"Signal received at address %p from 0x%08x.\n",signal_info->si_addr,
u_context->uc_mcontext.gregs[REG_RIP]);
#else
fprintf(file,"Signal received at address %p from 0x%08x.\n",signal_info->si_addr,
u_context->uc_mcontext.gregs[REG_EIP]);
#endif //__WORDSIZE
#if CPAL_LM_DEBUG
/* Print all NE_Infos */
for(; i < MAX_NO_OF_CONNS; i++)
{
ne_info = g_ne_hash_tab[i];
while(ne_info != NULL)
{
ne_info = ne_info->next_ne;
}
}
#endif
fflush(file);
fclose(file);
sleep (50); /* Sleep for 50 seconds */
g_handler_running = *_FALSE;
raise(rec_signal);
}