Skip to content

Commit 9f590dc

Browse files
authored
Merge pull request #280 from rust-osdev/deserialize-errors
Add basic error strings for deserialization errors
2 parents e02ead7 + 2ceec8a commit 9f590dc

File tree

6 files changed

+224
-30
lines changed

6 files changed

+224
-30
lines changed

README.md

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ You need a nightly [Rust](https://www.rust-lang.org) compiler with the `llvm-too
1212

1313
## Usage
1414

15+
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.
16+
17+
If you're already using an older version of the `bootloader` crate, follow our [migration guides](doc/migration).
18+
19+
### Kernel
20+
1521
To make your kernel compatible with `bootloader`:
1622

1723
- 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`:
2935
};
3036
bootloader_api::entry_point!(kernel_main, config = &CONFIG);
3137
```
32-
- 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.
38+
- 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.
39+
- 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.
40+
41+
### Booting
3342

3443
To combine your kernel with a bootloader and create a bootable disk image, follow these steps:
3544

36-
- Create a new runner crate, e.g. through `cargo new runner --bin`.
37-
- Add the `bootloader` crate as a `dependency` in the `runner/Cargo.toml`.
38-
- In the `main.rs`, invoke the build commands for your kernel.
39-
- 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:
40-
```toml
41-
[dependencies]
42-
my-kernel = { path = "..", artifact = "bin", target = "x86_64-unknown-none" }
43-
```
44-
- After building your kernel, obtain the path to the kernel executable.
45-
- When using an artifact dependency, you can retrieve this path using `env!("CARGO_BIN_FILE_MY_KERNEL_my-kernel")`
46-
- Use the `bootloader::create_boot_partition` function to create a bootable FAT partition at some chosen path.
47-
- Use one or multiple `bootloader::create_*_disk_image` functions to transform the bootable FAT partition into a disk image.
48-
- Use the `bootloader::create_uefi_disk_image` function to create an UEFI-compatible GPT-formatted disk image.
49-
- Use the `bootloader::create_bios_disk_image` function to create a BIOS-compatible MBR-formatted disk image.
45+
- Move your full kernel code into a `kernel` subdirectory.
46+
- Create a new `os` crate at the top level that defines a [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html).
47+
- Add a `build-dependencies` on the `bootloader` crate.
48+
- Create a [`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html) build script.
49+
- 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`:
50+
```toml
51+
# in Cargo.toml
52+
[build-dependencies]
53+
kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" }
54+
```
55+
```toml
56+
# .cargo/config.toml
57+
58+
[unstable]
59+
# enable the unstable artifact-dependencies feature, see
60+
# https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies
61+
bindeps = true
62+
```
63+
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.
64+
- 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")`
65+
- Use `bootloader::UefiBoot` and/or `bootloader::BiosBoot` to create a bootable disk image with your kernel.
66+
- Do something with the bootable disk images in your `main.rs` function. For example, run them with QEMU.
67+
68+
See our [disk image creation template](doc/create-disk-image.md) for a more detailed example.
5069

5170
## Architecture
5271

api/src/config.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -149,16 +149,16 @@ impl BootloaderConfig {
149149
/// ELF file.
150150
///
151151
/// TODO: return error enum
152-
pub fn deserialize(serialized: &[u8]) -> Result<Self, ()> {
152+
pub fn deserialize(serialized: &[u8]) -> Result<Self, &'static str> {
153153
if serialized.len() != Self::SERIALIZED_LEN {
154-
return Err(());
154+
return Err("invalid len");
155155
}
156156

157157
let s = serialized;
158158

159159
let (uuid, s) = split_array_ref(s);
160160
if uuid != &Self::UUID {
161-
return Err(());
161+
return Err("invalid UUID");
162162
}
163163

164164
let (version, s) = {
@@ -169,7 +169,7 @@ impl BootloaderConfig {
169169
let pre = match pre {
170170
[0] => false,
171171
[1] => true,
172-
_ => return Err(()),
172+
_ => return Err("invalid pre version"),
173173
};
174174

175175
let version = ApiVersion {
@@ -206,27 +206,27 @@ impl BootloaderConfig {
206206
physical_memory: match physical_memory_some {
207207
[0] if physical_memory == [0; 9] => Option::None,
208208
[1] => Option::Some(Mapping::deserialize(&physical_memory)?),
209-
_ => return Err(()),
209+
_ => return Err("invalid phys memory value"),
210210
},
211211
page_table_recursive: match page_table_recursive_some {
212212
[0] if page_table_recursive == [0; 9] => Option::None,
213213
[1] => Option::Some(Mapping::deserialize(&page_table_recursive)?),
214-
_ => return Err(()),
214+
_ => return Err("invalid page table recursive value"),
215215
},
216216
aslr: match alsr {
217217
1 => true,
218218
0 => false,
219-
_ => return Err(()),
219+
_ => return Err("invalid aslr value"),
220220
},
221221
dynamic_range_start: match dynamic_range_start_some {
222222
[0] if dynamic_range_start == [0; 8] => Option::None,
223223
[1] => Option::Some(u64::from_le_bytes(dynamic_range_start)),
224-
_ => return Err(()),
224+
_ => return Err("invalid dynamic range start value"),
225225
},
226226
dynamic_range_end: match dynamic_range_end_some {
227227
[0] if dynamic_range_end == [0; 8] => Option::None,
228228
[1] => Option::Some(u64::from_le_bytes(dynamic_range_end)),
229-
_ => return Err(()),
229+
_ => return Err("invalid dynamic range end value"),
230230
},
231231
};
232232
(mappings, s)
@@ -242,19 +242,19 @@ impl BootloaderConfig {
242242
minimum_framebuffer_height: match min_framebuffer_height_some {
243243
[0] if min_framebuffer_height == [0; 8] => Option::None,
244244
[1] => Option::Some(u64::from_le_bytes(min_framebuffer_height)),
245-
_ => return Err(()),
245+
_ => return Err("minimum_framebuffer_height invalid"),
246246
},
247247
minimum_framebuffer_width: match min_framebuffer_width_some {
248248
[0] if min_framebuffer_width == [0; 8] => Option::None,
249249
[1] => Option::Some(u64::from_le_bytes(min_framebuffer_width)),
250-
_ => return Err(()),
250+
_ => return Err("minimum_framebuffer_width invalid"),
251251
},
252252
};
253253
(frame_buffer, s)
254254
};
255255

256256
if !s.is_empty() {
257-
return Err(());
257+
return Err("unexpected rest");
258258
}
259259

260260
Ok(Self {
@@ -509,17 +509,17 @@ impl Mapping {
509509
}
510510
}
511511

512-
fn deserialize(serialized: &[u8; 9]) -> Result<Self, ()> {
512+
fn deserialize(serialized: &[u8; 9]) -> Result<Self, &'static str> {
513513
let (&variant, s) = split_array_ref(serialized);
514514
let (&addr, s) = split_array_ref(s);
515515
if !s.is_empty() {
516-
return Err(());
516+
return Err("invalid mapping format");
517517
}
518518

519519
match variant {
520520
[0] if addr == [0; 8] => Ok(Mapping::Dynamic),
521521
[1] => Ok(Mapping::FixedAddress(u64::from_le_bytes(addr))),
522-
_ => Err(()),
522+
_ => Err("invalid mapping value"),
523523
}
524524
}
525525
}
File renamed without changes.

docs/create-disk-image.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Template: Create a Disk Image
2+
3+
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.
4+
5+
A good way to implement this is to move your kernel into a `kernel` subdirectory. Then you can create
6+
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.
7+
8+
The files could look like this:
9+
10+
```toml
11+
# .cargo/config.toml
12+
13+
[unstable]
14+
# enable the unstable artifact-dependencies feature, see
15+
# https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies
16+
bindeps = true
17+
```
18+
19+
```toml
20+
# Cargo.toml
21+
22+
[package]
23+
name = "os" # or any other name
24+
version = "0.1.0"
25+
26+
[build-dependencies]
27+
bootloader = "0.11"
28+
test-kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" }
29+
30+
[dependencies]
31+
# used for UEFI booting in QEMU
32+
ovmf_prebuilt = "0.1.0-alpha.1"
33+
34+
[workspace]
35+
members = ["kernel"]
36+
```
37+
38+
```rust
39+
// build.rs
40+
41+
fn main() {
42+
// set by cargo, build scripts should use this directory for output files
43+
let out_dir = env::var_os("OUT_DIR").unwrap();
44+
// set by cargo's artifact dependency feature, see
45+
// https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies
46+
let kernel = env!("CARGO_BIN_FILE_KERNEL_kernel");
47+
48+
// create an UEFI disk image (optional)
49+
let uefi_path = out_dir.join("uefi.img");
50+
bootloader::UefiBoot::new(&kernel).create_disk_image(uefi_path).unwrap();
51+
52+
// create a BIOS disk image (optional)
53+
let out_bios_path = out_dir.join("bios.img");
54+
bootloader::BiosBoot::new(&kernel).create_disk_image(uefi_path).unwrap();
55+
56+
// pass the disk image paths as env variables to the `main.rs`
57+
println!("cargo:rustc-env=UEFI_PATH={}", uefi_path.display());
58+
println!("cargo:rustc-env=BIOS_PATH={}", bios_path.display());
59+
}
60+
```
61+
62+
```rust
63+
// src/main.rs
64+
65+
fn main() {
66+
// read env variables that were set in build script
67+
let uefi_path = env!("UEFI_PATH");
68+
let bios_path = env!("BIOS_PATH");
69+
70+
// choose whether to start the UEFI or BIOS image
71+
let uefi = true;
72+
73+
let mut cmd = std::process::Command::new("qemu-system-x86_64");
74+
if uefi {
75+
cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi());
76+
cmd.arg("-drive").arg(format!("format=raw,file={uefi_path}"));
77+
} else {
78+
cmd.arg("-drive").arg(format!("format=raw,file={bios_path}"));
79+
}
80+
let mut child = cmd.spawn()?;
81+
child.wait()?;
82+
}
83+
```
84+
85+
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).

docs/migration/v0.10.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Migration from bootloader `v0.10`
2+
3+
This guide summarizes the steps for migrating from `bootloader v0.10.X` to `bootloader v0.11`.
4+
5+
## Kernel
6+
7+
- Replace the `bootloader` dependency of your kernel with a dependency on the `bootloader_api` crate and adjust the import path in your `main.rs`:
8+
```diff
9+
# in Cargo.toml
10+
11+
-bootloader = { version = "0.10.13" }
12+
+bootloader_api = "0.11"
13+
```
14+
```diff
15+
// in main.rs
16+
17+
-use bootloader::{entry_point, BootInfo};
18+
+use bootloader_api::{entry_point, BootInfo};
19+
```
20+
- If you used optional features, such as `map-physical-memory`, you can enable them again through the `entry_point` macro:
21+
```rust
22+
use bootloader_api::config::{BootloaderConfig, Mapping};
23+
24+
pub static BOOTLOADER_CONFIG: BootloaderConfig = {
25+
let mut config = BootloaderConfig::new_default();
26+
config.mappings.physical_memory = Some(Mapping::Dynamic);
27+
config
28+
};
29+
30+
// add a `config` argument to the `entry_point` macro call
31+
entry_point!(kernel_main, config = &BOOTLOADER_CONFIG);
32+
```
33+
See the [`BootloaderConfig`](https://docs.rs/bootloader_api/0.11/bootloader_api/config/struct.BootloaderConfig.html) struct for all configuration options.
34+
35+
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.
36+
37+
## Booting
38+
39+
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.
40+
41+
See our [disk image creation template](../create-disk-image.md) for a detailed explanation of the new build process.

docs/migration/v0.9.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Migration from bootloader `v0.9`
2+
3+
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).
4+
5+
## Kernel
6+
7+
- Replace the `bootloader` dependency of your kernel with a dependency on the `bootloader_api` crate:
8+
```diff
9+
-bootloader = { version = "0.9.23", features = [...]}
10+
+bootloader_api = "0.11"
11+
```
12+
- In your `main.rs`, adjust the import path and change the signature of the entry point function:
13+
```diff
14+
-use bootloader::{entry_point, BootInfo};
15+
+use bootloader_api::{entry_point, BootInfo};
16+
17+
entry_point!(kernel_main);
18+
19+
-fn kernel_main(boot_info: &'static BootInfo) -> ! {
20+
+fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
21+
```
22+
- If you used optional features, such as `map_physical_memory`, you can enable them again through the `entry_point` macro:
23+
```rust
24+
use bootloader_api::config::{BootloaderConfig, Mapping};
25+
26+
pub static BOOTLOADER_CONFIG: BootloaderConfig = {
27+
let mut config = BootloaderConfig::new_default();
28+
config.mappings.physical_memory = Some(Mapping::Dynamic);
29+
config
30+
};
31+
32+
// add a `config` argument to the `entry_point` macro call
33+
entry_point!(kernel_main, config = &BOOTLOADER_CONFIG);
34+
```
35+
See the [`BootloaderConfig`](https://docs.rs/bootloader_api/0.11/bootloader_api/config/struct.BootloaderConfig.html) struct for all configuration options.
36+
- 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.
37+
- 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:
38+
- 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.
39+
- 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.
40+
- 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.
41+
- 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).
42+
43+
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.
44+
45+
## Booting
46+
47+
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.
48+
49+
See our [disk image creation template](../create-disk-image.md) for a detailed explanation of the new build process.

0 commit comments

Comments
 (0)