C++封装一个易用的打印backtrace信息的函数

1、前言

当我们平时在调试程序时,在遇见某些错误时,往往需要打印出当前错误点的函数调用堆栈信息(通常是在调用assert函数之前调用),这样可以更快地找出程序的问题。接下来重点讲一下如何设计一个易用的打印backtrace的函数

2、几个需要用到函数

2.1、backtrace函数

函数原型如下

#include <execinfo.h>

int backtrace(void buffer, int size);

  • 1
  • 2
  • 3
  • 功能描述: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);

  • 1
  • 2
  • 3
  • 功能描述:第一个参数是backtrace返回信息bufferbacktrace_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)

  • 1
  • 2
  • 3
  • 功能描述:__cxa_demangle是C++的函数,而且是ABI(应用程序二进制接口),用于将已被编译器转换后的函数名给还原为原来的形式(编译的时候加上-rdunamic选项,加入函数符号表才能正确显示),即进行符号重组。第一个参数mangled_name就是要进行符号重组的名称,后三个参数用处不大,可以不作理会
  • 返回值:__cxa_demangle的返回值是一个字符串指针,内容就是函数的原始名称
    (需要注意的是__cxa_demangle的返回值是调用malloc申请的内存空间,调用者必须手动释放它)

3、测试各个函数的使用

测试程序如下,比较简单,递归5次之后调用Backtrace并结束递归

void fun()
{
    static int count = 0;
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">++</span>count <span class="token operator">==</span> <span class="token number">5</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
    <span class="token function">Backtrace</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">fun</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

}

int main(int argc, char* argv[])
{
fun();
return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.1、使用backtrace()获取到的堆栈信息

0x401c43
0x4024da
0x402568
0x402568
0x402568
0x402568
0x40259f
0x7f0b40312b35
0x401999

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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]

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

从结果可以看到,括号里面的字母即为被编译器转换后的函数名,我们需要将它提取出来,然后传给__cxa_demangle去生成真实的函数名
提取可以用如下代码实现,正则语法比较简单,大家不懂的可以自行百度查一下sscanf的正则规则

std::string sstr;
sstr.resize(256);
sscanf(str, "%*[^(]%*[^_]%255[^)+]", &sstr[0]);

 
 
  • 1
  • 2
  • 3

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()

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

从结果上看,函数调用了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);

<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">==</span> <span class="token function">sscanf</span><span class="token punctuation">(</span>str<span class="token punctuation">,</span> <span class="token string">"%*[^(]%*[^_]%255[^)+]"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>sstr<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
    <span class="token keyword">char</span><span class="token operator">*</span> tmp <span class="token operator">=</span> abi<span class="token operator">::</span><span class="token function">__cxa_demangle</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>sstr<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token keyword">nullptr</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>size<span class="token punctuation">,</span> <span class="token operator">&amp;</span>status<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>tmp<span class="token punctuation">)</span>
    <span class="token punctuation">{<!-- --></span>
        std<span class="token operator">::</span>string <span class="token function">result</span><span class="token punctuation">(</span>tmp<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">free</span><span class="token punctuation">(</span>tmp<span class="token punctuation">)</span><span class="token punctuation">;</span> 
        <span class="token keyword">return</span> result<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">==</span> <span class="token function">sscanf</span><span class="token punctuation">(</span>str<span class="token punctuation">,</span> <span class="token string">"%255s"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>sstr<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token keyword">return</span> sstr<span class="token punctuation">;</span>

<span class="token keyword">return</span> str<span class="token punctuation">;</span>

}

/**

  • 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;

<span class="token keyword">if</span> <span class="token punctuation">(</span>str <span class="token operator">==</span> <span class="token keyword">nullptr</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
    std<span class="token operator">::</span>cout <span class="token operator">&lt;&lt;</span> <span class="token string">"backtrace_symbols fail"</span> <span class="token operator">&lt;&lt;</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span>
    <span class="token keyword">goto</span> good<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span>size_t i <span class="token operator">=</span> skip<span class="token punctuation">;</span> i <span class="token operator">&lt;</span> s<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    bt<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span><span class="token function">demangle</span><span class="token punctuation">(</span>str<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">free</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">free</span><span class="token punctuation">(</span>array<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">auto</span><span class="token operator">&amp;</span> b <span class="token operator">:</span> bt<span class="token punctuation">)</span>
    ss <span class="token operator">&lt;&lt;</span> prefix <span class="token operator">&lt;&lt;</span> b <span class="token operator">&lt;&lt;</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span>

good:
return ss.str();
}

void fun()
{
static int count = 0;

<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">++</span>count <span class="token operator">==</span> <span class="token number">5</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
    std<span class="token operator">::</span>cout <span class="token operator">&lt;&lt;</span> <span class="token function">Backtrace</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span>
    <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">fun</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

}

int main(int argc, char* argv[])
{
fun();

<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值