INT 13h AH=42h调用失败(AH=1,CF=1),Bootloader磁盘读取求助
我正在编写一个简单Bootloader,目标是将LBA=1位置的程序加载至内存0x1000并跳转执行。使用QEMU的-drive参数仿真时,调用INT 13h AH=42h读取磁盘块失败,返回AH=1、CF=1。已排查参数传递和DAP结构,未发现明显问题,附上相关源码、Makefile、链接脚本及调用前寄存器状态,请求排查原因并解决。
源码文件(GNU as编译)
boot0.S
.code16 .include "source/constants.S" .global _start0 .global PRINT_STRING .global HALT .section .text .global _start0 _start0: ljmp $0, $L0 # Canonicalize (%CS):%IP. L0: xorw %ax, %ax movw %ax, %ds # Load Segment Registers. movw %ax, %es movw %ax, %ss movw %ax, %fs movw %ax, %gs movw $STACK_TOP, %sp # Prepare stack. sti # Set interrupt flag. movw $BOOT0SEQ_STR, %si movb $0x07, %dh call PRINT_STRING INIT_ATTEMPT1: movb $0, %ah # Reset drive controller. int $0x13 jnc INIT_SUCCESS INIT_ATTEMPT2: movb $0, %ah # Try again. int $0x13 jc FAILURE INIT_SUCCESS: cmpb $0x80, %dl # Check whether we boot from hard drive or floppy. jb LBA_FAILURE_OR_FLOPPY HARDDRIVE: # We boot from a hard-drive. movb $0x41, %ah # Check whether we support Disk Address Packet. movw $0x55AA, %bx int $0x13 jc LBA_FAILURE_OR_FLOPPY cmpw $0xAA55, %bx jne LBA_FAILURE_OR_FLOPPY test $1, %cx jz LBA_FAILURE_OR_FLOPPY LBA_SUCCESS: # We can read from a disk using Disk Address Packet. movb $0x42, %ah movw $DAP, %si # %ds is already set to zero. int $0x13 jc FAILURE # Test whether read was successful. jmp SUCCESS LBA_FAILURE_OR_FLOPPY: # We boot from a floppy disk or the extension check failed. FLOPPY_ATTEMPT1: movw $0x0210, %ax movw $0x0001, %cx movb $0, %dh movw $BOOT1_START, %bx # %es is already set to zero. int $0x13 jnc FLOPPY_SUCCESS FLOPPY_ATTEMPT2: # Try reading for a second time! movw $0x0210, %ax movw $0x0001, %cx movw $BOOT1_START, %bx int $0x13 jc FAILURE FLOPPY_SUCCESS: jmp SUCCESS SUCCESS: movw $BOOT0_SUCCESS_STR, %si movb $0x0A, %dh # 0x0A -- Light Green. call PRINT_STRING jmp BOOT1_START FAILURE: # Something went wrong! movw $ERROR_MESSAGE, %si movb $0x04, %dh # 0x04 -- Red. call PRINT_STRING jmp HALT HALT: cli 1: hlt jmp 1b PRINT_STRING: # PRINT_STRING(string : %si, color : %dh) movb (%si), %al inc %si orb %al, %al jz PRINT_RET call PRINT_CHAR jmp PRINT_STRING PRINT_RET: ret PRINT_CHAR: movb $0x0E, %ah movb $0x00, %bh movb %dh, %bl int $0x10 ret .balign 4 DAP: # Disk Address Packet structure. DAP_packet_size: .byte 0x10 DAP_zero: .byte 0x00 DAP_Nsectors: .word 0x01 DAP_buffer: .long BOOT1_START DAP_lower_bits: .long 0x01 DAP_upper_bits: .long 0x00 .byte 0x00 BOOT0SEQ_STR: .asciz "[[ BootSeq0 ]]: " BOOT0_SUCCESS_STR: .asciz "Success!\n\r" ERROR_MESSAGE: .asciz "Error: Could not find Boot1!\n\r" .space 510 - (. - _start0) .word 0xAA55 # Boot Signature.
constants.S
.set BOOT1_START, 0x1000 .set BOOT1_END, 0x6000 .set STACK_TOP, 0x9000
Makefile(支持i686-elf或普通GNU binutils)
ARCH := i686-elf CC := $(ARCH)-gcc AS := $(ARCH)-as LD := $(ARCH)-ld BUILD_DIR := ./binaries SRC_DIR := . qemu: $(BUILD_DIR)/bootloader.img qemu-system-i386 -drive format=raw,file=$< # -S -gdb tcp::1234 $(BUILD_DIR)/bootloader.img: $(BUILD_DIR)/boot0.o $(LD) -T $(SRC_DIR)/linker.ld -format binary -o $@ $< $(BUILD_DIR)/boot0.o: $(SRC_DIR)/boot0.S $(BUILD_DIR) $(AS) $< -o $@ $(BUILD_DIR): mkdir $(BUILD_DIR) $(SRC_DIR)/boot0.S: echo "Error: no boot0.S file found!" clean: rm -rf $(BUILD_DIR)
linker.ld
ENTRY(_start0) SECTIONS { . = 0x7C00; .boot0 : { binaries/boot0.o(.text*) } }
INT 13h AH=42h调用前寄存器状态
eax 0x4200 16896 ecx 0x7 7 edx 0x780 1920 ebx 0xaa55 43605 esp 0x9000 0x9000 ebp 0x0 0x0 esi 0x7c9c 31900 edi 0x0 0 eip 0x7c48 0x7c48 eflags 0x202 [ IOPL=0 IF ] cs 0x0 0 ss 0x0 0 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0
排查情况
目前已确认参数传递和Disk Address Packet(DAP)结构设置无明显问题,但调用INT 13h AH=42h仍返回AH=1(无效函数参数)、CF=1。
问题原因与解决方法
原因1:DAP缓冲区地址格式不兼容
INT 13h AH=42h的DAP结构中,DAP_buffer字段要求是段:偏移的32位组合(高16位为段值,低16位为偏移值),当前代码直接使用线性地址0x1000,虽逻辑上等价于段0x0000+偏移0x1000,但部分BIOS对这种写法兼容性不佳。
解决方法:显式构造段:偏移格式的缓冲区地址
DAP_buffer: .word BOOT1_START, 0x0000 # 偏移0x1000,段0x0000
原因2:磁盘镜像LBA=1位置无有效数据
当前Makefile生成的bootloader.img仅包含boot0的512字节,LBA=1位置为空,BIOS读取不存在的扇区会返回错误。
解决方法:构建包含boot1的完整磁盘镜像
- 添加boot1编译规则(假设存在
boot1.S):
$(BUILD_DIR)/boot1.o: $(SRC_DIR)/boot1.S $(BUILD_DIR) $(AS) $< -o $@ $(BUILD_DIR)/boot1.bin: $(BUILD_DIR)/boot1.o $(LD) -Ttext 0x1000 -o $@ $< --oformat binary
- 修改镜像生成规则,合并boot0和boot1:
$(BUILD_DIR)/bootloader.img: $(BUILD_DIR)/boot0.o $(BUILD_DIR)/boot1.bin $(LD) -T $(SRC_DIR)/linker.ld -format binary -o $(BUILD_DIR)/boot0.bin $< cat $(BUILD_DIR)/boot0.bin $(BUILD_DIR)/boot1.bin > $@ # 可选:用dd填充到整扇区 # dd if=$@ of=$@.tmp bs=512 count=2 # mv $@.tmp $@
原因3:QEMU磁盘识别类型错误
QEMU默认可能将磁盘识别为软盘,代码中判断%dl >= 0x80才进入LBA模式,导致实际进入软盘分支,但软盘读取参数可能不匹配。
解决方法:强制QEMU识别为硬盘
修改QEMU启动命令:
qemu-system-i386 -drive format=raw,file=$(BUILD_DIR)/bootloader.img,index=0,media=disk
原因4:LBA支持检测逻辑过严
部分BIOS支持AH=42h,但AH=41h检测返回的bx或cx值不符合当前代码的判断条件。
解决方法:放宽检测逻辑,直接尝试AH=42h读取
HARDDRIVE: # 跳过严格检测,直接尝试LBA读取 movb $0x42, %ah movw $DAP, %si int $0x13 jnc SUCCESS # 失败后回退到CHS分支 jmp LBA_FAILURE_OR_FLOPPY
内容的提问来源于stack exchange,提问作者Alex Gendelbergen

