C++封装一个易用的打印backtrace信息的函数
Posted 彼方丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++封装一个易用的打印backtrace信息的函数相关的知识,希望对你有一定的参考价值。
C++封装一个易用的打印backtrace信息的函数
1、前言
当我们平时在调试程序时,在遇见某些错误时,往往需要打印出当前错误点的函数调用堆栈信息(通常是在调用
assert
函数之前调用),这样可以更快地找出程序的问题。接下来重点讲一下如何设计一个易用的打印backtrace的函数
2、几个需要用到函数
2.1、backtrace函数
函数原型如下
#include <execinfo.h>
int backtrace(void **buffer, int size);
-
功能描述:
backtrace
函数会将当前程序的调用堆栈信息写入buffer
所指向的数组。buffer中的每一项都是void *
类型的,是对应堆栈帧的返回地址。而size
参数指定可以存储在缓冲区中的最大地址数,如果回溯大于size
,则返回最近size
个调用堆栈信息,为了获得完整的回溯,我们必须确保缓冲区和大小足够大。
(需要注意的是返回信息是倒序的,最近的函数调用在最前面,最远的调用在返回信息的最后面) -
返回值:返回获取到的调用堆栈信息的数量,该值不大于size。如果返回值小于size,则表示获取到了存储调用堆栈信息;如果它等于size,那么它可能已经被截断了,在这种情况下,最早的堆栈帧的地址可能不会被返回了
2.2、backtrace_symbols函数
函数原型如下
#include <execinfo.h>
char **backtrace_symbols(void *const *buffer, int size);
- 功能描述:第一个参数是
backtrace
返回信息buffer
,backtrace_symbols
的功能将地址信息转换为一个字符串数组,用于描述堆栈信息。size参数指定缓冲区中地址的数量。 - 返回值:
backtrace_symbols
的返回值是一个指向字符串数组的指针,它的大小通buffer
相同,每个字符串包含了一个相对于buffer
中对应元素的可打印信息,它由函数名(如果可以确定)、函数的十六进制偏移量和实际返回地址(十六进制)组成。
(需要注意的是backtrace_symbols
的返回值是调用malloc
申请的内存空间,调用者必须手动释放它,但是指针数组所指向的字符串不需要也不应该被释放)
2.3、__cxa_demangle函数
函数原型如下
#include <cxxabi.h>
char* __cxa_demangle(const char *mangled_name, char *output_buffer, size_t *length, int *status)
- 功能描述:
__cxa_demangle
是C++的函数,而且是ABI(应用程序二进制接口),用于将已被编译器转换后的函数名给还原为原来的形式(编译的时候加上-rdunamic选项,加入函数符号表才能正确显示),即进行符号重组。第一个参数mangled_name
就是要进行符号重组的名称,后三个参数用处不大,可以不作理会 - 返回值:
__cxa_demangle
的返回值是一个字符串指针,内容就是函数的原始名称
(需要注意的是__cxa_demangle
的返回值是调用malloc
申请的内存空间,调用者必须手动释放它)
3、测试各个函数的使用
测试程序如下,比较简单,递归5次之后调用Backtrace
并结束递归
void fun()
{
static int count = 0;
if (++count == 5)
{
Backtrace(50, 0);
return;
}
fun();
}
int main(int argc, char* argv[])
{
fun();
return 0;
}
3.1、使用backtrace()获取到的堆栈信息
0x401c43
0x4024da
0x402568
0x402568
0x402568
0x402568
0x40259f
0x7f0b40312b35
0x401999
3.2、使用backtrace_symbols()转换后的堆栈信息
./all(_Z9BacktraceiiRKSs+0x58) [0x401c43]
./all(_Z3funv+0x48) [0x4024da]
./all(_Z3funv+0xd6) [0x402568]
./all(_Z3funv+0xd6) [0x402568]
./all(_Z3funv+0xd6) [0x402568]
./all(_Z3funv+0xd6) [0x402568]
./all(main+0x9) [0x40259f]
/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f0b40312b35]
./all() [0x401999]
从结果可以看到,括号里面的字母即为被编译器转换后的函数名,我们需要将它提取出来,然后传给__cxa_demangle
去生成真实的函数名
提取可以用如下代码实现,正则语法比较简单,大家不懂的可以自行百度查一下sscanf
的正则规则
std::string sstr;
sstr.resize(256);
sscanf(str, "%*[^(]%*[^_]%255[^)+]", &sstr[0]);
3.3、使用__cxa_demangle()获取原始的函数名
Backtrace(int, int, std::string const&)
fun()
fun()
fun()
fun()
fun()
./all(main+0x9)
/lib64/libc.so.6(__libc_start_main+0xf5)
./all()
从结果上看,函数调用了5次fun()
,符合预期
4、完整源代码展示
#include <iostream>
#include <vector>
#include <sstream>
#include <fstream>
#include <execinfo.h>
#include <assert.h>
#include <cxxabi.h>
#include <unistd.h>
std::string demangle(const char* str)
{
size_t size = 0;
int status = 0;
std::string sstr;
sstr.resize(256);
if (1 == sscanf(str, "%*[^(]%*[^_]%255[^)+]", &sstr[0]))
{
char* tmp = abi::__cxa_demangle(&sstr[0], nullptr, &size, &status);
if (tmp)
{
std::string result(tmp);
free(tmp);
return result;
}
}
if (1 == sscanf(str, "%255s", &sstr[0]))
return sstr;
return str;
}
/**
* brief: 获取当前栈信息的字符串
* param: size 栈的最大层数
* skip 跳过栈顶的层数
* prefix 栈信息前输出的内容
*/
std::string Backtrace(int size = 64, int skip = 2, const std::string& prefix = "")
{
std::vector<std::string> bt;
void** array = (void **)malloc(sizeof(void*) * size);
size_t s = backtrace(array, size);
char** str = backtrace_symbols(array, s);
std::stringstream ss;
if (str == nullptr)
{
std::cout << "backtrace_symbols fail" << std::endl;
goto good;
}
for (size_t i = skip; i < s; i++)
bt.push_back(demangle(str[i]));
free(str);
free(array);
for (auto& b : bt)
ss << prefix << b << std::endl;
good:
return ss.str();
}
void fun()
{
static int count = 0;
if (++count == 5)
{
std::cout << Backtrace(50, 0) << std::endl;
return;
}
fun();
}
int main(int argc, char* argv[])
{
fun();
return 0;
}
以上是关于C++封装一个易用的打印backtrace信息的函数的主要内容,如果未能解决你的问题,请参考以下文章