4
votes

I'm working on making my own boot loader and kernel in D, and I've come across a stumbling block.

Background:

  • I'm writing everything from scratch. So the boot sector is in assembly. And I'm not using GRUB.
  • I'm using Qemu for testing.
  • The boot sector reads the kernel from the "disk" (which is currently just a flat binary file, whose first sector is the boot loader and the rest of which is the kernel code) into virtual address 0xC0000000, and calls kmain(), the entrypoint of my kernel.
  • I'm using the PE file format for my kernel. (Please don't tell me to use Elf -- my choice is PE.)

The Problem

It's part of the kernel's job to be able to load PE files. So how do I load the kernel itself into memory in the first place, so that it can actually execute correctly?

I can't do this from the boot sector because (1) it doesn't fit in 512 bytes, and (2) it's painful to do in assembly. Obviously, I can't do it in the kernel itself either. So how should I do this?

1
You've pretty much answered your own question. Loading a complex file format like PE (or Elf) requires a fair bit of infrastructure. So the kernel shouldn't use it. The Windows kernel doesn't come in .exe format either. It should probably be plain machine code which can be loaded directly into memory and executed - jalf
@jalf: The Windows kernel (ntoskrnl.exe) is a valid PE file... isn't it? - user541686
Doubleclick on it, and see what happens then. :) - jalf
@jalf: Uhm... have you ever double-clicked a DLL? Are those PE files? - user541686
But yeah, I stand corrected. Nevertheless, my point remains. The point of any executable file format (whether PE or Elf or something else) is to provide general purpose executables to be loaded and executed by the OS. For your kernel, there's little need for the infrastructure provided by such file formats. All you need is the ability to load a chunk of exectuable code into memory, and start executing it. Microsoft chose to package it as a PE file, apparently, but unless you're writing the next Windows, that decision, and that constraint, does not have to apply to you - jalf

1 Answers

10
votes

How does GRUB do it?

In GRUB, the 512 byte boot sector does not load the kernel. Instead, it loads the rest of the bootloader, which is much larger than 512 bytes. It is this second-stage bootloader that loads the kernel. You will have to do something similar.

The code for loading this second stage can be much simpler than the code for loading the full kernel - it basically loads a few sectors directly into a fixed memory address (in low memory - it is still in real mode by this point) and jumps to a fixed memory address.

This second stage can be mostly written in C. You only need a bit of setup in assembly (entering protected mode, setting up the stack, and few other bits of low-level processor stuff) before jumping to a C function to do the rest of the setup.

The Linux kernel did something like this in the past. You could copy the raw kernel directly into a floppy disk. Its first 512 bytes were a floppy disk boot sector, which loaded the next few sectors (still in real mode) into a fixed address in low memory. These next few sectors had the code (still in assembly) to load the rest of the kernel into a fixed memory address, and jump to its real entry point. Nowadays, IIRC most of this code was ripped out and the Linux kernel now depends on external bootloaders.