Skip to content
Published at:

函数回调栈跟踪

上周在移植dbus,在设备上运行不起来,当然问题后面被我领导解决了;有处地方报错,在源代码里面找到了,当时我在想代码是怎么调到这里来的?怎么去跟踪函数栈的调用?类似,Java在抛出异常之后回打印出,是怎么一步步调到这里来的。写C的时候在其它地方也遇到过:GDB的backtrace命令;Valgrind跟踪内存问题的时候也会打印函数调用栈;还有AddressSanitizer检测内存问题也能打印出来。当时去Google搜索了下,记录下

两种方式:

  • GUN拓展函数:backtracebacktrace_symbolsbacktrace_symbols_fd
  • GCC编译选项:-finstrument-functions

GUN拓展函数

文档:https://man7.org/linux/man-pages/man3/backtrace.3.html

API:

c
#include <execinfo.h>

int backtrace(void **buffer, int size);

char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);

backtrace_symbols_fdbacktrace_symbols的区别是,backtrace_symbols_fd把信息输出对应的fd,而backtrace_symbols把信息返回给返回值

示例:

代码就不自己写了,用的文档中的示例

c
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BT_BUF_SIZE 100

void myfunc3(void) {
    int    nptrs;
    void*  buffer[BT_BUF_SIZE];
    char** strings;

    nptrs = backtrace(buffer, BT_BUF_SIZE);
    printf("backtrace() returned %d addresses\n", nptrs);

    /* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
       would produce similar output to the following: */

    strings = backtrace_symbols(buffer, nptrs);
    if (strings == NULL) {
        perror("backtrace_symbols");
        exit(EXIT_FAILURE);
    }

    for (int j = 0; j < nptrs; j++) {
        printf("%s\n", strings[j]);
    }

    free(strings);
}

/* "static" means don't export the symbol... */
static void myfunc2(void) {
    myfunc3();
}

void myfunc(int ncalls) {
    if (ncalls > 1)
        myfunc(ncalls - 1);
    else
        myfunc2();
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        fprintf(stderr, "%s num-calls\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    myfunc(atoi(argv[1]));
    exit(EXIT_SUCCESS);
}

输出:

bash
[I] ➜ cc -rdynamic main.c && ./a.out 10
backtrace() returned 14 addresses
0   a.out                               0x0000000100436d3a myfunc3 + 42
1   a.out                               0x0000000100436e59 myfunc2 + 9
2   a.out                               0x0000000100436e4a myfunc + 42
3   a.out                               0x0000000100436e40 myfunc + 32
4   a.out                               0x0000000100436e40 myfunc + 32
5   a.out                               0x0000000100436e40 myfunc + 32
6   a.out                               0x0000000100436e40 myfunc + 32
7   a.out                               0x0000000100436e40 myfunc + 32
8   a.out                               0x0000000100436e40 myfunc + 32
9   a.out                               0x0000000100436e40 myfunc + 32
10  a.out                               0x0000000100436e40 myfunc + 32
11  a.out                               0x0000000100436e40 myfunc + 32
12  a.out                               0x0000000100436ebd main + 93
13  dyld                                0x0000000100a354fe start + 462

用的clang前端编译器,LLVM牛批,输出格式都比GCC的好看多了

有几个值的注意的地方:

  • Inline函数没有栈帧信息,无法打印
  • static修饰的函数不会导出symbol,无法打印
  • 另外一个是如果程序开启了优化级别,也会受到一定程度的影响

然后我就一顿操作,打印,就解决了前面提到的问题;然后看了一下午dbus的代码,然后,然后,发现dbus里面已经写好了工具函数_dbus_print_backtrace()[dbus-sysdeps-unix.c:3417],直接调用就可以了,我tm。然后,然后默默的copy、paste到自己项目里面来。像这种十的多的年的老项目,里面还是有很多值得学习的地方,后面有时间在讲讲dbus源码

GCC编译选项

// todo

http://gcc.gnu.org/onlinedocs/gcc-4.4.7/gcc/Code-Gen-Options.html

Updated at: