美文网首页
打印函数调用堆栈

打印函数调用堆栈

作者: jiangling500 | 来源:发表于2019-05-04 16:28 被阅读0次

相关函数签名

#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);
  • 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
    注意:
  • 编译时加上-rdynamic,这样所有的符号信息symbols就会添加到动态符号表中,以便查看完整的堆栈信息。
  • static函数不会导出符号信息symbols,在backtrace中无效。

使用示例

#define BT_BUF_SIZE 100

void printStackTrace(void)
{
    int i, nptrs;
    void *buffer[BT_BUF_SIZE];
    char **strings;

    nptrs = backtrace(buffer, BT_BUF_SIZE);
    printf("backtrace() returned %d addresses\n", nptrs);

    /* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
       would produce similar output to the following: */
    // backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO);

    strings = backtrace_symbols(buffer, nptrs);
    if (strings == NULL)
    {
        perror("backtrace_symbols");
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < nptrs; i++)
    {
        printf("%s\n", strings[j]);
    }

    free(strings);
}

在C++中打印函数调用堆栈

由于C++编译器会对函数名进行mangle,使用backtrace()函数解析出来的函数名可读性较差,可使用demangle来提高可读性。

string stackTrace(bool demangle)
{
  string stack;
  const int max_frames = 200;
  void* frame[max_frames];
  int nptrs = ::backtrace(frame, max_frames);
  char** strings = ::backtrace_symbols(frame, nptrs);
  if (strings)
  {
    size_t len = 256;
    char* demangled = demangle ? static_cast<char*>(::malloc(len)) : nullptr;
    for (int i = 1; i < nptrs; ++i)  // skipping the 0-th, which is this function
    {
      if (demangle)
      {
        // https://panthema.net/2008/0901-stacktrace-demangled/
        // bin/exception_test(_ZN3Bar4testEv+0x79) [0x401909]
        char* left_par = nullptr;
        char* plus = nullptr;
        for (char* p = strings[i]; *p; ++p)
        {
          if (*p == '(')
            left_par = p;
          else if (*p == '+')
            plus = p;
        }

        if (left_par && plus)
        {
          *plus = '\0';
          int status = 0;
          char* ret = abi::__cxa_demangle(left_par+1, demangled, &len, &status);
          *plus = '+';
          if (status == 0)
          {
            demangled = ret;  // ret could be realloc()
            stack.append(strings[i], left_par+1);
            stack.append(demangled);
            stack.append(plus);
            stack.push_back('\n');
            continue;
          }
        }
      }
      // Fallback to mangled names
      stack.append(strings[i]);
      stack.push_back('\n');
    }
    free(demangled);
    free(strings);
  }
  return stack;
}

在段错误等致命信号的处理函数中打印函数调用堆栈

static void sigsegvHandler(int sig, siginfo_t *info, void *secret)
{
    void *trace[100];
    char **messages = NULL;
    int i, trace_size = 0;
    struct sigaction act;

    trace_size = backtrace(trace, 100);
    messages = backtrace_symbols(trace, trace_size);

    for (i=1; i<trace_size; ++i)
    {
    fprintf(stderr,"%s", messages[i]);
    }

    /* Make sure we exit with the right signal at the end. So for instance
     * the core will be dumped if enabled. */
    sigemptyset (&act.sa_mask);
    act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND;
    act.sa_handler = SIG_DFL;
    sigaction (sig, &act, NULL);
    kill(getpid(),sig);
}

void setupSignalHandlers(void)
{
    struct sigaction act;

    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND | SA_SIGINFO;
    act.sa_sigaction = sigsegvHandler;
    sigaction(SIGSEGV, &act, NULL); // 段错误信号
    sigaction(SIGBUS, &act, NULL); // 总线错误信号
    sigaction(SIGFPE, &act, NULL); // 除零异常信号
    sigaction(SIGILL, &act, NULL); // 非法程序映像信号

    return;
}

在try-catch语句块中打印函数调用堆栈

class Bar
{
 public:
  void test(std::vector<std::string> names = {})
  {
    printf("Stack:\n%s\n", muduo::CurrentThread::stackTrace(true).c_str());
    throw muduo::Exception("oops");
  }
};

void foo()
{
  Bar b;
  b.test();
}

int main()
{
  try
  {
    foo();
  }
  catch (const muduo::Exception& ex)
  {
    printf("reason: %s\n", ex.what());
    printf("stack trace:\n%s\n", ex.stackTrace());
  }
}

参考

相关文章

网友评论

      本文标题:打印函数调用堆栈

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