DEV Community

Wang - C++ Developer
Wang - C++ Developer

Posted on

C++ Crash Pattern S3 — Stack Corruption Crashes: How to Diagnose and Fix Them

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)