使用BIOS INT 13h ah=42h读取USB在真机失效的问题求助
我来帮你拆解这个真实PC和QEMU差异的坑——我之前做自制OS时也踩过类似的BIOS兼容性雷,咱们一步步捋清楚:
核心问题分析
QEMU对BIOS调用的容错性极高,但真实PC的BIOS(尤其是老型号或USB启动场景下)对参数的严谨性要求远高于模拟器。你遇到的「仅能读512字节、目标内存无写入」,大概率是这几个环节出了问题:
1. LBA数据包(DAP)的参数错误
int 0x13, ah=0x42完全依赖DAP结构的正确性,真实BIOS几乎不会容错:
- DAP长度必须严格为0x10(16字节):很多新手会不小心把长度写成其他值,QEMU可能忽略,但真实BIOS直接拒绝执行。
- 内存地址的段:偏移必须正确:如果你的目标内存是
0x8000:0x0000,必须把DAP里的dw 0x0000(偏移)和dw 0x8000(段)填对,不能把线性地址直接塞进偏移字段。 - 扇区数不要超过BIOS限制:部分老BIOS单次最多支持读64扇区,如果你一次性读太多,会直接失败,只能读取前512字节(单扇区)。
- LBA地址不能越界:要确保你读取的LBA地址在USB磁盘的实际扇区范围内,真实BIOS不会像QEMU那样自动截断。
2. 未检查BIOS扩展功能支持
很多人跳过这一步直接调用ah=0x42,但真实PC的BIOS可能不支持扩展LBA命令:
- 必须先调用
int 0x13, ah=0x41,传入bx=0xaa55验证扩展功能是否可用。 - 还要检查返回的
cx寄存器第0位是否置位,确认LBA模式被支持。如果不支持,就得降级用CHS模式读取。
3. BPB结构参数不正确
你的MBR里包含BPB(BIOS参数块),真实BIOS会读取这些参数识别磁盘类型,QEMU则可能默认使用通用参数:
- 比如
BPB_BytsPerSec必须是512(USB磁盘的标准扇区大小),BPB_TotSec32要填磁盘的总扇区数,BS_DrvNum(驱动器号)在真实PC里可能不是0x80(0x80是本地硬盘,USB盘可能是0x81/0x82等)。
4. USB磁盘的BIOS兼容性问题
真实PC的USB启动场景下,BIOS对int 0x13的处理有特殊情况:
- 部分老BIOS的扩展LBA命令只支持本地硬盘,不支持USB存储设备。
- USB设备初始化需要时间,可能要加一段延迟循环再执行读操作,避免BIOS还没识别到设备就调用命令。
修复步骤与示例代码
先给你一个加了完整错误检查的示例片段,你可以对照修改:
[bits 16] [ORG 0x7c00] ; 正确填充BPB结构(适配USB磁盘的标准参数) BS_jmpBoot: jmp short entry nop BS_OEMName: db "HARIBOTE" BPB_BytsPerSec:dw 512 ; 扇区大小固定512字节 BPB_SecPerClus:db 1 BPB_RsvdSecCnt:dw 1 BPB_NumFATs: db 2 BPB_RootEntCnt:dw 224 BPB_TotSec16: dw 0 ; 用32位总扇区数替代 BPB_Media: db 0xf0 BPB_FATSz16: dw 9 BPB_SecPerTrk:dw 63 ; 多数USB磁盘的磁道扇区数 BPB_NumHeads: dw 255 ; 多数USB磁盘的磁头数 BPB_HiddSec: dd 0 BPB_TotSec32: dd 0x100000 ; 示例:1GB磁盘的总扇区数 BS_DrvNum: db 0x81 ; 真实PC中USB盘常用驱动器号 BS_Reserved1: db 0 BS_BootSig: db 0x29 BS_VolID: dd 0x12345678 BS_VolLab: db "HARIBOTEOS " BS_FileSysType:db "FAT12 " entry: mov ax, 0 mov es, ax mov ds, ax mov ss, ax mov sp, 0x7c00 ; 第一步:检查BIOS扩展功能支持 mov ah, 0x41 mov bx, 0xaa55 mov dl, [BS_DrvNum] int 0x13 jc ext_not_supported ; CF置位=不支持 cmp bx, 0xaa55 jne ext_not_supported test cx, 0x0001 jz lba_not_supported ; 无LBA支持 ; 第二步:设置正确的DAP结构 mov si, dap mov ah, 0x42 mov dl, [BS_DrvNum] int 0x13 jc read_error ; CF置位=读取失败 ; 读取成功,跳转到目标内存执行 jmp 0x8000:0x0000 ; 错误处理分支 ext_not_supported: mov si, msg_ext_fail call print_str jmp $ lba_not_supported: mov si, msg_lba_fail call print_str jmp $ read_error: mov al, ah ; AH中是BIOS返回的错误码 call print_hex jmp $ ; 辅助函数:打印字符串 print_str: mov ah, 0x0e .print_loop: lodsb or al, al jz .print_done int 0x10 jmp .print_loop .print_done: ret ; 辅助函数:打印十六进制错误码 print_hex: push bx mov cx, 2 mov bx, 16 .hex_loop: mov dx, 0 div bx cmp dl, 10 jl .hex_digit add dl, 0x27 .hex_digit: add dl, 0x30 push dx dec cx jnz .hex_loop .hex_out: pop dx mov ah, 0x0e int 0x10 cmp sp, 0x7c00 jne .hex_out pop bx ret ; 标准LBA DAP结构 dap: db 0x10 ; DAP长度(必须是16) db 0x00 ; 保留字段 dw 0x0002 ; 读取2个扇区(可根据需求调整,建议单次不超64) dw 0x0000 ; 目标内存偏移 dw 0x8000 ; 目标内存段 dq 0x00000001 ; LBA地址(从第2扇区开始读,LBA从0计数) ; 错误提示字符串 msg_ext_fail: db "BIOS ext not supported", 0 msg_lba_fail: db "LBA not supported", 0 times 510-($-$$) db 0 dw 0xaa55
额外调试建议
- 打印BIOS错误码:调用
int 0x13后如果CF置位,AH里的错误码能直接告诉你问题(比如0x01=无效命令,0x04=扇区未找到),比只看内存更高效。 - 枚举驱动器号:真实PC中USB盘的驱动器号不一定是0x80,可以循环尝试0x80、0x81、0x82等,用
int 0x13, ah=0x00重置驱动器,找到能成功的那个。 - 缩小测试范围:先尝试只读1个扇区,确认能写入目标内存后,再逐步增加扇区数,排查是否是扇区数限制问题。
内容的提问来源于stack exchange,提问作者石原秀一
相关产品推荐
相关产品推荐

