Creating a 64-bit kernel

From OSDev.wiki
Revision as of 16:55, 5 July 2009 by Zenith (talk | contribs)
Jump to navigation Jump to search
Difficulty level

Advanced
This page is a work in progress.
This page may thus be incomplete. Its content may be changed in the near future.
Kernel Designs
Models
Other Concepts

Prerequisites

Make sure that you have the following done before proceeding:

  • Do you have an x86-64 cross-compiler?. Preferably use GCC 4.3 or newer to avoid having to patch your compiler.
  • Read up on long mode and how to initialize/use it
  • Know what a higher-half kernel is and how it works
  • Decide now on how to load your kernel - your own bootloader, GRUB (with separate loader executable), or GRUB2 (elf64 + 32-bit bootstrap code)

The Main Kernel

The kernel should run in a uniform environment. Let's make this simple for now...

kmain.c

void kmain(void) {
    /* What goes here is up to you */
 }

Compiling

Compile each source file like any piece of C code, just remember to use the cross-compiler and the proper options. Linking will be done later...

x86_64-pc-elf-gcc -ffreestanding -mcmodel=large -nostdlib <other options> -c -o <object file> <source file>

The -mcmodel=large argument enables us to run the kernel at any 64-bit virtual memory address we want (only in GCC 4.3+), and -nostdlib makes sure that GCC doesn't add any unneeded crap to our kernel.

Linking

The kernel will be linked as an elf64-x86-64 executable, to run at a virtual higher-half address. Let's use a linker script...

link.ld

OUTPUT_FORMAT(elf64-x86-64)
ENTRY(kmain)
SECTIONS
{
    . = KERNEL_VMA;

    .text : AT(ADDR(.text) - KERNEL_VMA)
    {
        _code = .;
        *(.text)
        *(.rodata*)
        . = ALIGN(4096);
    }

   .data : AT(ADDR(.data) - KERNEL_VMA)
   {
        _data = .;
        *(.data)
        . = ALIGN(4096);
   }

   .ehframe : AT(ADDR(.ehframe) - KERNEL_VMA)
   {
       _ehframe = .;
       *(.ehframe)
        . = ALIGN(4096);
   }

   .bss : AT(ADDR(.bss) - KERNEL_VMA)
   {
       _bss = .;
       *(.bss)
       . = ALIGN(4096);
   }

   _end = .;

   /DISCARD/ :
   {
        *(.comment)
   }
}

Feel free to edit this linker script to suit your needs. Set ENTRY(...) to your entry function, and KERNEL_VMA to your base virtual address.

Now link with the following:

x86_64-pc-elf-ld -nostdlib -nodefaultlibs <other options> -T <linker script> -o <kernel executable> <all object files>

Congratulations! Your kernel has been compiled!

Loading

Before you can actually use your kernel, you need to deal with the hard job of loading it. Here are your three options:

With your own boot loader

This method is the simplest (since you write all the code), though it requires the most work.

I won't give any code, but the basic outline is:

  • Set up a stable environment
  • Do Protected Mode readying stuff (GDT, IDT, A20 gate, etc.)
  • Enter Protected Mode (or skip this step and enter long mode directly)
  • Parse kernel ELF headers (if kernel is separate from executable)
  • Set up Long Mode readying stuff (PAE, PML4, etc.) - Remember to set up the higher-half addressing!
  • Enter Long Mode by far jump to the kernel entry point in (virtual) memory

With a separate loader

This requires the use of GRUB or another multiboot1-compliant loader. This may be the most error free of the three.

A quick rundown:

  • Set up a stable environment
  • Read the multiboot information struct to see where GRUB loaded your kernel (look at the module section)
  • Parse kernel ELF headers
  • Set up Long Mode readying stuff (PAE, PML4, etc.) - Remember to set up the higher-half addressing!
  • Enter Long Mode by far jump to the kernel entry point

Note that this code has to be compiled as elf32 and must contain the multiboot1-header. Either compile with

i*86-elf-gcc

or

x86_64-pc-elf-gcc -m32

Also remember to set the text section to start at 0x100000 (-Ttext 0x100000) when linking your loader.

Set up GRUB to boot your loader as a kernel in its own right, and your actual kernel as a module. Something like this in menu.lst:

title My Kernel
kernel --type=multiboot <loader executable>
module <kernel executable>

With a 32-bit bootstrap in your kernel

This requires the use of any ELF64-compatible loader that loads into protected-mode (GRUB2, or patched GRUB Legacy). This may be the simplest in the long run, but is hell to set up (well, it was for me - but I saved you some work ;).

Note that GRUB2 is still in beta and unreliably implements the multiboot draft or the original multiboot specification. Highly recommended that you use the latest CVS version.

First, create an assembly file like the following, which will set up virtual addressing and long mode:

bootstrap.S

.section .text
.code32

multiboot_header:
    (only needed if you're using multiboot)

bootstrap:
    (32-bit to 64-bit code goes here)
    (jump to 64-bit code)

Then, add the following to your original linker file:

link.ld

...
ENTRY(bootstrap)
...
SECTIONS
{
    . = KERNEL_LMA;

    .bootstrap :
    {
        <path of bootstrap object> (.text)
    }

    . += KERNEL_VMA;

    .text : AT(ADDR(.text) - KERNEL_VMA)
    {
        _code = .;
        *(EXCLUDE_FILE(*<path of bootstrap object>) .text)
        *(.rodata*)
        . = ALIGN(4096);
    }
...

The above edits allow the linker to link the bootstrap code with physical addressing, as virtual addressing is set up by the bootstrap. Note that in this case, KERNEL_VMA will be equivalent to 0x0, meaning that text would have a virtual address at KERNEL_LMA + KERNEL_VMA instead of just at KERNEL_VMA. Change '+=' to '=' and your bootstrap code if you do not want this behaviour.

Compile and link as usual, just remember to compile the bootstrap code as well!

Set up GRUB2 to boot your kernel (depends on your bootloader) with grub.cfg:

menuentry "My Kernel" {
    multiboot <kernel executable>
}

Possible Problems

You may experience some problems. Fix them immediately or risk spending a lot of time debugging later...

My kernel is way too big!!!

Try each of the following, in order:

  • Make sure you're compiling with the -nostdlib -nodefaultlibs options
  • You can try changing the OUTPUT_FORMAT to elf64-little
  • Try cross-compiling the latest version of binutils and gcc

GRUB2 is teh fail

The current version of GRUB2 sets the start tag's key to 1, bootloader tag's key to 2, etc. The multiboot spec however, defines the start tag's key to 0, bootloader tag's key to 1, etc. The current CVS does not have this incorrect behaviour fixed.

See Also

Articles

Threads