diff --git a/multiboot2-header/Changelog.md b/multiboot2-header/Changelog.md index a52dadca..29b17500 100644 --- a/multiboot2-header/Changelog.md +++ b/multiboot2-header/Changelog.md @@ -7,6 +7,8 @@ - **BREAKING** renamed `MULTIBOOT2_HEADER_MAGIC` to `MAGIC` - **BREAKING** renamed `Multiboot2HeaderBuilder` to `HeaderBuilder` - **BREAKING** renamed `from_addr` to `load`. The function now consumes a ptr. +- **BREAKING** `HeaderBuilder::build` now returns a value of type `HeaderBytes` + The old builder could produce misaligned structures. - added the optional `unstable` feature (requires nightly) - implement `core::error::Error` for `LoadError` diff --git a/multiboot2-header/src/builder/header.rs b/multiboot2-header/src/builder/header.rs index 7bf2d8f4..d15c542e 100644 --- a/multiboot2-header/src/builder/header.rs +++ b/multiboot2-header/src/builder/header.rs @@ -8,8 +8,41 @@ use crate::{ EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, ModuleAlignHeaderTag, Multiboot2BasicHeader, RelocatableHeaderTag, }; +use alloc::boxed::Box; use alloc::vec::Vec; use core::mem::size_of; +use core::ops::Deref; + +/// Holds the raw bytes of a boot information built with [`HeaderBuilder`] +/// on the heap. The bytes returned by [`HeaderBytes::as_bytes`] are +/// guaranteed to be properly aligned. +#[derive(Clone, Debug)] +pub struct HeaderBytes { + // Offset into the bytes where the header starts. This is necessary to + // guarantee alignment at the moment. + offset: usize, + structure_len: usize, + bytes: Box<[u8]>, +} + +impl HeaderBytes { + /// Returns the bytes. They are guaranteed to be correctly aligned. + pub fn as_bytes(&self) -> &[u8] { + let slice = &self.bytes[self.offset..self.offset + self.structure_len]; + // At this point, the alignment is guaranteed. If not, something is + // broken fundamentally. + assert_eq!(slice.as_ptr().align_offset(8), 0); + slice + } +} + +impl Deref for HeaderBytes { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.as_bytes() + } +} /// Builder to construct a valid Multiboot2 header dynamically at runtime. /// The tags will appear in the order of their corresponding enumeration, @@ -61,16 +94,11 @@ impl HeaderBuilder { /// easily calculate the size of a Multiboot2 header, where /// all the tags are 8-byte aligned. const fn size_or_up_aligned(size: usize) -> usize { - let remainder = size % 8; - if remainder == 0 { - size - } else { - size + 8 - remainder - } + (size + 7) & !7 } - /// Returns the expected length of the Multiboot2 header, - /// when the `build()`-method gets called. + /// Returns the expected length of the Multiboot2 header, when the + /// [`Self::build`]-method gets called. pub fn expected_len(&self) -> usize { let base_len = size_of::(); // size_or_up_aligned not required, because basic header length is 16 and the @@ -115,8 +143,12 @@ impl HeaderBuilder { } /// Adds the bytes of a tag to the final Multiboot2 header byte vector. - /// Align should be true for all tags except the end tag. fn build_add_bytes(dest: &mut Vec, source: &[u8], is_end_tag: bool) { + let vec_next_write_ptr = unsafe { dest.as_ptr().add(dest.len()) }; + // At this point, the alignment is guaranteed. If not, something is + // broken fundamentally. + assert_eq!(vec_next_write_ptr.align_offset(8), 0); + dest.extend(source); if !is_end_tag { let size = source.len(); @@ -128,55 +160,104 @@ impl HeaderBuilder { } /// Constructs the bytes for a valid Multiboot2 header with the given properties. - /// The bytes can be casted to a Multiboot2 structure. - pub fn build(mut self) -> Vec { - let mut data = Vec::new(); + pub fn build(mut self) -> HeaderBytes { + const ALIGN: usize = 8; + // PHASE 1/3: Prepare Vector + + // We allocate more than necessary so that we can ensure an correct + // alignment within this data. + let expected_len = self.expected_len(); + let alloc_len = expected_len + 7; + let mut bytes = Vec::::with_capacity(alloc_len); + // Pointer to check that no relocation happened. + let alloc_ptr = bytes.as_ptr(); + + // As long as there is no nice way in stable Rust to guarantee the + // alignment of a vector, I add zero bytes at the beginning and the + // header might not start at the start of the allocation. + // + // Unfortunately, it is not possible to reliably test this in a unit + // test as long as the allocator_api feature is not stable. + // Due to my manual testing, however, it works. + let offset = bytes.as_ptr().align_offset(ALIGN); + bytes.extend([0].repeat(offset)); + + // ----------------------------------------------- + // PHASE 2/3: Add Tags + self.build_add_tags(&mut bytes); + + // ----------------------------------------------- + // PHASE 3/3: Finalize Vector + + // Ensure that the vector has the same length as it's capacity. This is + // important so that miri doesn't complain that the boxed memory is + // smaller than the original allocation. + bytes.extend([0].repeat(bytes.capacity() - bytes.len())); + + assert_eq!( + alloc_ptr, + bytes.as_ptr(), + "Vector was reallocated. Alignment of header probably broken!" + ); + assert_eq!( + bytes[0..offset].iter().sum::(), + 0, + "The offset to alignment area should be zero." + ); + + // Construct a box from a vec without `into_boxed_slice`. The latter + // calls `shrink` on the allocator, which might reallocate this memory. + // We don't want that! + let bytes = unsafe { Box::from_raw(bytes.leak()) }; + + HeaderBytes { + offset, + bytes, + structure_len: expected_len, + } + } + + /// Helper method that adds all the tags to the given vector. + fn build_add_tags(&mut self, bytes: &mut Vec) { Self::build_add_bytes( - &mut data, + bytes, // important that we write the correct expected length into the header! &Multiboot2BasicHeader::new(self.arch, self.expected_len() as u32).struct_as_bytes(), false, ); - if self.information_request_tag.is_some() { - Self::build_add_bytes( - &mut data, - &self.information_request_tag.take().unwrap().build(), - false, - ) + if let Some(irs) = self.information_request_tag.clone() { + Self::build_add_bytes(bytes, &irs.build(), false) } if let Some(tag) = self.address_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.entry_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.console_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.framebuffer_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.module_align_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.efi_bs_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.efi_32_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.efi_64_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.relocatable_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } - - Self::build_add_bytes(&mut data, &EndHeaderTag::new().struct_as_bytes(), true); - - data + Self::build_add_bytes(bytes, &EndHeaderTag::new().struct_as_bytes(), true); } // clippy thinks this can be a const fn but the compiler denies it @@ -245,62 +326,71 @@ mod tests { #[test] fn test_builder() { - let builder = HeaderBuilder::new(HeaderTagISA::I386); - // Multiboot2 basic header + end tag - let mut expected_len = 16 + 8; - assert_eq!(builder.expected_len(), expected_len); - - // add information request tag - let ifr_builder = - InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required).add_irs(&[ - MbiTagType::EfiMmap, - MbiTagType::Cmdline, - MbiTagType::ElfSections, - ]); - let ifr_tag_size_with_padding = ifr_builder.expected_len() + 4; - assert_eq!( - ifr_tag_size_with_padding % 8, - 0, - "the length of the IFR tag with padding must be a multiple of 8" - ); - expected_len += ifr_tag_size_with_padding; - let builder = builder.information_request_tag(ifr_builder); - assert_eq!(builder.expected_len(), expected_len); - - let builder = builder.relocatable_tag(RelocatableHeaderTag::new( - HeaderTagFlag::Required, - 0x1337, - 0xdeadbeef, - 4096, - RelocatableHeaderTagPreference::None, - )); - expected_len += 0x18; - assert_eq!(builder.expected_len(), expected_len); - - println!("builder: {:#?}", builder); - println!("expected_len: {} bytes", builder.expected_len()); - - let mb2_hdr_data = builder.build(); - let mb2_hdr = mb2_hdr_data.as_ptr().cast(); - let mb2_hdr = unsafe { Multiboot2Header::load(mb2_hdr) } - .expect("the generated header to be loadable"); - println!("{:#?}", mb2_hdr); - assert_eq!( - mb2_hdr.relocatable_tag().unwrap().flags(), - HeaderTagFlag::Required - ); - assert_eq!(mb2_hdr.relocatable_tag().unwrap().min_addr(), 0x1337); - assert_eq!(mb2_hdr.relocatable_tag().unwrap().max_addr(), 0xdeadbeef); - assert_eq!(mb2_hdr.relocatable_tag().unwrap().align(), 4096); - assert_eq!( - mb2_hdr.relocatable_tag().unwrap().preference(), - RelocatableHeaderTagPreference::None - ); + // Step 1/2: Build Header + let (mb2_hdr_data, expected_len) = { + let builder = HeaderBuilder::new(HeaderTagISA::I386); + // Multiboot2 basic header + end tag + let mut expected_len = 16 + 8; + assert_eq!(builder.expected_len(), expected_len); + + // add information request tag + let ifr_builder = InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required) + .add_irs(&[ + MbiTagType::EfiMmap, + MbiTagType::Cmdline, + MbiTagType::ElfSections, + ]); + let ifr_tag_size_with_padding = ifr_builder.expected_len() + 4; + assert_eq!( + ifr_tag_size_with_padding % 8, + 0, + "the length of the IFR tag with padding must be a multiple of 8" + ); + expected_len += ifr_tag_size_with_padding; + let builder = builder.information_request_tag(ifr_builder); + assert_eq!(builder.expected_len(), expected_len); + + let builder = builder.relocatable_tag(RelocatableHeaderTag::new( + HeaderTagFlag::Required, + 0x1337, + 0xdeadbeef, + 4096, + RelocatableHeaderTagPreference::None, + )); + expected_len += 0x18; + assert_eq!(builder.expected_len(), expected_len); - /* you can write the binary to a file and a tool such as crate "bootinfo" - will be able to fully parse the MB2 header - let mut file = std::file::File::create("mb2_hdr.bin").unwrap(); - use std::io::Write; - file.write_all(mb2_hdr_data.as_slice()).unwrap();*/ + println!("builder: {:#?}", builder); + println!("expected_len: {} bytes", builder.expected_len()); + + (builder.build(), expected_len) + }; + + assert_eq!(mb2_hdr_data.as_bytes().len(), expected_len); + + // Step 2/2: Test the built Header + { + let mb2_hdr = mb2_hdr_data.as_ptr().cast(); + let mb2_hdr = unsafe { Multiboot2Header::load(mb2_hdr) } + .expect("the generated header to be loadable"); + println!("{:#?}", mb2_hdr); + assert_eq!( + mb2_hdr.relocatable_tag().unwrap().flags(), + HeaderTagFlag::Required + ); + assert_eq!(mb2_hdr.relocatable_tag().unwrap().min_addr(), 0x1337); + assert_eq!(mb2_hdr.relocatable_tag().unwrap().max_addr(), 0xdeadbeef); + assert_eq!(mb2_hdr.relocatable_tag().unwrap().align(), 4096); + assert_eq!( + mb2_hdr.relocatable_tag().unwrap().preference(), + RelocatableHeaderTagPreference::None + ); + + /* you can write the binary to a file and a tool such as crate "bootinfo" + will be able to fully parse the MB2 header + let mut file = std::file::File::create("mb2_hdr.bin").unwrap(); + use std::io::Write; + file.write_all(mb2_hdr_data.as_slice()).unwrap();*/ + } } } diff --git a/multiboot2/src/builder/information.rs b/multiboot2/src/builder/information.rs index 46c5ca17..b3efa781 100644 --- a/multiboot2/src/builder/information.rs +++ b/multiboot2/src/builder/information.rs @@ -10,6 +10,38 @@ use crate::{ use alloc::boxed::Box; use alloc::vec::Vec; use core::mem::size_of; +use core::ops::Deref; + +/// Holds the raw bytes of a boot information built with [`InformationBuilder`] +/// on the heap. The bytes returned by [`BootInformationBytes::as_bytes`] are +/// guaranteed to be properly aligned. +#[derive(Clone, Debug)] +pub struct BootInformationBytes { + // Offset into the bytes where the MBI starts. This is necessary to + // guarantee alignment at the moment. + offset: usize, + structure_len: usize, + bytes: Box<[u8]>, +} + +impl BootInformationBytes { + /// Returns the bytes. They are guaranteed to be correctly aligned. + pub fn as_bytes(&self) -> &[u8] { + let slice = &self.bytes[self.offset..self.offset + self.structure_len]; + // At this point, the alignment is guaranteed. If not, something is + // broken fundamentally. + assert_eq!(slice.as_ptr().align_offset(8), 0); + slice + } +} + +impl Deref for BootInformationBytes { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.as_bytes() + } +} /// Builder to construct a valid Multiboot2 information dynamically at runtime. /// The tags will appear in the order of their corresponding enumeration, @@ -19,7 +51,7 @@ pub struct InformationBuilder { basic_memory_info_tag: Option, boot_loader_name_tag: Option>, command_line_tag: Option>, - efi_boot_services_not_exited: Option, + efi_boot_services_not_exited_tag: Option, efi_image_handle32: Option, efi_image_handle64: Option, efi_memory_map_tag: Option>, @@ -28,8 +60,8 @@ pub struct InformationBuilder { image_load_addr: Option, memory_map_tag: Option>, module_tags: Vec>, - efisdt32: Option, - efisdt64: Option, + efisdt32_tag: Option, + efisdt64_tag: Option, rsdp_v1_tag: Option, rsdp_v2_tag: Option, smbios_tags: Vec>, @@ -41,9 +73,9 @@ impl InformationBuilder { basic_memory_info_tag: None, boot_loader_name_tag: None, command_line_tag: None, - efisdt32: None, - efisdt64: None, - efi_boot_services_not_exited: None, + efisdt32_tag: None, + efisdt64_tag: None, + efi_boot_services_not_exited_tag: None, efi_image_handle32: None, efi_image_handle64: None, efi_memory_map_tag: None, @@ -63,16 +95,11 @@ impl InformationBuilder { /// easily calculate the size of a Multiboot2 header, where /// all the tags are 8-byte aligned. const fn size_or_up_aligned(size: usize) -> usize { - let remainder = size % 8; - if remainder == 0 { - size - } else { - size + 8 - remainder - } + (size + 7) & !7 } - /// Returns the expected length of the Multiboot2 header, - /// when the `build()`-method gets called. + /// Returns the expected length of the boot information, when the + /// [`Self::build`]-method gets called. pub fn expected_len(&self) -> usize { let base_len = size_of::(); // size_or_up_aligned not required, because length is 16 and the @@ -88,13 +115,13 @@ impl InformationBuilder { if let Some(tag) = &self.command_line_tag { len += Self::size_or_up_aligned(tag.byte_size()) } - if let Some(tag) = &self.efisdt32 { + if let Some(tag) = &self.efisdt32_tag { len += Self::size_or_up_aligned(tag.byte_size()) } - if let Some(tag) = &self.efisdt64 { + if let Some(tag) = &self.efisdt64_tag { len += Self::size_or_up_aligned(tag.byte_size()) } - if let Some(tag) = &self.efi_boot_services_not_exited { + if let Some(tag) = &self.efi_boot_services_not_exited_tag { len += Self::size_or_up_aligned(tag.byte_size()) } if let Some(tag) = &self.efi_image_handle32 { @@ -136,8 +163,12 @@ impl InformationBuilder { } /// Adds the bytes of a tag to the final Multiboot2 information byte vector. - /// Align should be true for all tags except the end tag. fn build_add_bytes(dest: &mut Vec, source: &[u8], is_end_tag: bool) { + let vec_next_write_ptr = unsafe { dest.as_ptr().add(dest.len()) }; + // At this point, the alignment is guaranteed. If not, something is + // broken fundamentally. + assert_eq!(vec_next_write_ptr.align_offset(8), 0); + dest.extend(source); if !is_end_tag { let size = source.len(); @@ -149,72 +180,126 @@ impl InformationBuilder { } /// Constructs the bytes for a valid Multiboot2 information with the given properties. - /// The bytes can be casted to a Multiboot2 structure. - pub fn build(self) -> Vec { - let mut data = Vec::new(); + pub fn build(self) -> BootInformationBytes { + const ALIGN: usize = 8; + + // PHASE 1/3: Prepare Vector + + // We allocate more than necessary so that we can ensure an correct + // alignment within this data. + let expected_len = self.expected_len(); + let alloc_len = expected_len + 7; + let mut bytes = Vec::::with_capacity(alloc_len); + // Pointer to check that no relocation happened. + let alloc_ptr = bytes.as_ptr(); + + // As long as there is no nice way in stable Rust to guarantee the + // alignment of a vector, I add zero bytes at the beginning and the MBI + // might not start at the start of the allocation. + // + // Unfortunately, it is not possible to reliably test this in a unit + // test as long as the allocator_api feature is not stable. + // Due to my manual testing, however, it works. + let offset = bytes.as_ptr().align_offset(ALIGN); + bytes.extend([0].repeat(offset)); + + // ----------------------------------------------- + // PHASE 2/3: Add Tags + self.build_add_tags(&mut bytes); + + // ----------------------------------------------- + // PHASE 3/3: Finalize Vector + + // Ensure that the vector has the same length as it's capacity. This is + // important so that miri doesn't complain that the boxed memory is + // smaller than the original allocation. + bytes.extend([0].repeat(bytes.capacity() - bytes.len())); + + assert_eq!( + alloc_ptr, + bytes.as_ptr(), + "Vector was reallocated. Alignment of MBI probably broken!" + ); + assert_eq!( + bytes[0..offset].iter().sum::(), + 0, + "The offset to alignment area should be zero." + ); + + // Construct a box from a vec without `into_boxed_slice`. The latter + // calls `shrink` on the allocator, which might reallocate this memory. + // We don't want that! + let bytes = unsafe { Box::from_raw(bytes.leak()) }; + assert_eq!(bytes.len(), alloc_len); + + BootInformationBytes { + offset, + bytes, + structure_len: expected_len, + } + } + + /// Helper method that adds all the tags to the given vector. + fn build_add_tags(&self, bytes: &mut Vec) { Self::build_add_bytes( - &mut data, + bytes, // important that we write the correct expected length into the header! &BootInformationHeader::new(self.expected_len() as u32).struct_as_bytes(), false, ); - if let Some(tag) = self.basic_memory_info_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.boot_loader_name_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.command_line_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } - if let Some(tag) = self.efisdt32.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + if let Some(tag) = self.efisdt32_tag.as_ref() { + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } - if let Some(tag) = self.efisdt64.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + if let Some(tag) = self.efisdt64_tag.as_ref() { + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } - if let Some(tag) = self.efi_boot_services_not_exited.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + if let Some(tag) = self.efi_boot_services_not_exited_tag.as_ref() { + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.efi_image_handle32.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.efi_image_handle64.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.efi_memory_map_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.elf_sections_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.framebuffer_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.image_load_addr.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.memory_map_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } - for tag in self.module_tags { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + for tag in &self.module_tags { + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.rsdp_v1_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } if let Some(tag) = self.rsdp_v2_tag.as_ref() { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } - for tag in self.smbios_tags { - Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + for tag in &self.smbios_tags { + Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) } - - Self::build_add_bytes(&mut data, &EndTag::default().struct_as_bytes(), true); - - data + Self::build_add_bytes(bytes, &EndTag::default().struct_as_bytes(), true); } pub fn basic_memory_info_tag(&mut self, basic_memory_info_tag: BasicMemoryInfoTag) { @@ -229,16 +314,16 @@ impl InformationBuilder { self.command_line_tag = Some(command_line_tag); } - pub fn efisdt32(&mut self, efisdt32: EFISdt32Tag) { - self.efisdt32 = Some(efisdt32); + pub fn efisdt32_tag(&mut self, efisdt32: EFISdt32Tag) { + self.efisdt32_tag = Some(efisdt32); } - pub fn efisdt64(&mut self, efisdt64: EFISdt64Tag) { - self.efisdt64 = Some(efisdt64); + pub fn efisdt64_tag(&mut self, efisdt64: EFISdt64Tag) { + self.efisdt64_tag = Some(efisdt64); } - pub fn efi_boot_services_not_exited(&mut self) { - self.efi_boot_services_not_exited = Some(EFIBootServicesNotExitedTag::new()); + pub fn efi_boot_services_not_exited_tag(&mut self) { + self.efi_boot_services_not_exited_tag = Some(EFIBootServicesNotExitedTag::new()); } pub fn efi_image_handle32(&mut self, efi_image_handle32: EFIImageHandle32Tag) { @@ -301,50 +386,63 @@ mod tests { #[test] fn test_builder() { - let mut builder = InformationBuilder::new(); - // Multiboot2 basic information + end tag - let mut expected_len = 8 + 8; - assert_eq!(builder.expected_len(), expected_len); - - // the most simple tag - builder.basic_memory_info_tag(BasicMemoryInfoTag::new(640, 7 * 1024)); - expected_len += 16; - assert_eq!(builder.expected_len(), expected_len); - // a tag that has a dynamic size - builder.command_line_tag(CommandLineTag::new("test")); - expected_len += 8 + 5 + 3; // padding - assert_eq!(builder.expected_len(), expected_len); - // many modules - builder.add_module_tag(ModuleTag::new(0, 1234, "module1")); - expected_len += 16 + 8; - assert_eq!(builder.expected_len(), expected_len); - builder.add_module_tag(ModuleTag::new(5678, 6789, "module2")); - expected_len += 16 + 8; - assert_eq!(builder.expected_len(), expected_len); - - println!("builder: {:#?}", builder); - println!("expected_len: {} bytes", builder.expected_len()); - assert_eq!(builder.expected_len(), expected_len); - - let mb2i_data = builder.build(); - let mb2i = unsafe { BootInformation::load(mb2i_data.as_ptr().cast()) } - .expect("the generated information to be readable"); - println!("{:#?}", mb2i); - assert_eq!(mb2i.basic_memory_info_tag().unwrap().memory_lower(), 640); - assert_eq!( - mb2i.basic_memory_info_tag().unwrap().memory_upper(), - 7 * 1024 - ); - assert_eq!(mb2i.command_line_tag().unwrap().cmdline().unwrap(), "test"); - let mut modules = mb2i.module_tags(); - let module_1 = modules.next().unwrap(); - assert_eq!(module_1.start_address(), 0); - assert_eq!(module_1.end_address(), 1234); - assert_eq!(module_1.cmdline().unwrap(), "module1"); - let module_2 = modules.next().unwrap(); - assert_eq!(module_2.start_address(), 5678); - assert_eq!(module_2.end_address(), 6789); - assert_eq!(module_2.cmdline().unwrap(), "module2"); - assert!(modules.next().is_none()); + // Step 1/2: Build MBI + let (mb2i_data, expected_len) = { + let mut builder = InformationBuilder::new(); + + // Multiboot2 basic information + end tag + let mut expected_len = 8 + 8; + assert_eq!(builder.expected_len(), expected_len); + + // the most simple tag + builder.basic_memory_info_tag(BasicMemoryInfoTag::new(640, 7 * 1024)); + expected_len += 16; + assert_eq!(builder.expected_len(), expected_len); + // a tag that has a dynamic size + builder.command_line_tag(CommandLineTag::new("test")); + expected_len += 8 + 5 + 3; // padding + assert_eq!(builder.expected_len(), expected_len); + // many modules + builder.add_module_tag(ModuleTag::new(0, 1234, "module1")); + expected_len += 16 + 8; + assert_eq!(builder.expected_len(), expected_len); + builder.add_module_tag(ModuleTag::new(5678, 6789, "module2")); + expected_len += 16 + 8; + assert_eq!(builder.expected_len(), expected_len); + + println!("builder: {:#?}", builder); + println!("expected_len: {} bytes", builder.expected_len()); + assert_eq!(builder.expected_len(), expected_len); + + (builder.build(), expected_len) + }; + + assert_eq!(mb2i_data.as_bytes().len(), expected_len); + + // Step 2/2: Test the built MBI + { + let mb2i = unsafe { BootInformation::load(mb2i_data.as_ptr().cast()) } + .expect("generated information should be readable"); + + assert_eq!(mb2i.basic_memory_info_tag().unwrap().memory_lower(), 640); + assert_eq!( + mb2i.basic_memory_info_tag().unwrap().memory_upper(), + 7 * 1024 + ); + assert_eq!(mb2i.command_line_tag().unwrap().cmdline().unwrap(), "test"); + let mut modules = mb2i.module_tags(); + let module_1 = modules.next().unwrap(); + assert_eq!(module_1.start_address(), 0); + assert_eq!(module_1.end_address(), 1234); + assert_eq!(module_1.cmdline().unwrap(), "module1"); + let module_2 = modules.next().unwrap(); + assert_eq!(module_2.start_address(), 5678); + assert_eq!(module_2.end_address(), 6789); + assert_eq!(module_2.cmdline().unwrap(), "module2"); + assert!(modules.next().is_none()); + + // Printing the MBI transitively ensures that a lot of stuff works. + println!("{:#?}", mb2i); + } } }