C/C++: printing stacktrace containing file name, function name, and line numbers using libbacktrace

2 minute read

I needed a library for printing stack traces when developing tan. More specifically, the compiler and the runtime had to print the function names, source file names, and the line numbers when an assertion failed or an error was raised.

Initial Solution

The library was initially implemented using libunwind. The code was based on this awesome blog post

But the problem was, it couldn’t show line numbers. The closest thing libunwind could provide was the program counter, like this:

main.cpp (main+0x14)

To achieve the goal, we need to somehow read the debug information stored in the binary file and use it to calculate to convert the program counter into the line numbers in the source file.

As suggested by some folks, I can use addr2line or gdb to do that. However, I didn’t want to invoke an external program since they couldn’t be integrated into the tan runtime.

Just as I was about to copy the source code of addr2line and wrap it into a library, I came across this SO answer.

libbacktrace FTW

The SO answer said that libbacktrace could provide the functionalities I needed. After some digging, I decided to use it and made a simple wrapper around it.

The final API is very simple

void init_back_trace(const char *filename);
void print_back_trace();

init_back_trace should be used at the beginning of the program. It is responsible for loading the debug information of a binary (use argv[0] for the program itself) and it is quite an expensive routine.

print_back_trace prints the stack trace starting from the current frame to the furthest frame.

The documentation of the APIs is well written, so I’m not copying and pasting them here.

For anyone that just wants to copy and paste my code (note that this code only works on linux and has only been tested using gcc and clang):

#include <cxxabi.h>
#include <cstdio>
#include <cstdlib>
#include <backtrace.h>

extern "C" void init_back_trace(const char *filename);
extern "C" void print_back_trace();

void *__bt_state = nullptr;

int bt_callback(void *, uintptr_t, const char *filename, int lineno, const char *function) {
  /// demangle function name
  const char *func_name = function;
  int status;
  char *demangled = abi::__cxa_demangle(function, nullptr, nullptr, &status);
  if (status == 0) {
    func_name = demangled;
  }

  /// print
  printf("%s:%d in function %s\n", filename, lineno, func_name);
  return 0;
}

void bt_error_callback(void *, const char *msg, int errnum) {
  printf("Error %d occurred when getting the stacktrace: %s", errnum, msg);
}

void bt_error_callback_create(void *, const char *msg, int errnum) {
  printf("Error %d occurred when initializing the stacktrace: %s", errnum, msg);
}

void init_back_trace(const char *filename) {
  __bt_state = backtrace_create_state(filename, 0, bt_error_callback_create, nullptr);
}

void print_back_trace() {
  if (!__bt_state) { /// make sure init_back_trace() is called
    printf("Make sure init_back_trace() is called before calling print_stack_trace()\n");
    abort();
  }
  backtrace_full((backtrace_state *) __bt_state, 0, bt_callback, bt_error_callback, nullptr);
}

Updated: