一般察看函数运行时堆栈的方法是使用 GDB(bt命令) 之类的外部调试器, 但是, 有些时候为了分析程序的 BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的.
#1.1
#include
/* Store up to SIZE return address of the current program state in
ARRAY and return the exact number of values stored. */
int backtrace(void **array, int size);
/* Return names of functions from the backtrace list in ARRAY in a newly
malloc()ed memory block. */
char **backtrace_symbols(void *const *array, int size);
/* This function is similar to backtrace_symbols() but it writes the
result immediately to a file. */
void backtrace_symbols_fd(void *const *array, int size, int fd);
使用它们的时候有一下几点需要我们注意的地方:
参数:
获取的信息将会被存放在 buffer 中,它是一个指针列表.
参数 size 用来指定 buffer 中可以保存多少个 void* 元素.
函数返回值:
实际获取的指针个数, 最大不超过 size大小.
在 buffer 中的指针实际是从堆栈中获取的返回地址, 每一个堆栈框架有一个返回地址
注意:某些编译器的优化选项对获取正确的调用堆栈有干扰,另外内联函数没有堆栈框架;删除框架指针也会导致无法正确解析堆栈内容
参数:
buffer 应该是从 backtrace 函数获取的指针数组
size 是该数组中的元素个数(backtrace 的返回值)
函数返回值:
一个指向字符串数组的指针, 它的大小同 buffer 相同.
每个字符串包含了一个相对于buffer中对应元素的可打印信息.
它包括函数名,函数的偏移地址,和实际的返回地址
现在, 只有使用ELF二进制格式的程序才能获取函数名称和偏移地址. 在其他系统,只有16进制的返回地址能被获取.
另外,你可能需要传递相应的符号给链接器,以能支持函数名功能
(比如,在使用GNU ld链接器的系统中,你需要传递(-rdynamic), -rdynamic可用来通知链接器将所有符号添加到动态符号表中,如果你的链接器支持-rdynamic的话,建议将其加上!)
该函数的返回值是通过malloc函数申请的空间,因此调用者必须使用free函数来释放指针.
注意 : 如果不能为字符串获取足够的空间函数的返回值将会为NULL
#2 示例
##2.1 简单用例(glibc 提供)
下面是 glibc 中的实例:
// http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
#include
#include
#include
/* Obtain a backtrace and print it to @code{stdout}. */
void print_trace (void)
{
void * array[10];
size_t size;
char ** strings;
size_t i;
size = backtrace(array, 10);
strings = backtrace_symbols (array, size);
if (NULL == strings)
{
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}
printf ("Obtained %zd stack frames.\n", size);
for (i = 0; i < size; i++)
printf ("%s\n", strings[i]);
free (strings);
strings = NULL;
}
/* A dummy function to make the backtrace more interesting. */
void dummy_function (void)
{
print_trace();
}
int main (int argc, char *argv[])
{
dummy_function();
return 0;
}
输出如下:
gcc -c example.c -o example.o -rdynamic -g
gcc example.o -o example -rdynamic -g
#./example
Obtained 5 stack frames.
./example(print_trace+0x19) [0x400916]
./example(dummy_function+0x9) [0x4009bb]
./example(main+0x14) [0x4009d1]
/lib64/libc.so.6(__libc_start_main+0xf5) [0x7fb5e7f49445]
./example() [0x400839]
##2.2 简单使用(man手册)
//http://man7.org/linux/man-pages/man3/backtrace.3.html
#include
#include
#include
#include
void
myfunc3(void)
{
int j, nptrs;
#define SIZE 100
void *buffer[100];
char **strings;
nptrs = backtrace(buffer, 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 (j = 0; j < nptrs; j++)
printf("%s\n", strings[j]);
free(strings);
}
static void /* "static" means don't export the symbol... */
myfunc2(void)
{
myfunc3();
}
void
myfunc(int ncalls)
{
if (ncalls > 1)
myfunc(ncalls - 1);
else
myfunc2();
}
编译运行程序
gcc -c prog.c -o prog.o -rdynamic -g
gcc prog.o -o prog -rdynamic -g
#./prog 3
backtrace() returned 8 addresses
./prog(myfunc3+0x1f) [0x4009cc]
./prog() [0x400a61]
./prog(myfunc+0x25) [0x400a88]
./prog(myfunc+0x1e) [0x400a81]
./prog(myfunc+0x1e) [0x400a81]
./prog(main+0x59) [0x400ae3]
/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f1d1b1f1445]
./prog() [0x4008e9]
##2.3 段错误时自动触发 call trace
我们还可以利用这 backtrace 来定位段错误位置.
通常情况系, 程序发生段错误时系统会发送 SIGSEGV 信号给程序, 缺省处理是退出函数.
我们可以使用 signal(SIGSEGV, &your_function); 函数来接管 SIGSEGV 信号的处理,
程序在发生段错误后, 自动调用我们准备好的函数, 从而在那个函数里来获取当前函数调用栈.
#include
#include
#include
#include
#include
/* Obtain a backtrace and print it to stdout. */
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
void dump_stack(void)
{
void *array[30] = { 0 };
size_t size = backtrace(array, ARRAY_SIZE(array));
char **strings = backtrace_symbols(array, size);
size_t i;
if (strings == NULL)
{
perror("backtrace_symbols.");
exit(EXIT_FAILURE);
}
printf("Obtained %zd stack frames.\n", size);
for (i = 0; i < size; i++)
printf("%s\n", strings[i]);
free(strings);
strings = NULL;
exit(EXIT_SUCCESS);
}
void sighandler_dump_stack(int sig)
{
psignal(sig, "handler");
dump_stack();
signal(sig, SIG_DFL);
raise(sig);
}
void func_c()
{
*((volatile int *)0x0) = 0x9999; /* ERROR */
}
void func_b()
{
func_c();
}
void func_a()
{
func_b();
}
int main(int argc, const char *argv[])
{
if (signal(SIGSEGV, sighandler_dump_stack) == SIG_ERR)
perror("can't catch SIGSEGV");
func_a();
return 0;
}
编译该程序
cc -c handler.c -o handler.o -rdynamic
cc handler.o -o handler -rdynamic
接着运行.
#./handler
handler: Segmentation fault
Obtained 9 stack frames.
./handler(dump_stack+0x39) [0x400aa6]
./handler(sighandler_dump_stack+0x1f) [0x400b6c]
/lib64/libc.so.6(+0x362f0) [0x7f0bc00f72f0]
./handler(func_c+0x9) [0x400b90]
./handler(func_b+0xe) [0x400ba6]
./handler(func_a+0xe) [0x400bb6]
./handler(main+0x38) [0x400bf0]
/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f0bc00e3445]
./handler() [0x4009a9]
可以看出, 真正出异常的函数位置在 ./handler(func_c+0x9) [0x400b90].
我们可以看下这个位置位于哪里:
使用 addr2line
addr2line -C -f -e ./handler 0x400b90
对应错误的行号.
使用 objdump
使用 objdump 将函数的指令信息 dump 出来.
其中 -D 参数表示显示所有汇编代码, -S 表示将对应的源码也显示出来
最后用 grep 显示地址 0x400b90 处前后 6 行的信息
objdump -DS ./handler | grep -6 "400b90"
参考代码:
a user-space simulated dump_stack(), based on mips.
kernel perf source dump_stack
#3 更低层次的函数
只有使用 glibc 2.1 或更新版本, 可以使用 backtrace() 函数, 参看
因此 GCC 提供了两个内置函数用来在运行时取得函数调用栈中的返回地址和框架地址
void *__builtin_return_address(int level);
得到当前函数层次为 level 的返回地址, 即此函数被别的函数调用, 然后此函数执行完毕后, 返回, 所谓返回地址就是调用的时候的地址(其实是调用位置的下一条指令的地址).
void* __builtin_frame_address (unsigned int level);
得到当前函数的栈帧的地址.
#include
#include
#include
#include
#include
#include
#include
void showBacktrace()
{
void * ret = __builtin_return_address(1);
printf("ret address [%p]\n", ret);
void * caller = __builtin_frame_address(0);
printf("call address [%p]\n", caller);
#ifdef __cplusplus
Dl_info dlinfo;
void *ip = ret;
if(!dladdr(ip, &dlinfo)) {
perror("addr not found\n");
return;
}
const char *symname = dlinfo.dli_sname;
int f = 0;
fprintf(stderr, "% 2d: %p %s+%u (%s)\n",
++f,
ip,
symname, 0,
// (unsigned)(ip - dlinfo.dli_saddr),
dlinfo.dli_fname);
#endif
}
int MyFunc_A()
{
showBacktrace();
return 10;
}
int MyFunc_B()
{
return MyFunc_A();
}
int main()
{
MyFunc_B();
return 0;
}
#4 参考资料
Stack backtrace 的实现
backtrace.c:Code Content
一个glibc中abort不能backtrace的问题
在Linux中如何利用backtrace信息解决问题
内核中dump_stack()的实现,并在用户态模拟dump_stack()