backtrace 接口详细说明
我们经常会有需求来获取调用的堆栈,来排查一些问题,或者实现一些需求。本文来介绍 backtrace()
和 backtrace_symbols()
函数的使用。
1 |
|
backtrace()
获取函数调用堆栈数据,数据放在 buffer 中,参数 size 用来指定 buffer 中可以保存多少个void*
元素(每个栈帧的地址)。如果回溯的函数调用个数大于 size,则只有 size 个函数调用地址被返回。因此保证一个适当的 buffer 和 size 的大小返回通过 buffer 返回的地址个数,这个数目小于等于 size。
backtrace_symbols()
参数 buffer 是从backtrace()
函数获取的数组指针,size 是该数组中的元素个数(backtrace()
函数的返回值)。该函数的主要功能是:将从
backtrace()
函数获取的地址转为描述这些地址的字符串数组。每个地址的字符串信息包含对应函数的名字、在函数内的十六进制偏移地址、以及实际的返回地址(十六进制)。需要注意的是,当前只有使用 ELF 二进制文件格式的程序才能获取函数名称和偏移地址,此外,为支持函数名功能,可能需要添加相应的编译选项如:
-rdynamic
;否则只有十六进制的返回能被获取。该函数返回值是一个字符串指针,是通过 malloc 函数申请的空间,需要调用者将其释放。
注意:如果不能为字符串获取足够的空间,该函数的返回值为 NULL
backtrace_symbols_fd()
函数与backtrace_symbols()
函数具有相同的功能,不同的是他不会给调用者返回字符串数组,而是将结果写入文件描述符为 fd 的文件中,每个函数对应一行,他不会调用 malloc 函数。
注意事项:
这些函数对函数返回地址如何保存在栈中有一些假设,注意如下:
- 忽略栈帧指针(由 gcc 的非零优化级别处理)可能引起这些假设的混乱
- 内联函数没有栈帧
- Tail-call(尾调用)优化会导致栈帧被其他调用覆盖
- 为支持函数名功能,可能需要添加相应的编译链接选项如
-rdynamic
;否则,只有十六进制的返回地址能被获取 - “static” 函数名是不会导出的,也不会出现在函数调用列表中,即使指定了
-rdynamic
链接选项
如下用代码举个例子:
1 |
|
编译:g++ xxx.cpp -rdynamic -g -O0 -o main
运行如下:
1 | Current function calls list is: |
多说一点:
在产生 Segmentation Fault
错误时,一般会产生一个 SIGSEGV 信号。利用这个机制,上述问题传统的做法是,在程序中安装SIGSEGV信号,然后在该信号处理函数中,回溯函数调用列表,从而分析定位错误,一劳永逸。
当然产生的 core 文件也可以使用,但是对于大型程序,出现 Segmentation Fault 错误时,其分析定位,比较棘手。