需要已掌握:动态符号表、符号表相关内容
backtrace, backtrace_symbols, backtrace_symbols_fd - 用于支持应用程序自调试
提要
#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()
用于获取调用程序的堆栈。获取的堆栈信息存放在参数buffer
中,它是一个指针数组。堆栈表示程序对该函数的一连串的调用。buffer
指向的数组中的每一项都是void*
类型,存放的是来自相应堆栈帧的返回地址。size
参数指定了buffer
中最多可存放几个地址。backtrace()
函数的返回值是实际获取的指针个数,最大不超过size
大小。如果堆栈大小大于size
指定的值,则只能获取到最近调用该函数的部分堆栈帧地址;为了获得完整的堆栈,请确保buffer
和size
设置的足够大。
给定backtrace()
中buffer
返回的地址集,backtrace_symbols()
可以将地址转为用符号地址描述的字符串数组。size
参数指定了该字符串数组中包含的地址数(即backtrace()
的返回值)。每个符号地址中包含了函数名(如果可以确定的话),十六进制的函数偏移量和十六进制的实际返回地址。backtrace_symbols()
的返回值是一个指向字符串数组的指针,它的大小和buffer
相同。该数组的内存是backtrace_symbols()
分配的(调用malloc
),并且必须被调用者释放(free
)。(指针数组指向的字符串不需要也不应该被释放。)
backtrace_symbols()
返回的字符串都是malloc
出来的,但是不要最后一个一个的free
,因为backtrace_symbols()
是根据backtrace()
给出的堆栈层数,一次性的malloc
出来一块内存来存放结果字符串的。所以,像示例中的代码一样,只需要在最后,free
掉backtrace_symbols()
的返回指针就OK了。
backtrace_symbols_fd()
采用与backtrace_symbols()
相同的buffer
和size
参数,但它不向调用方返回字符串数组,而是将字符串(每行一个)写入文件描述符fd
。backtrace_symbols_fd()
不调用malloc
,因此可以在调用该函数可能发生失败的情况下使用,具体请参见注意。
返回值
backtrace()
返回buffer
中返回的地址数,该地址数<=size
指定的大小。如果返回值小于size
,则存储完整堆栈;如果它等于size,则它可能已被截断,在这种情况下,可能不会返回最开始堆栈帧的地址。
成功时,backtrace_symbols()
返回一个指向调用者所分配的数组的指针;失败时,返回NULL。
属性
Interface | Attribute | Value |
---|---|---|
backtrace() | Thread safety | MT-Safe |
backtrace_symbols() | Thread safety | MT-Safe |
backtrace_symbols_fd() | Thread safety | MT-Safe |
注意
这些函数对函数的返回地址如何存储在堆栈上进行了一些假设。注意以下几点:
- Omission of the frame pointers (as implied by any of gcc’s nonzero optimization levels) may cause these assumptions to be violated.(删除堆栈帧指针会导致无法正确解析堆栈内容)
- 内联函数没有堆栈帧。
- 尾部调用优化会导致一个堆栈帧替换另一个堆栈帧。
backtrace()
和backtrace_symbols_fd()
不显式调用malloc()
,但它们是libgcc
的一部分,libgcc
第一次使用时会动态加载。动态加载通常会触发对malloc
的调用。如果需要对这两个函数进行某些调用而不分配内存(例如,在信号处理程序中),则需要确保事先加载libgcc
。- 某些编译器的优化选项对获取正确的调用堆栈有干扰
例子
下面的程序演示了backtrace()
和backtrace_symbols_fd()
的使用。下面的shell会话显示了运行程序时可能看到的内容:
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BT_BUF_SIZE 100
void myfunc3(void)
{
int j, nptrs;
void *buffer[BT_BUF_SIZE];
char **strings;
nptrs = backtrace(buffer, BT_BUF_SIZE);
printf("backtrace() returned %d addresses\n", nptrs);
// 下方整个实现打印堆栈的代码可以用一行代码代替,即
// backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO);
strings = backtrace_symbols(buffer, nptrs);
if (strings == NULL)
{
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}
for (j = 0; j < nptrs; j++)
printf("%s\n", strings[j]);
free(strings);
}
// static 的作用是不导出符号,即在打印堆栈时不显示该函数名“myfunc2”
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);
}
编译并运行:
# 在使用GNU ld的系统中,为了支持函数名功能,需要传递-rdynamic标志给链接器
cc -rdynamic prog.c -o prog
运行结果
具体参见
man 3 backtrace