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:
BootInformationHeaderimplementingHeaderBootInformationwrappingDynSizedStructuretype TagIter<'a> = multiboot2_common::TagIter<'a, TagHeader>(TagIter)TagHeaderimplementingHeader- Structs for each tag, each implementing
MaybeDynSized
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§
- Bytes
Ref - Wraps a byte slice representing a Multiboot2 structure including an optional terminating padding, if necessary.
- DynSized
Structure - A C ABI-compatible dynamically sized type with a common sized
Headerand 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 commonHeaderwith the corresponding ABI guarantees.
Enums§
- Memory
Error - 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. - Maybe
DynSized - A trait to abstract sized and unsized structures (DSTs). It enables
casting a
DynSizedStructureto sized or unsized structures usingDynSizedStructure::cast. - Tag
- Extension of
MaybeDynSizedfor Tags.
Functions§
- clone_
dyn - Clones a
MaybeDynSizedby callingnew_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
MaybeDynSizedon the heap. - validate_
tag_ sequence - Validates a sequence of padded Multiboot2 (header) tags.