|
15 | 15 | //! - header structure (whole)
|
16 | 16 | //! - header tags
|
17 | 17 | //!
|
18 |
| -//! # The Problem / Difficulties |
| 18 | +//! # Solved Problem & Difficulties Along the Way |
| 19 | +//! |
| 20 | +//! Firstly, the design choice to have ABI-compatible rusty types influenced the |
| 21 | +//! requirements and difficulties along the way. They, on the other side, |
| 22 | +//! influenced the design. The outcome is what we perceive as the optimal rusty |
| 23 | +//! and convenient solution. |
19 | 24 | //!
|
20 | 25 | //! ## Multiboot2 Structures
|
21 | 26 | //!
|
|
34 | 39 | //!
|
35 | 40 | //! Note that these structures can also be nested. So for example, the
|
36 | 41 | //! Multiboot2 boot information contains Multiboot2 tags, and the Multiboot2
|
37 |
| -//! header contains Multiboot2 header tags - both are itself dynamic structures. |
| 42 | +//! header contains Multiboot2 header tags - both are itself **dynamically |
| 43 | +//! sized** structures. This means, you can know the size (and amount of |
| 44 | +//! elements) **only at runtime!** |
38 | 45 | //!
|
39 | 46 | //! A final `[u8]` field in the structs is the most rusty way to model this.
|
40 | 47 | //! However, this makes the type a Dynamically Sized Type (DST). To create
|
41 | 48 | //! references to these types from a byte slice, one needs fat pointers. They
|
42 | 49 | //! are a language feature currently not constructable with stable Rust.
|
43 | 50 | //! Luckily, we can utilize [`ptr_meta`].
|
44 | 51 | //!
|
45 |
| -//! ## Dynamic and Sized Structs |
| 52 | +//! Figure 1 in the [README](https://crates.io/crates/multiboot2-common) |
| 53 | +//! (currently not embeddable in lib.rs unfortunately) provides an overview of |
| 54 | +//! Multiboot2 structures. |
| 55 | +//! |
| 56 | +//! ## Dynamic and Sized Structs in Rust |
46 | 57 | //!
|
47 | 58 | //! Note that we also have structures (tags) in Multiboot2 that looks like this:
|
48 | 59 | //!
|
|
68 | 79 | //! }
|
69 | 80 | //! ```
|
70 | 81 | //!
|
71 |
| -//! ## Fat Pointer Requirements |
| 82 | +//! ## Chosen Design |
| 83 | +//! |
| 84 | +//! The overall common abstractions needed to solve the problems mentioned in |
| 85 | +//! this section are also mainly influenced by the fact that the `multiboot2` |
| 86 | +//! and `multiboot2-header` crates use a **zero-copy** design for parsing |
| 87 | +//! the corresponding structures. |
| 88 | +//! |
| 89 | +//! Further, by having **ABI-compatible types** that fully represent the |
| 90 | +//! reality, we can use the same type for parsing **and** for construction, |
| 91 | +//! as modelled in the following simplified example: |
| 92 | +//! |
| 93 | +//! ```rust,ignore |
| 94 | +//! /// ABI-compatible tag for parsing. |
| 95 | +//! pub struct MemoryMapTag { |
| 96 | +//! header: TagHeader, |
| 97 | +//! entry_size: u32, |
| 98 | +//! entry_version: u32, |
| 99 | +//! areas: [MemoryArea], |
| 100 | +//! } |
| 101 | +//! |
| 102 | +//! impl MemoryMapTag { |
| 103 | +//! // We can also create an ABI-compatible structure of that type. |
| 104 | +//! pub fn new(areas: &[MemoryArea]) -> Box<Self> { |
| 105 | +//! // omitted |
| 106 | +//! } |
| 107 | +//! } |
| 108 | +//! ``` |
| 109 | +//! |
| 110 | +//! Hence, the structures can also be build at runtime. This is what we |
| 111 | +//! consider **idiomatic and rusty**. |
| 112 | +//! |
| 113 | +//! ## Creating Fat Pointers with [`ptr_meta`] |
72 | 114 | //!
|
73 | 115 | //! To create fat pointers with [`ptr_meta`], each tag needs a `Metadata` type
|
74 | 116 | //! which is either `usize` (for DSTs) or `()`. A trait is needed to abstract
|
75 | 117 | //! above sized or unsized types.
|
76 | 118 | //!
|
77 | 119 | //! ## Multiboot2 Requirements
|
78 | 120 | //!
|
79 |
| -//! All tags must be 8-byte aligned. Space between multiple tags may be |
80 |
| -//! filled with zeroes if necessary. These zeroes are not reflected in the |
81 |
| -//! previous tag's size. |
| 121 | +//! All tags must be 8-byte aligned. The actual payload of tags may be followed |
| 122 | +//! by padding zeroes to fill the gap until the next alignment boundary, if |
| 123 | +//! necessary. These zeroes are not reflected in the tag's size, but for Rust, |
| 124 | +//! must be reflected in the memory allocation size. |
82 | 125 | //!
|
83 | 126 | //! ## Rustc Requirements
|
84 | 127 | //!
|
85 |
| -//! The allocation space that Rust requires for types is a multiple of the |
| 128 | +//! The required allocation space that Rust uses for types is a multiple of the |
86 | 129 | //! alignment. This means that if we cast between byte slices and specific
|
87 |
| -//! types, Rust doesn't just see the size reported by the header but also |
88 |
| -//! any necessary padding bytes. If this is not the case, for example we |
89 |
| -//! cast to a structure from a `&[u8; 15]`, Miri will complain as it expects |
90 |
| -//! `&[u8; 16]` |
| 130 | +//! types, Rust doesn't just see the "trimmed down actual payload" defined by |
| 131 | +//! struct members, but also any necessary, but hidden, padding bytes. If we |
| 132 | +//! don't guarantee the correct is not the case, for example we cast the bytes |
| 133 | +//! from a `&[u8; 15]` to an 8-byte aligned struct, Miri will complain as it |
| 134 | +//! expects `&[u8; 16]`. |
91 | 135 | //!
|
92 | 136 | //! See <https://doc.rust-lang.org/reference/type-layout.html> for information.
|
93 | 137 | //!
|
94 |
| -//! # Provided Abstractions |
| 138 | +//! Further, this also means that we can't cast references to smaller structs |
| 139 | +//! to bigger ones. Also, once we construct a `Box` on the heap and construct |
| 140 | +//! it using the [`new_boxed`] helper, we must ensure that the default |
| 141 | +//! [`Layout`] for the underlying type equals the one we manually used for the |
| 142 | +//! allocation. |
| 143 | +//! |
| 144 | +//! # Architecture & Provided Abstractions |
| 145 | +//! |
| 146 | +//! Figure 2 in the [README](https://crates.io/crates/multiboot2-common) |
| 147 | +//! (currently not embeddable in lib.rs unfortunately) provides an overview of |
| 148 | +//! the parsing of Multiboot2 structures and how the definitions from this |
| 149 | +//! crate are used. |
95 | 150 | //!
|
96 | 151 | //! ## Parsing and Casting
|
97 | 152 | //!
|
|
122 | 177 | //! # No Public API
|
123 | 178 | //!
|
124 | 179 | //! Not meant as stable public API for others outside Multiboot2.
|
| 180 | +//! |
| 181 | +//! [`Layout`]: core::alloc::Layout |
125 | 182 |
|
126 | 183 | #![no_std]
|
127 | 184 | #![cfg_attr(feature = "unstable", feature(error_in_core))]
|
@@ -270,10 +327,17 @@ impl<H: Header> DynSizedStructure<H> {
|
270 | 327 | &self.payload
|
271 | 328 | }
|
272 | 329 |
|
273 |
| - /// Casts the structure tag to a specific [`MaybeDynSized`] implementation which |
274 |
| - /// may be a ZST or DST typed tag. The output type will have the exact same |
275 |
| - /// size as `*self`. The target type must be sufficient for that. If not, |
276 |
| - /// the function will panic. |
| 330 | + /// Performs a memory-safe same-size cast from the base-structure to a |
| 331 | + /// specific [`MaybeDynSized`]. The idea here is to cast the generic |
| 332 | + /// mostly semantic-free version to a specific type with fields that have |
| 333 | + /// a semantic. |
| 334 | + /// |
| 335 | + /// The provided `T` of type [`MaybeDynSized`] might be may be sized type |
| 336 | + /// or DST. This depends on the type. |
| 337 | + /// |
| 338 | + /// # Panic |
| 339 | + /// Panics if base assumptions are violated. For example, the |
| 340 | + /// `T` of type [`MaybeDynSized`] must allow a proper casting to it. |
277 | 341 | ///
|
278 | 342 | /// # Safety
|
279 | 343 | /// This function is safe due to various sanity checks and the overall
|
|
0 commit comments