mirror of
https://github.com/reactos/reactos.git
synced 2025-08-05 07:22:58 +00:00

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.
583 lines
14 KiB
ArmAsm
583 lines
14 KiB
ArmAsm
/*
|
|
* 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
|