Bootloader自行加载而不是内核加载

时间:2019-07-09 03:28:22

标签: assembly memory x86-16 bootloader fat16

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

要测试引导加载程序,我有一个已装载的空白映像,该映像已通过使用以下命令写入引导加载程序: dd if=LOADER.BIN of=/dev/loop0 (我也将R.BIN复制到了它) 之后,我将保存此新图像,并在Virtualbox中对其进行测试。

这是我完整的代码:

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

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

2 个答案:

答案 0 :(得分:2)

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报告不等于,那么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]

答案 1 :(得分:1)

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 值。


引导扇区和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”。这更好地表达了它所做的不仅仅是简单的转换,而且还强调了圆柱,标头和扇形之间的相对重要性。例如将其与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,程序的这一部分将永远不会加载文件的1个以上的簇。所有 good 群集编号将为 Greater 。当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显示功能有关的部分有其自身的一些问题,但我认为目前这不是很重要。
如果您设法编辑所有这些更改,但仍然没有得到结果,则可以对修订后的程序发布后续问题。 (如果您今天做的话,我可以再看看...)
永远不要将从答案中获得的信息整合到原始问题中,除非您当然将其写在单独的部分中,但是仍然要这样做。重大更改要求跟进问题(单独发布)。