C++获得程序的调用栈的几种方法

打印程序的调用栈是一种常见的debug工具,最常用的就是backtrace函数去获得堆栈信息。不过,这个函数的调用成本很高,是容易使用不当,造成性能问题。在这里整理几种获得程序调用栈的方法。

1. 使用__builtin_return_address

gcc提供了一个内置的函数,可以打印出一个函数的调用堆栈,__builtin_return_address(level)打印出一个函数的堆栈地址。

其中 level代表是堆栈中第几层调用地址,__builtin_return_address(0)表示第一层调用地址,即当前函数,__builtin_return_address(1)表示第二层。

#include <stdio.h>
  
void func()
{
    printf("level 0 addr %p \n", __builtin_return_address(0));
    printf("level 1 addr %p \n", __builtin_return_address(1));
}

void foo()
{
    func();
}
int main()
{
    foo();
    return 0;
}

写一个测试程序运行一下看看结果

g++ test_build_in_return_address.c 

./a.out                           
level 0 addr 0x401166 
level 1 addr 0x401172

对于打印出来的地址,可以使用addr2line查看到对应的文件:

addr2line -e a.out -f 401166
_Z3foov
??:?

addr2line -e a.out -f 401172
main
??:?

addr2line可以看到对应的符号名,但是对应的文件名和函数名就看不到。看了一下原因,应该是编译的时候,没有带上调试信息。带上-g重新编译一次,效果就正常了。

g++ test_build_in_return_address.c -g

addr2line -e a.out -f 401166         
_Z3foov
/xxx/test_build_in_return_address.c:12

addr2line -e a.out -f 401172
main
/xxx/test_build_in_return_address.c:16
2. 使用backtrace_symbols

当然,常见的获得函数调用栈的方法是backtrace函数,比起backtrace函数,__builtin_return_address的性能要好太多。

#include <execinfo.h>

#define BACKTRACE_SIZ   64
void do_backtrace()
{
    void    *array[BACKTRACE_SIZ];
    size_t   size, i;
    char   **strings;

    size = backtrace(array, BACKTRACE_SIZ);
    strings = backtrace_symbols(array, size);

    for (i = 0; i < size; i++) {
        printf("%p : %s\n", array[i], strings[i]);
    }

    free(strings);  // malloced by backtrace_symbols
}

// gcc -g -rdynamic -o backtrace ./backtrace.c

具体性能对比:

// 10w次调用 使用__builtin_return_address
time ./a.out                      
./a.out  0.00s user 0.00s system 95% cpu 0.005 total

// 10w次调用 使用backtrace_symbols
time ./a.out                      
./a.out  11.28s user 0.00s system 99% cpu 11.291 total

backtrace也可以获得根据函数的地址获得它的名称
比如有些场景不需要全部的bt,只需知道调用的函数的名称就好。

#include <stdio.h>
#include <execinfo.h>

void this_is_func(void) {
    printf("this_is_func\n");
}

int main(int argc, char *argv[]) 
{
    void    *funptr = &this_is_func;
    backtrace_symbols_fd(&funptr, 1, 1);
    return 0;
}

// gcc test.c -rdynamic

./a.out
./a.out(this_is_func+0x0)[0x401136]
3. 使用unwind库

除了上面介绍的两种打印bt的方法,libunwind库也提供了相关的方法。看文档unwind使用了栈指针遍历的方式去获得bt(性能也许会更好?文档里面也没说 不过文档里面说这种方式可以打印出调用的每层函数里面的寄存器值)

#include <libunwind.h>

void do_backtrace2()
{
    unw_cursor_t    cursor;
    unw_context_t   context;

    unw_getcontext(&context);
    unw_init_local(&cursor, &context);

    while (unw_step(&cursor) > 0) {
        unw_word_t  offset, pc;
        char        fname[64];

        unw_get_reg(&cursor, UNW_REG_IP, &pc);

        fname[0] = '\0';
        (void) unw_get_proc_name(&cursor, fname, sizeof(fname), &offset);

        printf ("%p : (%s+0x%x) [%p]\n", pc, fname, offset, pc);
    }
}
输出:
0x80486b3 : (foo+0xb) [0x80486b3]
0x80486ca : (main+0x15) [0x80486ca]
0x016379d : (__libc_start_main+0xed) [0x16379d]
0x80484c9 : (_start+0x21) [0x80484c9]

小结

__builtin_return_address()是一个比较轻量的方法去获得调用的函数栈,性能比backtrace_symbols好太多(backtrace_symbols这个函数啊,如果调用的频次稍高一些,很容易cpu100%),这个函数可以加入debug工具箱。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容