1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/*!
An experimental x86_64 bootloader that works on both BIOS and UEFI systems.

To use this crate, specify it as a dependency in the `Cargo.toml` of your operating system
kernel. Then you can use the [`entry_point`] macro to mark your entry point function. This
gives you access to the [`BootInfo`] struct, which is passed by the bootloader.

## Disk Image Creation

Including the `bootloader` crate as a dependency makes the kernel binary suitable for booting,
but does not create any bootable disk images. To create them, two additional steps are needed:

1. **Locate the source code of the `bootloader` dependency** on your local system. By using the
   dependency source code directly, we ensure that the kernel and bootloader use the same version
   of the [`BootInfo`] struct.
    - When creating a builder binary written in Rust, the
      [`bootloader_locator`](https://docs.rs/bootloader-locator/0.0.4/bootloader_locator/) crate can
      be used to automate this step.
    - Otherwise, the
      [`cargo metadata`](https://doc.rust-lang.org/cargo/commands/cargo-metadata.html) subcommand
      can be used to locate the dependency. The command outputs a JSON object with various metadata
      for the current package. To find the `bootloader` source path in it, first look for the
      "bootloader" dependency under `resolve.nodes.deps` to find out its ID (in the `pkg` field).
      Then use that ID to find the bootloader in `packages`. Its `manifest_path` field contains the
      local path to the `Cargo.toml` of the bootloader.
2. **Run the following command** in the source code directory of the `bootloader` dependency to create
   the bootable disk images:

    ```notrust
    cargo builder --kernel-manifest path/to/kernel/Cargo.toml --kernel-binary path/to/kernel_bin
    ```

    The `--kernel-manifest` argument should point to the `Cargo.toml` of your kernel. It is used
    for applying configuration settings. The `--kernel-binary` argument should point to the kernel
    executable that should be used for the bootable disk images.

    In addition to the `--kernel-manifest` and `--kernel-binary` arguments, it is recommended to also
    set the `--target-dir` and `--out-dir` arguments. The former specifies the directory that should
    used for cargo build artifacts and the latter specfies the directory where the resulting disk
    images should be placed. It is recommended to set `--target-dir` to the `target` folder of your
    kernel and `--out-dir` to the the parent folder of `--kernel-binary`.

This will result in the following files, which are placed in the specified `--out-dir`:

- A disk image suitable for BIOS booting, named `boot-bios-<kernel>.img`, where `<kernel>` is the
  name of your kernel executable. This image can be started in QEMU or booted on a real machine
  after burning it to an USB stick..
- A disk image suitable for UEFI booting, named `boot-uefi-<kernel>.img`. Like the BIOS disk image,
  this can be started in QEMU (requires OVMF) and burned to an USB stick to run it on a real
  machine.
- Intermediate UEFI files
  - A FAT partition image named `boot-uefi-<kernel>.fat`, which can be directly started in QEMU
    or written as an EFI system partition to a GPT-formatted disk.
  - An EFI file named `boot-uefi-<kernel>.efi`. This executable is the combination of the
    bootloader and kernel executables. It can be started in QEMU or used to construct a bootable
    disk image: Create an EFI system partition formatted with the FAT filesystem and place the
    EFI file under `efi\boot\bootx64.efi` on that filesystem.

**You can find some examples that implement the above steps [in our GitHub repo](https://github.com/rust-osdev/bootloader/tree/main/examples).**

## Configuration

The bootloader can be configured through a `[package.metadata.bootloader]` table in the
`Cargo.toml` of the kernel (the one passed as `--kernel-manifest`). See the [`Config`] struct
for all possible configuration options.
*/

#![cfg_attr(not(feature = "builder"), no_std)]
#![feature(maybe_uninit_extra)]
#![feature(maybe_uninit_slice)]
#![deny(unsafe_op_in_unsafe_fn)]
#![warn(missing_docs)]

pub use crate::boot_info::BootInfo;
pub use crate::config::Config;

/// Configuration options for the bootloader.
mod config;

/// Contains the boot information struct sent by the bootloader to the kernel on startup.
pub mod boot_info;

/// Contains the actual bootloader implementation ("bootloader as a binary").
///
/// Useful for reusing part of the bootloader implementation for other crates.
///
/// Only available when the `binary` feature is enabled.
#[cfg(feature = "binary")]
pub mod binary;

/// Provides a function to turn a bootloader executable into a disk image.
///
/// Used by the `builder` binary. Only available when the `builder` feature is enabled.
#[cfg(feature = "builder")]
pub mod disk_image;

#[cfg(all(target_arch = "x86", not(feature = "builder")))]
compile_error!(
    "This crate currently does not support 32-bit protected mode. \
         See https://github.com/rust-osdev/bootloader/issues/70 for more information."
);

#[cfg(all(
    not(target_arch = "x86_64"),
    not(target_arch = "x86"),
    not(feature = "builder")
))]
compile_error!("This crate only supports the x86_64 architecture.");

/// Defines the entry point function.
///
/// The function must have the signature `fn(&'static mut BootInfo) -> !`.
///
/// This macro just creates a function named `_start`, which the linker will use as the entry
/// point. The advantage of using this macro instead of providing an own `_start` function is
/// that the macro ensures that the function and argument types are correct.
#[macro_export]
macro_rules! entry_point {
    ($path:path) => {
        #[export_name = "_start"]
        pub extern "C" fn __impl_start(boot_info: &'static mut $crate::boot_info::BootInfo) -> ! {
            // validate the signature of the program entry point
            let f: fn(&'static mut $crate::boot_info::BootInfo) -> ! = $path;

            f(boot_info)
        }
    };
}