@@ -8,8 +8,40 @@ 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
+ 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
+ }
13
45
14
46
/// Builder to construct a valid Multiboot2 header dynamically at runtime.
15
47
/// The tags will appear in the order of their corresponding enumeration,
@@ -61,16 +93,11 @@ impl HeaderBuilder {
61
93
/// easily calculate the size of a Multiboot2 header, where
62
94
/// all the tags are 8-byte aligned.
63
95
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
70
97
}
71
98
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.
74
101
pub fn expected_len ( & self ) -> usize {
75
102
let base_len = size_of :: < Multiboot2BasicHeader > ( ) ;
76
103
// size_or_up_aligned not required, because basic header length is 16 and the
@@ -115,8 +142,12 @@ impl HeaderBuilder {
115
142
}
116
143
117
144
/// 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
145
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
+
120
151
dest. extend ( source) ;
121
152
if !is_end_tag {
122
153
let size = source. len ( ) ;
@@ -128,55 +159,90 @@ impl HeaderBuilder {
128
159
}
129
160
130
161
/// 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) ) ;
134
181
135
182
Self :: build_add_bytes (
136
- & mut data ,
183
+ & mut bytes ,
137
184
// important that we write the correct expected length into the header!
138
185
& Multiboot2BasicHeader :: new ( self . arch , self . expected_len ( ) as u32 ) . struct_as_bytes ( ) ,
139
186
false ,
140
187
) ;
141
188
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 )
148
191
}
149
192
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 )
151
194
}
152
195
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 )
154
197
}
155
198
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 )
157
200
}
158
201
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 )
160
203
}
161
204
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 )
163
206
}
164
207
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 )
166
209
}
167
210
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 )
169
212
}
170
213
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 )
172
215
}
173
216
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 )
175
218
}
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 = bytes. leak ( ) ;
241
+ let bytes = unsafe { Box :: from_raw ( bytes) } ;
176
242
177
- Self :: build_add_bytes ( & mut data , & EndHeaderTag :: new ( ) . struct_as_bytes ( ) , true ) ;
243
+ assert_eq ! ( bytes . len ( ) , alloc_len ) ;
178
244
179
- data
245
+ HeaderBytes { offset , bytes }
180
246
}
181
247
182
248
// clippy thinks this can be a const fn but the compiler denies it
@@ -245,62 +311,69 @@ mod tests {
245
311
246
312
#[ test]
247
313
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) ;
314
+ // Step 1/2: Build Header
315
+ let mb2_hdr_data = {
316
+ let builder = HeaderBuilder :: new ( HeaderTagISA :: I386 ) ;
317
+ // Multiboot2 basic header + end tag
318
+ let mut expected_len = 16 + 8 ;
319
+ assert_eq ! ( builder. expected_len( ) , expected_len) ;
252
320
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) ;
321
+ // add information request tag
322
+ let ifr_builder = InformationRequestHeaderTagBuilder :: new ( HeaderTagFlag :: Required )
323
+ . add_irs ( & [
324
+ MbiTagType :: EfiMmap ,
325
+ MbiTagType :: Cmdline ,
326
+ MbiTagType :: ElfSections ,
327
+ ] ) ;
328
+ let ifr_tag_size_with_padding = ifr_builder. expected_len ( ) + 4 ;
329
+ assert_eq ! (
330
+ ifr_tag_size_with_padding % 8 ,
331
+ 0 ,
332
+ "the length of the IFR tag with padding must be a multiple of 8"
333
+ ) ;
334
+ expected_len += ifr_tag_size_with_padding;
335
+ let builder = builder. information_request_tag ( ifr_builder) ;
336
+ assert_eq ! ( builder. expected_len( ) , expected_len) ;
269
337
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) ;
338
+ let builder = builder. relocatable_tag ( RelocatableHeaderTag :: new (
339
+ HeaderTagFlag :: Required ,
340
+ 0x1337 ,
341
+ 0xdeadbeef ,
342
+ 4096 ,
343
+ RelocatableHeaderTagPreference :: None ,
344
+ ) ) ;
345
+ expected_len += 0x18 ;
346
+ assert_eq ! ( builder. expected_len( ) , expected_len) ;
279
347
280
- println ! ( "builder: {:#?}" , builder) ;
281
- println ! ( "expected_len: {} bytes" , builder. expected_len( ) ) ;
348
+ println ! ( "builder: {:#?}" , builder) ;
349
+ println ! ( "expected_len: {} bytes" , builder. expected_len( ) ) ;
282
350
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
- ) ;
351
+ builder. build ( )
352
+ } ;
353
+
354
+ // Step 2/2: Test the built Header
355
+ {
356
+ let mb2_hdr = mb2_hdr_data. as_ptr ( ) . cast ( ) ;
357
+ let mb2_hdr = unsafe { Multiboot2Header :: load ( mb2_hdr) }
358
+ . expect ( "the generated header to be loadable" ) ;
359
+ println ! ( "{:#?}" , mb2_hdr) ;
360
+ assert_eq ! (
361
+ mb2_hdr. relocatable_tag( ) . unwrap( ) . flags( ) ,
362
+ HeaderTagFlag :: Required
363
+ ) ;
364
+ assert_eq ! ( mb2_hdr. relocatable_tag( ) . unwrap( ) . min_addr( ) , 0x1337 ) ;
365
+ assert_eq ! ( mb2_hdr. relocatable_tag( ) . unwrap( ) . max_addr( ) , 0xdeadbeef ) ;
366
+ assert_eq ! ( mb2_hdr. relocatable_tag( ) . unwrap( ) . align( ) , 4096 ) ;
367
+ assert_eq ! (
368
+ mb2_hdr. relocatable_tag( ) . unwrap( ) . preference( ) ,
369
+ RelocatableHeaderTagPreference :: None
370
+ ) ;
299
371
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();*/
372
+ /* you can write the binary to a file and a tool such as crate "bootinfo"
373
+ will be able to fully parse the MB2 header
374
+ let mut file = std::file::File::create("mb2_hdr.bin").unwrap();
375
+ use std::io::Write;
376
+ file.write_all(mb2_hdr_data.as_slice()).unwrap();*/
377
+ }
305
378
}
306
379
}
0 commit comments