Skip to content

Commit b17b09c

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

File tree

3 files changed

+334
-180
lines changed

3 files changed

+334
-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: 155 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,90 @@ 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 = bytes.leak();
241+
let bytes = unsafe { Box::from_raw(bytes) };
176242

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

179-
data
245+
HeaderBytes { offset, bytes }
180246
}
181247

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

246312
#[test]
247313
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);
314+
// Step 1/2: Build Header
315+
let mb2_hdr_data = {
316+
let builder = HeaderBuilder::new(HeaderTagISA::I386);
317+
// Multiboot2 basic header + end tag
318+
let mut expected_len = 16 + 8;
319+
assert_eq!(builder.expected_len(), expected_len);
252320

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);
321+
// add information request tag
322+
let ifr_builder = InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required)
323+
.add_irs(&[
324+
MbiTagType::EfiMmap,
325+
MbiTagType::Cmdline,
326+
MbiTagType::ElfSections,
327+
]);
328+
let ifr_tag_size_with_padding = ifr_builder.expected_len() + 4;
329+
assert_eq!(
330+
ifr_tag_size_with_padding % 8,
331+
0,
332+
"the length of the IFR tag with padding must be a multiple of 8"
333+
);
334+
expected_len += ifr_tag_size_with_padding;
335+
let builder = builder.information_request_tag(ifr_builder);
336+
assert_eq!(builder.expected_len(), expected_len);
269337

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);
338+
let builder = builder.relocatable_tag(RelocatableHeaderTag::new(
339+
HeaderTagFlag::Required,
340+
0x1337,
341+
0xdeadbeef,
342+
4096,
343+
RelocatableHeaderTagPreference::None,
344+
));
345+
expected_len += 0x18;
346+
assert_eq!(builder.expected_len(), expected_len);
279347

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

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-
);
351+
builder.build()
352+
};
353+
354+
// Step 2/2: Test the built Header
355+
{
356+
let mb2_hdr = mb2_hdr_data.as_ptr().cast();
357+
let mb2_hdr = unsafe { Multiboot2Header::load(mb2_hdr) }
358+
.expect("the generated header to be loadable");
359+
println!("{:#?}", mb2_hdr);
360+
assert_eq!(
361+
mb2_hdr.relocatable_tag().unwrap().flags(),
362+
HeaderTagFlag::Required
363+
);
364+
assert_eq!(mb2_hdr.relocatable_tag().unwrap().min_addr(), 0x1337);
365+
assert_eq!(mb2_hdr.relocatable_tag().unwrap().max_addr(), 0xdeadbeef);
366+
assert_eq!(mb2_hdr.relocatable_tag().unwrap().align(), 4096);
367+
assert_eq!(
368+
mb2_hdr.relocatable_tag().unwrap().preference(),
369+
RelocatableHeaderTagPreference::None
370+
);
299371

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();*/
372+
/* you can write the binary to a file and a tool such as crate "bootinfo"
373+
will be able to fully parse the MB2 header
374+
let mut file = std::file::File::create("mb2_hdr.bin").unwrap();
375+
use std::io::Write;
376+
file.write_all(mb2_hdr_data.as_slice()).unwrap();*/
377+
}
305378
}
306379
}

0 commit comments

Comments
 (0)