Skip to main content

Crate multiboot2_common

Crate multiboot2_common 

Source
Expand description

Common helpers for the multiboot2 and multiboot2-header crates.

§Features and no_std Compatibility

This crate is always no_std. The alloc feature enables heap-allocation helpers. The default builder feature enables alloc for consistency with the two consuming crates. Disable default features for allocator-free parsing.

§Value-add

The main value-add of this crate is to abstract away the parsing and construction of Multiboot2 structures. This is more complex than it may sound at first due to the difficulties listed below. It also provides tag iteration.

The abstractions provided by this crate serve as the base for the following related structures:

  • multiboot2:
    • boot information
    • boot information header (the fixed-size beginning of boot information)
    • boot information tags
    • boot information tag header (the fixed-size beginning of a tag)
  • multiboot2-header:
    • Multiboot2 header
    • basic header (the fixed-size beginning of a Multiboot2 header)
    • header tags
    • header tag header (the fixed-size beginning 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

The design choice to have ABI-compatible Rust types in multiboot2 and multiboot2-header mainly influenced the requirements and difficulties. These obstacles, in turn, influenced the design. The outcome is intended to provide a convenient, idiomatic Rust interface.

§Architecture Diagrams

The figures in the README (currently not embeddable in lib.rs unfortunately) provide 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 Rust 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 themselves dynamically sized structures. Their sizes and numbers of elements are known only at runtime.

A final [u8] field in the structs is the most direct Rust representation. 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 some Multiboot2 structures (tags) look 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 as ABI-compatible types that represent all of 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 built at runtime through the same types used for parsing.

§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 over sized and 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 hidden padding bytes. If we do not account for that padding, for example by casting bytes from a &[u8; 15] to an 8-byte-aligned struct, Miri will report an error because Rust expects the allocation to cover 16 bytes.

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

Further, this means that we can’t cast references to smaller structs to larger ones. Once we construct a Box using the new_boxed helper, we must also 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 type covers exactly the bytes reported by its header. 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 Rust 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

The parsing and construction APIs preserve the alignment and padding guarantees discussed above. Parsing APIs report malformed input with appropriate error types. Construction APIs establish the same invariants and may panic when their documented preconditions are violated. Neither malformed input nor a failed invariant may cause undefined behavior.

§Stability

This crate primarily supports multiboot2 and multiboot2-header. Its public API may evolve with their internals and is not intended as an independent stable abstraction.

Structs§

BytesRef
Wraps a byte slice representing a Multiboot2 structure including an optional terminating padding, if necessary.
DynSizedStructure
A 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 with the corresponding ABI guarantees.

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.
validate_tag_sequence
Validates a sequence of padded Multiboot2 (header) tags.