@@ -8,8 +8,41 @@ 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+ 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