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

打印函数调用堆栈

作者: 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