自制Bootloader遇qemu-system-i386 CPU概率性复位,Bochs无此问题原因
自行编写Bootloader时,在qemu-system-i386中CPU会出现概率性复位,但Bochs模拟器无此问题,请问原因是什么?
相关代码如下:
mbr.s
%include "boot.inc" SECTION MBR vstart=0x7c00 ... call rd_disk_m_16 ; rd_disk_m_16 工作正常 jmp LOADER_BASE_ADDR ... db 0x55,0xaa
loader.s
%include "boot.inc" section loader vstart=LOADER_BASE_ADDR LOADER_STACK_TOP equ LOADER_BASE_ADDR jmp loader_start ... loader_start: cli lgdt [gdt_ptr] mov eax, cr0 or eax, 0x00000001 mov cr0, eax jmp 0x08:p_mode_start [bits 32] p_mode_start: jmp $
操作步骤:
# 创建硬盘镜像 bximage -func=create -hd=16M -imgmode="flat" -sectsize=512 -q hd.img # 编译并写入MBR nasm -I include/ -o mbr.bin mbr.s && dd if=mbr.bin of=./hd.img bs=512 count=1 conv=notrunc # 编译并写入Loader nasm -I include/ -o loader.bin loader.s && dd if=loader.bin of=./hd.img bs=512 count=4 seek=2 conv=notrunc # 启动QEMU并调试复位与中断 qemu-system-i386 -hda hd.img -d cpu_reset,int -no-reboot
这种跨模拟器的差异,核心原因是QEMU对x86硬件规范的校验更严格,而Bochs的兼容性容错性更强。结合你的代码和操作流程,可能的触发点有这些:
GDT配置不规范
lgdt [gdt_ptr]加载的全局描述符表如果存在对齐错误(比如GDT基地址未按8字节对齐),或者表项的段权限、属性位不符合x86标准,QEMU会直接触发CPU复位,而Bochs会忽略这类非致命错误。检查gdt_ptr对应的GDT结构,确保基地址是8字节对齐,段限长、类型位等参数配置正确。保护模式切换前状态未清理彻底
切换保护模式时,除了执行cli关闭可屏蔽中断,还需要:- 禁用NMI(不可屏蔽中断):部分场景下NMI会干扰模式切换,QEMU会严格模拟这种触发复位的情况,而Bochs不会。可在
cli后添加代码:in al, 0x70; or al, 0x80; out 0x70, al来禁用NMI。 - 确保段寄存器有效性:切换保护模式后,DS、ES等段寄存器仍保留实模式下的段值,QEMU会因非法段状态触发复位,Bochs则会容错。建议在远跳转前先将段寄存器设置为合法的保护模式选择子。
- 禁用NMI(不可屏蔽中断):部分场景下NMI会干扰模式切换,QEMU会严格模拟这种触发复位的情况,而Bochs不会。可在
Loader加载不完整
你用dd写入Loader时指定count=4(即4个扇区2048字节),如果loader.bin实际编译后大小超过这个值,会导致部分代码未被加载到内存,QEMU执行到非法内存区域时触发复位;而Bochs会将未初始化的内存填充0,刚好不会触发错误。可以用ls -l loader.bin查看实际大小,调整dd的count参数。磁盘读取时序不规范
rd_disk_m_16函数如果没有严格等待磁盘就绪就发送命令,QEMU的磁盘模拟会返回错误状态,导致加载的Loader代码损坏,概率性触发复位;Bochs的磁盘模拟对时序要求宽松,不会出现这类问题。建议在磁盘操作的每一步添加等待逻辑:wait_disk_ready: in al, 0x1f7 test al, 0x80 jnz wait_disk_ready模式切换的指令时序问题
修改CR0开启保护模式后,紧接着的远跳转是必须的,但如果指令预取存在一致性问题,QEMU会触发复位。可以在mov cr0, eax后添加一到两个nop指令,再执行远跳转,避免时序冲突。
内容的提问来源于stack exchange,提问作者chenhao

