assemblyx86qemubochs

Why does my OS loader work fine in bochs but not in qemu?


Here is my MBR code:

config/boot.asm:

%ifndef BOOT_H
%define BOOT_H

; Kernel loader address.
LOADER_BASE_ADDR: equ 0x900
; The logical sector address of the kernel loader on the disk.
LOADER_START_SECTOR: equ 0x2
; The number of sectors occupied by the kernel loader.
LOADER_SECTORS: equ 4

%endif

mbr.asm:

section MBR vstart=0x7C00
    jmp main

%include "config/boot.asm"

[bits 16]
main:
    mov ax, 0
    mov ss, ax
    mov ds, ax
    mov fs, ax
    mov es, ax

    mov sp, $$
    push bp
    mov bp, sp

    call init
    ; Read starting sector from disk.
    call read_disk

    ; Started supporting OS loader.
    jmp LOADER_BASE_ADDR

init:
    push bp
    mov bp, sp

    ; Clean up BIOS output.
    mov ax, 0xB800
    mov es, ax
    mov cx, 2000
    mov di, 0
    clean_screen:
        mov word [es: di], 0
        add di, 2
        loop clean_screen

    leave
    ret

; Read OS loader from disk.
read_disk:
    push bp
    mov bp, sp

    ; Set the number of sectors to read.
    mov dx, 0x1F2
    mov ax, LOADER_SECTORS
    out dx, al

    ; Set the logical sector address.
    mov ax, LOADER_START_SECTOR
    mov dx, 0x1F3
    out dx, al
    mov cl, 8
    mov dx, 0x1F4
    shr ax, cl
    out dx, al
    shr ax, cl
    mov dx, 0x1F5
    out dx, al
    shr ax, cl
    and al, 0x0F
    or al, 0xE0
    mov dx, 0x1F6
    out dx, al

    ; Send a read command.
    mov dx, 0x1F7
    mov al, 0x20
    out dx, al

    ; Wait for the hard drive to prepare the data.
    mov dx, 0x1F7
    .read_disk_wait:
        nop
        in al, dx
        and al, 0x88
        cmp al, 0x08
        jnz .read_disk_wait

    ; The hard disk is ready to start reading data.
    mov di, LOADER_BASE_ADDR
    mov ax, LOADER_SECTORS
    mov dx, 256
    mul dx
    mov cx, ax
    mov dx, 0x1F0
    .read_disk_read:
        in ax, dx
        mov [di], ax
        add di, 2
        loop .read_disk_read

    leave
    ret

times 510-($-$$) db 0
db 0x55, 0xAA

Here is the code of my kernel loader:

section CORE_LOADER vstart=LOADER_BASE_ADDR
jmp main

%include "config/boot.asm"
%include "config/gdt.asm"
%include "print.asm"

MESSAGE: db "Hello World", 0
STRLEN: equ $ - MESSAGE

[bits 16]
main:
    mov sp, $$
    push bp
    mov bp, sp

    ; Load GDT, turn on protected mode.
    in al, 0x92
    or al, 2
    out 0x92, al
    lgdt [GDT_PTR]
    mov eax,cr0
    or eax, 1
    mov cr0, eax

    jmp dword SELECTOR_CODE:p_mode_start

[bits 32]
p_mode_start:
    mov esp, $$
    mov ax, SELECTOR_DATA
    mov ds, ax
    mov ss, ax
    mov gs, ax
    mov es, ax

    ; clean screen
    call clean_screen

    ; Output "Hello World".
    push MESSAGE
    call print
    add esp, 8

    jmp $

I didn't give the code for the "print" and the structure of the "GDT", but I can guarantee that there are no problems with them.

Here is my Makefile:

SOURCE=src
BUILD_DIR=build
ASSEMBLER=@nasm -I $(SOURCE)
DD=@dd

OS_IMG=$(BUILD_DIR)/aszswaz.img
MBR=$(BUILD_DIR)/mbr.bin
OS_LOADER=$(BUILD_DIR)/os-loader.bin

IMG_SECTOR=60

all: $(BUILD_DIR) \
    $(OS_IMG)

$(BUILD_DIR):
    @mkdir -p $@

.PHONY: clean
clean:
    @rm -rf $(BUILD_DIR)

# Build an OS image.
$(OS_IMG): $(MBR) $(OS_LOADER)
    $(DD) if=/dev/zero of=$@ bs=1M count=$(IMG_SECTOR) >> /dev/null 2>&1
    $(DD) if=$(MBR) of=$@ bs=512 count=1 conv=notrunc >> /dev/null 2>&1
    $(DD) if=$(OS_LOADER) of=$@ bs=512 seek=2 conv=notrunc >> /dev/null 2>&1

$(MBR): $(SOURCE)/mbr.asm $(SOURCE)/print.asm $(SOURCE)/config/boot.asm
    $(ASSEMBLER) $< -o $@

$(OS_LOADER): $(SOURCE)/os-loader.asm $(SOURCE)/print.asm $(SOURCE)/config/boot.asm $(SOURCE)/config/gdt.asm
    $(ASSEMBLER) $< -o $@

Here is my bochs configuration:

megs: 32

# Set BIOS and vga.
romimage: file=/usr/share/bochs/BIOS-bochs-latest
vgaromimage: file=/usr/share/bochs/VGABIOS-lgpl-latest
boot: disk
log: bochs.log
mouse: enabled=0
keyboard: keymap=/usr/share/bochs/keymaps/x11-pc-us.map
# Set up the hard disk.
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata0-master: type=disk, path=build/aszswaz.img, mode=flat, cylinders=121, heads=16, spt=63

Here is my qemu configuration:

$ qemu-system-i386 \
    -name 'guest=aszswaz' \
    -m 1M \
    -boot 'menu=on,strict=on' \
    -drive 'file=build/aszswaz.img,format=raw'

Here are my bochs and qemu versions:

$ bochs --help
========================================================================
                        Bochs x86 Emulator 2.7
              Built from SVN snapshot on August  1, 2021
                Timestamp: Sun Aug  1 10:07:00 CEST 2021
========================================================================
...
$ qemu-system-i386 -version
QEMU emulator version 7.1.0
Copyright (c) 2003-2022 Fabrice Bellard and the QEMU Project developers

I checked the memory at 0x900 through GDB, and the MBR running in qemu doesn't seem to be reading the OS loader normally, but the IO port doesn't give any exceptions. But this works fine in bochs.


Solution

  • Following Brendan's suggestion, I tried to use the BIOS to read the OS loader, the modified MBR code is as follows:

    section MBR vstart=0x7C00
        jmp main
    
    %include "config/boot.asm"
    %include "print16.asm"
    %include "config/memory-management.asm"
    
    DISK_READ_ERROR: db "OS loader read failed, the error code is: 0x", 0
    
    [bits 16]
    main:
        mov ax, 0
        mov ss, ax
        mov ds, ax
        mov fs, ax
        mov es, ax
        mov sp, $$
    
        call clean_screen
    
        ; Read OS loader via BIOS interrupt.
        ; Set the function number, 2 means read sector.
        mov ah, 2
        ; Set the number of sectors to read.
        mov al, LOADER_SECTORS
        ; Set the cylinder.
        mov ch, 0
        ; Set the head.
        mov dh, 0
        ; Set sector.
        mov cl, LOADER_START_SECTOR
        ; Set the drive letter, 0 ~ 0x7F is floppy disk, 0x80 ~ 0xFF is hard disk.
        mov dl, 0x80
        ; Set the destination address.
        mov bx, LOADER_BASE_ADDR
        int 0x13
        ; If the disk read error, the BIOS will set the CF of the flags register to 1, and AH is the error code.
        jc disk_error
    
        jmp LOADER_BASE_ADDR
    
    disk_error:
        mov [REGISTER_AX], ax
        mov ax, DISK_READ_ERROR
        call print
        mov ax, [REGISTER_AX]
        call print_hex
        jmp $
    
    times 510-($-$$) db 0
    db 0x55, 0xAA
    

    Another thing to note is that interrupts need to be disabled using the cli command before entering the protection, otherwise in qemu this will result in an infinite restart of the computer.