По большей части ваш код имеет правильное представление.Основные проблемы в загрузчике находятся в 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 это то, что отображается: