I'm developing a small bootloader project in Assembly for BIOS Legacy (real mode). It displays a disk selection menu using BIOS interrupts (INT 10h for video, INT 16h for keyboard).
However, I'm experiencing inconsistent behavior between the .img and .iso formats:
Current behavior: .img (512 bytes, tested via QEMU or written directly to USB): The screen stays black (nothing is displayed). But keyboard input works: keys are correctly detected using INT 16h.
.iso (created using xorriso and isohdpfx.bin): The menu is displayed correctly (INT 10h works). But the keyboard does not respond (INT 16h seems to fail silently).
How I'm generating the files:
nasm -f bin -o boot.img boot.asm
xorriso -as mkisofs \
-o boot.iso \
-b boot.img \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
-isohybrid-mbr isohdpfx.bin \
-V BOOTDISK .
First code:
start:
cli ; desabilita interrupções
xor ax, ax
mov ss, ax ; SS = 0x0000
mov sp, 0x7BFF ; SP = 0x7BFF (pilha logo abaixo do código em 0x7C00, evitando overlap)
mov ax, 0x07C0 ; segmento base para dados (0x07C0 * 16 = 0x7C00)
mov ds, ax
mov es, ax
sti
; limpar buffer de teclado (descarta teclas pendentes)
.clear_buffer:
mov ah, 01h
int 16h
jz .done_clear
mov ah, 00h
int 16h
jmp .clear_buffer
.done_clear:
; agora tudo limpo, pode iniciar normalmente
mov byte [selected], 0
call print_menu
.wait_key:
; Aguarda tecla
mov ah, 0
int 0x16
cmp ah, 0x48 ; seta para cima
je .up
cmp ah, 0x50 ; seta para baixo
je .down
cmp al, 13 ; Enter
je .enter
jmp .wait_key
.up:
cmp byte [selected], 0
je .wait_key
dec byte [selected]
call print_menu
jmp .wait_key
.down:
cmp byte [selected], 1
je .wait_key
inc byte [selected]
call print_menu
jmp .wait_key
.enter:
mov al, [selected]
cmp al, 0
je .selected_disk1
cmp al, 1
je .selected_disk2
jmp $
.selected_disk1:
call clear_screen
mov si, msg_disk1
call print_string
jmp $
.selected_disk2:
call clear_screen
mov si, msg_disk2
call print_string
jmp $
print_menu:
call clear_screen
mov si, msg_title
call print_string
mov al, [selected]
mov bl, al
cmp bl, 0
jne .no_arrow0
mov ah, 0x0E
mov al, '>'
int 0x10
jmp .print_item0
.no_arrow0:
mov ah, 0x0E
mov al, ' '
int 0x10
.print_item0:
mov si, menu_item1
call print_string
mov ah, 0x0E
mov al, 0x0D
int 0x10
mov al, 0x0A
int 0x10
cmp bl, 1
jne .no_arrow1
mov ah, 0x0E
mov al, '>'
int 0x10
jmp .print_item1
.no_arrow1:
mov ah, 0x0E
mov al, ' '
int 0x10
.print_item1:
mov si, menu_item2
call print_string
ret
print_string:
lodsb
or al, al
jz .done
mov ah, 0x0E
int 0x10
jmp print_string
.done:
ret
clear_screen:
mov ah, 0x06
mov al, 0
mov bh, 0x07
mov cx, 0
mov dx, 0x184F
int 0x10
mov ah, 2
mov bh, 0
mov dx, 0
int 0x10
ret
; Dados
msg_title db 'Selecione um disco para continuar:', 0x0D, 0x0A, 0
menu_item1 db ' Disco 1', 0
menu_item2 db ' Disco 2', 0
msg_disk1 db 'Voce selecionou o Disco 1.', 0
msg_disk2 db 'Voce selecionou o Disco 2.', 0
selected db 0
; Boot signature
times 510-($-$$) db 0
dw 0xAA55
Code now:
bits 16
start:
cli
xor ax, ax
mov ss, ax
mov sp, 0x7C00 ; Stack word-aligned
mov ax, 0x07C0
mov ds, ax
mov es, ax
sti
; limpar buffer de teclado
.clear_buffer:
mov ah, 01h
int 16h
jz .done_clear
mov ah, 00h
int 16h
jmp .clear_buffer
.done_clear:
mov byte [selected], 0
call print_menu
.wait_key:
xor ah, ah
int 16h
cmp ah, 0x48
je .up
cmp ah, 0x50
je .down
cmp al, 13
je .enter
jmp .wait_key
.up:
cmp byte [selected], 0
je .wait_key
dec byte [selected]
call print_menu
jmp .wait_key
.down:
cmp byte [selected], 1
je .wait_key
inc byte [selected]
call print_menu
jmp .wait_key
.enter:
mov al, [selected]
cmp al, 0
je .selected_disk1
cmp al, 1
je .selected_disk2
jmp $
.selected_disk1:
call clear_screen
mov si, msg_disk1
call print_string
jmp $
.selected_disk2:
call clear_screen
mov si, msg_disk2
call print_string
jmp $
print_menu:
call clear_screen
mov si, msg_title
call print_string
mov al, [selected]
mov bl, al
cmp bl, 0
jne .no_arrow0
mov ah, 0x0E
mov al, '>'
mov bh, 0
int 10h
jmp .print_item0
.no_arrow0:
mov ah, 0x0E
mov al, ' '
mov bh, 0
int 10h
.print_item0:
mov si, menu_item1
call print_string
mov ah, 0x0E
mov al, 0x0D
mov bh, 0
int 10h
mov al, 0x0A
int 10h
cmp bl, 1
jne .no_arrow1
mov ah, 0x0E
mov al, '>'
mov bh, 0
int 10h
jmp .print_item1
.no_arrow1:
mov ah, 0x0E
mov al, ' '
mov bh, 0
int 10h
.print_item1:
mov si, menu_item2
call print_string
ret
print_string:
lodsb
or al, al
jz .done
mov ah, 0x0E
mov bh, 0
int 10h
jmp print_string
.done:
ret
clear_screen:
mov ah, 06h
mov al, 0
mov bh, 07h
mov cx, 0
mov dx, 184Fh
int 10h
mov ah, 02h
mov bh, 0
mov dx, 0
int 10h
ret
; Dados
msg_title db 'Selecione um disco para continuar:', 0x0D, 0x0A, 0
menu_item1 db ' Disco 1', 0
menu_item2 db ' Disco 2', 0
msg_disk1 db 'Voce selecionou o Disco 1.', 0
msg_disk2 db 'Voce selecionou o Disco 2.', 0
selected db 0
times 510 - ($ - $$) db 0
dw 0xAA55
Images:
.iso: enter image description here
.img: enter image description here
My questions:
Thanks for helping.
TLDR Either remove -boot-info-table
switch, or make your bootloader compatible with it (see https://wiki.osdev.org/El-Torito#A_BareBones_Boot_Image_with_Boot_Information_Table).
Now, a long version: here is how you could've debugged this yourself.
First, let's load up Bochs with its debugger, set it up for loading your ISO, set breakpoint on 0x7c00 and run it so we could debug and see why it is misbeving. We see something bad right away:
0x7C00 FA cli
0x7C01 31C0 xor ax, ax
0x7C03 8ED0 mov ss, ax
0x7C05 8C007C mov sp, 0x7C00
0x7C08 1000 adc byte ptr ds:[bx+si], al
0x7C0A 0000 add byte ptr ds:[bx+si], al
The code is corrupted since the very start. Let's see memory dump... Indeed it is. Addresses 0x7C08-0x7C3F are overwritten. So your code is not an issue, at least not the first issue to deal with. BIOS doesn't boot your ISO the way you wanted it to. Let's see ISO itself, shall we?
We see hybrid isolinux MBR isohdpfx.bin at 0x0000. This is not interesting to us because it is not used for ISO 9660 boot (it is only for when the ISO image is used for floppy/hdd/usb boot). Let's look at ISO volume descriptors starting sector 16 (offset 0x8000). We see a primary volume descriptor, we'll skip that. Next descriptor is at sector 17 (0x8800), and it's a El Torito boot record, pointing to a boot catalog at sector 0x21. This is that we are interested in. Just in case, let's look at third descriptor at 0x9000 - and that's a terminating descriptor.
So let's look at boot catalog:
So sector 0x22 (0x11000) which should contain our boot image...
00011000: fa 31 c0 8e d0 bc 00 7c 10 00 00 00 22 00 00 00 .1.....|...."...
00011010: 00 02 00 00 e2 27 cd 47 00 00 00 00 00 00 00 00 .....'.G........
00011020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00011030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00011040: 0e 63 01 e8 35 00 eb dc 80 3e 63 01 01 74 d5 fe .c..5....>c..t..
...
00011150: 65 63 69 6f 6e 6f 75 20 6f 20 44 69 73 63 6f 20 ecionou o Disco
It is a pre-corrupted boot image! Why is what? Obviously xorriso did this (no one else could, right?). It put some sort of data into there; namely, 56 bytes starting at offset 8. Quick look at xorriso man(Bootable ISO images section) gives us a clue: that's a Boot Information Table, and it gives boot loader correct offsets to certain data in ISO image (so the boot loader doesn't have to do all the reading and parsing itself). xorriso would patch that in as requested by -boot-info-table
switch. Remove that switch, and everything works as intended.