美文网首页
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++获得程序的调用栈的几种方法

    1. 使用__builtin_return_address gcc提供了一个内置的函数,可以打印出一个函数的调用堆...

  • 递归

    关于方法的递归调用1、方法自身调用自身2、有结束条件该程序执行过程种只有压栈动作,没有任何出栈动作,最终会导致内存...

  • 2.垃圾收集与内存分配策略

    1.哪些内存需要回收 程序计数器,虚拟机栈,本地方法栈随线程而生随线程而灭。栈中栈帧随着方法的调用与执行完毕而入栈...

  • JVM判断对象已经死的方式你知道吗?

    在Java中程序计数器、虚拟机栈、本地方法栈这三个区域随线程而生,随线程而灭:栈中的栈帧随着方法的调用和退出而有条...

  • Dalvik虚拟机异常处理

    方法调用栈 java虚拟机用方法调用栈来跟踪一系列的方法调用过程,该堆栈保存了每个调用方法的本地信息,比如方法的局...

  • C++子线程调用Java方法

    1. C++ 全局调用Java方法 之前讨论过,如何C++主线程中调用 Java 函数C++主线程调用Java方法...

  • 六、本地方法栈

    Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。 本地方法栈,也是线程私有的。 允...

  • JVM学习:本地方法栈

    一、概述 Java虚拟机栈于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。本地方法栈,也是线程私有的...

  • C++主线程调用Java方法

    1. C++ 全局调用Java方法 1.1 C++主线程调用Java方法 在 Android C++多线程-创建子...

  • JNI--c/c++调用Java方法

    上次说到c/c++调用Java的变量,同样的c/c++也可以调用Java的方法 1.c/c++native方法调用...

网友评论

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

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