【问题标题】:How to print call stack in C/C++ more beautifully?如何更漂亮地打印 C/C++ 中的调用堆栈?
【发布时间】:2013-10-11 23:40:00
【问题描述】:

我想在 gdb 中打印带有更多信息的调用堆栈。

这是我目前所拥有的。

void to_print_callstack()
{
    void *buffer[100];
    int n = backtrace(buffer,10);
    char **str = backtrace_symbols(buffer, n);

    for (int i = 0; i < n; i++)
    {
        printf("%d:  %s\n", i, str[i]);
    }
}

当它运行时,我得到如下内容。

0:  ./test-prog() [0x4466bf]
1:  ./test-prog() [0x445e1d]
2:  ./test-prog() [0x443fd5]
3:  ./test-prog() [0x439a99]
4:  ./test-prog() [0x43302f]
5:  ./test-prog() [0x4322c9]
6:  ./test-prog() [0x4320cd]
7:  ./test-prog() [0x43e76b]
8:  /lib/libc.so.6(__libc_start_main+0xfd) [0x7fc4de7d8c4d]
9:  ./test-prog() [0x431569]

很难阅读。使用函数名称会好得多。 非常感谢您的提示。

【问题讨论】:

    标签: c++ c callstack


    【解决方案1】:

    免责声明:以下主要适用于使用 GCC 或 Clang 和 libstdc++ 的 Linux,在其他系统上您可能需要不同的方式。

    最重要的是在链接时在命令行中添加-rdynamic。我不知道是否所有系统都需要这样做,但对我来说,这实际上将所有这些地址都变成了符号。

    现在您已经获得了一些信息,您可能想要解开符号。你从一个独立的函数开始对任何符号进行解码:

    // you most likely need these headers (plus stuff for std::cout, ...)
    #include <cxxabi.h>
    #include <execinfo.h>
    
    std::string demangle( const char* const symbol )
    {
        const std::unique_ptr< char, decltype( &std::free ) > demangled(
          abi::__cxa_demangle( symbol, 0, 0, 0 ), &std::free );
        if( demangled ) {
            return demangled.get();
        }
        else {
            return symbol;
        }
    }
    

    现在是真正的东西。我不知道是否指定了backtrace_symbols 的输出格式,但以下内容对我来说效果很好:

    void backtrace()
    {
      // TODO: replace hardcoded limit?                                                      
      void* addresses[ 256 ];
      const int n = ::backtrace( addresses, std::extent< decltype( addresses ) >::value );
      const std::unique_ptr< char*, decltype( &std::free ) > symbols(
        ::backtrace_symbols( addresses, n ), &std::free );
      for( int i = 0; i < n; ++i ) {
        // we parse the symbols retrieved from backtrace_symbols() to                                                                                                                
        // extract the "real" symbols that represent the mangled names.                                                                                                              
        char* const symbol = symbols.get()[ i ];
        char* end = symbol;
        while( *end ) {
          ++end;
        }
        // scanning is done backwards, since the module name
        // might contain both '+' or '(' characters.
        while( end != symbol && *end != '+' ) {
          --end;
        }
        char* begin = end;
        while( begin != symbol && *begin != '(' ) {
          --begin;
        }
    
        if( begin != symbol ) {
          std::cout << std::string( symbol, ++begin - symbol );
          *end++ = '\0';
          std::cout << demangle( begin ) << '+' << end;
        }
        else {
          std::cout << symbol;
        }
        std::cout << std::endl;
      }
    }
    

    (由于我没有使用std::cout,因此我不得不稍微调整我的代码,所以那里可能有一些小怪癖 - 检查代码是否适合您,如果不适合,您需要帮助修复它,让我知道)。

    【讨论】:

    • 您在我阅读所需材料时回答了 -- First Hit On Google :) “如果不使用特殊的链接器选项,符号名称可能不可用。对于使用 GNU 链接器的系统,有必要使用 -rdynamic 链接器选项。请注意,“静态”函数的名称不会公开,并且不会在回溯中可用。”
    • @Jongware 您可能对更新后的答案感兴趣。
    • 这很棒。这个对我有用。我想知道是否有办法同时打印行号?
    • @Manoj 遗憾的是,我一直无法找到一种合理的方法来获取帧的行号。
    猜你喜欢
    • 2013-04-15
    • 1970-01-01
    • 1970-01-01
    • 2010-09-13
    • 2011-05-27
    • 2013-01-20
    • 2011-05-25
    • 1970-01-01
    • 2014-06-04
    相关资源
    最近更新 更多