[BOOTSECT][FREELDR] Rewrite the Ext bootsector (#7544)

Rewrite the Ext bootsector because the older one was broken and had no compatibility with Ext4 extents.
Introduce a 3rd-stage bootsector for complex code.

CORE-14235

- Why was the previous bootsector broken?
  Because of hardcoded inode size, hardcoded freeldr base address, etc.

- Why is there a extldr.sys?
  The extldr.sys was introduced because the limited code space in the bootsector
  prevents adding new features, such as Ext4 full extents support.

- What is extldr.sys and What does it do?
  It is the helper file for the Ext bootsector and that is necessary for adding
  Ext4 support. It locates the freeldr.sys file, loads it into memory and runs it.
This commit is contained in:
Daniel Victor 2025-03-18 17:53:47 -03:00 committed by GitHub
parent 7c61e5769f
commit ddf55b3772
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1313 additions and 690 deletions

View file

@ -4,7 +4,8 @@ include_directories(${REACTOS_SOURCE_DIR}/boot/freeldr)
if(ARCH STREQUAL "i386" OR ARCH STREQUAL "amd64")
CreateBootSectorTarget(dosmbr ${CMAKE_CURRENT_SOURCE_DIR}/dosmbr.S ${CMAKE_CURRENT_BINARY_DIR}/dosmbr.bin 7c00)
CreateBootSectorTarget(ext2 ${CMAKE_CURRENT_SOURCE_DIR}/ext2.S ${CMAKE_CURRENT_BINARY_DIR}/ext2.bin 0)
CreateBootSectorTarget(ext ${CMAKE_CURRENT_SOURCE_DIR}/ext.S ${CMAKE_CURRENT_BINARY_DIR}/ext.bin 7c00)
CreateBootSectorTarget(extldr ${CMAKE_CURRENT_SOURCE_DIR}/extldr.S ${CMAKE_CURRENT_BINARY_DIR}/extldr.sys 8000)
CreateBootSectorTarget(fat ${CMAKE_CURRENT_SOURCE_DIR}/fat.S ${CMAKE_CURRENT_BINARY_DIR}/fat.bin 7c00)
CreateBootSectorTarget(fat32 ${CMAKE_CURRENT_SOURCE_DIR}/fat32.S ${CMAKE_CURRENT_BINARY_DIR}/fat32.bin 7c00)
@ -31,7 +32,8 @@ if(ARCH STREQUAL "i386" OR ARCH STREQUAL "amd64")
CreateBootSectorTarget(isombr ${CMAKE_CURRENT_SOURCE_DIR}/isombr.S ${CMAKE_CURRENT_BINARY_DIR}/isombr.bin 7000)
add_cd_file(TARGET dosmbr DESTINATION loader NO_CAB FILE ${CMAKE_CURRENT_BINARY_DIR}/dosmbr.bin FOR bootcd regtest)
add_cd_file(TARGET ext2 DESTINATION loader NO_CAB FILE ${CMAKE_CURRENT_BINARY_DIR}/ext2.bin FOR bootcd regtest)
add_cd_file(TARGET ext DESTINATION loader NO_CAB FILE ${CMAKE_CURRENT_BINARY_DIR}/ext.bin FOR bootcd regtest)
add_cd_file(TARGET extldr DESTINATION loader NO_CAB FILE ${CMAKE_CURRENT_BINARY_DIR}/extldr.sys FOR bootcd regtest)
add_cd_file(TARGET btrfsvbr DESTINATION loader NO_CAB FILE ${CMAKE_CURRENT_BINARY_DIR}/btrfs.bin FOR bootcd regtest)
add_cd_file(TARGET fat DESTINATION loader NO_CAB FILE ${CMAKE_CURRENT_BINARY_DIR}/fat.bin FOR bootcd regtest)
add_cd_file(TARGET fat32 DESTINATION loader NO_CAB FILE ${CMAKE_CURRENT_BINARY_DIR}/fat32.bin FOR bootcd regtest)

726
boot/freeldr/bootsect/ext.S Normal file
View file

@ -0,0 +1,726 @@
/*
* PROJECT: FreeLoader
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: EXTFS volume boot sector
* COPYRIGHT: Copyright 2002-2003 Brian Palmer <brianp@sginet.com>
* Copyright 2024-2025 Daniel Victor <ilauncherdeveloper@gmail.com>
*/
#include <asm.inc>
#include <freeldr/include/arch/pc/x86common.h>
// Boot sector constants
#define BOOTSECTOR_BASE_ADDRESS HEX(7C00)
#define EXTLDR_BOOTSECTOR_SIZE 1024
#define EXT_POINTER_SIZE 4
#define EXT_EXTENT_SIZE 12
// Maximum extent values
#define EXT_EXTENT_MAX_LEVEL 5
#define EXT_EXTENT_MAX_LENGTH 32768
// Group descriptor offsets
#define EXT_GROUP_DESC_INODE_TABLE_OFFSET 8
// Extent offsets
#define EXT_EXTENT_HEADER_ENTRIES_OFFSET 2
#define EXT_EXTENT_HEADER_DEPTH_OFFSET 6
#define EXT_EXTENT_INDEX_LEAF_OFFSET 4
#define EXT_EXTENT_LENGTH_OFFSET 4
#define EXT_EXTENT_START_OFFSET 8
// Inode offsets
#define EXT_INODE_SIZE_OFFSET 4
#define EXT_INODE_FLAGS_OFFSET 32
#define EXT_INODE_BLOCK_POINTER_OFFSET 40
// Directory entry offsets
#define EXT_DIRECTORY_ENTRY_SIZE_OFFSET 4
#define EXT_DIRECTORY_ENTRY_NAME_LENGTH_OFFSET 6
#define EXT_DIRECTORY_ENTRY_NAME_OFFSET 8
// Inode flags
#define EXT_INODE_FLAG_EXTENTS HEX(80000)
// Inode blocks constants
#define EXT_INODE_BLOCKS 12
#define EXT_INODE_INDIRECT_BLOCKS 3
// Root Inode
#define EXT_ROOT_INODE 2
// Inode address
#define EXT_INODE_ADDRESS HEX(9000)
// Data block addresses
#define EXT_BLOCK_ADDRESS HEX(1000)
#define EXT_BLOCK2_ADDRESS HEX(2000)
#define EXT_BLOCK3_ADDRESS HEX(3000)
#define EXT_BLOCK4_ADDRESS HEX(4000)
#define EXT_BLOCK5_ADDRESS HEX(5000)
#define EXT_BLOCK6_ADDRESS HEX(6000)
#define EXT_BLOCK7_ADDRESS HEX(A000)
// Inode types
#define EXT_INODE_TYPE_MASK HEX(F000)
#define EXT_INODE_TYPE_REGULAR HEX(8000)
// File size limit
#define EXT_INODE_DATA_SIZE_LIMIT HEX(F000)
// Offset of functions addresses that will be used by the extldr.sys 3rd-stage bootsector
#define ExtReadBlockOffset 2
#define ExtReadInodeOffset 4
#define DisplayItAndRebootOffset 6
#define PutCharsOffset 8
// Boot sector stack constants
#define BOOTSECTOR_STACK_TEMP_VARIABLES 2
#define BOOTSECTOR_STACK_TEMP_VARIABLES_SIZE (4 * BOOTSECTOR_STACK_TEMP_VARIABLES)
#define BOOTSECTOR_STACK_OFFSET (8 + BOOTSECTOR_STACK_TEMP_VARIABLES_SIZE)
#define BOOTSECTOR_STACK_BASE (BOOTSECTOR_BASE_ADDRESS - BOOTSECTOR_STACK_OFFSET)
#define BP_REL(x) ss:[bp + (x - BOOTSECTOR_BASE_ADDRESS)]
// Temporary variables
#define ExtFileSizeState ((BOOTSECTOR_STACK_BASE + BOOTSECTOR_STACK_TEMP_VARIABLES_SIZE) - 4)
#define LBASectorsRead (ExtFileSizeState - 4)
.code16
#ifndef INCLUDED_ASM
start:
jmp short main
nop
// Fields that will be changed by the installer
BootDrive:
.byte HEX(FF)
ExtVolumeStartSector:
.long 263088 // Start sector of the ext2 volume
ExtBlockSize:
.long 2 // Block size in sectors
ExtBlockSizeInBytes:
.long 1024 // Block size in bytes
ExtPointersPerBlock:
.long 256 // Number of block pointers that can be contained in one block
ExtGroupDescSize:
.long 32 // Size of Group Descriptor
ExtFirstDataBlock:
.long 2 // First data block (2 for 1024-byte blocks, 1 for bigger sizes)
ExtInodeSize:
.long 128 // Size of Inode
ExtInodesPerGroup:
.long 2048 // Number of inodes per group
// File variables
ExtFileSize:
.long 0 // File size in bytes
ExtFileAddress:
.long FREELDR_BASE // File address
ExtFileAddressOld:
.long FREELDR_BASE // Old file address
// Inode variables
ExtReadInodeGroup:
.long 0
ExtReadInodeIndex:
.long 0
ExtReadInodeGroupBlock:
.long 0
ExtReadInodeIndexBlock:
.long 0
ExtReadInodeGroupOffset:
.word 0
ExtReadInodeIndexOffset:
.word 0
main:
xor ax, ax // Setup segment registers
mov ds, ax // Make DS correct
mov es, ax // Make ES correct
mov ss, ax // Make SS correct
mov bp, BOOTSECTOR_BASE_ADDRESS
mov sp, bp // Setup a stack
sub sp, BOOTSECTOR_STACK_OFFSET
// Save the function addresses so the helper code knows where to call them
mov word ptr ss:[bp-ExtReadBlockOffset], offset ExtReadBlock
mov word ptr ss:[bp-ExtReadInodeOffset], offset ExtReadInode
mov word ptr ss:[bp-DisplayItAndRebootOffset], offset DisplayItAndReboot
mov word ptr ss:[bp-PutCharsOffset], offset PutChars
mov si, offset BootDrive
cmp byte ptr [si], HEX(0ff) // If they have specified a boot drive then use it
jne CheckInt13hExtensions
mov byte ptr [si], dl // Save the boot drive
// Now check if this computer supports extended reads. This boot sector will not work without it.
CheckInt13hExtensions:
mov ah, HEX(41) // AH = 41h
mov bx, HEX(55aa) // BX = 55AAh
int HEX(13) // IBM/MS INT 13 Extensions - INSTALLATION CHECK
jc PrintDiskError // CF set on error (extensions not supported)
cmp bx, HEX(aa55) // BX = AA55h if installed
jne PrintDiskError
test cl, 1 // si = API subset support bitmap
jz PrintDiskError // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
LoadExtraBootCode:
// First we have to load our extra boot code at
// sector 1 into memory at [0000:7e00h]
xor eax, eax
inc eax
mov cx, 1
xor bx, bx
mov es, bx // Read sector to [0000:7e00h]
mov bx, HEX(7e00)
call ReadSectors
jmp LoadRootDirectory
// Reads logical sectors into ES:[BX]
// EAX has logical sector number to read
// CX has number of sectors to read
ReadSectors:
push es
add eax, dword ptr BP_REL(ExtVolumeStartSector) // Add the start of the volume
// If at all possible we want to use LBA routines because
// they are optimized to read more than 1 sector per read
ReadSectorsLBA:
pushad // Save logical sector number & sector count
cmp cx, 64 // Since the LBA calls only support 0x7F sectors at a time we will limit ourselves to 64
jbe ReadSectorsSetupDiskAddressPacket // If we are reading less than 65 sectors then just do the read
mov cx, 64 // Otherwise read only 64 sectors on this loop iteration
ReadSectorsSetupDiskAddressPacket:
movzx ecx, cx
mov dword ptr BP_REL(LBASectorsRead), ecx
data32 push 0
push eax // Put 64-bit logical block address on stack
push es // Put transfer segment on stack
push bx // Put transfer offset on stack
push cx // Set transfer count
push 16 // Set size of packet to 10h
mov si, sp // Setup disk address packet on stack
mov dl, byte ptr BP_REL(BootDrive) // Drive number
mov ah, HEX(42) // Int 13h, AH = 42h - Extended Read
int HEX(13) // Call BIOS
jc PrintDiskError // If the read failed then abort
add sp, 16 // Remove disk address packet from stack
popad // Restore sector count & logical sector number
push bx
mov ebx, dword ptr BP_REL(LBASectorsRead)
add eax, ebx // Increment sector to read
shl ebx, 5
mov dx, es
add dx, bx // Setup read buffer for next sector
mov es, dx
pop bx
sub cx, word ptr BP_REL(LBASectorsRead)
jnz ReadSectorsLBA // Read next sector
pop es
ret
// Displays a disk error message
// And reboots
PrintDiskError:
mov si, offset msgDiskError // Bad boot disk message
call PutChars // Display it
Reboot:
mov si, offset msgAnyKey // Press any key message
call PutChars // Display it
xor ax, ax
int HEX(16) // Wait for a keypress
int HEX(19) // Reboot
.PutCharsLoop:
mov ah, HEX(0E)
mov bx, 7
int HEX(10)
PutChars:
lodsb
or al, al
jnz .PutCharsLoop
ret
SwapESWithDS:
// Swap ES and DS
push es
push ds
pop es
pop ds
ret
ExtReadGroupDescriptor:
mov eax, dword ptr BP_REL(ExtReadInodeGroupBlock) // Get Inode group block
add eax, dword ptr BP_REL(ExtFirstDataBlock) // Add the Group Descriptor offset
call ExtSetInodeSegment
ExtReadBlock:
xor edx, edx
mov ecx, dword ptr BP_REL(ExtBlockSize)
mul ecx
jmp ReadSectors
// EAX
ExtCalculateBlock:
xor edx, edx // Clear EDX before division
div dword ptr BP_REL(ExtBlockSizeInBytes) // Inode /= ExtBlockSizeInBytes
mov dword ptr ds:[bp + si], eax // Store the Inode block
ret
// SI, DI
ExtCalculateOffset:
add bx, bp // Sum BX with BP for absolute address
xor edx, edx // Clear EDX before multiplication
mov eax, dword ptr ds:[bp + si] // Get the Inode block
mul dword ptr BP_REL(ExtBlockSizeInBytes) // Inode *= ExtBlockSizeInBytes
mov ecx, dword ptr ds:[bx] // Get the Inode
sub ecx, eax // Subtract the original Inode with rounded down Inode
mov word ptr ds:[bp + di], cx // Store the rounded down Inode
ret
ExtSetOldFileSegment:
mov ebx, dword ptr BP_REL(ExtFileAddressOld) // Get the EXT old file address
jmp .ExtSegSkip
ExtSetFileSegment:
mov ebx, dword ptr BP_REL(ExtFileAddress) // Get the EXT file address
.ExtSegSkip:
shr ebx, 4 // Shift four bits to the right to get segment
jmp .ExtSkip
ExtSetInodeSegment:
mov bx, EXT_INODE_ADDRESS / 16 // Get the EXT inode address
.ExtSkip:
mov es, bx // Set ES
xor bx, bx // Clear BX
ret
// Read the Inode in EAX register
ExtReadInode:
xor edx, edx // Clear EDX before division
dec eax // Inode--
div dword ptr BP_REL(ExtInodesPerGroup) // Inode /= ExtInodesPerGroup
mov dword ptr BP_REL(ExtReadInodeGroup), eax // Store the Inode group
mov dword ptr BP_REL(ExtReadInodeIndex), edx // Store the Inode index
xor edx, edx // Clear EDX before multiplication
mul dword ptr BP_REL(ExtGroupDescSize) // Inode group *= ExtGroupDescSize
mov dword ptr BP_REL(ExtReadInodeGroup), eax // Store the precalculated Inode group
xor edx, edx // Clear EDX before multiplication
mov eax, dword ptr BP_REL(ExtReadInodeIndex) // Get the read Inode index
mul dword ptr BP_REL(ExtInodeSize) // Inode group *= ExtInodeSize
mov dword ptr BP_REL(ExtReadInodeIndex), eax // Store the Inode index
// Calculate the Inode index block
mov si, offset ExtReadInodeIndexBlock - start
call ExtCalculateBlock
// Calculate the Inode group block
mov eax, dword ptr BP_REL(ExtReadInodeGroup)
mov si, offset ExtReadInodeGroupBlock - start
call ExtCalculateBlock
// Calculate the Inode group offset
mov bx, offset ExtReadInodeGroup - start
mov si, offset ExtReadInodeGroupBlock - start
mov di, offset ExtReadInodeGroupOffset - start
call ExtCalculateOffset
// Calculate the Inode index offset
mov bx, offset ExtReadInodeIndex - start
mov si, offset ExtReadInodeIndexBlock - start
mov di, offset ExtReadInodeIndexOffset - start
call ExtCalculateOffset
// Read group descriptor
call ExtReadGroupDescriptor
// Set the offset address
mov si, word ptr BP_REL(ExtReadInodeGroupOffset)
// Get InodeTable field from the ExtGroupDescriptor structure
mov eax, dword ptr es:[si + EXT_GROUP_DESC_INODE_TABLE_OFFSET]
// Sum EAX with Inode index block
add eax, dword ptr BP_REL(ExtReadInodeIndexBlock)
jmp ExtReadBlock
msgDiskError:
.ascii "Disk error", CR, LF, NUL
msgAnyKey:
.ascii "Press any key", CR, LF, NUL
.org 509
BootPartition:
.byte 0
.word HEX(AA55) // BootSector signature
// End of bootsector
//
// Now starts the extra boot code that we will store
// at sector 1 on a EXT volume
LoadRootDirectory:
mov al, EXT_ROOT_INODE // Put the root directory inode number in AL
movzx eax, al // Convert AL to EAX
call ExtReadInode // Read the inode
call BasicReadFile // Load the directory entries using basic function
call SearchFile // Find the extended loader and run it
jmp ExtLdrPrintFileNotFound // If the extended loader wasn't found, display an error
ExtInodeDetectExtentsFlag:
mov eax, es:[si + EXT_INODE_FLAGS_OFFSET]
test eax, EXT_INODE_FLAG_EXTENTS
ret
ExtUpdateFileSize:
mov eax, dword ptr BP_REL(ExtBlockSizeInBytes)
ExtAdjustFileSize:
// Update the file size
sub dword ptr BP_REL(ExtFileSizeState), eax
add dword ptr BP_REL(ExtFileAddress), eax
ret
ExtReadFileDone:
push eax
mov eax, dword ptr BP_REL(ExtFileSizeState)
cmp eax, dword ptr BP_REL(ExtBlockSizeInBytes)
pop eax
ret
ExtFileReadBlocks:
push es
.FRLoop:
// Check if there is no more blocks to read
call ExtReadFileDone
jb .FRDone
// If the block count is zero then do nothing
test bx, bx
jz .FRDone
// Read the block
pushad
call ExtSetFileSegment
call ExtReadBlock
popad
// Update the file size
call ExtUpdateFileSize
// Go to the next block and decrement the block count
inc eax
dec bx
// Loop until all blocks are read
jmp .FRLoop
.FRDone:
pop es
ret
BasicReadFileExtents:
// Add block pointer offset
add si, EXT_INODE_BLOCK_POINTER_OFFSET
.DepthExtentsLoop:
// Load extent header depth
mov dx, word ptr es:[si + EXT_EXTENT_HEADER_DEPTH_OFFSET]
// Check if depth is zero
test dx, dx
jz .DepthExtentsDone
// Go to next extent
add si, EXT_EXTENT_SIZE
// Push all registers
pushad
// Read the extent block
mov eax, dword ptr es:[si + EXT_EXTENT_INDEX_LEAF_OFFSET]
call ExtSetInodeSegment
call ExtReadBlock
// Pop all registers
popad
// Reset SI
xor si, si
jmp .DepthExtentsLoop
.DepthExtentsDone:
// Load extent header entries
mov cx, word ptr es:[si + EXT_EXTENT_HEADER_ENTRIES_OFFSET]
.FinalExtentsLoop:
// Check if there is no more blocks to read
call ExtReadFileDone
jb .FinalExtentsDone
// Go to next extent
add si, EXT_EXTENT_SIZE
// Load extent length
mov bx, word ptr es:[si + EXT_EXTENT_LENGTH_OFFSET]
and ebx, HEX(FFFF)
// Check if extent is sparse
cmp bx, EXT_EXTENT_MAX_LENGTH
jbe .NotSparse
// Adjust sparse extent length
sub bx, EXT_EXTENT_MAX_LENGTH
// Adjust extent length to byte count
// by multiplying extent length to block size
xor edx, edx
mov eax, dword ptr BP_REL(ExtBlockSizeInBytes)
mul ebx
// Adjust file size for sparse extent
call ExtAdjustFileSize
jmp .FinalExtentsSkip
.NotSparse:
// Read blocks from extent start
mov eax, dword ptr es:[si + EXT_EXTENT_START_OFFSET]
call ExtFileReadBlocks
.FinalExtentsSkip:
// Loop to process next extent
loop .FinalExtentsLoop
.FinalExtentsDone:
ret
BasicReadFile:
push es
pushad
call ExtSetInodeSegment
// Set the correct Inode offset
mov si, word ptr BP_REL(ExtReadInodeIndexOffset)
// Set the old file address
mov eax, dword ptr BP_REL(ExtFileAddress)
mov dword ptr BP_REL(ExtFileAddressOld), eax
// Set the file size limit
mov eax, EXT_INODE_DATA_SIZE_LIMIT
// Load file size from Inode
mov ebx, dword ptr es:[si + EXT_INODE_SIZE_OFFSET]
// Compare and limit file size
cmp ebx, eax
jbe .BelowOrEqualSize
mov ebx, eax
.BelowOrEqualSize:
// Store the file size in the ExtFileSize variable
mov dword ptr BP_REL(ExtFileSize), ebx
// Set rounded up file size
add ebx, dword ptr BP_REL(ExtBlockSizeInBytes)
dec ebx
mov dword ptr BP_REL(ExtFileSizeState), ebx
// Don't use the extents method if theres no extents flag
call ExtInodeDetectExtentsFlag
jz .NoExtents
// If this Inode use Extents mapping then use the extents method and skip the entire classic method
call BasicReadFileExtents
jmp .LDone
.NoExtents:
// Set up for reading direct block addresses
xor ecx, ecx
mov cl, EXT_INODE_BLOCKS
add si, EXT_INODE_BLOCK_POINTER_OFFSET
.LLoop:
call ExtSetInodeSegment
call ExtReadFileDone
jb .LDone
// Get the block address
mov eax, dword ptr es:[si]
// If the block address is zero, skip the block
test eax, eax
jz .LSkipBlock
// Set the file segment
call ExtSetFileSegment
// Read the block
call ExtReadBlock
.LSkipBlock:
call ExtUpdateFileSize
// Increment block
add si, EXT_POINTER_SIZE
// Loop until all blocks are loaded
loop .LLoop
.LDone:
popad
pop es
ret
SearchFile:
call ExtSetOldFileSegment
call SwapESWithDS
xor si, si
mov dx, word ptr BP_REL(ExtFileSize)
.FLoop:
mov eax, dword ptr ds:[si] // Load directory Inode
cmp si, dx // End of buffer reached?
jae .Done // Abort the search if yes
// Save SI
push si
test eax, eax // Check if Inode is zero
jz .Skip // Skip this entry if yes
mov di, offset ExtLdrFileName // Load target filename address
mov cx, offset ExtLdrFileNameEnd - ExtLdrFileName // Length of filename to compare
cmp byte ptr ds:[si + EXT_DIRECTORY_ENTRY_NAME_LENGTH_OFFSET], cl // Compare if both names have the same length
jnz .Skip // Skip this entry if yes
add si, EXT_DIRECTORY_ENTRY_NAME_OFFSET // Move to filename in entry
repe cmpsb // Compare filenames
pop si // Restore SI
jz LoadExtLdr // Found matching file
push si // Save SI
.Skip:
// Restore SI
pop si
// Move to next directory entry and continue looping
add si, word ptr ds:[si + EXT_DIRECTORY_ENTRY_SIZE_OFFSET]
jmp .FLoop
.Done:
ret
LoadExtLdr:
// Swap ES and DS
call SwapESWithDS
push si // Save SI
mov si, offset msgLoadingExtLdr // Point SI to a loading message
call PutChars // Show the message
pop si // Restore SI
mov eax, dword ptr es:[si] // Load directory Inode
call ExtReadInode // Read the inode
mov si, word ptr BP_REL(ExtReadInodeIndexOffset) // Set the correct offset
// Get Inode type
mov ax, word ptr es:[si]
and ax, EXT_INODE_TYPE_MASK
cmp ax, EXT_INODE_TYPE_REGULAR // Check if regular file
jnz ExtLdrPrintRegFileError // If not, handle error
call BasicReadFile // Load the file using basic function
call ExtSetOldFileSegment // Set old file segment
call SwapESWithDS // Swap ES with DS before copy
// Copy the loaded file to 1KB ahead of this bootsector
xor si, si
mov di, offset ExtLdrEntryPoint
mov cx, EXTLDR_BOOTSECTOR_SIZE
rep movsb
ljmp16 0, ExtLdrEntryPoint
ExtLdrPrintFileNotFound:
// Make DS correct, display it and reboot
call SwapESWithDS
mov si, offset msgExtLdr
jmp DisplayItAndReboot
ExtLdrPrintRegFileError:
mov si, offset msgExtLdrNotRegularFile // ExtLdr not found message
DisplayItAndReboot:
call PutChars // Display it
jmp Reboot
ExtLdrFileName:
.ascii "extldr.sys"
ExtLdrFileNameEnd:
msgExtLdr:
.ascii "extldr.sys not found", CR, LF, NUL
msgExtLdrNotRegularFile:
.ascii "extldr.sys is not a regular file", CR, LF, NUL
msgLoadingExtLdr:
.ascii "Loading ExtLoader...", CR, LF, NUL
.org 1022
.word HEX(AA55) // BootSector signature
ExtLdrEntryPoint:
// ExtLdr is loaded here
.endcode16
END
#else
#define start BOOTSECTOR_BASE_ADDRESS
#define BootDrive (start + 3)
#define ExtVolumeStartSector (BootDrive + 1)
#define ExtBlockSize (ExtVolumeStartSector + 4)
#define ExtBlockSizeInBytes (ExtBlockSize + 4)
#define ExtPointersPerBlock (ExtBlockSizeInBytes + 4)
#define ExtGroupDescSize (ExtPointersPerBlock + 4)
#define ExtFirstDataBlock (ExtGroupDescSize + 4)
#define ExtInodeSize (ExtFirstDataBlock + 4)
#define ExtInodesPerGroup (ExtInodeSize + 4)
#define ExtFileSize (ExtInodesPerGroup + 4)
#define ExtFileAddress (ExtFileSize + 4)
#define ExtFileAddressOld (ExtFileAddress + 4)
#define ExtReadInodeGroup (ExtFileAddressOld + 4)
#define ExtReadInodeIndex (ExtReadInodeGroup + 4)
#define ExtReadInodeGroupBlock (ExtReadInodeIndex + 4)
#define ExtReadInodeIndexBlock (ExtReadInodeGroupBlock + 4)
#define ExtReadInodeGroupOffset (ExtReadInodeIndexBlock + 4)
#define ExtReadInodeIndexOffset (ExtReadInodeGroupOffset + 2)
#define BootPartition (BootDrive + 506)
#define ExtReadBlock word ptr ss:[bp-ExtReadBlockOffset]
#define ExtReadInode word ptr ss:[bp-ExtReadInodeOffset]
#define DisplayItAndReboot word ptr ss:[bp-DisplayItAndRebootOffset]
#define PutChars word ptr ss:[bp-PutCharsOffset]
#endif

View file

@ -1,688 +0,0 @@
// EXT2.ASM
// EXT2 Boot Sector
// Copyright (c) 2002, 2003 Brian Palmer
// [bp-0x04] Here we will store the number of sectors per track
// [bp-0x08] Here we will store the number of heads
// [bp-0x0c] Here we will store the size of the disk as the BIOS reports in CHS form
// [bp-0x10] Here we will store the number of LBA sectors read
#include <asm.inc>
.code16
SECTORS_PER_TRACK = HEX(04)
NUMBER_OF_HEADS = HEX(08)
BIOS_CHS_DRIVE_SIZE = HEX(0C)
LBA_SECTORS_READ = HEX(10)
EXT2_ROOT_INO = 2
EXT2_S_IFMT = HEX(0f0)
EXT2_S_IFREG = HEX(080)
//org 7c00h
start:
jmp short main
nop
BootDrive:
.byte HEX(80)
//BootPartition db 0 // Moved to end of boot sector to have a standard format across all boot sectors
//SectorsPerTrack db 63 // Moved to [bp-SECTORS_PER_TRACK]
//NumberOfHeads dw 16 // Moved to [bp-NUMBER_OF_HEADS]
//BiosCHSDriveSize dd (1024 * 1024 * 63) // Moved to [bp-BIOS_CHS_DRIVE_SIZE]
//LBASectorsRead dd 0 // Moved to [bp-LBA_SECTORS_READ]
Ext2VolumeStartSector:
.long 263088 // Start sector of the ext2 volume
Ext2BlockSize:
.long 2 // Block size in sectors
Ext2BlockSizeInBytes:
.long 1024 // Block size in bytes
Ext2PointersPerBlock:
.long 256 // Number of block pointers that can be contained in one block
Ext2GroupDescPerBlock:
.long 32 // Number of group descriptors per block
Ext2FirstDataBlock:
.long 1 // First data block (1 for 1024-byte blocks, 0 for bigger sizes)
Ext2InodesPerGroup:
.long 2048 // Number of inodes per group
Ext2InodesPerBlock:
.long 8 // Number of inodes per block
Ext2ReadEntireFileLoadSegment:
.word 0
Ext2InodeIndirectPointer:
.long 0
Ext2InodeDoubleIndirectPointer:
.long 0
Ext2BlocksLeftToRead:
.long 0
main:
xor ax,ax // Setup segment registers
mov ds,ax // Make DS correct
mov es,ax // Make ES correct
mov ss,ax // Make SS correct
mov bp, HEX(7c00)
mov sp, HEX(7b00) // Setup a stack
mov si, offset BootDrive
cmp byte ptr [si], HEX(0ff) // If they have specified a boot drive then use it
jne GetDriveParameters
mov [si],dl // Save the boot drive
GetDriveParameters:
mov ah, 8
mov dl,[si] // Get boot drive in dl
int HEX(13) // Request drive parameters from the bios
jnc CalcDriveSize // If the call succeeded then calculate the drive size
// If we get here then the call to the BIOS failed
// so just set CHS equal to the maximum addressable
// size
mov cx, HEX(0ffff)
mov dh,cl
CalcDriveSize:
// Now that we have the drive geometry
// lets calculate the drive size
mov bl,ch // Put the low 8-bits of the cylinder count into BL
mov bh,cl // Put the high 2-bits in BH
shr bh,6 // Shift them into position, now BX contains the cylinder count
and cl, HEX(3f) // Mask off cylinder bits from sector count
// CL now contains sectors per track and DH contains head count
movzx eax,dh // Move the heads into EAX
movzx ebx,bx // Move the cylinders into EBX
movzx ecx,cl // Move the sectors per track into ECX
inc eax // Make it one based because the bios returns it zero based
mov [bp-NUMBER_OF_HEADS],eax // Save number of heads
mov [bp-SECTORS_PER_TRACK],ecx // Save number of sectors per track
inc ebx // Make the cylinder count one based also
mul ecx // Multiply heads with the sectors per track, result in edx:eax
mul ebx // Multiply the cylinders with (heads * sectors) [stored in edx:eax already]
// We now have the total number of sectors as reported
// by the bios in eax, so store it in our variable
mov [bp-BIOS_CHS_DRIVE_SIZE],eax
LoadExtraBootCode:
// First we have to load our extra boot code at
// sector 1 into memory at [0000:7e00h]
//mov eax,01h
xor eax,eax
inc eax // Read logical sector 1, EAX now = 1
mov cx,1 // Read one sector
mov bx, HEX(7e00) // Read sector to [0000:7e00h]
call ReadSectors
jmp LoadRootDirectory
// Reads ext2 group descriptor into [7000:8000]
// We read it to this arbitrary location so
// it will not cross a 64k boundary
// EAX has group descriptor number to read
Ext2ReadGroupDesc:
shl eax,5 // Group = (Group * sizeof(GROUP_DESCRIPTOR) /* 32 */)
xor edx,edx
div dword ptr [bp+Ext2GroupDescPerBlock] // Group = (Group / Ext2GroupDescPerBlock)
add eax, dword ptr [bp+Ext2FirstDataBlock] // Group = Group + Ext2FirstDataBlock + 1
inc eax // EAX now has the group descriptor block number
// EDX now has the group descriptor offset in the block
// Adjust the read offset so that the
// group descriptor is read to 7000:8000
mov ebx, HEX(78000)
sub ebx,edx
shr ebx,4
mov es,bx
xor bx,bx
// Everything is now setup to call Ext2ReadBlock
// Instead of using the call instruction we will
// just put Ext2ReadBlock right after this routine
// Reads ext2 block into ES:[BX]
// EAX has logical block number to read
Ext2ReadBlock:
mov ecx, dword ptr [bp+Ext2BlockSize]
mul ecx
jmp ReadSectors
// Reads ext2 inode into [6000:8000]
// We read it to this arbitrary location so
// it will not cross a 64k boundary
// EAX has inode number to read
Ext2ReadInode:
dec eax // Inode = Inode - 1
xor edx,edx
div dword ptr [bp+Ext2InodesPerGroup] // Inode = (Inode / Ext2InodesPerGroup)
mov ebx,eax // EBX now has the inode group number
mov eax,edx
xor edx,edx
div dword ptr [bp+Ext2InodesPerBlock] // Inode = (Inode / Ext2InodesPerBlock)
shl edx,7 // FIXME: InodeOffset *= 128 (make the array index a byte offset)
// EAX now has the inode offset block number from inode table
// EDX now has the inode offset in the block
// Save the inode values and put the group
// descriptor number in EAX and read it in
push edx
push eax
mov eax,ebx
call Ext2ReadGroupDesc
// Group descriptor has been read, now
// grab the inode table block number from it
push HEX(7000)
pop es
mov di, HEX(8008)
pop eax // Restore inode offset block number from stack
add eax, es:[di] // Add the inode table start block
// Adjust the read offset so that the
// inode we want is read to 6000:8000
pop edx // Restore inode offset in the block from stack
mov ebx, HEX(68000)
sub ebx,edx
shr ebx,4
mov es,bx
xor bx,bx
call Ext2ReadBlock
ret
// Reads logical sectors into ES:[BX]
// EAX has logical sector number to read
// CX has number of sectors to read
ReadSectors:
add eax, dword ptr [bp+Ext2VolumeStartSector] // Add the start of the volume
cmp eax, [bp-BIOS_CHS_DRIVE_SIZE] // Check if they are reading a sector outside CHS range
jae ReadSectorsLBA // Yes - go to the LBA routine
// If at all possible we want to use LBA routines because
// They are optimized to read more than 1 sector per read
pushad // Save logical sector number & sector count
CheckInt13hExtensions: // Now check if this computer supports extended reads
mov ah, HEX(41) // AH = 41h
mov bx, HEX(55aa) // BX = 55AAh
mov dl, byte ptr [bp+BootDrive] // DL = drive (80h-FFh)
int HEX(13) // IBM/MS INT 13 Extensions - INSTALLATION CHECK
jc ReadSectorsCHS // CF set on error (extensions not supported)
cmp bx, HEX(0aa55) // BX = AA55h if installed
jne ReadSectorsCHS
test cl,1 // CX = API subset support bitmap
jz ReadSectorsCHS // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
popad // Restore sector count & logical sector number
ReadSectorsLBA:
pushad // Save logical sector number & sector count
cmp cx, 64 // Since the LBA calls only support 0x7F sectors at a time we will limit ourselves to 64
jbe ReadSectorsSetupDiskAddressPacket // If we are reading less than 65 sectors then just do the read
mov cx,64 // Otherwise read only 64 sectors on this loop iteration
ReadSectorsSetupDiskAddressPacket:
mov [bp-LBA_SECTORS_READ],cx
mov word ptr [bp-LBA_SECTORS_READ+2],0
data32 push 0
push eax // Put 64-bit logical block address on stack
push es // Put transfer segment on stack
push bx // Put transfer offset on stack
push cx // Set transfer count
push 16 // Set size of packet to 10h
mov si,sp // Setup disk address packet on stack
mov dl, byte ptr [bp+BootDrive] // Drive number
mov ah, HEX(42) // Int 13h, AH = 42h - Extended Read
int HEX(13) // Call BIOS
jc PrintDiskError // If the read failed then abort
add sp, 16 // Remove disk address packet from stack
popad // Restore sector count & logical sector number
push bx
mov ebx, [bp-LBA_SECTORS_READ]
add eax,ebx // Increment sector to read
shl ebx,5
mov dx,es
add dx,bx // Setup read buffer for next sector
mov es,dx
pop bx
sub cx,[bp-LBA_SECTORS_READ]
jnz ReadSectorsLBA // Read next sector
ret
// Reads logical sectors into ES:[BX]
// EAX has logical sector number to read
// CX has number of sectors to read
ReadSectorsCHS:
popad // Get logical sector number & sector count off stack
ReadSectorsCHSLoop:
pushad
xor edx,edx
mov ecx, [bp-SECTORS_PER_TRACK]
div ecx // Divide logical by SectorsPerTrack
inc dl // Sectors numbering starts at 1 not 0
mov cl,dl // Sector in CL
mov edx,eax
shr edx,16
div word ptr [bp-NUMBER_OF_HEADS] // Divide logical by number of heads
mov dh,dl // Head in DH
mov dl, byte ptr [bp+BootDrive] // Drive number in DL
mov ch,al // Cylinder in CX
ror ah,2 // Low 8 bits of cylinder in CH, high 2 bits
// in CL shifted to bits 6 & 7
or cl,ah // Or with sector number
mov ax, HEX(0201)
int HEX(13) // DISK - READ SECTORS INTO MEMORY
// AL = number of sectors to read, CH = track, CL = sector
// DH = head, DL = drive, ES:BX -> buffer to fill
// Return: CF set on error, AH = status (see AH=01h), AL = number of sectors read
jc PrintDiskError // If the read failed then abort
popad
inc eax // Increment Sector to Read
mov dx,es
add dx, HEX(20) // Increment read buffer for next sector
mov es,dx
loop ReadSectorsCHSLoop // Read next sector
ret
// Displays a disk error message
// And reboots
PrintDiskError:
mov si,msgDiskError // Bad boot disk message
call PutChars // Display it
Reboot:
mov si,msgAnyKey // Press any key message
call PutChars // Display it
xor ax,ax
int HEX(16) // Wait for a keypress
int HEX(19) // Reboot
PutChars:
lodsb
or al,al
jz short Done
call PutCharsCallBios
jmp short PutChars
PutCharsCallBios:
mov ah, HEX(0e)
mov bx, HEX(07)
int HEX(10)
ret
Done:
mov al, HEX(0d)
call PutCharsCallBios
mov al, HEX(0a)
call PutCharsCallBios
ret
msgDiskError:
.ascii "Disk error", NUL
// Sorry, need the space...
//msgAnyKey db 'Press any key to restart',0
msgAnyKey:
.ascii "Press key", NUL
// times 509-($-$$) db 0 // Pad to 509 bytes
.org 509
BootPartition:
.byte 0
.word HEX(0aa55) // BootSector signature
// End of bootsector
//
// Now starts the extra boot code that we will store
// at sector 1 on a EXT2 volume
LoadRootDirectory:
mov eax,EXT2_ROOT_INO // Put the root directory inode number in EAX
call Ext2ReadInode // Read in the inode
// Point ES:DI to the inode structure at 6000:8000
push HEX(6000)
pop es
mov di, HEX(8000)
push di
push es // Save these for later
// Get root directory size from inode structure
mov eax, es:[di+4]
push eax
// Now that the inode has been read in load
// the root directory file data to 0000:8000
call Ext2ReadEntireFile
// Since the root directory was loaded to 0000:8000
// then add 8000h to the root directory's size
pop eax
mov edx, HEX(8000) // Set EDX to the current offset in the root directory
add eax,edx // Initially add 8000h to the size of the root directory
SearchRootDirectory:
push edx // Save current offset in root directory
push eax // Save the size of the root directory
// Now we have to convert the current offset
// in the root directory to a SEGMENT:OFFSET pair
mov eax,edx
xor edx,edx
mov ecx,16
div ecx // Now AX:DX has segment & offset
mov es,ax
mov di,dx
push di // Save the start of the directory entry
add di, 8 // Add the offset to the filename
mov si,filename
mov cl,11
repe cmpsb // Compare the file names
pop di
pop eax
pop edx
jz FoundFile
// Nope, didn't find it in this entry, keep looking
movzx ecx,word ptr es:[di+4]
add edx,ecx
// Check to see if we have reached the
// end of the root directory
cmp edx,eax
jb SearchRootDirectory
jmp PrintFileNotFound
FoundFile:
mov eax,es:[di] // Get inode number from directory entry
call Ext2ReadInode // Read in the inode
// Point ES:DI to the inode structure at 6000:8000
pop es
pop di // These were saved earlier
mov cx, es:[di] // Get the file mode so we can make sure it's a regular file
and ch,EXT2_S_IFMT // Mask off everything but the file type
cmp ch,EXT2_S_IFREG // Make sure it's a regular file
je LoadFreeLoader
jmp PrintRegFileError
LoadFreeLoader:
mov si,msgLoading // "Loading FreeLoader..." message
call PutChars // Display it
call Ext2ReadEntireFile // Read freeldr.sys to 0000:8000
mov dl, byte ptr [bp+BootDrive]
mov dh, byte ptr [bp+BootPartition]
push 0 // push segment (0x0000)
mov eax, [HEX(8000) + HEX(0A8)] // load the RVA of the EntryPoint into eax
add eax, HEX(8000) // RVA -> VA
push ax // push offset
retf // Transfer control to FreeLoader
// Reads ext2 file data into [0000:8000]
// This function assumes that the file's
// inode has been read in to 6000:8000 *and*
// ES:DI points to 6000:8000
// This will load all the blocks up to
// and including the double-indirect pointers.
// This should be sufficient because it
// allows for ~64MB which is much bigger
// than we need for a boot loader.
Ext2ReadEntireFile:
// Reset the load segment
mov word ptr [bp+Ext2ReadEntireFileLoadSegment], HEX(800)
// Now we must calculate how
// many blocks to read in
// We will do this by rounding the
// file size up to the next block
// size and then dividing by the block size
mov eax, dword ptr [bp+Ext2BlockSizeInBytes] // Get the block size in bytes
push eax
dec eax // Ext2BlockSizeInBytes -= 1
add eax, es:[di+4] // Add the file size
xor edx,edx
pop ecx // Divide by the block size in bytes
div ecx // EAX now contains the number of blocks to load
push eax
// Make sure the file size isn't zero
cmp eax, 0
jnz Ext2ReadEntireFile2
jmp PrintFileSizeError
Ext2ReadEntireFile2:
// Save the indirect & double indirect pointers
mov edx, es:[di+ HEX(58)] // Get indirect pointer
mov dword ptr [bp+Ext2InodeIndirectPointer], edx // Save indirect pointer
mov edx, es:[di+ HEX(5c)] // Get double indirect pointer
mov dword ptr [bp+Ext2InodeDoubleIndirectPointer],edx // Save double indirect pointer
// Now copy the direct pointers to 7000:0000
// so that we can call Ext2ReadDirectBlocks
push ds // Save DS
push es
push HEX(7000)
pop es
pop ds
mov si, HEX(8028)
xor di,di // DS:SI = 6000:8028 ES:DI = 7000:0000
mov cx,24 // Moving 24 words of data
rep movsw
pop ds // Restore DS
// Now we have all the block pointers in the
// right location so read them in
pop eax // Restore the total number of blocks in this file
xor ecx,ecx // Set the max count of blocks to read to 12
mov cl,12 // which is the number of direct block pointers in the inode
call Ext2ReadDirectBlockList
// Check to see if we actually have
// blocks left to read
cmp eax, 0
jz Ext2ReadEntireFileDone
// Now we have read all the direct blocks in
// the inode. So now we have to read the indirect
// block and read all it's direct blocks
push eax // Save the total block count
mov eax, dword ptr [bp+Ext2InodeIndirectPointer] // Get the indirect block pointer
push HEX(7000)
pop es
xor bx,bx // Set the load address to 7000:0000
call Ext2ReadBlock // Read the block
// Now we have all the block pointers from the
// indirect block in the right location so read them in
pop eax // Restore the total block count
mov ecx, dword ptr [bp+Ext2PointersPerBlock] // Get the number of block pointers that one block contains
call Ext2ReadDirectBlockList
// Check to see if we actually have
// blocks left to read
cmp eax, 0
jz Ext2ReadEntireFileDone
// Now we have read all the direct blocks from
// the inode's indirect block pointer. So now
// we have to read the double indirect block
// and read all it's indirect blocks
// (whew, it's a good thing I don't support triple indirect blocks)
mov dword ptr [bp+Ext2BlocksLeftToRead],eax // Save the total block count
mov eax, dword ptr [bp+Ext2InodeDoubleIndirectPointer] // Get the double indirect block pointer
push HEX(7800)
pop es
push es // Save an extra copy of this value on the stack
xor bx,bx // Set the load address to 7000:8000
call Ext2ReadBlock // Read the block
pop es // Put 7800h into ES (saved on the stack already)
xor di,di
Ext2ReadIndirectBlock:
mov eax, es:[di] // Get indirect block pointer
add di, 4 // Update DI for next array index
push es
push di
push HEX(7000)
pop es
xor bx,bx // Set the load address to 7000:0000
call Ext2ReadBlock // Read the indirect block
// Now we have all the block pointers from the
// indirect block in the right location so read them in
mov eax, dword ptr [bp+Ext2BlocksLeftToRead] // Restore the total block count
mov ecx, dword ptr [bp+Ext2PointersPerBlock] // Get the number of block pointers that one block contains
call Ext2ReadDirectBlockList
mov dword ptr [bp+Ext2BlocksLeftToRead],eax // Save the total block count
pop di
pop es
// Check to see if we actually have
// blocks left to read
cmp eax, 0
jnz Ext2ReadIndirectBlock
Ext2ReadEntireFileDone:
ret
// Reads a maximum number of blocks
// from an array at 7000:0000
// and updates the total count
// ECX contains the max number of blocks to read
// EAX contains the number of blocks left to read
// On return:
// EAX contains the new number of blocks left to read
Ext2ReadDirectBlockList:
cmp eax,ecx // Compare it to the maximum number of blocks to read
ja CallExt2ReadDirectBlocks // If it will take more blocks then just read all of the blocks
mov cx,ax // Otherwise adjust the block count accordingly
CallExt2ReadDirectBlocks:
sub eax,ecx // Subtract the number of blocks being read from the total count
push eax // Save the new total count
call Ext2ReadDirectBlocks
pop eax // Restore the total count
ret
// Reads a specified number of blocks
// from an array at 7000:0000
// CX contains the number of blocks to read
Ext2ReadDirectBlocks:
push HEX(7000)
pop es
xor di,di // Set ES:DI = 7000:0000
Ext2ReadDirectBlocksLoop:
mov eax,es:[di] // Get direct block pointer from array
add di, 4 // Update DI for next array index
push cx // Save number of direct blocks left
push es // Save array segment
push di // Save array offset
mov es,[bp+Ext2ReadEntireFileLoadSegment]
xor bx,bx // Setup load address for next read
call Ext2ReadBlock // Read the block (this updates ES for the next read)
mov [bp+Ext2ReadEntireFileLoadSegment],es // Save updated ES
pop di // Restore the array offset
pop es // Restore the array segment
pop cx // Restore the number of blocks left
loop Ext2ReadDirectBlocksLoop
// At this point all the direct blocks should
// be loaded and ES (Ext2ReadEntireFileLoadSegment)
// should be ready for the next read.
ret
// Displays a file not found error message
// And reboots
PrintFileNotFound:
mov si,msgFreeLdr // FreeLdr not found message
jmp short DisplayItAndReboot
// Displays a file size is 0 error
// And reboots
PrintFileSizeError:
mov si,msgFileSize // Error message
jmp short DisplayItAndReboot
// Displays a file is not a regular file error
// And reboots
PrintRegFileError:
mov si,msgRegFile // Error message
DisplayItAndReboot:
call PutChars // Display it
jmp Reboot
msgFreeLdr:
.ascii "freeldr.sys not found", NUL
msgFileSize:
.ascii "File size 0", NUL
msgRegFile:
.ascii "freeldr.sys isnt a regular file", NUL
filename:
.ascii "freeldr.sys"
msgLoading:
.ascii "Loading...", NUL
// times 1022-($-$$) db 0 // Pad to 1022 bytes
.org 1022
.word HEX(0aa55) // BootSector signature
.endcode16
END

View file

@ -0,0 +1,583 @@
/*
* PROJECT: FreeLoader
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: EXTFS second stage loader
* COPYRIGHT: Copyright 2024-2025 Daniel Victor <ilauncherdeveloper@gmail.com>
*/
#define INCLUDED_ASM
#include "ext.S"
ExtLdrEntryPoint:
// Clear segment registers
xor eax, eax
mov ds, ax
mov es, ax
// Read root directory Inode
mov al, EXT_ROOT_INODE
call ExtReadInode
// Set file base address and search for freeldr
mov dword ptr BP_REL(ExtFileAddress), FREELDR_BASE
call ExtSetFileSegment
// Swap ES with DS segment and clear SI
call SwapESWithDS
xor si, si
// Set the directory entries size limit
mov eax, EXT_INODE_DATA_SIZE_LIMIT
// Get directory entries size
mov edx, dword ptr BP_REL(ExtFileSize)
// Set the buffer size with limit
cmp edx, eax
jbe .FLoop2
mov edx, eax
.FLoop2:
// Load directory Inode
mov eax, dword ptr ds:[si]
// If the buffer reached the end then abort the search
cmp si, dx
jae FreeLdrPrintFileNotFound
// Save SI
push si
// If the Inode is zero then skip this entry
test eax, eax
jz .FSkip2
mov di, offset FreeLdrFileName // Load target filename address
mov cx, offset FreeLdrFileNameEnd - FreeLdrFileName // Length of filename to compare
cmp byte ptr ds:[si + EXT_DIRECTORY_ENTRY_NAME_LENGTH_OFFSET], cl // Compare if both names have the same length
jnz .FSkip2 // Skip this entry if yes
add si, EXT_DIRECTORY_ENTRY_NAME_OFFSET // Move to filename in entry
repe cmpsb // Compare filenames
pop si // Restore SI
jz LoadFreeLdr // Found matching file
push si // Save SI
.FSkip2:
// Restore SI
pop si
// Move to next directory entry
add si, word ptr ds:[si + EXT_DIRECTORY_ENTRY_SIZE_OFFSET]
jmp .FLoop2
ExtFileIndirectPointer:
.long 0
ExtFileIndirectPointerDouble:
.long 0
SwapESWithDS:
// Swap ES and DS
push es
push ds
pop es
pop ds
ret
ExtSetFileSegment:
mov ebx, dword ptr BP_REL(ExtFileAddress) // Get the EXT file address
shr ebx, 4 // Shift four bits to the right to get segment
jmp .ExtSkip
ExtSetInodeSegment:
mov bx, EXT_INODE_ADDRESS / 16 // Get the EXT inode address
jmp .ExtSkip
ExtSetBlockSegment:
mov bx, EXT_BLOCK_ADDRESS / 16 // Get the EXT block address
jmp .ExtSkip
ExtSetBlock2Segment:
mov bx, EXT_BLOCK2_ADDRESS / 16 // Get the EXT second block address
jmp .ExtSkip
ExtSetBlock3Segment:
mov bx, EXT_BLOCK3_ADDRESS / 16 // Get the EXT third block address
jmp .ExtSkip
ExtSetBlock4Segment:
mov bx, EXT_BLOCK4_ADDRESS / 16 // Get the EXT fourth block address
jmp .ExtSkip
ExtSetBlock5Segment:
mov bx, EXT_BLOCK5_ADDRESS / 16 // Get the EXT fifth block address
jmp .ExtSkip
ExtSetBlock6Segment:
mov bx, EXT_BLOCK6_ADDRESS / 16 // Get the EXT sixth block address
jmp .ExtSkip
ExtSetBlock7Segment:
mov bx, EXT_BLOCK7_ADDRESS / 16 // Get the EXT seventh block address
.ExtSkip:
mov es, bx // Set ES
xor bx, bx // Clear BX
ret
ExtUpdateFileSize:
mov eax, dword ptr BP_REL(ExtBlockSizeInBytes)
ExtAdjustFileSize:
// Update the file size
sub dword ptr BP_REL(ExtFileSizeState), eax
add dword ptr BP_REL(ExtFileAddress), eax
ret
ExtInodeDetectExtentsFlag:
push eax
mov eax, es:[si + EXT_INODE_FLAGS_OFFSET]
test eax, EXT_INODE_FLAG_EXTENTS
pop eax
ret
ExtFileReadBlocks:
// If the block count is zero then do nothing
test bx, bx
jz .FRDone
// Read the block
call ExtFileReadBlock
// Go to the next block and decrement the block count
inc eax
dec bx
// Loop until all blocks are read
jmp ExtFileReadBlocks
.FRDone:
ret
ExtSafeReadBlock:
pushad
// If the block is zero then just clear the block
test eax, eax
jz .NullBlk
// Read the block safely
call ExtReadBlock
jmp .RBDone
.NullBlk:
// If the block is 0 then just clear the block
mov di, bx
xor ax, ax
mov cx, word ptr BP_REL(ExtBlockSizeInBytes)
rep stosb
.RBDone:
popad
ret
// Reads a block from the EXT file
// EAX has the block to read
// ExtFileAddress has the address to write to
ExtFileReadBlock:
push es
pushad
// Set the EXT file segment
call ExtSetFileSegment
// Read the block safely
call ExtSafeReadBlock
// Update the file size
call ExtUpdateFileSize
// Exit
popad
pop es
ret
ExtReadEntireFileDone:
push eax // Save EAX register
mov eax, dword ptr BP_REL(ExtBlockSizeInBytes) // Load block size in bytes into EAX
cmp dword ptr BP_REL(ExtFileSizeState), eax // Compare file size state with block size
pop eax // Restore EAX register
ret // Return from procedure
// Loops over the block pointers and reads the blocks
// until the file size state is zero
ExtReadEntireFileLoop:
call ExtReadEntireFileDone
jb .RDone
mov eax, dword ptr es:[si]
// Read the block safely
call ExtFileReadBlock
add si, EXT_POINTER_SIZE
loop ExtReadEntireFileLoop
.RDone:
ret
// Reads the double indirect block data
// This function is used to read the blocks that are not contiguous
// to the blocks that are pointed by the indirect block
ExtReadEntireFileIndirectDouble:
push es
pushad
// Check if there is no more blocks to read
call ExtReadEntireFileDone
jb .RDone4
// Reset block offset
xor si, si
// Load the double indirect block address
mov eax, dword ptr BP_REL(ExtFileIndirectPointerDouble)
call ExtSetBlock2Segment
call ExtSafeReadBlock
// Load the number of pointers per block
mov ecx, dword ptr BP_REL(ExtPointersPerBlock)
.RLoop4:
// Check if there is no more blocks to read
call ExtReadEntireFileDone
jb .RDone4
// Get the block address
mov eax, dword ptr es:[si]
// Read the indirect block
call ExtReadEntireFileIndirect
// Increment block offset
add si, EXT_POINTER_SIZE
// Loop until all blocks are read
loop .RLoop4
.RDone4:
popad
pop es
ret
// Reads the indirect block data
ExtReadEntireFileIndirect:
push es
pushad
// Check if there is no more blocks to read
call ExtReadEntireFileDone
jb .RDone3
// Reset block offset
xor si, si
// Set the block segment
call ExtSetBlockSegment
// Read the block safely
call ExtSafeReadBlock
// Read the blocks
mov ecx, dword ptr BP_REL(ExtPointersPerBlock)
call ExtReadEntireFileLoop
.RDone3:
popad
pop es
ret
// Reads the direct block data
ExtReadEntireFileDirect:
push es
pushad
// If there is no more blocks to read then abort it
call ExtReadEntireFileDone
jb .RDone2
// Move to block pointer in Inode
add si, EXT_INODE_BLOCK_POINTER_OFFSET
xor ecx, ecx
mov cl, EXT_INODE_BLOCKS
// Set the correct segment
call ExtSetInodeSegment
// Read the blocks
call ExtReadEntireFileLoop
.RDone2:
popad
pop es
ret
ExtExtentsLevelSegmentFunctionTable:
.word ExtSetBlockSegment
.word ExtSetBlock2Segment
.word ExtSetBlock3Segment
.word ExtSetBlock4Segment
.word ExtSetBlock5Segment
.word ExtSetBlock6Segment
ExtExtentsLevelFunctionTable:
.word ExtReadEntireFileExtentsLevel0
.word ExtReadEntireFileExtentsLevelX
.word ExtReadEntireFileExtentsLevelX
.word ExtReadEntireFileExtentsLevelX
.word ExtReadEntireFileExtentsLevelX
.word ExtReadEntireFileExtentsLevelX
ExtReadEntireFileExtentsLevelX:
push es
pushad
// Load extent header entries
mov cx, word ptr es:[si + EXT_EXTENT_HEADER_ENTRIES_OFFSET]
.ExtentsLoopX:
// Check if there is no more blocks to read
call ExtReadEntireFileDone
jb .ExtentsDoneX
// Go to next extent
add si, EXT_EXTENT_SIZE
// Save current state
push es
pushad
// Read the extent block
mov eax, dword ptr es:[si + EXT_EXTENT_INDEX_LEAF_OFFSET]
// Set block segment and read block safely
call ExtSetBlock7Segment
call ExtSafeReadBlock
// Reset SI for calculations
xor si, si
// Calculate function table offset
mov bp, word ptr es:[si + EXT_EXTENT_HEADER_DEPTH_OFFSET]
shl bp, 1
// Call the segment and level functions
call word ptr cs:ExtExtentsLevelSegmentFunctionTable[bp]
call ExtSafeReadBlock
call word ptr cs:ExtExtentsLevelFunctionTable[bp]
// Restore state and continue loop
popad
pop es
loop .ExtentsLoopX
.ExtentsDoneX:
popad
pop es
ret
ExtReadEntireFileExtentsLevel0:
push es
pushad
// Load extent header entries
mov cx, word ptr es:[si + EXT_EXTENT_HEADER_ENTRIES_OFFSET]
.ExtentsLoop0:
// Check if there is no more blocks to read
call ExtReadEntireFileDone
jb .ExtentsDone0
// Go to next extent
add si, EXT_EXTENT_SIZE
// Load extent length
mov bx, word ptr es:[si + EXT_EXTENT_LENGTH_OFFSET]
and ebx, HEX(FFFF)
// Check if extent is sparse
cmp bx, EXT_EXTENT_MAX_LENGTH
jbe .NotSparse
// Adjust sparse extent length
sub bx, EXT_EXTENT_MAX_LENGTH
push es
.SparseLoop:
// Check if the sparse blocks reached the end
test bx, bx
jz .SparseDone
xor eax, eax
call ExtFileReadBlock
// Decrement the block count
dec bx
jmp .SparseLoop
.SparseDone:
pop es
jmp .ExtentsSkip0
.NotSparse:
// Read blocks from extent start
mov eax, dword ptr es:[si + EXT_EXTENT_START_OFFSET]
call ExtFileReadBlocks
.ExtentsSkip0:
loop .ExtentsLoop0
.ExtentsDone0:
popad
pop es
ret
ExtReadEntireFileExtents:
push es
pushad
// Add the extent header offset
add si, EXT_INODE_BLOCK_POINTER_OFFSET
// Load extent header depth
mov ax, word ptr es:[si + EXT_EXTENT_HEADER_DEPTH_OFFSET]
// Check if the file has more than the maximum allowed levels
cmp ax, EXT_EXTENT_MAX_LEVEL
ja FreeLdrPrintFileBig
// If the extent header depth is zero then this is a level 0 extent
test ax, ax
jz .Level0
// Call the recursive function
.LevelX:
call ExtReadEntireFileExtentsLevelX
// Jump to the end of the function
jmp .FEDone
// Level 0 extent
.Level0:
// Call the level 0 extent function
call ExtReadEntireFileExtentsLevel0
// End of the function
.FEDone:
popad
pop es
ret
// Reads the entire file in EXTFS
ExtReadEntireFile:
// Set the correct Inode offset
mov si, word ptr BP_REL(ExtReadInodeIndexOffset)
// Set file size
mov ecx, dword ptr es:[si + EXT_INODE_SIZE_OFFSET]
mov dword ptr BP_REL(ExtFileSize), ecx
// If file size is zero then abort it
test ecx, ecx
jz FreeLdrPrintFileZero
// Set rounded up file size
add ecx, dword ptr BP_REL(ExtBlockSizeInBytes)
dec ecx
mov dword ptr BP_REL(ExtFileSizeState), ecx
// Use extents method if necessary
call ExtInodeDetectExtentsFlag
jz .NoExtents
// Call the extents method
call ExtReadEntireFileExtents
ret
.NoExtents:
// Set Indirect pointer
mov eax, dword ptr es:[si + (EXT_INODE_BLOCK_POINTER_OFFSET + (EXT_INODE_BLOCKS * EXT_POINTER_SIZE))]
mov dword ptr BP_REL(ExtFileIndirectPointer), eax
// Set Double indirect pointer
mov eax, dword ptr es:[si + (EXT_INODE_BLOCK_POINTER_OFFSET + (EXT_INODE_BLOCKS * EXT_POINTER_SIZE) + EXT_POINTER_SIZE)]
mov dword ptr BP_REL(ExtFileIndirectPointerDouble), eax
call ExtReadEntireFileDirect // Read the direct blocks
mov eax, dword ptr BP_REL(ExtFileIndirectPointer) // Load the simple indirect pointer
call ExtReadEntireFileIndirect // Read the simple indirect blocks
call ExtReadEntireFileIndirectDouble // Read the double indirect blocks
ret
LoadFreeLdr:
// Swap ES and DS
call SwapESWithDS
// Show output loading status
push si
mov si, offset msgLoadingFreeLdr
call PutChars
pop si
// Load Inode at directory entry
mov eax, dword ptr es:[si]
call ExtReadInode
mov si, word ptr BP_REL(ExtReadInodeIndexOffset)
// Get Inode type
mov ax, word ptr es:[si]
and ax, EXT_INODE_TYPE_MASK
// If it's not a regular file then abort it
cmp ax, EXT_INODE_TYPE_REGULAR
jnz FreeLdrPrintRegFileError
// Load Freeldr at FREELDR_BASE
mov dword ptr BP_REL(ExtFileAddress), FREELDR_BASE
call ExtReadEntireFile
// Restore the boot drive and partition
mov dl, byte ptr BP_REL(BootDrive)
mov dh, byte ptr BP_REL(BootPartition)
// Transfer execution to the bootloader
ljmp16 FREELDR_BASE / 16, 0
FreeLdrPrintFileBig:
// Make DS correct, display it and reboot
call SwapESWithDS
mov si, offset msgFreeLdrBig
jmp DisplayItAndReboot
FreeLdrPrintFileZero:
// Make DS correct, display it and reboot
call SwapESWithDS
mov si, offset msgFreeLdrZero
jmp DisplayItAndReboot
FreeLdrPrintFileNotFound:
// Make DS correct, display it and reboot
call SwapESWithDS
mov si, offset msgFreeLdr
jmp DisplayItAndReboot
FreeLdrPrintRegFileError:
// Make DS correct, display it and reboot
call SwapESWithDS
mov si, offset msgFreeLdrNotRegularFile // FreeLdr not found message
jmp DisplayItAndReboot
FreeLdrFileName:
.ascii "freeldr.sys"
FreeLdrFileNameEnd:
msgFreeLdrZero:
.ascii "freeldr.sys size is zero", CR, LF, NUL
msgFreeLdrBig:
.ascii "freeldr.sys has many extent levels", CR, LF, NUL
msgFreeLdr:
.ascii "freeldr.sys not found", CR, LF, NUL
msgFreeLdrNotRegularFile:
.ascii "freeldr.sys is not a regular file", CR, LF, NUL
msgLoadingFreeLdr:
.ascii "Loading FreeLoader...", CR, LF, NUL
.org (EXTLDR_BOOTSECTOR_SIZE - 2)
.word HEX(AA55) // BootSector signature
.endcode16
END