Assembly Stubs
The assembly stubs are the bridge between the CPU’s interrupt mechanism and our Rust dispatcher. Every interrupt goes through one of these before hitting Rust code.
The Standard Pattern
All exception handlers follow the same pattern:
extern rust_interrupt_dispatcher
global divide_by_zero_handler
divide_by_zero_handler:
cli ; disable interrupts during handler
pusha ; save all general purpose registers
push esp ; pointer to saved registers (InterruptFrame)
push 0 ; interrupt number
call rust_interrupt_dispatcher
add esp, 8 ; clean up both pushed arguments
popa ; restore all registers
iretd ; return from interrupt (32-bit)
cli ensures we don’t get interrupted while handling an interrupt. pusha saves all registers onto the stack. At this point esp points directly to our InterruptFrame, so we push it as the second argument. Then we push the interrupt number as the first argument and call the dispatcher.
Error Code Exceptions
Some exceptions push an error code onto the stack before the CPU-saved registers. Double fault (8), General Protection Fault (13), and Page Fault (14) all do this. We need to remove the error code before pusha so the stack layout matches our InterruptFrame:
global double_fault_handler
double_fault_handler:
cli
add esp, 4 ; remove error code pushed by CPU
pusha
push esp
push 8
call rust_interrupt_dispatcher
add esp, 8
popa
iretd
The Keyboard Handler
The keyboard handler follows the same pattern but with sti before iretd to re-enable interrupts after handling:
global kb_pic_handler
extern rust_interrupt_dispatcher
kb_pic_handler:
cli
pusha
push esp ; InterruptFrame pointer
push 33 ; keyboard = IRQ1 = interrupt 33 after PIC remapping
call rust_interrupt_dispatcher
add esp, 8
popa
sti ; re-enable interrupts
iretd
The Default Handler
Catches everything not explicitly handled:
global default_interrupt_handler
default_interrupt_handler:
cli
pusha
push esp
push 0xFF ; 255 = unknown interrupt sentinel
call rust_interrupt_dispatcher
add esp, 8
popa
iretd
Why One Extern?
Previously each assembly file had multiple extern declarations pointing to individual Rust functions. Now there’s just one:
extern rust_interrupt_dispatcher
Everything goes through this single entry point. The signal table inside the dispatcher decides what actually runs.