Skip to content

Commit c3a4e03

Browse files
authored
Merge pull request #156 from rust-osdev/builder-align-8
builder: use new abstraction to guarantee alignment
2 parents 53af759 + e521fd4 commit c3a4e03

File tree

3 files changed

+378
-188
lines changed

3 files changed

+378
-188
lines changed

multiboot2-header/Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
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`
11+
The old builder could produce misaligned structures.
1012
- added the optional `unstable` feature (requires nightly)
1113
- implement `core::error::Error` for `LoadError`
1214

multiboot2-header/src/builder/header.rs

Lines changed: 178 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,41 @@ 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+
structure_len: usize,
25+
bytes: Box<[u8]>,
26+
}
27+
28+
impl HeaderBytes {
29+
/// Returns the bytes. They are guaranteed to be correctly aligned.
30+
pub fn as_bytes(&self) -> &[u8] {
31+
let slice = &self.bytes[self.offset..self.offset + self.structure_len];
32+
// At this point, the alignment is guaranteed. If not, something is
33+
// broken fundamentally.
34+
assert_eq!(slice.as_ptr().align_offset(8), 0);
35+
slice
36+
}
37+
}
38+
39+
impl Deref for HeaderBytes {
40+
type Target = [u8];
41+
42+
fn deref(&self) -> &Self::Target {
43+
self.as_bytes()
44+
}
45+
}
1346

1447
/// Builder to construct a valid Multiboot2 header dynamically at runtime.
1548
/// The tags will appear in the order of their corresponding enumeration,
@@ -61,16 +94,11 @@ impl HeaderBuilder {
6194
/// easily calculate the size of a Multiboot2 header, where
6295
/// all the tags are 8-byte aligned.
6396
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-
}
97+
(size + 7) & !7
7098
}
7199

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

117145
/// 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.
119146
fn build_add_bytes(dest: &mut Vec<u8>, source: &[u8], is_end_tag: bool) {
147+
let vec_next_write_ptr = unsafe { dest.as_ptr().add(dest.len()) };
148+
// At this point, the alignment is guaranteed. If not, something is
149+
// broken fundamentally.
150+
assert_eq!(vec_next_write_ptr.align_offset(8), 0);
151+
120152
dest.extend(source);
121153
if !is_end_tag {
122154
let size = source.len();
@@ -128,55 +160,104 @@ impl HeaderBuilder {
128160
}
129161

130162
/// 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();
163+
pub fn build(mut self) -> HeaderBytes {
164+
const ALIGN: usize = 8;
134165

166+
// PHASE 1/3: Prepare Vector
167+
168+
// We allocate more than necessary so that we can ensure an correct
169+
// alignment within this data.
170+
let expected_len = self.expected_len();
171+
let alloc_len = expected_len + 7;
172+
let mut bytes = Vec::<u8>::with_capacity(alloc_len);
173+
// Pointer to check that no relocation happened.
174+
let alloc_ptr = bytes.as_ptr();
175+
176+
// As long as there is no nice way in stable Rust to guarantee the
177+
// alignment of a vector, I add zero bytes at the beginning and the
178+
// header might not start at the start of the allocation.
179+
//
180+
// Unfortunately, it is not possible to reliably test this in a unit
181+
// test as long as the allocator_api feature is not stable.
182+
// Due to my manual testing, however, it works.
183+
let offset = bytes.as_ptr().align_offset(ALIGN);
184+
bytes.extend([0].repeat(offset));
185+
186+
// -----------------------------------------------
187+
// PHASE 2/3: Add Tags
188+
self.build_add_tags(&mut bytes);
189+
190+
// -----------------------------------------------
191+
// PHASE 3/3: Finalize Vector
192+
193+
// Ensure that the vector has the same length as it's capacity. This is
194+
// important so that miri doesn't complain that the boxed memory is
195+
// smaller than the original allocation.
196+
bytes.extend([0].repeat(bytes.capacity() - bytes.len()));
197+
198+
assert_eq!(
199+
alloc_ptr,
200+
bytes.as_ptr(),
201+
"Vector was reallocated. Alignment of header probably broken!"
202+
);
203+
assert_eq!(
204+
bytes[0..offset].iter().sum::<u8>(),
205+
0,
206+
"The offset to alignment area should be zero."
207+
);
208+
209+
// Construct a box from a vec without `into_boxed_slice`. The latter
210+
// calls `shrink` on the allocator, which might reallocate this memory.
211+
// We don't want that!
212+
let bytes = unsafe { Box::from_raw(bytes.leak()) };
213+
214+
HeaderBytes {
215+
offset,
216+
bytes,
217+
structure_len: expected_len,
218+
}
219+
}
220+
221+
/// Helper method that adds all the tags to the given vector.
222+
fn build_add_tags(&mut self, bytes: &mut Vec<u8>) {
135223
Self::build_add_bytes(
136-
&mut data,
224+
bytes,
137225
// important that we write the correct expected length into the header!
138226
&Multiboot2BasicHeader::new(self.arch, self.expected_len() as u32).struct_as_bytes(),
139227
false,
140228
);
141229

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-
)
230+
if let Some(irs) = self.information_request_tag.clone() {
231+
Self::build_add_bytes(bytes, &irs.build(), false)
148232
}
149233
if let Some(tag) = self.address_tag.as_ref() {
150-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
234+
Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
151235
}
152236
if let Some(tag) = self.entry_tag.as_ref() {
153-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
237+
Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
154238
}
155239
if let Some(tag) = self.console_tag.as_ref() {
156-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
240+
Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
157241
}
158242
if let Some(tag) = self.framebuffer_tag.as_ref() {
159-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
243+
Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
160244
}
161245
if let Some(tag) = self.module_align_tag.as_ref() {
162-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
246+
Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
163247
}
164248
if let Some(tag) = self.efi_bs_tag.as_ref() {
165-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
249+
Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
166250
}
167251
if let Some(tag) = self.efi_32_tag.as_ref() {
168-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
252+
Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
169253
}
170254
if let Some(tag) = self.efi_64_tag.as_ref() {
171-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
255+
Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
172256
}
173257
if let Some(tag) = self.relocatable_tag.as_ref() {
174-
Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
258+
Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
175259
}
176-
177-
Self::build_add_bytes(&mut data, &EndHeaderTag::new().struct_as_bytes(), true);
178-
179-
data
260+
Self::build_add_bytes(bytes, &EndHeaderTag::new().struct_as_bytes(), true);
180261
}
181262

182263
// clippy thinks this can be a const fn but the compiler denies it
@@ -245,62 +326,71 @@ mod tests {
245326

246327
#[test]
247328
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);
252-
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);
269-
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);
279-
280-
println!("builder: {:#?}", builder);
281-
println!("expected_len: {} bytes", builder.expected_len());
282-
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-
);
329+
// Step 1/2: Build Header
330+
let (mb2_hdr_data, expected_len) = {
331+
let builder = HeaderBuilder::new(HeaderTagISA::I386);
332+
// Multiboot2 basic header + end tag
333+
let mut expected_len = 16 + 8;
334+
assert_eq!(builder.expected_len(), expected_len);
335+
336+
// add information request tag
337+
let ifr_builder = InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required)
338+
.add_irs(&[
339+
MbiTagType::EfiMmap,
340+
MbiTagType::Cmdline,
341+
MbiTagType::ElfSections,
342+
]);
343+
let ifr_tag_size_with_padding = ifr_builder.expected_len() + 4;
344+
assert_eq!(
345+
ifr_tag_size_with_padding % 8,
346+
0,
347+
"the length of the IFR tag with padding must be a multiple of 8"
348+
);
349+
expected_len += ifr_tag_size_with_padding;
350+
let builder = builder.information_request_tag(ifr_builder);
351+
assert_eq!(builder.expected_len(), expected_len);
352+
353+
let builder = builder.relocatable_tag(RelocatableHeaderTag::new(
354+
HeaderTagFlag::Required,
355+
0x1337,
356+
0xdeadbeef,
357+
4096,
358+
RelocatableHeaderTagPreference::None,
359+
));
360+
expected_len += 0x18;
361+
assert_eq!(builder.expected_len(), expected_len);
299362

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();*/
363+
println!("builder: {:#?}", builder);
364+
println!("expected_len: {} bytes", builder.expected_len());
365+
366+
(builder.build(), expected_len)
367+
};
368+
369+
assert_eq!(mb2_hdr_data.as_bytes().len(), expected_len);
370+
371+
// Step 2/2: Test the built Header
372+
{
373+
let mb2_hdr = mb2_hdr_data.as_ptr().cast();
374+
let mb2_hdr = unsafe { Multiboot2Header::load(mb2_hdr) }
375+
.expect("the generated header to be loadable");
376+
println!("{:#?}", mb2_hdr);
377+
assert_eq!(
378+
mb2_hdr.relocatable_tag().unwrap().flags(),
379+
HeaderTagFlag::Required
380+
);
381+
assert_eq!(mb2_hdr.relocatable_tag().unwrap().min_addr(), 0x1337);
382+
assert_eq!(mb2_hdr.relocatable_tag().unwrap().max_addr(), 0xdeadbeef);
383+
assert_eq!(mb2_hdr.relocatable_tag().unwrap().align(), 4096);
384+
assert_eq!(
385+
mb2_hdr.relocatable_tag().unwrap().preference(),
386+
RelocatableHeaderTagPreference::None
387+
);
388+
389+
/* you can write the binary to a file and a tool such as crate "bootinfo"
390+
will be able to fully parse the MB2 header
391+
let mut file = std::file::File::create("mb2_hdr.bin").unwrap();
392+
use std::io::Write;
393+
file.write_all(mb2_hdr_data.as_slice()).unwrap();*/
394+
}
305395
}
306396
}

0 commit comments

Comments
 (0)