You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何OS加载器在Bochs中正常运行却无法在QEMU中工作?

OS加载器在Bochs正常但QEMU无法工作的问题排查

我开发的OS加载器在Bochs模拟器中运行完全正常,但在QEMU中无法正常工作。通过GDB调试发现,QEMU环境下MBR未能将OS加载器正确读取到0x900内存地址,但IO端口操作无异常。以下是相关代码、配置及排查方向:

MBR代码

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

内核加载器代码

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 $

注:print代码与GDT结构已确认无问题

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 $@

Bochs配置

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

QEMU启动命令

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

版本信息

$ 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

排查方向及解决方案

1. 磁盘几何参数不匹配

Bochs配置中明确指定了磁盘参数cylinders=121,heads=16,spt=63,但QEMU启动时未指定这些参数,QEMU会自动推断磁盘几何结构,可能与Bochs的解析逻辑不一致,导致LBA地址映射错误。

解决方法:在QEMU命令中添加磁盘几何参数:

$ qemu-system-i386 \
    -name 'guest=aszswaz' \
    -m 32M \
    -boot 'menu=on,strict=on' \
    -drive 'file=build/aszswaz.img,format=raw,cyls=121,heads=16,secs=63'

2. 磁盘就绪等待逻辑缺陷

当前代码中等待磁盘就绪的逻辑是and al, 0x88后判断等于0x08,但正确的磁盘就绪判断应该先检查忙位(bit7)是否为0,再检查就绪位(bit3)是否为1。QEMU的磁盘状态变化更快,可能导致当前逻辑无法正确触发。

修改后的等待逻辑:

.read_disk_wait:
    in al, dx
    test al, 0x80 ; 检查忙位,为1表示磁盘忙
    jnz .read_disk_wait
    test al, 0x08 ; 检查就绪位,为1表示磁盘就绪
    jz .read_disk_wait

3. 内存限制差异

Bochs设置了32M内存,而QEMU仅设置1M内存。虽然0x900在1M范围内,但BIOS在不同内存配置下的初始化行为可能有差异,导致目标内存区域被覆盖。

解决方法:将QEMU的内存调整为32M,即-m 32M。

4. 加载器文件大小验证

确认os-loader.bin的大小是否正好是4个扇区(2048字节),如果实际大小不足,QEMU读取时会获取到空数据。

验证命令:

wc -c build/os-loader.bin

如果大小不足2048字节,可在加载器代码末尾添加times 2048-($-$$) db 0填充到4个扇区。

内容的提问来源于stack exchange,提问作者aszswaz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 12:45:49