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
相关产品推荐
相关产品推荐

