2026-10-30

How to trigger signals on Linux i386 without calling a syscall

This blog post explains how to reliably trigger various signals on Linux i386 using only CPU instructions, without calling a syscall (such as kill(2)). The signals not mentioned below are harder to trigger. There are many ways to trigger the same signal; minimum-length byte sequences are shown below.

Illegal instruction == SIGILL == 4

Byte sequence: db 0fh, 0bh
32-bit disassembly: ud2

Trace/breakpoint trap == SIGTRAP == 5

Byte sequence: db 0cch
32-bit disassembly: int3

Floating point exception == SIGFPE == 8

Byte sequence: db 0d4h, 0
32-bit disassembly: aam 0

Segmentation fault == SIGSEGV == 11

Byte sequence: db 0f4h
32-bit disassembly: hlt

How to trigger a CPU exception on x86 16-bit and 32-bit protected mode

This blog post shows byte sequences in assembly to trigger various x86 CPU exceptions. The CPU must be in protected mode. The CPU may be in any ring (security level). Paging may or may not be enabled. The byte sequences work in both 16-bit protected mode (80286 CPU or later) and 32-bit protected mode (80386 CPU or later). The exceptions not mentioned below are harder to trigger. The reason why triggering an exception deliberately is useful is that some environments (such as PMODE/W) display a register dump upon an unhandled CPU exception, and that can be used for debugging. GDB (the GNU Debugger) can do the same: when you run a program, and it fails with a fatal signal caused by a CPU exception, use info registers to see the register values at the time of the signal, and use bt to see a stack trace.

Exception 0: Division Error #DE

Byte sequence: db 0d4h, 0
16-bit and 32-bit disassembly: aam 0

Exception 1: Debug #DB

Byte sequence: db 9ch, 58h, 80h, 0cch, 1, 50h, 9dh
16-bit disassembly: pushf; pop ax; or ah, 1; push ax; popf
32-bit disassembly: pushf; pop eax; or ah, 1; push eax; popf

This needs a valid stack. This sets TF (Trap Flag) to 1.

Exception 3: Breakpoint #BP

Byte sequence: db 0cch
16-bit and 32-bit disassembly: int3

Exception 4: Overflow #OF

Byte sequence: db 0b0h, 7fh, 0feh, 0c0h, 0ceh
16-bit and 32-bit disassembly: mov al, 127; inc al; into

This triggers a signed integer overflow by setting OF (Overflow Bit) := 1, and doing an into.

Exception 5: Bound Range Exceeded #BR

Byte sequence: db 31h, 0c0h, 50h, 50h, 40h, 89h, 0e3h, 89h, 0e7h, 36h, 62h, 7
16-bit disassembly: xor ax, ax; push ax; push ax; inc ax; mov bx, sp; mov di, sp; bound ax, [ss:bx]
32-bit disassembly: xor eax, eax; push eax; push eax; inc ax; mov ebx, esp; mov edi, esp; bound eax, [ss:edi]

This needs a valid stack. This triggers a signed integer out-of-bounds in the bound instruction.

Exception 6: Invalid Opcode #UD

Byte sequence: db 0fh, 0bh
16-bit and 32-bit disassembly: ud2

Exception 13 == 0dh: General Protection Fault #GP

Byte sequence: db 31h, 0c0h, 48h, 8eh, 0c0h
32-bit disassembly: xor eax, eax; dec eax; mov es, eax
16-bit disassembly: xor ax, ax; dec ax; mov es, ax

This doesn't trigger the exception if the LDT is full (i.e. it has limit 0ffffh), which is unlikely. LDTs typically have only a couple of entries.