|
@@ -0,0 +1,284 @@
|
|
|
+use crate::HeaderTagISA;
|
|
|
+use crate::{
|
|
|
+ AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag, EntryEfi32HeaderTag,
|
|
|
+ EntryEfi64HeaderTag, EntryHeaderTag, FramebufferHeaderTag, InformationRequestHeaderTagBuilder,
|
|
|
+ ModuleAlignHeaderTag, Multiboot2HeaderInner, RelocatableHeaderTag, StructAsBytes,
|
|
|
+};
|
|
|
+use core::mem::size_of;
|
|
|
+
|
|
|
+/// Builder to construct a valid Multiboot2 header dynamically at runtime.
|
|
|
+/// The tags will appear in the order of their corresponding enumeration,
|
|
|
+/// except for the END tag.
|
|
|
+#[derive(Debug)]
|
|
|
+pub struct Multiboot2HeaderBuilder {
|
|
|
+ arch: HeaderTagISA,
|
|
|
+ // first
|
|
|
+ information_request_tag: Option<InformationRequestHeaderTagBuilder>,
|
|
|
+ // second
|
|
|
+ address_tag: Option<AddressHeaderTag>,
|
|
|
+ // third
|
|
|
+ entry_tag: Option<EntryHeaderTag>,
|
|
|
+ // fourth
|
|
|
+ console_tag: Option<ConsoleHeaderTag>,
|
|
|
+ // fifth
|
|
|
+ framebuffer_tag: Option<FramebufferHeaderTag>,
|
|
|
+ // sixth
|
|
|
+ module_align_tag: Option<ModuleAlignHeaderTag>,
|
|
|
+ // seventh
|
|
|
+ efi_bs_tag: Option<EfiBootServiceHeaderTag>,
|
|
|
+ // eighth
|
|
|
+ efi_32_tag: Option<EntryEfi32HeaderTag>,
|
|
|
+ // ninth
|
|
|
+ efi_64_tag: Option<EntryEfi64HeaderTag>,
|
|
|
+ // tenth (last)
|
|
|
+ relocatable_tag: Option<RelocatableHeaderTag>,
|
|
|
+}
|
|
|
+
|
|
|
+impl Multiboot2HeaderBuilder {
|
|
|
+ pub const fn new(arch: HeaderTagISA) -> Self {
|
|
|
+ Self {
|
|
|
+ arch,
|
|
|
+ information_request_tag: None,
|
|
|
+ address_tag: None,
|
|
|
+ entry_tag: None,
|
|
|
+ console_tag: None,
|
|
|
+ framebuffer_tag: None,
|
|
|
+ module_align_tag: None,
|
|
|
+ efi_bs_tag: None,
|
|
|
+ efi_32_tag: None,
|
|
|
+ efi_64_tag: None,
|
|
|
+ relocatable_tag: None,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 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::<Multiboot2HeaderInner>();
|
|
|
+ // size_or_up_aligned not required, because basic header 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_builder) = self.information_request_tag.as_ref() {
|
|
|
+ // we use size_or_up_aligned, because each tag will start at an 8 byte aligned address.
|
|
|
+ // Attention: expected len from builder, not the size of the builder itself!
|
|
|
+ len += Self::size_or_up_aligned(tag_builder.expected_len())
|
|
|
+ }
|
|
|
+ if self.address_tag.is_some() {
|
|
|
+ // we use size_or_up_aligned, because each tag will start at an 8 byte aligned address
|
|
|
+ len += Self::size_or_up_aligned(size_of::<AddressHeaderTag>())
|
|
|
+ }
|
|
|
+ if self.entry_tag.is_some() {
|
|
|
+ len += Self::size_or_up_aligned(size_of::<EntryHeaderTag>())
|
|
|
+ }
|
|
|
+ if self.console_tag.is_some() {
|
|
|
+ len += Self::size_or_up_aligned(size_of::<ConsoleHeaderTag>())
|
|
|
+ }
|
|
|
+ if self.framebuffer_tag.is_some() {
|
|
|
+ len += Self::size_or_up_aligned(size_of::<FramebufferHeaderTag>())
|
|
|
+ }
|
|
|
+ if self.module_align_tag.is_some() {
|
|
|
+ len += Self::size_or_up_aligned(size_of::<ModuleAlignHeaderTag>())
|
|
|
+ }
|
|
|
+ if self.efi_bs_tag.is_some() {
|
|
|
+ len += Self::size_or_up_aligned(size_of::<EfiBootServiceHeaderTag>())
|
|
|
+ }
|
|
|
+ if self.efi_32_tag.is_some() {
|
|
|
+ len += Self::size_or_up_aligned(size_of::<EntryEfi32HeaderTag>())
|
|
|
+ }
|
|
|
+ if self.efi_64_tag.is_some() {
|
|
|
+ len += Self::size_or_up_aligned(size_of::<EntryEfi64HeaderTag>())
|
|
|
+ }
|
|
|
+ if self.relocatable_tag.is_some() {
|
|
|
+ len += Self::size_or_up_aligned(size_of::<RelocatableHeaderTag>())
|
|
|
+ }
|
|
|
+ // only here size_or_up_aligned is not important, because it is the last tag
|
|
|
+ len += size_of::<EndHeaderTag>();
|
|
|
+ len
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 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<u8>, source: &Vec<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 header with the given properties.
|
|
|
+ /// The bytes can be casted to a Multiboot2 structure.
|
|
|
+ pub fn build(mut 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!
|
|
|
+ &Multiboot2HeaderInner::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(tag) = self.address_tag.as_ref() {
|
|
|
+ Self::build_add_bytes(&mut data, &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)
|
|
|
+ }
|
|
|
+ if let Some(tag) = self.console_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.module_align_tag.as_ref() {
|
|
|
+ Self::build_add_bytes(&mut data, &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)
|
|
|
+ }
|
|
|
+ if let Some(tag) = self.efi_32_tag.as_ref() {
|
|
|
+ Self::build_add_bytes(&mut data, &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)
|
|
|
+ }
|
|
|
+ if let Some(tag) = self.relocatable_tag.as_ref() {
|
|
|
+ Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
|
|
|
+ }
|
|
|
+
|
|
|
+ Self::build_add_bytes(&mut data, &EndHeaderTag::new().struct_as_bytes(), true);
|
|
|
+
|
|
|
+ data
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn information_request_tag(
|
|
|
+ mut self,
|
|
|
+ information_request_tag: InformationRequestHeaderTagBuilder,
|
|
|
+ ) -> Self {
|
|
|
+ self.information_request_tag = Some(information_request_tag);
|
|
|
+ self
|
|
|
+ }
|
|
|
+ pub fn address_tag(mut self, address_tag: AddressHeaderTag) -> Self {
|
|
|
+ self.address_tag = Some(address_tag);
|
|
|
+ self
|
|
|
+ }
|
|
|
+ pub fn entry_tag(mut self, entry_tag: EntryHeaderTag) -> Self {
|
|
|
+ self.entry_tag = Some(entry_tag);
|
|
|
+ self
|
|
|
+ }
|
|
|
+ pub fn console_tag(mut self, console_tag: ConsoleHeaderTag) -> Self {
|
|
|
+ self.console_tag = Some(console_tag);
|
|
|
+ self
|
|
|
+ }
|
|
|
+ pub fn framebuffer_tag(mut self, framebuffer_tag: FramebufferHeaderTag) -> Self {
|
|
|
+ self.framebuffer_tag = Some(framebuffer_tag);
|
|
|
+ self
|
|
|
+ }
|
|
|
+ pub fn module_align_tag(mut self, module_align_tag: ModuleAlignHeaderTag) -> Self {
|
|
|
+ self.module_align_tag = Some(module_align_tag);
|
|
|
+ self
|
|
|
+ }
|
|
|
+ pub fn efi_bs_tag(mut self, efi_bs_tag: EfiBootServiceHeaderTag) -> Self {
|
|
|
+ self.efi_bs_tag = Some(efi_bs_tag);
|
|
|
+ self
|
|
|
+ }
|
|
|
+ pub fn efi_32_tag(mut self, efi_32_tag: EntryEfi32HeaderTag) -> Self {
|
|
|
+ self.efi_32_tag = Some(efi_32_tag);
|
|
|
+ self
|
|
|
+ }
|
|
|
+ pub fn efi_64_tag(mut self, efi_64_tag: EntryEfi64HeaderTag) -> Self {
|
|
|
+ self.efi_64_tag = Some(efi_64_tag);
|
|
|
+ self
|
|
|
+ }
|
|
|
+ pub fn relocatable_tag(mut self, relocatable_tag: RelocatableHeaderTag) -> Self {
|
|
|
+ self.relocatable_tag = Some(relocatable_tag);
|
|
|
+ self
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod tests {
|
|
|
+ use crate::{
|
|
|
+ load_mb2_header, HeaderTagFlag, HeaderTagISA, InformationRequestHeaderTagBuilder,
|
|
|
+ MbiTagType, Multiboot2HeaderBuilder, RelocatableHeaderTag,
|
|
|
+ RelocatableHeaderTagPreference,
|
|
|
+ };
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_size_or_up_aligned() {
|
|
|
+ assert_eq!(0, Multiboot2HeaderBuilder::size_or_up_aligned(0));
|
|
|
+ assert_eq!(8, Multiboot2HeaderBuilder::size_or_up_aligned(1));
|
|
|
+ assert_eq!(8, Multiboot2HeaderBuilder::size_or_up_aligned(8));
|
|
|
+ assert_eq!(16, Multiboot2HeaderBuilder::size_or_up_aligned(9));
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_size_builder() {
|
|
|
+ let builder = Multiboot2HeaderBuilder::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,
|
|
|
+ ));
|
|
|
+
|
|
|
+ println!("builder: {:#?}", builder);
|
|
|
+ println!("expected_len: {} bytes", builder.expected_len());
|
|
|
+
|
|
|
+ let mb2_hdr_data = builder.build();
|
|
|
+ let mb2_hdr = mb2_hdr_data.as_ptr() as usize;
|
|
|
+ let mb2_hdr = unsafe { load_mb2_header(mb2_hdr) };
|
|
|
+ println!("{:#?}", mb2_hdr);
|
|
|
+
|
|
|
+ /* 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();*/
|
|
|
+ }
|
|
|
+}
|