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

1 打印原始栈信息

核心函数 :backtrace() 和 backtrace_symbols()
头文件:#include <execinfo.h>
例子:

    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]);
	}

2 原栈信息的解析 => 文件+行号+函数名

核心命令:addr2line
其实很简单,先得到原栈信息,然后
addr2line -Cif -e [可执行文件或者.so文件名] [地址,如果是.so文件要计算相对地址]
比如这样:addr2line -Cif -e ./a 0x400d27

下面是一个例子,我开了.h里面的调试开关的.
在这里插入图片描述
先看最下面的,对 ./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项的.

3 工具

如果每次都手动解析,那也太麻烦了.所以我写了一个自动解析工具(代码在后面).
怎么使用:step1:#include"debug.h" step2: debug_backtrace_init();
例子:
main.c

#include<stdio.h>
#include"debug.h"  //step1

/*操作空指针*/
int test_null_ptr()
{
    int *p = NULL;
    *p = 1;
    return 0;
}

int to_do()
{
    int a = 100;
    a++;
    test_null_ptr();
    return a;
}

int main()
{
    char c[10];
    debug_backtrace_init();//step2
    to_do();
    gets(c);
    return 0;
}
编译运行:要加 -g
gcc main.c debug.c -g -o a
./a

结果:
[dump_library_maps][159]debug_backtrace_init success,num_exe:7

##################backtrace###################
./a() [0x40145b] signal_exit() /workbench/jlh/1/debug.c:242
/lib/x86_64-linux-gnu/libc.so.6(+0x360b0) [0x7f2445d130b0] ??() ??:0
./a() [0x400a74] test_null_ptr() /workbench/jlh/1/main.c:8
./a() [0x400a9e] to_do() /workbench/jlh/1/main.c:17
./a() [0x400ace] main() /workbench/jlh/1/main.c:25
################################################
Error: signal_str:SIGSEGV(11)

4 代码

debug.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <execinfo.h>
#include <signal.h>
#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     

效果

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

在这里插入图片描述

高版本linux,稍微调整一下

注释debug.c 的46,48行:

    //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);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值