Ste*_*ith 4 x86 mbr qemu osdev bootloader
我正在编写MBR并使用QEMU进行测试.
当将读取扇区用于内存(int 0x13, ah=0x02)时,该int指令似乎阻止了我的程序的执行,并且它继续挂起.我已经用各种打印语句对此进行了测试,以确认这是特定的指令阻塞.
什么可以使中断阻止?我认为这只能通过cli指令完成,即使这样也不会阻止int指令.
对于上下文,这是导致阻塞中断的代码read_sectors_16:
[bits 16]
[ORG 0x7C00]
jmp 0x000:start_16 ; ensure cs == 0x0000
reset_failure_str db 'error: boot disk reset', 13, 10, 0
read_failure_str db 'error: boot disk read', 13, 10, 0
boot_segaddr dw 0x7E00
read_attempt_str db 'read attempt', 13, 10, 0
end_read_attempt_str db 'end read attempt', 13, 10, 0
start_16:
;; Initialize segment registers
mov ax, cs
mov ds, ax
mov es, ax
jmp load_bootsector_2
load_bootsector_2: ; Read program from disk
;; dl set by BIOS to the drive number MBR was loaded from
mov cx, 0x0002 ; cylinder 0, sector 2
xor dh, dh ; head 0
mov al, 0x01 ; load 1 sector
mov bx, boot_segaddr ; destination - load right after the boot loader
call read_sectors_16
jnc .success
mov si, read_failure_str
call print_string_16
jmp halt ; halt
.success:
jmp boot_segaddr:0000 ; jump to program
Run Code Online (Sandbox Code Playgroud)
这是带阻塞中断的函数:
;;; read_sectors_16
;;;
;;; Read sectors from disk in memory using BIOS services
;;;
;;; input: dl = drive
;;; ch = cylinder[7:0]
;;; cl[7:6] = cylinder[9:8]
;;; dh = head
;;; cl[5:0] = sector (1-63)
;;; es:bx -> destination
;;; al = number of sectors
;;;
;;; output: cf (0 = success, 1 = failure)
read_sectors_16:
pusha
mov di, 0x02 ; set attempts (max attempts - 1)
.attempt:
mov ah, 0x02 ; read sectors into memory (int 0x13, ah = 0x02)
int 0x13 ; TODO: this call is not returning!
jnc .end ; exit if read succeeded
dec di ; record attempt
test di, di
jz .end ; end if no more attempts
xor ah, ah ; reset disk (int 0x13, ah = 0x00)
int 0x13
jnc .attempt ; retry if reset succeeded, otherwise exit
jmp .end
.end:
popa
ret
Run Code Online (Sandbox Code Playgroud)
突出的是你的细分.首先,您的代码定义boot_segaddr为:
boot_segaddr dw 0x7E00
Run Code Online (Sandbox Code Playgroud)
这会在内存中创建一个值为0x7E00的16位字.然后你有这两行:
mov bx, boot_segaddr
[snip]
jmp boot_segaddr:0000
Run Code Online (Sandbox Code Playgroud)
在这两种情况下boot_segaddr都被用作直接的.您使用的是地址boot_segaddr,而不是值boot_segaddr.
我会boot_segaddr dw 0x7E00改为常量值(使用EQU)并将其重命名为:
BOOT_OFFSET EQU 0x7E00
Run Code Online (Sandbox Code Playgroud)
然后你可以修改mov bx, boot_segaddr为:
mov bx, BOOT_OFFSET
Run Code Online (Sandbox Code Playgroud)
这会产生将0x7E00移动到BX的效果.对Int 13/AH = 2的调用将读取从ES开始的扇区:BX = 0x0000:0x7E00这是你想要的.
下一个问题是如果我们为FAR JMP重用相同的常量,如下所示:
jmp BOOT_OFFSET:0000
Run Code Online (Sandbox Code Playgroud)
这将导致FAR JMP为0x7E00:0x0000.不幸的是,这是物理地址(0x7E00 << 4)+ 0x0000 = 0x7E000,这不是你想跳的地方.您想跳转到物理地址0x07E00.你真的想要一个FAR JMP到0x07E0:0x0000,这将是物理地址(0x07E0 << 4)+ 0x0000 = 0x7E00.为了使FAR JMP正常工作,我们可以BOOT_OFFSET向右移动4位.您可以将该行更改为:
jmp (BOOT_OFFSET>>4):0000
Run Code Online (Sandbox Code Playgroud)
进行这些更改应该让您的引导程序正常工作.因为它原始代码中有2个错误:
显而易见的挂起可能是由于从boot_segaddr引导加载程序中的内存地址开始读取扇区引起的.可能你覆盖了引导程序中的所有代码,使它在int 13h最终返回时不正常地工作.
正如Peter指出的那样使用像BOCHS这样的仿真器,它的内部调试器允许你单步执行16位实模式代码.您可能已经发现了这些问题.