Getting things setup#

First let’s create a project.

cargo new hv-rs

It will contain a workspace with two members, our bootloader and the hypervisor “kernel” itself. Put the following in cargo.toml.

[workspace]
members = ["hv", "hv_uefi"]
resolver = "3"

Now let’s create those projects.

cargo new hv_uefi
cargo new hv

Now change the directory to hv, and let’s setup making the target of our hypervisor. If you didn’t know, the Rust compiler has a set of prefab “targets” you can compile to, for example, Redox OS, Linux, Windows using MSVC or tiny microcontrollers. And we can create our own! We do this with a simple .json file. Create a .json file named “hv_x86_64.json” and let’s populate it some simple stuff.

{
  "llvm-target": "x86_64-unknown-none",
  "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
  "arch": "x86_64",
  "target-endian": "little",
  "target-pointer-width": "64",
  "target-c-int-width": 32,
  "os": "none",
  "executables": true,
  "linker-flavor": "ld.lld",
  "linker": "rust-lld",
  "panic-strategy": "abort",
  "disable-redzone": true,
  "features": "-mmx,-sse,+soft-float",
  "rustc-abi": "x86-softfloat",
  "code-model": "kernel",
  "exe-suffix": ".bin",

  "pre-link-args": {
    "ld.lld": [
      "-Thv/link.ld"
    ]
  }
}

This provides our binary with the linker script, sets the file extension, target endian, a code model designed for our kernel needs, and also some other things that speed up the performance of our “kernel” such as emulating floating point operations without SIMD, for more information, see here. Also, by default, in the rust target “x86_64-unknown-none” follows the SysV ABI (when using “extern “C”) which has the same calling covention as Linux, as in function parameters are called through registers rdi, rsi, rdx, rcx, r8, and r9 etc.

Next, let’s create that linker script. In link.ld:

ENTRY(hventry)

PHDRS {
    text PT_LOAD;
    rodata PT_LOAD FLAGS(4);
    data PT_LOAD;
}

SECTIONS {

    . = 0xFFFFFFFF80000000; 

    .text :  {
        *(.text .text.*)
        . = ALIGN(4K);
    } : text

    .bss  : {
        *(.bss .bss.*) 
        . = ALIGN(4K);
    } : data

    .data : {
        *(.data .data.*) 
        . = ALIGN(4K);
    } : data

    .got : {
        *(.got)
        . = ALIGN(4K);
    } : rodata

    .rodata : {
        *(.rodata .rodata.*)
        . = ALIGN(4K);
    } : rodata


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

Nothing special, simply page aligning each section and setting the kernel into the upper half of memory, that memory address is the virtual address we will be loading our kernel into. For more information on linker scripts, this blog post is great.

Now let’s make a new directory “.cargo” and put some things in there so we can build the core library.

In that directory make “config.toml” and put this in there.

[unstable]
build-std-features = ["compiler-builtins-mem"]
build-std = ["core", "compiler_builtins"]

[build]
target = "hv_x86_64.json"

and finally, let’s make a build script. In the src directory, make a build.rs with the following contents:

fn main() {
    println!("cargo:rerun-if-changed=link.ld");
}

Next let’s just add some simple things so it can boot. in src/main.rs:

#![no_std]
#![no_main]

#[panic_handler]
fn panic_handler(_info: &core::panic::PanicInfo) -> ! {
    loop {};
}


#[unsafe(no_mangle)]
pub extern "C" fn hventry() {
    loop {};
}

That’s it for now. Next we are going to set up our bootloader.