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:
BootInformationHeader
implementingHeader
BootInformation
wrappingDynSizedStructure
type TagIter<'a> = multiboot2_common::TagIter<'a, TagHeader>
(TagIter
)TagHeader
implementingHeader
- 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
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§
- Bytes
Ref - Wraps a byte slice representing a Multiboot2 structure including an optional terminating padding, if necessary.
- DynSized
Structure - 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 commonHeader
.
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
DynSizedStructure
to sized or unsized structures usingDynSizedStructure::cast
. - Tag
- Extension of
MaybeDynSized
for Tags.
Functions§
- clone_
dyn - Clones a
MaybeDynSized
by 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
MaybeDynSized
on the heap.