C++
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.
-
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.
-
Choose Your Method:
-
Debugger (e.g., GDB for C/C++):
- Compile your code with debugging symbols (e.g.,
-gflag for GCC). - Start your program under the debugger (e.g.,
gdb ./my_program). - Set a breakpoint at the beginning of your target function:
break my_function. - 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. - Run your program:
run.
- Compile your code with debugging symbols (e.g.,
-
**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}")
-
-
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_traceworks similarly tocallerin 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_functionputs some sort of internal breakpoint that will cause a stack trace to be printed wheneverfoois 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 `Empty cells mean "TODO", not "no".` 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 | -
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)andmy_func_1(float)are necessarily mangled as a way to implement C++ function overload.We differentiate between the benchmarks by using different
-Iincludes to point to different implementations ofprint_stacktrace().Each benchmark is done with a command of form:
time ./stacktrace.out 100000 &>/dev/nullThe number of iterations is adjusted for each implementation to produce a total runtime of the order of 1s for that benchmark.
-O0is 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++_libbacktraceSample 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++_libbacktraceIt 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 -vdoes not show:
--enable-libstdcxx-backtrace=yesso it wasn’t compiled in. Bibliography:
It does not fail on the include because the header file:
/usr/include/c++/12has a feature check:
#if __cplusplus > 202002L && _GLIBCXX_HAVE_STACKTRACEBoost
stacktraceThe 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”.
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
addr2lineexecutable and be 1000x slower than the newer Boost version.The package
libboost-stacktrace-devdid 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_backtraceSample 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.outNote 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
stacktraceheader onlyWhat the
BOOST_STACKTRACE_LINKdoes is to require-lboost_stacktrace_backtraceat 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.cppcontains 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.outwhich 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_toThis is an interesting alternative to
boost::stacktrace::stacktraceas 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 crashesDocumented 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[FUChanges drastically each time, suggesting it is random memory addresses.
Tested on Ubuntu 22.04, boost 1.74.0.
glibc
backtraceThis 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.cppSample 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
-rdynamicwe can useaddr2line:addr2line -C -e glibc_backtrace_symbols_fd_no_rdynamic.out 0x11f0 0x123c 0x126c 0x12c7addr2linecannot 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 | addr2linesglibc
backtrace_symbolsA version of
backtrace_symbols_fdthat 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
backtracewith C++ demangling hack 1:-export-dynamic+dladdrI couldn’t find a simple way to automatically demangle C++ symbols with glibc
backtrace.- https://panthema.net/2008/0901-stacktrace-demangled/
- https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
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.outoutput:
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
backtracewith C++ demangling hack 2: parse backtrace outputShown 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 continuewe can run:
gdb -nh -batch -x main.gdb --args gdb.outSample 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 2I don’t know how to make
commandswith-exwithout the temporary file: Problems adding a breakpoint with commands from command line with ex commandTested 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:
- with
compile code+ one of the other options, ideally C++23<stacktrace>: How to call assembly in gdb? Might already be possible. Butcompile codeis mega-quirky so I’m lazy to even try - a built-in
dbtcommand analogous todprintfdynamic printf: How to do an specific action when a certain breakpoint is hit in GDB?
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 -lunwindEither
#define _XOPEN_SOURCE 700must be on top, or we must use-std=gnu99:- Is the type
stack\_tno longer defined on linux? - Glibc - error in ucontext.h, but only with -std=c11
Run:
./main.outOutput:
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 0x4007e2gives:
/home/ciro/main.c:34 /home/ciro/main.c:49With
-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 0x4009f8gives:
/home/ciro/main.c:47 /home/ciro/main.c:48Tested 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.outOutput:
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_2andmy_func_1(int)with:addr2line -e unwind.out 0x400c80 0x400cb7which gives:
/home/ciro/test/unwind.cpp:43 /home/ciro/test/unwind.cpp:54TODO: 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.outWe 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:-1Benchmark 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/nullOutput:
real 0m3.751s user 0m2.822s sys 0m0.928sSo 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
- 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:
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.outIn 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.
-