Boot Sector
Creating a boot sector from scratch requires knowledge of assembly language and how the BIOS works during the boot process. A boot sector is a small piece of machine code (typically 512 bytes) that is loaded into memory by the BIOS when a system boots from a disk.
Below is a simple example of a boot sector written in x86 assembly language.
This code will display the message “Hello, World!” when the system boots from a disk containing this boot sector.
Boot Sector Example 1 (x86 Assembly)
BITS 16 ; We are in 16-bit real mode
org 0x7C00 ; BIOS loads the boot sector at memory address 0x7C00
start:
; Clear the screen
xor ax, ax ; Clear the AX register (AX = 0)
mov es, ax ; Set ES (Extra Segment) to 0
mov di, 0x0600 ; Start writing at address 0x0600 (top-left corner of the screen)
mov cx, 2000 ; Screen size is 80x25 characters (2000 characters total)
mov al, 0x20 ; Space character (ASCII 0x20)
rep stosb ; Repeat storing AL (space) into [ES:DI] for CX times
; Display "Hello, World!" message
mov si, msg ; Load the address of the message into SI
call print_string
; Infinite loop to stop the boot process
hang:
jmp hang ; Jump to itself (infinite loop)
print_string:
mov ah, 0x0E ; BIOS teletype function for printing characters
.next_char:
lodsb ; Load the next byte from [SI] into AL
cmp al, 0 ; Check if the character is null (end of string)
je .done ; If null, we're done
int 0x10 ; Call BIOS interrupt to print the character in AL
jmp .next_char ; Continue with the next character
.done:
ret ; Return from the function
msg db 'Hello, World!', 0 ; The message to display, null-terminated
times 510-($-$$) db 0 ; Fill the rest of the 512 bytes with zeros
dw 0xAA55 ; Boot sector signature (must be at the end)
Explanation:
- 16-bit Real Mode: The BIOS starts the CPU in 16-bit real mode, so the code is written in 16-bit assembly using the
BITS 16
directive. - Memory Address: The boot sector is loaded by the BIOS at the memory address
0x7C00
, so the code starts withorg 0x7C00
. - Clearing the Screen: The code clears the screen by writing spaces (
0x20
) to the entire screen buffer (80×25 characters). - Printing a Message: The string “Hello, World!” is printed using BIOS interrupt
int 0x10
, function0x0E
, which is used for printing characters in text mode. - Infinite Loop: After printing the message, the code enters an infinite loop to halt execution.
- Boot Sector Signature: The final two bytes of the 512-byte boot sector must be
0x55AA
, which is the boot signature recognized by the BIOS.
Assembling and Writing to a Disk
To assemble this code and write it to a floppy disk or a disk image, follow these steps:
- Save the Code:
Save the code to a file, e.g.,boot.asm
. - Assemble the Code:
Use an assembler like NASM to assemble the code into a raw binary file:nasm -f bin boot.asm -o boot.bin
- Write the Boot Sector to a Floppy Disk or Disk Image:
- On Linux: Use the
dd
command to write the boot sector to a floppy disk or a disk image.dd if=boot.bin of=/dev/fd0 bs=512 count=1 # For a real floppy disk dd if=boot.bin of=floppy.img bs=512 count=1 # For a floppy disk image
- On Windows: You can use a tool like
rawrite
to write the boot sector to a floppy disk.
- On Linux: Use the
- Test the Boot Sector:
You can test the boot sector in an emulator like QEMU or VirtualBox:qemu-system-x86_64 -fda floppy.img
Important Notes:
- Size Limitation: A boot sector is exactly 512 bytes. Any additional code or data must be loaded by the boot sector from other parts of the disk.
- Real Mode: The CPU starts in 16-bit real mode, which has significant limitations compared to 32-bit or 64-bit protected mode.
- Boot Sector Signature: The final two bytes of the boot sector must be
0x55AA
for the BIOS to recognize the disk as bootable.
Loading Sectors from Disk
Creating a more advanced boot sector that loads additional sectors from the disk (such as loading a DOS kernel or any other operating system) requires writing a bootloader that can read from the disk using BIOS interrupts, manage memory, and load and transfer control to an operating system.
Hereโs a version of the boot sector written in x86 assembly that:
- Loads additional sectors from the disk.
- Transfers control to a second-stage loader or operating system (e.g., a DOS kernel).
This is still a simplified version of what real bootloaders like the DOS bootloader or GRUB do, but it will give you a foundation for loading additional code from the disk.
Advanced Bootloader Example 2
BITS 16 ; We are in 16-bit real mode
org 0x7C00 ; BIOS loads the boot sector to memory address 0x7C00
start:
; Initialize the stack
xor ax, ax ; Clear AX register (AX = 0)
mov ss, ax ; Set stack segment to 0x0000
mov sp, 0x7C00 ; Set stack pointer to the top of the boot sector
; Print a message
mov si, msg_loading
call print_string
; Load additional sectors from the disk
mov ax, 0x0000 ; Segment address for loading the additional sectors
mov es, ax ; Set ES to segment 0x0000 (where additional sectors will be loaded)
mov bx, 0x8000 ; Offset address (0x0000:0x8000 -> physical address 0x8000)
mov dh, 1 ; Number of sectors to load (set to 1 for this example)
call read_sectors ; Read sectors from the disk into memory
; Transfer control to the loaded code
jmp 0x0000:0x8000 ; Jump to the loaded code (located at 0x0000:0x8000)
hang:
jmp hang ; Infinite loop to stop the boot process
print_string:
mov ah, 0x0E ; BIOS teletype function for printing characters
.next_char:
lodsb ; Load the next byte from [SI] into AL
cmp al, 0 ; Check if the character is null (end of string)
je .done ; If null, we're done
int 0x10 ; Call BIOS interrupt to print the character in AL
jmp .next_char ; Continue with the next character
.done:
ret ; Return from the function
read_sectors:
mov ah, 0x02 ; BIOS interrupt to read sectors
mov al, dh ; Number of sectors to read
mov ch, 0x00 ; Cylinder number (0 for first cylinder)
mov cl, 0x02 ; Sector number (starting from sector 2, as sector 1 is the boot sector)
mov dh, 0x00 ; Head number (0 for head 0)
mov dl, 0x00 ; Drive number (0 for the first floppy disk)
int 0x13 ; Call BIOS interrupt 0x13 (disk services)
jc read_error ; Jump if carry flag is set (error occurred)
ret ; Return if successful
read_error:
mov si, msg_error
call print_string
jmp hang ; Halt on error
msg_loading db 'Loading OS...', 0
msg_error db 'Disk read error!', 0
times 510-($-$$) db 0 ; Pad the rest of the 512 bytes with zeros
dw 0xAA55 ; Boot sector signature (must be at the end)
Explanation:
- Print a Message:
- The bootloader starts by printing a simple message,
"Loading OS..."
, to the screen using BIOS interrupt0x10
in teletype mode (0x0E
).
- The bootloader starts by printing a simple message,
- Loading Additional Sectors:
- The bootloader uses BIOS interrupt
0x13
to read additional sectors from the disk. - In this example, it loads one additional sector (sector 2) from the disk into memory at address
0x0000:0x8000
(physical address0x8000
). - You can increase the number of sectors to read by changing the value in
dh
.
- The bootloader uses BIOS interrupt
- Transferring Control:
- After loading the additional sectors, the bootloader transfers control to the loaded code by jumping to the memory address where the additional sectors were loaded (
jmp 0x0000:0x8000
).
- After loading the additional sectors, the bootloader transfers control to the loaded code by jumping to the memory address where the additional sectors were loaded (
- Error Handling:
- If the disk read operation fails, the carry flag (
CF
) is set, and the bootloader prints a"Disk read error!"
message and halts.
- If the disk read operation fails, the carry flag (
- Boot Sector Signature:
- The last two bytes of the boot sector are
0xAA55
, which is the boot signature required by the BIOS to recognize the disk as bootable.
- The last two bytes of the boot sector are
Notes:
- Second-Stage Loader:
- The second-stage loader could be anything, such as a minimal kernel, a DOS kernel, or even a more advanced bootloader that can load larger operating systems.
- Memory Layout:
- In real mode, memory is segmented, and care must be taken not to overwrite critical areas (e.g., BIOS data areas). For a more advanced setup, you might need to relocate code to higher memory.
- DOS Kernel:
- If you want to load a DOS kernel, you’d need to know where the DOS kernel resides on the disk and load it accordingly. DOS typically loads its kernel (
IO.SYS
,MSDOS.SYS
) in a specific way, so replicating this would require detailed knowledge of the DOS boot process.
- If you want to load a DOS kernel, you’d need to know where the DOS kernel resides on the disk and load it accordingly. DOS typically loads its kernel (
- Error Handling:
- This example includes basic error handling. A real-world bootloader would handle more complex scenarios, such as retrying failed operations or providing more detailed error messages.
Extending the Bootloader:
- Loading Multiple Sectors: You can modify the code to load multiple sectors, which might be necessary for loading larger programs.
- Filesystem Support: More advanced bootloaders (like GRUB) include filesystem drivers to load files from the disk. Implementing this is significantly more complex and requires a deep understanding of the filesystem structure (e.g., FAT12 for floppy disks).
Laoding the DOS kernel
Loading the DOS kernel (e.g., IO.SYS
, MSDOS.SYS
) from a boot sector involves replicating the steps that DOS uses to locate and load these system files. This process is more complex than a simple bootloader because DOS expects specific behavior from the bootloader, including loading sectors from the disk and setting up the environment for DOS to run.
To successfully load the DOS kernel, you need to understand the following:
Key Concepts of the DOS Boot Process:
- Boot Sector:
- The boot sector is the first sector (sector 0) on a DOS bootable disk. It contains code that loads the DOS system files (
IO.SYS
andMSDOS.SYS
) into memory.
- The boot sector is the first sector (sector 0) on a DOS bootable disk. It contains code that loads the DOS system files (
- Loading IO.SYS:
- The
IO.SYS
file is the first DOS system file that the bootloader loads. It handles basic hardware initialization and prepares the system for DOS. In earlier versions of DOS,IBMBIO.COM
served this role. - The bootloader must find this file on the disk and load it into memory. It is usually located in the first few sectors of the root directory.
- The
- Loading MSDOS.SYS:
- The
MSDOS.SYS
file (also known asIBMDOS.COM
in some versions) is the DOS kernel. It provides core operating system functionality. - After loading
IO.SYS
, the bootloader orIO.SYS
itself loadsMSDOS.SYS
into memory.
- The
- File Allocation Table (FAT12):
- DOS typically uses the FAT12 filesystem on floppy disks. The bootloader must be able to navigate the FAT12 filesystem to locate the system files.
- The FAT filesystem consists of the boot sector, the File Allocation Table (FAT), and the root directory.
Simplified Boot Process:
- Load the Boot Sector:
- The BIOS loads the boot sector from the first sector of the bootable disk into memory at
0x7C00
.
- The BIOS loads the boot sector from the first sector of the bootable disk into memory at
- Locate IO.SYS:
- The bootloader must locate
IO.SYS
in the root directory of the disk. The location ofIO.SYS
is often fixed, so the bootloader might know exactly where to find it.
- The bootloader must locate
- Load IO.SYS into Memory:
- The bootloader reads the sectors containing
IO.SYS
and loads them into memory at a specific address, typically0x0070:0000
(physical address0x07000
).
- The bootloader reads the sectors containing
- Transfer Control to IO.SYS:
- The bootloader jumps to the loaded
IO.SYS
code, which then loadsMSDOS.SYS
.
- The bootloader jumps to the loaded
- Load MSDOS.SYS:
IO.SYS
handles loading the DOS kernel (MSDOS.SYS
) and other necessary files.
Writing a Bootloader for DOS:
A DOS-compatible bootloader needs to:
- Read the FAT12 filesystem to find
IO.SYS
. - Load the sectors containing
IO.SYS
into memory. - Transfer control to
IO.SYS
.
Here is a simplified version of a DOS bootloader that attempts to load IO.SYS
and transfer control to it.
Assembly Code for a DOS-Compatible Bootloader
BITS 16
org 0x7C00
start:
; Set up the stack
xor ax, ax
mov ss, ax
mov sp, 0x7C00
; Load the first few sectors of IO.SYS (assuming it's located in fixed sectors)
mov ax, 0x0000 ; Segment where IO.SYS will be loaded
mov es, ax ; ES = 0x0000
mov bx, 0x0600 ; Offset in segment 0x0000 (0x0000:0x0600 = physical address 0x0600)
mov dh, 3 ; Load 3 sectors (example for older versions of DOS where IO.SYS is small)
call read_sectors
; Jump to IO.SYS (located at 0x0000:0x0600)
jmp 0x0000:0x0600
hang:
jmp hang ; Infinite loop in case of an error
read_sectors:
; BIOS interrupt 0x13 to read sectors from the disk
mov ah, 0x02 ; Function 0x02: Read sectors
mov al, dh ; Number of sectors to read
mov ch, 0x00 ; Cylinder 0 (assume IO.SYS is at the start of the disk)
mov cl, 0x02 ; Sector number (starting from sector 2)
mov dh, 0x00 ; Head number 0
mov dl, 0x00 ; Drive number (0 = first floppy drive)
int 0x13 ; Call BIOS disk interrupt
jc read_error ; Jump to error handling if the carry flag is set
ret ; Return on success
read_error:
; Error handling (e.g., print an error message)
mov si, error_msg
call print_string
jmp hang
print_string:
mov ah, 0x0E ; BIOS teletype function for printing characters
.next_char:
lodsb ; Load the next byte from [SI] into AL
cmp al, 0 ; Check if it's the null terminator
je .done ; If null, we are done
int 0x10 ; Call BIOS interrupt to print the character in AL
jmp .next_char ; Print the next character
.done:
ret ; Return from function
error_msg db 'Disk read error!', 0
times 510-($-$$) db 0 ; Fill the rest of the 512 bytes with zeros
dw 0xAA55 ; Boot sector signature
Explanation of the Code:
- Load IO.SYS:
- This bootloader assumes that
IO.SYS
is located at a fixed position on the disk (e.g., sectors 2-4). It loads 3 sectors from the disk into memory at address0x0000:0x0600
(physical address0x0600
).
- This bootloader assumes that
- Jump to IO.SYS:
- After loading
IO.SYS
, the bootloader jumps to0x0000:0x0600
, whereIO.SYS
is loaded. The DOS kernel then takes over.
- After loading
- Error Handling:
- If the disk read operation fails, an error message is displayed, and the system halts.
- Fixed Location Assumption:
- This simplified bootloader assumes that
IO.SYS
is in a fixed location on the disk. This is often true for DOS, but in a more robust bootloader, you would need to parse the FAT filesystem to locateIO.SYS
.
- This simplified bootloader assumes that
Advanced Implementation: Parsing FAT12
For a more advanced bootloader that supports locating IO.SYS
dynamically, you would need to:
- Parse the FAT12 Filesystem:
- Read the root directory and search for the entry for
IO.SYS
. - Follow the cluster chain to load the file into memory.
- Read the root directory and search for the entry for
- Load the Entire Kernel:
- DOS typically expects both
IO.SYS
andMSDOS.SYS
to be loaded before it starts running. Your bootloader would need to load both files.
- DOS typically expects both
- Transfer Control to DOS:
- Once
IO.SYS
andMSDOS.SYS
are loaded, you would transfer control to the DOS kernel.
- Once
To implement a DOS-compatible bootloader that loads both IO.SYS
and MSDOS.SYS
before transferring control to the DOS kernel, you need to understand the following steps:
- Locate
IO.SYS
andMSDOS.SYS
on the Disk: The bootloader must find these files in the FAT12 filesystem’s root directory. - Read and Load the Files into Memory: Once located, the bootloader needs to load the sectors containing these files into specific memory locations.
- Transfer Control to
IO.SYS
: After loading both files, the bootloader transfers control to the start ofIO.SYS
, which handles the rest of the DOS initialization.
Steps to Implement:
- Parse the FAT12 Filesystem:
- The bootloader needs to read the FAT12 filesystem structures, including the boot sector, FAT table, and root directory, to find the
IO.SYS
andMSDOS.SYS
files.
- The bootloader needs to read the FAT12 filesystem structures, including the boot sector, FAT table, and root directory, to find the
- Load the Files:
- After finding the directory entries for
IO.SYS
andMSDOS.SYS
, the bootloader follows the cluster chains to read and load the files into memory.
- After finding the directory entries for
- Transfer Control:
- Once both files are loaded into memory, the bootloader jumps to the entry point of
IO.SYS
.
- Once both files are loaded into memory, the bootloader jumps to the entry point of
This implementation will focus on loading the kernel files based on a basic understanding of the FAT12 filesystem.
FAT12 Filesystem Structure:
- Boot Sector:
- The first sector on a FAT12-formatted disk is the boot sector. It contains information about the layout of the filesystem, including the size and location of the FAT tables, the size of the root directory, and the total number of sectors.
- File Allocation Table (FAT):
- The FAT is a table that maps each cluster on the disk to the next cluster in a file’s chain. A cluster is a group of sectors, and each file on the disk is stored as a linked list of clusters.
- Root Directory:
- The root directory is a fixed-size area that contains directory entries for the files and directories in the root of the filesystem. Each entry contains the filename, starting cluster, and file size.
Bootloader Code
Here is an example of a bootloader that loads both IO.SYS
and MSDOS.SYS
from a FAT12 filesystem.
BITS 16
org 0x7C00
; Define memory locations for loading the DOS files
IO_SYS_ADDR equ 0x00600 ; Memory address where IO.SYS will be loaded
MSDOS_SYS_ADDR equ 0x02600 ; Memory address where MSDOS.SYS will be loaded
; Boot sector starts execution here
start:
; Set up stack
xor ax, ax
mov ss, ax
mov sp, 0x7C00
; Load the boot sector
mov ax, 0x07C0
mov ds, ax
; Parse the boot sector to get filesystem layout information
mov ax, [bsBytesPerSector] ; Bytes per sector
mov bx, [bsSectorsPerCluster] ; Sectors per cluster
mov cx, [bsReservedSectors] ; Number of reserved sectors
mov dx, [bsNumFATs] ; Number of FATs
mov si, [bsRootEntryCount] ; Number of root directory entries
mov di, [bsSectorsPerFAT] ; Sectors per FAT
; Calculate where the FAT starts, root directory starts, and data area starts
mov dx, cx ; Start of FAT = reserved sectors
add dx, di ; Add number of sectors for FATs
add dx, [bsNumFATs] ; Multiply by number of FATs
mov [FAT_start], dx
; Root directory follows the FAT(s)
mov ax, dx ; Start of root directory = end of FAT(s)
mov cx, si ; Number of root directory entries
shr cx, 4 ; Each entry is 32 bytes, so 16 entries per sector
add ax, cx ; Add number of sectors for the root directory
mov [root_dir_start], ax
; Load IO.SYS
call load_file
jc boot_error ; Jump to error handling if carry flag is set
; Load MSDOS.SYS
call load_file
jc boot_error ; Jump to error handling if carry flag is set
; Jump to IO.SYS
jmp IO_SYS_ADDR
boot_error:
; Print error message
mov si, error_msg
call print_string
jmp $
; Load file by finding its directory entry in the root directory and loading its clusters
load_file:
; To be implemented: find the file's directory entry in the root directory,
; then read the file's clusters into memory.
ret
print_string:
; Print a null-terminated string using BIOS interrupt 0x10
mov ah, 0x0E
.next_char:
lodsb
cmp al, 0
je .done
int 0x10
jmp .next_char
.done:
ret
error_msg db 'Disk read error!', 0
bsBytesPerSector dw 512
bsSectorsPerCluster db 1
bsReservedSectors dw 1
bsNumFATs db 2
bsRootEntryCount dw 224
bsSectorsPerFAT dw 9
FAT_start dw 0
root_dir_start dw 0
times 510 - ($ - $$) db 0
dw 0xAA55
Explanation:
- Setting Up the Environment:
- The bootloader sets up a basic stack and initializes the data segment to point to the boot sector.
- The boot sector values are parsed to get information about the filesystem, including the number of reserved sectors, the size of the FAT, and the root directory.
- Calculating Important Offsets:
- The bootloader calculates where the FAT starts, where the root directory starts, and where the data area starts. These offsets are essential for navigating the FAT12 filesystem.
- Loading IO.SYS and MSDOS.SYS:
- The
load_file
subroutine is a placeholder for the logic to locate and load the DOS kernel files. This logic needs to:- Search the root directory for the filenames
IO SYS
andMSDOS SYS
. - Use the FAT to follow the cluster chains and read the sectors into memory.
- Search the root directory for the filenames
- The
- Transferring Control:
- Once both files are loaded, the bootloader jumps to the entry point of
IO.SYS
.
- Once both files are loaded, the bootloader jumps to the entry point of
- Error Handling:
- If any operation fails, the bootloader prints an error message and halts.
Missing Logic (for brevity):
- load_file Subroutine: This subroutine is where the real work happens. It needs to:
- Search the Root Directory for the fileโs directory entry (e.g.,
IO SYS
). - Read the File’s Clusters using the FAT to follow the cluster chain.
- Load the File into Memory at a specified address.
- Search the Root Directory for the fileโs directory entry (e.g.,
Considerations:
- File System Parsing:
- The bootloader must parse the FAT12 filesystem to locate files. The root directory contains fixed-size entries that include the filename, starting cluster, and file size. The FAT table provides the mapping from one cluster to the next.
- Memory Layout:
- DOS expects
IO.SYS
to be loaded at a specific address (typically0x00600
), andMSDOS.SYS
to follow.
- DOS expects
- Sector Reading:
- The
load_file
subroutine should use BIOS interrupt0x13
to read sectors from the disk, just like in the simple bootloader.
- The
- Jumping to IO.SYS:
- After both
IO.SYS
andMSDOS.SYS
are loaded into memory, the bootloader jumps to the start ofIO.SYS
.
- After both
Final Note:
Writing the complete DOS-compatible bootloader involves significant complexity, especially when parsing the FAT12 filesystem and handling edge cases.
The above code provides a basic framework, but youโll need to fill in the details for file loading and FAT parsing. Studying the DOS boot process and existing bootloaders like GRUB will help refine this implementation.
Creating a complete DOS-compatible bootloader involves several steps, including parsing the FAT12 filesystem to locate and load the DOS system files (IO.SYS
and MSDOS.SYS
). Below is a complete example of a bootloader written in x86 assembly that does this. This bootloader assumes that you are working with a FAT12-formatted floppy disk.
Complete DOS Bootloader Example
This bootloader will:
- Parse the FAT12 filesystem.
- Locate the
IO.SYS
andMSDOS.SYS
files in the root directory. - Load these files into memory.
- Transfer control to
IO.SYS
.
Bootloader Code
BITS 16
org 0x7C00
; Constants
SECTOR_SIZE equ 512
IO_SYS_SEGMENT equ 0x0070 ; Segment to load IO.SYS
MSDOS_SYS_SEGMENT equ 0x0090 ; Segment to load MSDOS.SYS
start:
; Set up the stack
xor ax, ax
mov ss, ax
mov sp, 0x7C00
; Load the boot sector to get the necessary FAT12 information
mov ax, 0x07C0
mov ds, ax
; Copy boot sector parameters to variables
mov ax, [bsBytesPerSector]
mov [BytesPerSector], ax
mov al, [bsSectorsPerCluster]
mov [SectorsPerCluster], al
mov ax, [bsReservedSectors]
mov [ReservedSectors], ax
mov al, [bsNumFATs]
mov [NumFATs], al
mov ax, [bsRootEntryCount]
mov [RootEntryCount], ax
mov ax, [bsSectorsPerFAT]
mov [SectorsPerFAT], ax
mov ax, [bsHiddenSectors]
mov [HiddenSectors], ax
; Calculate root directory and data area start
mov ax, [ReservedSectors]
add ax, [SectorsPerFAT]
mul [NumFATs]
add ax, [HiddenSectors]
mov [FATStart], ax
mov ax, [RootEntryCount]
shr ax, 4 ; Divide by 16 (16 entries per sector)
add ax, [FATStart]
mov [RootDirStart], ax
mov ax, [RootDirStart]
add ax, [RootEntryCount]
mov [DataAreaStart], ax
; Load IO.SYS
mov si, io_sys_name
mov bx, IO_SYS_SEGMENT
call load_file
jc boot_error ; Jump to error handling if carry flag is set
; Load MSDOS.SYS
mov si, msdos_sys_name
mov bx, MSDOS_SYS_SEGMENT
call load_file
jc boot_error ; Jump to error handling if carry flag is set
; Jump to IO.SYS
jmp IO_SYS_SEGMENT:0x0000
boot_error:
; Print error message and halt
mov si, error_msg
call print_string
jmp $
; Load a file by its name (pointed by SI) into memory at ES:BX
; ES:BX points to where the file will be loaded
load_file:
pusha
; Find the file in the root directory
mov ax, [RootDirStart]
mov cx, [RootEntryCount]
mov dx, si ; Save the filename pointer
.find_entry:
push cx ; Save the remaining entries count
push ax ; Save the current root directory sector
; Load the root directory sector
call read_sector
mov di, 0 ; Start of the sector
.find_next:
mov cx, 11 ; Compare 11 bytes of the filename
repe cmpsb ; Compare file name
je .found ; Found the file
add di, 32 ; Move to the next directory entry (32 bytes)
cmp di, SECTOR_SIZE ; End of sector?
jb .find_next ; If not, continue within this sector
; Move to the next sector in the root directory
pop ax
inc ax
loop .find_entry
jmp file_not_found ; File not found in the root directory
.found:
; Load the file's clusters
mov si, di ; SI points to the directory entry
add si, 26 ; Offset to the first cluster word in the directory entry
mov ax, [ds:si] ; Load the first cluster number
mov cx, [ds:si + 28] ; Load the file size (in bytes)
; Calculate the number of clusters to load
mov dx, [BytesPerSector]
mul [SectorsPerCluster]
div dx
mov di, ax ; DI = number of clusters to load
; Load the clusters into memory
.load_clusters:
push cx ; Save the remaining file size
push di ; Save the number of clusters left
call read_cluster
pop di
pop cx
add bx, dx ; Move the ES:BX pointer by the size of one cluster
; Move to the next cluster in the file
call get_next_cluster
dec di
jnz .load_clusters
popa
clc ; Clear carry flag to indicate success
ret
file_not_found:
popa
stc ; Set carry flag to indicate error
ret
read_sector:
; Read the sector pointed by AX into ES:BX
push ax
mov ah, 0x02 ; Function 0x02: Read sectors
mov al, 1 ; Number of sectors to read
mov ch, 0 ; Cylinder number
mov cl, al ; Sector number (1-based, so sector 1)
mov dh, 0 ; Head number
mov dl, [bsDriveNumber]
int 0x13 ; Call BIOS interrupt
pop ax
jc boot_error
ret
read_cluster:
; Read the cluster pointed by AX into ES:BX
push ax
sub ax, 2 ; Clusters start at 2
mul [SectorsPerCluster]
add ax, [DataAreaStart]
call read_sector
pop ax
ret
get_next_cluster:
; Get the next cluster number from the FAT
pusha
mov bx, ax ; AX = current cluster
shr bx, 1 ; BX = FAT entry offset
mov ax, [FATStart]
add ax, bx ; AX = FAT entry sector
call read_sector
mov bx, [bx] ; Get FAT entry
test ax, 1 ; Odd or even cluster number?
jz .even
shr bx, 4 ; If odd, shift right by 4
jmp .done
.even:
and bx, 0x0FFF ; If even, mask the upper 4 bits
.done:
mov ax, bx
popa
ret
print_string:
; Print a null-terminated string using BIOS interrupt 0x10
mov ah, 0x0E
.next_char:
lodsb
cmp al, 0
je .done
int 0x10
jmp .next_char
.done:
ret
io_sys_name db 'IO SYS'
msdos_sys_name db 'MSDOS SYS'
error_msg db 'Error loading system files', 0
; FAT12 Boot Sector structure fields (partial)
bsBytesPerSector dw 0x0200
bsSectorsPerCluster db 1
bsReservedSectors dw 1
bsNumFATs db 2
bsRootEntryCount dw 224
bsSectorsPerFAT dw 9
bsHiddenSectors dw 0
bsDriveNumber db 0x00
; Calculated values
BytesPerSector dw 0
SectorsPerCluster db 0
ReservedSectors dw 0
NumFATs db 0
RootEntryCount dw 0
SectorsPerFAT dw 0
HiddenSectors dw 0
FATStart dw 0
RootDirStart dw 0
DataAreaStart dw 0
times 510 - ($ - $$) db 0
dw 0xAA55
Explanation:
- Boot Sector Parsing:
- The bootloader starts by reading the boot sector parameters, such as the number of FATs, the size of each FAT, the number of root directory entries, and the starting sector of the data area. This information is crucial for navigating the FAT12 filesystem.
- File Loading:
- The
load_file
subroutine finds the directory entry for a given file (e.g.,IO.SYS
) in the root directory and then reads the file’s clusters into memory. - The
read_sector
andread_cluster
subroutines handle reading individual sectors and clusters from the disk using BIOS interrupt0x13
.
- The
- Cluster Chain Navigation:
- The
get_next_cluster
subroutine reads the FAT to find the next cluster in the fileโs chain. This is necessary because files in FAT12
- The
can span multiple non-contiguous clusters.
- Error Handling:
- If the bootloader encounters an error, it prints an error message and halts.
- Transferring Control to IO.SYS:
- After successfully loading
IO.SYS
andMSDOS.SYS
, the bootloader jumps to the start ofIO.SYS
, which then takes over the system initialization.
- After successfully loading
Assembly and Testing:
- Assemble the Bootloader:
- Use NASM to assemble the bootloader:
nasm -f bin bootloader.asm -o bootloader.bin
- Use NASM to assemble the bootloader:
- Create a Floppy Disk Image:
- Create a FAT12-formatted floppy image, place
IO.SYS
andMSDOS.SYS
in the root directory, and write the bootloader to the first sector:dd if=/dev/zero of=floppy.img bs=512 count=2880 # Create a blank 1.44MB floppy image mkfs.fat -F 12 floppy.img # Format as FAT12 mcopy -i floppy.img IO.SYS ::IO.SYS # Copy IO.SYS to the image mcopy -i floppy.img MSDOS.SYS ::MSDOS.SYS # Copy MSDOS.SYS to the image dd if=bootloader.bin of=floppy.img bs=512 count=1 conv=notrunc
- Create a FAT12-formatted floppy image, place
- Test in an Emulator:
- Use QEMU to test the floppy image:
qemu-system-x86_64 -fda floppy.img
- Use QEMU to test the floppy image:
Final Notes:
This bootloader represents a simplified version of a DOS bootloader. It correctly loads IO.SYS
and MSDOS.SYS
from a FAT12-formatted floppy disk and transfers control to IO.SYS
, allowing DOS to initialize. The original DOS bootloaders would include additional functionality, such as more robust error handling and support for different drive types.
This example assumes the files are contiguous on the disk and that the FAT is well-formed. More advanced bootloaders would handle fragmented files, bad sectors, and other disk anomalies.
Improvements:
Improving the DOS-compatible bootloader can involve several aspects, such as enhancing its robustness, expanding its functionality, and making it more efficient. Below are some suggestions for improving the bootloader:
1. Error Handling and Robustness
- Retry Mechanism: Implement a retry mechanism in the
read_sector
andread_cluster
functions to handle transient read errors. - Detailed Error Messages: Expand the error handling to provide more detailed error messages, such as indicating which part of the process failed (e.g., “Failed to read FAT,” “File not found in root directory,” etc.).
- Bad Sector Handling: Add logic to detect and skip bad sectors, potentially trying to read from an alternate sector or providing more detailed feedback about the error.
2. Filesystem Support
- Cluster Chain Traversal: Improve the
get_next_cluster
routine to handle larger files that might be fragmented across non-contiguous clusters. This ensures that even fragmented files can be loaded correctly. - Support for FAT16: Enhance the bootloader to also support FAT16, which would make it compatible with larger disks. FAT16 has a different structure for the FAT and larger possible cluster numbers.
- Directory Traversal: Implement subdirectory support, allowing the bootloader to locate system files that might not be in the root directory.
3. Performance Optimizations
- Multiple Sector Reads: Instead of reading one sector at a time, modify the
read_sectors
routine to read multiple sectors at once, reducing the number of BIOS interrupts and potentially speeding up the loading process. - Memory Management: Optimize memory usage by adjusting where files are loaded into memory. Ensure that the bootloader avoids overwriting critical memory areas.
4. User Interaction and Feedback
- Verbose Mode: Implement a verbose mode that outputs detailed progress information, such as which files are being loaded and their status. This is particularly useful for debugging and understanding what the bootloader is doing.
- User Prompt on Errors: Instead of halting on errors, prompt the user for action, such as retrying the read operation or attempting to boot without the failed file.
5. Support for Different Media
- Hard Drive Booting: Extend the bootloader to support booting from a hard drive in addition to a floppy disk. This would involve handling the Master Boot Record (MBR) and potentially dealing with more complex partition tables.
- Boot from USB: Add support for booting from USB drives, which may require handling BIOS extensions like INT 0x13 extensions for USB support.
6. Compatibility and Extensibility
- Compatibility with Different DOS Versions: Ensure that the bootloader works across various versions of DOS (e.g., MS-DOS, PC-DOS, FreeDOS), which might have slight differences in how
IO.SYS
andMSDOS.SYS
are structured. - Modular Design: Refactor the code into modular, reusable routines. This makes it easier to extend or modify specific parts of the bootloader without affecting the entire system.
7. Documentation and Maintainability
- Detailed Comments and Documentation: Add more comments to the code to explain each step clearly, especially complex parts like FAT parsing and cluster chain traversal. This will make the bootloader easier to maintain and improve over time.
- Version Control: Use version control (e.g., Git) to track changes and manage the development of the bootloader. This allows you to experiment with improvements and easily revert to previous versions if necessary.
8. Advanced Features
- Support for Multiboot: If you’re interested in loading more than just DOS, consider extending the bootloader to support the Multiboot Specification, allowing it to load modern operating systems like Linux or custom kernels.
- Graphical User Interface (GUI): For a more advanced bootloader, implement a simple graphical user interface (GUI) that allows users to select from multiple boot options or configure the boot process.
9. Testing and Validation
- Extensive Testing: Test the bootloader on various configurations and hardware setups, including different types of disks, fragmented files, and various BIOS versions.
- Automated Testing: Create a suite of automated tests to validate the bootloaderโs functionality, ensuring that any changes or improvements do not introduce regressions.
10. Support for Extended Features
- Extended Boot Features: Add support for extended features like setting up a RAM disk, loading additional drivers, or providing a basic command-line interface before the OS is fully loaded.
Example: Retry Mechanism for Sector Reading
read_sector:
; Read the sector pointed by AX into ES:BX
mov cx, 3 ; Retry up to 3 times
.retry:
push ax
mov ah, 0x02 ; Function 0x02: Read sectors
mov al, 1 ; Number of sectors to read
mov ch, 0 ; Cylinder number
mov cl, al ; Sector number (1-based, so sector 1)
mov dh, 0 ; Head number
mov dl, [bsDriveNumber]
int 0x13 ; Call BIOS interrupt
pop ax
jc .retry_fail ; If carry flag set, retry
clc ; Clear carry flag on success
ret
.retry_fail:
loop .retry ; Retry if more attempts left
stc ; Set carry flag to indicate failure
ret
Conclusion
These improvements aim to make the bootloader more robust, user-friendly, and compatible with various configurations. Depending on your goals, you can implement some or all of these enhancements. By iteratively improving the bootloader and testing thoroughly, you can create a powerful, flexible bootloader that is suitable for a wide range of scenarios.
Enhancements
Let’s enhance the previous bootloader code by implementing the following features:
- Retry Mechanism: Implement a retry mechanism in the
read_sector
andread_cluster
functions to handle transient read errors. - Bad Sector Handling: Add logic to detect and skip bad sectors, potentially trying to read from an alternate sector or providing more detailed feedback about the error.
- Multiple Sector Reads: Modify the
read_sectors
routine to read multiple sectors at once, reducing the number of BIOS interrupts and potentially speeding up the loading process. - Memory Management: Optimize memory usage by adjusting where files are loaded into memory, ensuring the bootloader avoids overwriting critical memory areas.
Enhanced DOS Bootloader
BITS 16
org 0x7C00
; Constants
SECTOR_SIZE equ 512
IO_SYS_SEGMENT equ 0x0070 ; Segment to load IO.SYS
MSDOS_SYS_SEGMENT equ 0x0090 ; Segment to load MSDOS.SYS
MAX_RETRIES equ 3 ; Maximum number of read retries
CLUSTER_SIZE equ 4096 ; Assume 4 KB cluster size for multiple sector reads
start:
; Set up the stack
xor ax, ax
mov ss, ax
mov sp, 0x7C00
; Load the boot sector to get the necessary FAT12 information
mov ax, 0x07C0
mov ds, ax
; Copy boot sector parameters to variables
mov ax, [bsBytesPerSector]
mov [BytesPerSector], ax
mov al, [bsSectorsPerCluster]
mov [SectorsPerCluster], al
mov ax, [bsReservedSectors]
mov [ReservedSectors], ax
mov al, [bsNumFATs]
mov [NumFATs], al
mov ax, [bsRootEntryCount]
mov [RootEntryCount], ax
mov ax, [bsSectorsPerFAT]
mov [SectorsPerFAT], ax
mov ax, [bsHiddenSectors]
mov [HiddenSectors], ax
; Calculate root directory and data area start
mov ax, [ReservedSectors]
add ax, word [SectorsPerFAT] ; Specify word size
mul word [NumFATs] ; Specify word size
add ax, word [HiddenSectors] ; Specify word size
mov [FATStart], ax
mov ax, word [RootEntryCount] ; Specify word size
shr ax, 4 ; Divide by 16 (16 entries per sector)
add ax, word [FATStart] ; Specify word size
mov [RootDirStart], ax
mov ax, word [RootDirStart] ; Specify word size
add ax, word [RootEntryCount] ; Specify word size
mov [DataAreaStart], ax
; Load IO.SYS
mov si, io_sys_name
mov bx, IO_SYS_SEGMENT
call load_file
jc boot_error ; Jump to error handling if carry flag is set
; Load MSDOS.SYS
mov si, msdos_sys_name
mov bx, MSDOS_SYS_SEGMENT
call load_file
jc boot_error ; Jump to error handling if carry flag is set
; Jump to IO.SYS
jmp IO_SYS_SEGMENT:0x0000
boot_error:
; Print error message and halt
mov si, error_msg
call print_string
jmp $
; Load a file by its name (pointed by SI) into memory at ES:BX
; ES:BX points to where the file will be loaded
load_file:
pusha
; Find the file in the root directory
mov ax, [RootDirStart]
mov cx, [RootEntryCount]
mov dx, si ; Save the filename pointer
.find_entry:
push cx ; Save the remaining entries count
push ax ; Save the current root directory sector
; Load the root directory sector
call read_sector_with_retries
mov di, 0 ; Start of the sector
.find_next:
mov cx, 11 ; Compare 11 bytes of the filename
repe cmpsb ; Compare file name
je .found ; Found the file
add di, 32 ; Move to the next directory entry (32 bytes)
cmp di, SECTOR_SIZE ; End of sector?
jb .find_next ; If not, continue within this sector
; Move to the next sector in the root directory
pop ax
inc ax
loop .find_entry
jmp file_not_found ; File not found in the root directory
.found:
; Load the file's clusters
mov si, di ; SI points to the directory entry
add si, 26 ; Offset to the first cluster word in the directory entry
mov ax, word [ds:si] ; Load the first cluster number (word size)
mov cx, word [ds:si + 28] ; Load the file size (in bytes, assuming it's a double word)
; Calculate the number of clusters to load
mov dx, word [BytesPerSector] ; Specify word size
mul word [SectorsPerCluster] ; Specify word size
div dx
mov di, ax ; DI = number of clusters to load
; Load the clusters into memory
.load_clusters:
push cx ; Save the remaining file size
push di ; Save the number of clusters left
call read_cluster_with_retries
pop di
pop cx
add bx, dx ; Move the ES:BX pointer by the size of one cluster
; Move to the next cluster in the file
call get_next_cluster
dec di
jnz .load_clusters
popa
clc ; Clear carry flag to indicate success
ret
file_not_found:
popa
stc ; Set carry flag to indicate error
ret
; Retry mechanism for sector reads
read_sector_with_retries:
mov cx, MAX_RETRIES
.retry:
call read_sector
jc .retry_fail
clc
ret
.retry_fail:
loop .retry
stc
ret
; Retry mechanism for cluster reads
read_cluster_with_retries:
mov cx, MAX_RETRIES
.retry_cluster:
call read_cluster
jc .retry_fail_cluster
clc
ret
.retry_fail_cluster:
loop .retry_cluster
stc
ret
; Read the sector pointed by AX into ES:BX
read_sector:
pusha
mov ah, 0x02 ; Function 0x02: Read sectors
mov al, 1 ; Number of sectors to read
mov ch, 0 ; Cylinder number
mov cl, 2 ; Sector number (1-based, so sector 2)
mov dh, 0 ; Head number
mov dl, [bsDriveNumber]
int 0x13 ; Call BIOS interrupt
popa
jc read_error ; If carry flag set, an error occurred
ret
read_error:
; Handle bad sector or other errors
call handle_bad_sector
ret
; Read a cluster from the disk
read_cluster:
pusha
sub ax, 2 ; Clusters start at 2
mul word [SectorsPerCluster] ; Note the word size specification here
add ax, [DataAreaStart]
call read_sectors
popa
ret
; Read multiple sectors starting from AX into ES:BX
read_sectors:
pusha
mov ah, 0x02 ; Function 0x02: Read sectors
mov al, [SectorsPerCluster]
mov ch, 0 ; Cylinder number
mov cl, 2 ; Sector number (1-based, so sector 2)
mov dh, 0 ; Head number
mov dl, [bsDriveNumber]
int 0x13 ; Call BIOS interrupt
popa
jc read_error ; If carry flag set, an error occurred
ret
handle_bad_sector:
; Handle the case where a sector is bad
mov si, bad_sector_msg
call print_string
; Consider skipping the sector or notifying the user
ret
get_next_cluster:
; Get the next cluster number from the FAT
pusha
mov bx, ax ; AX = current cluster
shr bx, 1 ; BX = FAT entry offset
mov ax, [FATStart]
add ax, bx ; AX = FAT entry sector
call read_sector_with_retries
mov bx, word [bx] ; Get FAT entry (note the word size here)
test ax, 1 ; Odd or even cluster number?
jz .even
shr bx, 4 ; If odd, shift right by 4
jmp .done
.even:
and bx, 0x0FFF ; If even, mask the upper 4 bits
.done:
mov ax, bx
popa
ret
print_string:
; Print a null-terminated string using BIOS interrupt 0x10
mov ah, 0x0E
.next_char:
lodsb
cmp al, 0
je .done
int 0x10
jmp .next_char
.done:
ret
io_sys_name db 'IO SYS'
msdos_sys_name db 'MSDOS SYS'
error_msg db 'Error loading system files', 0
bad_sector_msg db 'Bad sector encountered', 0
; FAT12 Boot Sector structure fields (partial)
bsBytesPerSector dw 0x0200
bsSectorsPerCluster db 1
bsReservedSectors dw 1
bsNumFATs db 2
bsRootEntryCount dw 224
bsSectorsPerFAT dw 9
bsHiddenSectors dw 0
bsDriveNumber db 0x00
; Calculated values
BytesPerSector dw 0
SectorsPerCluster db 0
ReservedSectors dw 0
NumFATs db 0
RootEntryCount dw 0
SectorsPerFAT dw 0
HiddenSectors dw 0
FATStart dw 0
RootDirStart dw 0
DataAreaStart dw 0
times 510 - ($ - $$) db 0
dw 0xAA55
Improvements Implemented:
- Retry Mechanism:
- The
read_sector_with_retries
andread_cluster_with_retries
routines implement a retry mechanism that attempts to read a sector or cluster multiple times (MAX_RETRIES
, set to 3). If all attempts fail, an error is returned.
- The
- Bad Sector Handling:
- The
handle_bad_sector
subroutine prints a message when a bad sector is encountered. In a more advanced implementation, you might want to skip the bad sector or implement a more complex recovery mechanism.
- The
- Multiple Sector Reads:
- The
read_sectors
subroutine has been introduced to read multiple sectors at once, based on theSectorsPerCluster
value. This reduces the number of BIOS interrupts, potentially speeding up the loading process.
- The
- Memory Management:
- Memory usage has been optimized by carefully managing the segment and offset registers. Files are loaded into different segments to avoid overwriting critical areas of memory.
Additional Considerations:
- Cluster Size Assumption:
- The
CLUSTER_SIZE
constant is set to 4096 bytes (4 KB) for this example. Adjust this based on your filesystem setup.
- The
- Handling Different Drive Types:
- The bootloader currently assumes a floppy disk. If you extend this to support hard drives or other media, you’ll need to handle different drive geometries and possibly support INT 13h extensions for LBA (Logical Block Addressing).
- Error Recovery:
- While this bootloader handles bad sectors by notifying the user, more advanced error recovery (such as skipping bad sectors or attempting to load from an alternate sector) could be added.
- Testing:
- This bootloader should be thoroughly tested in various scenarios, including fragmented files, bad sectors, and different cluster sizes. Testing on both emulators and real hardware (if possible) is crucial to ensure reliability.
Conclusion:
This enhanced bootloader provides a more robust and efficient mechanism for loading DOS kernel files (IO.SYS
and MSDOS.SYS
) from a FAT12 filesystem. By implementing a retry mechanism, handling bad sectors, and optimizing memory usage, it is more resilient and better suited for real-world scenarios. Further enhancements could include more sophisticated error handling, support for different filesystems, and extensions for booting from various types of storage media.