CONCEPT Cited by 1 source
Stack unwinding¶
Definition¶
Stack unwinding is the runtime operation of walking backwards through a thread's chain of function calls, starting from the current stack pointer and following saved frame / return-address links to reach every parent frame up to the call stack's root.
It is foundational to several runtime features:
- Garbage collection — the GC walks goroutine stacks to
locate live references to heap-allocated objects (in Go:
runtime.scanstack). - Panic recovery —
recover()runs deferred functions by unwinding frames until it finds a matchingdefer. - Traceback generation — printing a stack trace
(crash report,
runtime.Stack(...)) requires unwinding every active frame. - Exception handling (C++, Rust panics) — the same operation in other runtimes.
The core invariant: sp must be valid¶
Unwinders read the stack pointer sp and dereference it to
locate the calling function. The runtime assumes sp points
to a consistent frame state: return addresses, saved
frame-pointer, and saved registers are at the offsets the
calling convention prescribes.
If sp is partially adjusted — e.g. mid-function-epilogue
on an ISA where stack-pointer adjustment is split across
multiple opcodes — the unwinder reads "the middle of the
stack" as if it were a frame header. The data it reads is
meaningless when interpreted as a return address. Two failure
modes result:
- Return address is null → unwinder aborts with
traceback did not unwind completely. In Go:finishInternalthrows afatal error. - Return address is non-zero but points to non-code →
unwinder assumes the goroutine is currently running and
attempts to access scheduler state via
m. In Go: a dereference ofm.incgoat offset0x118→ SIGSEGV.
Canonical wiki instance: sources/2025-10-08-cloudflare-we-found-a-bug-in-gos-arm64-compiler.
Triggers for unwinding during a bug's exposure window¶
What makes a stack-pointer invariant violation so dangerous is that it only needs to coincide with a later unwinding to crash. Any of the following can trigger the unwinder:
- GC stack scanning (
scanstack) — any time a GC cycle runs. - Preemption for preempt-at-safe-point (concepts/async-preemption-go in Go 1.14+).
- Stack growth — the runtime copies the stack and must rewrite return addresses.
- A
panic()→recover()chain walking deferred functions. - User-initiated
runtime.Stack(...)or profiler sampling.
In the Cloudflare 2025-10 bug, GC's stack scan was the most common crash trigger — the service had frequent GC cycles, and each one walked every goroutine stack.
Compile-time obligation¶
The compiler must emit code such that no preemption
boundary (any instruction boundary under async preemption)
leaves sp in a partially-adjusted state that the runtime can
observe. See patterns/preemption-safe-compiler-emit.
Seen in¶
- sources/2025-10-08-cloudflare-we-found-a-bug-in-gos-arm64-compiler
— canonical wiki instance. Stack unwinding on a
partially-adjusted
spproduced two distinct crash shapes at the unwinder's decision points.
Related¶
- concepts/async-preemption-go — the Go-specific mechanism that widens the window in which unwinding can catch an invariant violation.
- concepts/split-instruction-race-window — the failure class when SP adjustment is non-atomic.
- systems/go-runtime-scheduler — the system whose scheduler/GC/panic paths perform unwinding.