Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

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 (0xC00000FD on 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, or INT_MIN / -1, on x86-64 (ARM64 just returns 0).
  • SIGILL → an instruction this CPU doesn't have (missing feature check) or a deliberate ud2.
  • SIGTRAP → a breakpoint or trap instruction (int3, brk): a debugger, __builtin_trap(), or Apple's allocator catching a bad free.
  • SIGABRT → someone called abort() on purpose: a failed assert, 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.

The common causes and their signals on all three systems.

Bad addresses

A fault the kernel can repair is invisible; one it can't becomes SIGSEGV.

The address a program tried to use when a page fault happened, which the processor records for the operating system.

Example

A null Account* read at offset 8 faults at address 0x8, not 0.
A pointer that still holds the address of something that has already been freed or has gone out of scope.

Runaway recursion runs into the guard page below the stack.

A strip of forbidden addresses just past the end of the stack, placed there so running off the end is caught instead of silently trampling other memory.

Bad instructions and bad arithmetic

x86-64 idiv stops the program on a zero divisor; ARM64 sdiv returns 0.

An AVX2 instruction on a CPU without AVX2 is SIGILL; check before you call.

Where misaligned accesses come from, and when one becomes SIGBUS instead of just slow.

Deliberate stops

The signal a program sends itself when a library check decides it must stop at once, for example a failed assert or a detected heap problem.

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:

  1. If slot = G, the function restores its saved registers and returns.
  2. If slot ≠ G, a failure handler ends the process without returning (__stack_chk_fail on 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.

A breakpoint made by temporarily writing a special trap instruction over the program's code at the chosen spot.

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.

Debugging the crash after the process is gone.