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 |
|
divide by zero ( |
|
memory access violation (unmapped, wrong protection) |
|
misaligned access where strict alignment is enforced |
|
breakpoint instruction ( |
|
hardware breakpoint |
|
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#
I/O for
syscallABI andread/write/open/exit_group.Control flow for
jz/js/jaeand the flag conventions.Testing for
gdbscripting around faults.