Crate multiboot2_common

Source
Expand description

Common helpers for the multiboot2 and multiboot2-header crates.

§Value-add

The main value-add of this crate is to abstract away the parsing and construction of Multiboot2 structures. This is more complex as it may sound at first due to the difficulties listed below. Further, functionality for the iteration of tags are provided.

The abstractions provided by this crate serve as the base to work with the following structures in interaction:

  • multiboot2:
    • boot information
    • boot information header (the fixed sized begin portion of a boot information)
    • boot information tags
    • boot information tag header (the fixed sized begin portion of a tag)
  • multiboot2-header:
    • header
    • header header (the fixed sized begin portion of a header)
    • header tags
    • header tag header (the fixed sized begin portion of a tag)

§TL;DR: Specific Example

To name a specific example, the multiboot2 crate just needs the following types:

Then, all the magic using the TagIter and DynSizedStructure::cast can easily be utilized.

The same correspondingly applies to the structures in multiboot2-header.

§Design, Solved Problem, and Difficulties along the Way

Firstly, the design choice to have ABI-compatible rusty types in multiboot2 and multiboot2-header mainly influenced the requirements and difficulties along the way. These obstacles on the other side, influenced the design. The outcome is what we perceive as the optimal rusty and convenient solution.

§Architecture Diagrams

The figures in the README (currently not embeddable in lib.rs unfortunately) provides an overview of the parsing of Multiboot2 structures and how the definitions from this crate are used.

Note that although the diagrams seem complex, most logic is in multiboot2-common. For downstream users, the usage is quite simple.

§Multiboot2 Structures

Multiboot2 structures are a consecutive chunk of bytes in memory. They use the “header pattern”, which means a fixed size and known Header type indicates the total size of the structure. This is roughly translated to the following rusty base type:

#[repr(C, align(8))]
struct DynStructure {
    header: MyHeader,
    payload: [u8]
}

Note that these structures can also be nested. So for example, the Multiboot2 boot information contains Multiboot2 tags, and the Multiboot2 header contains Multiboot2 header tags - both are itself dynamically sized structures. This means, you can know the size (and amount of elements) only at runtime!

A final [u8] field in the structs is the most rusty way to model this. However, this makes the type a Dynamically Sized Type (DST). To create references to these types from a byte slice, one needs fat pointers. They are a language feature currently not constructable with stable Rust. Luckily, we can utilize ptr_meta.

Figure 1 in the README (currently not embeddable in lib.rs unfortunately) provides an overview of Multiboot2 structures.

§Dynamic and Sized Structs in Rust

Note that we also have structures (tags) in Multiboot2 that looks like this:

#[repr(C, align(8))]
struct DynStructure {
    header: MyHeader,
    // Not just [`u8`]
    payload: [SomeType]
}

or

#[repr(C, align(8))]
struct CommandLineTag {
    header: TagHeader,
    start: u32,
    end: u32,
    // More than just the base header before the dynamic portion
    data: [u8]
}

§Chosen Design

The overall common abstractions needed to solve the problems mentioned in this section are also mainly influenced by the fact that the multiboot2 and multiboot2-header crates use a zero-copy design by parsing the corresponding raw bytes with ABI-compatible types owning all their memory.

Further, by having ABI-compatible types that fully represent the reality, we can use the same type for parsing and for construction, as modelled in the following simplified example:

/// ABI-compatible tag for parsing.
#[repr(C)]
pub struct MemoryMapTag {
    header: TagHeader,
    entry_size: u32,
    entry_version: u32,
    areas: [MemoryArea],
}

impl MemoryMapTag {
    // We can also create an ABI-compatible structure of that type.
    pub fn new(areas: &[MemoryArea]) -> Box<Self> {
        // omitted
    }
}

Hence, the structures can also be build at runtime. This is what we consider idiomatic and rusty.

§Creating Fat Pointers with ptr_meta

Fat pointers are a language feature and the base for references to dynamically sized types, such as &str, &[T], dyn T or &DynamicallySizedStruct.

Currently, they can’t be created using the standard library, but ptr_meta can be utilized.

To create fat pointers with ptr_meta, each tag needs a Metadata type which is either usize (for DSTs) or (). A trait is needed to abstract above sized or unsized types. This is done by MaybeDynSized.

§Multiboot2 Requirements

All tags must be 8-byte aligned. The actual payload of tags may be followed by padding zeroes to fill the gap until the next alignment boundary, if necessary. These zeroes are not reflected in the tag’s size, but for Rust, must be reflected in the type’s memory allocation.

§Rustc Requirements

The required allocation space that Rust uses for types is a multiple of the alignment. This means that if we cast between byte slices and specific types, Rust doesn’t just see the “trimmed down actual payload” defined by struct members, but also any necessary, but hidden, padding bytes. If we don’t guarantee the correct is not the case, for example we cast the bytes from a &[u8; 15] to an 8-byte aligned struct, Miri will complain as it expects &[u8; 16].

See https://doc.rust-lang.org/reference/type-layout.html for information.

Further, this also means that we can’t cast references to smaller structs to bigger ones. Also, once we construct a Box on the heap and construct it using the new_boxed helper, we must ensure that the default Layout for the underlying type equals the one we manually used for the allocation.

§Parsing and Casting

The general idea of parsing is that the lifetime of the original byte slice propagates through to references of target types.

First, we need byte slices which are guaranteed to be aligned and are a multiple of the alignment. We have BytesRef for that. With that, we can create a DynSizedStructure. This is a rusty type that owns all the bytes it owns, according to the size reported by its header. Using this type and with the help of MaybeDynSized, we can call DynSizedStructure::cast to cast this to arbitrary sized or unsized struct types fulfilling the corresponding requirements.

This way, one can create nice rusty structs modeling the structure of the tags, and we only need a single “complicated” type, namely DynSizedStructure.

§Iterating Tags

To iterate over the tags of a structure, use TagIter.

§Memory Guarantees and Safety Promises

For the parsing and construction of Multiboot2 structures, the alignment and necessary padding bytes as discussed above are guaranteed. When types are constructed, they return Results with appropriate error types. If during runtime something goes wrong, for example due to malformed tags, panics guarantee that no UB will happen.

§No Public API

Not meant as stable public API for others outside Multiboot2.

Modules§

test_utils
Various test utilities.

Structs§

BytesRef
Wraps a byte slice representing a Multiboot2 structure including an optional terminating padding, if necessary.
DynSizedStructure
An C ABI-compatible dynamically sized type with a common sized Header and a dynamic amount of bytes without hidden implicit padding.
TagIter
Iterates over the tags (modelled by DynSizedStructure) of the underlying byte slice. Each tag is expected to have the same common Header.

Enums§

MemoryError
Errors that may occur when working with memory.

Constants§

ALIGNMENT
The alignment of all Multiboot2 data structures.

Traits§

Header
A sized header type for DynSizedStructure.
MaybeDynSized
A trait to abstract sized and unsized structures (DSTs). It enables casting a DynSizedStructure to sized or unsized structures using DynSizedStructure::cast.
Tag
Extension of MaybeDynSized for Tags.

Functions§

clone_dyn
Clones a MaybeDynSized by calling new_boxed.
increase_to_alignment
Increases the given size to the next alignment boundary, if it is not a multiple of the alignment yet.
new_boxed
Creates a new tag implementing MaybeDynSized on the heap.