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

i386实模式Bootloader调用int 10h仅首次生效问题排查

问题:i386实模式Bootloader中int 0x10仅输出首个字符

问题背景

正在开发基于i386实模式(org 0x7C00)的Bootloader,目标是切换到保护模式。此前正常的int 0x10字符输出功能突然失效:尝试输出两个字符时,仅第一个字符显示。

核心代码

[org 0x7C00]
[bits 16]

jmp BootProcedure

BootProcedure:
    ; 关闭中断
    cli

    ; 初始化栈
    xor ax, 0x9000  ; 清零累加器(写法不规范,实际效果等同于mov ax,0x9000)
    mov ds, ax      ; 设置数据段
    mov ss, ax      ; 设置栈段
    mov sp, 0xFFFF  ; 设置栈大小
    mov ax, ax      ; 空操作

    ; 初始化磁盘加载器
    call DiskReadLoader

    ; 初始化GDT
    lgdt [GDTDescriptor]

    ; 启用A20线
    call GetA20State
    test ax, ax

    jnz .a20_done   ; 如果AX不为0,跳转到.a20_done

    in al, 0x92     ; 读取控制端口A到AL
    or al, 0x02     ; 设置A20启用位
    out 0x92, al    ; 写回控制端口A

    .a20_done:
        ; 初始化保护模式
        mov eax, cr0    ; 将CR0寄存器值读入EAX
        or eax, 0x01    ; 设置保护模式启用位
        mov cr0, eax    ; 将修改后的值写回CR0

        ; 进入保护模式
        jmp 0x08:ProtectedModeEntry

%include "Boot/Stage1/GDT.asm"
%include "Boot/Stage1/GetA20State.asm"
%include "Boot/Stage1/ProtectedModeEntry.asm"
%include "Boot/Stage1/DiskReadLoader.asm"

times 510 - ($ - $$) db 0x00
dw 0xAA55

DiskReadLoader:
    ; 保存寄存器到栈
    push ax
    push bx
    push cx
    push dx
    push es

    mov ax, 0x1000  ; 加载目标段
    mov es, ax      ; 从0x1000地址开始加载扇区
    xor bx, bx      ; 偏移量=0

    ; 重试计数器
    mov si, 3

    ; 输出'E'(正常工作)
    mov ah, 0x0E
    mov al, 'E'
    int 0x10

    .disk_retry:
        ; 输出'E'(无法工作)
        mov ah, 0x0E
        mov al, 'E'
        int 0x10

        mov ah, 0x02    ; BIOS功能:读取扇区
        mov al, 0x10    ; 读取扇区数量
        mov ch, 0x00    ; 柱面0
        mov cl, 0x02    ; 从扇区2开始
        mov dh, 0x00    ; 磁头0
        mov dl, 0x00
        
        int 0x13        ; 调用磁盘BIOS中断
        jnc .disk_ok    ; 如果没有进位(成功),跳转到.disk_ok

        ; 错误处理:重置磁盘并重试
        mov ah, 0x00    ; BIOS功能:重置磁盘系统
        int 0x13        ; 调用磁盘BIOS中断

        dec si          ; 重试计数器减1
        jnz .disk_retry ; 如果计数器不为0,跳回.disk_retry

        ; 重试3次失败,跳转到错误处理
        jmp disk_error

    .disk_ok:
        ; 从栈恢复寄存器
        pop es
        pop dx
        pop cx
        pop bx
        pop ax
        ret

; 死循环错误处理
disk_error:
    mov ah, 0x0E
    mov al, 'E'
    int 0x10
    jmp disk_error

预期与实际结果

  • 预期:屏幕显示两个'E'字符
  • 实际:仅显示第一个'E'字符

构建步骤(核心命令)

nasm -f bin Boot.asm -o boot.bin
nasm -f elf32 Loader.asm -o loader.o

ld.lld @build/link.rsp

dd if=/dev/zero of=build/bin/OS.img bs=512 count=2880
dd if=<路径>/boot.bin of=<路径>/OS.img bs=512 seek=0 conv=notrunc
dd if=<路径>/kernel.bin of=<路径>/OS.img bs=512 seek=1 conv=notrunc

qemu-system-i386 -drive format=raw,file=disk.img,if=floppy

解决方案

1. 修复栈初始化的不规范写法

原代码中xor ax, 0x9000是错误的清零写法(仅当前AX初始为0时碰巧生效),替换为标准写法:

xor ax, ax       ; 清零AX
mov ax, 0x9000   ; 设置AX为0x9000

或直接:

mov ax, 0x9000

2. 修复磁盘中断破坏视频BIOS状态的问题

int 0x13磁盘中断会破坏BH寄存器的值,而int 0x10的0x0E输出功能依赖BH指定视频页码。有两种修复方式:

方式一:保存/恢复BH寄存器

在DiskReadLoader中新增对BH的栈操作:

DiskReadLoader:
    ; 保存寄存器到栈(新增bh)
    push ax
    push bx
    push cx
    push dx
    push es
    push bh  ; 保存视频页码寄存器

    ; ... 原有代码 ...

    .disk_ok:
        ; 从栈恢复寄存器(新增bh)
        pop bh
        pop es
        pop dx
        pop cx
        pop bx
        pop ax
        ret

方式二:每次调用int 0x10前显式设置BH

在所有int 0x10调用前添加视频页码设置:

mov ah, 0x0E
mov al, 'E'
mov bh, 0x00  ; 指定默认视频页码
int 0x10

3. 修复磁盘驱动器号硬编码问题

BIOS会将启动驱动器号传入DL寄存器,不要硬编码dl=0x00,改为保存并复用该值:

jmp BootProcedure

BootDrive db 0  ; 定义存储驱动器号的变量

BootProcedure:
    mov [BootDrive], dl  ; 保存BIOS传入的启动驱动器号
    ; ... 原有代码 ...

然后在DiskReadLoader中使用该变量:

mov dl, [BootDrive]

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 14:53:09