Errors#

Assembly has no exception keyword and no error type. Failure modes split three ways: hardware faults the CPU raises and the kernel turns into signals (SIGSEGV, SIGILL, SIGFPE, SIGBUS), syscall errors the kernel reports through errno (a register value the operator inspects after the syscall), and logical errors the operator surfaces with sanity-check sequences (compare, conditional jump to a panic).

/io`.

CPU faults#

Synchronous interrupts the CPU raises on a violated invariant. The kernel forwards each one to the process as a signal.

Fault

Signal

invalid opcode

SIGILL

divide by zero (div / idiv)

SIGFPE

memory access violation (unmapped, wrong protection)

SIGSEGV

misaligned access where strict alignment is enforced

SIGBUS

breakpoint instruction (int3)

SIGTRAP

hardware breakpoint

SIGTRAP

The operator who debugs a faulting binary uses gdb to see which instruction caused the signal; signal plus info registers plus x/i $pc reveals the exact offset and opcode.

Syscall errno#

x86-64 Linux syscalls return through rax. A negative return between -1 and -4095 is the negated errno; anything else is a success result.

; open("/etc/shadow", O_RDONLY)
mov     rax, 2                  ; syscall number: open
lea     rdi, [rel path]
mov     rsi, 0                  ; O_RDONLY
syscall
; on failure, rax = -EACCES (or similar)
test    rax, rax
js      .err                    ; signed less than zero = error
mov     [rel fd], rax
jmp     .done

.err:
    neg     rax                  ; rax = errno
    ; ... report

.done:

ARM64 Linux syscalls return through x0 with the same convention.

Memorise the most common errnos by integer value (-2 ENOENT, -13 EACCES, -22 EINVAL, -12 ENOMEM).

Sanity-check idioms#

Insert compare / jump sequences before any operation that would crash on bad input.

; null check before deref
test    rdi, rdi
jz      .null

; bounds check before array index
cmp     rsi, rdx                ; index vs length
jae     .out_of_bounds          ; unsigned >=

; aligned-pointer check before SSE load
test    rdi, 0xf                ; low 4 bits must be zero
jnz     .misaligned

A jump to a single .panic label centralises the abort path; the panic block prints a message via the write syscall and calls exit_group.

.panic:
    mov     rax, 1              ; write
    mov     rdi, 2              ; stderr
    lea     rsi, [rel panic_msg]
    mov     rdx, panic_len
    syscall
    mov     rax, 231            ; exit_group
    mov     rdi, 1
    syscall

ud2 (forced fault)#

The ud2 instruction is guaranteed to raise SIGILL; compilers emit it in unreachable paths (the equivalent of Rust’s unreachable!()).

; should never run; if it does, fault immediately
ud2

Drop a ud2 after an unconditional jump in hand-written code so the disassembler does not chain the next function into this one if alignment fails.

int3 (debug trap)#

int3 is a one-byte breakpoint instruction (0xCC). The operator inserts it manually during development; gdb stops on it automatically.

int3                            ; debugger stops here

GDB’s catch syscall and catch signal stop on syscall returns and signal delivery respectively.

Stack canaries#

A compiler-inserted check that catches stack-buffer-overflow exploits. The operator sees this in disassembly as a __stack_chk_guard load at function entry and a __stack_chk_fail branch at exit.

; prologue
mov     rax, qword [fs:0x28]    ; load canary
mov     qword [rbp - 8], rax

; ... body ...

; epilogue
mov     rax, qword [rbp - 8]
sub     rax, qword [fs:0x28]
jne     __stack_chk_fail        ; abort on mismatch

Signals (catching faults)#

To recover from SIGSEGV / SIGBUS (rare, mostly in exploit / fuzzing harnesses), the operator installs a signal handler via rt_sigaction (syscall number 13 on x86-64 Linux). The handler runs on a separate signal stack (sigaltstack).

The default behaviour for these signals is termination; the operator who wants to keep running after a fault needs the handler.

References#