博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

c++运用backtrace追踪函数调用的堆栈

Posted on 2012-08-17 14:38  开源云  阅读(9987)  评论(0编辑  收藏  举报

一般察看函数运行时堆栈的方法是使用GDB之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的。

在头文件"execinfo.h"中声明了三个函数用于获取当前线程的函数调用堆栈

Function: int backtrace(void **buffer,int size)

该函数用与获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针列表。参数 size 用来指定buffer中可以保存多少个void* 元素。函数返回值是实际获取的指针个数,最大不超过size大小在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址,注意某些编译器的优化选项对获取正确的调用堆栈有干扰,另外内联函数没有堆栈框架;删除框架指针也会使无法正确解析堆栈内容。

Function: char ** backtrace_symbols (void *const *buffer, int size)

backtrace_symbols
将从backtrace函数获取的信息转化为一个字符串数组. 参数buffer应该是从backtrace函数获取的数组指针,size是该数组中的元素个数(backtrace的返回值)。函数返回值是一个指向字符串数组的指针,它的大小同buffer相同.每个字符串包含了一个相对于buffer中对应元素的可打印信息.它包括函数名,函数的偏移地址,和实际的返回地址。
现在,只有使用ELF二进制格式的程序和苦衷才能获取函数名称和偏移地址.在其他系统,只有16进制的返回地址能被获取.另外,你可能需要传递相应的标志给链接器,以能支持函数名功能(比如,在使用GNU ld的系统中,你需要传递(-rdynamic))。
该函数的返回值是通过malloc函数申请的空间,因此调用这必须使用free函数来释放指针
.
注意:如果不能为字符串获取足够的空间函数的返回值将会为
NULL

Function:void backtrace_symbols_fd (void *const *buffer, int size, int fd)

backtrace_symbols_fd与backtrace_symbols 函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行.它不需要调用malloc函数,因此适用于有可能调用该函数会失败的情况。

 

下面是一个使用backtrace捕获异常并打印函数调用堆栈的例子:

 

#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)
{
    void *array[10];
    size_t size;
    size = backtrace(array, 10);
#ifdef PRINT_DEBUG
    char **strings;
    int i;
    strings = backtrace_symbols(array, size);
    printf("Obtained %d stack frames.\n", size);
    for (i = 0; i < size; i++)
        printf("%s\n", strings[i]);
    free(strings);

    char cmd[64] = "addr2line -C -f -e ";
    char* prog = cmd + strlen(cmd);
    readlink("/proc/self/exe", prog, sizeof(cmd) - strlen(cmd) - 1);// 获取进程的完整路径

    FILE* fp = popen(cmd, "w");
    if (fp != NULL)
    {
        for (i = 0; i < size; ++i)
        {
            fprintf(fp, "%p\n", array[i]);
        }
        pclose(fp);
    }
#else
    int fd = open("err.log", O_CREAT | O_WRONLY);
    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_handler = print_reason;
    sigemptyset(&myAction.sa_mask);
    myAction.sa_flags = SA_RESTART | SA_SIGINFO;
    sigaction(SIGSEGV, &myAction, NULL); // 无效内存引用
    sigaction(SIGABRT, &myAction, NULL); // 异常终止
    test1();
}

 

 

我本机测试打印出的信息如下:

Obtained 7 stack frames.
/root/workspace/test/Debug/test(__gxx_personality_v0+0x12d) [0x80486c1]
[0x71b440]
/root/workspace/test/Debug/test(__gxx_personality_v0+0x2ac) [0x8048840]
/root/workspace/test/Debug/test(__gxx_personality_v0+0x2c0) [0x8048854]
/root/workspace/test/Debug/test(__gxx_personality_v0+0x339) [0x80488cd]
/lib/libc.so.6(__libc_start_main+0xdc) [0xbf3e9c]
/root/workspace/test/Debug/test(__gxx_personality_v0+0x5d) [0x80485f1]
print_reason
/root/workspace/test/Debug/../main.cpp:15
??
??:0
die()
/root/workspace/test/Debug/../main.cpp:51
test1()
/root/workspace/test/Debug/../main.cpp:56
main
/root/workspace/test/Debug/../main.cpp:65
??
??:0
_start
??:0