CLOSE

Read, load and call the binary kernel from the iso 9660 filesystem.

In the last article, we would read, load and call the binary kernel (kernel_entry) from the ISO9660 filesystem using ATAPI interface.

Step-by-Step Process

1 Read the PVD of the ISO9660 File System

Like as always we start reading the disk from the block 16 (LBA). This is the starting location of the volume descriptors. We read at LBA 16 check its type. If it is Primary Volume Descriptor, then we are done for this step, otherwise read right next LBA block i.e., 17. We do so until we encounter the Volume Descriptor Set Terminator which is type 0xFF. It indicates the end of volume descriptor table and which means we didn't find the Primary Volume Descriptor.

// This one is global variable, and always points to where the 
// PVD is read.
iso_9660_volume_descriptor_t * root = (iso_9660_volume_descriptor_t *)((uint8_t *)0x20000);


for (int i = 0x10; i < 0x15; ++i) {
		ata_device_read_sector_atapi(device, i, (uint8_t *)root);
		switch (root->type) {
			case 1:
				root_sector = i;
				goto done;
			case 0xFF:
				return;
		}
	}
	return;
done:

2 Parse the PVD to get the Root Directory Entries Record

At offset 156 in the Primary Volume Descriptor, we have the Root Entry Directory Record(points to entries of the root directory), which would be of size 34 bytes. So we need to parse the PVD to extract Root Entry Directory from it.

We will copy this root directory entry at some other location so that we have track of it, through a pointer.

// Structure of the Directory Entry Record


typedef struct {
    uint8_t length;                    // Length of the directory entry
    uint8_t ext_length;                // Extended attribute record length

    uint32_t extent_start_LSB;         // Logical block number of the start of the extent (LSB first)
    uint32_t extent_start_MSB;         // Logical block number of the start of the extent (MSB first)

    uint32_t extent_length_LSB;        // Length of the extent in logical blocks (LSB first)
    uint32_t extent_length_MSB;        // Length of the extent in logical blocks (MSB first)

    iso_9660_rec_date_t record_date;   // Recording date and time

    uint8_t flags;                     // File flags
    uint8_t interleave_units;          // File unit size for interleaved files
    uint8_t interleave_gap;            // Interleave gap size

    uint16_t volume_seq_LSB;           // Volume sequence number (LSB first)
    uint16_t volume_seq_MSB;           // Volume sequence number (MSB first)

    uint8_t name_len;                  // Length of the file identifier
    char name[];                       // File identifier (variable length)
} __attribute__((packed)) iso_9660_directory_entry_t;

We will copy the Root Directory Entry from PVD to the memory location 0x20800 which is at offset 2048 from the PVD which is read at location 0x20000.

// This one is global variable, which will always points to the location
// where root directory entry record is read.
iso_9660_directory_entry_t * dir_entry = (iso_9660_directory_entry_t *)((uint8_t *)0x20800);


memcpyb(dir_entry, (iso_9660_directory_entry_t *)&root->root, sizeof(iso_9660_directory_entry_t));

memcpyb: copies the root directory from PVD to memory location pointed by dir_entry which is 0x20800

3 Scan the Root Directory Entries for the Kernel

In the previous step, we got the Root Directory Entry Record. Now we can traverse the root directory entries to look for the kernel in the root directory. We only search in the root directory not in sub directories.

  • ISO9660 have three ISO levels from 1 to 3.
  • These levels differs by file identifier length.
  • By default xorriso have ISO level 1, which only support the file identifier length of 11. 8 character for the file name and 3 for the extension, separated by the dot (.).
  • If your file identifier during compilation is kernel_entry.bin, then in the ISO 9660 file system with ISO level 1, the file would be KERNEL_E.BIN.

Once we got the directory entry of KERNEL_E.BIN, we read it to dir_entry which points to location 0x20800.

char* name = "KERNEL_E.BIN";
unsigned int extent_start = dir_entry->extent_start_LSB;
unsigned int extent_length = dir_entry->extent_length_LSB;


// It compares two string,
// It returns,
// 0 = false, If both are equal
int strcmp(const char * l, const char * r) {
	for (; *l == *r && *l; l++, r++);
	return *(unsigned char *)l - *(unsigned char *)r;
}

for(int i = 0; i< extent_length; i += 2048){
   ata_device_read_sector_atapi(device, extent_start + i/2048, dir_entries + i);
}

long offset = 0;
	while (1) {
		iso_9660_directory_entry_t * dir = (iso_9660_directory_entry_t *)(dir_entries);
		if (dir->length == 0) {
			break;
		}
		if (!(dir->flags & FLAG_HIDDEN)) {
			char file_name[dir->name_len + 1];
			memcpyb(file_name, dir->name, dir->name_len);
			file_name[dir->name_len] = 0;
			char * s = strchr(file_name,';');
			if (s) {
				*s = '\0';
			}

			boot_print("Found a file: ");
			boot_print(" Name: ");
			boot_print(file_name); boot_print("\n");

			if (!strcmp(file_name, name)) {
			// We got the kernel
				// Read the kernel at location
				memcpyb(dir_entry, dir, sizeof(iso_9660_directory_entry_t));
				
			}
		}
		offset += dir->length;
	}

Note: For the time being, we expect our kernel is in root directory. Not in sub directory.

4 On Finding the Kernel, Load it to the Location 0x300000

Now at this point dir_entry is pointing to the directory entry of the found kernel. Now we parse the directory entry of the kernel and load it at location 0x300000.

#define KERNEL_LOAD_START 0x300000

unsigned int offset = 0;

for(int i = dir_entry->extent_start_LSB; i < dir_entry->extent_start_LSB + dir_entry->extent_length_LSB/2048 + 1; ++i, offset += 2048) {
    ata_device_read_sector_atapi(device, i, (uint8_t *)KERNEL_LOAD_START + offset);
}

5 Call the Loaded Kernel

At this stage our kernel would be loaded at location 0x300000, and since it is binary kernel we don't need to parse the kernel, we can just call it by jumping to this location. That's the beauty of the binary file.

We can call the kernel either in assembly or C.

// In assembly

jmp 0x300000
// In C

   uint32_t kernel_address = 0x300000;
   void (*kernel_entry)(void) = (void(*)(void))0x300000;
   kernel_entry();

Output:

image-209.png