美文网首页
C++获得程序的调用栈的几种方法

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

作者: DayDayUpppppp | 来源:发表于2022-04-24 14:39 被阅读0次
    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
    
    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工具箱。

    相关文章

      网友评论

          本文标题:C++获得程序的调用栈的几种方法

          本文链接:https://www.haomeiwen.com/subject/fjetyrtx.html