zoukankan      html  css  js  c++  java
  • C语言函数调用

    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 ", trace[i]);
    }
    free(trace);
    printf("----------done---------- ");
    }

    void func2()
    {
    my_backtrace();

    }

    void func()
    {
    func2();
    }

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

    编译执行上面的文件:

    g++ backtrace_ex.cpp
    ./a.out
    ./a.out() [0x400811]
    ./a.out() [0x400baf]
    ./a.out() [0x400bba]
    ./a.out() [0x400bc5]
    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7f2473cf5ec5]
    ./a.out() [0x400709]
    ----------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++ = '';
    *begin_offset++ = '';
    *end_offset = '';
    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 ", trace[i], name, begin_offset);
    }
    else {
    printf("%s:%s()+%s ", trace[i], begin_name, begin_offset);
    }
    }
    else {
    printf("%s ", trace[i]);
    }
    }
    free(name);
    free(trace);
    printf("----------done---------- ");
    }

    结果如下:

    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后函数名已清晰地显示出来了,没有那些奇奇怪怪的符号了。

  • 相关阅读:
    【HTML5 绘图与动画】使用canvas
    【H5新增元素和文档结构】新的全局属性 1. contentEditable 可编辑内容 2. contextmenu 快捷菜单 3. data 自定义属性 4. draggable 可拖动 5. dropzone 拖动数据 6. hidden 隐藏 7. spellcheck 语法检查 8. translate 可翻译
    【H5新增元素和文档结构】完善旧元素 1. a 超链接 2. ol 有序列表 3. dl 定义列表 4. cite 引用文本 5. small 小号字体 6. iframe 浮动框架 7. script 脚本
    【H5新增元素和文档结构】新的语义信息 1. address 2. time 3. figure 跟 figcaption 4. details 和 summary 5. mark 6. progress 7. meter 8. dialog 9.bdi 10. wbr 11. ruby、rt、rp 12. command
    【H5新增元素跟文档结构】新的文档结构 1. article 文章块 2. section 区块 3. nav 导航条 4. aside 辅助栏 5. main 主要区域 6. header 标题栏 7. hgroup 标题组 8. footer 页脚栏
    5_PHP数组_3_数组处理函数及其应用_9_数组集合运算函数
    【华为云技术分享】鲲鹏弹性云服务器GCC交叉编译环境搭建指南
    【华为云技术分享】7 分钟全面了解位运算
    【华为云技术分享】Linux内核编程环境 (1)
    【华为云技术分享】华为云MySQL 8.0正式商用,全新增强版开源利器强势来袭
  • 原文地址:https://www.cnblogs.com/wangshuyi/p/12066955.html
Copyright © 2011-2022 走看看