Skip to content

Commit e3a5159

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

File tree

3 files changed

+336
-180
lines changed

3 files changed

+336
-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: 156 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,91 @@ 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+
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);
176220

177-
Self::build_add_bytes(&mut data, &EndHeaderTag::new().struct_as_bytes(), true);
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+
for _ in 0..bytes.capacity() - bytes.len() {
224+
bytes.push(0);
225+
}
178226

179-
data
227+
assert_eq!(
228+
alloc_ptr,
229+
bytes.as_ptr(),
230+
"Vector was reallocated. Alignment of header probably broken!"
231+
);
232+
assert_eq!(
233+
bytes[0..offset].iter().sum::<u8>(),
234+
0,
235+
"The offset to alignment area should be zero."
236+
);
237+
238+
// Construct a box from a vec without `into_boxed_slice`. The latter
239+
// calls `shrink` on the allocator, which might reallocate this memory.
240+
// We don't want that!
241+
let bytes = bytes.leak();
242+
let bytes = unsafe { Box::from_raw(bytes) };
243+
244+
assert_eq!(bytes.len(), alloc_len);
245+
246+
HeaderBytes { offset, bytes }
180247
}
181248

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

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

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

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

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

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

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

0 commit comments

Comments
 (0)