rust-osdev/bootloader

repository·main·Indexed 23 days ago

https://github.com/rust-osdev/bootloader

An experimental x86_64 bootloader written in Rust that supports both BIOS and UEFI systems. It provides a mechanism to turn a #![no_std] kernel into a bootable disk image using the bootloader_api crate for configuration and entry point registration. The project includes tools for managing virtual memory mappings, kernel stack allocation, and framebuffer output.

Tokens
10.2K
Snippets
17
Records
60
Agent score
76%

What's inside rust-osdev-bootloader

  1. Set up APIC for UEFI booting

    main

    If you are using UEFI booting in v0.11, you cannot use the legacy PIC for interrupt handling and must set up the APIC instead.

    Basic workflow:

    1. Locate the RSDP (ACPI structure) using the physical address provided in boot_info.rsdp_addr.
    2. Use the acpi crate's AcpiTables::from_rsdp function to load ACPI tables.
    3. Use the platform_info() method on the tables and read the interrupt_model field of the returned PlatformInfo to determine the system's interrupt controller requirements.
    4. Parse and set up the local and IO APIC.
  2. How the bootloader architecture works

    main

    The bootloader project is composed of three main parts that work together to boot a kernel:

    1. bootloader_api library: A library included by the kernel. It provides the entry_point! macro, which encodes configuration and metadata into a special ELF section of the kernel binary.
    2. BIOS and UEFI binaries: The actual bootloader implementations. They share a common library and are responsible for loading the kernel from a FAT partition at runtime. They read the configuration from the special ELF section created by the bootloader_api.
    3. bootloader library: The top-level crate used by the user (typically in a build.rs script). It builds the BIOS/UEFI implementations and provides functions like UefiBoot and BiosBoot to create FAT-formatted bootable disk images containing the kernel.
  3. Create bootable disk images in v0.11

    main

    The bootloader v0.11 release has replaced the bootimage tool. Instead, the bootloader crate provides functions to create bootable disk images directly from your kernel.

    Workflow:

    1. Build your kernel using cargo build --target x86_64-unknown-none.
    2. Invoke a builder function (in your own build script or tool) that calls the disk image creation functions provided by the bootloader crate.
  4. Build the First Stage: Bootsector

    main

    The first stage bootsector must fit within a 512-byte limit. To build it, use cargo build with the specific stage-1 profile, enabling core via build-std, and targeting the i386-code16-boot-sector.json target. After building the ELF, use objcopy to convert it to a raw binary and write it to the disk image.

    Note: This process requires a custom target JSON file located at ../../i386-code16-boot-sector.json.

  5. Build your kernel for x86_64-unknown-none

    main

    When using v0.11, you should build your kernel using the x86_64-unknown-none target. This is a Tier-2 target, so you no longer need bootimage, cargo-xbuild, or xargo. You can use rustup to manage the target.

    1. Add the target: rustup target add x86_64-unknown-none

    2. Build the kernel: cargo build --target x86_64-unknown-none

    rustup target add x86_64-unknown-none
    cargo build --target x86_64-unknown-none
  6. Create a bootable disk image using a workspace

    main

    To combine your kernel with bootloader and create a bootable disk image, set up a workspace structure as follows:

    1. Structure: Move your kernel code into a kernel subdirectory and create a new os crate at the top level using cargo init --bin.
    2. Workspace Configuration: Define a workspace in the top-level Cargo.toml and add your kernel as a member.
    3. Artifact Dependency: Use an unstable artifact dependency to add the kernel crate as a build-dependency in the os crate's Cargo.toml.
    4. Enable Unstable Features:
      • In .cargo/config.toml, enable bindeps = true under [unstable].
      • In rust-toolchain.toml, ensure you are using the nightly channel and have the necessary targets installed.
    5. Build Script: Create a build.rs in the os crate. Use bootloader::UefiBoot and/or bootloader::BiosBoot to generate the disk image. You can retrieve the kernel path via the environment variable CARGO_BIN_FILE_MY_KERNEL_my-kernel.
    # in Cargo.toml (os crate)
    [build-dependencies]
    kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" }
    # in .cargo/config.toml
    [unstable]
    bindeps = true
    # in rust-toolchain.toml
    [toolchain]
    channel = "nightly"
    targets = ["x86_64-unknown-none", "x86_64-unknown-uefi"]
  7. Configure bootloader options in v0.11 using BootloaderConfig

    main

    In bootloader v0.11, optional features (like map-physical-memory) are no longer enabled via Cargo features. Instead, you configure them using a BootloaderConfig instance passed to the entry_point! macro.

    To use a custom configuration, define a static BOOTLOADER_CONFIG and pass it as a config argument to the entry_point! macro.

    use bootloader_api::config::{BootloaderConfig, Mapping};
    
    pub static BOOTLOADER_CONFIG: BootloaderConfig = {
        let mut config = BootloaderConfig::new_default();
        config.mappings.physical_memory = Some(Mapping::Dynamic);
        config
    };
    
    // add a `config` argument to the `entry_point` macro call
    entry_point!(kernel_main, config = &BOOTLOADER_CONFIG);
  8. Configure workspace requirements for disk image creation

    main

    To implement the automated disk image creation workflow, your project must meet the following configuration requirements:

    • Cargo.toml: Must include the kernel as a build-dependency and include bootloader as a build-dependency. If using UEFI booting in QEMU, include ovmf-prebuilt.
    • .cargo/config.toml: Must enable the unstable artifact-dependencies feature.
    • rust-toolchain.toml: Must set the default toolchain to nightly to support experimental features like artifact dependencies.
    • build.rs: Should contain the logic to call bootloader functions to create both BIOS and UEFI disk images.
    • src/main.rs: Should contain the logic to launch the resulting image using QEMU (e.g., via cargo run bios or cargo run uefi).
  9. Build the x86_64 UEFI bootloader

    main
    To build the UEFI bootloader for the x86_64 architecture, use cargo build with the specific target and unstable flags required to build the standard library (core) for the target platform. This requires a nightly Rust toolchain due to the -Z flags.
  10. Inspect Bootsector size and contents with objdump

    main

    Because the bootsector is strictly limited to 512 bytes, you can use objdump to inspect the disassembled ELF file to identify large sections and attempt to reduce the size.

    objdump -xsdS -M i8086,intel ../../target/i386-code16-boot-sector/stage-1/bootloader-x86_64-bios-boot-sector
  11. Chainload your OS using GNU GRUB

    main

    Chainloading allows you to use a bootloader like GNU GRUB to select and launch your OS binary. To set this up, you must create a GRUB configuration file and build an ISO image that GRUB can use to boot your kernel.

    1. Create the GRUB configuration

    Create a file at iso/boot/grub/grub.cfg in your OS source tree with the following content:

    menuentry "myOS" {
    	chainloader (hd1)+1
    }

    Note: The (hd1)+1 syntax tells GRUB that your binary is located on the first partition of the second disk (hd1). If booting on real hardware, you may need to adjust this disk/partition identifier. Alternatively, you can create a partition on the same ISO that GRUB creates and copy your binary there.

    2. Build the ISO

    Use grub-mkrescue to generate the bootable ISO image from your iso directory:

    grub-mkrescue -o grub.iso iso

    3. Test with QEMU

    Run QEMU, providing the GRUB ISO as the hard disk (-hda) and your OS boot image as the second hard disk (-hdb). Replace my_os with your actual target name:

    qemu-system-x86_64 -hda grub.iso -hdb target/x86_64-my_os/debug/bootimage-my_os.bin