Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

print call stack in C or C++

People also ask

Does C have a call stack?

What is Call Stack in C? Call stack is a dynamic data structure maintained inside the RAM memory by the Operating System. Primary task of Function Call Stack in C is to manage the function calls and how they pass parameters to each other. A call stack is maintained for each task and for each thread.

What is a call stack in C++?

A call stack is a stack data structure that stores information about the active functions. The call stack is also known as an execution stack, control stack, function stack, or run-time stack.

What is stack trace in C?

The stack trace shows where the error occurs, namely in the c function. It also shows that the c function was called by b , which was called by a , which was in turn called by the code on line 15 (the last line) of the program.

Is call stack and stack same?

In computer science, a call stack is a stack data structure that stores information about the active subroutines of a computer program. This kind of stack is also known as an execution stack, program stack, control stack, run-time stack, or machine stack, and is often shortened to just "the stack".


For a linux-only solution you can use backtrace(3) that simply returns an array of void * (in fact each of these point to the return address from the corresponding stack frame). To translate these to something of use, there's backtrace_symbols(3).

Pay attention to the notes section in backtrace(3):

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.


Boost stacktrace

Documented at: https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack

This is the most convenient option I've seen so far, because it:

  • can actually print out the line numbers.

    It just makes calls to addr2line however, which is ugly and might be slow if your are taking too many traces.

  • demangles by default

  • Boost is header only, so no need to modify your build system most likely

boost_stacktrace.cpp

#include <iostream>

#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>

void my_func_2(void) {
    std::cout << boost::stacktrace::stacktrace() << std::endl;
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main(int argc, char **argv) {
    long long unsigned int n;
    if (argc > 1) {
        n = strtoul(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    for (long long unsigned int i = 0; i < n; ++i) {
        my_func_1(1);   // line 28
        my_func_1(2.0); // line 29
    }
}

Unfortunately, it seems to be a more recent addition, and the package libboost-stacktrace-dev is not present in Ubuntu 16.04, only 18.04:

sudo apt-get install libboost-stacktrace-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o boost_stacktrace.out -std=c++11 \
  -Wall -Wextra -pedantic-errors boost_stacktrace.cpp -ldl
./boost_stacktrace.out

We have to add -ldl at the end or else compilation fails.

Output:

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
 1# my_func_1(int) at /home/ciro/test/boost_stacktrace.cpp:18
 2# main at /home/ciro/test/boost_stacktrace.cpp:29 (discriminator 2)
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./boost_stacktrace.out

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
 1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:13
 2# main at /home/ciro/test/boost_stacktrace.cpp:27 (discriminator 2)
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./boost_stacktrace.out

The output and is further explained on the "glibc backtrace" section below, which is analogous.

Note how my_func_1(int) and my_func_1(float), which are mangled due to function overload, were nicely demangled for us.

Note that the first int calls is off by one line (28 instead of 27 and the second one is off by two lines (27 instead of 29). It was suggested in the comments that this is because the following instruction address is being considered, which makes 27 become 28, and 29 jump off the loop and become 27.

We then observe that with -O3, the output is completely mutilated:

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
 1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:12
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./boost_stacktrace.out

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
 1# main at /home/ciro/test/boost_stacktrace.cpp:31
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./boost_stacktrace.out

Backtraces are in general irreparably mutilated by optimizations. Tail call optimization is a notable example of that: What is tail call optimization?

Benchmark run on -O3:

time  ./boost_stacktrace.out 1000 >/dev/null

Output:

real    0m43.573s
user    0m30.799s
sys     0m13.665s

So as expected, we see that this method is extremely slow likely to to external calls to addr2line, and is only going to be feasible if a limited number of calls are being made.

Each backtrace print seems to take hundreds of milliseconds, so be warned that if a backtrace happens very often, program performance will suffer significantly.

Tested on Ubuntu 19.10, GCC 9.2.1, boost 1.67.0.

glibc backtrace

Documented at: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html

main.c

#include <stdio.h>
#include <stdlib.h>

/* Paste this on the file you want to debug. */
#include <stdio.h>
#include <execinfo.h>
void print_trace(void) {
    char **strings;
    size_t i, size;
    enum Constexpr { MAX_SIZE = 1024 };
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
    strings = backtrace_symbols(array, size);
    for (i = 0; i < size; i++)
        printf("%s\n", strings[i]);
    puts("");
    free(strings);
}

void my_func_3(void) {
    print_trace();
}

void my_func_2(void) {
    my_func_3();
}

void my_func_1(void) {
    my_func_3();
}

int main(void) {
    my_func_1(); /* line 33 */
    my_func_2(); /* line 34 */
    return 0;
}

Compile:

gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -rdynamic -std=c99 \
  -Wall -Wextra -pedantic-errors main.c

-rdynamic is the key required option.

Run:

./main.out

Outputs:

./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0x9) [0x4008f9]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]

./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0xe) [0x4008fe]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]

So we immediately see that an inlining optimization happened, and some functions were lost from the trace.

If we try to get the addresses:

addr2line -e main.out 0x4008f9 0x4008fe

we obtain:

/home/ciro/main.c:21
/home/ciro/main.c:36

which is completely off.

If we do the same with -O0 instead, ./main.out gives the correct full trace:

./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_1+0x9) [0x400a68]
./main.out(main+0x9) [0x400a74]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]

./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_2+0x9) [0x400a5c]
./main.out(main+0xe) [0x400a79]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]

and then:

addr2line -e main.out 0x400a74 0x400a79

gives:

/home/cirsan01/test/main.c:34
/home/cirsan01/test/main.c:35

so the lines are off by just one, TODO why? But this might still be usable.

Conclusion: backtraces can only possibly show perfectly with -O0. With optimizations, the original backtrace is fundamentally modified in the compiled code.

I couldn't find a simple way to automatically demangle C++ symbols with this however, here are some hacks:

  • https://panthema.net/2008/0901-stacktrace-demangled/
  • https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3

Tested on Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtrace_symbols_fd

This helper is a bit more convenient than backtrace_symbols, and produces basically identical output:

/* Paste this on the file you want to debug. */
#include <execinfo.h>
#include <stdio.h>
#include <unistd.h>
void print_trace(void) {
    size_t i, size;
    enum Constexpr { MAX_SIZE = 1024 };
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
    backtrace_symbols_fd(array, size, STDOUT_FILENO);
    puts("");
}

Tested on Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtrace with C++ demangling hack 1: -export-dynamic + dladdr

Adapted from: https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3

This is a "hack" because it requires changing the ELF with -export-dynamic.

glibc_ldl.cpp

#include <dlfcn.h>     // for dladdr
#include <cxxabi.h>    // for __cxa_demangle

#include <cstdio>
#include <string>
#include <sstream>
#include <iostream>

// This function produces a stack backtrace with demangled function & method names.
std::string backtrace(int skip = 1)
{
    void *callstack[128];
    const int nMaxFrames = sizeof(callstack) / sizeof(callstack[0]);
    char buf[1024];
    int nFrames = backtrace(callstack, nMaxFrames);
    char **symbols = backtrace_symbols(callstack, nFrames);

    std::ostringstream trace_buf;
    for (int i = skip; i < nFrames; i++) {
        Dl_info info;
        if (dladdr(callstack[i], &info)) {
            char *demangled = NULL;
            int status;
            demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
            std::snprintf(
                buf,
                sizeof(buf),
                "%-3d %*p %s + %zd\n",
                i,
                (int)(2 + sizeof(void*) * 2),
                callstack[i],
                status == 0 ? demangled : info.dli_sname,
                (char *)callstack[i] - (char *)info.dli_saddr
            );
            free(demangled);
        } else {
            std::snprintf(buf, sizeof(buf), "%-3d %*p\n",
                i, (int)(2 + sizeof(void*) * 2), callstack[i]);
        }
        trace_buf << buf;
        std::snprintf(buf, sizeof(buf), "%s\n", symbols[i]);
        trace_buf << buf;
    }
    free(symbols);
    if (nFrames == nMaxFrames)
        trace_buf << "[truncated]\n";
    return trace_buf.str();
}

void my_func_2(void) {
    std::cout << backtrace() << std::endl;
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

Compile and run:

g++ -fno-pie -ggdb3 -O0 -no-pie -o glibc_ldl.out -std=c++11 -Wall -Wextra \
  -pedantic-errors -fpic glibc_ldl.cpp -export-dynamic -ldl
./glibc_ldl.out 

output:

1             0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2             0x40139e my_func_1(int) + 16
./glibc_ldl.out(_Z9my_func_1i+0x10) [0x40139e]
3             0x4013b3 main + 18
./glibc_ldl.out(main+0x12) [0x4013b3]
4       0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5             0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]

1             0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2             0x40138b my_func_1(double) + 18
./glibc_ldl.out(_Z9my_func_1d+0x12) [0x40138b]
3             0x4013c8 main + 39
./glibc_ldl.out(main+0x27) [0x4013c8]
4       0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5             0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]

Tested on Ubuntu 18.04.

glibc backtrace with C++ demangling hack 2: parse backtrace output

Shown at: https://panthema.net/2008/0901-stacktrace-demangled/

This is a hack because it requires parsing.

TODO get it to compile and show it here.

libunwind

TODO does this have any advantage over glibc backtrace? Very similar output, also requires modifying the build command, but not part of glibc so requires an extra package installation.

Code adapted from: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

main.c

/* This must be on top. */
#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>

/* Paste this on the file you want to debug. */
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <stdio.h>
void print_trace() {
    char sym[256];
    unw_context_t context;
    unw_cursor_t cursor;
    unw_getcontext(&context);
    unw_init_local(&cursor, &context);
    while (unw_step(&cursor) > 0) {
        unw_word_t offset, pc;
        unw_get_reg(&cursor, UNW_REG_IP, &pc);
        if (pc == 0) {
            break;
        }
        printf("0x%lx:", pc);
        if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
            printf(" (%s+0x%lx)\n", sym, offset);
        } else {
            printf(" -- error: unable to obtain symbol name for this frame\n");
        }
    }
    puts("");
}

void my_func_3(void) {
    print_trace();
}

void my_func_2(void) {
    my_func_3();
}

void my_func_1(void) {
    my_func_3();
}

int main(void) {
    my_func_1(); /* line 46 */
    my_func_2(); /* line 47 */
    return 0;
}

Compile and run:

sudo apt-get install libunwind-dev
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -std=c99 \
  -Wall -Wextra -pedantic-errors main.c -lunwind

Either #define _XOPEN_SOURCE 700 must be on top, or we must use -std=gnu99:

  • Is the type `stack_t` no longer defined on linux?
  • Glibc - error in ucontext.h, but only with -std=c11

Run:

./main.out

Output:

0x4007db: (main+0xb)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)

0x4007e2: (main+0x12)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)

and:

addr2line -e main.out 0x4007db 0x4007e2

gives:

/home/ciro/main.c:34
/home/ciro/main.c:49

With -O0:

0x4009cf: (my_func_3+0xe)
0x4009e7: (my_func_1+0x9)
0x4009f3: (main+0x9)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)

0x4009cf: (my_func_3+0xe)
0x4009db: (my_func_2+0x9)
0x4009f8: (main+0xe)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)

and:

addr2line -e main.out 0x4009f3 0x4009f8

gives:

/home/ciro/main.c:47
/home/ciro/main.c:48

Tested on Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.

libunwind with C++ name demangling

Code adapted from: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

unwind.cpp

#define UNW_LOCAL_ONLY
#include <cxxabi.h>
#include <libunwind.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>

void backtrace() {
  unw_cursor_t cursor;
  unw_context_t context;

  // Initialize cursor to current frame for local unwinding.
  unw_getcontext(&context);
  unw_init_local(&cursor, &context);

  // Unwind frames one by one, going up the frame stack.
  while (unw_step(&cursor) > 0) {
    unw_word_t offset, pc;
    unw_get_reg(&cursor, UNW_REG_IP, &pc);
    if (pc == 0) {
      break;
    }
    std::printf("0x%lx:", pc);

    char sym[256];
    if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
      char* nameptr = sym;
      int status;
      char* demangled = abi::__cxa_demangle(sym, nullptr, nullptr, &status);
      if (status == 0) {
        nameptr = demangled;
      }
      std::printf(" (%s+0x%lx)\n", nameptr, offset);
      std::free(demangled);
    } else {
      std::printf(" -- error: unable to obtain symbol name for this frame\n");
    }
  }
}

void my_func_2(void) {
    backtrace();
    std::cout << std::endl; // line 43
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}  // line 54

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

Compile and run:

sudo apt-get install libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o unwind.out -std=c++11 \
  -Wall -Wextra -pedantic-errors unwind.cpp -lunwind -pthread
./unwind.out

Output:

0x400c80: (my_func_2()+0x9)
0x400cb7: (my_func_1(int)+0x10)
0x400ccc: (main+0x12)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)

0x400c80: (my_func_2()+0x9)
0x400ca4: (my_func_1(double)+0x12)
0x400ce1: (main+0x27)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)

and then we can find the lines of my_func_2 and my_func_1(int) with:

addr2line -e unwind.out 0x400c80 0x400cb7

which gives:

/home/ciro/test/unwind.cpp:43
/home/ciro/test/unwind.cpp:54

TODO: why are the lines off by one?

Tested on Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.

GDB automation

We can also do this with GDB without recompiling by using: How to do an specific action when a certain breakpoint is hit in GDB?

Although if you are going to print the backtrace a lot, this will likely be less fast than the other options, but maybe we can reach native speeds with compile code, but I'm lazy to test it out now: How to call assembly in gdb?

main.cpp

void my_func_2(void) {}

void my_func_1(double f) {
    my_func_2();
}

void my_func_1(int i) {
    my_func_2();
}

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

main.gdb

start
break my_func_2
commands
  silent
  backtrace
  printf "\n"
  continue
end
continue

Compile and run:

g++ -ggdb3 -o main.out main.cpp
gdb -nh -batch -x main.gdb main.out

Output:

Temporary breakpoint 1 at 0x1158: file main.cpp, line 12.

Temporary breakpoint 1, main () at main.cpp:12
12          my_func_1(1);
Breakpoint 2 at 0x555555555129: file main.cpp, line 1.
#0  my_func_2 () at main.cpp:1
#1  0x0000555555555151 in my_func_1 (i=1) at main.cpp:8
#2  0x0000555555555162 in main () at main.cpp:12

#0  my_func_2 () at main.cpp:1
#1  0x000055555555513e in my_func_1 (f=2) at main.cpp:4
#2  0x000055555555516f in main () at main.cpp:13

[Inferior 1 (process 14193) exited normally]

TODO I wanted to do this with just -ex from the command line to not have to create main.gdb but I couldn't get the commands to work there.

Tested in Ubuntu 19.04, GDB 8.2.

Linux kernel

How to print the current thread stack trace inside the Linux kernel?

libdwfl

This was originally mentioned at: https://stackoverflow.com/a/60713161/895245 and it might be the best method, but I have to benchmark a bit more, but please go upvote that answer.

TODO: I tried to minimize the code in that answer, which was working, to a single function, but it is segfaulting, let me know if anyone can find why.

dwfl.cpp

#include <cassert>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>

#include <cxxabi.h> // __cxa_demangle
#include <elfutils/libdwfl.h> // Dwfl*
#include <execinfo.h> // backtrace
#include <unistd.h> // getpid

// https://stackoverflow.com/questions/281818/unmangling-the-result-of-stdtype-infoname
std::string demangle(const char* name) {
    int status = -4;
    std::unique_ptr<char, void(*)(void*)> res {
        abi::__cxa_demangle(name, NULL, NULL, &status),
        std::free
    };
    return (status==0) ? res.get() : name ;
}

std::string debug_info(Dwfl* dwfl, void* ip) {
    std::string function;
    int line = -1;
    char const* file;
    uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
    Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
    char const* name = dwfl_module_addrname(module, ip2);
    function = name ? demangle(name) : "<unknown>";
    if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
        Dwarf_Addr addr;
        file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
    }
    std::stringstream ss;
    ss << ip << ' ' << function;
    if (file)
        ss << " at " << file << ':' << line;
    ss << std::endl;
    return ss.str();
}

std::string stacktrace() {
    // Initialize Dwfl.
    Dwfl* dwfl = nullptr;
    {
        Dwfl_Callbacks callbacks = {};
        char* debuginfo_path = nullptr;
        callbacks.find_elf = dwfl_linux_proc_find_elf;
        callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
        callbacks.debuginfo_path = &debuginfo_path;
        dwfl = dwfl_begin(&callbacks);
        assert(dwfl);
        int r;
        r = dwfl_linux_proc_report(dwfl, getpid());
        assert(!r);
        r = dwfl_report_end(dwfl, nullptr, nullptr);
        assert(!r);
        static_cast<void>(r);
    }

    // Loop over stack frames.
    std::stringstream ss;
    {
        void* stack[512];
        int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);
        for (int i = 0; i < stack_size; ++i) {
            ss << i << ": ";

            // Works.
            ss << debug_info(dwfl, stack[i]);

#if 0
            // TODO intended to do the same as above, but segfaults,
            // so possibly UB In above function that does not blow up by chance?
            void *ip = stack[i];
            std::string function;
            int line = -1;
            char const* file;
            uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
            Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
            char const* name = dwfl_module_addrname(module, ip2);
            function = name ? demangle(name) : "<unknown>";
            // TODO if I comment out this line it does not blow up anymore.
            if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
              Dwarf_Addr addr;
              file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
            }
            ss << ip << ' ' << function;
            if (file)
                ss << " at " << file << ':' << line;
            ss << std::endl;
#endif
        }
    }
    dwfl_end(dwfl);
    return ss.str();
}

void my_func_2() {
    std::cout << stacktrace() << std::endl;
    std::cout.flush();
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main(int argc, char **argv) {
    long long unsigned int n;
    if (argc > 1) {
        n = strtoul(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    for (long long unsigned int i = 0; i < n; ++i) {
        my_func_1(1);
        my_func_1(2.0);
    }
}

Compile and run:

sudo apt install libdw-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
./dwfl.out

Output:

0: 0x402b74 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402ce0 my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d7d my_func_1(int) at /home/ciro/test/dwfl.cpp:112
3: 0x402de0 main at /home/ciro/test/dwfl.cpp:123
4: 0x7f7efabbe1e3 __libc_start_main at ../csu/libc-start.c:342
5: 0x40253e _start at ../csu/libc-start.c:-1

0: 0x402b74 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402ce0 my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d66 my_func_1(double) at /home/ciro/test/dwfl.cpp:107
3: 0x402df1 main at /home/ciro/test/dwfl.cpp:121
4: 0x7f7efabbe1e3 __libc_start_main at ../csu/libc-start.c:342
5: 0x40253e _start at ../csu/libc-start.c:-1

Benchmark run:

g++ -fno-pie -ggdb3 -O3 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
time ./dwfl.out 1000 >/dev/null

Output:

real    0m3.751s
user    0m2.822s
sys     0m0.928s

So we see that this method is 10x faster than Boost's stacktrace, and might therefore be applicable to more use cases.

Tested in Ubuntu 19.10 amd64, libdw-dev 0.176-1.1.

See also

  • How can one grab a stack trace in C?
  • How to make backtrace()/backtrace_symbols() print the function names?
  • Is there a portable/standard-compliant way to get filenames and linenumbers in a stack trace?
  • Best way to invoke gdb from inside program to print its stacktrace?
  • automatic stack trace on failure:
    • on C++ exception: C++ display stack trace on exception
    • generic: How to automatically generate a stacktrace when my program crashes

Is there any way to dump the call stack in a running process in C or C++ every time a certain function is called?

You can use a macro function instead of return statement in the specific function.

For example, instead of using return,

int foo(...)
{
    if (error happened)
        return -1;

    ... do something ...

    return 0
}

You can use a macro function.

#include "c-callstack.h"

int foo(...)
{
    if (error happened)
        NL_RETURN(-1);

    ... do something ...

    NL_RETURN(0);
}

Whenever an error happens in a function, you will see Java-style call stack as shown below.

Error(code:-1) at : so_topless_ranking_server (sample.c:23)
Error(code:-1) at : nanolat_database (sample.c:31)
Error(code:-1) at : nanolat_message_queue (sample.c:39)
Error(code:-1) at : main (sample.c:47)

Full source code is available here.

c-callstack at https://github.com/Nanolat


Linux specific, TLDR:

  1. backtrace in glibc produces accurate stacktraces only when -lunwind is linked (undocumented platform-specific feature).
  2. To output function name, source file and line number use #include <elfutils/libdwfl.h> (this library is documented only in its header file). backtrace_symbols and backtrace_symbolsd_fd are least informative.

On modern Linux your can get the stacktrace addresses using function backtrace. The undocumented way to make backtrace produce more accurate addresses on popular platforms is to link with -lunwind (libunwind-dev on Ubuntu 18.04) (see the example output below). backtrace uses function _Unwind_Backtrace and by default the latter comes from libgcc_s.so.1 and that implementation is most portable. When -lunwind is linked it provides a more accurate version of _Unwind_Backtrace but this library is less portable (see supported architectures in libunwind/src).

Unfortunately, the companion backtrace_symbolsd and backtrace_symbols_fd functions have not been able to resolve the stacktrace addresses to function names with source file name and line number for probably a decade now (see the example output below).

However, there is another method to resolve addresses to symbols and it produces the most useful traces with function name, source file and line number. The method is to #include <elfutils/libdwfl.h>and link with -ldw (libdw-dev on Ubuntu 18.04).

Working C++ example (test.cc):

#include <stdexcept>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <string>

#include <boost/core/demangle.hpp>

#include <execinfo.h>
#include <elfutils/libdwfl.h>

struct DebugInfoSession {
    Dwfl_Callbacks callbacks = {};
    char* debuginfo_path = nullptr;
    Dwfl* dwfl = nullptr;

    DebugInfoSession() {
        callbacks.find_elf = dwfl_linux_proc_find_elf;
        callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
        callbacks.debuginfo_path = &debuginfo_path;

        dwfl = dwfl_begin(&callbacks);
        assert(dwfl);

        int r;
        r = dwfl_linux_proc_report(dwfl, getpid());
        assert(!r);
        r = dwfl_report_end(dwfl, nullptr, nullptr);
        assert(!r);
        static_cast<void>(r);
    }

    ~DebugInfoSession() {
        dwfl_end(dwfl);
    }

    DebugInfoSession(DebugInfoSession const&) = delete;
    DebugInfoSession& operator=(DebugInfoSession const&) = delete;
};

struct DebugInfo {
    void* ip;
    std::string function;
    char const* file;
    int line;

    DebugInfo(DebugInfoSession const& dis, void* ip)
        : ip(ip)
        , file()
        , line(-1)
    {
        // Get function name.
        uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
        Dwfl_Module* module = dwfl_addrmodule(dis.dwfl, ip2);
        char const* name = dwfl_module_addrname(module, ip2);
        function = name ? boost::core::demangle(name) : "<unknown>";

        // Get source filename and line number.
        if(Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
            Dwarf_Addr addr;
            file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
        }
    }
};

std::ostream& operator<<(std::ostream& s, DebugInfo const& di) {
    s << di.ip << ' ' << di.function;
    if(di.file)
        s << " at " << di.file << ':' << di.line;
    return s;
}

void terminate_with_stacktrace() {
    void* stack[512];
    int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);

    // Print the exception info, if any.
    if(auto ex = std::current_exception()) {
        try {
            std::rethrow_exception(ex);
        }
        catch(std::exception& e) {
            std::cerr << "Fatal exception " << boost::core::demangle(typeid(e).name()) << ": " << e.what() << ".\n";
        }
        catch(...) {
            std::cerr << "Fatal unknown exception.\n";
        }
    }

    DebugInfoSession dis;
    std::cerr << "Stacktrace of " << stack_size << " frames:\n";
    for(int i = 0; i < stack_size; ++i) {
        std::cerr << i << ": " << DebugInfo(dis, stack[i]) << '\n';
    }
    std::cerr.flush();

    std::_Exit(EXIT_FAILURE);
}

int main() {
    std::set_terminate(terminate_with_stacktrace);
    throw std::runtime_error("test exception");
}

Compiled on Ubuntu 18.04.4 LTS with gcc-8.3:

g++ -o test.o -c -m{arch,tune}=native -std=gnu++17 -W{all,extra,error} -g -Og -fstack-protector-all test.cc
g++ -o test -g test.o -ldw -lunwind

Outputs:

Fatal exception std::runtime_error: test exception.
Stacktrace of 7 frames:
0: 0x55f3837c1a8c terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7fbc1c845ae5 <unknown>
2: 0x7fbc1c845b20 std::terminate()
3: 0x7fbc1c845d53 __cxa_throw
4: 0x55f3837c1a43 main at /home/max/src/test/test.cc:103
5: 0x7fbc1c3e3b96 __libc_start_main at ../csu/libc-start.c:310
6: 0x55f3837c17e9 _start

When no -lunwind is linked, it produces a less accurate stacktrace:

0: 0x5591dd9d1a4d terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7f3c18ad6ae6 <unknown>
2: 0x7f3c18ad6b21 <unknown>
3: 0x7f3c18ad6d54 <unknown>
4: 0x5591dd9d1a04 main at /home/max/src/test/test.cc:103
5: 0x7f3c1845cb97 __libc_start_main at ../csu/libc-start.c:344
6: 0x5591dd9d17aa _start

For comparison, backtrace_symbols_fd output for the same stacktrace is least informative:

/home/max/src/test/debug/gcc/test(+0x192f)[0x5601c5a2092f]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(+0x92ae5)[0x7f95184f5ae5]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(_ZSt9terminatev+0x10)[0x7f95184f5b20]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(__cxa_throw+0x43)[0x7f95184f5d53]
/home/max/src/test/debug/gcc/test(+0x1ae7)[0x5601c5a20ae7]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe6)[0x7f9518093b96]
/home/max/src/test/debug/gcc/test(+0x1849)[0x5601c5a20849]

In a production version (as well as C language version) you may like to make this code extra robust by replacing boost::core::demangle, std::string and std::cout with their underlying calls.

You can also override __cxa_throw to capture the stacktrace when an exception is thrown and print it when the exception is caught. By the time it enters catch block the stack has been unwound, so it is too late to call backtrace, and this is why the stack must be captured on throw which is implemented by function __cxa_throw. Note that in a multi-threaded program __cxa_throw can be called simultaneously by multiple threads, so that if it captures the stacktrace into a global array that must be thread_local.

You can also make the stack trace printing function async-signal safe, so that you can invoke it directly from your SIGSEGV, SIGBUS signal handlers (which should use their own stacks for robustness). Obtaining function name, source file and line number using libdwfl from a signal handler may fail because it is not async-signal safe or if the address space of the process has been substantially corrupted, but in practice it succeeds 99% of the time (I haven't seen it fail).


To summarize, a complete production-ready library for automatic stacktrace output should:

  1. Capture the stacktrace on throw into thread-specific storage.
  2. Automatically print the stacktrace on unhandled exceptions.
  3. Print the stacktrace in async-signal-safe manner.
  4. Provide a robust signal handler function which uses its own stack that prints the stacktrace in a async-signal-safe manner. The user can install this function as a signal handler for SIGSEGV, SIGBUS, SIGFPE, etc..
  5. The signal handler may as well print the values of all CPU registers at the point of the fault from ucontext_t signal function argument (may be excluding vector registers), a-la Linux kernel oops log messages.