From 7ea5ecf6962d741a5c98b0474ef5eab5886f25a2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 13 Nov 2022 14:20:38 +0100 Subject: [PATCH 1/3] Add basic error strings for deserialization errors --- api/src/config.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/api/src/config.rs b/api/src/config.rs index 4f69174c..5ca235ab 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -149,16 +149,16 @@ impl BootloaderConfig { /// ELF file. /// /// TODO: return error enum - pub fn deserialize(serialized: &[u8]) -> Result { + pub fn deserialize(serialized: &[u8]) -> Result { if serialized.len() != Self::SERIALIZED_LEN { - return Err(()); + return Err("invalid len"); } let s = serialized; let (uuid, s) = split_array_ref(s); if uuid != &Self::UUID { - return Err(()); + return Err("invalid UUID"); } let (version, s) = { @@ -169,7 +169,7 @@ impl BootloaderConfig { let pre = match pre { [0] => false, [1] => true, - _ => return Err(()), + _ => return Err("invalid pre version"), }; let version = ApiVersion { @@ -206,27 +206,27 @@ impl BootloaderConfig { physical_memory: match physical_memory_some { [0] if physical_memory == [0; 9] => Option::None, [1] => Option::Some(Mapping::deserialize(&physical_memory)?), - _ => return Err(()), + _ => return Err("invalid phys memory value"), }, page_table_recursive: match page_table_recursive_some { [0] if page_table_recursive == [0; 9] => Option::None, [1] => Option::Some(Mapping::deserialize(&page_table_recursive)?), - _ => return Err(()), + _ => return Err("invalid page table recursive value"), }, aslr: match alsr { 1 => true, 0 => false, - _ => return Err(()), + _ => return Err("invalid aslr value"), }, dynamic_range_start: match dynamic_range_start_some { [0] if dynamic_range_start == [0; 8] => Option::None, [1] => Option::Some(u64::from_le_bytes(dynamic_range_start)), - _ => return Err(()), + _ => return Err("invalid dynamic range start value"), }, dynamic_range_end: match dynamic_range_end_some { [0] if dynamic_range_end == [0; 8] => Option::None, [1] => Option::Some(u64::from_le_bytes(dynamic_range_end)), - _ => return Err(()), + _ => return Err("invalid dynamic range end value"), }, }; (mappings, s) @@ -242,19 +242,19 @@ impl BootloaderConfig { minimum_framebuffer_height: match min_framebuffer_height_some { [0] if min_framebuffer_height == [0; 8] => Option::None, [1] => Option::Some(u64::from_le_bytes(min_framebuffer_height)), - _ => return Err(()), + _ => return Err("minimum_framebuffer_height invalid"), }, minimum_framebuffer_width: match min_framebuffer_width_some { [0] if min_framebuffer_width == [0; 8] => Option::None, [1] => Option::Some(u64::from_le_bytes(min_framebuffer_width)), - _ => return Err(()), + _ => return Err("minimum_framebuffer_width invalid"), }, }; (frame_buffer, s) }; if !s.is_empty() { - return Err(()); + return Err("unexpected rest"); } Ok(Self { @@ -509,17 +509,17 @@ impl Mapping { } } - fn deserialize(serialized: &[u8; 9]) -> Result { + fn deserialize(serialized: &[u8; 9]) -> Result { let (&variant, s) = split_array_ref(serialized); let (&addr, s) = split_array_ref(s); if !s.is_empty() { - return Err(()); + return Err("invalid mapping format"); } match variant { [0] if addr == [0; 8] => Ok(Mapping::Dynamic), [1] => Ok(Mapping::FixedAddress(u64::from_le_bytes(addr))), - _ => Err(()), + _ => Err("invalid mapping value"), } } } From 46c7725f37d18dadeb4880cac8a2c64dc55be4ab Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 13 Nov 2022 16:07:37 +0100 Subject: [PATCH 2/3] Create migration guides --- doc/migration/v0.10.md | 121 ++++++++++++++++++++++++++++++++++++++ doc/migration/v0.9.md | 129 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 doc/migration/v0.10.md create mode 100644 doc/migration/v0.9.md diff --git a/doc/migration/v0.10.md b/doc/migration/v0.10.md new file mode 100644 index 00000000..a11b7795 --- /dev/null +++ b/doc/migration/v0.10.md @@ -0,0 +1,121 @@ +# Migration from bootloader `v0.10` + +This guide summarizes the steps for migrating from `bootloader v0.10.X` to `bootloader v0.11`. + +## Kernel + +- Replace the `bootloader` dependency of your kernel with a dependency on the `bootloader_api` crate and adjust the import path in your `main.rs`: + ```diff + # in Cargo.toml + + -bootloader = { version = "0.10.13" } + +bootloader_api = "0.11" + ``` + ```diff + // in main.rs + + -use bootloader::{entry_point, BootInfo}; + +use bootloader_api::{entry_point, BootInfo}; + ``` +- If you used optional features, such as `map-physical-memory`, you can enable them again through the `entry_point` macro: + ```rust + 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); + ``` + See the [`BootloaderConfig`](https://docs.rs/bootloader_api/0.11/bootloader_api/config/struct.BootloaderConfig.html) struct for all configuration options. + +To build your kernel, run **`cargo build --target x86_64-unknown-none`**. Since the `x86_64-unknown-none` target is a Tier-2 target, there is no need for `bootimage`, `cargo-xbuild`, or `xargo` anymore. Instead, you can run `rustup target add x86_64-unknown-none` to download precompiled versions of the `core` and `alloc` crates. There is no need for custom JSON-based target files anymore. + +## Booting + +The `bootloader v0.11` release simplifies the disk image creation. The [`bootloader`](https://docs.rs/bootloader/0.11) crate now 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](https://doc.rust-lang.org/cargo/reference/workspaces.html). The root package has build-dependencies on the `kernel` [artifact](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) and on the bootloader crate. This allows you to create the bootable disk image in a [cargo build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) and launch the created image in QEMU in the `main` function. + +The files could look like this: + +```toml +# .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 +``` + +```toml +# 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"] +``` + +```rust +// build.rs + +fn main() { + // set by cargo, build scripts should use this directory for output files + let out_dir = 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 = env!("CARGO_BIN_FILE_KERNEL"); + + // 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 (optional) + let out_bios_path = out_dir.join("bios.img"); + bootloader::BiosBoot::new(&kernel).create_disk_image(uefi_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()); +} +``` + +```rust +// 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()?; + child.wait()?; +} +``` + +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). diff --git a/doc/migration/v0.9.md b/doc/migration/v0.9.md new file mode 100644 index 00000000..636bed36 --- /dev/null +++ b/doc/migration/v0.9.md @@ -0,0 +1,129 @@ +# Migration from bootloader `v0.9` + +This guide summarizes the steps for migrating from `bootloader v0.9.X` to `bootloader v0.11`. Note that some bigger changes are required to support UEFI booting (to support the framebuffer and APIC). + +## Kernel + +- Replace the `bootloader` dependency of your kernel with a dependency on the `bootloader_api` crate: + ```diff + -bootloader = { version = "0.9.23", features = [...]} + +bootloader_api = "0.11" + ``` +- In your `main.rs`, adjust the import path and change the signature of the entry point function: + ```diff + -use bootloader::{entry_point, BootInfo}; + +use bootloader_api::{entry_point, BootInfo}; + + entry_point!(kernel_main); + + -fn kernel_main(boot_info: &'static BootInfo) -> ! { + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + ``` +- If you used optional features, such as `map_physical_memory`, you can enable them again through the `entry_point` macro: + ```rust + 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); + ``` + See the [`BootloaderConfig`](https://docs.rs/bootloader_api/0.11/bootloader_api/config/struct.BootloaderConfig.html) struct for all configuration options. +- The `v0.11` version of the bootloader sets up a pixel-based framebuffer instead of using the VGA text mode. This means that you have to rewrite your screen output code. See the [`common/logger.rs`](../../common/src/logger.rs) module for an example implementation based on the [`noto-sans-mono-bitmap`](https://docs.rs/noto-sans-mono-bitmap/latest/noto_sans_mono_bitmap/index.html) and [`log`](https://docs.rs/log/latest) crates. +- If you want to use UEFI booting, you cannot use the [legacy PIC](https://wiki.osdev.org/8259_PIC) for interrupt handling. Instead, you have to set up the [`APIC`](https://wiki.osdev.org/APIC). Unfortunately, we don't have a guide for this yet, but the basic steps are: + - The `boot_info.rsdp_addr` field will tell you the physical address of the [`RSDP`](https://wiki.osdev.org/RSDP) structure, which is part of the [`ACPI`](https://en.wikipedia.org/wiki/ACPI) standard. Note that `ACPI` (_Advanced Configuration and Power Interface_) and `APIC` (_Advanced Programmable Interrupt Controller_) are completely different things that just have confusingly similar acronyms. + - Use the [`acpi`](https://docs.rs/acpi/4.1.1/acpi/index.html) crate and its [`AcpiTables::from_rsdp`](https://docs.rs/acpi/4.1.1/acpi/struct.AcpiTables.html#method.from_rsdp) function to load the ACPI tables. Then use the [`platform_info`](https://docs.rs/acpi/4.1.1/acpi/struct.AcpiTables.html#method.platform_info) method and read the [`interrupt_model`](https://docs.rs/acpi/4.1.1/acpi/platform/struct.PlatformInfo.html#structfield.interrupt_model) field of the returned `PlatformInfo`. This field gives you all the necessary information about the system's interrupt controller. + - Parse and set up the local and IO APIC. We're working on a [crate](https://github.com/rust-osdev/apic) for that, but it's still in a very early state. We would love contributions! Alternatively, there are some other crates on crates.io that might work too. +- If you reload the GDT at some point, ensure that all segment registers are written, including `ss` and `ds`. The `v0.9` version of the bootloader used to initialize some of them to 0, but this is no longer the case. If you don't do this, [a general protection fault might happen on `iretq`](https://github.com/rust-osdev/bootloader/issues/196). + +To build your kernel, run **`cargo build --target x86_64-unknown-none`**. Since the `x86_64-unknown-none` target is a Tier-2 target, there is no need for `bootimage`, `cargo-xbuild`, or `xargo` anymore. Instead, you can run `rustup target add x86_64-unknown-none` to download precompiled versions of the `core` and `alloc` crates. There is no need for custom JSON-based target files anymore. + +## Booting + +The `bootloader v0.11` release does not use the `bootimage` tool anymore. Instead, the [`bootloader`](https://docs.rs/bootloader/0.11) crate provides 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](https://doc.rust-lang.org/cargo/reference/workspaces.html). The root package has build-dependencies on the `kernel` [artifact](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) and on the bootloader crate. This allows you to create the bootable disk image in a [cargo build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) and launch the created image in QEMU in the `main` function. + +The files could look like this: + +```toml +# .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 +``` + +```toml +# 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"] +``` + +```rust +// build.rs + +fn main() { + // set by cargo, build scripts should use this directory for output files + let out_dir = 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 = env!("CARGO_BIN_FILE_KERNEL"); + + // 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 (optional) + let out_bios_path = out_dir.join("bios.img"); + bootloader::BiosBoot::new(&kernel).create_disk_image(uefi_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()); +} +``` + +```rust +// 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()?; + child.wait()?; +} +``` + +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). From 2ceec8a7062be71240eb1dd18888dd25225638b8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 13 Nov 2022 16:34:35 +0100 Subject: [PATCH 3/3] Create migration guides and a disk image creation template --- README.md | 49 +++++++---- {doc => docs}/chainloading.md | 0 .../v0.10.md => docs/create-disk-image.md | 42 +--------- docs/migration/v0.10.md | 41 ++++++++++ {doc => docs}/migration/v0.9.md | 82 +------------------ 5 files changed, 79 insertions(+), 135 deletions(-) rename {doc => docs}/chainloading.md (100%) rename doc/migration/v0.10.md => docs/create-disk-image.md (60%) create mode 100644 docs/migration/v0.10.md rename {doc => docs}/migration/v0.9.md (59%) diff --git a/README.md b/README.md index 6050792f..8099df4c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,12 @@ You need a nightly [Rust](https://www.rust-lang.org) compiler with the `llvm-too ## Usage +To use this crate, you need to adjust your kernel to be bootable first. Then you can create a bootable disk image from your compiled kernel. These steps are explained in detail below. + +If you're already using an older version of the `bootloader` crate, follow our [migration guides](doc/migration). + +### Kernel + To make your kernel compatible with `bootloader`: - Add a dependency on the `bootloader_api` crate in your kernel's `Cargo.toml`. @@ -29,24 +35,37 @@ To make your kernel compatible with `bootloader`: }; bootloader_api::entry_point!(kernel_main, config = &CONFIG); ``` -- Compile your kernel as normal to an ELF executable. The executable will contain a special section with metadata and the serialized config, which will enable the `bootloader` crate to load it. +- Compile your kernel to an ELF executable by running **`cargo build --target x86_64-unknown-none`**. You might need to run `rustup target add x86_64-unknown-none` before to download precompiled versions of the `core` and `alloc` crates. +- Thanks to the `entry_point` macro, the compiled executable contains a special section with metadata and the serialized config, which will enable the `bootloader` crate to load it. + +### Booting To combine your kernel with a bootloader and create a bootable disk image, follow these steps: -- Create a new runner crate, e.g. through `cargo new runner --bin`. -- Add the `bootloader` crate as a `dependency` in the `runner/Cargo.toml`. -- In the `main.rs`, invoke the build commands for your kernel. - - Alternatively, you can set up an [artifact dependency](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) on your kernel, provided that you use a `rustup`-supported target for your kernel: - ```toml - [dependencies] - my-kernel = { path = "..", artifact = "bin", target = "x86_64-unknown-none" } - ``` -- After building your kernel, obtain the path to the kernel executable. - - When using an artifact dependency, you can retrieve this path using `env!("CARGO_BIN_FILE_MY_KERNEL_my-kernel")` -- Use the `bootloader::create_boot_partition` function to create a bootable FAT partition at some chosen path. -- Use one or multiple `bootloader::create_*_disk_image` functions to transform the bootable FAT partition into a disk image. - - Use the `bootloader::create_uefi_disk_image` function to create an UEFI-compatible GPT-formatted disk image. - - Use the `bootloader::create_bios_disk_image` function to create a BIOS-compatible MBR-formatted disk image. +- Move your full kernel code into a `kernel` subdirectory. +- Create a new `os` crate at the top level that defines a [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html). +- Add a `build-dependencies` on the `bootloader` crate. +- Create a [`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html) build script. +- Set up an [artifact dependency](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) to add your `kernel` crate as a `build-dependency`: + ```toml + # in Cargo.toml + [build-dependencies] + kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" } + ``` + ```toml + # .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 + ``` + Alternatively, you can use [`std::process::Command`](https://doc.rust-lang.org/stable/std/process/struct.Command.html) to invoke the build command of your kernel in the `build.rs` script. +- Obtain the path to the kernel executable. When using an artifact dependency, you can retrieve this path using `env!("CARGO_BIN_FILE_MY_KERNEL_my-kernel")` +- Use `bootloader::UefiBoot` and/or `bootloader::BiosBoot` to create a bootable disk image with your kernel. +- Do something with the bootable disk images in your `main.rs` function. For example, run them with QEMU. + +See our [disk image creation template](doc/create-disk-image.md) for a more detailed example. ## Architecture diff --git a/doc/chainloading.md b/docs/chainloading.md similarity index 100% rename from doc/chainloading.md rename to docs/chainloading.md diff --git a/doc/migration/v0.10.md b/docs/create-disk-image.md similarity index 60% rename from doc/migration/v0.10.md rename to docs/create-disk-image.md index a11b7795..67302b4a 100644 --- a/doc/migration/v0.10.md +++ b/docs/create-disk-image.md @@ -1,42 +1,6 @@ -# Migration from bootloader `v0.10` +# Template: Create a Disk Image -This guide summarizes the steps for migrating from `bootloader v0.10.X` to `bootloader v0.11`. - -## Kernel - -- Replace the `bootloader` dependency of your kernel with a dependency on the `bootloader_api` crate and adjust the import path in your `main.rs`: - ```diff - # in Cargo.toml - - -bootloader = { version = "0.10.13" } - +bootloader_api = "0.11" - ``` - ```diff - // in main.rs - - -use bootloader::{entry_point, BootInfo}; - +use bootloader_api::{entry_point, BootInfo}; - ``` -- If you used optional features, such as `map-physical-memory`, you can enable them again through the `entry_point` macro: - ```rust - 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); - ``` - See the [`BootloaderConfig`](https://docs.rs/bootloader_api/0.11/bootloader_api/config/struct.BootloaderConfig.html) struct for all configuration options. - -To build your kernel, run **`cargo build --target x86_64-unknown-none`**. Since the `x86_64-unknown-none` target is a Tier-2 target, there is no need for `bootimage`, `cargo-xbuild`, or `xargo` anymore. Instead, you can run `rustup target add x86_64-unknown-none` to download precompiled versions of the `core` and `alloc` crates. There is no need for custom JSON-based target files anymore. - -## Booting - -The `bootloader v0.11` release simplifies the disk image creation. The [`bootloader`](https://docs.rs/bootloader/0.11) crate now 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. +The [`bootloader`](https://docs.rs/bootloader/0.11) 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](https://doc.rust-lang.org/cargo/reference/workspaces.html). The root package has build-dependencies on the `kernel` [artifact](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) and on the bootloader crate. This allows you to create the bootable disk image in a [cargo build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) and launch the created image in QEMU in the `main` function. @@ -79,7 +43,7 @@ fn main() { let out_dir = 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 = env!("CARGO_BIN_FILE_KERNEL"); + let kernel = env!("CARGO_BIN_FILE_KERNEL_kernel"); // create an UEFI disk image (optional) let uefi_path = out_dir.join("uefi.img"); diff --git a/docs/migration/v0.10.md b/docs/migration/v0.10.md new file mode 100644 index 00000000..7536f231 --- /dev/null +++ b/docs/migration/v0.10.md @@ -0,0 +1,41 @@ +# Migration from bootloader `v0.10` + +This guide summarizes the steps for migrating from `bootloader v0.10.X` to `bootloader v0.11`. + +## Kernel + +- Replace the `bootloader` dependency of your kernel with a dependency on the `bootloader_api` crate and adjust the import path in your `main.rs`: + ```diff + # in Cargo.toml + + -bootloader = { version = "0.10.13" } + +bootloader_api = "0.11" + ``` + ```diff + // in main.rs + + -use bootloader::{entry_point, BootInfo}; + +use bootloader_api::{entry_point, BootInfo}; + ``` +- If you used optional features, such as `map-physical-memory`, you can enable them again through the `entry_point` macro: + ```rust + 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); + ``` + See the [`BootloaderConfig`](https://docs.rs/bootloader_api/0.11/bootloader_api/config/struct.BootloaderConfig.html) struct for all configuration options. + +To build your kernel, run **`cargo build --target x86_64-unknown-none`**. Since the `x86_64-unknown-none` target is a Tier-2 target, there is no need for `bootimage`, `cargo-xbuild`, or `xargo` anymore. Instead, you can run `rustup target add x86_64-unknown-none` to download precompiled versions of the `core` and `alloc` crates. There is no need for custom JSON-based target files anymore. + +## Booting + +The `bootloader v0.11` release simplifies the disk image creation. The [`bootloader`](https://docs.rs/bootloader/0.11) crate now 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. + +See our [disk image creation template](../create-disk-image.md) for a detailed explanation of the new build process. diff --git a/doc/migration/v0.9.md b/docs/migration/v0.9.md similarity index 59% rename from doc/migration/v0.9.md rename to docs/migration/v0.9.md index 636bed36..856b2154 100644 --- a/doc/migration/v0.9.md +++ b/docs/migration/v0.9.md @@ -46,84 +46,4 @@ To build your kernel, run **`cargo build --target x86_64-unknown-none`**. Since The `bootloader v0.11` release does not use the `bootimage` tool anymore. Instead, the [`bootloader`](https://docs.rs/bootloader/0.11) crate provides 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](https://doc.rust-lang.org/cargo/reference/workspaces.html). The root package has build-dependencies on the `kernel` [artifact](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) and on the bootloader crate. This allows you to create the bootable disk image in a [cargo build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) and launch the created image in QEMU in the `main` function. - -The files could look like this: - -```toml -# .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 -``` - -```toml -# 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"] -``` - -```rust -// build.rs - -fn main() { - // set by cargo, build scripts should use this directory for output files - let out_dir = 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 = env!("CARGO_BIN_FILE_KERNEL"); - - // 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 (optional) - let out_bios_path = out_dir.join("bios.img"); - bootloader::BiosBoot::new(&kernel).create_disk_image(uefi_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()); -} -``` - -```rust -// 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()?; - child.wait()?; -} -``` - -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). +See our [disk image creation template](../create-disk-image.md) for a detailed explanation of the new build process.