Skip to content

Latest commit

 

History

History
87 lines (66 loc) · 3.43 KB

create-disk-image.md

File metadata and controls

87 lines (66 loc) · 3.43 KB

Template: Create a Disk Image

The bootloader crate provides simple functions to create bootable disk images from a kernel. The basic idea is to build your kernel first and then invoke a builder function that calls the disk image creation functions of the bootloader crate.

A good way to implement this is to move your kernel into a kernel subdirectory. Then you can create a new os crate at the top level that defines a workspace. The root package has build-dependencies on the kernel artifact and on the bootloader crate. This allows you to create the bootable disk image in a cargo build script and launch the created image in QEMU in the main function.

The files could look like this:

# .cargo/config.toml

[unstable]
# enable the unstable artifact-dependencies feature, see
# https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies
bindeps = true
# Cargo.toml

[package]
name = "os"         # or any other name
version = "0.1.0"

[build-dependencies]
bootloader = "0.11"
test-kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" }

[dependencies]
# used for UEFI booting in QEMU
ovmf-prebuilt = "0.1.0-alpha.1"

[workspace]
members = ["kernel"]
// build.rs

use std::path::PathBuf;

fn main() {
    // set by cargo, build scripts should use this directory for output files
    let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
    // set by cargo's artifact dependency feature, see
    // https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies
    let kernel = PathBuf::from(std::env::var_os("CARGO_BIN_FILE_KERNEL_kernel").unwrap());

    // create an UEFI disk image (optional)
    let uefi_path = out_dir.join("uefi.img");
    bootloader::UefiBoot::new(&kernel).create_disk_image(&uefi_path).unwrap();

    // create a BIOS disk image
    let bios_path = out_dir.join("bios.img");
    bootloader::BiosBoot::new(&kernel).create_disk_image(&bios_path).unwrap();

    // pass the disk image paths as env variables to the `main.rs`
    println!("cargo:rustc-env=UEFI_PATH={}", uefi_path.display());
    println!("cargo:rustc-env=BIOS_PATH={}", bios_path.display());
}
// src/main.rs

fn main() {
    // read env variables that were set in build script
    let uefi_path = env!("UEFI_PATH");
    let bios_path = env!("BIOS_PATH");
    
    // choose whether to start the UEFI or BIOS image
    let uefi = true;

    let mut cmd = std::process::Command::new("qemu-system-x86_64");
    if uefi {
        cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi());
        cmd.arg("-drive").arg(format!("format=raw,file={uefi_path}"));
    } else {
        cmd.arg("-drive").arg(format!("format=raw,file={bios_path}"));
    }
    let mut child = cmd.spawn().unwrap();
    child.wait().unwrap();
}

Now you should be able to use cargo build to create a bootable disk image and cargo run to run in QEMU. Your kernel is automatically recompiled when it changes. For more advanced usage, you can add command-line arguments to your main.rs to e.g. pass additional arguments to QEMU or to copy the disk images to some path to make it easier to find them (e.g. for copying them to an thumb drive).