读取磁盘时出现奇怪的错误

Weird Errors While Reading Disk

所以,我一直在从事一个业余爱好项目。创建我自己的操作系统。我开始有一段时间了,但直到几天前才放弃。我只是修复了一个疏忽,导致无法从我想读取的扇区读取任何内容。排除了那个错误,又出现了一个新错误,老实说,我什至不知道从哪里开始调试这个错误。

我正在编写主引导记录并使用 GDB 和 QEMU 对其进行调试,这是我的主引导记录的代码(它是使用 YASM 组装的)

抱歉,如果我的代码不是很好。我不是汇编语言专家...

; yasm boot.asm -fbin

bits 16

%define part(n,l) section n vstart=l align=1
%define rpart(n,l) section n start=l align=1

; ----------------------- ;
part(entry, 0x7c00)       ;
; --ENTRY---------------- ;

_start:
    mov [boot_drive+0x7c00], dl

    xor ax, ax
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov sp, _start
    mov bp, _start

    mov cx, 512
    mov si, _start
    mov di, _strap

    rep movsb

    jmp 0:_strap+(b_boot_strapper-$$)

b_boot_strapper:
; ----------------------- ;
part(strap, 0x0600)       ;
; --BOOT STRAPPER-------- ;

_strap:
    xor cx, cx  
    .find_active_part:
        cmp cl, 4
        jge .no_active_part

        xor ax, ax
        mov ah, cl
        mov bl, 16
        mul bl

        mov bx, ax

        inc cl

        mov al, (1 << 7)
        mov ah, [partition_1+0x600+bx]
        and ah, al

        jnz .load_active_part
        jmp .find_active_part

    .load_active_part:
        xor ax, ax
        mov ds, ax

        mov ah, 42h
        mov dl, [boot_drive+0x600]
        mov si, dap+0x600
        push bx
        mov bx, dap+0x600
        mov es, bx
        pop bx

        mov cx, [partition_1+0x600+bx+8]
        mov [dap_startlba+0x600], cx
        mov cx, [partition_1+0x600+bx+12]
        mov [dap_sectors+0x600], cx

        int 13h

        jc .disk_error

        xor ax, ax
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, _start
        mov bp, _start


        mov dl, [boot_drive+0x600]
        jmp 0:0x7c00

    .no_active_part:
        mov si, msg_no_part

        call print
        jmp halt

    .disk_error:
        mov si, msg_er_read

        call print
        jmp halt

    print:
        mov dx, ax

        mov ah, 0Eh
        xor bh, bh
        mov bl, 0Fh

        .rep:
            lodsb
            or al, al
            jz .done
            int 10h
            jmp .rep

        .done:
            ret

    halt:
        cli
        hlt
        jmp halt

msg_er_read db 'Disk Read Error....', 0
msg_no_part db 'No Active Partition....', 0

; ----------------------- ;
rpart(variables, 300)     ;
; --VARIABLES------------ ;

boot_drive db 0

dap: ; Disk Address Packet
    db 16, 0
    dap_sectors  dw 0
    dap_offset   dw 0x7c00
    dap_segment  dw 0
    dap_startlba dq 0
dap_end:

; ----------------------- ;
rpart(partitions, 446)    ;
; --VARIABLES------------ ;

partition_1: ; This file has the following 16 bytes: 
; 0x80, 0x01, 0x00, 0x05, 0x17, 0x01, x03, 0x01, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00
%include "part_n1.asm"
partition_2: ; The rest of these files are just 16 null bytes.
%include "part_n2.asm"
partition_3:
%include "part_n3.asm"
partition_4:
%include "part_n4.asm"

; ------------------------------- ;
rpart(signature, 510)             ;
db 0x55, 0xAA                     ;
; ------------------------------- ;

此代码有效!但是,我不知道这是否是 QEMU 的问题,但是当它从扇区读取时它有一点损坏或数据丢失......

这些是预期位于 0x7c00 的字节

EB 1B B4 0E 30 FF B3 0F
AC 74 04 CD 10 EB F9 C3 
48 65 6C 6C 6F 20 57 6F 
72 6C 64 21 00 BE 10 7C 
E8 DF FF F4 

(打印"Hello World!"的基本函数)

这就是最终实际存在于该位置的内存中的内容:

EB 1B B4 0E 30 FF B3 0F 
AC 74 04 CD 10 EB F9 C3 
48 65 6C 6C 6F 20 57 6F 
72 6C 64 21 00 BE 10 7C 
F0 DF FF F4

如果你仔细观察,倒数第 4 个字节从 E8 变成了 F0,我不知道为什么会这样。在上一个 运行 中 "Hello World" 中的 "E" 也被更改,但它不在这个调试 运行.

我需要帮助甚至从哪里开始调试...


编辑 1

我意识到我打印 hello world 的函数有一些问题,天气是否与这件奇怪的事情有关,我真的不知道。在打印功能的重复部分(我正在加载的代码中的那个,而不是上面的 mbr 代码中的部分)我忘记在 lodsb 之后和 [=15= 之前添加 or al, al ] 这可能一直在干扰事情,我不完全确定,但在我更新了代码和 运行 几个调试会话之后,似乎这个问题不再发生了......

您的代码有很多问题,但问题很可能出在您没有显示的卷引导记录中。 MBR中的一些需要解决的问题:

  • SS之后设置SP,保证SS[设置之间不会出现中断=82=] 和 SP,这将破坏由未知的旧 SP 和新 SS[ 形成的地址处的内存=82=](反之亦然)。 CPU 在设置 SS 后自动关闭中断,并在 以下指令后重新启用它们。
  • 发出一条CLD指令来清除方向标志(DF),这样字符串指令如MOVSBLODSB 使用向前移动。
  • 当使用 Int 13h/ah=42h 时,有些 BIOS 需要 ES:BX 在磁盘地址包 (DAP) 中设置相同的值。您的代码未正确设置 ES。它应该被设置为零。
  • 当从活动分区条目中的值填充 DAP 中的起始 LBA 时,您的代码仅复制 32 位值的低 16 位。这将您限制为 <= 32MiB (512*65536) 的媒体。您应该将起始 LBA 的下半部分和上半部分从分区 table 复制到 DAP。
  • 进行磁盘读取或写入操作时,您应该在失败之前再重试 3 次。这在使用实际软盘和硬盘驱动器的实际硬件上可能是必需的。
  • 您应该通过检查值是否为 0x80 而不仅仅是最高位来检查活动分区。唯一的有效值是 0x00 或 0x80。
  • 您从分区加载的卷引导记录(VBR)通常是一个扇区。如果是一个扇区,则读取一个扇区,而不是整个分区。
  • 您的代码在设置部分的方式上过于复杂。如果您要将引导加载程序重新定位到 0x0600,则使用 0x0600 的 ORG。只需确保将引导扇区从 0x0000:0x7c00 重定位到 0x0000:0x0600 的代码不依赖于任何与 0x7c00.
  • 相关的标签

您的代码中有些东西很不错:

  • 清除如何循环遍历分区 table 搜索 active/bootable 分区。
  • 如果您想与某些古老的操作系统保持兼容,请在跳转到卷引导之前传递 DS:SI 中 bootable 分区条目的地址记录你读入内存。这不是必需的
  • 不是必需的,这只是一个注意事项:如果您想保持与 MS-DOS 的兼容性,硬盘驱动器上的分区应始终位于 cylinder boundary 上,最好以柱面结尾边界。

其中一些技巧可以在我的 Whosebug 中找到


你的 relocatable 链加载卷引导记录 (VBR) 的引导加载程序的修改版本可以编码为:

boot.asm:

DISK_RETRY     EQU 3
BOOT_ORG_RELOC EQU 0x0600
BOOT_ORG       EQU 0x7c00
MBR_SIZE       EQU 512

%define SECTION(n,l) section n start=l+BOOT_ORG_RELOC align=1

ORG BOOT_ORG_RELOC

_start:
    ; This code occurs before relocation so can't rely on any labels relative to
    ; BOOT_ORG_RELOC
    xor ax, ax
    mov es, ax
    mov ds, ax
    mov ss, ax
    mov sp, BOOT_ORG           ; Place stack at 0x0000:0x7c00 below bootloader

    cld                        ; DF=0 for forward direction of string instructions
    mov cx, MBR_SIZE/2         ; MBR Size to copy in bytes
    mov si, BOOT_ORG           ; Source address = DS:SI (0x0000:0x7c00)
    mov di, BOOT_ORG_RELOC     ; Destination address = ES:DI (0x0000:0x0600)
    rep movsw

    jmp 0x0000:.reloc_start    ; Set CS:IP to continue at the next instruction but in
                               ; the relocated boot sector
.reloc_start:
    ; Start at end of partition table and search to beginning looking for active
    ; boot partition.
    mov si, partition_start    ; SI = base of partition table
    mov bx, PARTITION_SIZE     ; Set the offset to search at to end of partition table
.active_search_loop:
    sub bx, 16                 ; Go to previous partition entry
    jl .no_active              ; If BX is neg we have passed beginning of partition table
    cmp byte [si + bx], 0x80   ; Is partition bootable?
    jnz .active_search_loop    ;     If not bootable go back and search again

.fnd_active:
    lea di, [si + bx]          ; Save offset of active partition to DI
    mov ax, [si + bx + 8]      ; Copy partition start LBA to DAP structure (lower 16-bits)
    mov [dap + 8], ax
    mov ax, [si + bx + 10]     ; Copy partition start LBA to DAP structure (upper 16-bits)
    mov [dap + 10], ax

    mov cx, DISK_RETRY
                               ; DL contains boot drive passed by BIOS
                               ; ES was previously set to 0
    mov bx, BOOT_ORG           ; ES:BX needs to be same values as the DAP for some BIOSes
    mov si, dap                ; DS:SI = beginning of DAP structure

.disk_retry:
    mov ah, 0x42               ; BIOS call for extended disk read
    int 0x13                   ; Read boot sector to 0x0000:0x7c00
    jnc .vbr_loaded            ; If int 0x13 succeeded (CF=0), run the loaded VBR

    dec cx                     ; Lower retry count by 1
    jge .disk_retry            ; If retry count >= 0 go back and try again

.disk_error:
    mov si, msg_er_read        ; Print disk error and halt
    call print
    jmp halt

.no_active:
    mov si, msg_no_part        ; Print no active partition error and halt
    call print
    jmp halt

.vbr_loaded:
                               ; DL is still same value oeiginally passed by BIOS
    mov si, di                 ; DS:SI=address of active partition for some old OSes
    jmp 0x0000:BOOT_ORG        ; Execute the chain loaded VBR

halt:                          ; Infinite HLT loop with interrupts off to end bootloader
    cli
.halt_loop:
    hlt
    jmp .halt_loop

; Print function
print:
    mov ah, 0x0e
    xor bh, bh

.rep:
    lodsb
    or al, al
    jz .done
    int 0x10
    jmp .rep

.done:
    ret

dap: ; Disk Address Packet
    db 16, 0                   ; DAP size, second byte always 0
    dap_sectors  dw 1          ; Read VBR (1 sector)
    dap_offset   dw BOOT_ORG   ; Read to 0x0000:0x7c00
    dap_segment  dw 0
    dap_startlba dq 0          ; To be filled in at runtime
dap_end:

msg_er_read: db 'Disk Read Error....', 0
msg_no_part: db 'No Active Partition....', 0

SECTION(parttbl, 446)
partition_start:
partition_1:
%include "part_n1.asm"
partition_2:
%include "part_n2.asm"
partition_3:
%include "part_n3.asm"
partition_4:
%include "part_n4.asm"
partition_end:
PARTITION_SIZE EQU partition_end - partition_start

SECTION(bootsig, 510)
dw 0xaa55

part_n1.asm:

db 0x80, 0x01, 0x00, 0x05, 0x17, 0x01, 0x03, 0x01, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00

part_n2.asm:

dq 0, 0

part_n3.asm:

dq 0, 0

part_n4.asm:

dq 0, 0

要测试的简单卷引导记录 (VBR) 可以是:

BOOT_ORG       EQU 0x7c00
%define SECTION(n,l) section n start=l+BOOT_ORG align=1

ORG BOOT_ORG

vbr_start:
    xor ax, ax                 ; ES=DS=SS=0
    mov es, ax
    mov ds, ax
    mov ss, ax
    mov sp, BOOT_ORG           ; Place stack at 0x0000:0x7c00 below bootloader
    cld                        ; DF=0 is forward direction for string instructions

    mov si, vbr_run_msg        ; Print a message that the VBR is running
    call print

halt:
    cli
.halt_loop:
    hlt
    jmp .halt_loop

; print function
print:
    mov ah, 0x0e
    xor bh, bh

.rep:
    lodsb
    or al, al
    jz .done
    int 0x10
    jmp .rep

.done:
    ret

vbr_run_msg: db "VBR running", 0x0d, 0x0a, 0

SECTION(bootsig, 510)
dw 0xaa55

您可以使用以下命令将此代码构建并运行为 10 兆字节的磁盘映像:

nasm -f bin boot.asm -o boot.bin
nasm -f bin vbr.asm -o vbr.bin

# create 10MiB disk image
dd if=/dev/zero of=disk.img bs=10M count=1

# place boot sector at LBA=0 without truncating the disk image
dd if=boot.bin of=disk.img conv=notrunc seek=0

# place vbr at LBA=4 without truncating the disk image
dd if=vbr.bin of=disk.img conv=notrunc seek=4

在 QEMU 中,您可以 运行 使用以下命令:

qemu-system-i386 -hda disk.img

如果有效,输出应类似于: