diff --git a/Cargo.lock b/Cargo.lock index 9ab178d0..8cc3e00a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,21 +27,33 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "multiboot2" -version = "0.21.0" +version = "0.22.0" dependencies = [ "bitflags", "derive_more", "log", + "multiboot2-common", "ptr_meta", "uefi-raw", ] +[[package]] +name = "multiboot2-common" +version = "0.1.0" +dependencies = [ + "derive_more", + "ptr_meta", +] + [[package]] name = "multiboot2-header" -version = "0.4.0" +version = "0.5.0" dependencies = [ "derive_more", + "log", "multiboot2", + "multiboot2-common", + "ptr_meta", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 59e8e1e5..57e82c6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ "multiboot2", + "multiboot2-common", "multiboot2-header", ] exclude = [ @@ -12,9 +13,11 @@ exclude = [ bitflags = "2.6.0" derive_more = { version = "~0.99.18", default-features = false, features = ["display"] } log = { version = "~0.4", default-features = false } +ptr_meta = { version = "~0.2", default-features = false } -# This way, the "multiboot2" dependency in the multiboot2-header crate can be -# referenced by version, while still the repository version is used -# transparently during local development. +# This way, the corresponding crate dependency can be normalley referenced by +# version, while still the repository version is used transparently during local +# development. [patch.crates-io] multiboot2 = { path = "multiboot2" } +multiboot2-common = { path = "multiboot2-common" } diff --git a/integration-test/bins/Cargo.lock b/integration-test/bins/Cargo.lock index 1a49ad75..5b185358 100644 --- a/integration-test/bins/Cargo.lock +++ b/integration-test/bins/Cargo.lock @@ -96,21 +96,33 @@ dependencies = [ [[package]] name = "multiboot2" -version = "0.21.0" +version = "0.22.0" dependencies = [ "bitflags 2.6.0", "derive_more", "log", + "multiboot2-common", "ptr_meta", "uefi-raw", ] +[[package]] +name = "multiboot2-common" +version = "0.1.0" +dependencies = [ + "derive_more", + "ptr_meta", +] + [[package]] name = "multiboot2-header" -version = "0.4.0" +version = "0.5.0" dependencies = [ "derive_more", + "log", "multiboot2", + "multiboot2-common", + "ptr_meta", ] [[package]] diff --git a/integration-test/bins/Cargo.toml b/integration-test/bins/Cargo.toml index 335dd53f..05bf49d6 100644 --- a/integration-test/bins/Cargo.toml +++ b/integration-test/bins/Cargo.toml @@ -24,3 +24,5 @@ util = { path = "./util" } # transparently during local development. [patch.crates-io] multiboot2 = { path = "../../multiboot2" } +multiboot2-common = { path = "../../multiboot2-common" } +multiboot2-header = { path = "../../multiboot2-header" } diff --git a/integration-test/bins/multiboot2_chainloader/src/loader.rs b/integration-test/bins/multiboot2_chainloader/src/loader.rs index 3668595e..e543867c 100644 --- a/integration-test/bins/multiboot2_chainloader/src/loader.rs +++ b/integration-test/bins/multiboot2_chainloader/src/loader.rs @@ -1,8 +1,8 @@ -use core::ops::Deref; +use alloc::boxed::Box; use elf_rs::{ElfFile, ProgramHeaderEntry, ProgramType}; use multiboot2::{ - BootLoaderNameTag, CommandLineTag, MemoryArea, MemoryAreaType, MemoryMapTag, ModuleTag, - SmbiosTag, + BootLoaderNameTag, CommandLineTag, MaybeDynSized, MemoryArea, MemoryAreaType, MemoryMapTag, + ModuleTag, SmbiosTag, }; /// Loads the first module into memory. Assumes that the module is a ELF file. @@ -43,27 +43,27 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! { // that the basic data structures are usable. // build MBI - let mbi = multiboot2::builder::InformationBuilder::new() - .bootloader_name_tag(&BootLoaderNameTag::new("mb2_integrationtest_chainloader")) - .command_line_tag(&CommandLineTag::new("chainloaded YEAH")) + let mbi = multiboot2::Builder::new() + .bootloader(BootLoaderNameTag::new("mb2_integrationtest_chainloader")) + .cmdline(CommandLineTag::new("chainloaded YEAH")) // random non-sense memory map - .memory_map_tag(&MemoryMapTag::new(&[MemoryArea::new( + .mmap(MemoryMapTag::new(&[MemoryArea::new( 0, 0xffffffff, MemoryAreaType::Reserved, )])) - .add_module_tag(&ModuleTag::new( + .add_module(ModuleTag::new( elf_mod.start as u32, elf_mod.end as u32, elf_mod.string.unwrap(), )) // Test that we can add SmbiosTag multiple times. - .add_tag(SmbiosTag::new(1, 1, &[1, 2, 3]).deref()) - .unwrap() - .add_tag(SmbiosTag::new(1, 2, &[1, 2, 3]).deref()) - .expect("should allow tag multiple times") + .add_smbios(SmbiosTag::new(1, 1, &[1, 2, 3])) + .add_smbios(SmbiosTag::new(2, 3, &[4, 5, 6])) .build(); + let mbi = Box::leak(mbi); + log::info!( "Handing over to ELF: {}", elf_mod.string.unwrap_or("") @@ -74,7 +74,7 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! { core::arch::asm!( "jmp *%ecx", in("eax") multiboot2::MAGIC, - in("ebx") mbi.as_ptr() as u32, + in("ebx") mbi.as_ptr(), in("ecx") elf.entry_point() as u32, options(noreturn, att_syntax)); } diff --git a/integration-test/bins/multiboot2_payload/src/verify/mod.rs b/integration-test/bins/multiboot2_payload/src/verify/mod.rs index 01f82269..aa196487 100644 --- a/integration-test/bins/multiboot2_payload/src/verify/mod.rs +++ b/integration-test/bins/multiboot2_payload/src/verify/mod.rs @@ -6,7 +6,7 @@ use alloc::vec::Vec; use multiboot2::BootInformation; pub fn run(mbi: &BootInformation) -> anyhow::Result<()> { - println!("{mbi:#x?}"); + println!("MBI: {mbi:#x?}"); println!(); let bootloader = mbi diff --git a/multiboot2-common/Cargo.toml b/multiboot2-common/Cargo.toml new file mode 100644 index 00000000..3fc1ae2e --- /dev/null +++ b/multiboot2-common/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "multiboot2-common" +description = """ +Common helpers for the `multiboot2` and `multiboot2-header` crates. +""" +version = "0.1.0" +authors = [ + "Philipp Schuster " +] +license = "MIT/Apache-2.0" +edition = "2021" +categories = [ + "no-std", + "no-std::no-alloc", +] +keywords = [ + "Multiboot2" +] +readme = "README.md" +homepage = "https://github.com/rust-osdev/multiboot2" +repository = "https://github.com/rust-osdev/multiboot2" +documentation = "https://docs.rs/multiboot2-common" +rust-version = "1.70" + +[features] +default = ["builder"] +alloc = [] +builder = ["alloc"] +unstable = [] + + +[dependencies] +derive_more.workspace = true +ptr_meta.workspace = true + +[package.metadata.docs.rs] +all-features = true diff --git a/multiboot2-common/Changelog.md b/multiboot2-common/Changelog.md new file mode 100644 index 00000000..3cdcf646 --- /dev/null +++ b/multiboot2-common/Changelog.md @@ -0,0 +1,5 @@ +# CHANGELOG for crate `multiboot2` + +## 0.1.0 (2024-08-20) + +Initial release. diff --git a/multiboot2-common/README.md b/multiboot2-common/README.md new file mode 100644 index 00000000..5ae6274a --- /dev/null +++ b/multiboot2-common/README.md @@ -0,0 +1 @@ +# multiboot2-common diff --git a/multiboot2-common/src/boxed.rs b/multiboot2-common/src/boxed.rs new file mode 100644 index 00000000..bbd45ad4 --- /dev/null +++ b/multiboot2-common/src/boxed.rs @@ -0,0 +1,116 @@ +//! Module for [`new_boxed`]. + +use crate::{increase_to_alignment, Header, MaybeDynSized, ALIGNMENT}; +use alloc::boxed::Box; +use core::alloc::Layout; +use core::mem; +use core::ops::Deref; +use core::ptr; + +/// Creates a new tag implementing [`MaybeDynSized`] on the heap. This works for +/// sized and unsized tags. However, it only makes sense to use this for tags +/// that are DSTs (unsized). For regular sized structs, you can just create a +/// typical constructor and box the result. +/// +/// The provided `header`' total size (see [`Header`]) will be set dynamically +/// by this function using [`Header::set_size`]. However, it must contain all +/// other relevant metadata or update it in the `set_size` callback. +/// +/// # Parameters +/// - `additional_bytes_slices`: Array of byte slices that should be included +/// without additional padding in-between. You don't need to add the bytes +/// for [`Header`], but only additional payload. +#[must_use] +pub fn new_boxed + ?Sized>( + mut header: T::Header, + additional_bytes_slices: &[&[u8]], +) -> Box { + let additional_size = additional_bytes_slices + .iter() + .map(|b| b.len()) + .sum::(); + + let tag_size = mem::size_of::() + additional_size; + header.set_size(tag_size); + + // Allocation size is multiple of alignment. + // See + let alloc_size = increase_to_alignment(tag_size); + let layout = Layout::from_size_align(alloc_size, ALIGNMENT).unwrap(); + let heap_ptr = unsafe { alloc::alloc::alloc(layout) }; + assert!(!heap_ptr.is_null()); + + // write header + { + let len = mem::size_of::(); + let ptr = core::ptr::addr_of!(header); + unsafe { + ptr::copy_nonoverlapping(ptr.cast::(), heap_ptr, len); + } + } + + // write body + { + let mut write_offset = mem::size_of::(); + for &bytes in additional_bytes_slices { + let len = bytes.len(); + let src = bytes.as_ptr(); + unsafe { + let dst = heap_ptr.add(write_offset); + ptr::copy_nonoverlapping(src, dst, len); + write_offset += len; + } + } + } + + // This is a fat pointer for DSTs and a thin pointer for sized `T`s. + let ptr: *mut T = ptr_meta::from_raw_parts_mut(heap_ptr.cast(), T::dst_len(&header)); + let reference = unsafe { Box::from_raw(ptr) }; + + // If this panic triggers, there is a fundamental flaw in my logic. This is + // not the fault of an API user. + assert_eq!( + mem::size_of_val(reference.deref()), + alloc_size, + "Allocation should match Rusts expectation" + ); + + reference +} + +/// Clones a [`MaybeDynSized`] by calling [`new_boxed`]. +#[must_use] +pub fn clone_dyn + ?Sized>(tag: &T) -> Box { + new_boxed(tag.header().clone(), &[tag.payload()]) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::{DummyDstTag, DummyTestHeader}; + use crate::Tag; + + #[test] + fn test_new_boxed() { + let header = DummyTestHeader::new(DummyDstTag::ID, 0); + let tag = new_boxed::(header, &[&[0, 1, 2, 3]]); + assert_eq!(tag.header().typ(), 42); + assert_eq!(tag.payload(), &[0, 1, 2, 3]); + + // Test that bytes are added consecutively without gaps. + let header = DummyTestHeader::new(0xdead_beef, 0); + let tag = new_boxed::(header, &[&[0], &[1], &[2, 3]]); + assert_eq!(tag.header().typ(), 0xdead_beef); + assert_eq!(tag.payload(), &[0, 1, 2, 3]); + } + + #[test] + fn test_clone_tag() { + let header = DummyTestHeader::new(DummyDstTag::ID, 0); + let tag = new_boxed::(header, &[&[0, 1, 2, 3]]); + assert_eq!(tag.header().typ(), 42); + assert_eq!(tag.payload(), &[0, 1, 2, 3]); + + let _cloned = clone_dyn(tag.as_ref()); + } +} diff --git a/multiboot2-common/src/bytes_ref.rs b/multiboot2-common/src/bytes_ref.rs new file mode 100644 index 00000000..45907418 --- /dev/null +++ b/multiboot2-common/src/bytes_ref.rs @@ -0,0 +1,87 @@ +//! Module for [`BytesRef`]. + +use crate::{Header, MemoryError, ALIGNMENT}; +use core::marker::PhantomData; +use core::mem; +use core::ops::Deref; + +/// Wraps a byte slice representing a Multiboot2 structure including an optional +/// terminating padding, if necessary. It guarantees that the memory +/// requirements promised in the crates description are respected. +#[derive(Clone, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct BytesRef<'a, H: Header> { + bytes: &'a [u8], + // Ensure that consumers can rely on the size properties for `H` that + // already have been verified when this type was constructed. + _h: PhantomData, +} + +impl<'a, H: Header> TryFrom<&'a [u8]> for BytesRef<'a, H> { + type Error = MemoryError; + + fn try_from(bytes: &'a [u8]) -> Result { + if bytes.len() < mem::size_of::() { + return Err(MemoryError::ShorterThanHeader); + } + // Doesn't work as expected: if align_of_val(&value[0]) < ALIGNMENT { + if bytes.as_ptr().align_offset(ALIGNMENT) != 0 { + return Err(MemoryError::WrongAlignment); + } + let padding_bytes = bytes.len() % ALIGNMENT; + if padding_bytes != 0 { + return Err(MemoryError::MissingPadding); + } + Ok(Self { + bytes, + _h: PhantomData, + }) + } +} + +impl<'a, H: Header> Deref for BytesRef<'a, H> { + type Target = &'a [u8]; + + fn deref(&self) -> &Self::Target { + &self.bytes + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::{AlignedBytes, DummyTestHeader}; + + #[test] + fn test_bytes_ref() { + let empty: &[u8] = &[]; + assert_eq!( + BytesRef::<'_, DummyTestHeader>::try_from(empty), + Err(MemoryError::ShorterThanHeader) + ); + + let slice = &[0_u8, 1, 2, 3, 4, 5, 6]; + assert_eq!( + BytesRef::<'_, DummyTestHeader>::try_from(&slice[..]), + Err(MemoryError::ShorterThanHeader) + ); + + let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0]); + // Guaranteed wrong alignment + let unaligned_slice = &slice[3..]; + assert_eq!( + BytesRef::<'_, DummyTestHeader>::try_from(unaligned_slice), + Err(MemoryError::WrongAlignment) + ); + + let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7]); + let slice = &slice[..]; + assert_eq!( + BytesRef::try_from(slice), + Ok(BytesRef { + bytes: slice, + _h: PhantomData:: + }) + ); + } +} diff --git a/multiboot2-common/src/iter.rs b/multiboot2-common/src/iter.rs new file mode 100644 index 00000000..3c1a1dfb --- /dev/null +++ b/multiboot2-common/src/iter.rs @@ -0,0 +1,125 @@ +//! Iterator over Multiboot2 structures. Technically, the process for iterating +//! Multiboot2 information tags and iterating Multiboot2 header tags is the +//! same. + +use crate::{increase_to_alignment, DynSizedStructure, Header, ALIGNMENT}; +use core::marker::PhantomData; +use core::mem; + +/// Iterates over the tags (modelled by [`DynSizedStructure`]) of the underlying +/// byte slice. Each tag is expected to have the same common [`Header`]. +/// +/// As the iterator emits elements of type [`DynSizedStructure`], users should +/// casted them to specific [`Tag`]s using [`DynSizedStructure::cast`] following +/// a user policy. This can for example happen on the basis of some ID. +/// +/// This type ensures the memory safety guarantees promised by this crates +/// documentation. +/// +/// [`Tag`]: crate::Tag +#[derive(Clone, Debug)] +pub struct TagIter<'a, H: Header> { + /// Absolute offset to next tag and updated in each iteration. + next_tag_offset: usize, + buffer: &'a [u8], + // Ensure that all instances are bound to a specific `Header`. + // Otherwise, UB can happen. + _t: PhantomData, +} + +impl<'a, H: Header> TagIter<'a, H> { + /// Creates a new iterator. + #[must_use] + pub fn new(mem: &'a [u8]) -> Self { + // Assert alignment. + assert_eq!(mem.as_ptr().align_offset(ALIGNMENT), 0); + + TagIter { + next_tag_offset: 0, + buffer: mem, + _t: PhantomData, + } + } +} + +impl<'a, H: Header + 'a> Iterator for TagIter<'a, H> { + type Item = &'a DynSizedStructure; + + fn next(&mut self) -> Option { + if self.next_tag_offset == self.buffer.len() { + return None; + } + assert!(self.next_tag_offset < self.buffer.len()); + + let ptr = unsafe { self.buffer.as_ptr().add(self.next_tag_offset) }.cast::(); + let tag_hdr = unsafe { &*ptr }; + + // Get relevant byte portion for the next tag. This includes padding + // bytes to fulfill Rust memory guarantees. Otherwise, Miri complains. + // See . + let slice = { + let from = self.next_tag_offset; + let len = mem::size_of::() + tag_hdr.payload_len(); + let to = from + len; + + // The size of (the allocation for) a value is always a multiple of + // its alignment. + // https://doc.rust-lang.org/reference/type-layout.html + let to = increase_to_alignment(to); + + // Update ptr for next iteration. + self.next_tag_offset += to - from; + + &self.buffer[from..to] + }; + + // unwrap: We should not fail at this point. + let tag = DynSizedStructure::ref_from_slice(slice).unwrap(); + Some(tag) + } +} + +#[cfg(test)] +mod tests { + use crate::test_utils::{AlignedBytes, DummyTestHeader}; + use crate::TagIter; + use core::borrow::Borrow; + + #[test] + fn test_tag_iter() { + #[rustfmt::skip] + let bytes = AlignedBytes::new( + [ + /* Some minimal tag. */ + 0xff, 0, 0, 0, + 8, 0, 0, 0, + /* Some tag with payload. */ + 0xfe, 0, 0, 0, + 12, 0, 0, 0, + 1, 2, 3, 4, + // Padding + 0, 0, 0, 0, + /* End tag */ + 0, 0, 0, 0, + 8, 0, 0, 0, + ], + ); + let mut iter = TagIter::::new(bytes.borrow()); + let first = iter.next().unwrap(); + assert_eq!(first.header().typ(), 0xff); + assert_eq!(first.header().size(), 8); + assert!(first.payload().is_empty()); + + let second = iter.next().unwrap(); + assert_eq!(second.header().typ(), 0xfe); + assert_eq!(second.header().size(), 12); + assert_eq!(&second.payload(), &[1, 2, 3, 4]); + + let third = iter.next().unwrap(); + assert_eq!(third.header().typ(), 0); + assert_eq!(third.header().size(), 8); + assert!(first.payload().is_empty()); + + assert_eq!(iter.next(), None); + } +} diff --git a/multiboot2-common/src/lib.rs b/multiboot2-common/src/lib.rs new file mode 100644 index 00000000..6f01e285 --- /dev/null +++ b/multiboot2-common/src/lib.rs @@ -0,0 +1,406 @@ +//! Common helpers for the `multiboot2` and `multiboot2-header` crates. +//! +//! # Value-add +//! +//! The main value-add of this crate is to abstract away the parsing and +//! construction of Multiboot2 structures. This is more complex as it may sound +//! at first due to the difficulties listed below. +//! +//! The abstractions provided by this crate serve as the base to work with the +//! following structures: +//! - multiboot2: +//! - boot information structure (whole) +//! - boot information tags +//! - multiboot2-header: +//! - header structure (whole) +//! - header tags +//! +//! # The Problem / Difficulties +//! +//! ## Multiboot2 Structures +//! +//! Multiboot2 structures are a consecutive chunk of bytes in memory. They use +//! the "header pattern", which means a fixed size and known [`Header`] type +//! indicates the total size of the structure. This is roughly translated to the +//! following rusty base type: +//! +//! ```ignore +//! #[repr(C, align(8))] +//! struct DynStructure { +//! header: MyHeader, +//! payload: [u8] +//! } +//! ``` +//! +//! Note that these structures can also be nested. So for example, the +//! Multiboot2 boot information contains Multiboot2 tags, and the Multiboot2 +//! header contains Multiboot2 header tags - both are itself dynamic structures. +//! +//! A final `[u8]` field in the structs is the most rusty way to model this. +//! However, this makes the type a Dynamically Sized Type (DST). To create +//! references to these types from a byte slice, one needs fat pointers. They +//! are a language feature currently not constructable with stable Rust. +//! Luckily, we can utilize [`ptr_meta`]. +//! +//! ## Dynamic and Sized Structs +//! +//! Note that we also have structures (tags) in Multiboot2 that looks like this: +//! +//! ```ignore +//! #[repr(C, align(8))] +//! struct DynStructure { +//! header: MyHeader, +//! // Not just [`u8`] +//! payload: [SomeType] +//! } +//! ``` +//! +//! or +//! +//! ```ignore +//! #[repr(C, align(8))] +//! struct CommandLineTag { +//! header: TagHeader, +//! start: u32, +//! end: u32, +//! // More than just the base header before the dynamic portion +//! data: [u8] +//! } +//! ``` +//! +//! ## Fat Pointer Requirements +//! +//! To create fat pointers with [`ptr_meta`], each tag needs a `Metadata` type +//! which is either `usize` (for DSTs) or `()`. A trait is needed to abstract +//! above sized or unsized types. +//! +//! ## Multiboot2 Requirements +//! +//! All tags must be 8-byte aligned. Space between multiple tags may be +//! filled with zeroes if necessary. These zeroes are not reflected in the +//! previous tag's size. +//! +//! ## Rustc Requirements +//! +//! The allocation space that Rust requires for types is a multiple of the +//! alignment. This means that if we cast between byte slices and specific +//! types, Rust doesn't just see the size reported by the header but also +//! any necessary padding bytes. If this is not the case, for example we +//! cast to a structure from a `&[u8; 15]`, Miri will complain as it expects +//! `&[u8; 16]` +//! +//! See for information. +//! +//! # Provided Abstractions +//! +//! ## Parsing and Casting +//! +//! First, we need byte slices which are guaranteed to be aligned and are a +//! multiple of the alignment. We have [`BytesRef`] for that. With that, we can +//! create a [`DynSizedStructure`]. This is a rusty type that owns all the bytes +//! it owns, according to the size reported by its header. Using this type +//! and with the help of [`MaybeDynSized`], we can call +//! [`DynSizedStructure::cast`] to cast this to arbitrary sized or unsized +//! struct types fulfilling the corresponding requirements. +//! +//! This way, one can create nice rusty structs modeling the structure of the +//! tags, and we only need a single "complicated" type, namely +//! [`DynSizedStructure`]. +//! +//! ## Iterating Tags +//! +//! To iterate over the tags of a structure, use [`TagIter`]. +//! +//! # Memory Guarantees and Safety Promises +//! +//! For the parsing and construction of Multiboot2 structures, the alignment +//! and necessary padding bytes as discussed above are guaranteed. When types +//! are constructed, they return Results with appropriate error types. If +//! during runtime something goes wrong, for example due to malformed tags, +//! panics guarantee that no UB will happen. +//! +//! # No Public API +//! +//! Not meant as stable public API for others outside Multiboot2. + +#![no_std] +#![cfg_attr(feature = "unstable", feature(error_in_core))] +// --- BEGIN STYLE CHECKS --- +#![deny( + clippy::all, + clippy::cargo, + clippy::must_use_candidate, + clippy::nursery, + missing_debug_implementations, + missing_docs, + rustdoc::all +)] +#![allow(clippy::multiple_crate_versions)] +// --- END STYLE CHECKS --- + +#[cfg_attr(test, macro_use)] +#[cfg(test)] +extern crate std; + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[allow(unused)] +pub mod test_utils; + +#[cfg(feature = "alloc")] +mod boxed; +mod bytes_ref; +mod iter; +mod tag; + +#[cfg(feature = "alloc")] +pub use boxed::{clone_dyn, new_boxed}; +pub use bytes_ref::BytesRef; +pub use iter::TagIter; +pub use tag::{MaybeDynSized, Tag}; + +use core::fmt::Debug; +use core::mem; +use core::ptr; +use core::ptr::NonNull; +use core::slice; + +/// The alignment of all Multiboot2 data structures. +pub const ALIGNMENT: usize = 8; + +/// A sized header type for [`DynSizedStructure`]. Note that `header` refers to +/// the header pattern. Thus, depending on the use case, this is not just a +/// tag header. Instead, it refers to all bytes that are fixed and not part of +/// any optional terminating dynamic `[u8]` slice in a [`DynSizedStructure`]. +/// +/// The alignment of implementors **must** be the compatible with the demands +/// for the corresponding structure, which typically is [`ALIGNMENT`]. +pub trait Header: Clone + Sized + PartialEq + Eq + Debug { + /// Returns the length of the payload, i.e., the bytes that are additional + /// to the header. The value is measured in bytes. + #[must_use] + fn payload_len(&self) -> usize; + + /// Returns the total size of the struct, thus the size of the header itself + /// plus [`Header::payload_len`]. + #[must_use] + fn total_size(&self) -> usize { + mem::size_of::() + self.payload_len() + } + + /// Updates the header with the given `total_size`. + fn set_size(&mut self, total_size: usize); +} + +/// An C ABI-compatible dynamically sized type with a common sized [`Header`] +/// and a dynamic amount of bytes. This structures owns all its bytes, unlike +/// [`Header`]. Instances guarantees that the memory requirements promised in +/// the crates description are respected. +/// +/// This can be a Multiboot2 header tag, information tag, boot information, or +/// a Multiboot2 header. Depending on the context, the [`Header`] is different. +/// +/// # ABI +/// This type uses the C ABI. The fixed [`Header`] portion is always there. +/// Further, there is a variable amount of payload bytes. Thus, this type can +/// only exist on the heap or references to it can be made by cast via fat +/// pointers. +#[derive(Debug, PartialEq, Eq, ptr_meta::Pointee)] +#[repr(C, align(8))] +pub struct DynSizedStructure { + header: H, + payload: [u8], + // Plus optional padding bytes to next alignment boundary, which are not + // reflected here. However, Rustc allocates them anyway and expects them + // to be there. + // See . +} + +impl DynSizedStructure { + /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`] + /// from the given [`BytesRef`]. + pub fn ref_from_bytes(bytes: BytesRef) -> Result<&Self, MemoryError> { + let ptr = bytes.as_ptr().cast::(); + let hdr = unsafe { &*ptr }; + + if hdr.payload_len() > bytes.len() { + return Err(MemoryError::InvalidReportedTotalSize); + } + + // At this point we know that the memory slice fulfills the base + // assumptions and requirements. Now, we safety can create the fat + // pointer. + + let dst_size = hdr.payload_len(); + // Create fat pointer for the DST. + let ptr = ptr_meta::from_raw_parts(ptr.cast(), dst_size); + let reference = unsafe { &*ptr }; + Ok(reference) + } + + /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`] + /// from the given `&[u8]`. + pub fn ref_from_slice(bytes: &[u8]) -> Result<&Self, MemoryError> { + let bytes = BytesRef::::try_from(bytes)?; + Self::ref_from_bytes(bytes) + } + + /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`] + /// from the given thin pointer to the [`Header`]. It reads the total size + /// from the header. + /// + /// # Safety + /// The caller must ensure that the function operates on valid memory. + pub unsafe fn ref_from_ptr<'a>(ptr: NonNull) -> Result<&'a Self, MemoryError> { + let ptr = ptr.as_ptr().cast_const(); + let hdr = unsafe { &*ptr }; + + let slice = unsafe { slice::from_raw_parts(ptr.cast::(), hdr.total_size()) }; + Self::ref_from_slice(slice) + } + + /// Returns the underlying [`Header`]. + pub const fn header(&self) -> &H { + &self.header + } + + /// Returns the underlying payload. + pub const fn payload(&self) -> &[u8] { + &self.payload + } + + /// Casts the structure tag to a specific [`MaybeDynSized`] implementation which + /// may be a ZST or DST typed tag. The output type will have the exact same + /// size as `*self`. The target type must be sufficient for that. If not, + /// the function will panic. + /// + /// # Safety + /// This function is safe due to various sanity checks and the overall + /// memory assertions done while constructing this type. + /// + /// # Panics + /// This panics if there is a size mismatch. However, this should never be + /// the case if all types follow their documented requirements. + pub fn cast + ?Sized>(&self) -> &T { + let base_ptr = ptr::addr_of!(*self); + + // This should be a compile-time assertion. However, this is the best + // location to place it for now. + assert!(T::BASE_SIZE >= mem::size_of::()); + + let t_dst_size = T::dst_len(self.header()); + let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size); + let t_ref = unsafe { &*t_ptr }; + + assert_eq!(mem::size_of_val(self), mem::size_of_val(t_ref)); + + t_ref + } +} + +/// Errors that may occur when working with memory. +#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, derive_more::Display)] +pub enum MemoryError { + /// The memory points to null. + Null, + /// The memory must be at least [`ALIGNMENT`]-aligned. + WrongAlignment, + /// The memory must cover at least the length of the sized structure header + /// type. + ShorterThanHeader, + /// The buffer misses the terminating padding to the next alignment + /// boundary. The padding is relevant to satisfy Rustc/Miri, but also the + /// spec mandates that the padding is added. + MissingPadding, + /// The size-property has an illegal value that can't be fulfilled with the + /// given bytes. + InvalidReportedTotalSize, +} + +#[cfg(feature = "unstable")] +impl core::error::Error for MemoryError {} + +/// Increases the given size to the next alignment boundary, if it is not a +/// multiple of the alignment yet. This is relevant as in Rust's [type layout], +/// the allocated size of a type is always a multiple of the alignment, even +/// if the type is smaller. +/// +/// [type layout]: https://doc.rust-lang.org/reference/type-layout.html +#[must_use] +pub const fn increase_to_alignment(size: usize) -> usize { + let mask = ALIGNMENT - 1; + (size + mask) & !mask +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::{AlignedBytes, DummyTestHeader}; + use core::borrow::Borrow; + + #[test] + fn test_increase_to_alignment() { + assert_eq!(increase_to_alignment(0), 0); + assert_eq!(increase_to_alignment(1), 8); + assert_eq!(increase_to_alignment(7), 8); + assert_eq!(increase_to_alignment(8), 8); + assert_eq!(increase_to_alignment(9), 16); + } + + #[test] + fn test_cast_generic_tag_to_sized_tag() { + #[repr(C)] + struct CustomSizedTag { + tag_header: DummyTestHeader, + a: u32, + b: u32, + } + + impl MaybeDynSized for CustomSizedTag { + type Header = DummyTestHeader; + + const BASE_SIZE: usize = mem::size_of::(); + + fn dst_len(_header: &DummyTestHeader) -> Self::Metadata {} + } + + let bytes = AlignedBytes([ + /* id: 0xffff_ffff */ + 0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */ + 16, 0, 0, 0, /* field a: 0xdead_beef */ + 0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */ + 0x37, 0x13, 0x37, 0x13, + ]); + let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap(); + let custom_tag = tag.cast::(); + + assert_eq!(mem::size_of_val(custom_tag), 16); + assert_eq!(custom_tag.a, 0xdead_beef); + assert_eq!(custom_tag.b, 0x1337_1337); + } + + #[test] + fn test_cast_generic_tag_to_self() { + #[rustfmt::skip] + let bytes = AlignedBytes::new( + [ + 0x37, 0x13, 0, 0, + /* Tag size */ + 18, 0, 0, 0, + /* Some payload. */ + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, + // Padding + 0, 0, 0, 0, 0, 0 + ], + ); + let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap(); + + // Main objective here is also that this test passes Miri. + let tag = tag.cast::>(); + assert_eq!(tag.header().typ(), 0x1337); + assert_eq!(tag.header().size(), 18); + } +} diff --git a/multiboot2-common/src/tag.rs b/multiboot2-common/src/tag.rs new file mode 100644 index 00000000..2ff85104 --- /dev/null +++ b/multiboot2-common/src/tag.rs @@ -0,0 +1,94 @@ +//! Module for the traits [`MaybeDynSized`] and [`Tag`]. + +use crate::{BytesRef, DynSizedStructure, Header}; +use core::mem; +use core::slice; +use ptr_meta::Pointee; + +/// A trait to abstract sized and unsized structures (DSTs). It enables +/// casting a [`DynSizedStructure`] to sized or unsized structures using +/// [`DynSizedStructure::cast`]. +/// +/// Structs that are a DST must provide a **correct** +/// [`MaybeDynSized::dst_len`] implementation. +/// +/// [`ID`]: Tag::ID +/// [`DynSizedStructure`]: crate::DynSizedStructure +pub trait MaybeDynSized: Pointee { + /// The associated [`Header`] of this tag. + type Header: Header; + + /// The true base size of the struct without any implicit or additional + /// padding. Note that `size_of::()` isn't sufficient, as for example + /// the type could have three `u32` fields, which would add an implicit + /// `u32` padding. However, this constant **must always** fulfill + /// `BASE_SIZE >= size_of::()`. + /// + /// The main purpose of this constant is to create awareness when you + /// implement [`Self::dst_len`], where you should use this. If this value + /// is correct, we prevent situations where we read uninitialized bytes, + /// especially when creating tags in builders. + const BASE_SIZE: usize; + + /// Returns the amount of items in the dynamically sized portion of the + /// DST. Note that this is not the amount of bytes. So if the dynamically + /// sized portion is 16 bytes in size and each element is 4 bytes big, then + /// this function must return 4. + /// + /// For sized tags, this just returns `()`. For DSTs, this returns an + /// `usize`. + fn dst_len(header: &Self::Header) -> Self::Metadata; + + /// Returns the corresponding [`Header`]. + fn header(&self) -> &Self::Header { + let ptr = core::ptr::addr_of!(*self); + unsafe { &*ptr.cast::() } + } + + /// Returns the payload, i.e., all memory that is not occupied by the + /// [`Header`] of the type. + fn payload(&self) -> &[u8] { + let from = mem::size_of::(); + &self.as_bytes()[from..] + } + + /// Returns the whole allocated bytes for this structure encapsulated in + /// [`BytesRef`]. This includes padding bytes. To only get the "true" tag + /// data, read the tag size from [`Self::header`] and create a sub slice. + fn as_bytes(&self) -> BytesRef { + let ptr = core::ptr::addr_of!(*self); + // Actual tag size, optionally with terminating padding. + let size = mem::size_of_val(self); + let slice = unsafe { slice::from_raw_parts(ptr.cast::(), size) }; + // Unwrap is fine as this type can't exist without the underlying memory + // guarantees. + BytesRef::try_from(slice).unwrap() + } + + /// Returns a pointer to this structure. + fn as_ptr(&self) -> *const Self::Header { + self.as_bytes().as_ptr().cast() + } +} + +/// Extension of [`MaybeDynSized`] for Tags. +pub trait Tag: MaybeDynSized { + /// The ID type that identifies the tag. + type IDType: PartialEq + Eq; + + /// The ID of this tag. This should be unique across all implementors. + /// + /// Although the ID is not yet used in `multiboot2-common`, it ensures + /// a consistent API in consumer crates. + const ID: Self::IDType; +} + +impl MaybeDynSized for DynSizedStructure { + type Header = H; + + const BASE_SIZE: usize = mem::size_of::(); + + fn dst_len(header: &Self::Header) -> Self::Metadata { + header.payload_len() + } +} diff --git a/multiboot2/src/test_util.rs b/multiboot2-common/src/test_utils.rs similarity index 50% rename from multiboot2/src/test_util.rs rename to multiboot2-common/src/test_utils.rs index 2a983dc9..c81cec8d 100644 --- a/multiboot2/src/test_util.rs +++ b/multiboot2-common/src/test_utils.rs @@ -1,17 +1,22 @@ //! Various test utilities. -use crate::ALIGNMENT; +#![allow(missing_docs)] + +use crate::{Header, MaybeDynSized, Tag}; use core::borrow::Borrow; +use core::mem; use core::ops::Deref; /// Helper to 8-byte align the underlying bytes, as mandated in the Multiboot2 /// spec. With this type, one can create manual and raw Multiboot2 boot /// information or just the bytes for simple tags, in a manual and raw approach. -#[cfg(test)] +#[derive(Debug)] #[repr(C, align(8))] pub struct AlignedBytes(pub [u8; N]); impl AlignedBytes { + /// Creates a new type. + #[must_use] pub const fn new(bytes: [u8; N]) -> Self { Self(bytes) } @@ -37,51 +42,103 @@ impl Deref for AlignedBytes { } } -// The tests down below are all Miri-approved. +/// Dummy test header. +#[derive(Clone, Debug, PartialEq, Eq)] +#[repr(C, align(8))] +pub struct DummyTestHeader { + typ: u32, + size: u32, +} + +impl DummyTestHeader { + #[must_use] + pub const fn new(typ: u32, size: u32) -> Self { + Self { typ, size } + } + + #[must_use] + pub const fn typ(&self) -> u32 { + self.typ + } + + #[must_use] + pub const fn size(&self) -> u32 { + self.size + } +} + +impl Header for DummyTestHeader { + fn payload_len(&self) -> usize { + self.size as usize - mem::size_of::() + } + + fn set_size(&mut self, total_size: usize) { + self.size = total_size as u32; + } +} + +#[derive(Debug, PartialEq, Eq, ptr_meta::Pointee)] +#[repr(C, align(8))] +pub struct DummyDstTag { + header: DummyTestHeader, + payload: [u8], +} + +impl DummyDstTag { + const BASE_SIZE: usize = mem::size_of::(); + + #[must_use] + pub const fn header(&self) -> &DummyTestHeader { + &self.header + } + + #[must_use] + pub const fn payload(&self) -> &[u8] { + &self.payload + } +} + +impl MaybeDynSized for DummyDstTag { + type Header = DummyTestHeader; + + const BASE_SIZE: usize = mem::size_of::(); + + fn dst_len(header: &Self::Header) -> Self::Metadata { + header.size as usize - Self::BASE_SIZE + } +} + +impl Tag for DummyDstTag { + type IDType = u32; + const ID: Self::IDType = 42; +} + #[cfg(test)] mod tests { - use super::*; - use crate::tag::TagBytesRef; use core::mem; use core::ptr::addr_of; + use crate::ALIGNMENT; + + use super::*; + #[test] fn abi() { + assert_eq!(mem::align_of::>(), ALIGNMENT); + let bytes = AlignedBytes([0]); - assert_eq!(mem::align_of_val(&bytes), ALIGNMENT); assert_eq!(bytes.as_ptr().align_offset(8), 0); assert_eq!((addr_of!(bytes[0])).align_offset(8), 0); let bytes = AlignedBytes([0, 1, 2, 3, 4, 5, 6, 7]); - assert_eq!(mem::align_of_val(&bytes), ALIGNMENT); assert_eq!(bytes.as_ptr().align_offset(8), 0); assert_eq!((addr_of!(bytes[0])).align_offset(8), 0); let bytes = AlignedBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - assert_eq!(mem::align_of_val(&bytes), ALIGNMENT); assert_eq!(bytes.as_ptr().align_offset(8), 0); assert_eq!((addr_of!(bytes[0])).align_offset(8), 0); assert_eq!((addr_of!(bytes[7])).align_offset(8), 1); assert_eq!((addr_of!(bytes[8])).align_offset(8), 0); assert_eq!((addr_of!(bytes[9])).align_offset(8), 7); } - - #[test] - fn compatible_with_tag_bytes_ref_type() { - #[rustfmt::skip] - let bytes = AlignedBytes( - [ - /* tag id */ - 0, 0, 0, 0, - /* size */ - 14, 0, 0, 0, - /* arbitrary payload */ - 1, 2, 3, 4, - 5, 6, - /* padding */ - 0, 0, - ] - ); - let _a = TagBytesRef::try_from(bytes.borrow()).unwrap(); - } } diff --git a/multiboot2-header/Cargo.toml b/multiboot2-header/Cargo.toml index c357e7c3..05fa5329 100644 --- a/multiboot2-header/Cargo.toml +++ b/multiboot2-header/Cargo.toml @@ -4,7 +4,7 @@ description = """ Library with type definitions and parsing functions for Multiboot2 headers. This library is `no_std` and can be used in bootloaders. """ -version = "0.4.0" +version = "0.5.0" authors = [ "Philipp Schuster " ] @@ -12,6 +12,7 @@ license = "MIT/Apache-2.0" edition = "2021" categories = [ "no-std", + "no-std::no-alloc", "parsing", ] keywords = [ @@ -41,7 +42,10 @@ unstable = [] [dependencies] derive_more.workspace = true -multiboot2 = { version = "0.21.0", default-features = false } +log.workspace = true +ptr_meta.workspace = true +multiboot2 = { version = "0.22.0", default-features = false } +multiboot2-common = "0.1.0" [package.metadata.docs.rs] all-features = true diff --git a/multiboot2-header/Changelog.md b/multiboot2-header/Changelog.md index 35065bc0..57e517ea 100644 --- a/multiboot2-header/Changelog.md +++ b/multiboot2-header/Changelog.md @@ -1,8 +1,20 @@ # CHANGELOG for crate `multiboot2-header` -## Unreleased +## v0.5.0 + +This release contains a major refactoring of the internals, guaranteeing +even more sanity checks for correct behaviour and lack of UB. In this release, +the `Builder` was rewritten and lots of corresponding UB in certain +corer-cases removed. Further, the builder's API was streamlined. + +If you are interested in the internals of the major refactorings recently taken +place, please head to the documentation of `multiboot2-common`. - **Breaking** All functions that returns something useful are now `#[must_use]` +- **Breaking** The builder type is now just called `Builder`. This needs the + `builder` feature. +- **Breaking:** The error type returned by `Multiboot2Header::load` has been + changed. - Updated to latest `multiboot2` dependency ## 0.4.0 (2024-05-01) diff --git a/multiboot2-header/examples/minimal.rs b/multiboot2-header/examples/minimal.rs index 2da23537..e77c02bc 100644 --- a/multiboot2-header/examples/minimal.rs +++ b/multiboot2-header/examples/minimal.rs @@ -1,14 +1,16 @@ -use multiboot2_header::builder::{HeaderBuilder, InformationRequestHeaderTagBuilder}; +use multiboot2_common::MaybeDynSized; +use multiboot2_header::Builder; use multiboot2_header::{ - HeaderTagFlag, HeaderTagISA, MbiTagType, Multiboot2Header, RelocatableHeaderTag, - RelocatableHeaderTagPreference, + HeaderTagFlag, HeaderTagISA, InformationRequestHeaderTag, MbiTagType, Multiboot2Header, + RelocatableHeaderTag, RelocatableHeaderTagPreference, }; /// Small example that creates a Multiboot2 header and parses it afterwards. fn main() { - // We create a Multiboot2 header during runtime here. A practical example is that your - // program gets the header from a file and parses it afterwards. - let mb2_hdr_bytes = HeaderBuilder::new(HeaderTagISA::I386) + // We create a Multiboot2 header during runtime here. A more practical + // example, however, would be that you parse the header from kernel binary + // at runtime. + let mb2_hdr_bytes = Builder::new(HeaderTagISA::I386) .relocatable_tag(RelocatableHeaderTag::new( HeaderTagFlag::Required, 0x1337, @@ -16,13 +18,18 @@ fn main() { 4096, RelocatableHeaderTagPreference::None, )) - .information_request_tag( - InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required) - .add_irs(&[MbiTagType::Cmdline, MbiTagType::BootLoaderName]), - ) + .information_request_tag(InformationRequestHeaderTag::new( + HeaderTagFlag::Required, + &[ + MbiTagType::Cmdline.into(), + MbiTagType::BootLoaderName.into(), + ], + )) .build(); // Cast bytes in vector to Multiboot2 information structure - let mb2_hdr = unsafe { Multiboot2Header::load(mb2_hdr_bytes.as_ptr().cast()) }; + let ptr = mb2_hdr_bytes.as_bytes().as_ptr(); + let mb2_hdr = unsafe { Multiboot2Header::load(ptr.cast()) }; + let mb2_hdr = mb2_hdr.unwrap(); println!("{:#?}", mb2_hdr); } diff --git a/multiboot2-header/src/address.rs b/multiboot2-header/src/address.rs index 7a450e9e..70d7c0ef 100644 --- a/multiboot2-header/src/address.rs +++ b/multiboot2-header/src/address.rs @@ -1,12 +1,13 @@ use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::mem::size_of; +use multiboot2_common::{MaybeDynSized, Tag}; /// This information does not need to be provided if the kernel image is in ELF /// format, but it must be provided if the image is in a.out format or in some /// other format. Required for legacy boot (BIOS). /// Determines load addresses. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct AddressHeaderTag { header: HeaderTagHeader, /// Contains the address corresponding to the beginning of the Multiboot2 header — the physical memory location at which the magic value is supposed to be loaded. This field serves to synchronize the mapping between OS image offsets and physical memory addresses. @@ -84,6 +85,19 @@ impl AddressHeaderTag { } } +impl MaybeDynSized for AddressHeaderTag { + type Header = HeaderTagHeader; + + const BASE_SIZE: usize = size_of::(); + + fn dst_len(_header: &Self::Header) -> Self::Metadata {} +} + +impl Tag for AddressHeaderTag { + type IDType = HeaderTagType; + const ID: HeaderTagType = HeaderTagType::Address; +} + #[cfg(test)] mod tests { use crate::AddressHeaderTag; diff --git a/multiboot2-header/src/builder.rs b/multiboot2-header/src/builder.rs new file mode 100644 index 00000000..9017154a --- /dev/null +++ b/multiboot2-header/src/builder.rs @@ -0,0 +1,238 @@ +//! Exports a builder [`Builder`]. + +use crate::{ + AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EntryAddressHeaderTag, + EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, HeaderTagISA, + InformationRequestHeaderTag, ModuleAlignHeaderTag, Multiboot2BasicHeader, RelocatableHeaderTag, +}; +use alloc::boxed::Box; +use alloc::vec::Vec; +use multiboot2_common::{new_boxed, DynSizedStructure, MaybeDynSized}; + +/// Builder for a Multiboot2 header information. +#[derive(Debug)] +pub struct Builder { + arch: HeaderTagISA, + information_request_tag: Option>, + address_tag: Option, + entry_tag: Option, + console_tag: Option, + framebuffer_tag: Option, + module_align_tag: Option, + efi_bs_tag: Option, + efi_32_tag: Option, + efi_64_tag: Option, + relocatable_tag: Option, + // TODO add support for custom tags once someone requests it. +} + +impl Builder { + /// Set the [`RelocatableHeaderTag`] tag. + #[must_use] + 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, + } + } + + /// Set the [`InformationRequestHeaderTag`] tag. + #[must_use] + pub fn information_request_tag( + mut self, + information_request_tag: Box, + ) -> Self { + self.information_request_tag = Some(information_request_tag); + self + } + + /// Set the [`AddressHeaderTag`] tag. + #[must_use] + pub const fn address_tag(mut self, address_tag: AddressHeaderTag) -> Self { + self.address_tag = Some(address_tag); + self + } + + /// Set the [`EntryAddressHeaderTag`] tag. + #[must_use] + pub const fn entry_tag(mut self, entry_tag: EntryAddressHeaderTag) -> Self { + self.entry_tag = Some(entry_tag); + self + } + + /// Set the [`ConsoleHeaderTag`] tag. + #[must_use] + pub const fn console_tag(mut self, console_tag: ConsoleHeaderTag) -> Self { + self.console_tag = Some(console_tag); + self + } + + /// Set the [`FramebufferHeaderTag`] tag. + #[must_use] + pub const fn framebuffer_tag(mut self, framebuffer_tag: FramebufferHeaderTag) -> Self { + self.framebuffer_tag = Some(framebuffer_tag); + self + } + + /// Set the [`ModuleAlignHeaderTag`] tag. + #[must_use] + pub const fn module_align_tag(mut self, module_align_tag: ModuleAlignHeaderTag) -> Self { + self.module_align_tag = Some(module_align_tag); + self + } + + /// Set the [`EfiBootServiceHeaderTag`] tag. + #[must_use] + pub const fn efi_bs_tag(mut self, efi_bs_tag: EfiBootServiceHeaderTag) -> Self { + self.efi_bs_tag = Some(efi_bs_tag); + self + } + + /// Set the [`EntryEfi32HeaderTag`] tag. + #[must_use] + pub const fn efi_32_tag(mut self, efi_32_tag: EntryEfi32HeaderTag) -> Self { + self.efi_32_tag = Some(efi_32_tag); + self + } + + /// Set the [`EntryEfi64HeaderTag`] tag. + #[must_use] + pub const fn efi_64_tag(mut self, efi_64_tag: EntryEfi64HeaderTag) -> Self { + self.efi_64_tag = Some(efi_64_tag); + self + } + + /// Set the [`RelocatableHeaderTag`] tag. + #[must_use] + pub const fn relocatable_tag(mut self, relocatable_tag: RelocatableHeaderTag) -> Self { + self.relocatable_tag = Some(relocatable_tag); + self + } + + /// Returns properly aligned bytes on the heap representing a valid + /// Multiboot2 header structure. + #[must_use] + pub fn build(self) -> Box> { + let header = Multiboot2BasicHeader::new(self.arch, 0); + let mut byte_refs = Vec::new(); + if let Some(tag) = self.information_request_tag.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.address_tag.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.entry_tag.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.console_tag.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.framebuffer_tag.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.module_align_tag.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.efi_bs_tag.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.efi_32_tag.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.efi_64_tag.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.relocatable_tag.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + // TODO add support for custom tags once someone requests it. + new_boxed(header, byte_refs.as_slice()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ConsoleHeaderTagFlags::ConsoleRequired; + use crate::HeaderTagFlag::{Optional, Required}; + use crate::RelocatableHeaderTagPreference::High; + use crate::{MbiTagType, Multiboot2Header}; + + #[test] + fn build_and_parse() { + let builder = Builder::new(HeaderTagISA::I386) + .information_request_tag(InformationRequestHeaderTag::new( + Optional, + &[ + MbiTagType::Cmdline.into(), + MbiTagType::BootLoaderName.into(), + MbiTagType::Module.into(), + MbiTagType::BasicMeminfo.into(), + MbiTagType::Bootdev.into(), + MbiTagType::Mmap.into(), + MbiTagType::Vbe.into(), + MbiTagType::Framebuffer.into(), + MbiTagType::ElfSections.into(), + MbiTagType::Apm.into(), + MbiTagType::Efi32.into(), + MbiTagType::Efi64.into(), + MbiTagType::Smbios.into(), + MbiTagType::AcpiV1.into(), + MbiTagType::AcpiV2.into(), + MbiTagType::Network.into(), + MbiTagType::EfiMmap.into(), + MbiTagType::EfiBs.into(), + MbiTagType::Efi32Ih.into(), + MbiTagType::Efi64Ih.into(), + MbiTagType::LoadBaseAddr.into(), + MbiTagType::Custom(0x1337).into(), + ], + )) + .address_tag(AddressHeaderTag::new( + Required, 0x1000, 0x2000, 0x3000, 0x4000, + )) + .entry_tag(EntryAddressHeaderTag::new(Required, 0x5000)) + .console_tag(ConsoleHeaderTag::new(Required, ConsoleRequired)) + .framebuffer_tag(FramebufferHeaderTag::new(Optional, 720, 1024, 8)) + .module_align_tag(ModuleAlignHeaderTag::new(Required)) + .efi_bs_tag(EfiBootServiceHeaderTag::new(Optional)) + .efi_32_tag(EntryEfi32HeaderTag::new(Required, 0x7000)) + .efi_64_tag(EntryEfi64HeaderTag::new(Required, 0x8000)) + .relocatable_tag(RelocatableHeaderTag::new( + Required, 0x9000, 0x10000, 4096, High, + )); + + let structure = builder.build(); + let header = + unsafe { Multiboot2Header::load(structure.as_bytes().as_ref().as_ptr().cast()) } + .unwrap(); + + assert!(header.verify_checksum()); + + for tag in header.iter() { + dbg!(tag); + } + + dbg!(header.arch()); + dbg!(header.checksum()); + dbg!(header.information_request_tag()); + dbg!(header.address_tag()); + dbg!(header.entry_address_tag()); + dbg!(header.console_flags_tag()); + dbg!(header.framebuffer_tag()); + dbg!(header.module_align_tag()); + dbg!(header.efi_boot_services_tag()); + dbg!(header.entry_address_efi32_tag()); + dbg!(header.entry_address_efi64_tag()); + dbg!(header.relocatable_tag()); + } +} diff --git a/multiboot2-header/src/builder/header.rs b/multiboot2-header/src/builder/header.rs deleted file mode 100644 index 2bf3ea93..00000000 --- a/multiboot2-header/src/builder/header.rs +++ /dev/null @@ -1,425 +0,0 @@ -//! Exports item [`HeaderBuilder`]. - -use crate::builder::information_request::InformationRequestHeaderTagBuilder; -use crate::builder::traits::StructAsBytes; -use crate::HeaderTagISA; -use crate::{ - AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag, - EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, - ModuleAlignHeaderTag, Multiboot2BasicHeader, RelocatableHeaderTag, -}; -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: Vec, -} - -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, -/// except for the END tag. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct HeaderBuilder { - arch: HeaderTagISA, - // first - information_request_tag: Option, - // second - address_tag: Option, - // third - entry_tag: Option, - // fourth - console_tag: Option, - // fifth - framebuffer_tag: Option, - // sixth - module_align_tag: Option, - // seventh - efi_bs_tag: Option, - // eighth - efi_32_tag: Option, - // ninth - efi_64_tag: Option, - // tenth (last) - relocatable_tag: Option, -} - -impl HeaderBuilder { - /// Creates a new builder. - #[must_use] - 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 { - (size + 7) & !7 - } - - /// Returns the expected length of the Multiboot2 header, when the - /// [`Self::build`]-method gets called. - #[must_use] - 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 - // 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::()) - } - if self.entry_tag.is_some() { - len += Self::size_or_up_aligned(size_of::()) - } - if self.console_tag.is_some() { - len += Self::size_or_up_aligned(size_of::()) - } - if self.framebuffer_tag.is_some() { - len += Self::size_or_up_aligned(size_of::()) - } - if self.module_align_tag.is_some() { - len += Self::size_or_up_aligned(size_of::()) - } - if self.efi_bs_tag.is_some() { - len += Self::size_or_up_aligned(size_of::()) - } - if self.efi_32_tag.is_some() { - len += Self::size_or_up_aligned(size_of::()) - } - if self.efi_64_tag.is_some() { - len += Self::size_or_up_aligned(size_of::()) - } - if self.relocatable_tag.is_some() { - len += Self::size_or_up_aligned(size_of::()) - } - // only here size_or_up_aligned is not important, because it is the last tag - len += size_of::(); - len - } - - /// Adds the bytes of a tag to the final Multiboot2 header byte vector. - 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(); - 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. - #[must_use] - pub fn build(self) -> HeaderBytes { - const ALIGN: usize = 8; - - // PHASE 1/2: 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/2: Add Tags - self.build_add_tags(&mut bytes); - - 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." - ); - - HeaderBytes { - 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( - 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 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(bytes, &tag.struct_as_bytes(), false) - } - if let Some(tag) = self.entry_tag.as_ref() { - Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) - } - if let Some(tag) = self.console_tag.as_ref() { - Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) - } - if let Some(tag) = self.framebuffer_tag.as_ref() { - Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) - } - if let Some(tag) = self.module_align_tag.as_ref() { - Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) - } - if let Some(tag) = self.efi_bs_tag.as_ref() { - Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) - } - if let Some(tag) = self.efi_32_tag.as_ref() { - Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) - } - if let Some(tag) = self.efi_64_tag.as_ref() { - Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) - } - if let Some(tag) = self.relocatable_tag.as_ref() { - Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false) - } - Self::build_add_bytes(bytes, &EndHeaderTag::new().struct_as_bytes(), true); - } - - // clippy thinks this can be a const fn but the compiler denies it - // #[allow(clippy::missing_const_for_fn)] - /// Adds information requests from the - /// [`InformationRequestHeaderTagBuilder`] to the builder. - #[must_use] - #[allow(clippy::missing_const_for_fn)] // only in Rust 1.70 necessary - pub fn information_request_tag( - mut self, - information_request_tag: InformationRequestHeaderTagBuilder, - ) -> Self { - self.information_request_tag = Some(information_request_tag); - self - } - - /// Adds a [`AddressHeaderTag`] to the builder. - #[must_use] - pub const fn address_tag(mut self, address_tag: AddressHeaderTag) -> Self { - self.address_tag = Some(address_tag); - self - } - - /// Adds a [`EntryAddressHeaderTag`] to the builder. - #[must_use] - pub const fn entry_tag(mut self, entry_tag: EntryAddressHeaderTag) -> Self { - self.entry_tag = Some(entry_tag); - self - } - - /// Adds a [`ConsoleHeaderTag`] to the builder. - #[must_use] - pub const fn console_tag(mut self, console_tag: ConsoleHeaderTag) -> Self { - self.console_tag = Some(console_tag); - self - } - - /// Adds a [`FramebufferHeaderTag`] to the builder. - #[must_use] - pub const fn framebuffer_tag(mut self, framebuffer_tag: FramebufferHeaderTag) -> Self { - self.framebuffer_tag = Some(framebuffer_tag); - self - } - - /// Adds a [`ModuleAlignHeaderTag`] to the builder. - #[must_use] - pub const fn module_align_tag(mut self, module_align_tag: ModuleAlignHeaderTag) -> Self { - self.module_align_tag = Some(module_align_tag); - self - } - - /// Adds a [`EfiBootServiceHeaderTag`] to the builder. - #[must_use] - pub const fn efi_bs_tag(mut self, efi_bs_tag: EfiBootServiceHeaderTag) -> Self { - self.efi_bs_tag = Some(efi_bs_tag); - self - } - - /// Adds a [`EntryEfi32HeaderTag`] to the builder. - #[must_use] - pub const fn efi_32_tag(mut self, efi_32_tag: EntryEfi32HeaderTag) -> Self { - self.efi_32_tag = Some(efi_32_tag); - self - } - - /// Adds a [`EntryEfi64HeaderTag`] to the builder. - #[must_use] - pub const fn efi_64_tag(mut self, efi_64_tag: EntryEfi64HeaderTag) -> Self { - self.efi_64_tag = Some(efi_64_tag); - self - } - - /// Adds a [`RelocatableHeaderTag`] to the builder. - #[must_use] - pub const fn relocatable_tag(mut self, relocatable_tag: RelocatableHeaderTag) -> Self { - self.relocatable_tag = Some(relocatable_tag); - self - } -} - -#[cfg(test)] -mod tests { - use crate::builder::header::HeaderBuilder; - use crate::builder::information_request::InformationRequestHeaderTagBuilder; - use crate::{ - HeaderTagFlag, HeaderTagISA, MbiTagType, Multiboot2Header, RelocatableHeaderTag, - RelocatableHeaderTagPreference, - }; - - fn create_builder() -> HeaderBuilder { - 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); - - builder - } - - #[test] - fn test_size_or_up_aligned() { - assert_eq!(0, HeaderBuilder::size_or_up_aligned(0)); - assert_eq!(8, HeaderBuilder::size_or_up_aligned(1)); - assert_eq!(8, HeaderBuilder::size_or_up_aligned(8)); - assert_eq!(16, HeaderBuilder::size_or_up_aligned(9)); - } - - /// Test of the `build` method in isolation specifically for miri to check - /// for memory issues. - #[test] - fn test_builder_miri() { - let builder = create_builder(); - let expected_len = builder.expected_len(); - assert_eq!(builder.build().as_bytes().len(), expected_len); - } - - #[test] - #[cfg_attr(miri, ignore)] - fn test_builder() { - // Step 1/2: Build Header - let mb2_hdr_data = create_builder().build(); - - // 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 - ); - - // Printing the header transitively ensures that a lot of stuff works. - 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();*/ - } -} diff --git a/multiboot2-header/src/builder/information_request.rs b/multiboot2-header/src/builder/information_request.rs deleted file mode 100644 index f5448541..00000000 --- a/multiboot2-header/src/builder/information_request.rs +++ /dev/null @@ -1,138 +0,0 @@ -use super::traits::StructAsBytes; -use crate::{HeaderTagFlag, MbiTagType}; -use crate::{InformationRequestHeaderTag, MbiTagTypeId}; -use alloc::collections::BTreeSet; -use alloc::vec::Vec; -use core::fmt::Debug; -use core::mem::size_of; -use multiboot2::TagTypeId; - -/// Helper to build the dynamically sized [`InformationRequestHeaderTag`] -/// at runtime. The information request tag has a dedicated builder because this way one -/// can dynamically attach several requests to it. Otherwise, the number of requested tags -/// must be known at compile time. -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg(feature = "builder")] -pub struct InformationRequestHeaderTagBuilder { - flag: HeaderTagFlag, - // information requests (irs) - irs: BTreeSet, -} - -#[cfg(feature = "builder")] -impl InformationRequestHeaderTagBuilder { - /// New builder. - #[must_use] - pub const fn new(flag: HeaderTagFlag) -> Self { - Self { - irs: BTreeSet::new(), - flag, - } - } - - /// Returns the expected length of the information request tag, - /// when the `build`-method gets called. - #[must_use] - pub fn expected_len(&self) -> usize { - let basic_header_size = size_of::>(); - let req_tags_size = self.irs.len() * size_of::(); - basic_header_size + req_tags_size - } - - /// Adds an [`MbiTagType`] to the information request. - #[must_use] - pub fn add_ir(mut self, tag: MbiTagType) -> Self { - self.irs.insert(tag); - self - } - - /// Adds multiple [`MbiTagType`] to the information request. - #[must_use] - pub fn add_irs(mut self, tags: &[MbiTagType]) -> Self { - self.irs.extend(tags); - self - } - - /// Builds the bytes of the dynamically sized information request header. - pub fn build(self) -> Vec { - let expected_len = self.expected_len(); - let mut data = Vec::with_capacity(expected_len); - - let basic_tag = InformationRequestHeaderTag::<0>::new( - self.flag, - [], - // we put the expected length here already, because in the next step we write - // all the tags into the byte array. We can't know this during compile time, - // therefore N is 0. - Some(expected_len as u32), - ); - data.extend(basic_tag.struct_as_bytes()); - #[cfg(debug_assertions)] - { - let basic_tag_size = size_of::>(); - assert_eq!( - data.len(), - basic_tag_size, - "the vector must be as long as the basic tag!" - ); - } - - for tag_type in self - .irs - .into_iter() - // Transform to the ABI-compatible type - .map(TagTypeId::from) - { - let bytes: [u8; 4] = (u32::from(tag_type)).to_le_bytes(); - data.extend(&bytes); - } - - debug_assert_eq!( - data.len(), - expected_len, - "the byte vector must be as long as the expected size of the struct" - ); - - data - } -} -#[cfg(test)] -mod tests { - use crate::builder::information_request::InformationRequestHeaderTagBuilder; - use crate::{HeaderTagFlag, InformationRequestHeaderTag, MbiTagType, MbiTagTypeId}; - - #[test] - #[cfg_attr(miri, ignore)] - fn test_builder() { - let builder = InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required) - .add_ir(MbiTagType::EfiMmap) - .add_ir(MbiTagType::BootLoaderName) - .add_ir(MbiTagType::Cmdline); - // type(u16) + flags(u16) + size(u32) + 3 tags (u32) - assert_eq!(builder.expected_len(), 2 + 2 + 4 + 3 * 4); - let tag = builder.build(); - let tag = unsafe { - (tag.as_ptr() as *const InformationRequestHeaderTag<3>) - .as_ref() - .unwrap() - }; - assert_eq!(tag.flags(), HeaderTagFlag::Required); - // type(u16) + flags(u16) + size(u32) + 3 tags (u32) - assert_eq!(tag.size(), 2 + 2 + 4 + 3 * 4); - assert_eq!(tag.dynamic_requests_size(), 3); - assert!(tag - .requests() - .contains(&MbiTagTypeId::from(MbiTagType::EfiMmap))); - assert!(tag - .requests() - .contains(&MbiTagTypeId::from(MbiTagType::BootLoaderName))); - assert!(tag - .requests() - .contains(&MbiTagTypeId::from(MbiTagType::Cmdline))); - assert_eq!(tag.requests().len(), 3); - assert!(!tag - .requests() - .contains(&MbiTagTypeId::from(MbiTagType::AcpiV1))); - println!("{:#?}", tag); - } -} diff --git a/multiboot2-header/src/builder/mod.rs b/multiboot2-header/src/builder/mod.rs deleted file mode 100644 index 9f4037dd..00000000 --- a/multiboot2-header/src/builder/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Module for the builder-feature. - -mod header; -mod information_request; -pub(crate) mod traits; - -pub use header::HeaderBuilder; -pub use information_request::InformationRequestHeaderTagBuilder; diff --git a/multiboot2-header/src/builder/traits.rs b/multiboot2-header/src/builder/traits.rs deleted file mode 100644 index cea09fdf..00000000 --- a/multiboot2-header/src/builder/traits.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! Module for the helper trait [`StructAsBytes`]. - -use crate::{ - AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag, - EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, - InformationRequestHeaderTag, ModuleAlignHeaderTag, Multiboot2BasicHeader, RelocatableHeaderTag, -}; -use alloc::vec::Vec; -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 header with all its tags. -pub 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::() - } - - /// Returns a byte pointer to the begin of the struct. - fn as_ptr(&self) -> *const u8 { - self as *const Self as *const u8 - } - - /// Returns the structure as a vector of its bytes. - /// The length is determined by [`Self::byte_size`]. - fn struct_as_bytes(&self) -> Vec { - let ptr = self.as_ptr(); - let bytes = unsafe { core::slice::from_raw_parts(ptr, self.byte_size()) }; - Vec::from(bytes) - } -} - -impl StructAsBytes for AddressHeaderTag {} -impl StructAsBytes for ConsoleHeaderTag {} -impl StructAsBytes for EndHeaderTag {} -impl StructAsBytes for EntryEfi32HeaderTag {} -impl StructAsBytes for EntryEfi64HeaderTag {} -impl StructAsBytes for EntryAddressHeaderTag {} -impl StructAsBytes for FramebufferHeaderTag {} -impl StructAsBytes for InformationRequestHeaderTag<0> {} -impl StructAsBytes for ModuleAlignHeaderTag {} -impl StructAsBytes for RelocatableHeaderTag {} -impl StructAsBytes for EfiBootServiceHeaderTag {} - -impl StructAsBytes for Multiboot2BasicHeader {} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[cfg_attr(miri, ignore)] - fn test_as_bytes() { - struct Foobar { - a: u32, - b: u8, - c: u128, - } - impl StructAsBytes for Foobar {} - #[allow(clippy::disallowed_names)] - let foo = Foobar { - a: 11, - b: 22, - c: 33, - }; - let bytes = foo.struct_as_bytes(); - let foo_from_bytes = unsafe { (bytes.as_ptr() as *const Foobar).as_ref().unwrap() }; - assert_eq!(bytes.len(), size_of::()); - assert_eq!(foo.a, foo_from_bytes.a); - assert_eq!(foo.b, foo_from_bytes.b); - assert_eq!(foo.c, foo_from_bytes.c); - } -} diff --git a/multiboot2-header/src/console.rs b/multiboot2-header/src/console.rs index 392a6c07..107bda5a 100644 --- a/multiboot2-header/src/console.rs +++ b/multiboot2-header/src/console.rs @@ -1,5 +1,6 @@ use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; -use core::mem::size_of; +use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; /// Possible flags for [`ConsoleHeaderTag`]. #[repr(u32)] @@ -14,7 +15,7 @@ pub enum ConsoleHeaderTagFlags { /// Tells that a console must be available in MBI. /// Only relevant for legacy BIOS. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct ConsoleHeaderTag { header: HeaderTagHeader, console_flags: ConsoleHeaderTagFlags, @@ -25,7 +26,7 @@ impl ConsoleHeaderTag { #[must_use] pub const fn new(flags: HeaderTagFlag, console_flags: ConsoleHeaderTagFlags) -> Self { let header = - HeaderTagHeader::new(HeaderTagType::ConsoleFlags, flags, size_of::() as u32); + HeaderTagHeader::new(HeaderTagType::ConsoleFlags, flags, Self::BASE_SIZE as u32); Self { header, console_flags, @@ -57,12 +58,15 @@ impl ConsoleHeaderTag { } } -#[cfg(test)] -mod tests { - use crate::ConsoleHeaderTag; +impl MaybeDynSized for ConsoleHeaderTag { + type Header = HeaderTagHeader; - #[test] - fn test_assert_size() { - assert_eq!(core::mem::size_of::(), 2 + 2 + 4 + 4); - } + const BASE_SIZE: usize = mem::size_of::() + mem::size_of::(); + + fn dst_len(_header: &Self::Header) -> Self::Metadata {} +} + +impl Tag for ConsoleHeaderTag { + type IDType = HeaderTagType; + const ID: HeaderTagType = HeaderTagType::ConsoleFlags; } diff --git a/multiboot2-header/src/end.rs b/multiboot2-header/src/end.rs index 1e806460..6c118863 100644 --- a/multiboot2-header/src/end.rs +++ b/multiboot2-header/src/end.rs @@ -1,5 +1,6 @@ use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; -use core::mem::size_of; +use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; /// Terminates a list of optional tags in a Multiboot2 header. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -21,7 +22,7 @@ impl EndHeaderTag { let header = HeaderTagHeader::new( HeaderTagType::EntryAddress, HeaderTagFlag::Required, - size_of::() as u32, + mem::size_of::() as u32, ); Self { header } } @@ -45,6 +46,19 @@ impl EndHeaderTag { } } +impl MaybeDynSized for EndHeaderTag { + type Header = HeaderTagHeader; + + const BASE_SIZE: usize = mem::size_of::(); + + fn dst_len(_header: &Self::Header) -> Self::Metadata {} +} + +impl Tag for EndHeaderTag { + type IDType = HeaderTagType; + const ID: HeaderTagType = HeaderTagType::End; +} + #[cfg(test)] mod tests { use crate::EndHeaderTag; diff --git a/multiboot2-header/src/entry_address.rs b/multiboot2-header/src/entry_address.rs index cef429e3..a4940e7b 100644 --- a/multiboot2-header/src/entry_address.rs +++ b/multiboot2-header/src/entry_address.rs @@ -1,12 +1,13 @@ use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::fmt; use core::fmt::{Debug, Formatter}; -use core::mem::size_of; +use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; /// Specifies the physical address to which the boot loader should jump in /// order to start running the operating system. Not needed for ELF files. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct EntryAddressHeaderTag { header: HeaderTagHeader, entry_addr: u32, @@ -17,7 +18,7 @@ impl EntryAddressHeaderTag { #[must_use] pub const fn new(flags: HeaderTagFlag, entry_addr: u32) -> Self { let header = - HeaderTagHeader::new(HeaderTagType::EntryAddress, flags, size_of::() as u32); + HeaderTagHeader::new(HeaderTagType::EntryAddress, flags, Self::BASE_SIZE as u32); Self { header, entry_addr } } @@ -57,12 +58,15 @@ impl Debug for EntryAddressHeaderTag { } } -#[cfg(test)] -mod tests { - use crate::EntryAddressHeaderTag; +impl MaybeDynSized for EntryAddressHeaderTag { + type Header = HeaderTagHeader; - #[test] - fn test_assert_size() { - assert_eq!(core::mem::size_of::(), 2 + 2 + 4 + 4); - } + const BASE_SIZE: usize = mem::size_of::() + mem::size_of::(); + + fn dst_len(_header: &Self::Header) -> Self::Metadata {} +} + +impl Tag for EntryAddressHeaderTag { + type IDType = HeaderTagType; + const ID: HeaderTagType = HeaderTagType::EntryAddress; } diff --git a/multiboot2-header/src/entry_efi_32.rs b/multiboot2-header/src/entry_efi_32.rs index c6ca3a71..d62d346b 100644 --- a/multiboot2-header/src/entry_efi_32.rs +++ b/multiboot2-header/src/entry_efi_32.rs @@ -1,7 +1,8 @@ use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::fmt; use core::fmt::{Debug, Formatter}; -use core::mem::size_of; +use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; /// This tag is taken into account only on EFI i386 platforms when Multiboot2 image header /// contains EFI boot services tag. Then entry point specified in ELF header and the entry address @@ -10,7 +11,7 @@ use core::mem::size_of; /// Technically, this is equivalent to the [`crate::EntryAddressHeaderTag`] but with a different /// [`crate::HeaderTagType`]. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct EntryEfi32HeaderTag { header: HeaderTagHeader, entry_addr: u32, @@ -23,7 +24,7 @@ impl EntryEfi32HeaderTag { let header = HeaderTagHeader::new( HeaderTagType::EntryAddressEFI32, flags, - size_of::() as u32, + Self::BASE_SIZE as u32, ); Self { header, entry_addr } } @@ -64,12 +65,15 @@ impl Debug for EntryEfi32HeaderTag { } } -#[cfg(test)] -mod tests { - use crate::EntryEfi32HeaderTag; +impl MaybeDynSized for EntryEfi32HeaderTag { + type Header = HeaderTagHeader; - #[test] - fn test_assert_size() { - assert_eq!(core::mem::size_of::(), 2 + 2 + 4 + 4); - } + const BASE_SIZE: usize = mem::size_of::() + mem::size_of::(); + + fn dst_len(_header: &Self::Header) -> Self::Metadata {} +} + +impl Tag for EntryEfi32HeaderTag { + type IDType = HeaderTagType; + const ID: HeaderTagType = HeaderTagType::EntryAddressEFI32; } diff --git a/multiboot2-header/src/entry_efi_64.rs b/multiboot2-header/src/entry_efi_64.rs index e145f5cf..6b855d5b 100644 --- a/multiboot2-header/src/entry_efi_64.rs +++ b/multiboot2-header/src/entry_efi_64.rs @@ -1,7 +1,8 @@ use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::fmt; use core::fmt::{Debug, Formatter}; -use core::mem::size_of; +use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; /// This tag is taken into account only on EFI amd64 platforms when Multiboot2 image header /// contains EFI boot services tag. Then entry point specified in ELF header and the entry address @@ -10,7 +11,7 @@ use core::mem::size_of; /// Technically, this is equivalent to the [`crate::EntryAddressHeaderTag`] but with a different /// [`crate::HeaderTagType`]. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct EntryEfi64HeaderTag { header: HeaderTagHeader, entry_addr: u32, @@ -23,7 +24,7 @@ impl EntryEfi64HeaderTag { let header = HeaderTagHeader::new( HeaderTagType::EntryAddressEFI64, flags, - size_of::() as u32, + Self::BASE_SIZE as u32, ); Self { header, entry_addr } } @@ -64,12 +65,15 @@ impl Debug for EntryEfi64HeaderTag { } } -#[cfg(test)] -mod tests { - use crate::EntryEfi64HeaderTag; +impl MaybeDynSized for EntryEfi64HeaderTag { + type Header = HeaderTagHeader; - #[test] - fn test_assert_size() { - assert_eq!(core::mem::size_of::(), 2 + 2 + 4 + 4); - } + const BASE_SIZE: usize = mem::size_of::() + mem::size_of::(); + + fn dst_len(_header: &Self::Header) -> Self::Metadata {} +} + +impl Tag for EntryEfi64HeaderTag { + type IDType = HeaderTagType; + const ID: HeaderTagType = HeaderTagType::EntryAddressEFI64; } diff --git a/multiboot2-header/src/framebuffer.rs b/multiboot2-header/src/framebuffer.rs index dfddb2d8..e635fb24 100644 --- a/multiboot2-header/src/framebuffer.rs +++ b/multiboot2-header/src/framebuffer.rs @@ -1,12 +1,13 @@ use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; /// Specifies the preferred graphics mode. If this tag /// is present the bootloader assumes that the payload /// has framebuffer support. Note: This is only a /// recommended mode. Only relevant on legacy BIOS. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct FramebufferHeaderTag { header: HeaderTagHeader, width: u32, @@ -18,11 +19,8 @@ impl FramebufferHeaderTag { /// Constructs a new tag. #[must_use] pub const fn new(flags: HeaderTagFlag, width: u32, height: u32, depth: u32) -> Self { - let header = HeaderTagHeader::new( - HeaderTagType::Framebuffer, - flags, - mem::size_of::() as u32, - ); + let header = + HeaderTagHeader::new(HeaderTagType::Framebuffer, flags, Self::BASE_SIZE as u32); Self { header, width, @@ -68,15 +66,15 @@ impl FramebufferHeaderTag { } } -#[cfg(test)] -mod tests { - use super::*; +impl MaybeDynSized for FramebufferHeaderTag { + type Header = HeaderTagHeader; - #[test] - fn test_assert_size() { - assert_eq!( - mem::size_of::(), - 2 + 2 + 4 + 4 + 4 + 4 - ); - } + const BASE_SIZE: usize = mem::size_of::() + 3 * mem::size_of::(); + + fn dst_len(_header: &Self::Header) -> Self::Metadata {} +} + +impl Tag for FramebufferHeaderTag { + type IDType = HeaderTagType; + const ID: HeaderTagType = HeaderTagType::Framebuffer; } diff --git a/multiboot2-header/src/header.rs b/multiboot2-header/src/header.rs index 4c3c988b..a55b14e4 100644 --- a/multiboot2-header/src/header.rs +++ b/multiboot2-header/src/header.rs @@ -1,11 +1,15 @@ use crate::{ - AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag, - EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, - HeaderTagHeader, HeaderTagISA, HeaderTagType, InformationRequestHeaderTag, - ModuleAlignHeaderTag, RelocatableHeaderTag, + AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EntryAddressHeaderTag, + EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, HeaderTagHeader, HeaderTagISA, + HeaderTagType, InformationRequestHeaderTag, ModuleAlignHeaderTag, RelocatableHeaderTag, + TagIter, }; +#[cfg(feature = "unstable")] +use core::error::Error; use core::fmt::{Debug, Formatter}; use core::mem::size_of; +use core::ptr::NonNull; +use multiboot2_common::{DynSizedStructure, Header, MemoryError, Tag, ALIGNMENT}; /// Magic value for a [`Multiboot2Header`], as defined by the spec. pub const MAGIC: u32 = 0xe85250d6; @@ -16,9 +20,8 @@ pub const MAGIC: u32 = 0xe85250d6; /// Use this if you get a pointer to the header and just want /// to parse it. If you want to construct the type by yourself, /// please look at `HeaderBuilder` (requires the `builder` feature). -#[derive(Debug)] #[repr(transparent)] -pub struct Multiboot2Header<'a>(&'a Multiboot2BasicHeader); +pub struct Multiboot2Header<'a>(&'a DynSizedStructure); impl<'a> Multiboot2Header<'a> { /// Public constructor for this type with various validations. @@ -34,22 +37,18 @@ impl<'a> Multiboot2Header<'a> { /// This function may produce undefined behaviour, if the provided `addr` is not a valid /// Multiboot2 header pointer. pub unsafe fn load(ptr: *const Multiboot2BasicHeader) -> Result { - // null or not aligned - if ptr.is_null() || ptr.align_offset(8) != 0 { - return Err(LoadError::InvalidAddress); - } - - let reference = &*ptr; + let ptr = NonNull::new(ptr.cast_mut()).ok_or(LoadError::Memory(MemoryError::Null))?; + let inner = DynSizedStructure::ref_from_ptr(ptr).map_err(LoadError::Memory)?; + let this = Self(inner); - if reference.header_magic() != MAGIC { + let header = this.0.header(); + if header.header_magic != MAGIC { return Err(LoadError::MagicNotFound); } - - if !reference.verify_checksum() { + if !header.verify_checksum() { return Err(LoadError::ChecksumMismatch); } - - Ok(Self(reference)) + Ok(this) } /// Find the header in a given slice. @@ -60,8 +59,8 @@ impl<'a> Multiboot2Header<'a> { /// or because it is truncated), it returns a [`LoadError`]. /// If there is no header, it returns `None`. pub fn find_header(buffer: &[u8]) -> Result, LoadError> { - if buffer.as_ptr().align_offset(4) != 0 { - return Err(LoadError::InvalidAddress); + if buffer.as_ptr().align_offset(ALIGNMENT) != 0 { + return Err(LoadError::Memory(MemoryError::WrongAlignment)); } let mut windows = buffer[0..8192].windows(4); @@ -73,7 +72,7 @@ impl<'a> Multiboot2Header<'a> { if idx % 8 == 0 { idx } else { - return Err(LoadError::InvalidAddress); + return Err(LoadError::Memory(MemoryError::WrongAlignment)); } } None => return Ok(None), @@ -90,7 +89,7 @@ impl<'a> Multiboot2Header<'a> { let header_length: usize = u32::from_le_bytes( windows .next() - .ok_or(LoadError::TooSmall)? + .ok_or(LoadError::Memory(MemoryError::MissingPadding))? .try_into() .unwrap(), // 4 bytes are a u32 ) @@ -102,35 +101,36 @@ impl<'a> Multiboot2Header<'a> { ))) } + /// Returns a [`TagIter`]. + #[must_use] + pub fn iter(&self) -> TagIter { + TagIter::new(self.0.payload()) + } + /// Wrapper around [`Multiboot2BasicHeader::verify_checksum`]. #[must_use] pub const fn verify_checksum(&self) -> bool { - self.0.verify_checksum() + self.0.header().verify_checksum() } /// Wrapper around [`Multiboot2BasicHeader::header_magic`]. #[must_use] pub const fn header_magic(&self) -> u32 { - self.0.header_magic() + self.0.header().header_magic() } /// Wrapper around [`Multiboot2BasicHeader::arch`]. #[must_use] pub const fn arch(&self) -> HeaderTagISA { - self.0.arch() + self.0.header().arch() } /// Wrapper around [`Multiboot2BasicHeader::length`]. #[must_use] pub const fn length(&self) -> u32 { - self.0.length() + self.0.header().length() } /// Wrapper around [`Multiboot2BasicHeader::checksum`]. #[must_use] pub const fn checksum(&self) -> u32 { - self.0.checksum() - } - /// Wrapper around [`Multiboot2BasicHeader::tag_iter`]. - #[must_use] - pub fn iter(&self) -> Multiboot2HeaderTagIter { - self.0.tag_iter() + self.0.header().checksum() } /// Wrapper around [`Multiboot2BasicHeader::calc_checksum`]. #[must_use] @@ -138,98 +138,118 @@ impl<'a> Multiboot2Header<'a> { Multiboot2BasicHeader::calc_checksum(magic, arch, length) } - /// Search for the address header tag. + /// Search for the [`InformationRequestHeaderTag`] header tag. + #[must_use] + pub fn information_request_tag(&self) -> Option<&InformationRequestHeaderTag> { + self.get_tag() + } + + /// Search for the [`AddressHeaderTag`] header tag. #[must_use] pub fn address_tag(&self) -> Option<&AddressHeaderTag> { - self.get_tag(HeaderTagType::Address) - .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const AddressHeaderTag) }) + self.get_tag() } - /// Search for the entry address header tag. + /// Search for the [`EntryAddressHeaderTag`] header tag. #[must_use] pub fn entry_address_tag(&self) -> Option<&EntryAddressHeaderTag> { - self.get_tag(HeaderTagType::EntryAddress) - .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const EntryAddressHeaderTag) }) + self.get_tag() } - /// Search for the EFI32 entry address header tag. + /// Search for the [`EntryEfi32HeaderTag`] header tag. #[must_use] pub fn entry_address_efi32_tag(&self) -> Option<&EntryEfi32HeaderTag> { - self.get_tag(HeaderTagType::EntryAddressEFI32) - .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const EntryEfi32HeaderTag) }) + self.get_tag() } - /// Search for the EFI64 entry address header tag. + /// Search for the [`EntryEfi64HeaderTag`] header tag. #[must_use] pub fn entry_address_efi64_tag(&self) -> Option<&EntryEfi64HeaderTag> { - self.get_tag(HeaderTagType::EntryAddressEFI64) - .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const EntryEfi64HeaderTag) }) + self.get_tag() } - /// Search for the console flags header tag. + /// Search for the [`ConsoleHeaderTag`] header tag. #[must_use] pub fn console_flags_tag(&self) -> Option<&ConsoleHeaderTag> { - self.get_tag(HeaderTagType::ConsoleFlags) - .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const ConsoleHeaderTag) }) + self.get_tag() } - /// Search for the framebuffer header tag. + /// Search for the [`FramebufferHeaderTag`] header tag. #[must_use] pub fn framebuffer_tag(&self) -> Option<&FramebufferHeaderTag> { - self.get_tag(HeaderTagType::Framebuffer) - .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const FramebufferHeaderTag) }) + self.get_tag() } - /// Search for the module align header tag. + /// Search for the [`ModuleAlignHeaderTag`] header tag. #[must_use] pub fn module_align_tag(&self) -> Option<&ModuleAlignHeaderTag> { - self.get_tag(HeaderTagType::ModuleAlign) - .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const ModuleAlignHeaderTag) }) + self.get_tag() } - /// Search for the EFI Boot Services header tag. + /// Search for the [`EfiBootServiceHeaderTag`] header tag. #[must_use] pub fn efi_boot_services_tag(&self) -> Option<&EfiBootServiceHeaderTag> { - self.get_tag(HeaderTagType::EfiBS).map(|tag| unsafe { - &*(tag as *const HeaderTagHeader as *const EfiBootServiceHeaderTag) - }) + self.get_tag() } - /// Search for the EFI32 entry address header tag. + /// Search for the [`RelocatableHeaderTag`] header tag. #[must_use] pub fn relocatable_tag(&self) -> Option<&RelocatableHeaderTag> { - self.get_tag(HeaderTagType::Relocatable) - .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const RelocatableHeaderTag) }) + self.get_tag() } - fn get_tag(&self, typ: HeaderTagType) -> Option<&HeaderTagHeader> { + /// Searches for the specified tag by iterating the structure and returns + /// the first occurrence, if present. + #[must_use] + fn get_tag + ?Sized + 'a>( + &'a self, + ) -> Option<&'a T> { self.iter() - .map(|tag| unsafe { tag.as_ref() }.unwrap()) - .find(|tag| tag.typ() == typ) + .find(|tag| tag.header().typ() == T::ID) + .map(|tag| tag.cast::()) + } +} + +impl Debug for Multiboot2Header<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Multiboot2Header") + .field("magic", &self.header_magic()) + .field("arch", &self.arch()) + .field("length", &self.length()) + .field("checksum", &self.checksum()) + // TODO better debug impl + .field("tags", &"") + .finish() } } -/// Errors that can occur when parsing a header from a slice. -/// See [`Multiboot2Header::find_header`]. +/// Errors that occur when a chunk of memory can't be parsed as +/// [`Multiboot2Header`]. #[derive(Copy, Clone, Debug, derive_more::Display, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum LoadError { - /// The checksum does not match the data. + /// The provided checksum does not match the expected value. ChecksumMismatch, - /// The header is not properly 64-bit aligned (or a null pointer). - InvalidAddress, /// The header does not contain the correct magic number. MagicNotFound, - /// The header is truncated. - TooSmall, + /// The provided memory can't be parsed as [`Multiboot2Header`]. + /// See [`MemoryError`]. + Memory(MemoryError), } #[cfg(feature = "unstable")] -impl core::error::Error for LoadError {} +impl Error for LoadError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::Memory(inner) => Some(inner), + _ => None, + } + } +} /// The "basic" Multiboot2 header. This means only the properties, that are known during /// compile time. All other information are derived during runtime from the size property. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct Multiboot2BasicHeader { /// Must be the value of [`MAGIC`]. header_magic: u32, @@ -290,28 +310,16 @@ impl Multiboot2BasicHeader { pub const fn checksum(&self) -> u32 { self.checksum } +} + +impl Header for Multiboot2BasicHeader { + fn payload_len(&self) -> usize { + self.length as usize - size_of::() + } - /// Returns a [`Multiboot2HeaderTagIter`]. - /// - /// # Panics - /// See doc of [`Multiboot2HeaderTagIter`]. - #[must_use] - pub fn tag_iter(&self) -> Multiboot2HeaderTagIter { - let base_hdr_size = size_of::(); - if base_hdr_size == self.length as usize { - panic!("No end tag!"); - } - let tag_base_addr = self as *const Self; - // cast to u8 so that the offset in bytes works correctly - let tag_base_addr = tag_base_addr as *const u8; - // tag_base_addr should now point behind the "static" members - let tag_base_addr = unsafe { tag_base_addr.add(base_hdr_size) }; - // align pointer to 8 byte according to spec - let tag_base_addr = unsafe { tag_base_addr.add(tag_base_addr.align_offset(8)) }; - // cast back - let tag_base_addr = tag_base_addr as *const HeaderTagHeader; - let tags_len = self.length as usize - base_hdr_size; - Multiboot2HeaderTagIter::new(tag_base_addr, tags_len as u32) + fn set_size(&mut self, total_size: usize) { + self.length = total_size as u32; + self.checksum = Self::calc_checksum(self.header_magic, self.arch, total_size as u32); } } @@ -322,152 +330,11 @@ impl Debug for Multiboot2BasicHeader { .field("arch", &{ self.arch }) .field("length", &{ self.length }) .field("checksum", &{ self.checksum }) - .field("tags", &self.tag_iter()) + //.field("tags", &self.iter()) .finish() } } -/// Iterator over all tags of a Multiboot2 header. The number of items is derived -/// by the size/length of the header. -/// -/// # Panics -/// Panics if the `length`-attribute doesn't match the number of found tags, there are -/// more tags found than technically possible, or if there is more than one end tag. -/// All of these errors come from bigger, underlying problems. Therefore, they are -/// considered as "abort/panic" and not as recoverable errors. -#[derive(Clone)] -pub struct Multiboot2HeaderTagIter { - /// 8-byte aligned base address - base: *const HeaderTagHeader, - /// Offset in bytes from the base address. - /// Always <= than size. - n: u32, - /// Size / final value of [`Self::n`]. - size: u32, - /// Counts the number of found tags. If more tags are found - /// than technically possible, for example because the length property - /// was invalid and there are hundreds of "End"-tags, we can use - /// this and enforce a hard iteration limit. - tag_count: u32, - /// Marks if the end-tag was found. Together with `tag_count`, this - /// further helps to improve safety when invalid length properties are given. - end_tag_found: bool, -} - -impl Multiboot2HeaderTagIter { - fn new(base: *const HeaderTagHeader, size: u32) -> Self { - // transform to byte pointer => offset works properly - let base = base as *const u8; - let base = unsafe { base.add(base.align_offset(8)) }; - let base = base as *const HeaderTagHeader; - Self { - base, - n: 0, - size, - tag_count: 0, - end_tag_found: false, - } - } -} - -impl Iterator for Multiboot2HeaderTagIter { - type Item = *const HeaderTagHeader; - - fn next(&mut self) -> Option { - // no more bytes left to check; length reached - if self.n >= self.size { - return None; - } - - // transform to byte ptr => offset works correctly - let ptr = self.base as *const u8; - let ptr = unsafe { ptr.add(self.n as usize) }; - let ptr = ptr as *const HeaderTagHeader; - assert_eq!(ptr as usize % 8, 0, "must be 8-byte aligned"); - let tag = unsafe { &*ptr }; - assert!( - tag.size() <= 500, - "no real mb2 header should be bigger than 500bytes - probably wrong memory?! is: {}", - { tag.size() } - ); - assert!( - tag.size() >= 8, - "no real mb2 header tag is smaller than 8 bytes - probably wrong memory?! is: {}", - { tag.size() } - ); - assert!( - !self.end_tag_found, - "There is more than one end tag! Maybe the `length` property is invalid?" - ); - self.n += tag.size(); - // 8-byte alignment of pointer address - self.n += self.n % 8; - self.tag_count += 1; - if tag.typ() == HeaderTagType::End { - self.end_tag_found = true; - } - assert!(self.tag_count < HeaderTagType::count(), "Invalid Multiboot2 header tags! There are more tags than technically possible! Maybe the `length` property is invalid?"); - Some(ptr) - } -} - -impl Debug for Multiboot2HeaderTagIter { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - let mut debug = f.debug_list(); - self.clone().for_each(|t| unsafe { - let typ = (*t).typ(); - if typ == HeaderTagType::End { - let entry = t as *const EndHeaderTag; - let entry = &*(entry); - debug.entry(entry); - } else if typ == HeaderTagType::InformationRequest { - let entry = t as *const InformationRequestHeaderTag<0>; - let entry = &*(entry); - debug.entry(entry); - } else if typ == HeaderTagType::Address { - let entry = t as *const AddressHeaderTag; - let entry = &*(entry); - debug.entry(entry); - } else if typ == HeaderTagType::EntryAddress { - let entry = t as *const EntryAddressHeaderTag; - let entry = &*(entry); - debug.entry(entry); - } else if typ == HeaderTagType::ConsoleFlags { - let entry = t as *const ConsoleHeaderTag; - let entry = &*(entry); - debug.entry(entry); - } else if typ == HeaderTagType::Framebuffer { - let entry = t as *const FramebufferHeaderTag; - let entry = &*(entry); - debug.entry(entry); - } else if typ == HeaderTagType::EfiBS { - let entry = t as *const EfiBootServiceHeaderTag; - let entry = &*(entry); - debug.entry(entry); - } else if typ == HeaderTagType::EntryAddressEFI32 { - let entry = t as *const EntryEfi32HeaderTag; - let entry = &*(entry); - debug.entry(entry); - } else if typ == HeaderTagType::EntryAddressEFI64 { - let entry = t as *const EntryEfi64HeaderTag; - let entry = &*(entry); - debug.entry(entry); - } else if typ == HeaderTagType::ModuleAlign { - let entry = t as *const ModuleAlignHeaderTag; - let entry = &*(entry); - debug.entry(entry); - } else if typ == HeaderTagType::Relocatable { - let entry = t as *const RelocatableHeaderTag; - let entry = &*(entry); - debug.entry(entry); - } else { - panic!("unknown tag ({:?})!", typ); - } - }); - debug.finish() - } -} - #[cfg(test)] mod tests { use crate::Multiboot2BasicHeader; diff --git a/multiboot2-header/src/information_request.rs b/multiboot2-header/src/information_request.rs index f19e15bc..0db71efb 100644 --- a/multiboot2-header/src/information_request.rs +++ b/multiboot2-header/src/information_request.rs @@ -1,34 +1,37 @@ -use crate::{HeaderTagFlag, HeaderTagHeader, MbiTagType}; +use crate::{HeaderTagFlag, HeaderTagHeader}; use crate::{HeaderTagType, MbiTagTypeId}; use core::fmt; use core::fmt::{Debug, Formatter}; -use core::marker::PhantomData; -use core::mem::size_of; -use multiboot2::TagType; +use core::mem; +#[cfg(feature = "builder")] +use multiboot2_common::new_boxed; +use multiboot2_common::{MaybeDynSized, Tag}; +#[cfg(feature = "builder")] +use { + alloc::boxed::Box, + core::{ptr, slice}, +}; /// Specifies what specific tag types the bootloader should provide /// inside the mbi. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] -pub struct InformationRequestHeaderTag { +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, ptr_meta::Pointee)] +#[repr(C, align(8))] +pub struct InformationRequestHeaderTag { header: HeaderTagHeader, - // Length is determined by size. - // Must be parsed during runtime with unsafe pointer magic and the size field. - requests: [MbiTagTypeId; N], + requests: [MbiTagTypeId], } -impl InformationRequestHeaderTag { - /// Creates a new object. The size parameter is the value of the size property. - /// It doesn't have to match with `N` necessarily, because during compile time we - /// can't know the size of the tag in all runtime situations. +impl InformationRequestHeaderTag { + /// Creates a new object. + #[cfg(feature = "builder")] #[must_use] - pub fn new(flags: HeaderTagFlag, requests: [MbiTagTypeId; N], size: Option) -> Self { - let header = HeaderTagHeader::new( - HeaderTagType::InformationRequest, - flags, - size.unwrap_or(size_of::() as u32), - ); - Self { header, requests } + pub fn new(flags: HeaderTagFlag, requests: &[MbiTagTypeId]) -> Box { + let header = HeaderTagHeader::new(HeaderTagType::InformationRequest, flags, 0); + let requests = unsafe { + let ptr = ptr::addr_of!(*requests); + slice::from_raw_parts(ptr.cast::(), mem::size_of_val(requests)) + }; + new_boxed(header, &[requests]) } /// Returns the [`HeaderTagType`]. @@ -49,119 +52,76 @@ impl InformationRequestHeaderTag { self.header.size() } - /// Returns the requests as array. Only works if the number of requests - /// is known at compile time. For safety and correctness during runtime, - /// you should use `req_iter()`. - #[must_use] - pub const fn requests(&self) -> [MbiTagTypeId; N] { - // cheap to copy, otherwise difficult with lifetime - self.requests - } - - /// Returns the number of [`MbiTagType`]-requests derived - /// from the `size`-property. This method is useful - /// because this struct uses a const generic, but during runtime - /// we don't know the value in almost any case. + /// Returns the requests as array #[must_use] - pub const fn dynamic_requests_size(&self) -> u32 { - let base_struct_size = size_of::>(); - let size_diff = self.size() - base_struct_size as u32; - if size_diff > 0 { - size_diff / size_of::() as u32 - } else { - 0 - } - } - - /// Returns an [`InformationRequestHeaderTagIter`]. - #[must_use] - pub const fn req_iter(&self) -> InformationRequestHeaderTagIter { - let base_struct_size = size_of::>(); - let count = self.dynamic_requests_size(); - let base_ptr = self as *const Self; - let base_ptr = base_ptr as *const u8; - let base_ptr = unsafe { base_ptr.add(base_struct_size) }; - let base_ptr = base_ptr as *const MbiTagTypeId; - InformationRequestHeaderTagIter::new(count, base_ptr) + pub const fn requests(&self) -> &[MbiTagTypeId] { + &self.requests } } -impl Debug for InformationRequestHeaderTag { +impl Debug for InformationRequestHeaderTag { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("InformationRequestHeaderTag") .field("type", &self.typ()) .field("flags", &self.flags()) .field("size", &self.size()) - .field("requests", &self.req_iter()) + .field("requests", &self.requests()) .finish() } } -/// Iterates the dynamically sized information request structure and finds all MBI tags -/// that are requested. -#[derive(Copy, Clone)] -pub struct InformationRequestHeaderTagIter<'a> { - base_ptr: *const MbiTagTypeId, - i: u32, - count: u32, - _marker: PhantomData<&'a ()>, -} +impl MaybeDynSized for InformationRequestHeaderTag { + type Header = HeaderTagHeader; -impl<'a> InformationRequestHeaderTagIter<'a> { - const fn new(count: u32, base_ptr: *const MbiTagTypeId) -> Self { - Self { - i: 0, - count, - base_ptr, - _marker: PhantomData, - } - } -} + const BASE_SIZE: usize = mem::size_of::(); -impl<'a> Iterator for InformationRequestHeaderTagIter<'a> { - type Item = MbiTagType; - - fn next(&mut self) -> Option { - if self.i < self.count { - let ptr = unsafe { self.base_ptr.offset(self.i as isize) }; - self.i += 1; - let tag_type_id = unsafe { *ptr }; - Some(TagType::from(tag_type_id)) - } else { - None - } + fn dst_len(header: &Self::Header) -> Self::Metadata { + let dst_size = header.size() as usize - Self::BASE_SIZE; + assert_eq!(dst_size % mem::size_of::(), 0); + dst_size / mem::size_of::() } } -impl<'a> Debug for InformationRequestHeaderTagIter<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut debug = f.debug_list(); - self.for_each(|e| { - debug.entry(&e); - }); - debug.finish() - } +impl Tag for InformationRequestHeaderTag { + type IDType = HeaderTagType; + const ID: HeaderTagType = HeaderTagType::InformationRequest; } #[cfg(test)] +#[cfg(feature = "builder")] mod tests { - use crate::InformationRequestHeaderTag; + use super::*; + use crate::MbiTagType; #[test] - #[allow(clippy::erasing_op)] - #[allow(clippy::identity_op)] - fn test_assert_size() { - assert_eq!( - core::mem::size_of::>(), - 2 + 2 + 4 + 0 * 4 - ); - assert_eq!( - core::mem::size_of::>(), - 2 + 2 + 4 + 1 * 4 - ); - assert_eq!( - core::mem::size_of::>(), - 2 + 2 + 4 + 2 * 4 + fn creation() { + // Main objective here is to satisfy Miri. + let _ir = InformationRequestHeaderTag::new( + HeaderTagFlag::Optional, + &[ + MbiTagType::Cmdline.into(), + MbiTagType::BootLoaderName.into(), + MbiTagType::Module.into(), + MbiTagType::BasicMeminfo.into(), + MbiTagType::Bootdev.into(), + MbiTagType::Mmap.into(), + MbiTagType::Vbe.into(), + MbiTagType::Framebuffer.into(), + MbiTagType::ElfSections.into(), + MbiTagType::Apm.into(), + MbiTagType::Efi32.into(), + MbiTagType::Efi64.into(), + MbiTagType::Smbios.into(), + MbiTagType::AcpiV1.into(), + MbiTagType::AcpiV2.into(), + MbiTagType::Network.into(), + MbiTagType::EfiMmap.into(), + MbiTagType::EfiBs.into(), + MbiTagType::Efi32Ih.into(), + MbiTagType::Efi64Ih.into(), + MbiTagType::LoadBaseAddr.into(), + MbiTagType::Custom(0x1337).into(), + ], ); } } diff --git a/multiboot2-header/src/lib.rs b/multiboot2-header/src/lib.rs index 06b6b726..f9dd25d4 100644 --- a/multiboot2-header/src/lib.rs +++ b/multiboot2-header/src/lib.rs @@ -2,34 +2,16 @@ //! headers, as well as a builder to build them at runtime. This library is //! `no_std` and can be used in bootloaders. //! -//! # Example +//! # Example: Parsing a Header //! -//! ```rust -//! use multiboot2_header::builder::{InformationRequestHeaderTagBuilder, HeaderBuilder}; -//! use multiboot2_header::{HeaderTagFlag, HeaderTagISA, MbiTagType, RelocatableHeaderTag, RelocatableHeaderTagPreference, Multiboot2Header}; -//! -//! // Small example that creates a Multiboot2 header and parses it afterwards. -//! -//! // We create a Multiboot2 header during runtime here. A practical example is that your -//! // program gets the header from a file and parses it afterwards. -//! let mb2_hdr_bytes = HeaderBuilder::new(HeaderTagISA::I386) -//! .relocatable_tag(RelocatableHeaderTag::new( -//! HeaderTagFlag::Required, -//! 0x1337, -//! 0xdeadbeef, -//! 4096, -//! RelocatableHeaderTagPreference::None, -//! )) -//! .information_request_tag( -//! InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required) -//! .add_irs(&[MbiTagType::Cmdline, MbiTagType::BootLoaderName]), -//! ) -//! .build(); -//! -//! // Cast bytes in vector to Multiboot2 information structure -//! let mb2_hdr = unsafe { Multiboot2Header::load(mb2_hdr_bytes.as_ptr().cast()) }; -//! println!("{:#?}", mb2_hdr); +//! ```no_run +//! use multiboot2_header::Multiboot2Header; //! +//! let ptr = 0x1337_0000 as *const u8 /* use real ptr here */; +//! let mb2_hdr = unsafe { Multiboot2Header::load(ptr.cast()) }.unwrap(); +//! for _tag in mb2_hdr.iter() { +//! // +//! } //! ``` //! //! ## MSRV @@ -62,6 +44,13 @@ extern crate alloc; #[cfg(test)] extern crate std; +/// Iterator over the tags of a Multiboot2 boot information. +pub type TagIter<'a> = multiboot2_common::TagIter<'a, HeaderTagHeader>; + +/// A generic version of all boot information tags. +#[cfg(test)] +pub type GenericHeaderTag = multiboot2_common::DynSizedStructure; + mod address; mod console; mod end; @@ -77,7 +66,9 @@ mod tags; mod uefi_bs; #[cfg(feature = "builder")] -pub mod builder; +mod builder; + +pub use multiboot2_common::{DynSizedStructure, MaybeDynSized, Tag}; pub use self::address::*; pub use self::console::*; @@ -92,6 +83,8 @@ pub use self::module_align::*; pub use self::relocatable::*; pub use self::tags::*; pub use self::uefi_bs::*; +#[cfg(feature = "builder")] +pub use builder::Builder; /// Re-export of [`multiboot2::TagType`] from `multiboot2`-crate. pub use multiboot2::{TagType as MbiTagType, TagTypeId as MbiTagTypeId}; diff --git a/multiboot2-header/src/module_align.rs b/multiboot2-header/src/module_align.rs index c67acc58..f15dc611 100644 --- a/multiboot2-header/src/module_align.rs +++ b/multiboot2-header/src/module_align.rs @@ -1,9 +1,10 @@ use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; -use core::mem::size_of; +use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; /// If this tag is present, provided boot modules must be page aligned. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct ModuleAlignHeaderTag { header: HeaderTagHeader, } @@ -12,8 +13,11 @@ impl ModuleAlignHeaderTag { /// Constructs a new tag. #[must_use] pub const fn new(flags: HeaderTagFlag) -> Self { - let header = - HeaderTagHeader::new(HeaderTagType::ModuleAlign, flags, size_of::() as u32); + let header = HeaderTagHeader::new( + HeaderTagType::ModuleAlign, + flags, + mem::size_of::() as u32, + ); Self { header } } @@ -36,6 +40,19 @@ impl ModuleAlignHeaderTag { } } +impl MaybeDynSized for ModuleAlignHeaderTag { + type Header = HeaderTagHeader; + + const BASE_SIZE: usize = mem::size_of::(); + + fn dst_len(_header: &Self::Header) -> Self::Metadata {} +} + +impl Tag for ModuleAlignHeaderTag { + type IDType = HeaderTagType; + const ID: HeaderTagType = HeaderTagType::ModuleAlign; +} + #[cfg(test)] mod tests { use crate::ModuleAlignHeaderTag; diff --git a/multiboot2-header/src/relocatable.rs b/multiboot2-header/src/relocatable.rs index 80cfaf96..df54e981 100644 --- a/multiboot2-header/src/relocatable.rs +++ b/multiboot2-header/src/relocatable.rs @@ -1,7 +1,8 @@ use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::fmt; use core::fmt::{Debug, Formatter}; -use core::mem::size_of; +use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; /// It contains load address placement suggestion for boot loader. Boot loader /// should follow it. ‘0’ means none, ‘1’ means load image at lowest possible address @@ -20,7 +21,7 @@ pub enum RelocatableHeaderTagPreference { /// This tag indicates that the image is relocatable. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct RelocatableHeaderTag { header: HeaderTagHeader, /// Lowest possible physical address at which image should be loaded. The bootloader cannot load any part of image below this address @@ -42,8 +43,11 @@ impl RelocatableHeaderTag { align: u32, preference: RelocatableHeaderTagPreference, ) -> Self { - let header = - HeaderTagHeader::new(HeaderTagType::Relocatable, flags, size_of::() as u32); + let header = HeaderTagHeader::new( + HeaderTagType::Relocatable, + flags, + mem::size_of::() as u32, + ); Self { header, min_addr, @@ -111,6 +115,19 @@ impl Debug for RelocatableHeaderTag { } } +impl MaybeDynSized for RelocatableHeaderTag { + type Header = HeaderTagHeader; + + const BASE_SIZE: usize = mem::size_of::(); + + fn dst_len(_header: &Self::Header) -> Self::Metadata {} +} + +impl Tag for RelocatableHeaderTag { + type IDType = HeaderTagType; + const ID: HeaderTagType = HeaderTagType::Relocatable; +} + #[cfg(test)] mod tests { use crate::RelocatableHeaderTag; diff --git a/multiboot2-header/src/tags.rs b/multiboot2-header/src/tags.rs index c51aedc0..9d9f5aa6 100644 --- a/multiboot2-header/src/tags.rs +++ b/multiboot2-header/src/tags.rs @@ -2,6 +2,9 @@ //! code at the end of the official Multiboot2 spec. These tags follow in memory right after //! [`crate::Multiboot2BasicHeader`]. +use core::mem; +use multiboot2_common::Header; + /// ISA/ARCH in Multiboot2 header. #[repr(u32)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -102,6 +105,16 @@ impl HeaderTagHeader { } } +impl Header for HeaderTagHeader { + fn payload_len(&self) -> usize { + self.size as usize - mem::size_of::() + } + + fn set_size(&mut self, total_size: usize) { + self.size = total_size as u32; + } +} + #[cfg(test)] mod tests { use crate::HeaderTagHeader; diff --git a/multiboot2-header/src/uefi_bs.rs b/multiboot2-header/src/uefi_bs.rs index ce8b0a32..dbbf63b5 100644 --- a/multiboot2-header/src/uefi_bs.rs +++ b/multiboot2-header/src/uefi_bs.rs @@ -1,10 +1,11 @@ use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; -use core::mem::size_of; +use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; /// This tag indicates that payload supports starting without terminating UEFI boot services. /// Or in other words: The payload wants to use UEFI boot services. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct EfiBootServiceHeaderTag { header: HeaderTagHeader, } @@ -13,7 +14,8 @@ impl EfiBootServiceHeaderTag { /// Constructs a new tag. #[must_use] pub const fn new(flags: HeaderTagFlag) -> Self { - let header = HeaderTagHeader::new(HeaderTagType::EfiBS, flags, size_of::() as u32); + let header = + HeaderTagHeader::new(HeaderTagType::EfiBS, flags, mem::size_of::() as u32); Self { header } } @@ -36,6 +38,19 @@ impl EfiBootServiceHeaderTag { } } +impl MaybeDynSized for EfiBootServiceHeaderTag { + type Header = HeaderTagHeader; + + const BASE_SIZE: usize = mem::size_of::(); + + fn dst_len(_header: &Self::Header) -> Self::Metadata {} +} + +impl Tag for EfiBootServiceHeaderTag { + type IDType = HeaderTagType; + const ID: HeaderTagType = HeaderTagType::EfiBS; +} + #[cfg(test)] mod tests { use crate::EfiBootServiceHeaderTag; diff --git a/multiboot2/Cargo.toml b/multiboot2/Cargo.toml index 989a91a7..23881879 100644 --- a/multiboot2/Cargo.toml +++ b/multiboot2/Cargo.toml @@ -6,7 +6,7 @@ Multiboot2-compliant bootloaders, such as GRUB. It supports all tags from the specification including full support for the sections of ELF files. This library is `no_std` and can be used in a Multiboot2-kernel. """ -version = "0.21.0" +version = "0.22.0" authors = [ "Philipp Oppermann ", "Calvin Lee ", @@ -17,6 +17,7 @@ license = "MIT/Apache-2.0" edition = "2021" categories = [ "no-std", + "no-std::no-alloc", "parsing", ] keywords = [ @@ -35,24 +36,23 @@ rust-version = "1.70" [features] default = ["builder"] -alloc = [] -builder = ["alloc"] +alloc = ["multiboot2-common/alloc"] +builder = ["alloc", "multiboot2-common/builder"] # Nightly-only features, which will eventually be stabilized. -unstable = [] +unstable = ["multiboot2-common/unstable"] [dependencies] bitflags.workspace = true derive_more.workspace = true log.workspace = true +ptr_meta.workspace = true +multiboot2-common = { version = "0.1.0", default-features = false } -ptr_meta = { version = "~0.2", default-features = false } # We only use a very basic type definition from this crate. To prevent MSRV # bumps from uefi-raw, I restrict this here. Upstream users are likely to have # two versions of this library in it, which is no problem, as we only use the # type definition. uefi-raw = { version = "~0.5", default-features = false } -[dev-dependencies] - [package.metadata.docs.rs] all-features = true diff --git a/multiboot2/Changelog.md b/multiboot2/Changelog.md index 64c38e5f..fce99e9e 100644 --- a/multiboot2/Changelog.md +++ b/multiboot2/Changelog.md @@ -1,17 +1,50 @@ # CHANGELOG for crate `multiboot2` -## Unreleased - -- +## v0.22.0 + +This release contains another major refactoring of the internals, guaranteeing +even more sanity checks for correct behaviour and lack of UB. In this release, +the `Builder` was rewritten and lots of corresponding UB in certain +corer-cases removed. Further, the builder's API was streamlined. + +If you are interested in the internals of the major refactorings recently taken +place, please head to the documentation of `multiboot2-common`. + +- **Breaking:** The builder type is now just called `Builder`. This needs the + `builder` feature. +- **Breaking:** The framebuffer tag was refactored and several bugs, memory +- issues, and UB were fixed. It is now safe to use this, but some existing + usages might break and need to be slightly adapted. +- **Breaking:** The trait `TagTrait` was removed and was replaced by a new `Tag` + trait coming from `multiboot2-common`. This only affects you if you provide + custom tag types for the library. +- **Breaking:** The error type returned by `BootInformation::load` has been + changed. + +**General Note on Safety and UB (TL;DR: Crate is Safe)** + +The major refactorings of release `0.21` and `0.22` were an incredible step +forward in code quality and memory safety. We have a comprehensive test coverage +and all tests are passed by Miri. It might be that by using fuzzing, more +corner and niche cases where UB can occur get uncovered. However, for every-day +usage with sane bootloaders that do not intentionally create malformed tags, you +are now absolutely good to go. + +Sorry for all the UB that silently slept insight many parts of the code base. +This is a community project that has grown over the years. But now, the code +base is in excellent shape! ## 0.21.0 (2024-08-17) -This release contains a massive refactoring of various internals. Now, **all -unit tests pass Miri**, thus we removed lots of undefined behaviour and +This release contains a massive refactoring of various internals. Now, **almost +**unit tests pass Miri**, thus we removed lots of undefined behaviour and increased the memory safety! 🎉 Only a small part of these internal refactorings leak to the public interface. If you don't use external custom tags, you should be fine from any refactorings. +_**Edit**: The builder and the framebuffer still contain some UB. This is fixed +in the next release._ + Please note that **all previous releases** must be considered unsafe, as they contain UB. However, it is never clear how UB results in immediate incorrect behaviour and it _might_ work. **Nevertheless, please migrate to the latest diff --git a/multiboot2/src/boot_information.rs b/multiboot2/src/boot_information.rs index 8ed11b6b..6eb6628d 100644 --- a/multiboot2/src/boot_information.rs +++ b/multiboot2/src/boot_information.rs @@ -1,40 +1,41 @@ //! Module for [`BootInformation`]. -#[cfg(feature = "builder")] -use crate::builder::AsBytes; use crate::framebuffer::UnknownFramebufferType; -use crate::tag::{TagHeader, TagIter}; +use crate::tag::TagHeader; use crate::{ module, BasicMemoryInfoTag, BootLoaderNameTag, CommandLineTag, EFIBootServicesNotExitedTag, EFIImageHandle32Tag, EFIImageHandle64Tag, EFIMemoryMapTag, EFISdt32Tag, EFISdt64Tag, ElfSectionIter, ElfSectionsTag, EndTag, FramebufferTag, ImageLoadPhysAddrTag, MemoryMapTag, - ModuleIter, RsdpV1Tag, RsdpV2Tag, SmbiosTag, TagTrait, VBEInfoTag, + ModuleIter, RsdpV1Tag, RsdpV2Tag, SmbiosTag, TagIter, TagType, VBEInfoTag, }; +#[cfg(feature = "unstable")] +use core::error::Error; use core::fmt; use core::mem; -use core::ptr; +use core::ptr::NonNull; use derive_more::Display; +use multiboot2_common::{DynSizedStructure, Header, MaybeDynSized, MemoryError, Tag}; -/// Error type that describes errors while loading/parsing a multiboot2 information structure -/// from a given address. +/// Errors that occur when a chunk of memory can't be parsed as +/// [`BootInformation`]. #[derive(Display, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum MbiLoadError { - /// The address is invalid. Make sure that the address is 8-byte aligned, - /// according to the spec. - #[display(fmt = "The address is invalid")] - IllegalAddress, - /// The total size of the multiboot2 information structure must be not zero - /// and a multiple of 8. - #[display(fmt = "The size of the MBI is unexpected")] - IllegalTotalSize(u32), - /// Missing end tag. Each multiboot2 boot information requires to have an - /// end tag. - #[display(fmt = "There is no end tag")] +pub enum LoadError { + /// The provided memory can't be parsed as [`BootInformation`]. + /// See [`MemoryError`]. + Memory(MemoryError), + /// Missing mandatory end tag. NoEndTag, } #[cfg(feature = "unstable")] -impl core::error::Error for MbiLoadError {} +impl Error for LoadError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::Memory(inner) => Some(inner), + Self::NoEndTag => None, + } + } +} /// The basic header of a [`BootInformation`] as sized Rust type. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -62,40 +63,19 @@ impl BootInformationHeader { } } -#[cfg(feature = "builder")] -impl AsBytes for BootInformationHeader {} - -/// This type holds the whole data of the MBI. This helps to better satisfy miri -/// when it checks for memory issues. -#[derive(ptr_meta::Pointee)] -#[repr(C, align(8))] -struct BootInformationInner { - header: BootInformationHeader, - tags: [u8], -} - -impl BootInformationInner { - /// Checks if the MBI has a valid end tag by checking the end of the mbi's - /// bytes. - fn has_valid_end_tag(&self) -> bool { - let self_ptr = ptr::addr_of!(*self); - - let end_tag_ptr = unsafe { - self_ptr - .cast::() - .add(self.header.total_size as usize) - .sub(mem::size_of::()) - .cast::() - }; - let end_tag = unsafe { &*end_tag_ptr }; +impl Header for BootInformationHeader { + fn payload_len(&self) -> usize { + self.total_size as usize - mem::size_of::() + } - end_tag.typ == EndTag::ID && end_tag.size as usize == mem::size_of::() + fn set_size(&mut self, total_size: usize) { + self.total_size = total_size as u32; } } /// A Multiboot 2 Boot Information (MBI) accessor. #[repr(transparent)] -pub struct BootInformation<'a>(&'a BootInformationInner); +pub struct BootInformation<'a>(&'a DynSizedStructure); impl<'a> BootInformation<'a> { /// Loads the [`BootInformation`] from a pointer. The pointer must be valid @@ -115,36 +95,38 @@ impl<'a> BootInformation<'a> { /// ``` /// /// ## Safety - /// * `ptr` must be valid for reading. Otherwise this function might cause + /// * `ptr` must be valid for reading. Otherwise, this function might cause /// invalid machine state or crash your binary (kernel). This can be the /// case in environments with standard environment (segfault), but also in /// boot environments, such as UEFI. /// * The memory at `ptr` must not be modified after calling `load` or the /// program may observe unsynchronized mutation. - pub unsafe fn load(ptr: *const BootInformationHeader) -> Result { - // null or not aligned - if ptr.is_null() || ptr.align_offset(8) != 0 { - return Err(MbiLoadError::IllegalAddress); - } - - // mbi: reference to basic header - let mbi = &*ptr; + pub unsafe fn load(ptr: *const BootInformationHeader) -> Result { + let ptr = NonNull::new(ptr.cast_mut()).ok_or(LoadError::Memory(MemoryError::Null))?; + let inner = DynSizedStructure::ref_from_ptr(ptr).map_err(LoadError::Memory)?; - // Check if total size is not 0 and a multiple of 8. - if mbi.total_size == 0 || mbi.total_size & 0b111 != 0 { - return Err(MbiLoadError::IllegalTotalSize(mbi.total_size)); + let this = Self(inner); + if !this.has_valid_end_tag() { + return Err(LoadError::NoEndTag); } + Ok(this) + } - let slice_size = mbi.total_size as usize - mem::size_of::(); - // mbi: reference to full mbi - let mbi = ptr_meta::from_raw_parts::(ptr.cast(), slice_size); - let mbi = &*mbi; - - if !mbi.has_valid_end_tag() { - return Err(MbiLoadError::NoEndTag); - } + /// Checks if the MBI has a valid end tag by checking the end of the mbi's + /// bytes. + fn has_valid_end_tag(&self) -> bool { + let header = self.0.header(); + let end_tag_ptr = unsafe { + self.0 + .payload() + .as_ptr() + .add(header.payload_len()) + .sub(mem::size_of::()) + .cast::() + }; + let end_tag = unsafe { &*end_tag_ptr }; - Ok(Self(mbi)) + end_tag.typ == EndTag::ID && end_tag.size as usize == mem::size_of::() } /// Get the start address of the boot info. @@ -177,7 +159,7 @@ impl<'a> BootInformation<'a> { /// Get the total size of the boot info struct. #[must_use] pub const fn total_size(&self) -> usize { - self.0.header.total_size as usize + self.0.header().total_size as usize } // ###################################################### @@ -279,7 +261,7 @@ impl<'a> BootInformation<'a> { pub fn elf_sections(&self) -> Option { let tag = self.get_tag::(); tag.map(|t| { - assert!((t.entry_size() * t.shndx()) <= t.size() as u32); + assert!((t.entry_size() * t.shndx()) <= t.header().size); t.sections_iter() }) } @@ -289,10 +271,12 @@ impl<'a> BootInformation<'a> { #[must_use] pub fn framebuffer_tag(&self) -> Option> { self.get_tag::() - .map(|tag| match tag.buffer_type() { - Ok(_) => Ok(tag), - Err(e) => Err(e), - }) + // TODO temporarily. Someone needs to fix the framebuffer thingy. + .map(Ok) + /*.map(|tag| match tag.buffer_type() { + Ok(_) => Ok(tag), + Err(e) => Err(e), + })*/ } /// Search for the Image Load Base Physical Address tag. @@ -361,34 +345,44 @@ impl<'a> BootInformation<'a> { /// special handling is required. This is reflected by code-comments. /// /// ```no_run - /// use multiboot2::{BootInformation, BootInformationHeader, parse_slice_as_string, StringError, TagHeader, TagTrait, TagType, TagTypeId}; + /// use std::mem; + /// use multiboot2::{BootInformation, BootInformationHeader, parse_slice_as_string, StringError, TagHeader, TagType, TagTypeId}; /// + /// use multiboot2_common::{MaybeDynSized, Tag}; /// /// #[repr(C)] /// #[derive(multiboot2::Pointee)] // Only needed for DSTs. /// struct CustomTag { - /// tag: TagTypeId, - /// size: u32, - /// // begin of inline string + /// header: TagHeader, + /// some_other_prop: u32, + /// // Begin of C string, for example. /// name: [u8], /// } /// - /// // This implementation is only necessary for tags that are DSTs. - /// impl TagTrait for CustomTag { - /// const ID: TagType = TagType::Custom(0x1337); + /// impl CustomTag { + /// fn name(&self) -> Result<&str, StringError> { + /// parse_slice_as_string(&self.name) + /// } + /// } + /// + /// // Give the library hints how big this tag is. + /// impl MaybeDynSized for CustomTag { + /// type Header = TagHeader; + /// const BASE_SIZE: usize = mem::size_of::() + mem::size_of::(); /// + /// // This differs for DSTs and normal structs. See function + /// // documentation. /// fn dst_len(header: &TagHeader) -> usize { - /// // The size of the sized portion of the custom tag. - /// let tag_base_size = 8; // id + size is 8 byte in size - /// assert!(header.size >= 8); - /// header.size as usize - tag_base_size + /// assert!(header.size >= Self::BASE_SIZE as u32); + /// header.size as usize - Self::BASE_SIZE /// } /// } /// - /// impl CustomTag { - /// fn name(&self) -> Result<&str, StringError> { - /// parse_slice_as_string(&self.name) - /// } + /// // Make the Tag identifiable. + /// impl Tag for CustomTag { + /// type IDType = TagType; + /// const ID: TagType = TagType::Custom(0x1337); /// } + /// /// let mbi_ptr = 0xdeadbeef as *const BootInformationHeader; /// let mbi = unsafe { BootInformation::load(mbi_ptr).unwrap() }; /// @@ -400,15 +394,17 @@ impl<'a> BootInformation<'a> { /// /// [`TagType`]: crate::TagType #[must_use] - pub fn get_tag(&'a self) -> Option<&'a TagT> { + pub fn get_tag + ?Sized + 'a>( + &'a self, + ) -> Option<&'a T> { self.tags() - .find(|tag| tag.header().typ == TagT::ID) - .map(|tag| tag.cast::()) + .find(|tag| tag.header().typ == T::ID) + .map(|tag| tag.cast::()) } /// Returns an iterator over all tags. - fn tags(&self) -> TagIter { - TagIter::new(&self.0.tags) + pub(crate) fn tags(&self) -> TagIter { + TagIter::new(self.0.payload()) } } diff --git a/multiboot2/src/boot_loader_name.rs b/multiboot2/src/boot_loader_name.rs index 60a2cc74..1b4e45dd 100644 --- a/multiboot2/src/boot_loader_name.rs +++ b/multiboot2/src/boot_loader_name.rs @@ -1,13 +1,12 @@ //! Module for [`BootLoaderNameTag`]. use crate::tag::TagHeader; -use crate::{parse_slice_as_string, StringError, TagTrait, TagType}; +use crate::{parse_slice_as_string, StringError, TagType}; use core::fmt::{Debug, Formatter}; use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; #[cfg(feature = "builder")] -use {crate::new_boxed, alloc::boxed::Box}; - -const METADATA_SIZE: usize = mem::size_of::(); +use {alloc::boxed::Box, multiboot2_common::new_boxed}; /// The bootloader name tag. #[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -23,11 +22,12 @@ impl BootLoaderNameTag { #[cfg(feature = "builder")] #[must_use] pub fn new(name: &str) -> Box { + let header = TagHeader::new(Self::ID, 0); let bytes = name.as_bytes(); if bytes.ends_with(&[0]) { - new_boxed(&[bytes]) + new_boxed(header, &[bytes]) } else { - new_boxed(&[bytes, &[0]]) + new_boxed(header, &[bytes, &[0]]) } } @@ -75,21 +75,29 @@ impl Debug for BootLoaderNameTag { } } -impl TagTrait for BootLoaderNameTag { - const ID: TagType = TagType::BootLoaderName; +impl MaybeDynSized for BootLoaderNameTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::(); fn dst_len(header: &TagHeader) -> usize { - assert!(header.size as usize >= METADATA_SIZE); - header.size as usize - METADATA_SIZE + assert!(header.size as usize >= Self::BASE_SIZE); + header.size as usize - Self::BASE_SIZE } } +impl Tag for BootLoaderNameTag { + type IDType = TagType; + + const ID: TagType = TagType::BootLoaderName; +} + #[cfg(test)] mod tests { use super::*; - use crate::tag::{GenericTag, TagBytesRef}; - use crate::test_util::AlignedBytes; + use crate::GenericInfoTag; use core::borrow::Borrow; + use multiboot2_common::test_utils::AlignedBytes; #[rustfmt::skip] fn get_bytes() -> AlignedBytes<16> { @@ -106,8 +114,7 @@ mod tests { #[test] fn test_parse_str() { let bytes = get_bytes(); - let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); - let tag = GenericTag::ref_from(bytes); + let tag = GenericInfoTag::ref_from_slice(bytes.borrow()).unwrap(); let tag = tag.cast::(); assert_eq!(tag.header.typ, TagType::BootLoaderName); assert_eq!(tag.name(), Ok("hello")); @@ -118,14 +125,16 @@ mod tests { #[cfg(feature = "builder")] fn test_build_str() { let tag = BootLoaderNameTag::new("hello"); - let bytes = tag.as_bytes(); - assert_eq!(bytes, &get_bytes()[..tag.size()]); + let bytes = tag.as_bytes().as_ref(); + let bytes = &bytes[..tag.header.size as usize]; + assert_eq!(bytes, &get_bytes()[..tag.header.size as usize]); assert_eq!(tag.name(), Ok("hello")); // With terminating null. let tag = BootLoaderNameTag::new("hello\0"); - let bytes = tag.as_bytes(); - assert_eq!(bytes, &get_bytes()[..tag.size()]); + let bytes = tag.as_bytes().as_ref(); + let bytes = &bytes[..tag.header.size as usize]; + assert_eq!(bytes, &get_bytes()[..tag.header.size as usize]); assert_eq!(tag.name(), Ok("hello")); // test also some bigger message diff --git a/multiboot2/src/builder.rs b/multiboot2/src/builder.rs new file mode 100644 index 00000000..6ee4dea6 --- /dev/null +++ b/multiboot2/src/builder.rs @@ -0,0 +1,346 @@ +//! Module for [`Builder`]. + +use crate::{ + BasicMemoryInfoTag, BootInformationHeader, BootLoaderNameTag, CommandLineTag, + EFIBootServicesNotExitedTag, EFIImageHandle32Tag, EFIImageHandle64Tag, EFIMemoryMapTag, + EFISdt32Tag, EFISdt64Tag, ElfSectionsTag, EndTag, FramebufferTag, ImageLoadPhysAddrTag, + MemoryMapTag, ModuleTag, RsdpV1Tag, RsdpV2Tag, SmbiosTag, TagHeader, TagType, VBEInfoTag, +}; +use alloc::boxed::Box; +use alloc::vec::Vec; +use multiboot2_common::{new_boxed, DynSizedStructure, MaybeDynSized}; + +/// Builder for a Multiboot2 header information. +// #[derive(Debug)] +#[derive(Debug)] +pub struct Builder { + cmdline: Option>, + bootloader: Option>, + modules: Vec>, + meminfo: Option, + // missing bootdev: Option + mmap: Option>, + vbe: Option, + framebuffer: Option>, + elf_sections: Option>, + // missing apm: + efi32: Option, + efi64: Option, + smbios: Vec>, + rsdpv1: Option, + rsdpv2: Option, + // missing: network + efi_mmap: Option>, + efi_bs: Option, + efi32_ih: Option, + efi64_ih: Option, + image_load_addr: Option, + custom_tags: Vec>>, +} + +impl Default for Builder { + fn default() -> Self { + Self::new() + } +} + +impl Builder { + /// Creates a new builder. + #[must_use] + pub const fn new() -> Self { + Self { + cmdline: None, + bootloader: None, + modules: vec![], + meminfo: None, + mmap: None, + vbe: None, + framebuffer: None, + elf_sections: None, + efi32: None, + efi64: None, + smbios: vec![], + rsdpv1: None, + rsdpv2: None, + efi_mmap: None, + efi_bs: None, + efi32_ih: None, + efi64_ih: None, + image_load_addr: None, + custom_tags: vec![], + } + } + + /// Sets the [`CommandLineTag`] tag. + #[must_use] + pub fn cmdline(mut self, cmdline: Box) -> Self { + self.cmdline = Some(cmdline); + self + } + + /// Sets the [`BootLoaderNameTag`] tag. + #[must_use] + pub fn bootloader(mut self, bootloader: Box) -> Self { + self.bootloader = Some(bootloader); + self + } + + /// Adds a [`ModuleTag`] tag. + #[must_use] + pub fn add_module(mut self, module: Box) -> Self { + self.modules.push(module); + self + } + + /// Sets the [`BasicMemoryInfoTag`] tag. + #[must_use] + pub const fn meminfo(mut self, meminfo: BasicMemoryInfoTag) -> Self { + self.meminfo = Some(meminfo); + self + } + + /// Sets the [`MemoryMapTag`] tag. + #[must_use] + pub fn mmap(mut self, mmap: Box) -> Self { + self.mmap = Some(mmap); + self + } + + /// Sets the [`VBEInfoTag`] tag. + #[must_use] + pub const fn vbe(mut self, vbe: VBEInfoTag) -> Self { + self.vbe = Some(vbe); + self + } + + /// Sets the [`FramebufferTag`] tag. + #[must_use] + pub fn framebuffer(mut self, framebuffer: Box) -> Self { + self.framebuffer = Some(framebuffer); + self + } + + /// Sets the [`ElfSectionsTag`] tag. + #[must_use] + pub fn elf_sections(mut self, elf_sections: Box) -> Self { + self.elf_sections = Some(elf_sections); + self + } + + /// Sets the [`EFISdt32Tag`] tag. + #[must_use] + pub const fn efi32(mut self, efi32: EFISdt32Tag) -> Self { + self.efi32 = Some(efi32); + self + } + + /// Sets the [`EFISdt64Tag`] tag. + #[must_use] + pub const fn efi64(mut self, efi64: EFISdt64Tag) -> Self { + self.efi64 = Some(efi64); + self + } + + /// Adds a [`SmbiosTag`] tag. + #[must_use] + pub fn add_smbios(mut self, smbios: Box) -> Self { + self.smbios.push(smbios); + self + } + + /// Sets the [`RsdpV1Tag`] tag. + #[must_use] + pub const fn rsdpv1(mut self, rsdpv1: RsdpV1Tag) -> Self { + self.rsdpv1 = Some(rsdpv1); + self + } + + /// Sets the [`RsdpV2Tag`] tag. + #[must_use] + pub const fn rsdpv2(mut self, rsdpv2: RsdpV2Tag) -> Self { + self.rsdpv2 = Some(rsdpv2); + self + } + + /// Sets the [`EFIMemoryMapTag`] tag. + #[must_use] + pub fn efi_mmap(mut self, efi_mmap: Box) -> Self { + self.efi_mmap = Some(efi_mmap); + self + } + + /// Sets the [`EFIBootServicesNotExitedTag`] tag. + #[must_use] + pub const fn efi_bs(mut self, efi_bs: EFIBootServicesNotExitedTag) -> Self { + self.efi_bs = Some(efi_bs); + self + } + + /// Sets the [`EFIImageHandle32Tag`] tag. + #[must_use] + pub const fn efi32_ih(mut self, efi32_ih: EFIImageHandle32Tag) -> Self { + self.efi32_ih = Some(efi32_ih); + self + } + + /// Sets the [`EFIImageHandle64Tag`] tag. + #[must_use] + pub const fn efi64_ih(mut self, efi64_ih: EFIImageHandle64Tag) -> Self { + self.efi64_ih = Some(efi64_ih); + self + } + + /// Sets the [`ImageLoadPhysAddrTag`] tag. + #[must_use] + pub const fn image_load_addr(mut self, image_load_addr: ImageLoadPhysAddrTag) -> Self { + self.image_load_addr = Some(image_load_addr); + self + } + + /// Adds a custom tag. + #[must_use] + pub fn add_custom_tag(mut self, custom_tag: Box>) -> Self { + if let TagType::Custom(_c) = custom_tag.header().typ.into() { + self.custom_tags.push(custom_tag); + } else { + panic!("Only for custom types!"); + } + self + } + + /// Returns properly aligned bytes on the heap representing a valid + /// Multiboot2 header structure. + #[must_use] + pub fn build(self) -> Box> { + let header = BootInformationHeader::new(0); + let mut byte_refs = Vec::new(); + if let Some(tag) = self.cmdline.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.bootloader.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + for i in &self.modules { + byte_refs.push(i.as_bytes().as_ref()); + } + if let Some(tag) = self.meminfo.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.mmap.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.vbe.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.framebuffer.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.elf_sections.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.efi32.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.efi64.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + for i in &self.smbios { + byte_refs.push(i.as_bytes().as_ref()); + } + if let Some(tag) = self.rsdpv1.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.rsdpv2.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.efi_mmap.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.efi_bs.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.efi32_ih.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.efi64_ih.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + if let Some(tag) = self.image_load_addr.as_ref() { + byte_refs.push(tag.as_bytes().as_ref()); + } + for i in &self.custom_tags { + byte_refs.push(i.as_bytes().as_ref()); + } + let end_tag = EndTag::default(); + byte_refs.push(end_tag.as_bytes().as_ref()); + new_boxed(header, byte_refs.as_slice()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + BootInformation, FramebufferType, MemoryArea, MemoryAreaType, VBEControlInfo, VBEModeInfo, + }; + use uefi_raw::table::boot::MemoryDescriptor; + + #[test] + fn build_and_parse() { + let builder = Builder::new() + .cmdline(CommandLineTag::new("this is a command line")) + .bootloader(BootLoaderNameTag::new("this is the bootloader")) + .add_module(ModuleTag::new(0x1000, 0x2000, "module 1")) + .add_module(ModuleTag::new(0x3000, 0x4000, "module 2")) + .meminfo(BasicMemoryInfoTag::new(0x4000, 0x5000)) + .mmap(MemoryMapTag::new(&[MemoryArea::new( + 0x1000000, + 0x1000, + MemoryAreaType::Available, + )])) + .vbe(VBEInfoTag::new( + 42, + 2, + 4, + 9, + VBEControlInfo::default(), + VBEModeInfo::default(), + )) + // Currently causes UB. + .framebuffer(FramebufferTag::new( + 0x1000, + 1, + 756, + 1024, + 8, + FramebufferType::Text, + )) + .elf_sections(ElfSectionsTag::new(0, 32, 0, &[])) + .efi32(EFISdt32Tag::new(0x1000)) + .efi64(EFISdt64Tag::new(0x1000)) + .add_smbios(SmbiosTag::new(0, 0, &[1, 2, 3])) + .add_smbios(SmbiosTag::new(1, 1, &[4, 5, 6])) + .rsdpv1(RsdpV1Tag::new(0, *b"abcdef", 5, 6)) + .rsdpv2(RsdpV2Tag::new(0, *b"abcdef", 5, 6, 5, 4, 7)) + .efi_mmap(EFIMemoryMapTag::new_from_descs(&[ + MemoryDescriptor::default(), + MemoryDescriptor::default(), + ])) + .efi_bs(EFIBootServicesNotExitedTag::new()) + .efi32_ih(EFIImageHandle32Tag::new(0x1000)) + .efi64_ih(EFIImageHandle64Tag::new(0x1000)) + .image_load_addr(ImageLoadPhysAddrTag::new(0x1000)) + .add_custom_tag(new_boxed::>( + TagHeader::new(TagType::Custom(0x1337), 0), + &[], + )); + + let structure = builder.build(); + + let info = unsafe { BootInformation::load(structure.as_bytes().as_ptr().cast()) }.unwrap(); + for tag in info.tags() { + // Mainly a test for Miri. + dbg!(tag.header(), tag.payload().len()); + } + } +} diff --git a/multiboot2/src/builder/information.rs b/multiboot2/src/builder/information.rs deleted file mode 100644 index a52a48da..00000000 --- a/multiboot2/src/builder/information.rs +++ /dev/null @@ -1,391 +0,0 @@ -//! Exports item [`InformationBuilder`]. -use crate::builder::AsBytes; -use crate::util::increase_to_alignment; -use crate::{ - BasicMemoryInfoTag, BootInformationHeader, BootLoaderNameTag, CommandLineTag, - EFIBootServicesNotExitedTag, EFIImageHandle32Tag, EFIImageHandle64Tag, EFIMemoryMapTag, - EFISdt32Tag, EFISdt64Tag, ElfSectionsTag, EndTag, FramebufferTag, ImageLoadPhysAddrTag, - MemoryMapTag, ModuleTag, RsdpV1Tag, RsdpV2Tag, SmbiosTag, TagTrait, TagType, VBEInfoTag, - ALIGNMENT, -}; -use alloc::vec::Vec; -use core::fmt::{Display, Formatter}; -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: Vec, -} - -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() - } -} - -type SerializedTag = Vec; - -/// Error that indicates a tag was added multiple times that is not allowed to -/// be there multiple times. -#[derive(Debug)] -#[allow(unused)] -pub struct RedundantTagError(TagType); - -impl Display for RedundantTagError { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!(f, "{:?}", self) - } -} - -#[cfg(feature = "unstable")] -impl core::error::Error for RedundantTagError {} - -/// Builder to construct a valid Multiboot2 information dynamically at runtime. -/// The tags will appear in the order of their corresponding enumeration, -/// except for the END tag. -#[derive(Debug, PartialEq, Eq)] -pub struct InformationBuilder(Vec<(TagType, SerializedTag)>); - -impl Default for InformationBuilder { - fn default() -> Self { - Self::new() - } -} - -impl InformationBuilder { - /// Creates a new builder. - #[must_use] - pub const fn new() -> Self { - Self(Vec::new()) - } - - /// Returns the expected length of the boot information, when the - /// [`Self::build`]-method is called. This function assumes that the begin - /// of the boot information is 8-byte aligned and automatically adds padding - /// between tags to ensure that each tag is 8-byte aligned. - #[must_use] - pub fn expected_len(&self) -> usize { - let tag_size_iter = self.0.iter().map(|(_, bytes)| bytes.len()); - - let payload_tags_size = - tag_size_iter.fold(0, |acc, tag_size| acc + increase_to_alignment(tag_size)); - - size_of::() + payload_tags_size + size_of::() - } - - /// Adds the bytes of a tag to the final Multiboot2 information byte vector. - fn build_add_tag(dest_buf: &mut Vec, tag_serialized: &[u8], tag_type: TagType) { - let vec_next_write_ptr = unsafe { dest_buf.as_ptr().add(dest_buf.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_buf.extend(tag_serialized); - - if tag_type != TagType::End { - let size = tag_serialized.len(); - let size_to_8_align = increase_to_alignment(size); - let size_to_8_align_diff = size_to_8_align - size; - // fill zeroes so that next data block is 8-byte aligned - dest_buf.extend([0].repeat(size_to_8_align_diff)); - } - } - - /// Constructs the bytes for a valid Multiboot2 information with the given properties. - #[must_use] - pub fn build(self) -> BootInformationBytes { - // PHASE 1/2: 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(ALIGNMENT); - bytes.extend([0].repeat(offset)); - - // ----------------------------------------------- - // PHASE 2/2: Add Tags - bytes.extend(BootInformationHeader::new(self.expected_len() as u32).as_bytes()); - - for (tag_type, tag_serialized) in self.0 { - Self::build_add_tag(&mut bytes, tag_serialized.as_slice(), tag_type) - } - Self::build_add_tag(&mut bytes, EndTag::default().as_bytes(), TagType::End); - - 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." - ); - - BootInformationBytes { - offset, - bytes, - structure_len: expected_len, - } - } - - /// Adds a arbitrary tag that implements [`TagTrait`] to the builder. Only - /// [`TagType::Module`] and [`TagType::Custom`] are allowed to occur - /// multiple times. For other tags, this function returns an error. - /// - /// It is not required to manually add the [`TagType::End`] tag. - /// - /// The tags of the boot information will be ordered naturally, i.e., by - /// their numeric ID. - pub fn add_tag(mut self, tag: &T) -> Result { - // not required to do this manually - if T::ID == TagType::End { - return Ok(self); - } - - let is_redundant_tag = self - .0 - .iter() - .map(|(typ, _)| *typ) - // TODO make a type for tag_is_allowed_multiple_times so that we can - // link to it in the doc. - .any(|typ| typ == T::ID && !Self::tag_is_allowed_multiple_times(typ)); - - if is_redundant_tag { - log::debug!( - "Can't add tag of type {:?}. Only Module tags and Custom tags are allowed to appear multiple times.", - T::ID - ); - return Err(RedundantTagError(T::ID)); - } - self.0.push((T::ID, tag.as_bytes().to_vec())); - self.0.sort_by_key(|(typ, _)| *typ); - - Ok(self) - } - - /// Adds a 'basic memory information' tag (represented by [`BasicMemoryInfoTag`]) to the builder. - #[must_use] - pub fn basic_memory_info_tag(self, tag: &BasicMemoryInfoTag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'bootloader name' tag (represented by [`BootLoaderNameTag`]) to the builder. - #[must_use] - pub fn bootloader_name_tag(self, tag: &BootLoaderNameTag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'command line' tag (represented by [`CommandLineTag`]) to the builder. - #[must_use] - pub fn command_line_tag(self, tag: &CommandLineTag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'EFI 32-bit system table pointer' tag (represented by [`EFISdt32Tag`]) to the builder. - #[must_use] - pub fn efisdt32_tag(self, tag: &EFISdt32Tag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'EFI 64-bit system table pointer' tag (represented by [`EFISdt64Tag`]) to the builder. - #[must_use] - pub fn efisdt64_tag(self, tag: &EFISdt64Tag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'EFI boot services not terminated' tag (represented by [`EFIBootServicesNotExitedTag`]) to the builder. - #[must_use] - pub fn efi_boot_services_not_exited_tag(self) -> Self { - self.add_tag(&EFIBootServicesNotExitedTag::new()).unwrap() - } - - /// Adds a 'EFI 32-bit image handle pointer' tag (represented by [`EFIImageHandle32Tag`]) to the builder. - #[must_use] - pub fn efi_image_handle32(self, tag: &EFIImageHandle32Tag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'EFI 64-bit image handle pointer' tag (represented by [`EFIImageHandle64Tag`]) to the builder. - #[must_use] - pub fn efi_image_handle64(self, tag: &EFIImageHandle64Tag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'EFI Memory map' tag (represented by [`EFIMemoryMapTag`]) to the builder. - #[must_use] - pub fn efi_memory_map_tag(self, tag: &EFIMemoryMapTag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'ELF-Symbols' tag (represented by [`ElfSectionsTag`]) to the builder. - #[must_use] - pub fn elf_sections_tag(self, tag: &ElfSectionsTag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'Framebuffer info' tag (represented by [`FramebufferTag`]) to the builder. - #[must_use] - pub fn framebuffer_tag(self, tag: &FramebufferTag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'Image load base physical address' tag (represented by [`ImageLoadPhysAddrTag`]) to the builder. - #[must_use] - pub fn image_load_addr(self, tag: &ImageLoadPhysAddrTag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a (*none EFI*) 'memory map' tag (represented by [`MemoryMapTag`]) to the builder. - #[must_use] - pub fn memory_map_tag(self, tag: &MemoryMapTag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'Modules' tag (represented by [`ModuleTag`]) to the builder. - /// This tag can occur multiple times in boot information. - #[must_use] - pub fn add_module_tag(self, tag: &ModuleTag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'ACPI old RSDP' tag (represented by [`RsdpV1Tag`]) to the builder. - #[must_use] - pub fn rsdp_v1_tag(self, tag: &RsdpV1Tag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'ACPI new RSDP' tag (represented by [`RsdpV2Tag`]) to the builder. - #[must_use] - pub fn rsdp_v2_tag(self, tag: &RsdpV2Tag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'SMBIOS tables' tag (represented by [`SmbiosTag`]) to the builder. - #[must_use] - pub fn smbios_tag(self, tag: &SmbiosTag) -> Self { - self.add_tag(tag).unwrap() - } - - /// Adds a 'VBE Info' tag (represented by [`VBEInfoTag`]) to the builder. - #[must_use] - pub fn vbe_info_tag(self, tag: &VBEInfoTag) -> Self { - self.add_tag(tag).unwrap() - } - - #[must_use] - const fn tag_is_allowed_multiple_times(tag_type: TagType) -> bool { - matches!( - tag_type, - TagType::Module | TagType::Smbios | TagType::Custom(_) - ) - } -} - -#[cfg(test)] -mod tests { - use crate::builder::information::InformationBuilder; - use crate::{BasicMemoryInfoTag, BootInformation, CommandLineTag, ModuleTag}; - - fn create_builder() -> InformationBuilder { - 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 = 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 = builder.command_line_tag(&CommandLineTag::new("test")); - expected_len += 8 + 5 + 3; // padding - assert_eq!(builder.expected_len(), expected_len); - // many modules - builder = builder.add_module_tag(&ModuleTag::new(0, 1234, "module1")); - expected_len += 16 + 8; - assert_eq!(builder.expected_len(), expected_len); - builder = 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()); - - builder - } - - /// Test of the `build` method in isolation specifically for miri to check - /// for memory issues. - #[test] - fn test_builder_miri() { - let builder = create_builder(); - let expected_len = builder.expected_len(); - assert_eq!(builder.build().as_bytes().len(), expected_len); - } - - #[test] - fn test_builder() { - // Step 1/2: Build MBI - let mb2i_data = create_builder().build(); - - // 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); - } -} diff --git a/multiboot2/src/builder/mod.rs b/multiboot2/src/builder/mod.rs deleted file mode 100644 index 4fd00932..00000000 --- a/multiboot2/src/builder/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Module for the builder-feature. - -mod information; - -pub use information::InformationBuilder; - -/// Helper trait for all structs that need to be serialized that do not -/// implement [`TagTrait`]. -/// -/// [`TagTrait`]: crate::TagTrait -pub trait AsBytes: Sized { - /// Returns the raw bytes of the type. - fn as_bytes(&self) -> &[u8] { - let ptr = core::ptr::addr_of!(*self); - let size = core::mem::size_of::(); - unsafe { core::slice::from_raw_parts(ptr.cast(), size) } - } -} diff --git a/multiboot2/src/command_line.rs b/multiboot2/src/command_line.rs index cd387f25..d3cd4b4e 100644 --- a/multiboot2/src/command_line.rs +++ b/multiboot2/src/command_line.rs @@ -1,14 +1,13 @@ //! Module for [`CommandLineTag`]. use crate::tag::TagHeader; -use crate::{parse_slice_as_string, StringError, TagTrait, TagType}; +use crate::{parse_slice_as_string, StringError, TagType}; use core::fmt::{Debug, Formatter}; use core::mem; use core::str; +use multiboot2_common::{MaybeDynSized, Tag}; #[cfg(feature = "builder")] -use {crate::new_boxed, alloc::boxed::Box}; - -const METADATA_SIZE: usize = mem::size_of::(); +use {alloc::boxed::Box, multiboot2_common::new_boxed}; /// This tag contains the command line string. /// @@ -27,11 +26,12 @@ impl CommandLineTag { #[cfg(feature = "builder")] #[must_use] pub fn new(command_line: &str) -> Box { + let header = TagHeader::new(Self::ID, 0); let bytes = command_line.as_bytes(); if bytes.ends_with(&[0]) { - new_boxed(&[bytes]) + new_boxed(header, &[bytes]) } else { - new_boxed(&[bytes, &[0]]) + new_boxed(header, &[bytes, &[0]]) } } @@ -69,21 +69,29 @@ impl Debug for CommandLineTag { } } -impl TagTrait for CommandLineTag { - const ID: TagType = TagType::Cmdline; +impl MaybeDynSized for CommandLineTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::(); fn dst_len(header: &TagHeader) -> usize { - assert!(header.size as usize >= METADATA_SIZE); - header.size as usize - METADATA_SIZE + assert!(header.size as usize >= Self::BASE_SIZE); + header.size as usize - Self::BASE_SIZE } } +impl Tag for CommandLineTag { + type IDType = TagType; + + const ID: TagType = TagType::Cmdline; +} + #[cfg(test)] mod tests { use super::*; - use crate::tag::{GenericTag, TagBytesRef}; - use crate::test_util::AlignedBytes; + use crate::GenericInfoTag; use core::borrow::Borrow; + use multiboot2_common::test_utils::AlignedBytes; #[rustfmt::skip] fn get_bytes() -> AlignedBytes<16> { @@ -100,8 +108,7 @@ mod tests { #[test] fn test_parse_str() { let bytes = get_bytes(); - let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); - let tag = GenericTag::ref_from(bytes); + let tag = GenericInfoTag::ref_from_slice(bytes.borrow()).unwrap(); let tag = tag.cast::(); assert_eq!(tag.header.typ, TagType::Cmdline); assert_eq!(tag.cmdline(), Ok("hello")); @@ -112,14 +119,16 @@ mod tests { #[cfg(feature = "builder")] fn test_build_str() { let tag = CommandLineTag::new("hello"); - let bytes = tag.as_bytes(); - assert_eq!(bytes, &get_bytes()[..tag.size()]); + let bytes = tag.as_bytes().as_ref(); + let bytes = &bytes[..tag.header.size as usize]; + assert_eq!(bytes, &get_bytes()[..tag.header().size as usize]); assert_eq!(tag.cmdline(), Ok("hello")); // With terminating null. let tag = CommandLineTag::new("hello\0"); - let bytes = tag.as_bytes(); - assert_eq!(bytes, &get_bytes()[..tag.size()]); + let bytes = tag.as_bytes().as_ref(); + let bytes = &bytes[..tag.header.size as usize]; + assert_eq!(bytes, &get_bytes()[..tag.header().size as usize]); assert_eq!(tag.cmdline(), Ok("hello")); // test also some bigger message diff --git a/multiboot2/src/efi.rs b/multiboot2/src/efi.rs index 4c8d727a..862bb9a6 100644 --- a/multiboot2/src/efi.rs +++ b/multiboot2/src/efi.rs @@ -7,8 +7,9 @@ //! - [`EFIBootServicesNotExitedTag`] use crate::tag::TagHeader; -use crate::{TagTrait, TagType}; +use crate::TagType; use core::mem::size_of; +use multiboot2_common::{MaybeDynSized, Tag}; /// EFI system table in 32 bit mode tag. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -19,11 +20,13 @@ pub struct EFISdt32Tag { } impl EFISdt32Tag { + const BASE_SIZE: usize = size_of::() + size_of::(); + /// Create a new tag to pass the EFI32 System Table pointer. #[must_use] pub fn new(pointer: u32) -> Self { Self { - header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), + header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32), pointer, } } @@ -35,12 +38,20 @@ impl EFISdt32Tag { } } -impl TagTrait for EFISdt32Tag { - const ID: TagType = TagType::Efi32; +impl MaybeDynSized for EFISdt32Tag { + type Header = TagHeader; + + const BASE_SIZE: usize = size_of::(); fn dst_len(_: &TagHeader) {} } +impl Tag for EFISdt32Tag { + type IDType = TagType; + + const ID: TagType = TagType::Efi32; +} + /// EFI system table in 64 bit mode tag. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C, align(8))] @@ -66,12 +77,20 @@ impl EFISdt64Tag { } } -impl TagTrait for EFISdt64Tag { - const ID: TagType = TagType::Efi64; +impl MaybeDynSized for EFISdt64Tag { + type Header = TagHeader; + + const BASE_SIZE: usize = size_of::(); fn dst_len(_: &TagHeader) {} } +impl Tag for EFISdt64Tag { + type IDType = TagType; + + const ID: TagType = TagType::Efi64; +} + /// Tag that contains the pointer to the boot loader's UEFI image handle /// (32-bit). #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -82,12 +101,13 @@ pub struct EFIImageHandle32Tag { } impl EFIImageHandle32Tag { + const BASE_SIZE: usize = size_of::() + size_of::(); + /// Constructs a new tag. - #[cfg(feature = "builder")] #[must_use] pub fn new(pointer: u32) -> Self { Self { - header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), + header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32), pointer, } } @@ -99,12 +119,20 @@ impl EFIImageHandle32Tag { } } -impl TagTrait for EFIImageHandle32Tag { - const ID: TagType = TagType::Efi32Ih; +impl MaybeDynSized for EFIImageHandle32Tag { + type Header = TagHeader; + + const BASE_SIZE: usize = size_of::(); fn dst_len(_: &TagHeader) {} } +impl Tag for EFIImageHandle32Tag { + type IDType = TagType; + + const ID: TagType = TagType::Efi32Ih; +} + /// Tag that contains the pointer to the boot loader's UEFI image handle /// (64-bit). #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -116,7 +144,6 @@ pub struct EFIImageHandle64Tag { impl EFIImageHandle64Tag { /// Constructs a new tag. - #[cfg(feature = "builder")] #[must_use] pub fn new(pointer: u64) -> Self { Self { @@ -132,12 +159,20 @@ impl EFIImageHandle64Tag { } } -impl TagTrait for EFIImageHandle64Tag { - const ID: TagType = TagType::Efi64Ih; +impl MaybeDynSized for EFIImageHandle64Tag { + type Header = TagHeader; + + const BASE_SIZE: usize = size_of::(); fn dst_len(_: &TagHeader) {} } +impl Tag for EFIImageHandle64Tag { + type IDType = TagType; + + const ID: TagType = TagType::Efi64Ih; +} + /// EFI ExitBootServices was not called tag. This tag has no payload and is /// just a marker. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -148,14 +183,12 @@ pub struct EFIBootServicesNotExitedTag { impl EFIBootServicesNotExitedTag { /// Constructs a new tag. - #[cfg(feature = "builder")] #[must_use] pub fn new() -> Self { Self::default() } } -#[cfg(feature = "builder")] impl Default for EFIBootServicesNotExitedTag { fn default() -> Self { Self { @@ -164,12 +197,20 @@ impl Default for EFIBootServicesNotExitedTag { } } -impl TagTrait for EFIBootServicesNotExitedTag { - const ID: TagType = TagType::EfiBs; +impl MaybeDynSized for EFIBootServicesNotExitedTag { + type Header = TagHeader; + + const BASE_SIZE: usize = size_of::(); fn dst_len(_: &TagHeader) {} } +impl Tag for EFIBootServicesNotExitedTag { + type IDType = TagType; + + const ID: TagType = TagType::EfiBs; +} + #[cfg(all(test, feature = "builder"))] mod tests { use super::{EFIImageHandle32Tag, EFIImageHandle64Tag, EFISdt32Tag, EFISdt64Tag}; diff --git a/multiboot2/src/elf_sections.rs b/multiboot2/src/elf_sections.rs index decbe734..bfe66dda 100644 --- a/multiboot2/src/elf_sections.rs +++ b/multiboot2/src/elf_sections.rs @@ -1,14 +1,13 @@ //! Module for [`ElfSectionsTag`]. -use crate::{TagHeader, TagTrait, TagType}; +use crate::{TagHeader, TagType}; use core::fmt::{Debug, Formatter}; use core::marker::PhantomData; use core::mem; use core::str::Utf8Error; +use multiboot2_common::{MaybeDynSized, Tag}; #[cfg(feature = "builder")] -use {crate::new_boxed, alloc::boxed::Box}; - -const METADATA_SIZE: usize = mem::size_of::() + 3 * mem::size_of::(); +use {alloc::boxed::Box, multiboot2_common::new_boxed}; /// This tag contains the section header table from an ELF binary. // The sections iterator is provided via the [`ElfSectionsTag::sections`] @@ -28,10 +27,14 @@ impl ElfSectionsTag { #[cfg(feature = "builder")] #[must_use] pub fn new(number_of_sections: u32, entry_size: u32, shndx: u32, sections: &[u8]) -> Box { + let header = TagHeader::new(Self::ID, 0); let number_of_sections = number_of_sections.to_ne_bytes(); let entry_size = entry_size.to_ne_bytes(); let shndx = shndx.to_ne_bytes(); - new_boxed(&[&number_of_sections, &entry_size, &shndx, sections]) + new_boxed( + header, + &[&number_of_sections, &entry_size, &shndx, sections], + ) } /// Get an iterator of loaded ELF sections. @@ -68,15 +71,23 @@ impl ElfSectionsTag { } } -impl TagTrait for ElfSectionsTag { - const ID: TagType = TagType::ElfSections; +impl MaybeDynSized for ElfSectionsTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::() + 3 * mem::size_of::(); fn dst_len(header: &TagHeader) -> usize { - assert!(header.size as usize >= METADATA_SIZE); - header.size as usize - METADATA_SIZE + assert!(header.size as usize >= Self::BASE_SIZE); + header.size as usize - Self::BASE_SIZE } } +impl Tag for ElfSectionsTag { + type IDType = TagType; + + const ID: TagType = TagType::ElfSections; +} + impl Debug for ElfSectionsTag { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("ElfSectionsTag") diff --git a/multiboot2/src/end.rs b/multiboot2/src/end.rs index 746ba271..d8f900c4 100644 --- a/multiboot2/src/end.rs +++ b/multiboot2/src/end.rs @@ -1,6 +1,8 @@ //! Module for [`EndTag`]. -use crate::{TagHeader, TagTrait, TagType, TagTypeId}; +use crate::{TagHeader, TagType, TagTypeId}; +use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; /// The end tag ends the information struct. #[derive(Debug)] @@ -19,12 +21,20 @@ impl Default for EndTag { } } -impl TagTrait for EndTag { - const ID: TagType = TagType::End; +impl MaybeDynSized for EndTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::(); fn dst_len(_: &TagHeader) {} } +impl Tag for EndTag { + type IDType = TagType; + + const ID: TagType = TagType::End; +} + #[cfg(test)] mod tests { use super::*; diff --git a/multiboot2/src/framebuffer.rs b/multiboot2/src/framebuffer.rs index d1370ee8..1e480fd6 100644 --- a/multiboot2/src/framebuffer.rs +++ b/multiboot2/src/framebuffer.rs @@ -1,53 +1,50 @@ //! Module for [`FramebufferTag`]. use crate::tag::TagHeader; -use crate::{TagTrait, TagType, TagTypeId}; +use crate::TagType; use core::fmt::Debug; use core::mem; use core::slice; use derive_more::Display; +use multiboot2_common::{MaybeDynSized, Tag}; #[cfg(feature = "builder")] -use {crate::builder::AsBytes, crate::new_boxed, alloc::boxed::Box, alloc::vec::Vec}; +use {alloc::boxed::Box, multiboot2_common::new_boxed}; /// Helper struct to read bytes from a raw pointer and increase the pointer /// automatically. -struct Reader { - ptr: *const u8, +struct Reader<'a> { + buffer: &'a [u8], off: usize, } -impl Reader { - const fn new(ptr: *const T) -> Self { - Self { - ptr: ptr as *const u8, - off: 0, - } +impl<'a> Reader<'a> { + const fn new(buffer: &'a [u8]) -> Self { + Self { buffer, off: 0 } } fn read_u8(&mut self) -> u8 { + let val = self + .buffer + .get(self.off) + .cloned() + // This is not a solution I'm proud of, but at least it is safe. + // The whole framebuffer tag code originally is not from me. + // I hope someone from the community wants to improve this overall + // functionality someday. + .expect("Embedded framebuffer info should be properly sized and available"); self.off += 1; - unsafe { *self.ptr.add(self.off - 1) } + val } fn read_u16(&mut self) -> u16 { self.read_u8() as u16 | (self.read_u8() as u16) << 8 } - fn read_u32(&mut self) -> u32 { - self.read_u16() as u32 | (self.read_u16() as u32) << 16 - } - - fn current_address(&self) -> usize { - unsafe { self.ptr.add(self.off) as usize } + const fn current_ptr(&self) -> *const u8 { + unsafe { self.buffer.as_ptr().add(self.off) } } } -const METADATA_SIZE: usize = mem::size_of::() - + 4 * mem::size_of::() - + mem::size_of::() - + mem::size_of::() - + 2 * mem::size_of::(); - /// The VBE Framebuffer information tag. #[derive(ptr_meta::Pointee, Eq)] #[repr(C, align(8))] @@ -73,13 +70,16 @@ pub struct FramebufferTag { /// Contains number of bits per pixel. bpp: u8, - /// The type of framebuffer, one of: `Indexed`, `RGB` or `Text`. - type_no: u8, + /// The type of framebuffer. See [`FramebufferTypeId`]. + // TODO: Strictly speaking this causes UB for invalid values. However, no + // sane bootloader puts something illegal there at the moment. When we + // refactor this (newtype pattern?), we should also streamline other + // parts in the code base accordingly. + framebuffer_type: FramebufferTypeId, - // In the multiboot spec, it has this listed as a u8 _NOT_ a u16. - // Reading the GRUB2 source code reveals it is in fact a u16. - _reserved: u16, + _padding: u16, + /// This optional data and its meaning depend on the [`FramebufferTypeId`]. buffer: [u8], } @@ -95,13 +95,27 @@ impl FramebufferTag { bpp: u8, buffer_type: FramebufferType, ) -> Box { + let header = TagHeader::new(Self::ID, 0); let address = address.to_ne_bytes(); let pitch = pitch.to_ne_bytes(); let width = width.to_ne_bytes(); let height = height.to_ne_bytes(); - let bpp = bpp.to_ne_bytes(); - let buffer_type = buffer_type.to_bytes(); - new_boxed(&[&address, &pitch, &width, &height, &bpp, &buffer_type]) + let buffer_type_id = buffer_type.id(); + let padding = [0; 2]; + let optional_buffer = buffer_type.serialize(); + new_boxed( + header, + &[ + &address, + &pitch, + &width, + &height, + &[bpp], + &[buffer_type_id as u8], + &padding, + &optional_buffer, + ], + ) } /// Contains framebuffer physical address. @@ -140,18 +154,31 @@ impl FramebufferTag { /// The type of framebuffer, one of: `Indexed`, `RGB` or `Text`. pub fn buffer_type(&self) -> Result { - let mut reader = Reader::new(self.buffer.as_ptr()); - let typ = FramebufferTypeId::try_from(self.type_no)?; - match typ { + let mut reader = Reader::new(&self.buffer); + + // TODO: We should use the newtype pattern instead or so to properly + // solve this. + let fb_type_raw = self.framebuffer_type as u8; + let fb_type = FramebufferTypeId::try_from(fb_type_raw)?; + + match fb_type { FramebufferTypeId::Indexed => { - let num_colors = reader.read_u32(); - // TODO static cast looks like UB? - let palette = unsafe { - slice::from_raw_parts( - reader.current_address() as *const FramebufferColor, - num_colors as usize, - ) - } as &'static [FramebufferColor]; + // TODO we can create a struct for this and implement + // DynSizedStruct for it to leverage the already existing + // functionality + let num_colors = reader.read_u16(); + + let palette = { + // Ensure the slice can be created without causing UB + assert_eq!(mem::size_of::(), 3); + + unsafe { + slice::from_raw_parts( + reader.current_ptr().cast::(), + num_colors as usize, + ) + } + }; Ok(FramebufferType::Indexed { palette }) } FramebufferTypeId::RGB => { @@ -181,15 +208,27 @@ impl FramebufferTag { } } -impl TagTrait for FramebufferTag { - const ID: TagType = TagType::Framebuffer; +impl MaybeDynSized for FramebufferTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::() + + mem::size_of::() + + 3 * mem::size_of::() + + 2 * mem::size_of::() + + mem::size_of::(); fn dst_len(header: &TagHeader) -> usize { - assert!(header.size as usize >= METADATA_SIZE); - header.size as usize - METADATA_SIZE + assert!(header.size as usize >= Self::BASE_SIZE); + header.size as usize - Self::BASE_SIZE } } +impl Tag for FramebufferTag { + type IDType = TagType; + + const ID: TagType = TagType::Framebuffer; +} + impl Debug for FramebufferTag { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("FramebufferTag") @@ -213,16 +252,16 @@ impl PartialEq for FramebufferTag { && self.width == { other.width } && self.height == { other.height } && self.bpp == { other.bpp } - && self.type_no == { other.type_no } + && self.framebuffer_type == { other.framebuffer_type } && self.buffer == other.buffer } } -/// Helper struct for [`FramebufferType`]. +/// ABI-compatible framebuffer type. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] #[allow(clippy::upper_case_acronyms)] -enum FramebufferTypeId { +pub enum FramebufferTypeId { Indexed = 0, RGB = 1, Text = 2, @@ -242,7 +281,18 @@ impl TryFrom for FramebufferTypeId { } } -/// The type of framebuffer. +impl From> for FramebufferTypeId { + fn from(value: FramebufferType) -> Self { + match value { + FramebufferType::Indexed { .. } => Self::Indexed, + FramebufferType::RGB { .. } => Self::RGB, + FramebufferType::Text => Self::Text, + } + } +} + +/// Structured accessory to the provided framebuffer type that is not ABI +/// compatible. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum FramebufferType<'a> { /// Indexed color. @@ -269,32 +319,44 @@ pub enum FramebufferType<'a> { Text, } -#[cfg(feature = "builder")] impl<'a> FramebufferType<'a> { - fn to_bytes(&self) -> Vec { - let mut v = Vec::new(); + #[must_use] + #[cfg(feature = "builder")] + const fn id(&self) -> FramebufferTypeId { + match self { + FramebufferType::Indexed { .. } => FramebufferTypeId::Indexed, + FramebufferType::RGB { .. } => FramebufferTypeId::RGB, + FramebufferType::Text => FramebufferTypeId::Text, + } + } + + #[must_use] + #[cfg(feature = "builder")] + fn serialize(&self) -> alloc::vec::Vec { + let mut data = alloc::vec::Vec::new(); match self { FramebufferType::Indexed { palette } => { - v.extend(0u8.to_ne_bytes()); // type - v.extend(0u16.to_ne_bytes()); // reserved - v.extend((palette.len() as u32).to_ne_bytes()); - for color in palette.iter() { - v.extend(color.as_bytes()); + // TODO we can create a struct for this and implement + // DynSizedStruct for it to leverage the already existing + // functionality + let num_colors = palette.len() as u16; + data.extend(&num_colors.to_ne_bytes()); + for color in *palette { + let serialized_color = [color.red, color.green, color.blue]; + data.extend(&serialized_color); } } - FramebufferType::RGB { red, green, blue } => { - v.extend(1u8.to_ne_bytes()); // type - v.extend(0u16.to_ne_bytes()); // reserved - v.extend(red.as_bytes()); - v.extend(green.as_bytes()); - v.extend(blue.as_bytes()); - } - FramebufferType::Text => { - v.extend(2u8.to_ne_bytes()); // type - v.extend(0u16.to_ne_bytes()); // reserved - } + FramebufferType::RGB { red, green, blue } => data.extend(&[ + red.position, + red.size, + green.position, + green.size, + blue.position, + blue.size, + ]), + FramebufferType::Text => {} } - v + data } } @@ -309,10 +371,9 @@ pub struct FramebufferField { pub size: u8, } -#[cfg(feature = "builder")] -impl AsBytes for FramebufferField {} - -/// A framebuffer color descriptor in the palette. +/// A framebuffer color descriptor in the palette. On the ABI level, multiple +/// values are consecutively without padding bytes. The spec is not precise in +/// that regard, but looking at Limine's and GRUB's source code confirm that. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] // no align(8) here is correct pub struct FramebufferColor { @@ -326,9 +387,6 @@ pub struct FramebufferColor { pub blue: u8, } -#[cfg(feature = "builder")] -impl AsBytes for FramebufferColor {} - /// Error when an unknown [`FramebufferTypeId`] is found. #[derive(Debug, Copy, Clone, Display, PartialEq, Eq)] #[display(fmt = "Unknown framebuffer type {}", _0)] @@ -346,4 +404,60 @@ mod tests { fn test_size() { assert_eq!(mem::size_of::(), 3) } + + #[test] + #[cfg(feature = "builder")] + fn create_new() { + let tag = FramebufferTag::new(0x1000, 1, 1024, 1024, 8, FramebufferType::Text); + // Good test for Miri + dbg!(tag); + + let tag = FramebufferTag::new( + 0x1000, + 1, + 1024, + 1024, + 8, + FramebufferType::Indexed { + palette: &[ + FramebufferColor { + red: 255, + green: 255, + blue: 255, + }, + FramebufferColor { + red: 127, + green: 42, + blue: 73, + }, + ], + }, + ); + // Good test for Miri + dbg!(tag); + + let tag = FramebufferTag::new( + 0x1000, + 1, + 1024, + 1024, + 8, + FramebufferType::RGB { + red: FramebufferField { + position: 0, + size: 0, + }, + green: FramebufferField { + position: 10, + size: 20, + }, + blue: FramebufferField { + position: 30, + size: 40, + }, + }, + ); + // Good test for Miri + dbg!(tag); + } } diff --git a/multiboot2/src/image_load_addr.rs b/multiboot2/src/image_load_addr.rs index 7248d782..bb4913d0 100644 --- a/multiboot2/src/image_load_addr.rs +++ b/multiboot2/src/image_load_addr.rs @@ -1,9 +1,10 @@ //! Module for [`ImageLoadPhysAddrTag`]. use crate::tag::TagHeader; -use crate::{TagTrait, TagType}; +use crate::TagType; #[cfg(feature = "builder")] use core::mem::size_of; +use multiboot2_common::{MaybeDynSized, Tag}; /// The physical load address tag. Typically, this is only available if the /// binary was relocated, for example if the relocatable header tag was @@ -16,12 +17,13 @@ pub struct ImageLoadPhysAddrTag { } impl ImageLoadPhysAddrTag { + const BASE_SIZE: usize = size_of::() + size_of::(); + /// Constructs a new tag. - #[cfg(feature = "builder")] #[must_use] pub fn new(load_base_addr: u32) -> Self { Self { - header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), + header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32), load_base_addr, } } @@ -32,13 +34,20 @@ impl ImageLoadPhysAddrTag { self.load_base_addr } } +impl MaybeDynSized for ImageLoadPhysAddrTag { + type Header = TagHeader; -impl TagTrait for ImageLoadPhysAddrTag { - const ID: TagType = TagType::LoadBaseAddr; + const BASE_SIZE: usize = size_of::(); fn dst_len(_: &TagHeader) {} } +impl Tag for ImageLoadPhysAddrTag { + type IDType = TagType; + + const ID: TagType = TagType::LoadBaseAddr; +} + #[cfg(all(test, feature = "builder"))] mod tests { use super::ImageLoadPhysAddrTag; diff --git a/multiboot2/src/lib.rs b/multiboot2/src/lib.rs index f43f9cfe..cc8f7f40 100644 --- a/multiboot2/src/lib.rs +++ b/multiboot2/src/lib.rs @@ -43,6 +43,7 @@ //! ## MSRV //! The MSRV is 1.70.0 stable. +#[cfg_attr(feature = "builder", macro_use)] #[cfg(feature = "builder")] extern crate alloc; @@ -55,9 +56,14 @@ extern crate std; extern crate bitflags; #[cfg(feature = "builder")] -pub mod builder; +mod builder; + +/// Iterator over the tags of a Multiboot2 boot information. +pub type TagIter<'a> = multiboot2_common::TagIter<'a, TagHeader>; + +/// A generic version of all boot information tags. #[cfg(test)] -pub(crate) mod test_util; +pub type GenericInfoTag = multiboot2_common::DynSizedStructure; mod boot_information; mod boot_loader_name; @@ -72,13 +78,16 @@ mod module; mod rsdp; mod smbios; mod tag; -mod tag_trait; mod tag_type; pub(crate) mod util; mod vbe_info; -pub use boot_information::{BootInformation, BootInformationHeader, MbiLoadError}; +pub use multiboot2_common::{DynSizedStructure, MaybeDynSized, Tag}; + +pub use boot_information::{BootInformation, BootInformationHeader, LoadError}; pub use boot_loader_name::BootLoaderNameTag; +#[cfg(feature = "builder")] +pub use builder::Builder; pub use command_line::CommandLineTag; pub use efi::{ EFIBootServicesNotExitedTag, EFIImageHandle32Tag, EFIImageHandle64Tag, EFISdt32Tag, EFISdt64Tag, @@ -98,10 +107,7 @@ pub use ptr_meta::Pointee; pub use rsdp::{RsdpV1Tag, RsdpV2Tag}; pub use smbios::SmbiosTag; pub use tag::TagHeader; -pub use tag_trait::TagTrait; pub use tag_type::{TagType, TagTypeId}; -#[cfg(feature = "alloc")] -pub use util::new_boxed; pub use util::{parse_slice_as_string, StringError}; pub use vbe_info::{ VBECapabilities, VBEControlInfo, VBEDirectColorAttributes, VBEField, VBEInfoTag, @@ -113,13 +119,12 @@ pub use vbe_info::{ /// machine state. pub const MAGIC: u32 = 0x36d76289; -/// The required alignment for tags and the boot information. -pub const ALIGNMENT: usize = 8; - #[cfg(test)] mod tests { use super::*; - use crate::test_util::AlignedBytes; + use multiboot2_common::test_utils::AlignedBytes; + use multiboot2_common::{MaybeDynSized, Tag}; + use std::mem; /// Compile time test to check if the boot information is Send and Sync. /// This test is relevant to give library users flexebility in passing the @@ -270,7 +275,6 @@ mod tests { assert_eq!(addr, bi.start_address()); assert_eq!(addr + bytes.0.len(), bi.end_address()); assert_eq!(bytes.0.len(), bi.total_size()); - use framebuffer::{FramebufferField, FramebufferType}; assert!(bi.framebuffer_tag().is_some()); let fbi = bi .framebuffer_tag() @@ -301,11 +305,11 @@ mod tests { } #[test] - #[cfg_attr(miri, ignore)] fn framebuffer_tag_indexed() { // indexed mode test: // this is synthetic, as I can't get QEMU // to run in indexed color mode. + #[rustfmt::skip] let bytes = AlignedBytes([ 64, 0, 0, 0, // total size 0, 0, 0, 0, // reserved @@ -316,10 +320,16 @@ mod tests { 0, 20, 0, 0, // framebuffer pitch 0, 5, 0, 0, // framebuffer width 208, 2, 0, 0, // framebuffer height - 32, 0, 0, 0, // framebuffer bpp, type, reserved word - 4, 0, 0, 0, // framebuffer palette length - 255, 0, 0, 0, // framebuffer palette - 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, // end tag type + 32, // framebuffer bpp + 0, // framebuffer type + 0, 0, // reserved word + 4, 0, // framebuffer palette length + 255, 0, 0, // framebuffer palette: 1/3 + 0, 255, 0, // framebuffer palette: 2/3 + 0, 0, 255, // framebuffer palette: 3/3 + 3, 7, 73, // framebuffer palette: 4/4 + 0, 0, // padding for 8-byte alignment + 0, 0, 0, 0, // end tag type 8, 0, 0, 0, // end tag size ]); let ptr = bytes.0.as_ptr(); @@ -329,7 +339,6 @@ mod tests { assert_eq!(addr, bi.start_address()); assert_eq!(addr + bytes.0.len(), bi.end_address()); assert_eq!(bytes.0.len(), bi.total_size()); - use framebuffer::{FramebufferColor, FramebufferType}; assert!(bi.framebuffer_tag().is_some()); let fbi = bi .framebuffer_tag() @@ -360,9 +369,9 @@ mod tests { blue: 255, }, FramebufferColor { - red: 0, - green: 0, - blue: 0, + red: 3, + green: 7, + blue: 73, } ] ), @@ -1089,7 +1098,7 @@ mod tests { /// This test succeeds if it compiles. fn mbi_load_error_implements_error() { fn consumer(_e: E) {} - consumer(MbiLoadError::IllegalAddress) + consumer(LoadError::NoEndTag) } /// Example for a custom tag. @@ -1102,11 +1111,20 @@ mod tests { foo: u32, } - impl TagTrait for CustomTag { - const ID: TagType = TagType::Custom(0x1337); + impl MaybeDynSized for CustomTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::(); - fn dst_len(_tag_header: &TagHeader) {} + fn dst_len(_: &TagHeader) -> Self::Metadata {} } + + impl Tag for CustomTag { + type IDType = TagType; + + const ID: TagType = TagType::Custom(0x1337); + } + // Raw bytes of a MBI that only contains the custom tag. let bytes = AlignedBytes([ 32, @@ -1171,8 +1189,10 @@ mod tests { } } - impl TagTrait for CustomTag { - const ID: TagType = TagType::Custom(0x1337); + impl MaybeDynSized for CustomTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::() + mem::size_of::(); fn dst_len(header: &TagHeader) -> usize { // The size of the sized portion of the command line tag. @@ -1181,6 +1201,13 @@ mod tests { header.size as usize - tag_base_size } } + + impl Tag for CustomTag { + type IDType = TagType; + + const ID: TagType = TagType::Custom(0x1337); + } + // Raw bytes of a MBI that only contains the custom tag. let bytes = AlignedBytes([ 32, diff --git a/multiboot2/src/memory_map.rs b/multiboot2/src/memory_map.rs index c7663d72..39c7edb2 100644 --- a/multiboot2/src/memory_map.rs +++ b/multiboot2/src/memory_map.rs @@ -6,14 +6,13 @@ pub use uefi_raw::table::boot::MemoryDescriptor as EFIMemoryDesc; pub use uefi_raw::table::boot::MemoryType as EFIMemoryAreaType; use crate::tag::TagHeader; -use crate::{TagTrait, TagType, TagTypeId}; +use crate::{TagType, TagTypeId}; use core::fmt::{Debug, Formatter}; use core::marker::PhantomData; use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; #[cfg(feature = "builder")] -use {crate::new_boxed, alloc::boxed::Box, core::slice}; - -const METADATA_SIZE: usize = mem::size_of::() + 2 * mem::size_of::(); +use {alloc::boxed::Box, core::slice, multiboot2_common::new_boxed}; /// This tag provides an initial host memory map (legacy boot, not UEFI). /// @@ -39,14 +38,15 @@ impl MemoryMapTag { #[cfg(feature = "builder")] #[must_use] pub fn new(areas: &[MemoryArea]) -> Box { - let entry_size = mem::size_of::().to_ne_bytes(); + let header = TagHeader::new(Self::ID, 0); + let entry_size = (mem::size_of::() as u32).to_ne_bytes(); let entry_version = 0_u32.to_ne_bytes(); let areas = { let ptr = areas.as_ptr().cast::(); let len = mem::size_of_val(areas); unsafe { slice::from_raw_parts(ptr, len) } }; - new_boxed(&[&entry_size, &entry_version, areas]) + new_boxed(header, &[&entry_size, &entry_version, areas]) } /// Returns the entry size. @@ -73,17 +73,25 @@ impl MemoryMapTag { } } -impl TagTrait for MemoryMapTag { - const ID: TagType = TagType::Mmap; +impl MaybeDynSized for MemoryMapTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::() + 2 * mem::size_of::(); fn dst_len(header: &TagHeader) -> usize { - assert!(header.size as usize >= METADATA_SIZE); - let size = header.size as usize - METADATA_SIZE; + assert!(header.size as usize >= Self::BASE_SIZE); + let size = header.size as usize - Self::BASE_SIZE; assert_eq!(size % mem::size_of::(), 0); size / mem::size_of::() } } +impl Tag for MemoryMapTag { + type IDType = TagType; + + const ID: TagType = TagType::Mmap; +} + /// A descriptor for an available or taken area of physical memory. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] @@ -282,13 +290,19 @@ impl BasicMemoryInfoTag { } } -impl TagTrait for BasicMemoryInfoTag { - const ID: TagType = TagType::BasicMeminfo; +impl MaybeDynSized for BasicMemoryInfoTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::(); fn dst_len(_: &TagHeader) {} } -const EFI_METADATA_SIZE: usize = mem::size_of::() + 3 * mem::size_of::(); +impl Tag for BasicMemoryInfoTag { + type IDType = TagType; + + const ID: TagType = TagType::BasicMeminfo; +} /// EFI memory map tag. The embedded [`EFIMemoryDesc`]s follows the EFI /// specification. @@ -337,10 +351,11 @@ impl EFIMemoryMapTag { #[cfg(feature = "builder")] #[must_use] pub fn new_from_map(desc_size: u32, desc_version: u32, efi_mmap: &[u8]) -> Box { + let header = TagHeader::new(Self::ID, 0); assert_ne!(desc_size, 0); let desc_size = desc_size.to_ne_bytes(); let desc_version = desc_version.to_ne_bytes(); - new_boxed(&[&desc_size, &desc_version, efi_mmap]) + new_boxed(header, &[&desc_size, &desc_version, efi_mmap]) } /// Returns an iterator over the provided memory areas. @@ -376,15 +391,23 @@ impl Debug for EFIMemoryMapTag { } } -impl TagTrait for EFIMemoryMapTag { - const ID: TagType = TagType::EfiMmap; +impl MaybeDynSized for EFIMemoryMapTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::() + 3 * mem::size_of::(); fn dst_len(header: &TagHeader) -> usize { - assert!(header.size as usize >= EFI_METADATA_SIZE); - header.size as usize - EFI_METADATA_SIZE + assert!(header.size as usize >= Self::BASE_SIZE); + header.size as usize - Self::BASE_SIZE } } +impl Tag for EFIMemoryMapTag { + type IDType = TagType; + + const ID: TagType = TagType::EfiMmap; +} + /// An iterator over the EFI memory areas emitting [`EFIMemoryDesc`] items. #[derive(Clone, Debug)] pub struct EFIMemoryAreaIter<'a> { @@ -443,7 +466,17 @@ mod tests { use std::mem::size_of; #[test] - fn construction_and_parsing() { + fn test_create_old_mmap() { + let _mmap = MemoryMapTag::new(&[]); + let mmap = MemoryMapTag::new(&[ + MemoryArea::new(0x1000, 0x2000, MemoryAreaType::Available), + MemoryArea::new(0x2000, 0x3000, MemoryAreaType::Available), + ]); + dbg!(mmap); + } + + #[test] + fn efi_construct_and_parse() { let descs = [ EFIMemoryDesc { ty: EFIMemoryAreaType::CONVENTIONAL, @@ -474,7 +507,7 @@ mod tests { /// This is taken from the uefi-rs repository. See /// for more info. #[test] - fn test_real_data() { + fn efi_test_real_data() { const DESC_SIZE: u32 = 48; const DESC_VERSION: u32 = 1; /// Sample with 10 entries of a real UEFI memory map extracted from our diff --git a/multiboot2/src/module.rs b/multiboot2/src/module.rs index c77eb7e7..73a334e1 100644 --- a/multiboot2/src/module.rs +++ b/multiboot2/src/module.rs @@ -1,13 +1,12 @@ //! Module for [`ModuleTag`]. -use crate::tag::{TagHeader, TagIter}; -use crate::{parse_slice_as_string, StringError, TagTrait, TagType}; +use crate::tag::TagHeader; +use crate::{parse_slice_as_string, StringError, TagIter, TagType}; use core::fmt::{Debug, Formatter}; use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; #[cfg(feature = "builder")] -use {crate::new_boxed, alloc::boxed::Box}; - -const METADATA_SIZE: usize = mem::size_of::() + 2 * mem::size_of::(); +use {alloc::boxed::Box, multiboot2_common::new_boxed}; /// The module tag can occur multiple times and specifies passed boot modules /// (blobs in memory). The tag itself doesn't include the blog, but references @@ -27,6 +26,7 @@ impl ModuleTag { #[cfg(feature = "builder")] #[must_use] pub fn new(start: u32, end: u32, cmdline: &str) -> Box { + let header = TagHeader::new(Self::ID, 0); assert!(end > start, "must have a size"); let start = start.to_ne_bytes(); @@ -34,9 +34,9 @@ impl ModuleTag { let cmdline = cmdline.as_bytes(); if cmdline.ends_with(&[0]) { - new_boxed(&[&start, &end, cmdline]) + new_boxed(header, &[&start, &end, cmdline]) } else { - new_boxed(&[&start, &end, cmdline, &[0]]) + new_boxed(header, &[&start, &end, cmdline, &[0]]) } } @@ -72,15 +72,23 @@ impl ModuleTag { } } -impl TagTrait for ModuleTag { - const ID: TagType = TagType::Module; +impl MaybeDynSized for ModuleTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::() + 2 * mem::size_of::(); fn dst_len(header: &TagHeader) -> usize { - assert!(header.size as usize >= METADATA_SIZE); - header.size as usize - METADATA_SIZE + assert!(header.size as usize >= Self::BASE_SIZE); + header.size as usize - Self::BASE_SIZE } } +impl Tag for ModuleTag { + type IDType = TagType; + + const ID: TagType = TagType::Module; +} + impl Debug for ModuleTag { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("ModuleTag") @@ -128,9 +136,9 @@ impl<'a> Debug for ModuleIter<'a> { #[cfg(test)] mod tests { use super::*; - use crate::tag::{GenericTag, TagBytesRef}; - use crate::test_util::AlignedBytes; + use crate::GenericInfoTag; use core::borrow::Borrow; + use multiboot2_common::test_utils::AlignedBytes; #[rustfmt::skip] fn get_bytes() -> AlignedBytes<24> { @@ -151,8 +159,7 @@ mod tests { #[test] fn test_parse_str() { let bytes = get_bytes(); - let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); - let tag = GenericTag::ref_from(bytes); + let tag = GenericInfoTag::ref_from_slice(bytes.borrow()).unwrap(); let tag = tag.cast::(); assert_eq!(tag.header.typ, TagType::Module); assert_eq!(tag.cmdline(), Ok("hello")); @@ -163,14 +170,16 @@ mod tests { #[cfg(feature = "builder")] fn test_build_str() { let tag = ModuleTag::new(0xff00, 0xffff, "hello"); - let bytes = tag.as_bytes(); - assert_eq!(bytes, &get_bytes()[..tag.size()]); + let bytes = tag.as_bytes().as_ref(); + let bytes = &bytes[..tag.header.size as usize]; + assert_eq!(bytes, &get_bytes()[..tag.header().size as usize]); assert_eq!(tag.cmdline(), Ok("hello")); // With terminating null. let tag = ModuleTag::new(0xff00, 0xffff, "hello\0"); - let bytes = tag.as_bytes(); - assert_eq!(bytes, &get_bytes()[..tag.size()]); + let bytes = tag.as_bytes().as_ref(); + let bytes = &bytes[..tag.header.size as usize]; + assert_eq!(bytes, &get_bytes()[..tag.header().size as usize]); assert_eq!(tag.cmdline(), Ok("hello")); // test also some bigger message diff --git a/multiboot2/src/rsdp.rs b/multiboot2/src/rsdp.rs index 7653ad61..fbc88005 100644 --- a/multiboot2/src/rsdp.rs +++ b/multiboot2/src/rsdp.rs @@ -13,12 +13,13 @@ //! use crate::tag::TagHeader; -use crate::{TagTrait, TagType}; +use crate::TagType; #[cfg(feature = "builder")] use core::mem::size_of; use core::slice; use core::str; use core::str::Utf8Error; +use multiboot2_common::{MaybeDynSized, Tag}; const RSDPV1_LENGTH: usize = 20; @@ -35,19 +36,17 @@ pub struct RsdpV1Tag { } impl RsdpV1Tag { + /// Signature of RSDP v1. + pub const SIGNATURE: [u8; 8] = *b"RSD PTR "; + + const BASE_SIZE: usize = size_of::() + 16 + 4; + /// Constructs a new tag. - #[cfg(feature = "builder")] #[must_use] - pub fn new( - signature: [u8; 8], - checksum: u8, - oem_id: [u8; 6], - revision: u8, - rsdt_address: u32, - ) -> Self { + pub fn new(checksum: u8, oem_id: [u8; 6], revision: u8, rsdt_address: u32) -> Self { Self { - header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), - signature, + header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32), + signature: Self::SIGNATURE, checksum, oem_id, revision, @@ -91,12 +90,20 @@ impl RsdpV1Tag { } } -impl TagTrait for RsdpV1Tag { - const ID: TagType = TagType::AcpiV1; +impl MaybeDynSized for RsdpV1Tag { + type Header = TagHeader; + + const BASE_SIZE: usize = size_of::(); fn dst_len(_: &TagHeader) {} } +impl Tag for RsdpV1Tag { + type IDType = TagType; + + const ID: TagType = TagType::AcpiV1; +} + /// This tag contains a copy of RSDP as defined per ACPI 2.0 or later specification. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C, align(8))] @@ -115,12 +122,16 @@ pub struct RsdpV2Tag { } impl RsdpV2Tag { + /// Signature of RSDP v2. + pub const SIGNATURE: [u8; 8] = *b"RSD PTR "; + + const BASE_SIZE: usize = + size_of::() + 16 + 2 * size_of::() + size_of::() + 4; + /// Constructs a new tag. - #[cfg(feature = "builder")] #[allow(clippy::too_many_arguments)] #[must_use] pub fn new( - signature: [u8; 8], checksum: u8, oem_id: [u8; 6], revision: u8, @@ -130,8 +141,8 @@ impl RsdpV2Tag { ext_checksum: u8, ) -> Self { Self { - header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), - signature, + header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32), + signature: Self::SIGNATURE, checksum, oem_id, revision, @@ -188,8 +199,16 @@ impl RsdpV2Tag { } } -impl TagTrait for RsdpV2Tag { - const ID: TagType = TagType::AcpiV2; +impl MaybeDynSized for RsdpV2Tag { + type Header = TagHeader; + + const BASE_SIZE: usize = size_of::(); fn dst_len(_: &TagHeader) {} } + +impl Tag for RsdpV2Tag { + type IDType = TagType; + + const ID: TagType = TagType::AcpiV2; +} diff --git a/multiboot2/src/smbios.rs b/multiboot2/src/smbios.rs index abd0e842..0d80b0b0 100644 --- a/multiboot2/src/smbios.rs +++ b/multiboot2/src/smbios.rs @@ -1,13 +1,12 @@ //! Module for [`SmbiosTag`]. use crate::tag::TagHeader; -use crate::{TagTrait, TagType}; +use crate::TagType; use core::fmt::Debug; use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; #[cfg(feature = "builder")] -use {crate::new_boxed, alloc::boxed::Box}; - -const METADATA_SIZE: usize = mem::size_of::() + mem::size_of::() * 8; +use {alloc::boxed::Box, multiboot2_common::new_boxed}; /// This tag contains a copy of SMBIOS tables as well as their version. #[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -25,8 +24,9 @@ impl SmbiosTag { #[cfg(feature = "builder")] #[must_use] pub fn new(major: u8, minor: u8, tables: &[u8]) -> Box { + let header = TagHeader::new(Self::ID, 0); let reserved = [0, 0, 0, 0, 0, 0]; - new_boxed(&[&[major, minor], &reserved, tables]) + new_boxed(header, &[&[major, minor], &reserved, tables]) } /// Returns the major number. @@ -48,15 +48,23 @@ impl SmbiosTag { } } -impl TagTrait for SmbiosTag { - const ID: TagType = TagType::Smbios; +impl MaybeDynSized for SmbiosTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::() + mem::size_of::() * 8; fn dst_len(header: &TagHeader) -> usize { - assert!(header.size as usize >= METADATA_SIZE); - header.size as usize - METADATA_SIZE + assert!(header.size as usize >= Self::BASE_SIZE); + header.size as usize - Self::BASE_SIZE } } +impl Tag for SmbiosTag { + type IDType = TagType; + + const ID: TagType = TagType::Smbios; +} + impl Debug for SmbiosTag { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("BootLoaderNameTag") @@ -71,9 +79,9 @@ impl Debug for SmbiosTag { #[cfg(test)] mod tests { use super::*; - use crate::tag::{GenericTag, TagBytesRef}; - use crate::test_util::AlignedBytes; + use crate::GenericInfoTag; use core::borrow::Borrow; + use multiboot2_common::test_utils::AlignedBytes; #[rustfmt::skip] fn get_bytes() -> AlignedBytes<32> { @@ -97,8 +105,7 @@ mod tests { #[test] fn test_parse() { let bytes = get_bytes(); - let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); - let tag = GenericTag::ref_from(bytes); + let tag = GenericInfoTag::ref_from_slice(bytes.borrow()).unwrap(); let tag = tag.cast::(); assert_eq!(tag.header.typ, TagType::Smbios); assert_eq!(tag.major, 7); @@ -111,7 +118,8 @@ mod tests { #[cfg(feature = "builder")] fn test_build() { let tag = SmbiosTag::new(7, 42, &[0, 1, 2, 3, 4, 5, 6, 7, 8]); - let bytes = tag.as_bytes(); - assert_eq!(bytes, &get_bytes()[..tag.size()]); + let bytes = tag.as_bytes().as_ref(); + let bytes = &bytes[..tag.header.size as usize]; + assert_eq!(bytes, &get_bytes()[..tag.header.size as usize]); } } diff --git a/multiboot2/src/tag.rs b/multiboot2/src/tag.rs index 3943456d..ebc11e14 100644 --- a/multiboot2/src/tag.rs +++ b/multiboot2/src/tag.rs @@ -1,20 +1,9 @@ -//! Module for the base tag definitions and helper types. -//! -//! The relevant exports of this module are [`TagHeader`], [`GenericTag`], and -//! [`TagIter`]. -//! -//! The (internal) workflow to parse a tag from bytes is the following: -//! - `&[u8]` --> [`TagBytesRef`] -//! - [`TagBytesRef`] --> [`TagHeader`] -//! - [`TagBytesRef`] + [`TagHeader`] --> [`GenericTag`] -//! - [`GenericTag`] --> cast to desired tag +//! Module for the base tag definition [`TagHeader`]. -use crate::util::increase_to_alignment; -use crate::{TagTrait, TagType, TagTypeId, ALIGNMENT}; -use core::fmt::{Debug, Formatter}; +use crate::TagTypeId; +use core::fmt::Debug; use core::mem; -use core::ops::Deref; -use core::ptr; +use multiboot2_common::Header; /// The common header that all tags have in common. This type is ABI compatible. /// @@ -26,6 +15,8 @@ use core::ptr; #[repr(C, align(8))] // Alignment also propagates to all tag types using this. pub struct TagHeader { /// The ABI-compatible [`TagType`]. + /// + /// [`TagType`]: crate::TagType pub typ: TagTypeId, /* u32 */ /// The total size of the tag including the header. pub size: u32, @@ -42,401 +33,13 @@ impl TagHeader { } } -/// Wraps a byte slice representing a tag, but guarantees that the memory -/// requirements are fulfilled. -/// -/// This is the only type that can be used to construct a [`GenericTag`]. -/// -/// The main reason for this dedicated type is to create fine-grained unit-tests -/// for Miri. -/// -/// # Memory Requirements (for Multiboot and Rust/Miri) -/// - At least as big as a `size_of()` -/// - at least [`ALIGNMENT`]-aligned -/// - Length is multiple of [`ALIGNMENT`]. In other words, there are enough -/// padding bytes until so that pointer coming right after the last byte -/// is [`ALIGNMENT`]-aligned -#[derive(Clone, Debug, PartialEq, Eq)] -#[repr(transparent)] -pub struct TagBytesRef<'a>(&'a [u8]); - -impl<'a> TryFrom<&'a [u8]> for TagBytesRef<'a> { - type Error = MemoryError; - - fn try_from(value: &'a [u8]) -> Result { - if value.len() < mem::size_of::() { - return Err(MemoryError::MinLengthNotSatisfied); - } - // Doesn't work as expected: if align_of_val(&value[0]) < ALIGNMENT { - if value.as_ptr().align_offset(ALIGNMENT) != 0 { - return Err(MemoryError::WrongAlignment); - } - let padding_bytes = value.len() % ALIGNMENT; - if padding_bytes != 0 { - return Err(MemoryError::MissingPadding); - } - Ok(Self(value)) - } -} - -impl<'a> Deref for TagBytesRef<'a> { - type Target = &'a [u8]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// Errors that occur when constructing a [`TagBytesRef`]. -#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub enum MemoryError { - /// The memory must be at least [`ALIGNMENT`]-aligned. - WrongAlignment, - /// The memory must cover at least the length of a [`TagHeader`]. - MinLengthNotSatisfied, - /// The buffer misses the terminating padding to the next alignment - /// boundary. - // This is mainly relevant to satisfy Miri. As the spec also mandates an - // alignment, we can rely on this property. - MissingPadding, -} - -/// A generic tag serving as base to cast to specific tags. This is a DST -/// version of [`TagHeader`] that solves various type and memory safety -/// problems by having a type that owns the whole memory of a tag. -#[derive(Eq, Ord, PartialEq, PartialOrd, ptr_meta::Pointee)] -#[repr(C)] -pub struct GenericTag { - header: TagHeader, - /// Payload of the tag that is reflected in the `size` attribute, thus, no - /// padding bytes! - payload: [u8], -} - -impl GenericTag { - /// Base size of the DST struct without the dynamic part. - const BASE_SIZE: usize = mem::size_of::(); - - /// Creates a reference to a [`GenericTag`] from the provided `bytes` - /// [`TagBytesRef`]. - pub(crate) fn ref_from(bytes: TagBytesRef) -> &Self { - let header = bytes.as_ptr().cast::(); - let header = unsafe { &*header }; - let dst_len = Self::dst_len(header); - assert_eq!(header.size as usize, Self::BASE_SIZE + dst_len); - - let generic_tag: *const Self = ptr_meta::from_raw_parts(bytes.as_ptr().cast(), dst_len); - unsafe { &*generic_tag } - } - - pub const fn header(&self) -> &TagHeader { - &self.header - } - - #[cfg(all(test, feature = "builder"))] - pub const fn payload(&self) -> &[u8] { - &self.payload - } - - /// Casts the generic tag to a specific [`TagTrait`] implementation which - /// may be a ZST or DST typed tag. - pub fn cast(&self) -> &T { - let base_ptr = ptr::addr_of!(*self); - let t_dst_size = T::dst_len(&self.header); - let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size); - let t_ref = unsafe { &*t_ptr }; - assert_eq!(mem::size_of_val(self), mem::size_of_val(t_ref)); - t_ref - } -} - -impl Debug for GenericTag { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - f.debug_struct("GenericTag") - .field("header", &self.header) - .field("payload", &"") - .finish() - } -} - -impl TagTrait for GenericTag { - const ID: TagType = TagType::Custom(0xffffffff); - - fn dst_len(header: &TagHeader) -> usize { - assert!(header.size as usize >= Self::BASE_SIZE); - header.size as usize - Self::BASE_SIZE - } -} - -/// Iterates the tags of the MBI from the first tag to the end tag. THe end tag -/// included. -#[derive(Clone, Debug)] -pub struct TagIter<'a> { - /// Absolute offset to next tag and updated in each iteration. - next_tag_offset: usize, - exclusive_end: *const u8, - buffer: &'a [u8], -} - -impl<'a> TagIter<'a> { - /// Creates a new iterator - pub fn new(mem: &'a [u8]) -> Self { - // Assert alignment. - assert_eq!(mem.as_ptr().align_offset(8), 0); - - let exclusive_end = unsafe { mem.as_ptr().add(mem.len()) }; - - TagIter { - next_tag_offset: 0, - buffer: mem, - exclusive_end, - } - } -} - -impl<'a> Iterator for TagIter<'a> { - type Item = &'a GenericTag; - - fn next(&mut self) -> Option { - let next_ptr = unsafe { self.buffer.as_ptr().add(self.next_tag_offset) }; - - if next_ptr == self.exclusive_end { - return None; - } - assert!(next_ptr < self.exclusive_end); - - let next_tag_ptr = next_ptr.cast::(); - - let tag_hdr = unsafe { &*next_tag_ptr }; - - // Get relevant byte portion for the next tag. This includes padding - // bytes to fulfill Rust memory guarantees. Otherwise, Miri complains. - // See . - let bytes = { - let from = self.next_tag_offset; - let to = from + tag_hdr.size as usize; - // The size of [the allocation for] a value is always a multiple of its - // alignment. - // https://doc.rust-lang.org/reference/type-layout.html - let to = increase_to_alignment(to); - - // Update ptr for next iteration. - self.next_tag_offset += to - from; - - &self.buffer[from..to] - }; - - // Should never fail at this point. - let tag_bytes = TagBytesRef::try_from(bytes).unwrap(); - - Some(GenericTag::ref_from(tag_bytes)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_util::AlignedBytes; - use core::borrow::Borrow; - use core::mem; - - #[test] - fn test_new_generic_tag() { - let bytes = AlignedBytes::new([ - /* id: 0xffff_ffff */ - 0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */ - 16, 0, 0, 0, /* field a: 0xdead_beef */ - 0xde, 0xad, 0xbe, 0xef, /* field b: 0x1337_1337 */ - 0x13, 0x37, 0x13, 0x37, - ]); - - let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); - let tag = GenericTag::ref_from(bytes); - assert_eq!(tag.header.typ, 0xffff_ffff); - assert_eq!(tag.header.size, 16); - assert_eq!(tag.payload.len(), 8); - } - - #[test] - fn test_cast_generic_tag_to_sized_tag() { - #[repr(C)] - struct CustomTag { - tag_header: TagHeader, - a: u32, - b: u32, - } - - impl TagTrait for CustomTag { - const ID: TagType = TagType::End; - - fn dst_len(_header: &TagHeader) -> Self::Metadata {} - } - - let bytes = AlignedBytes([ - /* id: 0xffff_ffff */ - 0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */ - 16, 0, 0, 0, /* field a: 0xdead_beef */ - 0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */ - 0x37, 0x13, 0x37, 0x13, - ]); - let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); - let tag = GenericTag::ref_from(bytes); - let custom_tag = tag.cast::(); - - assert_eq!(mem::size_of_val(custom_tag), 16); - assert_eq!(custom_tag.a, 0xdead_beef); - assert_eq!(custom_tag.b, 0x1337_1337); +impl Header for TagHeader { + fn payload_len(&self) -> usize { + assert!(self.size as usize >= mem::size_of::()); + self.size as usize - mem::size_of::() } - #[test] - fn test_cast_generic_tag_to_dynamically_sized_tag() { - #[repr(C)] - #[derive(ptr_meta::Pointee)] - struct CustomDstTag { - tag_header: TagHeader, - a: u32, - payload: [u8], - } - - impl TagTrait for CustomDstTag { - const ID: TagType = TagType::End; - - fn dst_len(header: &TagHeader) -> Self::Metadata { - let base_size = mem::size_of::() + mem::size_of::(); - header.size as usize - base_size - } - } - - let bytes = AlignedBytes([ - /* id: 0xffff_ffff */ - 0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */ - 16, 0, 0, 0, /* field a: 0xdead_beef */ - 0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */ - 0x37, 0x13, 0x37, 0x13, - ]); - - let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); - let tag = GenericTag::ref_from(bytes); - let custom_tag = tag.cast::(); - - assert_eq!(mem::size_of_val(custom_tag), 16); - assert_eq!(custom_tag.a, 0xdead_beef); - assert_eq!(custom_tag.payload.len(), 4); - assert_eq!(custom_tag.payload[0], 0x37); - assert_eq!(custom_tag.payload[1], 0x13); - assert_eq!(custom_tag.payload[2], 0x37); - assert_eq!(custom_tag.payload[3], 0x13); - } - - #[test] - fn test_tag_bytes_ref() { - let empty: &[u8] = &[]; - assert_eq!( - TagBytesRef::try_from(empty), - Err(MemoryError::MinLengthNotSatisfied) - ); - - let slice = &[0_u8, 1, 2, 3, 4, 5, 6]; - assert_eq!( - TagBytesRef::try_from(&slice[..]), - Err(MemoryError::MinLengthNotSatisfied) - ); - - let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0]); - // Guaranteed wrong alignment - let unaligned_slice = &slice[3..]; - assert_eq!( - TagBytesRef::try_from(unaligned_slice), - Err(MemoryError::WrongAlignment) - ); - - let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7]); - let slice = &slice[..]; - assert_eq!(TagBytesRef::try_from(slice), Ok(TagBytesRef(slice))); - } - - #[test] - fn test_create_generic_tag() { - #[rustfmt::skip] - let bytes = AlignedBytes::new( - [ - TagType::Cmdline.val() as u8, 0, 0, 0, - /* Tag size */ - 18, 0, 0, 0, - /* Some payload. */ - 0, 1, 2, 3, - 4, 5, 6, 7, - 8, 9, - // Padding - 0, 0, 0, 0, 0, 0 - ], - ); - let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); - let tag = GenericTag::ref_from(bytes); - assert_eq!(tag.header.typ, TagType::Cmdline); - assert_eq!(tag.header.size, 8 + 10); - } - - #[test] - fn test_cast_generic_tag_to_generic_tag() { - #[rustfmt::skip] - let bytes = AlignedBytes::new( - [ - TagType::Cmdline.val() as u8, 0, 0, 0, - /* Tag size */ - 18, 0, 0, 0, - /* Some payload. */ - 0, 1, 2, 3, - 4, 5, 6, 7, - 8, 9, - // Padding - 0, 0, 0, 0, 0, 0 - ], - ); - let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); - let tag = GenericTag::ref_from(bytes); - - // Main objective here is also that this test passes Miri. - let tag = tag.cast::(); - assert_eq!(tag.header.typ, TagType::Cmdline); - assert_eq!(tag.header.size, 8 + 10); - } - - #[test] - fn test_tag_iter() { - #[rustfmt::skip] - let bytes = AlignedBytes::new( - [ - /* Some minimal tag. */ - 0xff, 0, 0, 0, - 8, 0, 0, 0, - /* Some tag with payload. */ - 0xfe, 0, 0, 0, - 12, 0, 0, 0, - 1, 2, 3, 4, - // Padding - 0, 0, 0, 0, - /* End tag */ - 0, 0, 0, 0, - 8, 0, 0, 0, - ], - ); - let mut iter = TagIter::new(bytes.borrow()); - let first = iter.next().unwrap(); - assert_eq!(first.header.typ, TagType::Custom(0xff)); - assert_eq!(first.header.size, 8); - assert!(first.payload.is_empty()); - - let second = iter.next().unwrap(); - assert_eq!(second.header.typ, TagType::Custom(0xfe)); - assert_eq!(second.header.size, 12); - assert_eq!(&second.payload, &[1, 2, 3, 4]); - - let third = iter.next().unwrap(); - assert_eq!(third.header.typ, TagType::End); - assert_eq!(third.header.size, 8); - assert!(first.payload.is_empty()); - - assert_eq!(iter.next(), None); + fn set_size(&mut self, total_size: usize) { + self.size = total_size as u32 } } diff --git a/multiboot2/src/tag_trait.rs b/multiboot2/src/tag_trait.rs deleted file mode 100644 index 1d44d72f..00000000 --- a/multiboot2/src/tag_trait.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Module for [`TagTrait`]. - -use crate::tag::TagHeader; -use crate::TagType; -use ptr_meta::Pointee; - -/// A trait to abstract over all sized and unsized tags (DSTs). For sized tags, -/// this trait does not much. For DSTs, a [`TagTrait::dst_len`] implementation -/// must be provided, which returns the right size hint for the dynamically -/// sized portion of the struct. -/// -/// # Trivia -/// This crate uses the [`Pointee`]-abstraction of the [`ptr_meta`] crate to -/// create fat pointers for tags that are DST. -pub trait TagTrait: Pointee { - /// The numeric ID of this tag. - const ID: TagType; - - /// Returns the amount of items in the dynamically sized portion of the - /// DST. Note that this is not the amount of bytes. So if the dynamically - /// sized portion is 16 bytes in size and each element is 4 bytes big, then - /// this function must return 4. - /// - /// For sized tags, this just returns `()`. For DSTs, this returns an - /// `usize`. - fn dst_len(header: &TagHeader) -> Self::Metadata; - - /// Returns the tag as the common base tag structure. - fn as_base_tag(&self) -> &TagHeader { - let ptr = core::ptr::addr_of!(*self); - unsafe { &*ptr.cast::() } - } - - /// Returns the total size of the tag. The depends on the `size` field of - /// the tag. - fn size(&self) -> usize { - self.as_base_tag().size as usize - } - - /// Returns a slice to the underlying bytes of the tag. This includes all - /// bytes, also for tags that are DSTs. The slice length depends on the - /// `size` field of the tag. - fn as_bytes(&self) -> &[u8] { - let ptr = core::ptr::addr_of!(*self); - unsafe { core::slice::from_raw_parts(ptr.cast(), self.size()) } - } -} diff --git a/multiboot2/src/util.rs b/multiboot2/src/util.rs index c9ed3fa6..31ecc892 100644 --- a/multiboot2/src/util.rs +++ b/multiboot2/src/util.rs @@ -1,16 +1,8 @@ //! Various utilities. -use crate::ALIGNMENT; use core::fmt; use core::fmt::{Display, Formatter}; use core::str::Utf8Error; -#[cfg(feature = "alloc")] -use { - crate::{TagHeader, TagTrait}, - core::{mem, ptr}, -}; -#[cfg(feature = "builder")] -use {alloc::alloc::Layout, alloc::boxed::Box}; /// Error type describing failures when parsing the string from a tag. #[derive(Debug, PartialEq, Eq, Clone)] @@ -38,52 +30,6 @@ impl core::error::Error for StringError { } } -/// Creates a new tag implementing [`TagTrait`] on the heap. This works for -/// sized and unsized tags. However, it only makes sense to use this for tags -/// that are DSTs (unsized), as for the sized ones, you can call a regular -/// constructor and box the result. -/// -/// # Parameters -/// - `additional_bytes_slices`: Array of byte slices that should be included -/// without additional padding in-between. You don't need to add the bytes -/// for [`TagHeader`], but only additional ones. -#[cfg(feature = "alloc")] -#[must_use] -pub fn new_boxed(additional_bytes_slices: &[&[u8]]) -> Box { - let additional_size = additional_bytes_slices - .iter() - .map(|b| b.len()) - .sum::(); - - let size = mem::size_of::() + additional_size; - let alloc_size = increase_to_alignment(size); - let layout = Layout::from_size_align(alloc_size, ALIGNMENT).unwrap(); - let heap_ptr = unsafe { alloc::alloc::alloc(layout) }; - assert!(!heap_ptr.is_null()); - - unsafe { - heap_ptr.cast::().write(T::ID.val()); - heap_ptr.cast::().add(1).write(size as u32); - } - - let mut write_offset = mem::size_of::(); - for &bytes in additional_bytes_slices { - unsafe { - let len = bytes.len(); - let src = bytes.as_ptr(); - let dst = heap_ptr.add(write_offset); - ptr::copy_nonoverlapping(src, dst, len); - write_offset += len; - } - } - - let header = unsafe { heap_ptr.cast::().as_ref() }.unwrap(); - - let ptr = ptr_meta::from_raw_parts_mut(heap_ptr.cast(), T::dst_len(header)); - - unsafe { Box::from_raw(ptr) } -} - /// Parses the provided byte sequence as Multiboot string, which maps to a /// [`str`]. pub fn parse_slice_as_string(bytes: &[u8]) -> Result<&str, StringError> { @@ -91,22 +37,9 @@ pub fn parse_slice_as_string(bytes: &[u8]) -> Result<&str, StringError> { cstr.to_str().map_err(StringError::Utf8) } -/// Increases the given size to the next alignment boundary, if it is not a -/// multiple of the alignment yet. This is relevant as in Rust's [type layout], -/// the allocated size of a type is always a multiple of the alignment, even -/// if the type is smaller. -/// -/// [type layout]: https://doc.rust-lang.org/reference/type-layout.html -pub const fn increase_to_alignment(size: usize) -> usize { - let mask = ALIGNMENT - 1; - (size + mask) & !mask -} - #[cfg(test)] mod tests { use super::*; - #[cfg(feature = "alloc")] - use {crate::tag::GenericTag, crate::CommandLineTag}; #[test] fn test_parse_slice_as_string() { @@ -133,29 +66,4 @@ mod tests { // must skip everytihng after first null assert_eq!(parse_slice_as_string(b"hello\0foo"), Ok("hello")); } - - #[test] - fn test_increase_to_alignment() { - assert_eq!(increase_to_alignment(0), 0); - assert_eq!(increase_to_alignment(1), 8); - assert_eq!(increase_to_alignment(7), 8); - assert_eq!(increase_to_alignment(8), 8); - assert_eq!(increase_to_alignment(9), 16); - } - - #[test] - #[cfg(feature = "alloc")] - fn test_new_boxed() { - let tag = new_boxed::(&[&[0, 1, 2, 3]]); - assert_eq!(tag.header().typ, GenericTag::ID); - assert_eq!(tag.payload(), &[0, 1, 2, 3]); - - // Test that bytes are added consecutively without gaps. - let tag = new_boxed::(&[&[0], &[1], &[2, 3]]); - assert_eq!(tag.header().typ, GenericTag::ID); - assert_eq!(tag.payload(), &[0, 1, 2, 3]); - - let tag = new_boxed::(&[b"hello\0"]); - assert_eq!(tag.cmdline(), Ok("hello")); - } } diff --git a/multiboot2/src/vbe_info.rs b/multiboot2/src/vbe_info.rs index 8670c0ae..a7547182 100644 --- a/multiboot2/src/vbe_info.rs +++ b/multiboot2/src/vbe_info.rs @@ -1,8 +1,9 @@ //! Module for [`VBEInfoTag`]. -use crate::{TagHeader, TagTrait, TagType}; +use crate::{TagHeader, TagType}; use core::fmt; use core::mem; +use multiboot2_common::{MaybeDynSized, Tag}; /// This tag contains VBE metadata, VBE controller information returned by the /// VBE Function 00h and VBE mode information returned by the VBE Function 01h. @@ -20,7 +21,6 @@ pub struct VBEInfoTag { impl VBEInfoTag { /// Constructs a new tag. - #[cfg(feature = "builder")] #[must_use] pub fn new( mode: u16, @@ -83,12 +83,20 @@ impl VBEInfoTag { } } -impl TagTrait for VBEInfoTag { - const ID: TagType = TagType::Vbe; +impl MaybeDynSized for VBEInfoTag { + type Header = TagHeader; + + const BASE_SIZE: usize = mem::size_of::(); fn dst_len(_: &TagHeader) {} } +impl Tag for VBEInfoTag { + type IDType = TagType; + + const ID: TagType = TagType::Vbe; +} + /// VBE controller information. /// /// The capabilities of the display controller, the revision level of the @@ -156,6 +164,25 @@ impl fmt::Debug for VBEControlInfo { } } +impl Default for VBEControlInfo { + fn default() -> Self { + Self { + signature: Default::default(), + version: 0, + oem_string_ptr: 0, + capabilities: Default::default(), + mode_list_ptr: 0, + total_memory: 0, + oem_software_revision: 0, + oem_vendor_name_ptr: 0, + oem_product_name_ptr: 0, + oem_product_revision_ptr: 0, + reserved: [0; 222], + oem_data: [0; 256], + } + } +} + /// Extended information about a specific VBE display mode from the /// mode list returned by `VBEControlInfo` (VBE Function `00h`). #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -283,10 +310,44 @@ impl fmt::Debug for VBEModeInfo { } } +impl Default for VBEModeInfo { + fn default() -> Self { + Self { + mode_attributes: Default::default(), + window_a_attributes: Default::default(), + window_b_attributes: Default::default(), + window_granularity: 0, + window_size: 0, + window_a_segment: 0, + window_b_segment: 0, + window_function_ptr: 0, + pitch: 0, + resolution: (0, 0), + character_size: (0, 0), + number_of_planes: 0, + bpp: 0, + number_of_banks: 0, + memory_model: Default::default(), + bank_size: 0, + number_of_image_pages: 0, + reserved0: 0, + red_field: Default::default(), + green_field: Default::default(), + blue_field: Default::default(), + reserved_field: Default::default(), + direct_color_attributes: Default::default(), + framebuffer_base_ptr: 0, + offscreen_memory_offset: 0, + offscreen_memory_size: 0, + reserved1: [0; 206], + } + } +} + /// A VBE colour field. /// /// Describes the size and position of some colour capability. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C, packed)] pub struct VBEField { /// The size, in bits, of the color components of a direct color pixel. @@ -386,11 +447,12 @@ bitflags! { } /// The MemoryModel field specifies the general type of memory organization used in modes. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] #[allow(missing_docs)] #[allow(clippy::upper_case_acronyms)] pub enum VBEMemoryModel { + #[default] Text = 0x00, CGAGraphics = 0x01, HerculesGraphics = 0x02,