Currently, we have a Stage 1
boot code that serves as the entry point of the bootloader and a Stage 2
loader that is loaded and called by Stage 1
. The Stage 1
boot code is 512 bytes
in size and is loaded by the BIOS after the POST process at address 0x7C00
. The Stage 2
loader is located at offset 1
of disk.img
and is loaded by Stage 1
.
Switch to the ISO9660 Boot Code
1️⃣ Stage1 Bootloader:
Sectors in ISO 9660 file system is of 2048 bytes (2KB). We can have our stage 1 bootloader to be 2048 or more but should be aligned with the 2048 boundary. We will pad the Stage 1 boot code to be 20KB (20480 bytes) which sits at the location 0x7C00
and ends at 0xCC00
(0xCC00 - 0x7c00 = 0x5000 [20480]).
- Starting Address = 0x7c00
- Ending Address = 0xCC00 - 1 = 0xCBFF
- Its range includes the start address and goes up to one byte before the end address,
0xCC00
is the first byte that is not part of the range.
The things done by our stage 1 bootloader:
For the time being, the solo work of the stage 1 bootloader should be,
- To load the stage 2 loader by reading the ISO 9660 File system.
However we are not doing it in this article, this article is only for porting the stage 1 boot-code to ISO 9660 and for sample we will read the ISO 9660 fs, scan the root directory of the fs to locate the sample text file named ab.txt
and print its content.
stage1.asm
:
BITS 16
ORG 0x7C00 ;
stage1_start:
jmp main
; Memory Map:
; 0x00000000 - 0x000003FF Reserved (1KB), Real Mode IVT (Interrupt Vector Table)
; 0x00000400 - 0x000004FF Reserved (256 bytes), BDA (BIOS Data Area)
; 0x00000500 - 0x00007AFF Second Stage Bootloader (~29 Kb)
; 0x00007B00 - 0x00007BFF Stack Space (256 Bytes)
; 0x00007C00 - 0x0000CBFF ISO Stage1 Bootloader (20 KiloBytes = 20,480 bytes)
; 0x0000CC00 - 0x0007FFFF 460 KB, File Loading.
; 0x00080000 - 0x0009FFFF 128 KB, Can be used by subsystems in this bootloader
; This memory will be known as the Subsystem memory area
; It can be accesses with segment:offset
; segment = 0x8000, offset = 0x00
; Thus complete address => segement*16 + offset
; 0x8000 * 16 + 0 = 0x80000
; 0x000A0000 - 0x000BFFFF 128 KB, Video Display Memory, reserved
; 0x000C0000 - 0x000C7FFF 32 KB, Video BIOS
; 0x000C8000 - 0x000EFFFF 160 KB BIOS Expansion
; 0x000F0000 - 0x000FFFFF 64 KB Motherboard BIOS
%define SUBSYSTEM_MEM_SEGEMENT 0x8000
%define SUBSYSTEM_MEM_OFFSET 0x00
;; Note: In order to access the memory above 0xFFFF, we need to use the combination of
;; segment: offset, such that by default es is set to 0x00 and with this
;; we can have offset of from 0x0000 to 0xFFFF. We can't access more than this
;; with the segment set to 0x0000, which is the limit of every segment window
;; 64 KB. Thus suppose we need to access the 0x10000 which is above the 64 KB
;; mark of the default segment: offset when segment set to 0x00.
;; In order to access the 0x10000, we can set segment (like es) to 0x1000 and
;; offset to 0x00 (like si). Thus es:si = 0x1000:0x0000 = 0x1000*16 + 0x0000
;; = 0x10000
; Includes
%include "defines.inc" ; For constants and common variables
%include "print16.inc" ; For printing functions
%include "disk.inc" ; For disk read function
%include "iso9660.inc" ; For ISO 9660 file system
main:
; Disable Interrupts, unsafe passage
cli
; Far jump to fix segment registers
jmp 0x0:FixCS
FixCS:
; Fix segment registers to 0
xor ax, ax
mov ds, ax
mov es, ax
; Set stack
; The sp register is used to point to the top of the stack. By setting sp to 0x7C00, the bootloader ensures that the stack starts at the top of the memory allocated for the bootloader. This is important because the stack grows downward in memory, so it's set up before any other code runs.
mov ss, ax
mov ax, 0x7C00 ; It ensure that there's space for the stack to grow downward without overlapping with other code or any other data in memory.
mov sp, ax
; set interrupts
sti
; Save the DL register value, which contains the disk number
mov byte [bPhysicalDriveNum], dl
call ClearScreenAndResetCursor ; Clear the screen and reset the cursor
;Print Welcome to the Screen
mov si, WelcomeToStage1 ; Load the address of the string into si register
call PrintString16BIOS ; String printing function.
call PrintNewline ; \n
;; Calculate and print the actual code size of stage1
; The actual code is without padding, from start to the right before the
; ending times statement.
mov ax, actual_code_end - stage1_start
mov si, sActualStage1SizeStatement
call PrintString16BIOS
call PrintWordNumber
call PrintNewline
;; Calculate and print the padded code size of stage1
; The padded code is with padding, from start to the very end line
; after the times statement.
mov ax, stage1_end - stage1_start
mov si, sPaddedStage1SizeStatement
call PrintString16BIOS
call PrintWordNumber
call PrintNewline
;; calculate and print the size of PVD structure, for debugging purpose only.
; It should be 2048 bytes
mov ax, PrimaryVolumeDescriptor.PVD_End - PrimaryVolumeDescriptor
call PrintWordNumber
call PrintNewline ; '\n'
; Read the ISO 9660
call Read_volume_descriptors
mov si, SUBSYSTEM_MEM_OFFSET ; Memory Address where the Root Directory Entries (Record) is read.
;; Find and load the file from the root,
;; File identifier is given in the iso9660.inc include file.
xor eax, eax
mov ax, SUBSYSTEM_MEM_SEGEMENT
mov es, ax
call Find_and_Load_File_from_Root
jmp $
; Infinite loop
; **************************
; Variables
; **************************
bPhysicalDriveNum db 0 ; Define variable to store disk number
WelcomeToStage1 db 'Welcome to the Stage1', 0 ; Define welcome message
sPassedDriveNumber db 'Passed Drive Number from Stage1 : ', 0
sActualStage1SizeStatement db 'Actual size of the stage1 code (without padding in bytes): ', 0
sPaddedStage1SizeStatement db 'Padded size of the stage1 code (with padding in bytes): ', 0
;; This is the structure which get populated when we read the Volume Descriptors from the ISO 9660 FS.
PrimaryVolumeDescriptor:
.PVD_Type db 0 ; 1 byte: Volume Descriptor Type
.PVD_StandardIdentifier db 5 dup(0) ; 5 bytes: Standard Identifier (CD001)
.PVD_Version db 0 ; 1 byte: Volume Descriptor Version
.PVD_Unused1 db 1 dup(0) ; 1 byte: Unused Field
.PVD_SystemIdentifier db 32 dup(0) ; 32 bytes: System Identifier
.PVD_VolumeIdentifier db 32 dup(0) ; 32 bytes: Volume Identifier
.PVD_Unused2 db 8 dup(0) ; 8 bytes: Unused Field
.PVD_VolumeSpaceSize dd 0 ; 4 bytes: Volume Space Size (little-endian)
.PVD_VolumeSpaceSizeBE dd 0 ; 4 bytes: Volume Space Size (big-endian)
.PVD_Unused3 db 32 dup(0) ; 32 bytes: Unused Field
.PVD_VolumeSetSize dw 0 ; 2 bytes: Volume Set Size (little-endian)
.PVD_VolumeSetSizeBE dw 0 ; 2 bytes: Volume Set Size (big-endian)
.PVD_VolumeSequenceNumber dw 0 ; 2 bytes: Volume Sequence Number (little-endian)
.PVD_VolumeSequenceNumberBE dw 0 ; 2 bytes: Volume Sequence Number (big-endian)
.PVD_LogicalBlockSize dw 0 ; 2 bytes: Logical Block Size (little-endian)
.PVD_LogicalBlockSizeBE dw 0 ; 2 bytes: Logical Block Size (big-endian)
.PVD_PathTableSize dd 0 ; 4 bytes: Path Table Size (little-endian)
.PVD_PathTableSizeBE dd 0 ; 4 bytes: Path Table Size (big-endian)
.PVD_LocTypeLPathTable dd 0 ; 4 bytes: Location of Type L Path Table (little-endian)
.PVD_LocOptionalTypeLPathTable dd 0 ; 4 bytes: Location of Optional Type L Path Table (little-endian)
.PVD_LocTypeMPathTable dd 0 ; 4 bytes: Location of Type M Path Table (big-endian)
.PVD_LocOptionalTypeMPathTable dd 0 ; 4 bytes: Location of Optional Type M Path Table (big-endian)
.PVD_DirectoryEntry db 34 dup(0) ; 34 bytes: Directory Entry for Root Directory
.PVD_VolumeSetIdentifier db 128 dup(0) ; 128 bytes: Volume Set Identifier
.PVD_PublisherIdentifier db 128 dup(0) ; 128 bytes: Publisher Identifier
.PVD_DataPreparerIdentifier db 128 dup(0) ; 128 bytes: Data Preparer Identifier
.PVD_ApplicationIdentifier db 128 dup(0) ; 128 bytes: Application Identifier
.PVD_CopyrightFileIdentifier db 37 dup(0) ; 37 bytes: Copyright File Identifier
.PVD_AbstractFileIdentifier db 37 dup(0) ; 37 bytes: Abstract File Identifier
.PVD_BibliographicFileIdentifier db 37 dup(0) ; 37 bytes: Bibliographic File Identifier
.PVD_VolumeCreationDate db 17 dup(0) ; 17 bytes: Volume Creation Date and Time
.PVD_VolumeModificationDate db 17 dup(0) ; 17 bytes: Volume Modification Date and Time
.PVD_VolumeExpirationDate db 17 dup(0) ; 17 bytes: Volume Expiration Date and Time
.PVD_VolumeEffectiveDate db 17 dup(0) ; 17 bytes: Volume Effective Date and Time
.PVD_FileStructureVersion db 0 ; 1 byte: File Structure Version
.PVD_Reserved1 db 1 dup(0) ; 1 byte: Reserved Field
.PVD_ApplicationUse db 512 dup(0) ; 512 bytes: Application Use
.PVD_Reserved2 db 653 dup(0) ; 653 bytes: Reserved for future standardization
; End of structure
.PVD_End db 0 ; End marker (not part of the ISO 9660 standard, added for structure alignment)
actual_code_end: ; After this there is padding only
times 20480 - ($ - $$) db 0 ; 20 KB padding
stage1_end: ; Ending with padding
Explanation:
- This code is loaded at origin
0x7c00
, thus all the labels get the address based on the loading origin. - Memory Map: We have changed our memory map a little for the growing needs. Since the Real mode memory available is 1 MB max. For the time being, we play only in real mode (0x0000 0000 to 0x 000F FFFF).
0x00000000 - 0x000003FF
- This slice of memory is reserved for the system.
- This slice of memory is
1024 bytes
(1 KB). - It contains the Real Mode Vector Table, which is used to handle interrupts in real mode. Each entry in the IVT is 4 bytes, and there are 256 entries, thus covering 1 KB of memory.
0x00000400 - 0x000004FF
- This slice of memory is known as BIOS Data Area.
- It is of 256 bytes in size.
- It stores information about the system's hardware configuration, such as the number of disk drives, the size of memory, and so on.
0x00000500 - 0x00007AFF
- This is the slice of memory where our second stage bootloader, would be loaded.
- It is of size around 29 KB.
- This region is allocated for the second stage bootloader, which typically performs more complex initialization and setup tasks before loading the operating system.
0x00007B00 - 0x00007BFF
- This slice of memory is for stack.
- It is of 256 bytes in size.
- Allocated for the stack, which is used for storing return addresses, local variables, and for managing function calls and interrupts.
0x00007C00 - 0x0000CBFF
- This is the region where our stage 1 boot code is loaded by the BIOS.
- It is of 20 KB (20480 bytes) in size.
- The ISO Stage 1 bootloader code resides in this memory area. It is responsible for initializing the system and loading the second stage bootloader. The start address
0x7C00
is a conventional location for bootloaders, as the BIOS loads the boot sector to this address.
0x0000CC00 - 0x0007FFFF
- This region is known as the File Loading Area.
- It is of 460 KB in size.
- This memory area is used for loading files needed during the boot process or by the bootloader.
0x00080000 - 0x0009FFFF
- This region of memory is known as the Subsystem memory area.
- It is of size 128 KB.
- Reserved for use by subsystems within the bootloader. This memory can be accessed using segment: addressing. For example, with segment
0x8000
and offset0x00
, the physical address is calculated as:0x8000x16 + 0
= 0x80000.
0x000A0000 - 0x000BFFFF
- This region of memory is known as Video Display Memory.
- It is of size 128 KB.
- It is reserved for video display memory, which is used by the graphics card to display text and images on the screen.
0x000C0000 - 0x000C7FFF
- This region of memory is known as the Video BIOS.
- It is of size 32 KB.
- It is reserved for the Video BIOS, which provides low-level routines for interfacing with the video hardware.
0x000C8000 - 0x000EFFFF
- This region of memory is known as the BIOS Expansion.
- It is of size 160 KB.
- It is reserved for BIOS expansion ROMs. These could include additional device drivers or system extensions.
0x000F0000 - 0x000FFFFF
- This is last slice of memory to make up to 1 MB.
- This region is known as the Motherboard BIOS.
- It is of size 64 KB.
- It is reserved for the motherboard BIOS, which contains the firmware necessary to initialize and test the hardware components of the system, and to load the operating system.
- After explaining the Memory Map as the comments.
- Set up the stack pointer to
0x7c00
. And it grows downwards. - Save the disk number, received in the
DL
register to variable. - Clear the screen.
- Prints the Welcome message.
- Calculate and print the actual code size (without padding), padded code size, PVD structure (should be 2048 bytes).
- Set up the stack pointer to
- Now comes the main parts of the code, which read the volume descriptors. The volume descriptors starts at LBA 16 (sector) of the ISO 9660 file system.
Read_volume_descriptors
: It will be explained separately because it is defined iniso9660.inc
include file.- But here is the short summary of it, it read the volume descriptors from the LBA 16 (0-indexed) in the
PrimaryVolumeDescriptor
structure until it find the Primary Volume Descriptor (PVD) or encounter the end of the list which is Volume Set Terminator Descriptor, it keep reading the next block. - Once We got the PVD, it validates its PVD identifier
CD001
after that read the root directory entry from the PVD and then reads the data of the root directory entry at0x8000:0x0000
and then iterate through the root directory entries printing each one's identifier until it encounter the last entry.
- But here is the short summary of it, it read the volume descriptors from the LBA 16 (0-indexed) in the
Find_and_Load_File_from_Root
: The above function reads the root directory entry at location0x8000:0x0000
. This function reads the same directory entry and scan for the file with identifierAB.TXT
, When found it loads the file at0xCC00
and at last print its content.
iso9660.inc
:
%ifndef __ISO_9660_INC__
%define __ISO_9660_INC__
%define FILE_LOADING_AREA 0xCC00 ; It is the file loading area in memory map, just after the stage 1 bootcode.
%define ROOT_DIRECTORY_ENTRY_LOCATION 0x90000 ; It is the subsystem memory area
; **************************
; ReadVolumeDescriptor:
; Reads the LBA 16 of the ISO disk and checks, if it is Primary Volume Descriptor.
; -- If it is, then Proceed further by extracting the Root Directory Entry and
; printing the root directory entries.
; -- If not the PVD, then Read the next LBA which is 17, until we have the PVD or
; we encounter the Volume set terminator descriptor (which marks the end of the descriptors)
;
; IN:
; - Nothing
; OUT:
; - Nothing
; **************************
Read_volume_descriptors:
pushad ; save the state
mov si, sReadingVolumeDescriptorsStatement
call PrintString16BIOS
call PrintNewline
.iterate_descriptor:
xor eax, eax ; clear out the eax
mov esi, eax ; clear out the esi
mov ax, 0x0000
mov es, ax ; set es to 0x0000
mov bx, PrimaryVolumeDescriptor;VOLUME_DESCRIPTOR_READ_LOCATION
mov eax, [dwVolumeDescriptorStartingSector] ; starting sector low 32 bit (0-indexed LBA)
mov esi, 0 ; starting sector high 32 bit
mov ecx, 1 ; number of sectors to read
mov edx, 2048 ; Sector sizes in bytes (1 sector = 2048 in ISO 9660)
call ReadFromDiskUsingExtendedBIOSFunction
; Verify the PVD identifier 'CD001',
; which would be at offset 1 of Volume Descriptor structure.
mov si, PrimaryVolumeDescriptor.PVD_StandardIdentifier;VOLUME_DESCRIPTOR_READ_LOCATION + 1
mov cx, 5 ; 5 characters to check
mov di, iso_id
repe cmpsb
jne .invalid_iso_disk
;; We got the valid iso disk identifier.
; Print valid iso disk statement.
push si
mov si, sValidISODiskIdentifierString
call PrintString16BIOS
call PrintNewline
pop si
;; Location where the descriptor was read, load it in al.
; Print the Volume Descriptor Type
mov si, sVolumeDescriptorTypeStatement
call PrintString16BIOS
call PrintWordNumber
call PrintNewline
mov byte al, [PrimaryVolumeDescriptor.PVD_Type];[VOLUME_DESCRIPTOR_READ_LOCATION]
; Check if the descriptor is Primary Volume Descriptor.
cmp al, 0x01 ; Check for the Primary Volume Descriptor (PVD)
jne .read_next_volume_descriptor ; If not PVD, read next descriptor
;; Got PVD
; Print the PVD statement
mov si, sGotPVDStatement
call PrintString16BIOS
call PrintNewline
;; Read the Root Directory Entry from the PVD,
;; which is at offset 156.
;; Root Directory Entry is of 34 bytes.
mov si, PrimaryVolumeDescriptor.PVD_DirectoryEntry
mov dword eax, es:[si + 2] ; Logical Block Address of the extent (first 4 bytes), which is at offset 2 of the Root Directory Entry.
xor edx, edx ; Clear out the edx
mov edx, eax ; Save LBA
;; Read the Root Directory Entry at Location 0x9000
mov ax, SUBSYSTEM_MEM_SEGEMENT ; 0x8000
mov es, ax
mov bx, SUBSYSTEM_MEM_OFFSET ; 0x0000
mov eax, edx ; starting sector low 32 bit (0-indexed LBA)
mov esi, 0 ; starting sector high 32 bit
mov ecx, 1 ; number of sectors to read
mov edx, 2048 ; Sector sizes in bytes (1 sector = 2048 in ISO 9660)
call ReadFromDiskUsingExtendedBIOSFunction
;; Read Root Directory Entry and Print its Entries Identifier.
mov si, ROOT_DIRECTORY_ENTRY_LOCATION ; 0x9000 ; Memory Address where the Root Directory Entries (Record)
; is read.
call Read_Root_Directory_Entry
jmp .done_reading_volume_descriptor ; We are done reading and printing root directory entries.
;; Read the very next volume decriptor.
.read_next_volume_descriptor:
cmp al, 0xFF ; Check for the end of volume descriptor list
; which is Volume Descriptor Set Terminator
; whose type (first byte) is 0xFF.
je .volume_descriptor_terminator
add dword [dwVolumeDescriptorStartingSector], 1 ; read next sector i.e next volume descriptor.
jmp .iterate_descriptor
;; Volume Descriptor Set Terminator
.volume_descriptor_terminator:
mov si, volume_descriptor_terminator_encountered
call PrintString16BIOS
jmp .done_reading_volume_descriptor
.invalid_iso_disk:
;; We got the Invalid ISO DISK
; Print the invalid statement and go to infinite loop
mov si, invalid_iso_disk_statement
call PrintString16BIOS
call PrintNewline
;;;TODO return the status, in case of failure as well as success.
.done_reading_volume_descriptor:
popad ; restore the state
ret ; end of Read_volume_descriptors function.
; **************************
; Reads the Root Directory Entry which should be pointed
; by `SI`. It scans all the entries of the root directory
; and prints the identifier(name) of one and all.
; IN:
; - SI Root Directory Entry
; **************************
Read_Root_Directory_Entry:
pushad ; save the state
.iterate_entry:
xor ax, ax ; Clear out the ax register
mov byte al, es:[si] ; it points to the 0 offset in directory entry.
; At Offset 0 is length of the record.
; It should be non zero, if zero means no valid record.
test al, al
jz .reading_done
;; It is valid directory record.
xor cx, cx
xor dx, dx
;mov cx, 10
mov byte cl, es:[si + 32] ; get the file identifier length.
mov byte dx, cx
call PrintWordHex
call PrintNewline
mov di, si ; di and si both contains the current directory entry.
add di, 33 ; Add 33 to di which is the starting of
; file identifier of the current directory entry.
.print_entries_identifier_char:
mov byte al, es:[di] ; read the first character of current directory
; entry's file identifier.
call PrintChar16BIOS
inc di ; jmp to next char in file identifier.
loop .print_entries_identifier_char ; loop to print next char.
call PrintNewline ; '\n'
add byte si, es:[si] ; jump to next directory entry by adding
; the length of the current directory entry.
jmp .iterate_entry
.reading_done:
popad ; restore the state
ret
; **************************
; Reads the Root Directory Entry which should be pointed
; by `SI`. It scans all the entries of the root directory
; and prints the identifier(name) of one and all.
; IN:
; - SI Root Directory Entry
; LOCAL VARIABLE:
; VAR1 = [bp - 4] = Address of the Current Directory entry.
; VAR2 = [bp - 8] = Found file LBA.
; VAR3 = [bp - 12] = Sector rounded off file size.
; **************************
Find_and_Load_File_from_Root:
pusha ; save the state
push bp ; Save old base pointer
mov bp, sp ; Set new base pointer
sub sp, 12 ; Allocate 8 bytes for local variables
; 2 local variables of size 4 bytes each
mov word [bp - 4], si ; store the address of first entry
; of Root Directory Entries.
mov ax, SUBSYSTEM_MEM_SEGEMENT
mov es, ax
; Print the function job message
; SI - is needy for the function so push the initial state into the stack
; and pop after printing the string. Thus storing the initial value.
push si
mov si, sFindAndLoadFileStatement
call PrintString16BIOS
call PrintNewline
pop si
.iterate_entry:
xor ax, ax ; Clear out the ax register
mov byte al, es:[si] ; it points to the 0 offset in directory entry.
; At Offset 0 is length of the record.
; It should be non zero, if zero means no valid record.
test al, al
jz .didnt_find ; Reached the end of the directory entry list
; and didnt find the file
;; It is valid directory record.
; Get the File Flags field (offset 25 from the start of the entry).
mov byte al, es:[si + 25]
; Check if it's a directory
test al, 0x02 ; Check if bit 1 is set
jnz .is_directory
;; It is a file
;; Check it's identifier.
xor cx, cx ; clear out cx
xor dx, dx ; clear out dx
mov byte cl, es:[si + 32] ; get the file identifier length,
; which is at offset 32 of directory_entry_record.
mov byte dx, cx ; Store the identifier length
; into the DX.
;; Check the file identifier
mov di, si ; DI and SI points to the Directory Entry Record
add di, 33 ; add offset 33, which is the start of the file identifier
; DI, now points to the file identifier beginning.
mov cx, 6 ; The length of the file identifier.
lea si, [file_identifier] ; Load SI to the identifier of the required file.
repe cmpsb ; Keep checking the SI and DI for the match.
; if the length of the file identifier in both DI and SI matches,
; then we have the required file, else jump to the next
; directory entry record.
jne .onto_next ; Jump to the next directory record if file identifier of
; particular size mismatch
;; Found the file
mov si, [bp-4] ; Store the current Directory Entry Record in the Local variable.
.got_file:
;; got the file
; Print the message claiming that we found the file, you searched for.
push si
mov si, sFoundTheFile
call PrintString16BIOS
call PrintNewline
pop si
;; Location of the extent of the File
; i.e the LBA of the data of the file.
mov eax, es:[si + 2] ; Logical Block Address of the extent (first 4 bytes)
;; Save LBA, in local variable
;; Store File LBA in local variable BP - 8
mov dword [bp - 8], eax ; Store the File LBA
; to second local variable.
; Read the file at the FILE_LOADING AREA which is
; 0x0500
; Save File Data Length in local variable
xor eax, eax
mov byte ax, es:[si+ 10] ; Data length of the file in LSB
; It is present at offset 10 of Directory Entry Record.
; It is of 4 byte in size
; and data length is in bytes.
mov dword [bp - 12], eax
;; Prints the Data length
push si
mov si, sLengthOfFile
call PrintString16BIOS
call PrintWordNumber
call PrintNewline
pop si
mov ax, 0x0000
mov es, ax ; Zero the extra segment.
mov bx, FILE_LOADING_AREA ; 0xB000
;; Roundoff the size to the 2048 which is the sector size.
; i.e if the data length is less than 2048, then sector to load should be
; 1 which is 2048 bytes
; If the data length is more than 2048, 2049 for instance, one more than 2048
; then the sector to load will be 2 = (2048*2)bytes.
; Now eax, consists the rounded off value to sector size
mov eax, [bp - 12] ; Actual data length of the file.
add eax, 2047
mov ecx, 2048
div ecx ; division also affect the edx register.
; so be careful, if you have stored the necessary
; information in edx, it must have got wiped out.
; move the sector count to ecx.
mov ecx, eax
mov eax, [bp - 8] ; starting sector low 32 bit (0-indexed LBA)
; fetch from the second local variable.
mov esi, 0 ; starting sector high 32 bit
;mov ecx, 1 ; Here you can explicitly specify the sector count,
; Otherwise we already calculated the rounded off
; sector count to sector size based on the data length.
mov edx, 2048 ; Sector sizes in bytes (1 sector = 2048 in ISO 9660)
call ReadFromDiskUsingExtendedBIOSFunction
;jmp FILE_LOADING_AREA
;; Tell user about displaying the content of the file.
mov si, sPrintingTheFileContent
call PrintString16BIOS
call PrintNewline
;; Print the contents of the file.
mov si, FILE_LOADING_AREA ; 0xB000
; Prints the data from the
; file loaded area.
call PrintString16BIOS
call PrintNewline
; We are done here reading the contents of the file.
jmp .reading_done
.is_directory:
; handle the directory, for the time being, just jump to next entry.
; Maybe you can recursively search for the file in every directory.
; Or you can search for particular directory.
;; Update the si to point to next entry.
.onto_next:
; load the base address of current entry back to si
mov word si, [bp - 4]
add byte si, es:[si] ; increment si to point to next entry
; Increment by adding the size of current entry.
; at offset 0 of directory entry, we have the size
; of each directory entry. we can get to the second
; entry by adding the size of current entry to the
; current entry address.
mov word [bp - 4], si ; update the next entry address in the local
; variable as well.
jmp .iterate_entry ; onto the next entry.
;; Sorry but we didnt find your file.
.didnt_find:
mov si, sDidntFindTheFile
call PrintString16BIOS
call PrintNewline
.reading_done:
add sp, 8 ; Clear local variables
mov sp, bp ; Reset Stack pointer
pop bp ; Restore old base pointer
popa ; restore the state
;; End of the Find_and_Load_File_from_Root function
ret
;; Data for the Find_and_Load_File_from_Root function
sLengthOfFileIdentifierStatement: db 'Length of the File Identifier is (in bytes): ', 0
sLengthOfFile: db 'Length of the File (in bytes): ', 0
sUnpaddedSizeStatement: db 'Un-Padded Size of Bootloader (bytes): ', 0
sFindAndLoadFileStatement: db 'Find and Load the file.', 0
sFoundTheFile: db 'Found the file.', 0
sPrintingTheFileContent: db 'Printing the content of the file.', 0
sDidntFindTheFile: db 'Didnt find the file.', 0
file_identifier: db 'AB.TXT', 0 ;'AB.TXT', 0
dwVolumeDescriptorStartingSector dd 16 ;; starting sector where the volume descriptor resides.
invalid_iso_disk_statement: db 'Invalid ISO disk identifier.', 0
volume_descriptor_terminator_encountered: db 'Volume Descriptor Set Terminator Encountered.', 0
sReadingVolumeDescriptorsStatement: db 'Reading Volume Descriptors...', 0
sValidISODiskIdentifierString: db 'It`s valid ISO Disk Identifier.', 0
sVolumeDescriptorTypeStatement: db 'Volume Descriptor Type = ', 0
sGotPVDStatement: db 'We got the PVD.', 0
;sItsDirectoryStatement: db 'Its a directory.', 0
iso_id db 'CD001'
%endif
2️⃣ Makefile
:
1 Create the Build Directory:
mkdir -p build
2 Assemble stage1.asm:
nasm -f bin -o build/stage1.bin
3 Create a sample ab.txt
file with some content in it:
Hi, congratulations....
The JAT
4 Create ISO image:
mkdir -p iso_dir
cp build/stage.bin iso_dir/
cp ab.txt iso_dir/
xorriso -as mkisofs -R -J -b stage1.bin -no-emul-boot -boot-load-size 4 -o image.iso iso_dir
5 run:
qemu-system-x86_64 -cdrom image.iso