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