1. Introduction
Stack corruption crashes are among the most destructive failures in C++ systems. They break the assumptions that make debugging possible: the backtrace becomes invalid, the crash location becomes meaningless, and the unwinder walks garbage because the metadata it depends on has been overwritten.
Crash Pattern S3 is defined by one sentence:
S3 — The crash location cannot be trusted.
This article explains how S3 behaves, how to recognize it, and how to diagnose it using a consistent workflow.
2. What Is a Stack Corruption Crash?
A stack corruption crash occurs when the stack frame is damaged: return address, frame pointer, saved registers, locals, or unwind metadata are overwritten.
This makes S3 fundamentally different from other patterns:
- S1: crash location is the bug location
- S2: backtrace is valid but misleading
- S3: backtrace is invalid or impossible
Stack corruption is a structural failure of the execution model itself.
3. How Stack Corruption Crashes Behave
S3 has a distinctive set of symptoms:
Broken or impossible backtrace
Missing frames, impossible order, unwinding into unmapped memory, or backtrace shape changing between runs.
Crash immediately after a function returns
Return address overwritten → CPU jumps into garbage.
Impossible instruction pointer
Crashes at non‑executable or random addresses (e.g., 0x41414141).
Unwinder walking garbage
Corrupted CFA, LSDA, saved registers, or unwind rules.
High sensitivity to optimization
Crash appears/disappears depending on inlining, frame pointers, LTO/PGO, compiler version.
These symptoms together form the S3 signature.
4. Root Causes Behind Stack Corruption
Most S3 crashes come from one of these mechanisms:
- Stack buffer overflow (local array overwritten)
- Use‑after‑return (pointer to stack escapes)
- Incorrect memcpy/memmove size
- Corrupted frame pointer (inline asm, ABI mismatch)
- ABI mismatch between modules (different struct layout, alignment, calling convention)
- Corrupted exception metadata (LSDA, unwind rules)
These mechanisms produce the S3 failure shape.
5. Diagnostic Workflow
5.1 Enable Frame Pointers
Stabilizes the backtrace and helps detect corruption earlier.
Flags: -fno-omit-frame-pointer -fno-optimize-sibling-calls.
5.2 Use AddressSanitizer
ASan catches the defect before the corrupted frame returns.
Flags: -fsanitize=address -fno-omit-frame-pointer -g -O1.
5.3 Use Stack Protector
Detects corruption before returning from the function.
Flags: -fstack-protector-strong.
5.4 Inspect the Corrupted Frame
Look at:
- saved return address
- saved frame pointer
- locals
- padding
- callee‑saved registers
- LSDA/unwind metadata
This reveals the shape of the corruption and narrows the search region.
5.5 Check for ABI Mismatches
Compare struct sizes, alignment, calling conventions, and compiler flags across modules.
ABI mismatches frequently cause S3 crashes at call boundaries.
5.6 Reproduce with Different Optimizations
If the crash moves or disappears, it’s almost certainly S3.
6. Examples
Example 1 — Stack Buffer Overflow
Code
std::memcpy(u.name, "this-string-is-way-too-long", 32); // overflow
Symptom
Crash in unrelated code — classic S3.
Diagnostic Path
- Inspect locals → struct fields corrupted
- Inspect saved frame pointer → 0x41414141 (garbage)
- Inspect return address → still valid → delayed crash
Root Cause
Overflow overwrote u.id, caller’s frame, and possibly saved frame pointer.
Example 2 — Use‑After‑Return (ASan)
Code
char buf[16];
return buf; // invalid
Symptom
Crash later in unrelated code — typical UAR.
Diagnostic Path
ASan reports stack‑use‑after‑return at the exact instruction where the invalid read occurs.
Root Cause
Pointer to dead stack frame escapes; memory reused; crash delayed.
Example 3 — ABI Mismatch
Code
// module A (compiled with -O2, default packing)
struct Config {
int id;
char flag;
};
// module B (compiled with #pragma pack(1) or different compiler)
#pragma pack(push, 1)
struct Config {
int id;
char flag;
};
#pragma pack(pop)
Symptom
Crash inside a harmless function; arguments contain garbage.
Diagnostic Path
- Compare struct sizes: 8 bytes vs 5 bytes → mismatch
- Inspect disassembly → caller and callee disagree on how struct is passed
Root Cause
Packed struct + type mismatch across modules → corrupted frame at call boundary.
7. When It’s Not Stack Corruption
- S1: backtrace clean and stable
- S2: backtrace valid but misleading
- S3: backtrace broken or impossible
Stack corruption is the only pattern where the crash location itself is meaningless.
8. Summary
Stack corruption crashes look chaotic, but they follow a predictable shape.
The backtrace lies, the crash location is meaningless, and the failure often appears far from the real defect — but the corrupted frame always tells the truth.
9. Key Takeaways
- If the backtrace looks impossible → S3.
- The corrupted frame is the only reliable evidence.
- Corruption patterns map directly to diagnostic branches.
- ASan catches the defect before the crash.
- ABI mismatches are real stack‑corruption bugs.
- Fix the corrupting function → crash disappears completely.
Top comments (0)