C++

How to print a stack trace whenever a certain function is called

25 September 2026 · 22 min read

How to print a stack trace whenever a certain function is called

Understanding the execution flow of your software is paramount for effective debugging and performance optimization. When an application behaves unexpectedly, or you need to trace the exact sequence of function calls leading to a specific state, knowing how to print a stack trace whenever a certain function is called becomes an invaluable skill. A stack trace provides a snapshot of the active subroutines in a program at a particular point in time, essentially showing you the nested function calls that led to the current execution point. This capability allows developers to pinpoint the origin of issues, analyze control flow, and gain deep insights into their code’s runtime behavior, transforming a frustrating debugging session into a methodical investigation. This guide will walk you through various techniques to achieve this, from debugger-assisted methods to programmatic approaches across different languages.

Understanding the Call Stack and Stack Traces

At the heart of every running program lies the call stack, a fundamental data structure that manages function calls. When a function is invoked, a new frame is pushed onto the call stack, containing local variables, arguments, and the return address. When the function completes, its frame is popped off the stack, and execution returns to the caller. This continuous pushing and popping dictate the program’s execution flow. As an experienced software engineer, I can attest that mastering call stack analysis is essential for debugging complex systems.

A stack trace is essentially a textual representation of the call stack’s contents at a specific moment. It lists the sequence of function calls that are currently active, starting from the most recently called function down to the initial entry point of the program. For instance, if function A calls B, and B calls C, a stack trace from within C would show C, then B, then A. This “bread crumb” trail is incredibly powerful for identifying where an error originated, understanding unexpected recursion, or profiling performance bottlenecks. According to a study on debugging effectiveness, developers who leverage stack traces effectively resolve issues significantly faster.

The utility of a stack trace extends beyond just error detection. It helps visualize the dynamic interactions between different parts of your codebase, making it easier to comprehend complex logic or unravel intricate dependencies. When you need to understand how a particular function is being invoked under specific conditions, conditionally printing its stack trace offers unparalleled clarity, revealing all upstream callers in precise order. This runtime introspection is a cornerstone of advanced debugging techniques.

Common Approaches to Conditional Stack Tracing

There are generally two primary methods for obtaining a stack trace whenever a specific function is called: using a debugger with conditional breakpoints or implementing programmatic logging within your code. Each approach has its own strengths and use cases, depending on the environment and the nature of the problem you’re trying to solve.

For developers looking to capture a stack trace programmatically when a specific function is invoked, the most common method involves inserting a code snippet within the target function itself that queries the runtime environment for the current call stack. Many modern programming languages offer built-in modules or APIs (e.g., Python’s traceback module, Java’s Thread.currentThread().getStackTrace(), C++’s boost::stacktrace or platform-specific APIs like backtrace on Linux) that can generate and print this information directly to a log file or console. This approach is highly flexible and can be integrated into custom logging frameworks.

Debuggers, such as GDB for C/C++, Visual Studio Debugger, or PyCharm’s debugger for Python, provide powerful non-invasive ways to achieve this. You can set a breakpoint at the entry point of the function in question and then add a condition to that breakpoint. The debugger will only pause execution (or execute a command) when the condition evaluates to true. For instance, you could set a condition based on the value of a specific variable, allowing you to print a stack trace only when certain criteria are met. This method is particularly useful during interactive debugging sessions where you need to explore the program state at the exact moment of interest without modifying the source code.

  • Debugger-Assisted Tracing: Ideal for interactive debugging, allows setting conditions without code modification.
  • Programmatic Tracing: Best for automated testing, continuous integration, or when deployed applications need detailed logging.

Step-by-Step Guide: Printing a Stack Trace on Function Call

Let’s outline a general strategy that combines both debugger and programmatic insights to effectively print a stack trace. This approach focuses on typical scenarios you’ll encounter in real-world development.

  1. Identify the Target Function: Pinpoint the exact function whose invocation you want to monitor. This could be a function suspected of being called unexpectedly, or one whose callers you need to understand under specific conditions.

  2. Choose Your Method:

    • Debugger (e.g., GDB for C/C++):

      1. Compile your code with debugging symbols (e.g., -g flag for GCC).
      2. Start your program under the debugger (e.g., gdb ./my_program).
      3. Set a breakpoint at the beginning of your target function: break my_function.
      4. Add a command to the breakpoint to print the stack trace. For GDB, this is often done with commands: ``` (gdb) commands 1 > silent > bt > continue > end
        
         (where '1' is the breakpoint number). The `silent` command prevents GDB from stopping and printing "Breakpoint 1, my\_function() at...", `bt` prints the backtrace, and `continue` resumes execution.
        
      5. Run your program: run.
    • **Programmatic (e.g., Python):**Insert code directly into the target function. For Python, this might look like:

      import traceback import logging Configure logging if you want to save traces to a file logging.basicConfig(filename='function_calls.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') def my_target_function(arg1, arg2): if arg1 == "specific_condition": Optional: Add a condition stack_trace = traceback.format_stack() logging.info(f"Stack trace for my_target_function call with arg1='{arg1}':\n{''.join(stack_trace)}") Or simply print: print(f"Stack trace for my_target_function call with arg1='{arg1}':") traceback.print_stack() Rest of your function logic print(f"Executing my_target_function with {arg1}, {arg2}")
      
  3. Add Conditions (Optional but Recommended): For both debugger and programmatic approaches, adding Question & Answer :
    Is there any way to dump the call stack in a running process in C or C++ every time a certain function is called? What I have in mind is something like this:

    void foo() { print_stack_trace(); // foo's body return } 
    

    Where print_stack_trace works similarly to caller in Perl.

    Or something like this:

    int main (void) { // will print out debug info every time foo() is called register_stack_trace_function(foo); // etc... } 
    

    where register_stack_trace_function puts some sort of internal breakpoint that will cause a stack trace to be printed whenever foo is called.

    Does anything like this exist in some standard C library?

    I am working on Linux, using GCC.


    Background

    I have a test run that behaves differently based on some commandline switches that shouldn’t affect this behavior. My code has a pseudo-random number generator that I assume is being called differently based on these switches. I want to be able to run the test with each set of switches and see if the random number generator is called differently for each one.

    Survey of C/C++ backtrace methods

    In this answer I will try to run a single benchmark for a bunch of solutions to see which one runs faster, while also considering other points such as features and portability.

    | Tool | Time / call | Line number | Function name | C++ demangling | Recompile | Signal safe | As string | C | |---|---|---|---|---|---|---|---|---| | C++23 `` GCC 12.1 | 7 us | y | y | y | y | n | y | n | | Boost 1.74 `stacktrace()` | 5 us | y | y | y | y | n | y | n | | Boost 1.74 `stacktrace::safe_dump_to` | | | | | | y (deprecated) | n | n | | glibc `backtrace_symbols_fd` | 25 us | n | `-rdynamic` | hacks | y | n | n | y | | glibc `backtrace_symbols` | 21 us | n | `-rdynamic` | hacks | y | n | y | y | | GDB scripting | 600 us | y | y | y | n | y | n | y | | GDB code injection | | | | | n | | n | y | | libunwind | | | | | y | | | | | libdwfl | 4 ms | n | | | y | | | | | libbacktrace | | | | | y | | | | | cpptrace | | y | y | y | n | y | y | y |
    Empty cells mean "TODO", not "no".
    • us: microsecond

    • Line number: shows actual line number, not just function name + a memory address.

      It is usually possible to recover the line number from an address manually after the fact with addr2line. But it is a pain.

    • Recompile: requires recompiling the program to get your traces. Not recompiling is better!

    • Signal safe: crucial for the important uses case of “getting a stack trace in case of segfault”: How to automatically generate a stacktrace when my program crashes

    • As string: you get the stack trace as a string in the program itself, as opposed to e.g. just printing to stdout. Usually implies not signal safe, as we don’t know the size of the stack trace string size in advance, and therefore requires malloc which is not async signal safe.

    • C: does it work on a plain-C project (yes, there are still poor souls out there), or is C++ required?

    Test setup

    All benchmarks will run the following

    main.cpp

    #include <cstdlib> // strtoul #include <mystacktrace.h> void my_func_2(void) { print_stacktrace(); // line 6 } void my_func_1(double f) { (void)f; my_func_2(); } void my_func_1(int i) { (void)i; my_func_2(); // line 16 } int main(int argc, char **argv) { long long unsigned int n; if (argc > 1) { n = std::strtoul(argv[1], NULL, 0); } else { n = 1; } for (long long unsigned int i = 0; i < n; ++i) { my_func_1(1); // line 27 } } 
    

    This input is designed to test C++ name demangling since my_func_1(int) and my_func_1(float) are necessarily mangled as a way to implement C++ function overload.

    We differentiate between the benchmarks by using different -I includes to point to different implementations of print_stacktrace().

    Each benchmark is done with a command of form:

    time ./stacktrace.out 100000 &>/dev/null 
    

    The number of iterations is adjusted for each implementation to produce a total runtime of the order of 1s for that benchmark.

    -O0 is used on all tests below unless noted. Stack traces may be irreparably mutilated by certain optimizations. Tail call optimization is a notable example of that: What is tail call optimization? There’s nothing we can do about it.

    C++23 <stacktrace>

    This method was previously mentioned at: https://stackoverflow.com/a/69384663/895245 please consider upvoting that answer.

    This is the best solution… it’s portable, fast, shows line numbers and demangles C++ symbols. This option will displace every other alternative as soon as it becomes more widely available, with the exception perhaps only of GDB for one-offs without the need or recompilation.

    cpp20_stacktrace/mystacktrace.h

    #include <iostream> #include <stacktrace> void print_stacktrace() { std::cout << std::stacktrace::current(); } 
    

    GCC 12.1.0 from Ubuntu 22.04 does not have support compiled in, so for now I built it from source as per: How to edit and re-build the GCC libstdc++ C++ standard library source? and set --enable-libstdcxx-backtrace=yes, and it worked!

    Compile with:

    g++ -O0 -ggdb3 -Wall -Wextra -pedantic -std=c++23 -o cpp20_stacktrace.out main.cpp -lstdc++_libbacktrace 
    

    Sample output:

    0# print_stacktrace() at cpp20_stacktrace/mystacktrace.h:5 1# my_func_2() at /home/ciro/main.cpp:6 2# my_func_1(int) at /home/ciro/main.cpp:16 3# at /home/ciro/main.cpp:27 4# at :0 5# at :0 6# at :0 7# 
    

    If we try to use GCC 12.1.0 from Ubuntu 22.04:

    sudo apt install g++-12 g++-12 -ggdb3 -O2 -std=c++23 -Wall -Wextra -pedantic -o stacktrace.out stacktrace.cpp -lstdc++_libbacktrace 
    

    It fails with:

    stacktrace.cpp: In function ‘void my_func_2()’: stacktrace.cpp:6:23: error: ‘std::stacktrace’ has not been declared 6 | std::cout << std::stacktrace::current(); | ^~~~~~~~~~ 
    

    Checking build options with:

    g++-12 -v 
    

    does not show:

    --enable-libstdcxx-backtrace=yes 
    

    so it wasn’t compiled in. Bibliography:

    It does not fail on the include because the header file:

    /usr/include/c++/12 
    

    has a feature check:

    #if __cplusplus > 202002L && _GLIBCXX_HAVE_STACKTRACE 
    

    Boost stacktrace

    The library has changed quite a lot around Ubuntu 22.04, so make sure your version matches: Boost stack-trace not showing function names and line numbers

    The library is pretty much superseded by the more portable C++23 implementation, but remains a very good option for those that are not at that standard version yet, but already have a “Boost clearance”.

    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

    Tested on Ubuntu 22.04, boost 1.74.0, you should do:

    boost_stacktrace/mystacktrace.h

    #include <iostream> #define BOOST_STACKTRACE_LINK #include <boost/stacktrace.hpp> void print_stacktrace(void) { std::cout << boost::stacktrace::stacktrace(); } 
    

    On Ubuntu 19.10 boost 1.67.0 to get the line numbers we had to instead:

    #include <iostream> #define BOOST_STACKTRACE_USE_ADDR2LINE #include <boost/stacktrace.hpp> void print_stacktrace(void) { std::cout << boost::stacktrace::stacktrace(); } 
    

    which would call out to the addr2line executable and be 1000x slower than the newer Boost version.

    The package libboost-stacktrace-dev did not exist at all on Ubuntu 16.04.

    The rest of this section considers only the Ubuntu 22.04, boost 1.74 behaviour.

    Compile:

    sudo apt-get install libboost-stacktrace-dev g++ -O0 -ggdb3 -Wall -Wextra -pedantic -std=c++11 -o boost_stacktrace.out main.cpp -lboost_stacktrace_backtrace 
    

    Sample output:

    0# print_stacktrace() at boost_stacktrace/mystacktrace.h:7 1# my_func_2() at /home/ciro/main.cpp:7 2# my_func_1(int) at /home/ciro/main.cpp:17 3# main at /home/ciro/main.cpp:26 4# __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58 5# __libc_start_main at ../csu/libc-start.c:379 6# _start in ./boost_stacktrace.out 
    

    Note that the lines are off by one line. It was suggested in the comments that this is because the following instruction address is being considered.

    Boost stacktrace header only

    What the BOOST_STACKTRACE_LINK does is to require -lboost_stacktrace_backtrace at link time, so we imagine without that it will just work. This would be a good option for devs who don’t have the “Boost clearance” to just add as one offs to debug.

    TODO unfortunately it didn’t so well for me:

    #include <iostream> #include <boost/stacktrace.hpp> void print_stacktrace(void) { std::cout << boost::stacktrace::stacktrace(); } 
    

    then:

    g++ -O0 -ggdb3 -Wall -Wextra -pedantic -std=c++11 -o boost_stacktrace_header_only.out main.cpp 
    

    contains the overly short output:

    0# 0x000055FF74AFB601 in ./boost_stacktrace_header_only.out 1# 0x000055FF74AFB66C in ./boost_stacktrace_header_only.out 2# 0x000055FF74AFB69C in ./boost_stacktrace_header_only.out 3# 0x000055FF74AFB6F7 in ./boost_stacktrace_header_only.out 4# 0x00007F0176E7BD90 in /lib/x86_64-linux-gnu/libc.so.6 5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6 6# 0x000055FF74AFB4E5 in ./boost_stacktrace_header_only.out 
    

    which we can’t even use with addr2line. Maybe we have to pass some other define from: https://www.boost.org/doc/libs/1_80_0/doc/html/stacktrace/configuration_and_build.html ?

    Tested on Ubuntu 22.04. boost 1.74.

    Boost boost::stacktrace::safe_dump_to

    This is an interesting alternative to boost::stacktrace::stacktrace as it writes the stack trace in a async signal safe manner to a file, which makes it a good option for automatically dumping stack traces on segfaults which is a super common use case: How to automatically generate a stacktrace when my program crashes

    Documented at: https://www.boost.org/doc/libs/1_70_0/doc/html/boost/stacktrace/safe_dump_1_3_38_7_6_2_1_6.html

    TODO get it to work. All I see each time is a bunch of random bytes. My attempt:

    boost_stacktrace_safe/mystacktrace.h

    #include <unistd.h> #define BOOST_STACKTRACE_LINK #include <boost/stacktrace.hpp> void print_stacktrace(void) { boost::stacktrace::safe_dump_to(0, 1024, STDOUT_FILENO); } 
    

    Sample output:

    1[FU1[FU"2[FU}2[FUm1@n10[FU 
    

    Changes drastically each time, suggesting it is random memory addresses.

    Tested on Ubuntu 22.04, boost 1.74.0.

    glibc backtrace

    This method is quite portable as it comes with glibc itself. Documented at: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html

    Tested on Ubuntu 22.04, glibc 2.35.

    glibc_backtrace_symbols_fd/mystacktrace.h

    #include <execinfo.h> /* backtrace, backtrace_symbols_fd */ #include <unistd.h> /* STDOUT_FILENO */ void print_stacktrace(void) { size_t size; enum Constexpr { MAX_SIZE = 1024 }; void *array[MAX_SIZE]; size = backtrace(array, MAX_SIZE); backtrace_symbols_fd(array, size, STDOUT_FILENO); } 
    

    Compile with:

    g++ -O0 -ggdb3 -Wall -Wextra -pedantic -rdynamic -std=c++11 -o glibc_backtrace_symbols_fd.out main.cpp 
    

    Sample output with -rdynamic:

    ./glibc_backtrace_symbols.out(_Z16print_stacktracev+0x47) [0x556e6a131230] ./glibc_backtrace_symbols.out(_Z9my_func_2v+0xd) [0x556e6a1312d6] ./glibc_backtrace_symbols.out(_Z9my_func_1i+0x14) [0x556e6a131306] ./glibc_backtrace_symbols.out(main+0x58) [0x556e6a131361] /lib/x86_64-linux-gnu/libc.so.6(+0x29d90) [0x7f175e7bdd90] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80) [0x7f175e7bde40] ./glibc_backtrace_symbols.out(_start+0x25) [0x556e6a131125] 
    

    Sample output without -rdynamic:

    ./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x11f0)[0x556bd40461f0] ./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x123c)[0x556bd404623c] ./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x126c)[0x556bd404626c] ./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x12c7)[0x556bd40462c7] /lib/x86_64-linux-gnu/libc.so.6(+0x29d90)[0x7f0da2b70d90] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80)[0x7f0da2b70e40] ./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x10e5)[0x556bd40460e5] 
    

    To get the line numbers without -rdynamic we can use addr2line:

    addr2line -C -e glibc_backtrace_symbols_fd_no_rdynamic.out 0x11f0 0x123c 0x126c 0x12c7 
    

    addr2line cannot unfortunately handle the function name + offset in function format of when we are not using -rdynamic, e.g. _Z9my_func_2v+0xd.

    GDB can however:

    gdb -nh -batch -ex 'info line *(_Z9my_func_2v+0xd)' -ex 'info line *(_Z9my_func_1i+0x14)' glibc_backtrace_symbols.out Line 7 of "main.cpp" starts at address 0x12d6 <_Z9my_func_2v+13> and ends at 0x12d9 <_Z9my_func_1d>. Line 17 of "main.cpp" starts at address 0x1306 <_Z9my_func_1i+20> and ends at 0x1309 <main(int, char**)>. 
    

    A helper to make it more bearable:

    addr2lines() ( perl -ne '$m = s/(.*).*\(([^)]*)\).*/gdb -nh -q -batch -ex "info line *\2" \1/;print $_ if $m' | bash ) 
    

    Usage:

    xsel -b | addr2lines 
    

    glibc backtrace_symbols

    A version of backtrace_symbols_fd that returns a string rather than printing to a file handle.

    glibc_backtrace_symbols/mystacktrace.h

    #include <execinfo.h> /* backtrace, backtrace_symbols */ #include <stdio.h> /* printf */ void print_stacktrace(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]); free(strings); } 
    

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

    I couldn’t find a simple way to automatically demangle C++ symbols with glibc backtrace.

    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.

    GDB scripting

    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?

    We setup an empty backtrace function for our testing:

    gdb/mystacktrace.h

    void print_stacktrace(void) {} 
    

    and then with:

    main.gdb

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

    we can run:

    gdb -nh -batch -x main.gdb --args gdb.out 
    

    Sample output:

    Temporary breakpoint 1 at 0x11a7: file main.cpp, line 21. [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Temporary breakpoint 1, main (argc=1, argv=0x7fffffffc3e8) at main.cpp:21 warning: Source file is more recent than executable. 21 if (argc > 1) { Breakpoint 2 at 0x555555555151: file gdb/mystacktrace.h, line 1. #0 print_stacktrace () at gdb/mystacktrace.h:1 #1 0x0000555555555161 in my_func_2 () at main.cpp:6 #2 0x0000555555555191 in my_func_1 (i=1) at main.cpp:16 #3 0x00005555555551ec in main (argc=1, argv=0x7fffffffc3e8) at main.cpp:27 [Inferior 1 (process 165453) exited normally] 
    

    The above can be made more usable with the following Bash function:

    gdbbt() ( tmpfile=$(mktemp /tmp/gdbbt.XXXXXX) fn="$1" shift printf '%s' " start break $fn commands silent backtrace printf \"\n\" continue end continue " > "$tmpfile" gdb -nh -batch -x "$tmpfile" -args "$@" rm -f "$tmpfile" ) 
    

    Usage:

    gdbbt print_stacktrace gdb.out 2 
    

    I don’t know how to make commands with -ex without the temporary file: Problems adding a breakpoint with commands from command line with ex command

    Tested in Ubuntu 22.04, GDB 12.0.90.

    GDB code injection

    TODO this is the dream! It might allow for both compiled-liked speeds, but without the need to recompile! Either:

    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:

    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.

    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: answer reached 30k chars and this was the easiest cut: https://gist.github.com/cirosantilli/f1dd3ee5d324b9d24e40f855723544ac

    Compile and run:

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

    We also need libunwind as that makes results more correct. If you do without it, it runs, but you will see that some of the lines are a bit wrong.

    Output:

    0: 0x402b72 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65 1: 0x402cda my_func_2() at /home/ciro/test/dwfl.cpp:100 2: 0x402d76 my_func_1(int) at /home/ciro/test/dwfl.cpp:111 3: 0x402dd1 main at /home/ciro/test/dwfl.cpp:122 4: 0x7ff227ea0d8f __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58 5: 0x7ff227ea0e3f __libc_start_main@@GLIBC_2.34 at ../csu/libc-start.c:392 6: 0x402534 _start at ../csu/libc-start.c:-1 0: 0x402b72 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65 1: 0x402cda my_func_2() at /home/ciro/test/dwfl.cpp:100 2: 0x402d5f my_func_1(double) at /home/ciro/test/dwfl.cpp:106 3: 0x402de2 main at /home/ciro/test/dwfl.cpp:123 4: 0x7ff227ea0d8f __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58 5: 0x7ff227ea0e3f __libc_start_main@@GLIBC_2.34 at ../csu/libc-start.c:392 6: 0x402534 _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 22.04 amd64, libdw-dev 0.186, libunwind 1.3.2.

    libbacktrace

    https://github.com/ianlancetaylor/libbacktrace

    Considering the harcore library author, it is worth trying this out, maybe it is The One. TODO check it out.

    A C library that may be linked into a C/C++ program to produce symbolic backtraces

    As of October 2020, libbacktrace supports ELF, PE/COFF, Mach-O, and XCOFF executables with DWARF debugging information. In other words, it supports GNU/Linux, *BSD, macOS, Windows, and AIX. The library is written to make it straightforward to add support for other object file and debugging formats.

    The library relies on the C++ unwind API defined at https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html This API is provided by GCC and clang.

    See also

    cpptrace

    Cpptrace is simple and portable supporting C++11 and newer. Unlike other solutions it supports all major platforms and compilers and is almost entirely self-contained.

    cpptrace::generate_trace().print(); 
    

    Sample output:

    Stack trace (most recent call first): #0 0x000055fb1305f235 in my_func_2() at /path/to/main.cpp:6 #1 0x000055fb1305f2b5 in my_func_1(int) at /path/to/main.cpp:16 #2 0x000055fb1305f310 in main at /path/to/main.cpp:27 #3 0x00007f648990cd8f in __libc_start_call_main at ./csu/../sysdeps/nptl/libc_start_call_main.h:58 #4 0x00007f648990ce3f in __libc_start_main_impl at ./csu/../csu/libc-start.c:392 #5 0x000055fb1305f144 in _start at ./stacktrace.out 
    

    In addition to general stacktrace generation it includes a traced exception object that generates a trace when thrown and allows for generation of raw stack traces that can be resolved later.

    More information can be found here.