Skip to content

Commit e9d04dd

Browse files
committed
builder: use new abstraction to guarantee alignment
1 parent cb4a10b commit e9d04dd

File tree

3 files changed

+233
-93
lines changed

3 files changed

+233
-93
lines changed

multiboot2-header/Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- **BREAKING** renamed `MULTIBOOT2_HEADER_MAGIC` to `MAGIC`
88
- **BREAKING** renamed `Multiboot2HeaderBuilder` to `HeaderBuilder`
99
- **BREAKING** renamed `from_addr` to `load`. The function now consumes a ptr.
10+
- **BREAKING** `HeaderBuilder::build` now returns a value of type `HeaderBytes`
1011
- added the optional `unstable` feature (requires nightly)
1112
- implement `core::error::Error` for `LoadError`
1213

multiboot2-header/src/builder/header.rs

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,40 @@ use crate::{
88
EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag,
99
ModuleAlignHeaderTag, Multiboot2BasicHeader, RelocatableHeaderTag,
1010
};
11+
use alloc::boxed::Box;
1112
use alloc::vec::Vec;
1213
use core::mem::size_of;
14+
use core::ops::Deref;
15+
16+
/// Holds the raw bytes of a boot information built with [`HeaderBuilder`]
17+
/// on the heap. The bytes returned by [`HeaderBytes::as_bytes`] are
18+
/// guaranteed to be properly aligned.
19+
#[derive(Clone, Debug)]
20+
pub struct HeaderBytes {
21+
// Offset into the bytes where the header starts. This is necessary to
22+
// guarantee alignment at the moment.
23+
offset: usize,
24+
bytes: Box<[u8]>,
25+
}
26+
27+
impl HeaderBytes {
28+
/// Returns the bytes. They are guaranteed to be correctly aligned.
29+
pub fn as_bytes(&self) -> &[u8] {
30+
let slice = &self.bytes[self.offset..];
31+
// At this point, the alignment is guaranteed. If not, something is
32+
// broken fundamentally.
33+
assert_eq!(slice.as_ptr().align_offset(8), 0);
34+
slice
35+
}
36+
}
37+
38+
impl Deref for HeaderBytes {
39+
type Target = [u8];
40+
41+
fn deref(&self) -> &Self::Target {
42+
self.as_bytes()
43+
}
44+
}
1345

1446
/// Builder to construct a valid Multiboot2 header dynamically at runtime.
1547
/// The tags will appear in the order of their corresponding enumeration,
@@ -61,16 +93,11 @@ impl HeaderBuilder {
6193
/// easily calculate the size of a Multiboot2 header, where
6294
/// all the tags are 8-byte aligned.
6395
const fn size_or_up_aligned(size: usize) -> usize {
64-
let remainder = size % 8;
65-
if remainder == 0 {
66-
size
67-
} else {
68-
size + 8 - remainder
69-
}
96+
(size + 7) & !7
7097
}
7198

72-
/// Returns the expected length of the Multiboot2 header,
73-
/// when the `build()`-method gets called.
99+
/// Returns the expected length of the Multiboot2 header, when the
100+
/// [`Self::build]-method gets called.
74101
pub fn expected_len(&self) -> usize {
75102
let base_len = size_of::<Multiboot2BasicHeader>();
76103
// size_or_up_aligned not required, because basic header length is 16 and the
@@ -115,8 +142,12 @@ impl HeaderBuilder {
115142
}
116143

117144
/// Adds the bytes of a tag to the final Multiboot2 header byte vector.
118-
/// Align should be true for all tags except the end tag.
119145
fn build_add_bytes(dest: &mut Vec<u8>, source: &[u8], is_end_tag: bool) {
146+
let vec_next_write_ptr = unsafe { dest.as_ptr().add(dest.len()) };
147+
// At this point, the alignment is guaranteed. If not, something is
148+
// broken fundamentally.
149+
assert_eq!(vec_next_write_ptr.align_offset(8), 0);
150+
120151
dest.extend(source);
121152
if !is_end_tag {
122153
let size = source.len();
@@ -128,9 +159,22 @@ impl HeaderBuilder {
128159
}
129160

130161
/// Constructs the bytes for a valid Multiboot2 header with the given properties.
131-
/// The bytes can be casted to a Multiboot2 structure.
132-
pub fn build(mut self) -> Vec<u8> {
133-
let mut data = Vec::new();
162+
pub fn build(mut self) -> HeaderBytes {
163+
// We allocate more than necessary so that we can ensure an correct
164+
// alignment within this data.
165+
let mut data = Vec::<u8>::with_capacity(self.expected_len() + 7);
166+
// Pointer to check that no relocation happened.
167+
let alloc_ptr = data.as_ptr();
168+
169+
// As long as there is no nice way in stable Rust to guarantee the
170+
// alignment of a vector, I add zero bytes at the beginning and the
171+
// header might not start at the start of the allocation.
172+
//
173+
// Unfortunately, it is not possible to reliably test this in a unit
174+
// test as long as the allocator_api feature is not stable.
175+
// Due to my manual testing, however, it works.
176+
let offset = data.as_ptr().align_offset(8);
177+
data.extend([0].repeat(offset));
134178

135179
Self::build_add_bytes(
136180
&mut data,
@@ -176,7 +220,28 @@ impl HeaderBuilder {
176220

177221
Self::build_add_bytes(&mut data, &EndHeaderTag::new().struct_as_bytes(), true);
178222

179-
data
223+
assert_eq!(
224+
alloc_ptr,
225+
data.as_ptr(),
226+
"Vector was reallocated. Alignment of header probably broken!"
227+
);
228+
assert_eq!(
229+
data[0..offset].iter().sum::<u8>(),
230+
0,
231+
"The offset to alignment area should be zero."
232+
);
233+
234+
let boxed = data.into_boxed_slice();
235+
assert_eq!(
236+
boxed.as_ptr(),
237+
alloc_ptr,
238+
"Memory was reallocated. Alignment of header probably broken!"
239+
);
240+
241+
HeaderBytes {
242+
offset,
243+
bytes: boxed,
244+
}
180245
}
181246

182247
// clippy thinks this can be a const fn but the compiler denies it

0 commit comments

Comments
 (0)