I am currently working on a bootloader written in x86 NASM assembly, designed to load the kernel (R.BIN) from a FAT16 formatted disk. It has not been doing so, and after incorporating short messages for debugging (A, B, C, D, J, !; lines involving them marked with an asterisk), I discovered that the bootloader goes all the way through to the point at which it jumps to the loaded kernel perfectly, though it does not jump to the kernel, and instead seems to load itself again.
To test out the bootloader, I have a mounted blank image that I have been writing the bootloader to by using the following:
dd if=LOADER.BIN of=/dev/loop0
(I also copied R.BIN to it)
After this, I would save this new image, and test it out in Virtualbox.
This is my complete code:
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
As I said, the bootloader seems to be doing everything as planned, until making the jump to the kernel, when it starts over at the beginning of the debugging messages and cycles again.