【C】使用backtrace获取堆栈信息

【C】使用backtrace获取堆栈信息1 backtrace 一些内存检测工具如 Valgrind 调试工具如 GDB 可以查看程序运行时函数调用的堆栈信息 有时候在分析程序时要获得堆栈信息 借助于 backtrace 是很有帮助的 其原型如下 includeexeci hintbacktrac voidbuffer intsize charbacktrac sy

1、backtrace

一些内存检测工具如Valgrind,调试工具如GDB,可以查看程序运行时函数调用的堆栈信息,有时候在分析程序时要获得堆栈信息,借助于backtrace是很有帮助的,其原型如下:

 #include <execinfo.h> int backtrace(void buffer, int size); char backtrace_symbols(void *const *buffer, int size); void backtrace_symbols_fd(void *const *buffer, int size, int fd);

头文件“execinfo.h”提供了三个相关的函数,简单的说,backtrace函数用于获取堆栈的地址信息, backtrace_symbols函数把堆栈地址翻译成我们易识别的字符串, backtrace_symbols_fd函数则把字符串堆栈信息输出到文件中。

backtrace:该函数用于获取当前线程的函数调用堆栈,获取的信息将存放在buffer中,buffer是一个二级指针,可以当作指针数组来用,数组中的元素类型是void*,即从堆栈中获取的返回地址,每一个堆栈框架stack frame有一个返回地址,参数 size 用来指定buffer中可以保存void* 元素的最大值,函数返回值是buffer中实际获取的void*指针个数,最大不超过参数size的大小。

backtrace_symbols:该函数把从backtrace函数获取的信息buffer转化为一个字符串数组char,每个字符串包含了相对于buffer中对应元素的可打印信息,包括函数名、函数的偏移地址和实际的返回地址,size指定了该数组中的元素个数,可以是backtrace函数的返回值,也可以小于这个值。需要注意的是,backtrace_symbols的返回值调用了malloc以分配存储空间,为了防止内存泄露,我们要手动调用free来释放这块内存。

backtrace_symbols_fd:该函数与backtrace_symbols 函数功能类似,不同的是,这个函数直接把结果输出到文件描述符为fd的文件中,且没有调用malloc。

在使用以上三个函数时,还需要注意一下几点:

(1)如果使用的是GCC编译链接的话,建议加上“-rdynamic”参数,这个参数的意思是告诉ELF连接器添加“-export-dynamic”标记,这样所有的符号信息symbols就会添加到动态符号表中,以便查看完整的堆栈信息。

(2)static函数不会导出符号信息symbols,在backtrace中无效。

(3)某些编译器的优化选项对获取正确的函数调用堆栈有干扰,内联函数没有堆栈框架,删除框架指针也会导致无法正确解析堆栈内容。

下面是一个简单的例子:

//backtrace_ex.cpp #include <stdio.h> #include <stdlib.h> #include <execinfo.h> void my_backtrace() { void *buffer[100] = { NULL }; char trace = NULL; int size = backtrace(buffer, 100); trace = backtrace_symbols(buffer, size); if (NULL == trace) { return; } for (int i = 0; i < size; ++i) { printf("%s\n", trace[i]); } free(trace); printf("----------done----------\n"); } void func2() { my_backtrace(); } void func() { func2(); } int main() { func(); return 0; }

编译执行上面的文件:

g++ backtrace_ex.cpp ./a.out ./a.out() [0x] ./a.out() [0x400baf] ./a.out() [0x400bba] ./a.out() [0x400bc5] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7f2473cf5ec5] ./a.out() [0x] ----------done----------

咦!堆栈信息虽然打出来了,但是函数调用栈并不是很明确,原因是少了“-rdynamic”参数,重新编译执行如下:

g++ -rdynamic backtrace_ex.cpp ./a.out ./a.out(_Z12my_backtracev+0x44) [0x400b11] ./a.out(_Z5func2v+0x9) [0x400eaf] ./a.out(_Z4funcv+0x9) [0x400eba] ./a.out(main+0x9) [0x400ec5] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7f006bdfbec5] ./a.out() [0x400a09] ----------done----------

加了“-rdynamic”参数后就很好了,我们可以看到函数名称,由于不同的平台、编译器有不同的编译规则,所以用backtrace解析出来的函数名形式是不同的,以“./a.out(_Z4funcv+0x9) [0x400eba]”为例说明,重点在于圆括号中的内容,“_Z”是个函数名开始标识符,后面的“4”表示函数名长度,接着便是真正的函数名“func”,后面的“v”表示函数参数类型为void,随后的“+0x9”是偏移地址。虽然有一定的编译规则,但可读性还不是很好,我们可以用下面介绍的方法demangle来解析这些符号。

2、demangle

demangle即符号重组,函数原型如下:

#include <cxxabi.h> char* __cxa_demangle(const char* __mangled_name, char* __output_buffer, size_t* __length, int* __status);

cxxabi.h是一个C++函数运行时库,要用g++编译链接,gcc会有问题。__mangled_name即原符号信息,是个字符串,以空字符结尾,__output_buffer用来保存符号重组后的信息,长度为__length,__status表示demangle结果,为0时表示成功,返回值指向符号重组后的字符串首地址,字符串以空字符结尾。

我们使用demangle来改进上面的例子:(把my_backtrace替换为my_backtrace2)

void my_backtrace2() { void *buffer[100] = { NULL }; char trace = NULL; int size = backtrace(buffer, 100); trace = backtrace_symbols(buffer, size); if (NULL == trace) { return; } size_t name_size = 100; char *name = (char*)malloc(name_size); for (int i = 0; i < size; ++i) { char *begin_name = 0; char *begin_offset = 0; char *end_offset = 0; for (char *p = trace[i]; *p; ++p) { // 利用了符号信息的格式 if (*p == '(') { // 左括号 begin_name = p; } else if (*p == '+' && begin_name) { // 地址偏移符号 begin_offset = p; } else if (*p == ')' && begin_offset) { // 右括号 end_offset = p; break; } } if (begin_name && begin_offset && end_offset ) { *begin_name++ = '\0'; *begin_offset++ = '\0'; *end_offset = '\0'; int status = -4; // 0 -1 -2 -3 char *ret = abi::__cxa_demangle(begin_name, name, &name_size, &status); if (0 == status) { name = ret; printf("%s:%s+%s\n", trace[i], name, begin_offset); } else { printf("%s:%s()+%s\n", trace[i], begin_name, begin_offset); } } else { printf("%s\n", trace[i]); } } free(name); free(trace); printf("----------done----------\n"); }

结果如下:

g++ -rdynamic backtrace_ex.cpp ./a.out ./a.out:my_backtrace2()+0x44 ./a.out:func2()+0x9 ./a.out:func()+0x9 ./a.out:main()+0x9 /lib/x86_64-linux-gnu/libc.so.6:__libc_start_main()+0xf5 ./a.out() [0x400a09] ----------done----------

可以看出来,demangle后函数名已清晰地显示出来了,没有那些奇奇怪怪的符号了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/231073.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • python集合_python集合交集

    python集合_python集合交集一、集合1.1:集合的特性集合是无序的,集合中的元素是唯一的,集合一般用于元组或者列表中的元素去重1.2:集合的格式格式1:变量名=set(元素,元素)格式2:变量名={元素,元素…}注意:下面写法为一个空字典,为空默认是字典,如果有数据在根据格式判断为字典还是集合name={}1.3:添加元素方式一:add案例:nums={11,24,45,96,28}nums.add(42)print(nums)#{96,42,

    2025年6月26日
    7
  • Java 注解与反射

    Java 注解与反射

    2021年10月7日
    41
  • 【windows屏幕扩展】把你多余屏幕利用起来,spacedesk屏幕扩展超低延迟解决方案[通俗易懂]

    【windows屏幕扩展】把你多余屏幕利用起来,spacedesk屏幕扩展超低延迟解决方案[通俗易懂]目录扫盲扫盲spacedesk是一款基于TCP/IP协议的屏幕扩展工具,通过这款工具你可以把自己身边的闲置的平板手机或者笔记本利用起来,扩展你的屏幕。只要你的两台设备处于同一个网络环境下(只要互相能够ping通),你就可以实现屏幕扩展(卡不卡我就不知道了)。用过win10中的wifi扩展屏幕的同学都知道,扩展的屏幕显示质量和网络环境成正比。而win10的屏幕扩展很玄学,…

    2022年8月13日
    9
  • Mac PyCharm 打不开处理

    Mac PyCharm 打不开处理1 第一步 先输入 cd Applications PyCharm app Contents MacOS2 第二步 查看无法打开 pycharm 的原因 需要输入 c pycharm3 第三步 下面就是展示的分析日志 其中许多小伙伴都找不到网上说的这个地址 cd Users 用户名 Library Preferences PyCharm2019 1 其实地址不是这个 而是 Users liuxiaoming Library ApplicationS JetBrain

    2025年6月28日
    8
  • vue实现上传文件[通俗易懂]

    vue实现上传文件[通俗易懂]Vue实现上传文件

    2022年8月16日
    10
  • c++迭代器iterator遍历map_iterator迭代器原理

    c++迭代器iterator遍历map_iterator迭代器原理什么是迭代器迭代器是一种可以遍历容器元素的数据类型。迭代器是一个变量,相当于容器和操纵容器的算法之间的中介。C++更趋向于使用迭代器而不是数组下标操作,因为标准库为每一种标准容器(如vector、map和list等)定义了一种迭代器类型,而只有少数容器(如vector)支持数组下标操作访问容器元素。可以通过迭代器指向你想访问容器的元素地址,通过*x打印出元素值。这和我们所熟知的指针极其类似。C语言有指针,指针用起来十分灵活高效。C++语言有迭代器,迭代器相对于指针而言功能更为丰富。vector,是数

    2025年7月1日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号