从 FAT 软盘映像读取第二阶段引导加载程序

Reading second stage bootloader from FAT floppy image

我正在尝试开发一个带有自定义引导加载程序的小型 OS。我在 OSDEV 方面有一点经验,但不是很多......我的问题是第一阶段引导加载程序不会从磁盘加载秒数。这是 boot.asm 文件:

org 0
bits 16

jmp boot

%include "include/fat12.inc"
%include "include/io.inc"

Mem.Loader1 equ 0x00007c00
Mem.Loader1.Size equ 0x00000200
Mem.Loader1.Segment equ Mem.Loader1 >> 4
Mem.Stack.Top equ 0x00007c00

boot: jmp Mem.Loader1.Segment : .init
.init:

cli

; adjust segment registers
mov ax, cs
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax

; create stack
xor ax, ax
mov ss, ax
mov sp, Mem.Stack.Top

sti

call LoadRoot
xor ebx, ebx
mov bp, secondStage
mov si, ImageName
call LoadFile
cmp ax, 0
je secondStage
BiosPrint msgBooting
jmp $
 
msgBooting db "E", 0
ImageName db "loader bin"
 
times 510-($-$$) db 0
dw 0xAA55

secondStage:

您可能认得 MonkOS and Brokenthorn since all my knowledge comes from them and OSDevWiki 中的一些代码。

引导加载程序无法找到 loader.bin 文件并打印“E”。 我绝对确定 LoadRootLoadFile 可以正常工作,因为我从上一个项目中复制了它,它运行良好,但如果需要,我会在此处添加它们。

用 nasm 组装文件后,我创建了一个软盘映像:

dd if=/dev/zero of=BonsOS.img bs=1024 count=1440
/sbin/mkfs.msdos BonsOS.img
mcopy -i BonsOS.img ./bin/boot/loader.bin ::/
dd if=./bin/boot/boot.bin of=BonsOS.img seek=0 count=1 conv=notrunc

最后 运行

qemu-system-x86_64 -fda BonsOS.img -m 512M -no-reboot -no-shutdown

如何解决找不到文件的问题?

编辑

fat12.inc:

;*******************************************************
;
;   Fat12.inc
;       FAT12 filesystem for 3-1/2 floppies
;
;   OS Development Series
;*******************************************************

%ifndef __FAT12_INC_67343546FDCC56AAB872_INCLUDED__
%define __FAT12_INC_67343546FDCC56AAB872_INCLUDED__

bits    16

%include "include/floppy16.inc"                 ; the erm.. floppy driver

%define ROOT_OFFSET 0x2e00
%define FAT_SEG 0x2c0
%define ROOT_SEG 0x2e0

;*******************************************
; LoadRoot ()
;   - Load Root Directory Table to 0x7e00
;*******************************************

LoadRoot:

    pusha                           ; store registers
    push    es

     ; compute size of root directory and store in "cx"
     
    xor     cx, cx                      ; clear registers
    xor     dx, dx
    mov     ax, 32                  ; 32 byte directory entry
    mul     WORD [bpbRootEntries]               ; total size of directory
    div     WORD [bpbBytesPerSector]            ; sectors used by directory
    xchg    ax, cx                      ; move into AX

     ; compute location of root directory and store in "ax"
     
    mov     al, BYTE [bpbNumberOfFATs]          ; number of FATs
    mul     WORD [bpbSectorsPerFAT]             ; sectors used by FATs
    add     ax, WORD [bpbReservedSectors]
    mov     WORD [datasector], ax               ; base of root directory
    add     WORD [datasector], cx

     ; read root directory into 0x7e00
 
    push    word ROOT_SEG
    pop     es
    mov     bx, 0                               ; copy root dir
    call    ReadSectors                         ; read in directory table
    pop     es
    popa                                        ; restore registers and return
    ret

;*******************************************
; LoadFAT ()
;   - Loads FAT table to 0x7c00
;
;   Parm/ ES:DI => Root Directory Table
;*******************************************

LoadFAT:

    pusha                           ; store registers
    push    es

     ; compute size of FAT and store in "cx"
     
    xor     ax, ax
    mov     al, BYTE [bpbNumberOfFATs]          ; number of FATs
    mul     WORD [bpbSectorsPerFAT]             ; sectors used by FATs
    mov     cx, ax

     ; compute location of FAT and store in "ax"

    mov     ax, WORD [bpbReservedSectors]

     ; read FAT into memory (Overwrite our bootloader at 0x7c00)

    push    word FAT_SEG
    pop     es
    xor     bx, bx
    call    ReadSectors
    pop     es
    popa                            ; restore registers and return
    ret
    
;*******************************************
; FindFile ()
;   - Search for filename in root table
;
; parm/ DS:SI => File name
; ret/ AX => File index number in directory table. -1 if error
;*******************************************

FindFile:

    push    cx                      ; store registers
    push    dx
    push    bx
    mov bx, si                      ; copy filename for later

     ; browse root directory for binary image

    mov     cx, WORD [bpbRootEntries]           ; load loop counter
    mov     di, ROOT_OFFSET                     ; locate first root entry at 1 MB mark
    cld                         ; clear direction flag

.LOOP:
    push    cx
    mov     cx, 11                  ; eleven character name. Image name is in SI
    mov si, bx                      ; image name is in BX
    push    di
     rep  cmpsb                         ; test for entry match
    pop     di
    je      .Found
    pop     cx
    add     di, 32                  ; queue next directory entry
    loop    .LOOP

.NotFound:
    pop bx                      ; restore registers and return
    pop dx
    pop cx
    mov ax, -1                      ; set error code
    ret

.Found:
    pop ax                      ; return value into AX contains entry of file
    pop bx                      ; restore registers and return
    pop dx
    pop cx
    ret

;*******************************************
; LoadFile ()
;   - Load file
; parm/ ES:SI => File to load
; parm/ EBX:BP => Buffer to load file to
; ret/ AX => -1 on error, 0 on success
; ret/ CX => number of sectors read
;*******************************************

LoadFile:

    xor ecx, ecx        ; size of file in sectors
    push    ecx

.FIND_FILE:

    push    bx          ; BX=>BP points to buffer to write to; store it for later
    push    bp
    call    FindFile        ; find our file. ES:SI contains our filename
    cmp ax, -1
    jne .LOAD_IMAGE_PRE
    pop bp
    pop bx
    pop ecx
    mov ax, -1
    ret

.LOAD_IMAGE_PRE:

    sub edi, ROOT_OFFSET
    sub eax, ROOT_OFFSET

    ; get starting cluster

    push    word ROOT_SEG       ;root segment loc
    pop es
    mov dx, WORD [es:di + 0x001A]; DI points to file entry in root directory table. Refrence the table...
    mov WORD [cluster], dx  ; file's first cluster
    pop bx          ; get location to write to so we dont screw up the stack
    pop es
    push    bx          ; store location for later again
    push    es
    call    LoadFAT

.LOAD_IMAGE:

    ; load the cluster

    mov ax, WORD [cluster]  ; cluster to read
    pop es          ; bx:bp=es:bx
    pop bx
    call    ClusterLBA
    xor cx, cx
    mov     cl, BYTE [bpbSectorsPerCluster]
    call    ReadSectors
    pop ecx
    inc ecx         ; add one more sector to counter
    push    ecx
    push    bx
    push    es
    mov ax, FAT_SEG     ;start reading from fat
    mov es, ax
    xor bx, bx

    ; get next cluster

    mov     ax, WORD [cluster]  ; identify current cluster
    mov     cx, ax          ; copy current cluster
    mov     dx, ax
    shr     dx, 0x0001      ; divide by two
    add     cx, dx          ; sum for (3/2)

    mov bx, 0           ;location of fat in memory
    add bx, cx
    mov dx, WORD [es:bx]
    test    ax, 0x0001      ; test for odd or even cluster
    jnz .ODD_CLUSTER

.EVEN_CLUSTER:

    and dx, 0000111111111111b   ; take low 12 bits
    jmp .DONE

.ODD_CLUSTER:

    shr dx, 0x0004      ; take high 12 bits

.DONE:

    mov WORD [cluster], dx
    cmp dx, 0x0ff0      ; test for end of file marker
    jb  .LOAD_IMAGE

.SUCCESS:
    pop es
    pop bx
    pop ecx
    xor ax, ax
    ret

%endif      ;__FAT12_INC_67343546FDCC56AAB872_INCLUDED__

依赖于floppy16.inc:


;*******************************************************
;
;   Floppy16.inc
;       Floppy drive interface routines
;
;   OS Development Series
;*******************************************************

%ifndef __FLOPPY16_INC_67343546FDCC56AAB872_INCLUDED__
%define __FLOPPY16_INC_67343546FDCC56AAB872_INCLUDED__

bits    16

bpbOEM          db "My OS   "
bpbBytesPerSector:      DW 512
bpbSectorsPerCluster:   DB 1
bpbReservedSectors:     DW 1
bpbNumberOfFATs:    DB 2
bpbRootEntries:     DW 224
bpbTotalSectors:    DW 2880
bpbMedia:       DB 0xf0  ;; 0xF1
bpbSectorsPerFAT:   DW 9
bpbSectorsPerTrack:     DW 18
bpbHeadsPerCylinder:    DW 2
bpbHiddenSectors:   DD 0
bpbTotalSectorsBig:     DD 0
bsDriveNumber:          DB 0
bsUnused:       DB 0
bsExtBootSignature:     DB 0x29
bsSerialNumber:         DD 0xa0a1a2a3
bsVolumeLabel:          DB "MOS FLOPPY "
bsFileSystem:           DB "FAT12   "

datasector  dw 0x0000
cluster     dw 0x0000

absoluteSector db 0x00
absoluteHead   db 0x00
absoluteTrack  db 0x00

;************************************************;
; Convert CHS to LBA
; LBA = (cluster - 2) * sectors per cluster
;************************************************;

ClusterLBA:
          sub     ax, 0x0002                          ; zero base cluster number
          xor     cx, cx
          mov     cl, BYTE [bpbSectorsPerCluster]     ; convert byte to word
          mul     cx
          add     ax, WORD [datasector]               ; base data sector
          ret

;************************************************;
; Convert LBA to CHS
; AX=>LBA Address to convert
;
; absolute sector = (logical sector / sectors per track) + 1
; absolute head   = (logical sector / sectors per track) MOD number of heads
; absolute track  = logical sector / (sectors per track * number of heads)
;
;************************************************;

LBACHS:
          xor     dx, dx                              ; prepare dx:ax for operation
          div     WORD [bpbSectorsPerTrack]           ; calculate
          inc     dl                                  ; adjust for sector 0
          mov     BYTE [absoluteSector], dl
          xor     dx, dx                              ; prepare dx:ax for operation
          div     WORD [bpbHeadsPerCylinder]          ; calculate
          mov     BYTE [absoluteHead], dl
          mov     BYTE [absoluteTrack], al
          ret


;************************************************;
; Reads a series of sectors
; CX=>Number of sectors to read
; AX=>Starting sector
; ES:EBX=>Buffer to read to
;************************************************;

ReadSectors:
     .MAIN
          mov     di, 0x0005                          ; five retries for error
     .SECTORLOOP
          push    ax
          push    bx
          push    cx
          call    LBACHS                              ; convert starting sector to CHS
          mov     ah, 0x02                            ; BIOS read sector
          mov     al, 0x01                            ; read one sector
          mov     ch, BYTE [absoluteTrack]            ; track
          mov     cl, BYTE [absoluteSector]           ; sector
          mov     dh, BYTE [absoluteHead]             ; head
          mov     dl, BYTE [bsDriveNumber]            ; drive
          int     0x13                                ; invoke BIOS
          jnc     .SUCCESS                            ; test for read error
          xor     ax, ax                              ; BIOS reset disk
          int     0x13                                ; invoke BIOS
          dec     di                                  ; decrement error counter
          pop     cx
          pop     bx
          pop     ax
          jnz     .SECTORLOOP                         ; attempt to read again
          int     0x18
     .SUCCESS
          pop     cx
          pop     bx
          pop     ax
          add     bx, WORD [bpbBytesPerSector]        ; queue next buffer
          inc     ax                                  ; queue next sector
          loop    .MAIN                               ; read next sector
          ret

%endif      ;__FLOPPY16_INC_67343546FDCC56AAB872_INCLUDED__

这两个文件不是我的代码,它们取自 Brokenthorn 说明。

终于io.inc:

;************************;
; Parameters:            ;
;   si => string pointer ;
;************************;
bits 16
%macro BiosPrint 1
    mov si, word %1
    call _BiosPrint
%endmacro

_BiosPrint:
    pusha
    .loop:
        lodsb
        or al, al
        jz .done
        mov ah, 0x0E
        int 0x10
        jmp .loop
    .done:
    popa
    ret

EDIT2 这是组织的完整项目的回购:https://github.com/Bonfra04/BonsOS

使用像 BOCHS 这样的调试器

我强烈建议使用 BOCHS 调试实模式代码,尤其是引导加载程序和内核开发的早期阶段。在 *nix 类型的系统上,您可以使用以下命令启动 BOCHS:

bochs -f /dev/null 'floppya: 1_44=BonsOS.img, status=inserted' 'boot: a'

然后在bootloader开始处设置断点,开始执行:

b 0x7c00
c

BOCHS步进使用说明;追踪;显示段寄存器;显示通用寄存器等可以在 BOCHS documentation.

中找到

问题

您似乎在某些时候更改了代码以不同方式处理段并引入了一些错误。您也在搜索错误的文件名。 FAT12 文件名全部以大写形式存储,长度为 11 个字节(文件名用空格填充 8 个字符)后跟 3 个字母的扩展名。您的代码正在寻找:

ImageName db "loader bin"    

什么时候应该是:

ImageName db "LOADER  BIN"  ; 2 spaces between LOADER and BIN

调用 LoadFile 时,您可以这样设置调用:

xor ebx, ebx
mov bp, secondStage
mov si, ImageName
call LoadFile

BX:BP 应该是要将 stage2 读入内存的 segment:offset 地址。应该是:

mov bx, Mem.Loader1.Segment
mov bp, secondStage
mov si, ImageName
call LoadFile

您似乎将 FindFile 修改为使用 ROOT_OFFSET,而 LoadFatLoadRoot 您使用 ROOT_SEG。您的代码最终为 CMPSB 指令提供了不正确的 DS:SIES:DI 值,因此您最终从错误的内存地址。我修改了您的 FindFile 代码以使用 ROOT_SEG 并最终得到如下内容:

FindFile:

    push    es                           ; Save ES
    push    cx                           ; store registers
    push    dx
    push    bx
    mov bx, si                           ; copy filename for later

     ; browse root directory for binary image
    mov ax, ROOT_SEG                     ; Set ES to ROOT_SEG not 0
    mov es, ax

    mov     cx, WORD [bpbRootEntries]    ; load loop counter
    xor     di, di                       ; Start at 0 offset from ES (ROOT_SEG)
; Remove   mov     di, ROOT_OFFSET       ; locate first root entry
    cld                                  ; clear direction flag

.LOOP:
    push    cx
    mov     cx, 11                       ; eleven character name. Image name is in SI
    mov si, bx                           ; image name is in BX
    push    di
     rep  cmpsb                          ; test for entry match
    pop     di
    je      .Found
    pop     cx
    add     di, 32                       ; queue next directory entry
    loop    .LOOP

.NotFound:
    pop bx                               ; restore registers and return
    pop dx
    pop cx
    pop es                               ; Restore ES
    mov ax, -1                           ; set error code
    ret

.Found:
    pop ax                               ; return value into AX contains entry of file
    pop bx                               ; restore registers and return
    pop dx
    pop cx
    pop es                               ; Restore ES
    ret

然后你需要从 .LOAD_IMAGE_PRE 的开头删除调整 EDI 和 EAX 的 2 行,所以它应该以:

开头
.LOAD_IMAGE_PRE:

    ; get starting cluster

    push    word ROOT_SEG                ; root segment loc

您没有提供 loader.asm 文件,所以作为示例,我使用它进行测试:

org 0x200
bits 16

jmp start

%include "include/io.inc" 

start:
    BiosPrint hello
    jmp $

hello: db "Hello, world!", 0

我使用了 org 0x200 因为你使用了从你的引导加载程序的近跳转来达到这个并且你的引导加载程序使用的是 0x07c0 的 CS。这意味着 stage2 所需的偏移量 (ORG) 仍然相对于 0x07c0,这就是我使用 0x200 的原因。 0x07c0:0x0200 是物理地址 0x07e00,它是引导加载程序之后的物理地址。

当我在 BOCHS 中 运行 它时,我得到这个输出: