bootloader切换LBA模式后QEMU运行正常但真实硬件反复重启问题求助
问题1:32位保护模式下数据段寄存器未初始化
进入32位保护模式执行load32标签后的代码前,你没有手动将所有数据相关段寄存器(ds/es/ss/fs/gs)设置为DATA_SEG的值。insw指令默认使用es作为段基址,未初始化的es会导致读取的磁盘数据写入错误内存地址,触发三重故障导致系统无限重启。QEMU对段寄存器初始值的处理比较宽松,因此未触发问题。
修复方案:在load32最开头添加如下代码:
mov ax, DATA_SEG mov ds, ax mov es, ax mov ss, ax mov fs, ax mov gs, ax mov esp, 0x90000 ; 重新设置栈指针到安全内存区域,避免栈覆盖代码
问题2:ATA驱动读取逻辑不符合真实硬件要求
当前ata_lba_read只检查了状态寄存器的DRQ位(第3位),未先检查BSY位(第7位)是否清零。真实ATA设备在接收命令后会先将BSY位置1,此时读取状态寄存器的结果无效,直接读取会导致逻辑错误。
修复方案:修改.try_again部分的逻辑,先等待BSY位清零再检查DRQ位:
.try_again: mov dx, 0x1F7 in al, dx test al, 0x80 ; 检查BSY位是否为1 jnz .try_again test al, 0x08 ; 再检查DRQ位是否为1 jz .try_again
问题3:驱动器号硬编码不符合真实硬件场景
你在FAT16的BPB中硬编码了DriveNumber db 0x80,但真实BIOS会将当前启动盘的驱动器号存入dl寄存器传入bootloader,硬编码为0x80在从非第一硬盘启动的场景下会直接失败。
修复方案:在16位实模式的load标签开头,将BIOS传入的dl的值写入DriveNumber地址保存,后续如果有16位磁盘操作可以直接使用该值。
问题4:32位打印函数存在多个逻辑错误
- 显存地址计算错误:VGA文本模式下每个字符占2字节(1字节ASCII+1字节属性),你当前的地址计算直接加x坐标,没有乘以2,会导致字符写入错误位置,甚至越界触发异常。
- 列计数重复自增:
printchar32中打印完字符先执行了inc ebx,判断不需要换行后在newcol标签下又执行了inc ebx,等于每打印一个字符列号加2,会出现隔一个字符打印一个的问题,最终很快超出列范围触发异常。 - 未初始化行列变量:你没有给出
col和row变量的定义与初始化代码,若未初始化为0,首次访问会读取到随机值,导致字符写入非法地址触发崩溃。 lodsb使用错误:lodsb只会读取al寄存器,你后续判断cmp eax,0是多余的,若eax高24位有垃圾值会导致字符串判断提前终止。
修复方案:
- 显存地址计算时将x坐标乘以2
- 删除
newcol标签下多余的inc ebx - 在32位代码开头初始化
col和row为0 - 将
cmp eax, 0改为cmp al, 0
问题5:16位实模式栈设置错误
你将16位下的栈指针sp设置为0,栈向下增长时会写入0xFFFF~0x0000的内存区域,该区域属于BIOS数据区,会破坏BIOS运行需要的关键数据,导致真实硬件运行异常。
修复方案:将16位的栈指针设置到0x7C00之前的安全区域,比如mov sp, 0x7C00,保证栈不会覆盖bootloader自身代码,也不会破坏BIOS数据区。
内容的提问来源于stack exchange,提问作者NotMyCode

