assemblyx86-64bootloaderbioslegacy

Bootloader in Assembly for BIOS Legacy: Keyboard input works in .img but screen is blank; in .iso the menu appears but keyboard input is ignored


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

Problem now on iso: enter image description here

My questions:

  1. Why does the keyboard (INT 16h) work in .img but not in .iso?
  2. Why does the screen stay blank in .img, but video output works in .iso?
  3. Could this be related to xorriso, isohdpfx.bin, segment alignment, or BIOS boot expectations?

Thanks for helping.


Solution

  • 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.