@@ -8,8 +8,40 @@ use crate::{
88 EntryAddressHeaderTag , EntryEfi32HeaderTag , EntryEfi64HeaderTag , FramebufferHeaderTag ,
99 ModuleAlignHeaderTag , Multiboot2BasicHeader , RelocatableHeaderTag ,
1010} ;
11+ use alloc:: boxed:: Box ;
1112use alloc:: vec:: Vec ;
1213use 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