3

我目前正在研究用 x86 NASM 程序集编写的引导加载程序,旨在从 FAT16 格式化磁盘加载内核(R.BIN)。它并没有这样做,在合并了用于调试的短消息(A、B、C、D、J、!;涉及它们的行标有星号)之后,我发现引导加载程序一直到点它完美地跳转到加载的内核,尽管它没有跳转到内核,而是似乎再次加载自己。

为了测试引导加载程序,我有一个已安装的空白映像,我一直在使用以下命令写入引导加载程序:( dd if=LOADER.BIN of=/dev/loop0 我还复制了 R.BIN 到它)之后,我将保存这个新映像,并在虚拟盒子。

这是我的完整代码:

BITS 16

jmp main                     ; Jump to main bootloader
nop                                 ; Pad out remaining bytes until boot descriptor

; Disk descriptor

OEM_name            db "MARSHMAL"   ; Disk label
bytes_sector        dw 0x0200       ; Bytes per sector
sectors_cluster     db 0x01         ; Sectors per cluster
sectors_record      dw 0x0001       ; Sectors reserved for boot record
fats                db 0x02         ; Number of file allocation tables
max_root_entries    dw 0x0200       ; Max number of root entries
sectors             dw 0x0B40       ; Number of sectors
medium_type         db 0xF0         ; Type of medium (removable or fixed?)
sectors_fat         dw 0x0009       ; Sectors per file allocation table
sectors_track       dw 0x0012       ; Sectors per track
heads               dw 0x0002       ; Number of heads
hidden_sectors      dd 0x00000000   ; Number of sectors before partition
total_sectors       dd 0x00000000   ; Number of sectors in medium (zero because 2B != 0)
drive_number        db 0x00         ; Drive number (for BIOS int 0x13)
drive_signature     db 0x00         ; NOT USED
ext_signature       db 0x29         ; Extended boot signature
volume_serial       dd 0x00000000   ; Volume's serial number
volume_label        db "MARSHMALLOW"; Volume label
fs_type             db "FAT16   "   ; Filesystem type

main:
    mov ax, 0x07C0
    add ax, 0x0220
    mov ss, ax
    mov sp, 0x1000                  ; 4K of stack
    mov ax, 0x07C0
    mov ds, ax
    mov byte [drive_num], dl        ; Save boot drive number

    mov bx, ds
    mov es, bx                      ; Set ES to Data Segment
    mov bx, disk_buffer             ; Set BX to disk buffer
    mov ax, 0x13                    ; Start of root = sectors_record + fats * sectors_fat = 1 + 2 * 9 = logical 19
    call ls_hts                     ; Convert logical 19 to head, track and sector
    mov al, 0x0E                    ; Number of sectors in root = max_root_entries * 32 / bytes_sector = 224 * 32 / 512 = 14
    mov si, a                       ; Read root dir message*
    call print_str                  ; Print!*

.read_disk:
    int 0x13                        ; BIOS disk interrupt
    jnc .search_init                ; If successful, get ready to search the disk
    call reset_disk                 ; Otherwise, reset the disk
    jmp .read_disk                  ; And retry

.search_init:
    mov si, success                 ; Success message*
    call print_str                  ; Print!*
    mov ax, ds
    mov es, ax                      ; Move data segment to extra segment
    mov di, disk_buffer             ; Location of disk buffer (ES:DI will be the location of the root entry we will be checking)
    mov si, r_name                  ; Location of filename of R (DS:SI will be the location of the string to compare to the root entry)
    mov bx, 0x00                    ; Start at root entry 0
    push si                         ; Push*
    mov si, b                       ; Search message*
    call print_str                  ; Print!
    pop si                          ; Pop*

.check_entry:
    mov cx, 0x0B                    ; Compare the first 11 bytes
    push si                         ; Push filename location to stack
    rep cmpsb                       ; Compare the two strings
    pop si                          ; Restore filename location to SI
    je .found_entry                 ; If equal, we found the root entry!
    add di, 0x15                    ; Otherwise, move to next entry
    inc bx                          ; Number of next entry
    cmp bx, max_root_entries        ; Have we gone through all root entries?
    jg .missing                     ; If so, R is missing
    jmp .check_entry                ; Otherwise, look at this next entry

.found_entry:
    mov si, success                 ; Success message*
    call print_str                  ; Print!*
    mov ax, word [es:di+0x0F]
    mov word [cluster], ax          ; Move starting cluster number to our spot in memory

    mov bx, disk_buffer             ; ES:BX points to disk buffer
    mov ax, 0x01                    ; 1st FAT begins at logical sector 1
    call ls_hts                     ; Convert to head, track and sector
    mov al, sectors_fat             ; Read all sectors in FAT
    mov si, c                       ; Read FAT message*
    call print_str                  ; Print!*

.read_fat:
    int 0x13                        ; BIOS disk interrupt
    jnc .read_cluster               ; If successful, load the first cluster of the file
    call reset_disk                 ; Otherwise, reset the disk
    jmp .read_fat                   ; And try again

.read_cluster:
    mov si, d                       ; Attempt to read cluster message*
    call print_str                  ; Print!*
    mov ax, 0x2000
    mov es, ax                      ; Segment into which we will load R
    mov bx, word [buffer_pointer]   ; Spot into which we will load this cluster
    mov ax, word [cluster]          ; Cluster to read
    add ax, 0x1F                    ; Convert to logical sector
    call ls_hts                     ; Convert to head, track and sector
    mov al, sectors_cluster         ; Read the number of sectors in 1 cluster
    int 0x13                        ; BIOS disk interrupt
    jnc .find_next_cluster          ; If successful, find the next cluster
    call reset_disk                 ; Otherwise, reset the disk
    jmp .read_cluster               ; And try again

.find_next_cluster:
    mov si, success                 ; Success message*
    call print_str                  ; Print!*
    mov ax, word [cluster]          ; Location of current cluster
    mov bx, 0x02                    ; There are two bytes per entry in FAT16
    mul bx                          ; The memory location of CLUSTER should fit in AL
    mov si, disk_buffer             ; Location of start of FAT
    add si, ax                      ; Add the number of bytes until current cluster
    mov ax, word [ds:si]            ; Number of next cluster
    mov word [cluster], ax          ; Store this
    cmp ax, 0xFFF8                  ; Check whether this next cluster is an end-of-file marker
    jae .jump                       ; If it is, we have fully loaded the kernel
    jge .jump
    add word [buffer_pointer], 0x0200 ; Otherwise, increment the buffer pointer a sector length
    jmp .read_cluster               ; And load it into memory

.jump:
    mov si, loaded                  ; Loaded kernel message
    call print_str                  ; Print!
    mov dl, byte [drive_num]        ; Make the boot drive number accessible to R
    jmp 0x2000:0x0000               ; Jump to R's location!

.missing:
    mov si, m_r_missing             ; Display the missing message
    call rsod                       ; Display it in a Red Screen of Death

reset_disk:
    pusha                           ; Push register states to stack
    mov ax, 0x00                    ; RESET disk
    mov dl, byte [drive_num]        ; Boot drive number
    int 0x13                        ; BIOS disk interrupt
    jc .disk_fail                   ; If failed, fatal error and reboot
    popa                            ; Restore register states
    ret                             ; And retry

.disk_fail:
    mov si, m_disk_error            ; Display the disk error message
    call rsod                       ; Display it in a Red Screen of Death

print_str:                          ; Prints string pointed to by REGISTER SI to cursor location (si=str)
    pusha                           ; Push register states to stack
    mov ah, 0x0E                    ; BIOS will PRINT

.repeat:
    lodsb                           ; Load next character from SI
    cmp al, 0x00                    ; Is this a null character?
    je .ret                         ; If it is, return to caller
    int 0x10                        ; Otherwise, BIOS interrupt
    jmp .repeat                     ; Do this again

.ret:
    mov ah, 0x00                    ; Read keyboard buffer
    int 0x16                        ; BIOS keyboard interrupt      
    popa                            ; Restore register states
    ret                             ; Return to caller

ls_hts:                             ; Convert logical sector to head, track, and sector configuration for int 0x13 (AX = logical sector)
    mov dx, 0x00                    ; Upper word of dividend is 0
    div word [sectors_track]        ; Divide to find the number of tracks before this
    mov cl, dl                      ; The remainder is the number of the sector within the track
    add cl, 0x01                    ; Sectors start at 1, not 0
    mov dx, 0x00                    ; Upper word of dividend is 0
    div word [heads]                ; Divide by number of heads/sides
    mov dh, dl                      ; The remainder is the head number (it should only take up the lower half of DX)
    mov ch, al                      ; The quotient is the track number (it should only take up the lower half of CX)
    mov dl, byte [drive_num]        ; Boot drive number
    mov ah, 0x02                    ; READ disk sectors
    ret                             ; Return to caller

rsod:                               ; Red Screen of Death (SI = line to print)
    mov al, 0x20                    ; SPACE
    mov bh, 0x00                    ; Page 0
    mov bl, 0x40                    ; Red background
    mov cx, 0x50                    ; Enough to fit the screen width

.repeat:
    mov ah, 0x09                    ; Write character and attribute
    int 0x10                        ; BIOS VGA interrupt
    mov ah, 0x03                    ; Get cursor position
    int 0x10                        ; BIOS VGA interrupt
    cmp dh, 0x1A                    ; Have we gone all the way down the screen?
    jge .write                      ; If we have, return to caller
    inc dh                          ; Otherwise, next row down
    mov ah, 0x02                    ; Set cursor position
    int 0x10                        ; BIOS VGA interrupt
    jmp .repeat                     ; Do this again for the next line

.write:
    mov ah, 0x02                    ; Set cursor position
    mov dh, 0x01                    ; Row 1
    mov dl, 0x03                    ; Col 3
    int 0x10                        ; BIOS VGA interrupt
    push si                         ; Push line to stack
    mov si, fatal                   ; Prepare to display "FATAL" message
    call print_str                  ; Print!
    pop si                          ; Restore line and prepare to print it
    call print_str                  ; Print!
    mov si, press_a_key             ; Prepare to display prompt
    call print_str                  ; Print!
    int 0x19                        ; Reboot

data:
    r_name          db "R       BIN"        ; Filename of R
    cluster         dw 0x0000               ; Cluster that we are working with
    buffer_pointer  dw 0x0000               ; Pointer to offset of buffer
    drive_num       db 0x00                 ; Boot drive number
    fatal           db "FATAL: ", 0x00      ; Fatal error message
    press_a_key     db "! Press a key", 0x00; Instruct the user to press a key and reboot
    m_r_missing     db "R missing", 0x00    ; Missing message
    m_disk_error    db "Disk failed", 0x00  ; Disk error message
    a               db "A", 0x00            ; About to read root dir*
    b               db "B", 0x00            ; About to search root dir*
    c               db "C", 0x00            ; About to read FAT*
    d               db "D", 0x00            ; About to attempt cluster read*
    success         db "!", 0x00            ; Success!*
    loaded          db "J", 0x00            ; Loaded R message*

    times 510-($-$$) db 0x00        ; Pad remainder of boot sector
    sig             dw 0xAA55       ; Boot signature

disk_buffer:                        ; Space in memory for loading disk contents

正如我所说,引导加载程序似乎正在按计划执行所有操作,直到跳转到内核,当它在调试消息开始时重新开始并再次循环。

4

2 回答 2

3
mov di, disk_buffer             ; Location of disk buffer (ES:DI will be the location of the root entry we will be checking)
mov si, r_name                  ; Location of filename of R (DS:SI will be the location of the string to compare to the root entry)
mov bx, 0x00                    ; Start at root entry 0
push si                         ; Push*
mov si, b                       ; Search message*
call print_str                  ; Print!
pop si                          ; Pop*

.check_entry:
mov cx, 0x0B                    ; Compare the first 11 bytes
push si                         ; Push filename location to stack
rep cmpsb                       ; Compare the two strings
pop si                          ; Restore filename location to SI
je .found_entry                 ; If equal, we found the root entry!
add di, 0x15      !!!!!!!!!!!!  ; Otherwise, move to next entry
inc bx                          ; Number of next entry
cmp bx, max_root_entries

最好写信repe cmpsb以更好地表明您正在比较平等!

.check_entry 中,您总是将 21 添加到DI. 这是不正确的!如果repe cmpsb报告 NotEqual 那么DI可能在任何地方。您需要DI放回 32 字节记录的开头,然后添加 32 以转到下一个根记录。

.check_entry:
  mov cx, 0x0B               ; Compare the first 11 bytes
  push si di                 ; Push filename location to stack
  repe cmpsb                 ; Compare the two strings
  pop di si                  ; Restore filename location to SI
  je .found_entry            ; If equal, we found the root entry!
  add di, 0x20               ; Otherwise, move to next entry
  inc bx                     ; Number of next entry
  cmp bx, max_root_entries
  jg .missing                ; If so, R is missing
  jmp .check_entry           ; Otherwise, look at this next entry

.found_entry:
  mov si, success            ; Success message*
  call print_str             ; Print!*
  mov ax, [es:di+0x1A]
  mov [cluster], ax

请注意更新您以后创建的根记录中的所有引用。
上面我已经[es:di+0x0F]变成了现在正确的形式[es:di+0x1A]

于 2019-07-10T15:57:11.577 回答
2
cmp bx, max_root_entries        ; Have we gone through all root entries?
mov al, sectors_fat             ; Read all sectors in FAT
mov al, sectors_cluster         ; Read the number of sectors in 1 cluster

以上都是错误的。您正在使用 NASM,因此您需要编写方括号才能从内存中获取某些内容。如果没有括号,您将获得地址本身而不是内容。

cmp bx, [bpbRootEntries]
...

我的 FAT 文件系统规范提到:

不存在少于 4085 个簇的 FAT16 卷... ...如果您尝试制作违反此规则的 FAT 卷,Microsoft 操作系统将无法正确处理它们,因为它们会认为该卷具有与您认为的不同类型的 FAT。

您使用的磁盘有 2880 个扇区,因此簇太少而无法识别为 FAT16。也许您的 Linux 操作系统会很好地识别它,但也许您的mount命令应该使用类似fat=16.

那些相同的 2880 个扇区并看到 bpbSectorsPerCluster=0x01,将要求 FAT 每 12 个扇区。尽管如此,我看到 bpbFATSize16=0x0009 这是我对 FAT 12的期望值。


Boot Sector 和 BPB结构有很多信息,您的程序应该使用这些信息,而不是依赖于您未能正确初始化的常量。但是,在我的回答中,为了简单起见,我将继续使用这些(更正的)常量!

FirstFATSecNum = bpbHiddenSectors + bpbReservedSectors
               = 0x00000000 + 0x0001
               = 1

FirstRootDirSecNum = FirstFATSecNum + bpbNumberOfFATs * bpbFATSize16
                   = 1 + 0x02 * 0x0009
                   = 19

RootDirSectors = ((bpbRootEntries * 32) + (bpbBytesPerSector - 1)) / bpbBytesPerSector
               = ((0x0200 * 32) + (0x200 - 1)) / 0x200
               = 32

FirstDataSecNum = FirstRootDirSecNum + RootDirSectors
                = 19 + 32
                = 51

重要的是要看到 RootDir 占用 32 个扇区或 16384 个字节!您的程序设置了一个只有 8192 字节的 DiskBuffer。这是基于您错误的假设,即 RootDir 包含 224 个条目,这是 FAT 12的正常计数。


转换逻辑扇区号的例程可以更恰当地命名为“SetupCHS”。这更好地表达了它不仅仅是一个简单的转换,它还强调了 Cylinder、Head 和 Sector 之间的相对重要性。例如,将它与 HMS 比较小时、分钟和秒。所以从最重要的(C)到最不重要的(S)。

; IN (ax) OUT (cx,dx) MOD (ax)
SetupCHS:
    cwd
    div  word [bpbSectorsPerTrack]
    mov  cl, dl
    inc  cx                        ; Sector number
    cwd
    div  word [bpbNumberOfHeads]
    mov  dh, dl                    ; Head number
    mov  ch, al                    ; Cylinder number
    mov  dl, [drive_num]           ; Drive number
    ret

为清楚起见,您不应在AH此处设置功能编号,而应靠近您有int 0x13说明的位置!


许多 BIOS 无法一次读取/写入多个扇区,尤其是当它们必须跨越某个边界时,无论是另一个磁头还是另一个柱面。
这就是为什么大多数谨慎的程序员会使用 1 扇区读/写循环。

; Loading the RootDir
    mov  bx, DiskBuffer            ; ES:BX
    mov  ax, 19                    ; FirstRootDirSecNum
    call SetupCHS                  ; -> CX DX (AX)
    mov  bp, 32                    ; RootDirSectors
.Next:
    call ReadOneSector             ; -> (AX DI)
    add  bx, [bpbBytesPerSector]
    dec  bp
    jnz  .Next

因为有时磁盘访问可能会因为一些无害的原因而失败,所以我们重复该操作的次数是有限的。5是一个不错的选择。您的程序选择无限重复,我认为这是有问题的。

; IN (es:bx,cx,dx) OUT () MOD (ax,di)
ReadOneSector:
    mov  di, 5
.ReTry:
    mov  ax, 0x0201                ; BIOS.ReadSector
    int  0x13                      ; -> CF
    jnc  .OK
    dec  di
    jz   DiskFail
    call ResetDisk
    jmp  .Retry
.OK:
    ret

加载第一个 FAT 当然是一个类似的过程。
对于加载文件集群,你很幸运,因为每个集群在这个磁盘上只有一个扇区。不需要循环。


搜索 RootDir。Fifoernik 已经告诉过您使用rep cmpsb. 使用rep而不是repe让你认为DI寄存器总是会提前 11,实际上重复可以提前结束。
这里的其他问题是:

  • 您不测试 RootDir 条目的第一个字节。它有您必须检查的重要信息。如果为 0,则您已到达 RootDir 的末尾,继续评估条目是没有用的。如果它是 0xE5,则该条目是免费的,您应该跳过它。

  • 您不测试属性字节。该条目很可能是用于卷 ID 的目录。两者都不是您要查找的文件,因此请跳过该条目!

接下来应用上述内容:

    mov  di, DiskBuffer          ; ES:DI but knowing that ES=DS
    mov  bx, [bpbRootEntries]
.CheckEntry:
    cmp  byte [di], 0
    je   .FileNotFound
    cmp  byte [di], 0xE5
    je   .SkipEntry
    test byte [di+11], 00011000b ; DIR | VOL
    jnz  .SkipEntry              ; Is not a file
    mov  si, r_name
    mov  cx, 11
    push di
    repe cmpsb
    pop  di
    je   .FileFound
.SkipEntry:
    add  di, 32
    dec  bx                      ; Counting downward is easier
    jnz  .CheckEntry
.FileNotFound
    jmp  Missing
.FileFound:
    mov  ax, [di+0x1A]
    mov  [cluster], ax

跟随集群链。以下是将簇号 N 转换为其第一个/唯一扇区的扇区号的公式:

FirstSectorOfCluster = FirstDataSecNum + (N - 2) * bpbSectorsPerCluster
                     = 51 + (N - 2) * 0x01
                     = N + 49

由于奇怪的jge .jump. 所有好的簇数都会更大cmp ax, 0xFFF8以签名方式解释的指令(这是什么jge)读取cmp ax, -8. 因此,从 2 到几千的所有簇数都将更大。

提示:为了能够加载大文件(大于 64KB),您应该更改ES段寄存器并将偏移量保持BX在 0。

    mov  ax, 0x2000
    xor  bx, bx
LoadNextCluster:
    add  ax, bx
    mov  es, ax
    xor  bx, bx
    mov  ax, [cluster]
    add  ax, 49
    call SetupCHS                ; -> CX DX (AX)
    call ReadOneSector           ; -> (AX DI)
    mov  si, [cluster]
    shl  si, 1                   ; You don't need MUL to calculate x2
    mov  ax, [DiskBuffer+si]     ; FAT gives number of next cluster
    mov  [cluster], ax
    cmp  ax, 0xFFF8              ; End-of-file marker ?
    mov  ax, es
    mov  bx, 512 / 16
    jb   LoadNextCluster
Loaded:                          ; We have fully loaded the kernel

处理 BIOS 显示功能的程序部分有一些问题,但我认为这不是现在重要的。
如果您设法编辑了所有这些更改,但仍然没有得到结果,您可以在修改后的程序中发布后续问题。(如果你今天这样做,我可以再看一次......)
永远不要将你从答案中获得的信息合并到你原来的问题中,除非你当然把它写在一个单独的部分,但仍然如此。重大更改要求提出后续问题(单独发布)。

于 2019-07-12T22:45:47.783 回答