Parcourir la source

multiboot2: Implement building the multiboot2 information

Niklas Sombert il y a 2 ans
Parent
commit
122fc9ce03

+ 22 - 1
multiboot2/src/boot_loader_name.rs

@@ -4,7 +4,10 @@ use core::mem::size_of;
 use core::str::Utf8Error;
 
 #[cfg(feature = "builder")]
-use {crate::builder::boxed_dst_tag, alloc::boxed::Box, alloc::vec::Vec};
+use {
+    crate::builder::boxed_dst_tag, crate::builder::traits::StructAsBytes, alloc::boxed::Box,
+    alloc::vec::Vec,
+};
 
 const METADATA_SIZE: usize = size_of::<TagTypeId>() + size_of::<u32>();
 
@@ -63,6 +66,13 @@ impl TagTrait for BootLoaderNameTag {
     }
 }
 
+#[cfg(feature = "builder")]
+impl StructAsBytes for BootLoaderNameTag {
+    fn byte_size(&self) -> usize {
+        self.size.try_into().unwrap()
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use crate::{BootLoaderNameTag, Tag, TagType};
@@ -95,4 +105,15 @@ mod tests {
         assert_eq!({ tag.typ }, TagType::BootLoaderName);
         assert_eq!(tag.name().expect("must be valid UTF-8"), MSG);
     }
+
+    /// Test to generate a tag from a given string.
+    #[test]
+    #[cfg(feature = "builder")]
+    fn test_build_str() {
+        use crate::builder::traits::StructAsBytes;
+
+        let tag = BootLoaderNameTag::new(MSG);
+        let bytes = tag.struct_as_bytes();
+        assert_eq!(bytes, get_bytes());
+    }
 }

+ 140 - 2
multiboot2/src/builder/information.rs

@@ -1,12 +1,13 @@
 //! Exports item [`Multiboot2InformationBuilder`].
 use crate::builder::traits::StructAsBytes;
 use crate::{
-    BasicMemoryInfoTag, BootLoaderNameTag, CommandLineTag, ElfSectionsTag, FramebufferTag,
-    MemoryMapTag, ModuleTag,
+    BasicMemoryInfoTag, BootInformationInner, BootLoaderNameTag, CommandLineTag, ElfSectionsTag,
+    EndTag, FramebufferTag, MemoryMapTag, ModuleTag,
 };
 
 use alloc::boxed::Box;
 use alloc::vec::Vec;
+use core::mem::size_of;
 
 /// Builder to construct a valid Multiboot2 information dynamically at runtime.
 /// The tags will appear in the order of their corresponding enumeration,
@@ -35,6 +36,105 @@ impl Multiboot2InformationBuilder {
         }
     }
 
+    /// Returns the size, if the value is a multiple of 8 or returns
+    /// the next number that is a multiple of 8. With this, one can
+    /// 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
+        }
+    }
+
+    /// Returns the expected length of the Multiboot2 header,
+    /// when the `build()`-method gets called.
+    pub fn expected_len(&self) -> usize {
+        let base_len = size_of::<BootInformationInner>();
+        // size_or_up_aligned not required, because length is 16 and the
+        // begin is 8 byte aligned => first tag automatically 8 byte aligned
+        let mut len = Self::size_or_up_aligned(base_len);
+        if let Some(tag) = &self.basic_memory_info_tag {
+            // we use size_or_up_aligned, because each tag will start at an 8 byte aligned address
+            len += Self::size_or_up_aligned(tag.byte_size())
+        }
+        if let Some(tag) = &self.boot_loader_name_tag {
+            len += Self::size_or_up_aligned(tag.byte_size())
+        }
+        if let Some(tag) = &self.command_line_tag {
+            len += Self::size_or_up_aligned(tag.byte_size())
+        }
+        if let Some(tag) = &self.elf_sections_tag {
+            len += Self::size_or_up_aligned(tag.byte_size())
+        }
+        if let Some(tag) = &self.framebuffer_tag {
+            len += Self::size_or_up_aligned(tag.byte_size())
+        }
+        if let Some(tag) = &self.memory_map_tag {
+            len += Self::size_or_up_aligned(tag.byte_size())
+        }
+        for tag in &self.module_tags {
+            len += Self::size_or_up_aligned(tag.byte_size())
+        }
+        // only here size_or_up_aligned is not important, because it is the last tag
+        len += size_of::<EndTag>();
+        len
+    }
+
+    /// 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<u8>, source: &[u8], is_end_tag: bool) {
+        dest.extend(source);
+        if !is_end_tag {
+            let size = source.len();
+            let size_to_8_align = Self::size_or_up_aligned(size);
+            let size_to_8_align_diff = size_to_8_align - size;
+            // fill zeroes so that next data block is 8-byte aligned
+            dest.extend([0].repeat(size_to_8_align_diff));
+        }
+    }
+
+    /// 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<u8> {
+        let mut data = Vec::new();
+
+        Self::build_add_bytes(
+            &mut data,
+            // important that we write the correct expected length into the header!
+            &BootInformationInner::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)
+        }
+        if let Some(tag) = self.boot_loader_name_tag.as_ref() {
+            Self::build_add_bytes(&mut data, &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)
+        }
+        if let Some(tag) = self.elf_sections_tag.as_ref() {
+            Self::build_add_bytes(&mut data, &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)
+        }
+        if let Some(tag) = self.memory_map_tag.as_ref() {
+            Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
+        }
+        for tag in self.module_tags {
+            Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
+        }
+
+        Self::build_add_bytes(&mut data, &EndTag::default().struct_as_bytes(), true);
+
+        data
+    }
+
     pub fn basic_memory_info_tag(&mut self, basic_memory_info_tag: BasicMemoryInfoTag) {
         self.basic_memory_info_tag = Some(basic_memory_info_tag)
     }
@@ -63,3 +163,41 @@ impl Multiboot2InformationBuilder {
         self.module_tags.push(module_tag);
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::builder::information::Multiboot2InformationBuilder;
+    use crate::{load, BasicMemoryInfoTag, CommandLineTag, ModuleTag};
+
+    #[test]
+    fn test_size_or_up_aligned() {
+        assert_eq!(0, Multiboot2InformationBuilder::size_or_up_aligned(0));
+        assert_eq!(8, Multiboot2InformationBuilder::size_or_up_aligned(1));
+        assert_eq!(8, Multiboot2InformationBuilder::size_or_up_aligned(8));
+        assert_eq!(16, Multiboot2InformationBuilder::size_or_up_aligned(9));
+    }
+
+    #[test]
+    fn test_size_builder() {
+        let mut builder = Multiboot2InformationBuilder::new();
+        // Multiboot2 basic information + end tag
+        let 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));
+        // a tag that has a dynamic size
+        builder.command_line_tag(CommandLineTag::new("test"));
+        // many modules
+        builder.add_module_tag(ModuleTag::new(0, 1234, "module1"));
+        builder.add_module_tag(ModuleTag::new(5678, 6789, "module2"));
+
+        println!("builder: {:#?}", builder);
+        println!("expected_len: {} bytes", builder.expected_len());
+
+        let mb2i_data = builder.build();
+        let mb2i_addr = mb2i_data.as_ptr() as usize;
+        let mb2i = unsafe { load(mb2i_addr) };
+        println!("{:#?}", mb2i);
+    }
+}

+ 5 - 8
multiboot2/src/builder/traits.rs

@@ -1,16 +1,13 @@
 //! Module for the helper trait [`StructAsBytes`].
 
-use core::mem::size_of;
-
 /// Trait for all tags that helps to create a byte array from the tag.
 /// Useful in builders to construct a byte vector that
 /// represents the Multiboot2 information with all its tags.
-pub(crate) trait StructAsBytes: Sized {
-    /// Returns the size in bytes of the struct, as known during compile
-    /// time. This doesn't use read the "size" field of tags.
-    fn byte_size(&self) -> usize {
-        size_of::<Self>()
-    }
+pub(crate) trait StructAsBytes {
+    /// Returns the size in bytes of the struct.
+    /// This can be either the "size" field of tags or the compile-time size
+    /// (if known).
+    fn byte_size(&self) -> usize;
 
     /// Returns a byte pointer to the begin of the struct.
     fn as_ptr(&self) -> *const u8 {

+ 24 - 1
multiboot2/src/command_line.rs

@@ -1,12 +1,17 @@
 //! Module for [CommandLineTag].
 
 use crate::{Tag, TagTrait, TagType, TagTypeId};
+
+use core::convert::TryInto;
 use core::fmt::{Debug, Formatter};
 use core::mem;
 use core::str;
 
 #[cfg(feature = "builder")]
-use {crate::builder::boxed_dst_tag, alloc::boxed::Box, alloc::vec::Vec};
+use {
+    crate::builder::boxed_dst_tag, crate::builder::traits::StructAsBytes, alloc::boxed::Box,
+    alloc::vec::Vec,
+};
 
 pub(crate) const METADATA_SIZE: usize = mem::size_of::<TagTypeId>() + mem::size_of::<u32>();
 
@@ -71,6 +76,13 @@ impl TagTrait for CommandLineTag {
     }
 }
 
+#[cfg(feature = "builder")]
+impl StructAsBytes for CommandLineTag {
+    fn byte_size(&self) -> usize {
+        self.size.try_into().unwrap()
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use crate::{CommandLineTag, Tag, TagType};
@@ -103,4 +115,15 @@ mod tests {
         assert_eq!({ tag.typ }, TagType::Cmdline);
         assert_eq!(tag.command_line().expect("must be valid UTF-8"), MSG);
     }
+
+    /// Test to generate a tag from a given string.
+    #[test]
+    #[cfg(feature = "builder")]
+    fn test_build_str() {
+        use crate::builder::traits::StructAsBytes;
+
+        let tag = CommandLineTag::new(MSG);
+        let bytes = tag.struct_as_bytes();
+        assert_eq!(bytes, get_bytes());
+    }
 }

+ 8 - 1
multiboot2/src/elf_sections.rs

@@ -5,7 +5,7 @@ use core::mem::size_of;
 use core::str::Utf8Error;
 
 #[cfg(feature = "builder")]
-use {crate::builder::boxed_dst_tag, alloc::boxed::Box};
+use {crate::builder::boxed_dst_tag, crate::builder::traits::StructAsBytes, alloc::boxed::Box};
 
 const METADATA_SIZE: usize = size_of::<TagTypeId>() + 4 * size_of::<u32>();
 
@@ -76,6 +76,13 @@ impl TagTrait for ElfSectionsTag {
     }
 }
 
+#[cfg(feature = "builder")]
+impl StructAsBytes for ElfSectionsTag {
+    fn byte_size(&self) -> usize {
+        self.size.try_into().unwrap()
+    }
+}
+
 /// An iterator over some ELF sections.
 #[derive(Clone)]
 pub struct ElfSectionIter {

+ 19 - 2
multiboot2/src/framebuffer.rs

@@ -149,6 +149,13 @@ impl TagTrait for FramebufferTag {
     }
 }
 
+#[cfg(feature = "builder")]
+impl StructAsBytes for FramebufferTag {
+    fn byte_size(&self) -> usize {
+        self.size.try_into().unwrap()
+    }
+}
+
 /// Helper struct for [`FramebufferType`].
 #[derive(Debug, PartialEq, Eq)]
 #[repr(u8)]
@@ -226,7 +233,12 @@ pub struct FramebufferField {
     pub size: u8,
 }
 
-impl StructAsBytes for FramebufferField {}
+#[cfg(feature = "builder")]
+impl StructAsBytes for FramebufferField {
+    fn byte_size(&self) -> usize {
+        size_of::<Self>()
+    }
+}
 
 /// A framebuffer color descriptor in the palette.
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -250,4 +262,9 @@ pub struct UnknownFramebufferType(u8);
 #[cfg(feature = "unstable")]
 impl core::error::Error for UnknownFramebufferType {}
 
-impl StructAsBytes for FramebufferColor {}
+#[cfg(feature = "builder")]
+impl StructAsBytes for FramebufferColor {
+    fn byte_size(&self) -> usize {
+        size_of::<Self>()
+    }
+}

+ 18 - 0
multiboot2/src/lib.rs

@@ -47,6 +47,8 @@ pub use ptr_meta::Pointee;
 
 use crate::framebuffer::UnknownFramebufferType;
 pub use boot_loader_name::BootLoaderNameTag;
+#[cfg(feature = "builder")]
+use builder::traits::StructAsBytes;
 pub use command_line::CommandLineTag;
 pub use efi::{EFIImageHandle32, EFIImageHandle64, EFISdt32, EFISdt64};
 pub use elf_sections::{
@@ -203,6 +205,22 @@ struct BootInformationInner {
     _reserved: u32,
 }
 
+impl BootInformationInner {
+    fn new(total_size: u32) -> Self {
+        Self {
+            total_size,
+            _reserved: 0,
+        }
+    }
+}
+
+#[cfg(feature = "builder")]
+impl StructAsBytes for BootInformationInner {
+    fn byte_size(&self) -> usize {
+        core::mem::size_of::<Self>()
+    }
+}
+
 impl BootInformation {
     /// Get the start address of the boot info.
     pub fn start_address(&self) -> usize {

+ 20 - 1
multiboot2/src/memory_map.rs

@@ -69,6 +69,13 @@ impl TagTrait for MemoryMapTag {
     }
 }
 
+#[cfg(feature = "builder")]
+impl StructAsBytes for MemoryMapTag {
+    fn byte_size(&self) -> usize {
+        self.size.try_into().unwrap()
+    }
+}
+
 /// A memory area entry descriptor.
 #[derive(Debug, Clone)]
 #[repr(C)]
@@ -111,7 +118,12 @@ impl MemoryArea {
     }
 }
 
-impl StructAsBytes for MemoryArea {}
+#[cfg(feature = "builder")]
+impl StructAsBytes for MemoryArea {
+    fn byte_size(&self) -> usize {
+        mem::size_of::<Self>()
+    }
+}
 
 /// An enum of possible reported region types.
 /// Inside the Multiboot2 spec this is kind of hidden
@@ -203,6 +215,13 @@ impl BasicMemoryInfoTag {
     }
 }
 
+#[cfg(feature = "builder")]
+impl StructAsBytes for BasicMemoryInfoTag {
+    fn byte_size(&self) -> usize {
+        mem::size_of::<Self>()
+    }
+}
+
 /// EFI memory map as per EFI specification.
 #[derive(Debug)]
 #[repr(C)]

+ 11 - 1
multiboot2/src/module.rs

@@ -5,7 +5,10 @@ use core::mem::size_of;
 use core::str::Utf8Error;
 
 #[cfg(feature = "builder")]
-use {crate::builder::boxed_dst_tag, alloc::boxed::Box, alloc::vec::Vec};
+use {
+    crate::builder::boxed_dst_tag, crate::builder::traits::StructAsBytes, alloc::boxed::Box,
+    alloc::vec::Vec,
+};
 
 const METADATA_SIZE: usize = size_of::<TagTypeId>() + 3 * size_of::<u32>();
 
@@ -70,6 +73,13 @@ impl TagTrait for ModuleTag {
     }
 }
 
+#[cfg(feature = "builder")]
+impl StructAsBytes for ModuleTag {
+    fn byte_size(&self) -> usize {
+        self.size.try_into().unwrap()
+    }
+}
+
 impl Debug for ModuleTag {
     fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
         f.debug_struct("ModuleTag")

+ 9 - 0
multiboot2/src/tag_type.rs

@@ -5,6 +5,8 @@
 //! - [`TagTypeId`]
 //! - [`TagType`]
 //! - [`Tag`]
+#[cfg(feature = "builder")]
+use crate::builder::traits::StructAsBytes;
 
 use crate::TagTrait;
 use core::fmt::{Debug, Formatter};
@@ -368,6 +370,13 @@ impl Default for EndTag {
     }
 }
 
+#[cfg(feature = "builder")]
+impl StructAsBytes for EndTag {
+    fn byte_size(&self) -> usize {
+        core::mem::size_of::<Self>()
+    }
+}
+
 #[derive(Clone, Debug)]
 pub struct TagIter<'a> {
     pub current: *const Tag,