Загрузка самого загрузчика, а не ядра - PullRequest
2 голосов
/ 09 июля 2019

В настоящее время я работаю над загрузчиком, написанным на сборке x86 NASM, предназначенной для загрузки ядра (R.BIN) с диска, отформатированного в FAT16. Это не было сделано, и после включения коротких сообщений для отладки (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 ]

2 голосов
/ 10 июля 2019
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].

1 голос
/ 13 июля 2019
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 упоминается:

Нет такой вещи, как том FAT16, который имеет менее 4085 кластеров ... ... Если вы попытаетесь создать том 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 байта! Ваша программа настроила DiskBuffer размером всего 8192 байта. Это было основано на вашем неверном предположении, что 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, вход бесплатный, и вы должны просто пропустить его.

  • Вы не проверяете байт атрибута. Запись вполне может быть для каталога для идентификатора тома. Также не может быть файл, который вы ищете, поэтому пропустите запись!

Далее применяется вышеупомянутое:

    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

Эта часть вашей программы никогда не загрузит более 1 кластера файла из-за странного jge .jump. Все номера кластеров good будут Greater . Инструкция cmp ax, 0xFFF8 при интерпретации со знаком (что делает jge) гласит cmp ax, -8. Таким образом, все номера кластеров от 2 до нескольких тысяч будут больше.

Совет: чтобы можно было загружать большой файл (размером более 64 КБ), вы должны изменить регистр сегмента 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, имеют несколько собственных проблем, но я думаю, что это не то, что сейчас важно.
Если вам удастся отредактировать все эти изменения и до сих пор не получить результаты, вы можете опубликовать дополнительный вопрос с исправленной программой. (Если вы сделаете это сегодня, я могу еще раз взглянуть на это ...)
Никогда не включайте информацию, полученную из ответа, в свой первоначальный вопрос, если, конечно, вы не напишите это в отдельном разделе, но все же. Основные изменения требуют дополнительного вопроса (отдельный пост).

...