Пользовательский загрузчик не находит ядро - PullRequest
1 голос
/ 09 июня 2019

Я работал над хобби-загрузчиком и ядром, но загрузчик никогда не находит ядро, несмотря ни на что.Вот загрузчик:

%define BUFFER_SEG 0x2000
%define BUFFER_OFF 0x0000
%define LOAD_SEG 0x1000
%define LOAD_OFF 0x0000

[bits 16]
[org 0x7c00]

jmp short start
nop

;DISK DESCRIPTION(BIOS PARAMETER BLOCK)
OEMLabel        db "BOOT    "
BytesPerSector      dw 512
SectorsPerCluster   db 1
ReservedForBoot     dw 1
NumberOfFats        db 2
RootDirEntries      dw 224      ; Number of entries in root dir
                    ; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors      dw 2880
MediumByte      db 0F0h
SectorsPerFat       dw 9
SectorsPerTrack     dw 18       ; Sectors per track (36/cylinder)
Sides           dw 2
HiddenSectors       dd 0
LargeSectors        dd 0
DriveNo         dw 0
Signature       db 0x29 
VolumeID        dd 00000000h
VolumeLabel     db "myOS       "
FileSystem      db "FAT12   "

;BOOTLOADER
start:
    xor ax, ax
    mov ds, ax
    cli
    mov ss, ax
    mov sp, 0x7c00
    cld
    clc
    sti
    mov [drive], dl

load_root:
    mov ax, 19
    call lba_to_hts
    mov ah, 2
    mov al, 14
    mov si, BUFFER_SEG
    mov es, si
    mov bx, BUFFER_OFF
    int 13h
    jc reset
    mov si, load_root_str
    call print

search_file:
    mov di, BUFFER_OFF
    mov cx, word [RootDirEntries]
    xor ax, ax
.loop_search:
    xchg cx, dx
    mov si, filename
    mov cx, 11
    rep cmpsb
    je file_found
    add ax, 32
    mov di, BUFFER_OFF
    add di, ax
    xchg dx, cx
    loop .loop_search
    jmp file_not_found

file_found:
    mov ax, word [es:di+15]
    mov [cluster], ax
    mov ax, 1
    call lba_to_hts
    mov di, BUFFER_OFF
    mov bx, di
    mov ah, 2
    mov al, 9

load_FAT:
    mov si, FAT_str
    call print
    int 13h
    jnc load_file
    call reset
    jnc load_FAT
    jmp disk_error

load_file:
    mov si, load_file_str
    call print
    mov ax, LOAD_SEG
    mov es, ax
    xor bx, bx
    mov ah, 2
    mov al, 1
.load_sector:
    mov ax, word [cluster]
    add ax, 31
    call lba_to_hts
    mov ax, LOAD_SEG
    mov es, ax
    mov bx, word [pointer]
    pop ax
    push ax
    ;stc
    int 13h
    jnc next_cluster
    call reset
    jmp .load_sector

next_cluster:
    mov ax, [cluster]
    xor dx, dx
    mov bx, 3
    mul bx
    mov bx, 2
    div bx
    mov si, BUFFER_OFF
    add si, ax
    mov ax, word [ds:si]
    or dx, dx
    jz .even
.odd:
    shr ax, 4
    jmp short finish_load
.even:
    and ax, 0FFFh

finish_load:
    mov word [cluster], ax
    cmp ax, 0FF8h
    jae .jump_to_file
    add word [pointer], 512
    jmp next_cluster
.jump_to_file:
    pop ax
    mov dl, byte [drive]
    jmp LOAD_SEG:LOAD_OFF

;SUBROUTINES
file_not_found:
    mov si, not_found_str
    call print
    jmp reboot
print:
    pusha
    mov ah, 0x0E
.next:
    lodsb
    cmp al,0
    je .done
    int 0x10
    jmp .next
.done:
    popa
    ret
lba_to_hts:
    push ax
    push bx
    mov bx, ax
    xor dx, dx
    div word [SectorsPerTrack]
    add dl, 1
    mov cl, dl
    mov ax, bx
    xor dx, dx
    div word [SectorsPerTrack]
    xor dx, dx
    div word [Sides]
    mov dh, dl
    mov ch, al
    pop ax
    pop bx
    mov dl, [drive]
    ret
reset:
    mov ah, 0
    int 13h             ;reset disk
    jc disk_error           ;if failed jump to search fail
    ret
disk_error:
    mov si, disk_error_str
    call print
reboot:
    mov si, reboot_pmpt
    call print
    mov ax, 0
    int 16h
    mov ax, 0
    int 19h

;DATA
load_root_str db 'Loading Root',13,10,0
disk_error_str db 'Disk Error!',13,10,0
reboot_pmpt db 'PRESS A KEY TO REBOOT',13,10,0
not_found_str db 'KERNEL NOT FOUND',13,10,0
FAT_str db 'Loading FAT',13,10,0
load_file_str db 'Loading KERNEL',13,10,0
drive dw 0
cluster dw 0
pointer dw 0
filename db 'KERNEL  BIN',0
;PADDING AND SIGNATURE
times (510-($-$$)) db 0x00
dw 0AA55h

Вот ядро:

[bits 16]               ;16-bit binary format

;VECTORS
os_vectors:
    jmp os_main

;KERNEL
os_main:
    cli             ;clear interrupts
    mov ss, ax          ;set stack segment and pointer
    mov sp, 0FFFFh
    sti             ;restore interrupts
    cld             ;set RAM direction(for strings)
    mov ax, 1000h           ;set registers to match kernel location
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov si, hello           ;print welcome
    call print_string
    hlt

;SUBROUTINES
print_string:
    mov ah, 0x0e

.next_char:
    lodsb
    cmp al,0
    je .done_print
    int 0x10
    jmp .next_char

.done_print:
    ret

;DATA
hello db 'Hello',0

;PADDING
times (512-($-$$)) db 0x00

Я дополнил остальную часть сектора, так как я слышал, что некоторые эмуляторы не читают его правильно, если онне один полный размер сектора.Я использую команды

#! bin/bash
cd image
hdiutil create -fs MS-DOS -sectors 2880 floppy
cd ../system
nasm -f bin boot.asm -o boot.bin
nasm -f bin kernel.asm -o kernel.bin
cd ..
dd conv=notrunc if=system/boot.bin of=image/floppy.dmg
dev=`hdid -nomount image/floppy.dmg`
sudo mkdir tmp-loop
sudo cp system/kernel.bin tmp-loop/
sudo mount -t msdos ${dev} tmp-loop
diskutil umount tmp-loop
hdiutil detach ${dev}
sudo rm -rf tmp-loop
hdiutil convert image/floppy.dmg -format UDTO -o image/image.iso

для сборки, а затем эмулирую его в qemu.Я делаю это на Macbook Air.Когда я эмулирую свой загрузчик в qemu, я всегда получаю строку, сообщающую мне, что не может найти ядро.Я не знаю, почему и как это исправить.

1 Ответ

3 голосов
/ 17 июня 2019

По большей части ваш код имеет правильное представление.Основные проблемы в загрузчике находятся в load_file и next_cluster.Также есть ошибка в lba_to_hts.В вашем ядре также есть ошибки, которые нужно исправить.

Считайте, что это серьезная рекомендация - установите копию BOCH и используйте отладчик, а не QEMU.BOCH идеально подходит для начальных загрузчиков, поскольку правильно работает с 20-битным сегментом: смещенная адресация.Для любого реального режима код BOCHs является отличным инструментом.Умение правильно использовать отладчик позволяет увидеть, что находится в регистрах;исследовать память, устанавливать точки останова и т. д. Вы должны быть в состоянии обнаружить ошибки, выявленные в этом ответе, с некоторым опытом.


Проблемы в boot.asm

Ошибка в lba_to_hts можетздесь можно увидеть:

lba_to_hts:
    push ax
    push bx

    ...

    pop ax
    pop bx

Вы нажимаете AX, а затем BX в стеке в начале, но вам нужно вытолкнуть их в порядке в обратном порядке .Это должно быть:

push ax
push bx

...

pop bx
pop ax

В next_cluster у вас есть проблема с этой строкой:

mov ax, word [ds:si]

Вы вычислили смещение в таблице FAT (FAT12), гдеследующий кластер можно найти.Проблема в том, что DS не указывает на сегмент, где таблица FAT находится в памяти, он установлен в 0000h.Вы не можете использовать:

mov ax, word [es:si]

, потому что вы установили ES в сегмент загрузки ядра (LOAD_SEG = 1000h).Вы можете сохранить регистр DS (стек в стек), загрузить DS с BUFFER_SEG.Тогда вы могли бы использовать:

mov ax, word [ds:si]

Затем вам нужно будет восстановить DS, когда next_cluster закончится, POPing старое значение из стека.Следует отметить, что mov ax, word [ds:si] совпадает с mov ax, word [si], за исключением того, что префикс DS будет выводиться в инструкции без необходимости.Если регистры в операнде памяти не включают BP , то доступ к памяти осуществляется неявно через DS , в противном случае это неявно SS .Основное правило: не добавляйте ненужные переопределения, так как они увеличат размер кода.

Я не рекомендую такой подход.Самый простой способ исправить это - поместить BUFFER_OFF в тот же сегмент, что и загрузчик (сегмент 0000h).Существует 32 КБ свободной памяти с 0000h: 8000h до 0000h: 0ffffh.Если вы измените свой код для загрузки структуры FAT и корневого каталога в 0000h: 8000h, то вы сможете получить доступ к данным загрузчика, структуре FAT и записям корневого каталога через DS .При загрузке ядра вы можете переключить ES на LOAD_SEG.

В этом коде есть еще одна проблема:

finish_load:
    mov word [cluster], ax
    cmp ax, 0FF8h
    jae .jump_to_file
    add word [pointer], 512
    jmp next_cluster

Вы проверяете, есть ли у васдостиг последнего кластера для файла, сравнив его с 0FF8h.Если оно меньше 0FF8h, вы добавляете 512 к [pointer], чтобы перейти к следующему смещению в буфере для чтения.Проблема в том, что jmp next_cluster не возвращается, чтобы прочитать следующий кластер!jmp next_cluster должно быть jmp load_sector


В load_file у вас есть этот код:

load_file:
    mov si, load_file_str
    call print
    mov ax, LOAD_SEG
    mov es, ax
    xor bx, bx
    mov ah, 2
    mov al, 1


.load_sector:
    mov ax, word [cluster]
    add ax, 31
    call lba_to_hts
    mov ax, LOAD_SEG
    mov es, ax
    mov bx, word [pointer]
    pop ax
    push ax
    ;stc
    int 13h
    jnc next_cluster
    call reset
    jmp .load_sector

next_cluster:

Непосредственно перед меткой .load_sector вы устанавливаете регистры AX и BX дляInt 13h вызов BIOS.К сожалению, вы закрываете линии AX и BX сразу после метки .load_sector:.Есть также необычный POP / PUSH в середине, который не имеет смысла.Вы можете изменить этот раздел кода следующим образом:

load_file:
    mov si, load_file_str
    call print
    mov ax, LOAD_SEG           ; ES=load segment for kernel
    mov es, ax

load_sector:
    mov ax, word [cluster]     ; Get cluster number to read
    add ax, 33-2               ; Add 31 to cluster since FAT data area
                               ; starts at Logical Block Address (LBA) 33
                               ; and we need to subtract 2 since valid
                               ; cluster numbers start at 2
    call lba_to_hts

    mov bx, word [pointer]     ; BX=Current offset in buffer to read to
    mov ax, 201h               ; AH=2 is read, AL=1 read 1 sector

    ;stc
    int 13h
    jnc next_cluster
    call reset
    jmp load_sector

next_cluster:

Пересмотренная версия кода будет выглядеть следующим образом:

%define BUFFER_OFF 0x8000
%define BUFFER_SEG 0x0000
%define LOAD_SEG 0x1000
%define LOAD_OFF 0x0000

[bits 16]
[org 0x7c00]

jmp short start
nop

;DISK DESCRIPTION(BIOS PARAMETER BLOCK)
OEMLabel        db "BOOT    "
BytesPerSector      dw 512
SectorsPerCluster   db 1
ReservedForBoot     dw 1
NumberOfFats        db 2
RootDirEntries      dw 224     ; Number of entries in root dir
                               ; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors      dw 2880
MediumByte      db 0F0h
SectorsPerFat       dw 9
SectorsPerTrack     dw 18      ; Sectors per track (36/cylinder)
Sides           dw 2
HiddenSectors       dd 0
LargeSectors        dd 0
DriveNo         dw 0
Signature       db 0x29
VolumeID        dd 00000000h
VolumeLabel     db "myOS       "
FileSystem      db "FAT12   "

;BOOTLOADER
start:
    xor ax, ax
    mov ds, ax
    cli
    mov ss, ax
    mov sp, 0x7c00
    sti
    cld
    mov [drive], dl

    mov si, BUFFER_SEG         ; ES=buffer segment. Only has to be set once
    mov es, si
    mov bx, BUFFER_OFF

load_root:
    mov ax, 19                 ; Root directory starts at LBA 19
    call lba_to_hts
    mov ax, (2<<8) | 14        ; Root directory for this media fits in 14 sectors
                               ; Combine 2 moves (AH/AL) into one
                               ; same as 'mov ah, 2' and 'mov al, 14'
    int 13h
    jc reset
    mov si, load_root_str
    call print

search_file:
    mov di, BUFFER_OFF
    mov cx, word [RootDirEntries]
    xor ax, ax
.loop_search:
    xchg cx, dx
    mov si, filename
    mov cx, 11
    rep cmpsb
    je file_found
    add ax, 32
    mov di, BUFFER_OFF
    add di, ax
    xchg dx, cx
    loop .loop_search
    jmp file_not_found

file_found:
    mov ax, word [di+15]       ; Buffer and Bootloader now in same segment DS
                               ; Don't need ES:
    mov [cluster], ax
    mov ax, 1
    call lba_to_hts
    mov bx, BUFFER_OFF
    mov ax, (2<<8) | 9         ; Combine 2 moves (AH/AL) into one
                               ; same as 'mov ah, 2' and 'mov al, 9'    

load_FAT:
    mov si, FAT_str
    call print
    int 13h
    jnc load_file
    call reset
    jnc load_FAT
    jmp disk_error

load_file:
    mov si, load_file_str
    call print
    mov ax, LOAD_SEG           ; ES=load segment for kernel
    mov es, ax

load_sector:
    mov ax, word [cluster]     ; Get cluster number to read
    add ax, 33-2               ; Add 31 to cluster since FAT data area
                               ; starts at Logical Block Address (LBA) 33
                               ; and we need to subtract 2 since valid
                               ; cluster numbers start at 2
    call lba_to_hts
    mov bx, word [pointer]     ; BX=Current offset in buffer to read to
    mov ax, (2<<8) | 1         ; AH=2 is read, AL=1 read 1 sector
                               ; Combine 2 moves (AH/AL) into one
                               ; same as 'mov ah, 2' and 'mov al, 1'
    int 13h
    jnc next_cluster
    call reset
    jmp load_sector

next_cluster:
    mov ax, [cluster]
    xor dx, dx
    mov bx, 3
    mul bx
    mov bx, 2
    div bx
    mov si, BUFFER_OFF
    add si, ax
    mov ax, word [si]
    or dx, dx
    jz .even
.odd:
    shr ax, 4
    jmp short finish_load
.even:
    and ax, 0FFFh

finish_load:
    mov word [cluster], ax
    cmp ax, 0FF8h
    jae .jump_to_file
    add word [pointer], 512    ; We haven't reached end of kernel. Add 512 for next read
    jmp load_sector            ; Go back and load the next sector
.jump_to_file:
    mov dl, byte [drive]
    jmp LOAD_SEG:LOAD_OFF

;SUBROUTINES
file_not_found:
    mov si, not_found_str
    call print
    jmp reboot
print:
    pusha
    mov ah, 0x0E
.next:
    lodsb
    cmp al,0
    je .done
    int 0x10
    jmp .next
.done:
    popa
    ret
lba_to_hts:
    push ax
    push bx
    mov bx, ax
    xor dx, dx
    div word [SectorsPerTrack]
    add dl, 1
    mov cl, dl
    mov ax, bx
    xor dx, dx
    div word [SectorsPerTrack]
    xor dx, dx
    div word [Sides]
    mov dh, dl
    mov ch, al
    pop bx                     ; Need to POP in reverse order to the pushes!
    pop ax
    mov dl, [drive]
    ret
reset:
    mov ah, 0
    int 13h                    ;reset disk
    jc disk_error              ;if failed jump to search fail
    ret
disk_error:
    mov si, disk_error_str
    call print
reboot:
    mov si, reboot_pmpt
    call print
    mov ax, 0
    int 16h
    mov ax, 0
    int 19h

;DATA
load_root_str db 'Loading Root',13,10,0
disk_error_str db 'Disk Error!',13,10,0
reboot_pmpt db 'PRESS A KEY TO REBOOT',13,10,0
not_found_str db 'KERNEL NOT FOUND',13,10,0
FAT_str db 'Loading FAT',13,10,0
load_file_str db 'Loading KERNEL',13,10,0
drive dw 0
cluster dw 0
pointer dw 0
filename db 'KERNEL  BIN',0
;PADDING AND SIGNATURE
times (510-($-$$)) db 0x00
dw 0AA55h

Проблемы в kernel.asm

Вы неправильно настроили регистры сегментов, и стек должен быть на четной границе байта.Если вы установите SP в ноль, первое нажатие вычтет 2 из SP, поместив данные в 0000-2 = 0fffe в верхней части сегмента.Я бы просто установил ES = DS = FS = GS = SS на CS .Во-вторых, когда вы выполняете инструкцию HLT, она останавливается только до следующего прерывания, а затем попадает в инструкцию после HLT.Если вы хотите HLT на неопределенное время отключить прерывания с помощью CLI в первую очередь.Хорошей идеей будет поставить HLT в цикл, если вы получили немаскируемое прерывание (NMI), которое не маскируется CLI.

Ваше ядро ​​может быть изменено таким образом.:

[bits 16]               ;16-bit binary format

;VECTORS
os_vectors:
    jmp os_main

;KERNEL
os_main:
    mov ax, cs      ;CS is segment where we were loaded
    cli             ;clear interrupts
    mov ss, ax      ;set stack segment and pointer
    xor sp, sp      ;SP=0. First push will wrap SP to 0fffeh
    sti             ;restore interrupts
    cld             ;set RAM direction(for strings)
    mov ds, ax      ;DS=ES=FS=GS=CS
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov si, hello   ;print welcome
    call print_string
    cli             ;Turn off interrupts so that HLT doesn't continue
                    ;when an interrupt occurs
.hlt_loop:
    hlt
    jmp .hlt_loop   ; Infinite loop to avoid NMI dropping us into the code of
                    ; print_string

;SUBROUTINES
print_string:
    mov ah, 0x0e

.next_char:
    lodsb
    cmp al,0
    je .done_print
    int 0x10
    jmp .next_char

.done_print:
    ret

;DATA
hello db 'Hello',0

Другие замечания

В вашем коде есть ряд недостатков, но я остановлюсь на некоторых из них.Хотя ваш код next_cluster работает, он использует больше регистров, чем нужно, и он дольше кодируется в памяти.Чтобы умножить любое значение на 3, вы можете умножить значение на 2 и добавить к нему исходное значение.Формула будет выглядеть следующим образом:

valtimes3 = (значение * 2) + значение

Это важно, потому что для умножения значения в регистре на 2 вам нужно толькосдвиньте биты влево на 1 бит с помощью инструкции SHL.Деление на 2 выполняется смещением битов в регистре вправо на 1 с помощью команды SHR.Преимущество SHR состоит в том, что бит, который вы сдвигаете из регистра, помещается в флаг переноса (CF).Если CF установлен, то значение было нечетным, а если оно ясно, то число было четным.Код next_cluster может выглядеть следующим образом:

next_cluster:
    mov bx, [cluster]          ; BX = current cluster number
    mov ax, bx                 ; AX = copy of cluster number
    shl bx, 1                  ; BX = BX * 2
    add bx, ax                 ; BX = BX + AX (BX now contains BX * 3)
    shr bx, 1                  ; Divide BX by 2
    mov ax, [bx+BUFFER_OFF]    ; Get cluster entry from FAT table
    jnc .even                  ; If carry not set by SHR then result was even
.odd:
    shr ax, 4                  ; If cluster entry is odd then cluster number is AX >> 4
    jmp short finish_load
.even:
    and ah, 0Fh                ; If cluster entry is even then cluster number is AX & 0fffh
                               ; We just need to and AH with 0fh to achieve the same result
finish_load:

Вы можете упростить lba_to_hts, изменив стандартное вычисление.Я написал предыдущий Stackoverflow ответ о том, как это сделать, и произошла ошибка замены с использованием пересмотренной формулы:

lba_to_chs функция, которая принимает LBA и преобразует его вCHS и работает только для хорошо известных IBM-совместимых форматов дискет.

;    Function: lba_to_chs
; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
;              Works **ONLY** for well known IBM PC compatible **floppy disk formats**.
;
;   Resources: http://www.ctyme.com/intr/rb-0607.htm
;              https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
;              https://stackoverflow.com/q/45434899/3857942
;              Sector    = (LBA mod SPT) + 1
;              Head      = (LBA / SPT) mod HEADS
;              Cylinder  = (LBA / SPT) / HEADS
;
;      Inputs: SI = LBA
;     Outputs: DL = Boot Drive Number
;              DH = Head
;              CH = Cylinder
;              CL = Sector
;
;       Notes: Output registers match expectation of Int 13h/AH=2 inputs
;
lba_to_chs:
    push ax                    ; Preserve AX
    mov ax, si                 ; Copy 16-bit LBA to AX
    div byte [SectorsPerTrack] ; 16-bit by 8-bit DIV : LBA / SPT
    mov cl, ah                 ; CL = S = LBA mod SPT
    inc cl                     ; CL = S = (LBA mod SPT) + 1
    xor ah, ah                 ; Upper 8-bit of 16-bit value set to 0 for DIV
    div byte [NumberOfHeads]   ; 16-bit by 8-bit DIV : (LBA / SPT) / HEADS
    mov ch, al                 ; CH = C = (LBA / SPT) / HEADS
    mov dh, ah                 ; DH = H = (LBA / SPT) mod HEADS
    mov dl, [boot_device]      ; boot device, not necessary to set but convenient
    pop ax                     ; Restore scratch register
    ret

Вам просто нужно изменить имя функции на lba_to_hts;изменить NumberOfHeads на Sides;изменить boot_drive на drive;и измените код так, чтобы LBA передавался через AX , а не SI . AX даже не нужно сохранять способ написания существующего кода.


Когда вы обнаружите, что вам нужно прочитать другой кластер в память, вы фактически добавляете 512 в [pointer] чтобы перейти к следующей позиции в памяти.Проблема в том, что вы ограничиваете себя ядром длиной 65536 байт.Как только вы достигнете 128 512-байтовых секторов, считанных в память, вы превысите 65536 (128 * 512 = 65536).[pointer] обернется и начнёт с 0, и вы перезапишете часть ядра, которую вы уже прочитали.Вы можете решить эту проблему, всегда читая диск со смещением 0 (BX = 0) и добавляя 32 к ES .Когда вы добавляете 1 в регистр сегмента, вы продвигаете 16 байтов (абзац) в памяти.Если вы хотите увеличить 512 байтов, добавьте 32 к ES (32 * 16 = 512).В вашем load_sectors коде вы можете изменить:

    call lba_to_hts
    mov bx, word [pointer]     ; BX=Current offset in buffer to read to

на:

    call lba_to_hts
    xor bx, bx

В Finish_load вы можете изменить:

finish_load:
    mov word [cluster], ax
    cmp ax, 0FF8h
    jae .jump_to_file
    add word [pointer], 512    ; We haven't reached end of kernel. Add 512 for next read
    jmp load_sector            ; Go back and load the next sector
.jump_to_file:
    mov dl, byte [drive]
    jmp LOAD_SEG:LOAD_OFF

на:

finish_load:
    mov word [cluster], ax
    cmp ax, 0FF8h
    jae .jump_to_file
    mov ax, es
    add ax, 32                 ; Increasing segment by 1 advances 16 bytes (paragraph)
                               ; in memory. Adding 32 is same advancing 512 bytes (32*16)
    mov es, ax                 ; Advance ES to point at next 512 byte block to read into
    jmp load_sector            ; Go back and load the next sector
.jump_to_file:
    mov dl, byte [drive]
    jmp LOAD_SEG:LOAD_OFF

Версия boot.asm, которая реализует эти изменения, может выглядеть следующим образом:

%define BUFFER_OFF 0x8000
%define BUFFER_SEG 0x0000
%define LOAD_SEG 0x1000
%define LOAD_OFF 0x0000

[bits 16]
[org 0x7c00]

jmp short start
nop

;DISK DESCRIPTION(BIOS PARAMETER BLOCK)
OEMLabel        db "BOOT    "
BytesPerSector      dw 512
SectorsPerCluster   db 1
ReservedForBoot     dw 1
NumberOfFats        db 2
RootDirEntries      dw 224     ; Number of entries in root dir
                               ; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors      dw 2880
MediumByte      db 0F0h
SectorsPerFat       dw 9
SectorsPerTrack     dw 18      ; Sectors per track (36/cylinder)
Sides           dw 2
HiddenSectors       dd 0
LargeSectors        dd 0
DriveNo         dw 0
Signature       db 0x29
VolumeID        dd 00000000h
VolumeLabel     db "myOS       "
FileSystem      db "FAT12   "

;BOOTLOADER
start:
    xor ax, ax
    mov ds, ax
    cli
    mov ss, ax
    mov sp, 0x7c00
    sti
    cld
    mov [drive], dl

    mov si, BUFFER_SEG         ; ES=buffer segment. Only has to be set once
    mov es, si
    mov bx, BUFFER_OFF

load_root:
    mov ax, 19                 ; Root directory starts at LBA 19
    call lba_to_hts
    mov ax, (2<<8) | 14        ; Root directory for this media fits in 14 sectors
                               ; Combine 2 moves (AH/AL) into one
                               ; same as 'mov ah, 2' and 'mov al, 14'
    int 13h
    jc reset
    mov si, load_root_str
    call print

search_file:
    mov di, BUFFER_OFF
    mov cx, word [RootDirEntries]
    xor ax, ax
.loop_search:
    xchg cx, dx
    mov si, filename
    mov cx, 11
    rep cmpsb
    je file_found
    add ax, 32
    mov di, BUFFER_OFF
    add di, ax
    xchg dx, cx
    loop .loop_search
    jmp file_not_found

file_found:
    mov ax, word [di+15]       ; Buffer and Bootloader now in same segment DS
                               ; Don't need ES:
    mov [cluster], ax
    mov ax, 1
    call lba_to_hts
    mov bx, BUFFER_OFF
    mov ax, (2<<8) | 9         ; Combine 2 moves (AH/AL) into one
                               ; same as 'mov ah, 2' and 'mov al, 9'    

load_FAT:
    mov si, FAT_str
    call print
    int 13h
    jnc load_file
    call reset
    jnc load_FAT
    jmp disk_error

load_file:
    mov si, load_file_str
    call print
    mov ax, LOAD_SEG           ; ES=load segment for kernel
    mov es, ax

load_sector:
    mov ax, word [cluster]     ; Get cluster number to read
    add ax, 33-2               ; Add 31 to cluster since FAT data area
                               ; starts at Logical Block Address (LBA) 33
                               ; and we need to subtract 2 since valid
                               ; cluster numbers start at 2
    call lba_to_hts
    xor bx, bx                 ; Always read a kernel sector to offset 0

    mov ax, (2<<8) | 1         ; AH=2 is read, AL=1 read 1 sector
                               ; Combine 2 moves (AH/AL) into one
                               ; same as 'mov ah, 2' and 'mov al, 1'
    int 13h
    jnc next_cluster
    call reset
    jmp load_sector

next_cluster:
    mov bx, [cluster]          ; BX = current cluster number
    mov ax, bx                 ; AX = copy of cluster number
    shl bx, 1                  ; BX = BX * 2
    add bx, ax                 ; BX = BX + AX (BX now contains BX * 3)
    shr bx, 1                  ; Divide BX by 2
    mov ax, [bx+BUFFER_OFF]    ; Get cluster entry from FAT table
    jnc .even                  ; If carry not set by SHR then result was even
.odd:
    shr ax, 4                  ; If cluster entry is odd then cluster number is AX >> 4
    jmp short finish_load
.even:
    and ah, 0Fh                ; If cluster entry is even then cluster number is AX & 0fffh
                               ; We just need to and AH with 0fh to achieve the same result
finish_load:
    mov word [cluster], ax
    cmp ax, 0FF8h
    jae .jump_to_file
    mov ax, es
    add ax, 32                 ; Increasing segment by 1 advances 16 bytes (paragraph)
                               ; in memory. Adding 32 is same advancing 512 bytes (32*16)
    mov es, ax                 ; Advance ES to point at next 512 byte block to read into
    jmp load_sector            ; Go back and load the next sector
.jump_to_file:
    mov dl, byte [drive]
    jmp LOAD_SEG:LOAD_OFF

;SUBROUTINES
file_not_found:
    mov si, not_found_str
    call print
    jmp reboot
print:
    pusha
    mov ah, 0x0E
.next:
    lodsb
    cmp al,0
    je .done
    int 0x10
    jmp .next
.done:
    popa
    ret

;    Function: lba_to_hts
; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
;              Works ONLY for well known IBM PC compatible floppy disk formats.
;
;   Resources: http://www.ctyme.com/intr/rb-0607.htm
;              https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
;              https://stackoverflow.com/q/45434899/3857942
;              Sector    = (LBA mod SPT) + 1
;              Head      = (LBA / SPT) mod HEADS
;              Cylinder  = (LBA / SPT) / HEADS
;
;      Inputs: AX = LBA
;     Outputs: DL = Boot Drive Number
;              DH = Head
;              CH = Cylinder
;              CL = Sector
;
;       Notes: Output registers match expectation of Int 13h/AH=2 inputs
;
lba_to_hts:
    div byte [SectorsPerTrack] ; 16-bit by 8-bit DIV : LBA / SPT
    mov cl, ah                 ; CL = S = LBA mod SPT
    inc cl                     ; CL = S = (LBA mod SPT) + 1
    xor ah, ah                 ; Upper 8-bit of 16-bit value set to 0 for DIV
    div byte [Sides]           ; 16-bit by 8-bit DIV : (LBA / SPT) / HEADS
    mov ch, al                 ; CH = C = (LBA / SPT) / HEADS
    mov dh, ah                 ; DH = H = (LBA / SPT) mod HEADS
    mov dl, [drive]            ; boot device, not necessary to set but convenient
    ret

reset:
    mov ah, 0
    int 13h                    ;reset disk
    jc disk_error              ;if failed jump to search fail
    ret
disk_error:
    mov si, disk_error_str
    call print
reboot:
    mov si, reboot_pmpt
    call print
    mov ax, 0
    int 16h
    mov ax, 0
    int 19h

;DATA
load_root_str db 'Loading Root',13,10,0
disk_error_str db 'Disk Error!',13,10,0
reboot_pmpt db 'PRESS A KEY TO REBOOT',13,10,0
not_found_str db 'KERNEL NOT FOUND',13,10,0
FAT_str db 'Loading FAT',13,10,0
load_file_str db 'Loading KERNEL',13,10,0
drive dw 0
cluster dw 0
filename db 'KERNEL  BIN',0
;PADDING AND SIGNATURE
times (510-($-$$)) db 0x00
dw 0AA55h

При запуске в QEMU это то, что отображается:

enter image description here

...