c/c++ backtrace打印函数调用栈

效果

解析动态库libtest.so和可执行文件m:

c/c++ backtrace打印函数调用栈_第1张图片

打印原始栈

#include

    void *array[32] = {0};
	size_t size;
	char **strings;
	
	size = backtrace (array, 32);
	strings = backtrace_symbols (array, size);
	for (int i = 1; i < size -2; i++)
	{
		printf("%s ",strings[i]);
	}

解析_addr2line

其实很简单,先得到原栈信息,然后
addr2line -Cif -e [可执行文件或者.so文件名] [地址,如果是.so文件要计算相对地址]
就可以啦.
下面是一个例子,我开了.h里面的调试开关的.
c/c++ backtrace打印函数调用栈_第2张图片
先看最下面的,对 ./m(main+0x107) [0x400d27]的解析就是直接用的命令addr2line -Cif -e ./m 0x400d27

然后看动态库的解析,./libtest.so(_Z7fun_funv+0x9) [0x7f85cf46a615] => addr2line -Cif -e ./libtest.so 0x615
0x615 = 0x7f85cf46a615 - 0x7f85cf46a000,0x7f85cf46a000是怎么知道的?
用/proc/self_pid/maps命令得到,图片里面上面部分的打印都是这个命令的输出.带 r-xp项的.

代码

debug.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "debug.h"

/*内部结构体*/
typedef struct {
   char not_care[4][32];
   char library_path[128];
   char *offset_start;
   char *offset_end;
}library_maps_t;

typedef struct{
    char exe[128];
    char* offset;
}bt_t;

static int max_lib = 0;
static library_maps_t lib[MAX_LIB_NUM];

/***********************************************************
  ret =  NULL || len = 0 : 直接printf打印结果
  ret != NULL && len > 0 : 结果保存到 char ret[len]
************************************************************/
static void output_addrline(bt_t *bt,char ret[],size_t len)
{
	char cmd[256]     = {0};
	char line[2][256] = {0};
	char addrline[32] = {0};
    int idx = 0;
    int i   = 0;
	FILE* file;
    char* offset = bt->offset;

    if(bt==NULL || bt->exe == NULL)
    {
        printf("[%s][%d]Error bt:%p,exe:%p\n",__FUNCTION__,__LINE__,bt,bt->exe);
        return;
    }
    
    if(offset<lib[idx].offset_start || offset>lib[idx].offset_end)
    {
        idx++;
        while(idx < max_lib && (offset<lib[idx].offset_start || offset>lib[idx].offset_end))
        {
            idx++;
        }
        if(idx == max_lib) 
        {
            return;
        }
        offset = (char*)(offset - lib[idx].offset_start);
    }

    snprintf(cmd, sizeof(cmd), "addr2line -Cif -e %s %p ", bt->exe, offset);
#ifdef BT_MOD_DEBUG_ON 
    printf("{cmd:%s}",cmd);
#endif
    file = popen(cmd, "r");
    if(file == NULL) return;
    
    while(i < 2 && NULL != fgets(line[i], 256, file)) i++;

    pclose(file);
    line[0][strlen(line[0])-1] = 0;
    if(ret==NULL || len == 0)
    {
#ifdef __cplusplus
        printf("%s %s", line[0], line[1]);
#else
        printf("%s() %s", line[0], line[1]);
#endif 
    }
    else
    {
#ifdef __cplusplus
        snprintf(ret, len, "%s %s", line[0], line[1]);
#else
        snprintf(ret, len, "%s() %s", line[0], line[1]);
#endif
    }

    return ;
}

/***********************************************************
return:
     0:success
    -1:false
************************************************************/
int dump_library_maps()
{
    char cmd[64] = {0};
    char maps_line[512];
    char *last_library_path = NULL;
    int num_exe = 0;
    int maps_column_num;
    library_maps_t* temp = &lib[0];
    FILE* fd_maps;
    
    memset(&lib, 0, sizeof(lib));
    memset(maps_line,0,sizeof(maps_line));
    
    /* 1 get maps info to file*/
    snprintf(cmd, sizeof(cmd), "/proc/%d/maps", getpid());
    fd_maps=fopen(cmd,"r");
    if(fd_maps == NULL)
    {
        printf("ERROR\n");
        return -1;
    }
    
    /* 2 save maps info to lib*/
    while(NULL!=fgets(maps_line,sizeof(maps_line),fd_maps))
    {
        maps_column_num = sscanf(maps_line,"%p-%p\t%s\t%s\t%s\t%s\t%s"
                              ,&temp->offset_start
                              ,&temp->offset_end
                              ,temp->not_care[0]
                              ,temp->not_care[1]
                              ,temp->not_care[2]
                              ,temp->not_care[3]
                              ,temp->library_path);
#ifdef BT_MOD_DEBUG_ON 
        printf("%p-%p\t%s\t%s\t%s\t%s\t%s\n",temp->offset_start,temp->offset_end,temp->not_care[0],
              temp->not_care[1],temp->not_care[2],temp->not_care[3],temp->library_path);
#endif
        if(maps_column_num == 7 &&( (num_exe == 0 && 0==strcmp("r-xp",temp->not_care[0])) 
           || strcmp(temp->library_path,temp[-1].library_path)) )
        {
            if(num_exe == MAX_LIB_NUM)
            {
                printf("Error MAX_LIB_NUM is %d!!!\n",MAX_LIB_NUM);
                break;
            }
            temp++;
            num_exe++;
        }
        else
        {   /*so文件 是记录[min_offset, r-xp:offset_end]*/
            if(  0==strcmp("r-xp",temp->not_care[0]) 
              && 0==strcmp(temp->library_path,temp[-1].library_path))
            {
                temp[-1].offset_end = temp->offset_end;
            }
            memset(temp, 0, sizeof(*temp));
        }
        
        memset(maps_line, 0, sizeof(maps_line));
    }

    fclose(fd_maps);
    max_lib = num_exe;
    printf("[%s][%d]debug_backtrace_init success,num_exe:%d\n",__FUNCTION__,__LINE__,num_exe);
#ifdef BT_MOD_DEBUG_ON 
    num_exe = 0;
    printf("\n============= so lib info ===========\n");
    while(num_exe < max_lib)
    {
        printf("%p-%p\t%s\t%s\n",lib[num_exe].offset_start,lib[num_exe].offset_end,temp->not_care[0],lib[num_exe].library_path);
        num_exe++;
    }
    printf("=============     end     ===========\n");
#endif
    return 0;
}

/***********************************************************
return:
     0:success
    -1:false
************************************************************/
void dump_backtrace(void)
{
	void *array[32] = {0};
	size_t size,i;
	char **strings;
	int num;
	bt_t bt;
    
	size = backtrace (array, 32);
	strings = backtrace_symbols (array, size);
	if (NULL == strings)
	{
		printf("[%s][%d]Error at backtrace_symbols()\n",__FUNCTION__,__LINE__);
		return ;
	}
    
	printf("\n##################backtrace###################\n");
	for (i = 1; i < size -2; i++)
	{
		printf("%s ",strings[i]);
        num = sscanf(strings[i],"%[^(]%*[^ ] [%p]",bt.exe,&bt.offset);
#ifdef BT_MOD_DEBUG_ON 
        printf("{%d,%s,%p}\n",num,bt.exe,bt.offset);
#endif
		output_addrline(&bt, NULL, 0);	
	}
	printf("################################################\n");
    
	free(strings);
}

static void signal_exit(int dunno) 
{ 
	const char* signal_str = "";
	char dunno_str[10] = {0};
	sprintf(dunno_str, "%d", dunno);
	switch (dunno) 
	{
		case 1:
			signal_str = "SIGHUP(1)";
			break;
		case 2:
			signal_str = "SIGINT(2:CTRL_C)";
			break;
		case 3:
			signal_str = "SIGQUIT(3)";
			break;
		case 6:
		{
			signal_str = "SIGABRT(6)";
			dump_backtrace();
		}
		break;
		case 9:
			signal_str = "SIGKILL(9)";
			break;
		case 15:
			signal_str = "SIGTERM(15 KILL)";
			break;
		case 11:
		{
			signal_str = "SIGSEGV(11)";            
			dump_backtrace();
		}
		break;	
		default:
			signal_str = "OTHER";
			break;
	}
    printf("Error: signal_str:%s\n",signal_str);
	exit(0);
}

void debug_backtrace_init()  
{                              
    signal(SIGHUP,  signal_exit); 
	signal(SIGINT,  signal_exit); 
	signal(SIGQUIT, signal_exit);
	signal(SIGABRT, signal_exit);
	signal(SIGKILL, signal_exit);
	signal(SIGTERM, signal_exit);
	signal(SIGSEGV, signal_exit);
    dump_library_maps();
}

debug.h

/**********************************************************
说明:
    1.编译时用了 -O1/2/3优化的,backtrace信息将不准确,建议用-O0
    2.本模块基于backtrace,backtrace_symbols,line2addr实现
    3.动态库编译加 -g -rdynamic:
        gcc test.c -g -rdynamic -fPIC -shared -o libtest.so 
        或者先.o再.so
        g++ test.c -g -rdynamic -fPIC -shared -o libtest.o
        g++ libtest.o -shared -funwind-tables -rdynamic  -o libtest.so
    4.执行文件编译需要加 -g
    5.为了兼容c/c++,打印方式使用printf
使用:
    1.基于线程,模块init :debug_backtrace_init();工作于当前线程
      可以修改该函数(有些信号是测试的时候用的)
    2.指针校验:ASSERT(p) 当p为NULL时,调用dump_backtrace(),打印函数调用栈
    3.dump_backtrace()  打印函数调用栈
    4.检测段错误,数组越界等问题,发生时调自动用dump_backtrace()
***********************************************************/
#ifndef _DEBUG_BACKTRACE_
#define _DEBUG_BACKTRACE_

#ifdef __cplusplus
extern "C" {
#endif

/*本模块的调试开关*/
/*1 放开下面注释*/
//#define BT_MOD_DEBUG_ON
/*2 或者,编译时候加 -D BT_MOD_DEBUG_ON*/

#define MAX_LIB_NUM (20)
#define ASSERT(p)  do{if(p==0) {dump_backtrace();}}while(0)

/*模块init*/
extern void debug_backtrace_init();

extern void dump_backtrace(void);

#ifdef __cplusplus
}
#endif

#endif

说明与功能

这里运行的环境为虚拟机,如果是开发版,命令可能需要变一下.
arm-xxxxxx-gcc
arm-xxxxxxxxx-addr2line

见头文件
/**********************************************************
说明:
1.编译时用了 -O1/2/3优化的,backtrace信息将不准确,建议用-O0
2.本模块基于backtrace,backtrace_symbols,line2addr实现
3.动态库编译加 -g -rdynamic:
gcc test.c -g -rdynamic -fPIC -shared -o libtest.so
或者先.o再.so
g++ test.c -g -rdynamic -fPIC -shared -o libtest.o
g++ libtest.o -shared -funwind-tables -rdynamic -o libtest.so
4.执行文件编译需要加 -g
5.为了兼容c/c++,打印方式使用printf
使用:
1.基于线程,模块init :debug_backtrace_init();工作于当前线程
可以修改该函数(有些信号是测试的时候用的)
2.指针校验:ASSERT§ 当p为NULL时,调用dump_backtrace(),打印函数调用栈
3.dump_backtrace() 打印函数调用栈
4.检测段错误,数组越界等问题,发生时调自动用dump_backtrace()
***********************************************************/
核心命令:
地址信息=>函数名+代码行数

addr2line -Cif -e a.out 0x1234  

maps表信息,包括动态库内存表

/proc/self/maps     

你可能感兴趣的:(c,c++,c语言,开发语言)