输出 函数调用栈

1.简介

在计算机科学中,函数调用栈是用来存储计算机程序中当前被调用函数的相关信息的一种数据结构,该数据结构以栈的形式被组织的。具体相关信息可以参考wiki上的介绍。

2.函数说明

这篇博客主要讲述如何使用API输出函数调用栈。

#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); 

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

#include <dlfcn.h>
int dladdr(void *addr, Dl_info *info);

int backtrace(void **buffer, int size)
该函数回溯程序的函数调用,并将值存储在buffer中,buffer中的每一个元素都是相应栈帧的返回地址, size指定buffer最多能够存储元素的个数。
如果回溯的结果大于size,那么buffer将会存储最近调用的size个栈帧;
否则,存储全部栈帧。

在获得这些地址之后,我们需要把它们转换成可读的形式,调用如下两个函数:

char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd); 

第一个函数将地址转换成可读的字符串,并将字符串写入到有malloc分配的内存空间中,并返回给调用函数。
第二个函数和第一个功能一样,不过将字符串写入由fd指定的文件中。
因此在调用第一个函数时,需要显示的释放(调用free)其返回值指向的内存。

返回值:
backtrace返回buffer中地址的个数。
backtrace_symbols如果成功返回一个由malloc分配的char*指针;否则返回NULL;

对于C程序来说,编译器不会对程序的名字进行修改;
但是C++存在mangle,所以我么还需要调用abi的函数进行demangle操作。
函数声明如下:

char* __cxa_demangle(const char* __mangled_name, char* __output_buffer, size_t* __length, int* __status);

其中,
__mangled_name是被mangle之后名字的字符,
__output_buffer是输出的结果,
__length是__output_buffer的大小,
__status指示该函数执行的结果。

3.代码实现

/**
 * date: 2016.1.17
 * author: zhouxiangxiang
 */
#include <execinfo.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <cxxabi.h>

#ifndef __DUMPSTACK_ZH__
#define __DUMPSTACK_ZH__

#define BACKTRACE_SIZE  100

/**
 * gcc/clang
 */

char* dumpCxxFun(char* pname, char* buffer, size_t* len, int* status){
    if ( (0 == strcmp(pname, "main")) ||
         (0 == strcmp(pname, "__libc_start_main")) ) {
        return pname;
    }

    char* ret = abi::__cxa_demangle(pname, buffer, len, status);
    if (0 == *status) {
        return ret;
    }
    else if (-1 == *status) {
        printf("memory allocation failure:");
        return NULL;    
    }
    else if (-2 == *status) {
        printf("invalid mangled name:");
        return NULL;    
    }
    else if (-3 == *status) {
        printf("invalid arguments: ");
        return NULL;    
    }
}

void parseFuncName(char* str) {
    char buffer[BACKTRACE_SIZE];
    size_t len = BACKTRACE_SIZE;
    int status = 0;
    char *ret = NULL;


    char* ptr_begin  = NULL;
    char* ptr_offset = NULL;
    char* ptr_offend = NULL;
    for (char* ptr = str; *ptr; ++ptr) {
        if ('(' == *ptr) {
        ptr_begin = ptr;
    }
        else if ('+' == *ptr) {
            ptr_offset = ptr;
    }
        else if (')' == *ptr && ptr_begin < ptr_offset) {
            ptr_offend = ptr;
    }
    }

    if (ptr_begin && 
        ptr_offset && 
    ptr_offend && 
    ptr_begin < ptr_offset) {

        *ptr_begin++  = '\0';
        *ptr_offset++ = '\0';
        *ptr_offend++ = '\0';

        ret = dumpCxxFun(ptr_begin, buffer, &len, &status);
        if (NULL != ret) {
            printf("\t%s : %s \n", ret, ptr_offset); // name, address
        }
        else {
            printf("\t%s : %s \n", ptr_begin, ptr_offset); // name, address
        }
    }
    else {
            printf("\t%s \n", str); // name, address
    }
}


void dumpStack() {
    printf("[backtrace] \n");
    int i = 0;
    int btNum = 0;
    void *btArray[BACKTRACE_SIZE];
    char** str = NULL;
    btNum = backtrace(btArray, BACKTRACE_SIZE);
    str = backtrace_symbols(btArray, BACKTRACE_SIZE);
    if (NULL == str) {
        perror("backtrace_symbols fialed");
    exit(EXIT_FAILURE);
    }

   for (i = 0; i < btNum; ++i) {

#ifdef __cplusplus
      parseFuncName(str[i]); // cxx
#else 
      printf("%s \n", str[i]); 
#endif
   }
   free(str);
}
#endif // 

4. 实例

#include "./dumpStack.h"

/**
 * gcc/clang
 */
void fun(int n) {
  if (n > 1) {
    fun(n - 1);
  }
  else {
    dumpStack();
  }
}

int main(int argc, char **argv) {
  if (argc != 2) {
    perror("usage: command num");
    exit(EXIT_SUCCESS);
  }

  fun(atoi(argv[1]));
  return 0;
}

编译:
g++ b.cpp -rdynamic
执行:
./a.out 2

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值