Question
How to Generate a Stack Trace in C++ with GCC When a Program Crashes
Question
I am developing a C++ program with GCC. When the program crashes, I want it to automatically generate a stack trace.
The application is used by many different users and runs on Linux, Windows, and macOS. All versions are compiled with GCC.
I would like the program to capture a stack trace when it crashes, store it, and then ask the user on the next launch whether they want to send that crash information to me for debugging.
I can handle the part where the program sends the information, but I do not know how to generate the stack trace text itself. What are the usual ways to do this in C++?
Short Answer
By the end of this page, you will understand what a stack trace is, how crash handling works in C++ programs compiled with GCC, and how to capture a basic stack trace on Linux. You will also learn the limits of in-process crash reporting, common portability issues across Linux, Windows, and macOS, and practical patterns real applications use to record crash details safely.
Concept
A stack trace is a list of function calls that were active when a program crashed or reached a particular point in execution. It helps you answer questions like:
- Which function failed?
- What code path led to the failure?
- Was the crash caused directly here or deeper below?
In C++, stack traces are especially useful for debugging:
- segmentation faults
- invalid memory access
- aborts caused by assertions
- unexpected termination
On systems using GCC, a common Linux approach is to use functions from execinfo.h, such as:
backtrace()backtrace_symbols()backtrace_symbols_fd()
These can capture return addresses from the current call stack and sometimes translate them into readable function names.
However, there is an important real-world detail: capturing a stack trace inside a crash handler is not always fully safe or portable.
Why?
- A crash may happen when memory is already corrupted.
- Signal handlers have strict rules about what functions are safe to call.
- Name resolution may depend on debug symbols and compiler/linker settings.
- Linux, Windows, and macOS use different APIs for crash handling.
That means there are usually two levels of crash reporting:
- Simple in-process reporting: install a signal handler and try to capture a stack trace.
- Robust production reporting: generate a core dump, minidump, or use an external crash reporter.
Mental Model
Think of the call stack like a trail of breadcrumbs.
Every time one function calls another, a new breadcrumb is dropped:
main()callsrunApp()runApp()callsloadConfig()loadConfig()callsparseFile()
If the program crashes inside parseFile(), a stack trace shows the breadcrumb trail back to where execution started.
So instead of only knowing where the crash happened, you also see how the program got there.
Another way to picture it: imagine a stack of plates.
- Each function call adds a plate.
- Returning from a function removes a plate.
- When the program crashes, the current stack of plates tells you what was active at that moment.
The stack trace is basically a snapshot of that stack.
Syntax and Examples
A common Linux/GCC technique is to use backtrace() from execinfo.h.
Basic syntax
#include <execinfo.h>
#include <unistd.h>
#include <cstdlib>
void print_stacktrace() {
void* frames[64];
int size = backtrace(frames, 64);
backtrace_symbols_fd(frames, size, STDERR_FILENO);
}
What this does
framesstores call stack addresses.backtrace()fills the array with the current stack frames.backtrace_symbols_fd()writes a readable form to a file descriptor such as standard error.
Example: capture a stack trace on a crash signal
#include <execinfo.h>
#include
{
* message = ;
(STDERR_FILENO, message, );
* frames[];
size = (frames, );
(frames, size, STDERR_FILENO);
_exit();
}
{
a / b;
}
{
(SIGSEGV, crash_handler);
(SIGABRT, crash_handler);
(SIGFPE, crash_handler);
(, );
}
Step by Step Execution
Consider this example:
#include <execinfo.h>
#include <signal.h>
#include <unistd.h>
#include <cstdlib>
void crash_handler(int signal_number) {
void* frames[10];
int size = backtrace(frames, 10);
backtrace_symbols_fd(frames, size, STDERR_FILENO);
_exit(1);
}
void third() {
int* p = nullptr;
*p = 42;
}
void second() {
third();
}
void first() {
second();
}
int main() {
signal(SIGSEGV, crash_handler);
first();
}
What happens step by step
Real World Use Cases
Stack traces are used in many practical situations:
Desktop applications
A GUI application crashes on a user's machine. The program stores crash information in a file and asks the user on the next launch whether to send the report.
Command-line tools
A utility used in automation fails unexpectedly. A stack trace in a log file helps developers reproduce and fix the issue.
Game development
A game crashes only on certain hardware or drivers. Stack traces help narrow down whether the issue is in rendering, file loading, or input handling.
Server processes
A backend service crashes under unusual traffic patterns. Capturing a stack trace can reveal which request path or internal function was active.
Plugin-based systems
An app that loads external modules crashes. The stack trace can show whether the fault happened in host code or plugin code.
Internal testing and QA
Testers may not have a debugger attached. Automatic crash traces give developers useful information without requiring local debugging.
Real Codebase Usage
In real projects, developers usually do more than just print a trace to the terminal.
Common patterns
Crash file creation
When a fatal error happens, the program writes a crash report to a file such as:
crash-report.txt
That file might include:
- timestamp
- application version
- operating system
- signal or exception type
- stack trace
Early startup recovery
On next launch, the app checks whether a crash report already exists.
- If yes, prompt the user.
- If approved, upload or attach the report.
- Then delete or archive it.
Guarded error boundaries
Teams often combine crash reporting with validation and early returns to reduce crashes before they happen.
For example:
if (configPath.empty()) {
return false;
}
This does not replace crash reporting, but it reduces avoidable failures.
Separate debug symbol handling
In production builds, symbols may be stored separately instead of shipped directly with the app. The crash report may contain addresses, and developers resolve them later using symbol files.
External crash reporters
Large codebases often prefer:
Common Mistakes
1. Assuming stack traces are fully portable
A common beginner mistake is expecting one solution to work identically on Linux, Windows, and macOS.
- Linux often uses signals and
execinfo.h - Windows uses different APIs such as structured exception handling and debugging libraries
- macOS has its own crash-reporting behavior
Avoid this by treating crash reporting as partly platform-specific.
2. Forgetting debug symbols
If you compile without debug-friendly settings, your trace may be much less useful.
Broken expectation:
g++ app.cpp -o app
Better:
g++ -g -O0 -rdynamic app.cpp -o app
3. Doing too much inside a signal handler
This is a big one. After a crash, the program may already be in a bad state.
Risky code:
void crash_handler(int sig) {
std::string msg = "crashed"; // may allocate memory
std::cout << msg << std::endl; // not signal-safe
exit(1); // not ideal here
}
Safer direction:
Comparisons
| Approach | What it does | Pros | Cons |
|---|---|---|---|
backtrace() in-process | Captures current call stack inside the program | Simple, quick to add, useful for debugging | Limited portability, not fully safe in crash context |
| Core dump | OS writes process memory/state to a file | Very detailed for postmortem debugging | Larger files, setup required, harder for end users |
| External crash reporter | Separate system or library records crashes | More robust in production | More setup and platform-specific work |
| Logging only | Writes recent app events before crash | Easy context for reproduction | Does not show actual call stack |
backtrace_symbols() vs backtrace_symbols_fd()
Cheat Sheet
Quick reference
Linux/GCC stack trace basics
#include <execinfo.h>
void* frames[64];
int size = backtrace(frames, 64);
backtrace_symbols_fd(frames, size, STDERR_FILENO);
Useful headers
#include <execinfo.h>
#include <signal.h>
#include <unistd.h>
#include <cstdlib>
Typical signals to consider
SIGSEGV— invalid memory accessSIGABRT— program abortedSIGFPE— arithmetic faultSIGILL— illegal instruction
Helpful compile flags
g++ -g -O0 -rdynamic file.cpp -o app
Key rules
FAQ
How do I get a stack trace in C++ on Linux with GCC?
A common approach is to use backtrace() and backtrace_symbols_fd() from execinfo.h, usually inside a signal handler for crashes like SIGSEGV.
Why are my function names missing from the stack trace?
You may need to compile with -g and -rdynamic. Without symbols, the trace may show only raw addresses.
Can I use the same stack trace code on Windows and macOS?
Not reliably. The overall idea is the same, but the APIs and crash-reporting mechanisms differ by platform.
Does a C++ try/catch block handle segmentation faults?
No, not in the normal portable sense. try/catch handles C++ exceptions, not low-level crash signals like SIGSEGV.
Is it safe to generate a stack trace after a crash?
It can work, but it is not guaranteed to be fully safe because the process may already be corrupted. Keep crash handlers minimal.
What is the difference between a stack trace and a core dump?
A stack trace is a summary of active function calls. A core dump is a much more complete snapshot of process memory and state.
Mini Project
Description
Build a small Linux/GCC C++ program that installs a crash handler, captures a stack trace when a crash happens, and writes the trace to a text file. This demonstrates the core idea behind automatic crash reporting: collect useful debugging data at failure time, then inspect or send it later.
Goal
Create a C++ program that intentionally crashes, records a stack trace to a file, and exits safely after the crash.
Requirements
- Install handlers for at least
SIGSEGVandSIGABRT. - Capture the current stack trace when a crash occurs.
- Write the stack trace to a file named
crash.log. - Trigger a crash through a clear test function.
- Compile the program with flags that improve stack trace readability.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.