@@ -8,8 +8,41 @@ use crate::{
8
8
EntryAddressHeaderTag , EntryEfi32HeaderTag , EntryEfi64HeaderTag , FramebufferHeaderTag ,
9
9
ModuleAlignHeaderTag , Multiboot2BasicHeader , RelocatableHeaderTag ,
10
10
} ;
11
+ use alloc:: boxed:: Box ;
11
12
use alloc:: vec:: Vec ;
12
13
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
+ }
13
46
14
47
/// Builder to construct a valid Multiboot2 header dynamically at runtime.
15
48
/// The tags will appear in the order of their corresponding enumeration,
@@ -61,16 +94,11 @@ impl HeaderBuilder {
61
94
/// easily calculate the size of a Multiboot2 header, where
62
95
/// all the tags are 8-byte aligned.
63
96
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
70
98
}
71
99
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.
74
102
pub fn expected_len ( & self ) -> usize {
75
103
let base_len = size_of :: < Multiboot2BasicHeader > ( ) ;
76
104
// size_or_up_aligned not required, because basic header length is 16 and the
@@ -115,8 +143,12 @@ impl HeaderBuilder {
115
143
}
116
144
117
145
/// 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.
119
146
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
+
120
152
dest. extend ( source) ;
121
153
if !is_end_tag {
122
154
let size = source. len ( ) ;
@@ -128,55 +160,104 @@ impl HeaderBuilder {
128
160
}
129
161
130
162
/// 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 ;
134
165
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 > ) {
135
223
Self :: build_add_bytes (
136
- & mut data ,
224
+ bytes ,
137
225
// important that we write the correct expected length into the header!
138
226
& Multiboot2BasicHeader :: new ( self . arch , self . expected_len ( ) as u32 ) . struct_as_bytes ( ) ,
139
227
false ,
140
228
) ;
141
229
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 )
148
232
}
149
233
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 )
151
235
}
152
236
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 )
154
238
}
155
239
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 )
157
241
}
158
242
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 )
160
244
}
161
245
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 )
163
247
}
164
248
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 )
166
250
}
167
251
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 )
169
253
}
170
254
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 )
172
256
}
173
257
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 )
175
259
}
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 ) ;
180
261
}
181
262
182
263
// clippy thinks this can be a const fn but the compiler denies it
@@ -245,62 +326,71 @@ mod tests {
245
326
246
327
#[ test]
247
328
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) ;
299
362
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
+ }
305
395
}
306
396
}
0 commit comments