Skip to content

Commit 7fe8a13

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

File tree

3 files changed

+332
-180
lines changed

3 files changed

+332
-180
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: 154 additions & 82 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,55 +159,89 @@ 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(self) -> HeaderBytes {
163+
const ALIGN: usize = 8;
164+
165+
// We allocate more than necessary so that we can ensure an correct
166+
// alignment within this data.
167+
let alloc_len = self.expected_len() + 7;
168+
let mut bytes = Vec::<u8>::with_capacity(alloc_len);
169+
// Pointer to check that no relocation happened.
170+
let alloc_ptr = bytes.as_ptr();
171+
172+
// As long as there is no nice way in stable Rust to guarantee the
173+
// alignment of a vector, I add zero bytes at the beginning and the
174+
// header might not start at the start of the allocation.
175+
//
176+
// Unfortunately, it is not possible to reliably test this in a unit
177+
// test as long as the allocator_api feature is not stable.
178+
// Due to my manual testing, however, it works.
179+
let offset = bytes.as_ptr().align_offset(ALIGN);
180+
bytes.extend([0].repeat(offset));
134181

135182
Self::build_add_bytes(
136-
&mut data,
183+
&mut bytes,
137184
// important that we write the correct expected length into the header!
138185
&Multiboot2BasicHeader::new(self.arch, self.expected_len() as u32).struct_as_bytes(),
139186
false,
140187
);
141188

142-
if self.information_request_tag.is_some() {
143-
Self::build_add_bytes(
144-
&mut data,
145-
&self.information_request_tag.take().unwrap().build(),
146-
false,
147-
)
189+
if let Some(irs) = self.information_request_tag {
190+
Self::build_add_bytes(&mut bytes, &irs.build(), false)
148191
}
149192
if let Some(tag) = self.address_tag.as_ref() {
150-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
193+
Self::build_add_bytes(&mut bytes, &tag.struct_as_bytes(), false)
151194
}
152195
if let Some(tag) = self.entry_tag.as_ref() {
153-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
196+
Self::build_add_bytes(&mut bytes, &tag.struct_as_bytes(), false)
154197
}
155198
if let Some(tag) = self.console_tag.as_ref() {
156-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
199+
Self::build_add_bytes(&mut bytes, &tag.struct_as_bytes(), false)
157200
}
158201
if let Some(tag) = self.framebuffer_tag.as_ref() {
159-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
202+
Self::build_add_bytes(&mut bytes, &tag.struct_as_bytes(), false)
160203
}
161204
if let Some(tag) = self.module_align_tag.as_ref() {
162-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
205+
Self::build_add_bytes(&mut bytes, &tag.struct_as_bytes(), false)
163206
}
164207
if let Some(tag) = self.efi_bs_tag.as_ref() {
165-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
208+
Self::build_add_bytes(&mut bytes, &tag.struct_as_bytes(), false)
166209
}
167210
if let Some(tag) = self.efi_32_tag.as_ref() {
168-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
211+
Self::build_add_bytes(&mut bytes, &tag.struct_as_bytes(), false)
169212
}
170213
if let Some(tag) = self.efi_64_tag.as_ref() {
171-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
214+
Self::build_add_bytes(&mut bytes, &tag.struct_as_bytes(), false)
172215
}
173216
if let Some(tag) = self.relocatable_tag.as_ref() {
174-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
217+
Self::build_add_bytes(&mut bytes, &tag.struct_as_bytes(), false)
175218
}
219+
Self::build_add_bytes(&mut bytes, &EndHeaderTag::new().struct_as_bytes(), true);
220+
221+
// Ensure that the vector has the same length as it's capacity. This is
222+
// important so that miri doesn't complain that the boxed memory is
223+
// smaller than the original allocation.
224+
bytes.extend([0].repeat(bytes.capacity() - bytes.len()));
225+
226+
assert_eq!(
227+
alloc_ptr,
228+
bytes.as_ptr(),
229+
"Vector was reallocated. Alignment of header probably broken!"
230+
);
231+
assert_eq!(
232+
bytes[0..offset].iter().sum::<u8>(),
233+
0,
234+
"The offset to alignment area should be zero."
235+
);
236+
237+
// Construct a box from a vec without `into_boxed_slice`. The latter
238+
// calls `shrink` on the allocator, which might reallocate this memory.
239+
// We don't want that!
240+
let bytes = unsafe { Box::from_raw(bytes.leak()) };
176241

177-
Self::build_add_bytes(&mut data, &EndHeaderTag::new().struct_as_bytes(), true);
242+
assert_eq!(bytes.len(), alloc_len);
178243

179-
data
244+
HeaderBytes { offset, bytes }
180245
}
181246

182247
// clippy thinks this can be a const fn but the compiler denies it
@@ -245,62 +310,69 @@ mod tests {
245310

246311
#[test]
247312
fn test_builder() {
248-
let builder = HeaderBuilder::new(HeaderTagISA::I386);
249-
// Multiboot2 basic header + end tag
250-
let mut expected_len = 16 + 8;
251-
assert_eq!(builder.expected_len(), expected_len);
313+
// Step 1/2: Build Header
314+
let mb2_hdr_data = {
315+
let builder = HeaderBuilder::new(HeaderTagISA::I386);
316+
// Multiboot2 basic header + end tag
317+
let mut expected_len = 16 + 8;
318+
assert_eq!(builder.expected_len(), expected_len);
252319

253-
// add information request tag
254-
let ifr_builder =
255-
InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required).add_irs(&[
256-
MbiTagType::EfiMmap,
257-
MbiTagType::Cmdline,
258-
MbiTagType::ElfSections,
259-
]);
260-
let ifr_tag_size_with_padding = ifr_builder.expected_len() + 4;
261-
assert_eq!(
262-
ifr_tag_size_with_padding % 8,
263-
0,
264-
"the length of the IFR tag with padding must be a multiple of 8"
265-
);
266-
expected_len += ifr_tag_size_with_padding;
267-
let builder = builder.information_request_tag(ifr_builder);
268-
assert_eq!(builder.expected_len(), expected_len);
320+
// add information request tag
321+
let ifr_builder = InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required)
322+
.add_irs(&[
323+
MbiTagType::EfiMmap,
324+
MbiTagType::Cmdline,
325+
MbiTagType::ElfSections,
326+
]);
327+
let ifr_tag_size_with_padding = ifr_builder.expected_len() + 4;
328+
assert_eq!(
329+
ifr_tag_size_with_padding % 8,
330+
0,
331+
"the length of the IFR tag with padding must be a multiple of 8"
332+
);
333+
expected_len += ifr_tag_size_with_padding;
334+
let builder = builder.information_request_tag(ifr_builder);
335+
assert_eq!(builder.expected_len(), expected_len);
269336

270-
let builder = builder.relocatable_tag(RelocatableHeaderTag::new(
271-
HeaderTagFlag::Required,
272-
0x1337,
273-
0xdeadbeef,
274-
4096,
275-
RelocatableHeaderTagPreference::None,
276-
));
277-
expected_len += 0x18;
278-
assert_eq!(builder.expected_len(), expected_len);
337+
let builder = builder.relocatable_tag(RelocatableHeaderTag::new(
338+
HeaderTagFlag::Required,
339+
0x1337,
340+
0xdeadbeef,
341+
4096,
342+
RelocatableHeaderTagPreference::None,
343+
));
344+
expected_len += 0x18;
345+
assert_eq!(builder.expected_len(), expected_len);
279346

280-
println!("builder: {:#?}", builder);
281-
println!("expected_len: {} bytes", builder.expected_len());
347+
println!("builder: {:#?}", builder);
348+
println!("expected_len: {} bytes", builder.expected_len());
282349

283-
let mb2_hdr_data = builder.build();
284-
let mb2_hdr = mb2_hdr_data.as_ptr().cast();
285-
let mb2_hdr = unsafe { Multiboot2Header::load(mb2_hdr) }
286-
.expect("the generated header to be loadable");
287-
println!("{:#?}", mb2_hdr);
288-
assert_eq!(
289-
mb2_hdr.relocatable_tag().unwrap().flags(),
290-
HeaderTagFlag::Required
291-
);
292-
assert_eq!(mb2_hdr.relocatable_tag().unwrap().min_addr(), 0x1337);
293-
assert_eq!(mb2_hdr.relocatable_tag().unwrap().max_addr(), 0xdeadbeef);
294-
assert_eq!(mb2_hdr.relocatable_tag().unwrap().align(), 4096);
295-
assert_eq!(
296-
mb2_hdr.relocatable_tag().unwrap().preference(),
297-
RelocatableHeaderTagPreference::None
298-
);
350+
builder.build()
351+
};
352+
353+
// Step 2/2: Test the built Header
354+
{
355+
let mb2_hdr = mb2_hdr_data.as_ptr().cast();
356+
let mb2_hdr = unsafe { Multiboot2Header::load(mb2_hdr) }
357+
.expect("the generated header to be loadable");
358+
println!("{:#?}", mb2_hdr);
359+
assert_eq!(
360+
mb2_hdr.relocatable_tag().unwrap().flags(),
361+
HeaderTagFlag::Required
362+
);
363+
assert_eq!(mb2_hdr.relocatable_tag().unwrap().min_addr(), 0x1337);
364+
assert_eq!(mb2_hdr.relocatable_tag().unwrap().max_addr(), 0xdeadbeef);
365+
assert_eq!(mb2_hdr.relocatable_tag().unwrap().align(), 4096);
366+
assert_eq!(
367+
mb2_hdr.relocatable_tag().unwrap().preference(),
368+
RelocatableHeaderTagPreference::None
369+
);
299370

300-
/* you can write the binary to a file and a tool such as crate "bootinfo"
301-
will be able to fully parse the MB2 header
302-
let mut file = std::file::File::create("mb2_hdr.bin").unwrap();
303-
use std::io::Write;
304-
file.write_all(mb2_hdr_data.as_slice()).unwrap();*/
371+
/* you can write the binary to a file and a tool such as crate "bootinfo"
372+
will be able to fully parse the MB2 header
373+
let mut file = std::file::File::create("mb2_hdr.bin").unwrap();
374+
use std::io::Write;
375+
file.write_all(mb2_hdr_data.as_slice()).unwrap();*/
376+
}
305377
}
306378
}

0 commit comments

Comments
 (0)