Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,26 @@ jobs:
if: runner.os == 'Linux'
run: cargo test --no-default-features --features bios

examples:
name: Build examples
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v2
- uses: r7kamura/rust-problem-matchers@v1.1.0
- name: Install QEMU (Linux)
run: sudo apt update && sudo apt install qemu-system-x86
- name: "Build basic example"
run: cargo build
working-directory: examples/basic
- name: "Run basic example (bios)"
run: cargo run -- bios
working-directory: examples/basic
- name: "Run basic example (uefi)"
run: cargo run -- uefi
working-directory: examples/basic

fmt:
name: Check Formatting
runs-on: ubuntu-latest
Expand Down
81 changes: 55 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@ You need a nightly [Rust](https://www.rust-lang.org) compiler with the `llvm-too

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.

### Migrating from older bootloader version

If you're already using an older version of the `bootloader` crate, follow our [migration guides](docs/migration).

### Kernel
### Starting from scratch

Our [basic example](examples/basic/basic-os.md) showcases an OS that boots a minimal kernel using `bootloader`.

### Using an existing kernel

To make your kernel compatible with `bootloader`:
To combine your kernel with `bootloader` and create a bootable disk image, follow these steps:

#### Make your kernel compatible with `bootloader`

- Add a dependency on the `bootloader_api` crate in your kernel's `Cargo.toml`.
- Your kernel binary should be `#![no_std]` and `#![no_main]`.
Expand All @@ -35,34 +43,55 @@ To make your kernel compatible with `bootloader`:
};
bootloader_api::entry_point!(kernel_main, config = &CONFIG);
```
- 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.
- 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. You can add `x86_64-unknown-none` as default target and add it to your toolchain so that `cargo build` takes care of this.
```toml
# .cargo/config.toml
[build]
target = "x86_64-unknown-none"
```
Comment on lines +47 to +51
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```toml
# .cargo/config.toml
[build]
target = "x86_64-unknown-none"
```

Let's also remove this.

```sh
$ cargo build
```
- 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:
#### Creating a bootable 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 `std::env::var_os("CARGO_BIN_FILE_MY_KERNEL_my-kernel")`
- Use `bootloader::UefiBoot` and/or `bootloader::BiosBoot` to create a bootable disk image with your kernel.
- Create a new `os` crate at the top level
```sh
$ cargo init --bin
```
- Define a [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html) and add your kernel as a workspace member.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're specifying the command used to create the top-level crate (cargo init --bin we should do the same for the kernel). We could remove the kernel from the members (and just use members = []) and then use cargo new kernel --bin in the workspace root.

```toml
# in Cargo.toml
[workspace]
resolver = "3"
members = ["kernel"]
```
- Enable the workspace to build your kernel:
- 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" }
```
Enable the unstable artifact-dependencies feature:
```toml
# .cargo/config.toml
[unstable]
bindeps = true
```
Experimental features are only available on the nightly channel:
```toml
# rust-toolchain.toml
[toolchain]
channel = "nightly"
targets = ["x86_64-unknown-none"]
```
- 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.
- Create a [`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html) build script in the `os` crate. See our [disk image creation template](docs/create-disk-image.md) for a more detailed example.
- Obtain the path to the kernel executable. When using an artifact dependency, you can retrieve this path using `std::env::var_os("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](docs/create-disk-image.md) for a more detailed example.
Expand Down
95 changes: 15 additions & 80 deletions docs/create-disk-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,83 +5,18 @@ The [`bootloader`](https://docs.rs/bootloader/0.11) crate provides simple functi
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

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());
}
```

```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().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).
Our [basic example](examples/basic/basic-os.md) showcases this setup:
- [Cargo.toml](/examples/basic/Cargo.toml)
- create a workspace & add kernel as member
- add kernel as build-dependency
- add ovmf-prebuilt for UEFI booting in QEMU
- [.cargo/config.toml](/examples/basic/Cargo.toml)
- enable the unstable artifact-dependencies feature
- [rust-toolchain.toml](/examples/basic/Cargo.toml)
- change the default toolchain to nightly to use experimental features
- [build.rs](/examples/basic/build.rs)
- create bios and uefi disk image
- [src/main.rs](/examples/basic/src/main.rs])
- launch the image using QEMU

Now you should be able to use `cargo build` to create a bootable disk image and `cargo run bios` and `cargo run uefi` to run it 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).
2 changes: 2 additions & 0 deletions examples/basic/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[unstable]
bindeps = true
2 changes: 2 additions & 0 deletions examples/basic/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target/
**/*.rs.bk
Loading
Loading