BITS 16
CPU 386
ORG 7c00h
%define IVT_OFFSET(n) (n*4) ; n'th entry in the IVT, offset part
%define IVT_SEGMENT(n) ((n*4)+2) ; segment selector part
DIV8_OPCODE equ 0xf6
DIV16_OPCODE equ 0xf7
start:
cli
xor ax, ax
mov ds, ax
; IRQ0 : division by zero
mov word [ds:IVT_OFFSET(0)], div0_handler
mov word [ds:IVT_SEGMENT(0)], 0
; IRQ9 : keyboard service needed
mov word [ds:IVT_OFFSET(9)], keyb_handler
mov word [ds:IVT_SEGMENT(9)], 0
sti
haltcpu:
hlt
jmp haltcpu
; IN : al - char to be written
bios_writechar:
pusha
mov ah, 0x0e
xor bx, bx
int 10h
popa
ret
; write a NUL-terminated string using int 10h
; IN : bx - address of the string
bios_puts:
push ax
.loop:
mov al, [bx]
test al, al
jz .end
call bios_writechar
add bx, 1
jmp .loop
.end:
pop ax
ret
; division by zero handler
div0_handler:
mov ax, sp ; save the stack pointer - important!
pusha
push ax
; the top of the stack should now point to the instruction, which raised
; the exception. we know that it's DIV, since it's the only one that can
; report an IRQ0. the opcode is always followed by a ModRM byte, which in
; turn can have another one or two displacement bytes following it. the job
; of this handler is to skip over the bad instruction and continue execution
; in a normal way. if the IP gets popped off the stack unedited, the CPU
; will fall into an endless loop.
mov bx, div_text
call bios_puts
mov bx, ax
mov di, [bx] ; get the IP of the instruction
mov al, [di] ; get the opcode
cmp al, DIV8_OPCODE
sete bl
cmp al, DIV16_OPCODE
sete cl
or cl, bl
jz .unk_opcode ; no idea what that is
; we're sure that it's a DIV, now get the ModRM
mov bl, [di+1]
mov al, bl
and al, 0b11_000000 ; mask the Mod field in bl
jz .checkRM ; Mod = 00
cmp al, 0b11_000000
je .skip2 ; Mod = 11
cmp al, 0b10_000000
je .skip4 ; Mod = 10
cmp al, 0b01_000000
je .skip3 ; Mod = 01
.checkRM:
; if Mod was 00, we need to check if RM=110, in which case we have to skip
; 4 bytes - otherwise, we skip 2
mov al, bl
and al, 0b00_000_111
cmp al, 0b00_000_110
jne .skip2
.skip4:
mov si, 4
jmp .end
.skip3:
mov si, 3
jmp .end
.skip2:
mov si, 2
jmp .end
.unk_opcode:
mov si, 1 ; just advance the pointer by 1
.end:
pop ax
mov bx, ax
add di, si
mov [bx], di
popa
iret
; keyboard handler
keyb_handler:
pusha
.spin:
in al, 0x64
and al, 1
jz .spin
in al, 0x60
mov bx, keyb_text
call bios_puts
mov al, 0x20
out 0x20, al
xor dx, dx
div dx
popa
iret
keyb_text db 'Keyboard handler',0x0d,0x0a,0
div_text db 'Division by zero',0x0d,0x0a,0
TIMES 510-($-$$) db 90h ; NOP
dw 0xaa55