背景

一般察看函数运行时堆栈的方法是使用GDB(backtrace)之类的外部调试器,但是,有些时候为了分析程序的BUG(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的。或者是在查看某个函数的被调用堆栈信息且不容易用GDB跟踪的时候,我们可以借助glibc提供了3个函数用于获取程序的堆栈信息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <execinfo.h>
/*
 * 功能描述: 获取当前堆栈地址
 * 参数说明: buffer - 函数地址数组(传出)
 *          size   - 数组的最大长度
 * 返 回 值:实际调用的层数
 */

int backtrace(void **buffer, int size);
/*
 * 功能描述: 将地址转换成符号字符串数组
 * 参数说明: buffer - 函数地址数组
 *          size   - 有效地址的个数
 * 返 回 值: 符号字符串数组指针(需要调用者释放)
 */
char **backtrace_symbols(void *const *buffer, int size);

/*
 * 功能描述: 将地址转换成符号字符串数组并写入指定文件
 * 参数说明: buffer - 函数地址数组
 *          size   - 有效地址的个数
 *          fd     - 文件描述符
 * 返 回 值: 无
 */
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
$ cat backtrace.c
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<string.h>
#include<execinfo.h>

//内存访问异常回调函数
void SegvHandle(int signum) {
    int maxlen = 1024;     // 堆栈最大值
    void *func[maxlen];
    char **funs;
    // 获取堆栈调用层数
    size_t size = backtrace(func, maxlen);
    // 获取符号
    funs = (char **)backtrace_symbols(func, size);
    // 打印输入相关信息
    fprintf(stderr, "Stack trace:\r\n");
    for (size_t i = 0; i < size; i++) {
        fprintf(stderr, "%lu %s \r\n", i, funs[i]);
    }
    //释放动态申请的内存
    free(funs);
}

void Segment() {
    char *p = NULL;
    // 制造内存访问异常
    *p = 'A';
}

void Func() {
    Segment();
}

int main() {
    // 无效内存异常信号捕获
    signal(SIGSEGV, SegvHandle);
    Func();
    return 0;
}

# 编译
g++ -ggdb3 -Wall backtrace.cpp -o backtrace -rdynamic
PS
    -rdynamic
        Pass the flag -export-dynamic to the ELF linker, on targets that
        support it. This instructs the linker to add all symbols, not only
        used ones, to the dynamic symbol table. This option is needed for
        some uses of "dlopen" or to allow obtaining backtraces from within
        a program.

# 运行
$ ./backtrace
Stack trace:
0 ./backtrace(_Z10SegvHandlei+0x95) [0x4009d2]
1 /lib64/libc.so.6(+0x363b0) [0x7f3c024653b0]
2 ./backtrace(_Z7Segmentv+0x10) [0x400a7e]
3 ./backtrace(_Z4Funcv+0x9) [0x400a8c]
4 ./backtrace(main+0x18) [0x400aa6]
5 /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f3c02451505]
6 ./backtrace() [0x400879]

# 借助 addr2line 和 c++filt 进一步处理
addr2line -e backtrace 0x4009d2 -f | xargs c++filt
SegvHandle(int)
/home/parallels/study/backtrace/backtrace.cpp:13

注意,使用glibcbacktrace追踪so库的调用信息后不能使用addr2line获取到行号(函数是可以的),若想获取到行号信息需要使用下面介绍的工具。

1
2
3
4
#/lib64/libc.so.6(+0x363b0) [0x7f3c024653b0]
$ backtrace addr2line -e /lib64/libc.so.6 0x7f3c024653b0 -f
??
??:0

libunwind

libunwind是一个unwind库,所谓unwind库主要是用于获取程序的调用栈和异常处理和跳转需要,常用的unwind库根据Assembling a Complete Toolchain有:

  • libunwind (llvm)LLVM内置的unwind库,主要为了不依赖GNU的实现。
  • libgcc_s (GNU)GCC内置的unwind库,不需要其他的外部unwind库。
  • libunwind (nongnu.org)gperftools依赖这个库。

使用libunwind需要查看机器是否有相应的头文件,否则需要自己安装,示例程序如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
$ cat trace.h
#ifndef TRACE_H_
#define TRACE_H_

#include <stdio.h>
void DebugInfo(FILE *out, const void *ip);
void __attribute__((noinline)) PrintStackTrace(FILE *out, int skip);
void FuncC(void);
void FuncB(void);
void FuncA(void);
#endif


$ cat trace.cpp
#define UNW_LOCAL_ONLY
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <libunwind.h>
#include <elfutils/libdwfl.h>

static void DebugInfo(FILE *out, const void *ip) {
    char *debuginfo_path = NULL;
    Dwfl_Callbacks callbacks;
    callbacks.find_elf = dwfl_linux_proc_find_elf;
    callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
    callbacks.debuginfo_path = &debuginfo_path;
    Dwfl *dwfl = dwfl_begin(&callbacks);
    assert(dwfl != NULL);

    assert(dwfl_linux_proc_report(dwfl, getpid()) == 0);
    assert(dwfl_report_end (dwfl, NULL, NULL) == 0);

    Dwarf_Addr addr = (uintptr_t)ip;
    Dwfl_Module *module = dwfl_addrmodule(dwfl, addr);
    const char *function_name = dwfl_module_addrname(module, addr);
    // 调用c++file命令还原函数名称
    char cmd[1024];
    snprintf(cmd, sizeof(cmd), "c++filt %s", function_name);
    FILE *fp = popen(cmd, "r");
    char result[1024];
    fgets(result, sizeof(result), fp);
    result[strlen(result) - 1] = '\0';
    fprintf(out, "%s(", result);
    pclose(fp);

    Dwfl_Line *line = dwfl_getsrc(dwfl, addr);
    if (line != NULL) {
        int nline;
        Dwarf_Addr addr;
        const char *filename = dwfl_lineinfo(line, &addr, &nline, NULL, NULL, NULL);
        fprintf(out, "%s:%d", strrchr(filename, '/') + 1, nline);
    } else {
        fprintf(out, "%p", ip);
    }
}

static void __attribute__((noinline)) PrintStackTrace(FILE *out, int skip) {
    unw_context_t uc;
    unw_getcontext(&uc);
    unw_cursor_t cursor;
    unw_init_local(&cursor, &uc);

    while (unw_step(&cursor) > 0) {
        unw_word_t ip;
        unw_get_reg(&cursor, UNW_REG_IP, &ip);
        unw_word_t offset;
        char name[32];
        assert(unw_get_proc_name(&cursor, name, sizeof(name), &offset) == 0);

        if (skip <= 0) {
            fprintf(out, "\tat ");
            DebugInfo(out, (void*)(ip - 4));
            fprintf(out, ")\n");
        }

        if (strcmp(name, "main") == 0) {
            break;
        }
        skip--;
    }
}

void FuncC(void) {
    PrintStackTrace(stdout, 0);
}

void FuncB(void) {
    FuncC();
}

void FuncA(void) {
    FuncB();
}


$ cat main.cpp
#include "trace.h"

int main() {
    FuncA();

    return 0;
}


$ cat makefile
CC = g++
CXXFLAGS = -ggdb3 -Wall
INCLUDE = -I./
LIB = -L./ -ltrace
TARGET = test_trace
all : $(TARGET)

$(TARGET) : main.o libtrace.so
	$(CC) $(INCLUDE) main.o -o $(TARGET) $(LIB)
main.o : main.cpp
	$(CC) $(CXXFLAGS) main.cpp -c
libtrace.so: trace.cpp trace.h
	$(CC) -fPIC -shared $(INCLUDE) $(CXXFLAGS) trace.cpp -o libtrace.so -ldw -lunwind

clean :
	rm -f *.o
	rm -f libtrace.so
	rm -f $(TARGET)


# 编译
$ make
g++ -ggdb3 -Wall main.cpp -c
g++ -fPIC -shared -I./ -ggdb3 -Wall trace.cpp -o libtrace.so -ldw -lunwind
g++ -I./ main.o -o test_trace -L./ -ltrace


# 运行
./test_trace
	at FuncC()(trace.cpp:76)
	at FuncB()(trace.cpp:80)
	at FuncA()(trace.cpp:84)
	at main(main.cpp:4)

TODO libbacktrace

The libbacktrace library may be linked into a program or library and used to produce symbolic backtraces. Sample uses would be to print a detailed backtrace when an error occurs or to gather detailed profiling information.

参考资料

http://oroct.com/20190629/155.html

https://juejin.im/post/5d1f8ca76fb9a07ef562568a

https://github.com/gcc-mirror/gcc/tree/master/libbacktrace