How to print call stack in C/C++ more beautifully?

11,239

Disclaimer: The following is mostly for Linux using GCC or Clang with libstdc++, you might need different ways on other systems.

The most important thing is to add -rdynamic to the command line when linking. I don't know if this is required on all systems, but for me, this actually turned all those addresses into symbols.

Now that you get some information, you probably want to demangle the symbols. You start with a stand-alone function to demangle any symbol:

// 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;
    }
}

And now for the real thing. I don't know if the format of the output from backtrace_symbols is specified, but the following works quite well for me:

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;
  }
}

(I had to adapt my code slightly since I'm not using std::cout, so there might be some little quirks there - check the code if it works for you and if not and you need help fixing it, let me know).

Share:
11,239

Related videos on Youtube

user180574
Author by

user180574

Updated on September 15, 2022

Comments

  • user180574
    user180574 over 1 year

    I would like to print call stack with more information like in gdb.

    This is what I have so far.

    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]);
        }
    }
    

    When it runs, I get something as below.

    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]
    

    It is hard to read. With function names, it would be much better. Thanks a lot for the tip.

  • Jongware
    Jongware over 10 years
    You answered while I was reading the required material -- First Hit On Google :) "The symbol names may be unavailable without the use of special linker options. For systems using the GNU linker, it is necessary to use the -rdynamic linker option. Note that names of "static" functions are not exposed, and won't be available in the backtrace."
  • Daniel Frey
    Daniel Frey over 10 years
    @Jongware You might be interested in the updated answer then.
  • Manoj
    Manoj over 5 years
    This is great. It works for me. I was wondering if there is a way to print the line number also along with it?
  • Daniel Frey
    Daniel Frey over 5 years
    @Manoj Sadly, I was never able to figure out a reasonable way to get line numbers for the frames.