Every way a program can crash
A bad address, a stack overflow, a misaligned access, a divide by zero, a bad instruction, a breakpoint or trap, and a library's deliberate abort(), each with the signal or exception it produces on Linux, macOS and Windows.
Start from the signal (or exception code), then look at the faulting instruction and the faulting address.
SIGSEGV/EXC_BAD_ACCESS/0xC0000005→ a bad address: null (a small faulting address, the field offset), wild, dangling, or a write to read-only memory. A faulting address just below the stack means a stack overflow (0xC00000FDon Windows).SIGBUS→ a misaligned access where alignment is required (or macOS reporting a protection fault), or a mapped file read past its end.SIGFPE→ integer divide by zero, orINT_MIN / -1, on x86-64 (ARM64 just returns 0).SIGILL→ an instruction this CPU doesn't have (missing feature check) or a deliberateud2.SIGTRAP→ a breakpoint or trap instruction (int3,brk): a debugger,__builtin_trap(), or Apple's allocator catching a badfree.SIGABRT→ someone calledabort()on purpose: a failedassert, glibc's "double free detected", a stack canary (__stack_chk_fail). The real bug happened earlier.
In every case the crash site is where the damage was noticed, not necessarily where the bug is: read the backtrace and the registers before deciding.
The machinery
Faults, traps and aborts, and where each one resumes.
Formal. The kernel's handler returns to the address the processor saved when the exception was taken:
- Fault (a page fault, x86-64 divide error, a general protection fault): the saved address is the faulting instruction itself, which had no effect; after the kernel fixes the cause, it runs again.
- Trap (a system call, an x86-64 int3 breakpoint): the event is reported after the instruction, and execution resumes at the next instruction. (RISC-V records the address of ecall itself, and the kernel adds 4 before returning.)
- Abort (a machine check, a double fault): the state can't be trusted, there is no restart, and the program or the machine stops.
- External interrupt: taken between two instructions, unrelated to either; the program resumes at the first instruction not yet done.
In plain English. A fault means "this instruction could not run", so once the problem is fixed the same instruction is tried again. A trap means "this instruction asked to stop here", so it carries on after it. An abort means something is too broken to carry on. An interrupt just slots in between two instructions and then lets the program carry on.
Example. A store to a page that hasn't been given memory yet faults; the kernel supplies the page and the same store runs again, this time successfully. A syscall instruction traps; the kernel does the work and returns to the instruction after syscall. An integer divide by zero also faults, but re-running the division would fault again, so instead the kernel reports it to the program (Linux and macOS send SIGFPE).
Why this rule exists. Restarting the exact instruction is what lets the operating system fill memory in lazily and invisibly, while continuing after a trap is what makes a system call behave like a function call.
Which signal (or Windows exception) each hardware exception becomes.
Section
Why programs crash
The common causes and their signals on all three systems.
Bad addresses
Section
The page-fault path
A fault the kernel can repair is invisible; one it can't becomes SIGSEGV.
Example
Account* read at offset 8 faults at address 0x8, not 0.Section
Stack depth and stack overflow
Runaway recursion runs into the guard page below the stack.
Term
guard page
Bad instructions and bad arithmetic
Section
Multiply and divide
x86-64 idiv stops the program on a zero divisor; ARM64 sdiv returns 0.
Section
Dispatching on CPU features
An AVX2 instruction on a CPU without AVX2 is SIGILL; check before you call.
Section
Alignment and cache lines
Where misaligned accesses come from, and when one becomes SIGBUS instead of just slow.
Deliberate stops
Term
SIGABRT
Formal. In a function compiled with a stack protector, the prologue copies a per-process random guard value G into a canary slot placed between the frame's local buffers (at lower addresses) and its saved registers and return address (at higher addresses). Before returning, the epilogue reloads the slot and compares it with G:
- If slot = G, the function restores its saved registers and returns.
- If slot ≠ G, a failure handler ends the process without returning
(
__stack_chk_failon Linux and macOS; on Windows the comparison itself is a call to__security_check_cookie, which reports the failure).
Because the stack grows down and an overflow writes upward through consecutive addresses, any contiguous overflow of a local buffer that reaches the saved registers or the return address must overwrite the canary slot first, and is detected at step 2 unless it rewrote the slot with exactly G.
In plain English. The compiler puts a secret number between your arrays and the address the function will return to, and checks it on the way out. Writing far enough past an array to reach the return address means writing over the secret number, so the program notices and stops before the bad return happens.
Example. On Linux x86-64, clang -O1 -fstack-protector-strong
compiles a function with a local char buf[16] into a prologue that
loads the guard from fs:0x28 (thread-local storage) and stores it in a
slot above buf, and an epilogue that compares that slot with fs:0x28
and calls __stack_chk_fail if they differ. A copy that runs far enough
past the end of buf to reach the saved return address changes the slot
on the way, so the process is ended at the check, with the C library's
"stack smashing detected" message (SIGABRT), instead of returning to a
damaged address.
Why this rule exists. It says exactly what the protection covers, and so what it doesn't: the check runs only at return, only catches contiguous overwrites that cross the slot, and says nothing about heap memory, out-of-bounds reads, or locals and pointers corrupted and used before the function returns.
Example
int3 (one byte, cc) on x86-64, brk #0 on ARM64: SIGTRAP if no debugger is attached.Reading what's left
Section
Reading a crash
The signal, the faulting instruction, the faulting address and the backtrace.
Section
Core dumps and minidumps
Debugging the crash after the process is gone.