diff --git a/.github/workflows/_build-rust.yml b/.github/workflows/_build-rust.yml index 2c834c64..d855a822 100644 --- a/.github/workflows/_build-rust.yml +++ b/.github/workflows/_build-rust.yml @@ -94,23 +94,15 @@ jobs: run: | cargo doc --no-deps --document-private-items --features ${{ inputs.features }} --no-default-features cargo clippy --all-targets --features ${{ inputs.features }} --no-default-features - - name: Unit Test (UNIX) - if: inputs.do-test && runner.os != 'Windows' - run: | - curl -LsSf https://get.nexte.st/latest/linux | tar zxf - - chmod u+x cargo-nextest - ./cargo-nextest nextest run --features ${{ inputs.features }} --no-default-features - - name: Unit Test (Windows) - if: inputs.do-test && runner.os == 'Windows' - run: | - Invoke-WebRequest https://get.nexte.st/latest/windows -OutFile cargo-nextest.zip - Expand-Archive .\cargo-nextest.zip - cp .\cargo-nextest/cargo-nextest.exe . - .\cargo-nextest.exe nextest run --features ${{ inputs.features }} --no-default-features + - name: Unit Test + run: cargo test --verbose - name: Unit Test with Miri if: inputs.do-miri - # "--tests" so that the doctests are skipped. Currently, the doctest - # in miri fails. run: | rustup component add miri - cargo miri test --tests + # Run with stack-borrow model + # XXX Temporarily, just for multiboot2 crate. + cargo miri test -p multiboot2 + # Run with tree-borrow model + # XXX Temporarily, just for multiboot2 crate. + MIRIFLAGS=-Zmiri-tree-borrows cargo +nightly miri test -p multiboot2 diff --git a/.github/workflows/integrationtest.yml b/.github/workflows/integrationtest.yml index 13de84fd..2386694d 100644 --- a/.github/workflows/integrationtest.yml +++ b/.github/workflows/integrationtest.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Check out uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v26 + - uses: cachix/install-nix-action@V27 with: # This channel is only required to invoke "nix-shell". # Everything inside that nix-shell will use a pinned version of diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 02fb711c..ed022c2e 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -9,4 +9,4 @@ jobs: steps: - uses: actions/checkout@v4 # Executes "typos ." - - uses: crate-ci/typos@v1.21.0 + - uses: crate-ci/typos@v1.23.6 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6c9eb137..70715b72 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -11,6 +11,10 @@ name: "Cargo workspace" # Run on every push (tag, branch) and pull_request on: [ pull_request, push, workflow_dispatch, merge_group ] +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always diff --git a/Cargo.lock b/Cargo.lock index 5a931553..806e722e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,30 +4,43 @@ version = 3 [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.74", ] [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "multiboot2" version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad7ef048d4783355163fd0c874aac3db54b919dc6a86dc29bb13f67308b114b0" +dependencies = [ + "bitflags", + "derive_more", + "log", + "ptr_meta", + "uefi-raw", +] + +[[package]] +name = "multiboot2" +version = "0.21.0" dependencies = [ "bitflags", "derive_more", @@ -41,14 +54,14 @@ name = "multiboot2-header" version = "0.4.0" dependencies = [ "derive_more", - "multiboot2", + "multiboot2 0.20.2", ] [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -70,7 +83,7 @@ checksum = "bca9224df2e20e7c5548aeb5f110a0f3b77ef05f8585139b7148b59056168ed2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -93,6 +106,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "uefi-raw" version = "0.5.2" diff --git a/Cargo.toml b/Cargo.toml index f4e5b0ef..59e8e1e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,8 @@ exclude = [ ] [workspace.dependencies] -bitflags = "2.0.2" -derive_more = { version = "~0.99", default-features = false, features = ["display"] } +bitflags = "2.6.0" +derive_more = { version = "~0.99.18", default-features = false, features = ["display"] } log = { version = "~0.4", default-features = false } # This way, the "multiboot2" dependency in the multiboot2-header crate can be diff --git a/integration-test/bins/Cargo.lock b/integration-test/bins/Cargo.lock index 261b0100..ec2c77f7 100644 --- a/integration-test/bins/Cargo.lock +++ b/integration-test/bins/Cargo.lock @@ -28,26 +28,26 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.74", ] [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elf_rs" @@ -55,7 +55,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "894d710b6b07dae25ce69f9227ec2ffa3a3f71dc7f071acea3e1928ab4aeafdf" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "num-traits", ] @@ -81,9 +81,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "multiboot" @@ -96,22 +96,9 @@ dependencies = [ [[package]] name = "multiboot2" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d67e1b461b49127f2226c78a2b4090f72212c44fa27342bcfef93dd39bd6b86" -dependencies = [ - "bitflags 2.5.0", - "derive_more", - "log", - "ptr_meta", - "uefi-raw", -] - -[[package]] -name = "multiboot2" -version = "0.20.1" +version = "0.20.2" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "derive_more", "log", "ptr_meta", @@ -123,7 +110,7 @@ name = "multiboot2-header" version = "0.4.0" dependencies = [ "derive_more", - "multiboot2 0.20.0", + "multiboot2", ] [[package]] @@ -135,7 +122,7 @@ dependencies = [ "good_memory_allocator", "log", "multiboot", - "multiboot2 0.20.1", + "multiboot2", "multiboot2-header", "util", ] @@ -147,7 +134,7 @@ dependencies = [ "anyhow", "good_memory_allocator", "log", - "multiboot2 0.20.1", + "multiboot2", "util", "x86", ] @@ -169,9 +156,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -193,7 +180,7 @@ checksum = "bca9224df2e20e7c5548aeb5f110a0f3b77ef05f8585139b7148b59056168ed2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -246,13 +233,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "uefi-raw" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efa8716f52e8cab8bcedfd5052388a0f263b69fe5cc2561548dc6a530678333c" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "ptr_meta", "uguid", ] diff --git a/integration-test/bins/Cargo.toml b/integration-test/bins/Cargo.toml index ddd6e60e..335dd53f 100644 --- a/integration-test/bins/Cargo.toml +++ b/integration-test/bins/Cargo.toml @@ -18,3 +18,9 @@ multiboot2 = { path = "../../multiboot2", features = ["builder", "unstable"] } multiboot2-header = { path = "../../multiboot2-header", features = ["builder", "unstable"] } good_memory_allocator = "0.1" util = { path = "./util" } + +# 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. +[patch.crates-io] +multiboot2 = { path = "../../multiboot2" } diff --git a/integration-test/bins/multiboot2_chainloader/src/loader.rs b/integration-test/bins/multiboot2_chainloader/src/loader.rs index 896cf2af..3668595e 100644 --- a/integration-test/bins/multiboot2_chainloader/src/loader.rs +++ b/integration-test/bins/multiboot2_chainloader/src/loader.rs @@ -44,15 +44,15 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! { // build MBI let mbi = multiboot2::builder::InformationBuilder::new() - .bootloader_name_tag(BootLoaderNameTag::new("mb2_integrationtest_chainloader")) - .command_line_tag(CommandLineTag::new("chainloaded YEAH")) + .bootloader_name_tag(&BootLoaderNameTag::new("mb2_integrationtest_chainloader")) + .command_line_tag(&CommandLineTag::new("chainloaded YEAH")) // random non-sense memory map - .memory_map_tag(MemoryMapTag::new(&[MemoryArea::new( + .memory_map_tag(&MemoryMapTag::new(&[MemoryArea::new( 0, 0xffffffff, MemoryAreaType::Reserved, )])) - .add_module_tag(ModuleTag::new( + .add_module_tag(&ModuleTag::new( elf_mod.start as u32, elf_mod.end as u32, elf_mod.string.unwrap(), diff --git a/multiboot2-header/Changelog.md b/multiboot2-header/Changelog.md index 693d8662..e37f1b34 100644 --- a/multiboot2-header/Changelog.md +++ b/multiboot2-header/Changelog.md @@ -2,6 +2,8 @@ ## Unreleased +- **Breaking** All functions that returns something useful are now `#[must_use]` + ## 0.4.0 (2024-05-01) - added `EndHeaderTag::default()` diff --git a/multiboot2-header/src/address.rs b/multiboot2-header/src/address.rs index e15e5035..7a450e9e 100644 --- a/multiboot2-header/src/address.rs +++ b/multiboot2-header/src/address.rs @@ -1,4 +1,4 @@ -use crate::{HeaderTagFlag, HeaderTagType}; +use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::mem::size_of; /// This information does not need to be provided if the kernel image is in ELF @@ -8,9 +8,7 @@ use core::mem::size_of; #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct AddressHeaderTag { - typ: HeaderTagType, - flags: HeaderTagFlag, - size: u32, + 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. header_addr: u32, /// Contains the physical address of the beginning of the text segment. The offset in the OS image file at which to start loading is defined by the offset at which the header was found, minus (header_addr - load_addr). load_addr must be less than or equal to header_addr. @@ -24,6 +22,8 @@ pub struct AddressHeaderTag { } impl AddressHeaderTag { + /// Constructs a new tag. + #[must_use] pub const fn new( flags: HeaderTagFlag, header_addr: u32, @@ -31,10 +31,9 @@ impl AddressHeaderTag { load_end_addr: u32, bss_end_addr: u32, ) -> Self { - AddressHeaderTag { - typ: HeaderTagType::Address, - flags, - size: size_of::() as u32, + let header = HeaderTagHeader::new(HeaderTagType::Address, flags, size_of::() as u32); + Self { + header, header_addr, load_addr, load_end_addr, @@ -42,24 +41,44 @@ impl AddressHeaderTag { } } + /// Returns the [`HeaderTagType`]. + #[must_use] pub const fn typ(&self) -> HeaderTagType { - self.typ + self.header.typ() } + + /// Returns the [`HeaderTagFlag`]s. + #[must_use] pub const fn flags(&self) -> HeaderTagFlag { - self.flags + self.header.flags() } + + /// Returns the size. + #[must_use] pub const fn size(&self) -> u32 { - self.size + self.header.size() } + + /// Returns the header address. + #[must_use] pub const fn header_addr(&self) -> u32 { self.header_addr } + + /// Returns the load begin address. + #[must_use] pub const fn load_addr(&self) -> u32 { self.load_addr } + + /// Returns the load end address. + #[must_use] pub const fn load_end_addr(&self) -> u32 { self.load_end_addr } + + /// Returns the bss end address. + #[must_use] pub const fn bss_end_addr(&self) -> u32 { self.bss_end_addr } diff --git a/multiboot2-header/src/builder/header.rs b/multiboot2-header/src/builder/header.rs index 0ab65d28..2bf3ea93 100644 --- a/multiboot2-header/src/builder/header.rs +++ b/multiboot2-header/src/builder/header.rs @@ -72,6 +72,8 @@ pub struct HeaderBuilder { } impl HeaderBuilder { + /// Creates a new builder. + #[must_use] pub const fn new(arch: HeaderTagISA) -> Self { Self { arch, @@ -98,6 +100,7 @@ impl HeaderBuilder { /// 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 @@ -159,7 +162,8 @@ impl HeaderBuilder { } /// Constructs the bytes for a valid Multiboot2 header with the given properties. - pub fn build(mut self) -> HeaderBytes { + #[must_use] + pub fn build(self) -> HeaderBytes { const ALIGN: usize = 8; // PHASE 1/2: Prepare Vector @@ -205,7 +209,7 @@ impl HeaderBuilder { } /// Helper method that adds all the tags to the given vector. - fn build_add_tags(&mut self, bytes: &mut Vec) { + fn build_add_tags(&self, bytes: &mut Vec) { Self::build_add_bytes( bytes, // important that we write the correct expected length into the header! @@ -247,7 +251,11 @@ impl HeaderBuilder { } // clippy thinks this can be a const fn but the compiler denies it - #[allow(clippy::missing_const_for_fn)] + // #[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, @@ -255,38 +263,65 @@ impl HeaderBuilder { 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 diff --git a/multiboot2-header/src/builder/information_request.rs b/multiboot2-header/src/builder/information_request.rs index 543e7cc9..f5448541 100644 --- a/multiboot2-header/src/builder/information_request.rs +++ b/multiboot2-header/src/builder/information_request.rs @@ -22,6 +22,7 @@ pub struct InformationRequestHeaderTagBuilder { #[cfg(feature = "builder")] impl InformationRequestHeaderTagBuilder { /// New builder. + #[must_use] pub const fn new(flag: HeaderTagFlag) -> Self { Self { irs: BTreeSet::new(), @@ -31,6 +32,7 @@ impl InformationRequestHeaderTagBuilder { /// 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::(); @@ -38,12 +40,14 @@ impl InformationRequestHeaderTagBuilder { } /// 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 diff --git a/multiboot2-header/src/builder/traits.rs b/multiboot2-header/src/builder/traits.rs index f936c4e8..cea09fdf 100644 --- a/multiboot2-header/src/builder/traits.rs +++ b/multiboot2-header/src/builder/traits.rs @@ -11,7 +11,7 @@ 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(crate) trait StructAsBytes: Sized { +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 { diff --git a/multiboot2-header/src/console.rs b/multiboot2-header/src/console.rs index 6307201d..392a6c07 100644 --- a/multiboot2-header/src/console.rs +++ b/multiboot2-header/src/console.rs @@ -1,4 +1,4 @@ -use crate::{HeaderTagFlag, HeaderTagType}; +use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::mem::size_of; /// Possible flags for [`ConsoleHeaderTag`]. @@ -16,31 +16,42 @@ pub enum ConsoleHeaderTagFlags { #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct ConsoleHeaderTag { - typ: HeaderTagType, - flags: HeaderTagFlag, - size: u32, + header: HeaderTagHeader, console_flags: ConsoleHeaderTagFlags, } impl ConsoleHeaderTag { + /// Constructs a new tag. + #[must_use] pub const fn new(flags: HeaderTagFlag, console_flags: ConsoleHeaderTagFlags) -> Self { - ConsoleHeaderTag { - typ: HeaderTagType::ConsoleFlags, - flags, - size: size_of::() as u32, + let header = + HeaderTagHeader::new(HeaderTagType::ConsoleFlags, flags, size_of::() as u32); + Self { + header, console_flags, } } + /// Returns the [`HeaderTagType`]. + #[must_use] pub const fn typ(&self) -> HeaderTagType { - self.typ + self.header.typ() } + + /// Returns the [`HeaderTagFlag`]s. + #[must_use] pub const fn flags(&self) -> HeaderTagFlag { - self.flags + self.header.flags() } + + /// Returns the size. + #[must_use] pub const fn size(&self) -> u32 { - self.size + self.header.size() } + + /// Returns the [`ConsoleHeaderTagFlags`]. + #[must_use] pub const fn console_flags(&self) -> ConsoleHeaderTagFlags { self.console_flags } diff --git a/multiboot2-header/src/end.rs b/multiboot2-header/src/end.rs index 15f8c76f..1e806460 100644 --- a/multiboot2-header/src/end.rs +++ b/multiboot2-header/src/end.rs @@ -1,15 +1,11 @@ -use crate::{HeaderTagFlag, HeaderTagType}; +use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::mem::size_of; /// Terminates a list of optional tags in a Multiboot2 header. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct EndHeaderTag { - // u16 value - typ: HeaderTagType, - // u16 value - flags: HeaderTagFlag, - size: u32, + header: HeaderTagHeader, } impl Default for EndHeaderTag { @@ -19,22 +15,33 @@ impl Default for EndHeaderTag { } impl EndHeaderTag { + /// Constructs a new tag. + #[must_use] pub const fn new() -> Self { - EndHeaderTag { - typ: HeaderTagType::End, - flags: HeaderTagFlag::Required, - size: size_of::() as u32, - } + let header = HeaderTagHeader::new( + HeaderTagType::EntryAddress, + HeaderTagFlag::Required, + size_of::() as u32, + ); + Self { header } } + /// Returns the [`HeaderTagType`]. + #[must_use] pub const fn typ(&self) -> HeaderTagType { - self.typ + self.header.typ() } + + /// Returns the [`HeaderTagFlag`]s. + #[must_use] pub const fn flags(&self) -> HeaderTagFlag { - self.flags + self.header.flags() } + + /// Returns the size. + #[must_use] pub const fn size(&self) -> u32 { - self.size + self.header.size() } } diff --git a/multiboot2-header/src/entry_address.rs b/multiboot2-header/src/entry_address.rs index 513e7b35..cef429e3 100644 --- a/multiboot2-header/src/entry_address.rs +++ b/multiboot2-header/src/entry_address.rs @@ -1,4 +1,4 @@ -use crate::{HeaderTagFlag, HeaderTagType}; +use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::fmt; use core::fmt::{Debug, Formatter}; use core::mem::size_of; @@ -8,31 +8,39 @@ use core::mem::size_of; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct EntryAddressHeaderTag { - typ: HeaderTagType, - flags: HeaderTagFlag, - size: u32, + header: HeaderTagHeader, entry_addr: u32, } impl EntryAddressHeaderTag { + /// Constructs a new tag. + #[must_use] pub const fn new(flags: HeaderTagFlag, entry_addr: u32) -> Self { - EntryAddressHeaderTag { - typ: HeaderTagType::EntryAddress, - flags, - size: size_of::() as u32, - entry_addr, - } + let header = + HeaderTagHeader::new(HeaderTagType::EntryAddress, flags, size_of::() as u32); + Self { header, entry_addr } } + /// Returns the [`HeaderTagType`]. + #[must_use] pub const fn typ(&self) -> HeaderTagType { - self.typ + self.header.typ() } + + /// Returns the [`HeaderTagFlag`]s. + #[must_use] pub const fn flags(&self) -> HeaderTagFlag { - self.flags + self.header.flags() } + + /// Returns the size. + #[must_use] pub const fn size(&self) -> u32 { - self.size + self.header.size() } + + /// Returns the entry address. + #[must_use] pub const fn entry_addr(&self) -> u32 { self.entry_addr } @@ -41,9 +49,9 @@ impl EntryAddressHeaderTag { impl Debug for EntryAddressHeaderTag { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("EntryAddressHeaderTag") - .field("type", &{ self.typ }) - .field("flags", &{ self.flags }) - .field("size", &{ self.size }) + .field("type", &self.typ()) + .field("flags", &self.flags()) + .field("size", &self.size()) .field("entry_addr", &(self.entry_addr as *const u32)) .finish() } diff --git a/multiboot2-header/src/entry_efi_32.rs b/multiboot2-header/src/entry_efi_32.rs index 5f6818c1..c6ca3a71 100644 --- a/multiboot2-header/src/entry_efi_32.rs +++ b/multiboot2-header/src/entry_efi_32.rs @@ -1,4 +1,4 @@ -use crate::{HeaderTagFlag, HeaderTagType}; +use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::fmt; use core::fmt::{Debug, Formatter}; use core::mem::size_of; @@ -12,31 +12,42 @@ use core::mem::size_of; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct EntryEfi32HeaderTag { - typ: HeaderTagType, - flags: HeaderTagFlag, - size: u32, + header: HeaderTagHeader, entry_addr: u32, } impl EntryEfi32HeaderTag { + /// Constructs a new tag. + #[must_use] pub const fn new(flags: HeaderTagFlag, entry_addr: u32) -> Self { - EntryEfi32HeaderTag { - typ: HeaderTagType::EntryAddressEFI32, + let header = HeaderTagHeader::new( + HeaderTagType::EntryAddressEFI32, flags, - size: size_of::() as u32, - entry_addr, - } + size_of::() as u32, + ); + Self { header, entry_addr } } + /// Returns the [`HeaderTagType`]. + #[must_use] pub const fn typ(&self) -> HeaderTagType { - self.typ + self.header.typ() } + + /// Returns the [`HeaderTagFlag`]s. + #[must_use] pub const fn flags(&self) -> HeaderTagFlag { - self.flags + self.header.flags() } + + /// Returns the size. + #[must_use] pub const fn size(&self) -> u32 { - self.size + self.header.size() } + + /// Returns the entry address. + #[must_use] pub const fn entry_addr(&self) -> u32 { self.entry_addr } @@ -45,9 +56,9 @@ impl EntryEfi32HeaderTag { impl Debug for EntryEfi32HeaderTag { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("EntryEfi32HeaderTag") - .field("type", &{ self.typ }) - .field("flags", &{ self.flags }) - .field("size", &{ self.size }) + .field("type", &self.typ()) + .field("flags", &self.flags()) + .field("size", &self.size()) .field("entry_addr", &(self.entry_addr as *const u32)) .finish() } diff --git a/multiboot2-header/src/entry_efi_64.rs b/multiboot2-header/src/entry_efi_64.rs index dea41a95..e145f5cf 100644 --- a/multiboot2-header/src/entry_efi_64.rs +++ b/multiboot2-header/src/entry_efi_64.rs @@ -1,4 +1,4 @@ -use crate::{HeaderTagFlag, HeaderTagType}; +use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::fmt; use core::fmt::{Debug, Formatter}; use core::mem::size_of; @@ -12,31 +12,42 @@ use core::mem::size_of; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct EntryEfi64HeaderTag { - typ: HeaderTagType, - flags: HeaderTagFlag, - size: u32, + header: HeaderTagHeader, entry_addr: u32, } impl EntryEfi64HeaderTag { + /// Constructs a new tag. + #[must_use] pub const fn new(flags: HeaderTagFlag, entry_addr: u32) -> Self { - EntryEfi64HeaderTag { - typ: HeaderTagType::EntryAddressEFI64, + let header = HeaderTagHeader::new( + HeaderTagType::EntryAddressEFI64, flags, - size: size_of::() as u32, - entry_addr, - } + size_of::() as u32, + ); + Self { header, entry_addr } } + /// Returns the [`HeaderTagType`]. + #[must_use] pub const fn typ(&self) -> HeaderTagType { - self.typ + self.header.typ() } + + /// Returns the [`HeaderTagFlag`]s. + #[must_use] pub const fn flags(&self) -> HeaderTagFlag { - self.flags + self.header.flags() } + + /// Returns the size. + #[must_use] pub const fn size(&self) -> u32 { - self.size + self.header.size() } + + /// Returns the entry address. + #[must_use] pub const fn entry_addr(&self) -> u32 { self.entry_addr } @@ -45,9 +56,9 @@ impl EntryEfi64HeaderTag { impl Debug for EntryEfi64HeaderTag { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("EntryEfi64HeaderTag") - .field("type", &{ self.typ }) - .field("flags", &{ self.flags }) - .field("size", &{ self.size }) + .field("type", &self.typ()) + .field("flags", &self.flags()) + .field("size", &self.size()) .field("entry_addr", &(self.entry_addr as *const u32)) .finish() } diff --git a/multiboot2-header/src/framebuffer.rs b/multiboot2-header/src/framebuffer.rs index 1b9d1772..dfddb2d8 100644 --- a/multiboot2-header/src/framebuffer.rs +++ b/multiboot2-header/src/framebuffer.rs @@ -1,5 +1,5 @@ -use crate::{HeaderTagFlag, HeaderTagType}; -use core::mem::size_of; +use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; +use core::mem; /// Specifies the preferred graphics mode. If this tag /// is present the bootloader assumes that the payload @@ -8,41 +8,61 @@ use core::mem::size_of; #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct FramebufferHeaderTag { - typ: HeaderTagType, - flags: HeaderTagFlag, - size: u32, + header: HeaderTagHeader, width: u32, height: u32, depth: u32, } impl FramebufferHeaderTag { + /// Constructs a new tag. + #[must_use] pub const fn new(flags: HeaderTagFlag, width: u32, height: u32, depth: u32) -> Self { - FramebufferHeaderTag { - typ: HeaderTagType::Framebuffer, + let header = HeaderTagHeader::new( + HeaderTagType::Framebuffer, flags, - size: size_of::() as u32, + mem::size_of::() as u32, + ); + Self { + header, width, height, depth, } } + /// Returns the [`HeaderTagType`]. + #[must_use] pub const fn typ(&self) -> HeaderTagType { - self.typ + self.header.typ() } + + /// Returns the [`HeaderTagFlag`]s. + #[must_use] pub const fn flags(&self) -> HeaderTagFlag { - self.flags + self.header.flags() } + + /// Returns the size. + #[must_use] pub const fn size(&self) -> u32 { - self.size + self.header.size() } + + /// Returns the width. + #[must_use] pub const fn width(&self) -> u32 { self.width } + + /// Returns the height. + #[must_use] pub const fn height(&self) -> u32 { self.height } + + /// Returns the depth. + #[must_use] pub const fn depth(&self) -> u32 { self.depth } @@ -50,12 +70,12 @@ impl FramebufferHeaderTag { #[cfg(test)] mod tests { - use crate::FramebufferHeaderTag; + use super::*; #[test] fn test_assert_size() { assert_eq!( - core::mem::size_of::(), + mem::size_of::(), 2 + 2 + 4 + 4 + 4 + 4 ); } diff --git a/multiboot2-header/src/header.rs b/multiboot2-header/src/header.rs index cb256f92..4c3c988b 100644 --- a/multiboot2-header/src/header.rs +++ b/multiboot2-header/src/header.rs @@ -1,8 +1,8 @@ use crate::{ AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag, EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, - HeaderTag, HeaderTagISA, HeaderTagType, InformationRequestHeaderTag, ModuleAlignHeaderTag, - RelocatableHeaderTag, + HeaderTagHeader, HeaderTagISA, HeaderTagType, InformationRequestHeaderTag, + ModuleAlignHeaderTag, RelocatableHeaderTag, }; use core::fmt::{Debug, Formatter}; use core::mem::size_of; @@ -103,89 +103,106 @@ impl<'a> Multiboot2Header<'a> { } /// Wrapper around [`Multiboot2BasicHeader::verify_checksum`]. + #[must_use] pub const fn verify_checksum(&self) -> bool { self.0.verify_checksum() } /// Wrapper around [`Multiboot2BasicHeader::header_magic`]. + #[must_use] pub const fn header_magic(&self) -> u32 { self.0.header_magic() } /// Wrapper around [`Multiboot2BasicHeader::arch`]. + #[must_use] pub const fn arch(&self) -> HeaderTagISA { self.0.arch() } /// Wrapper around [`Multiboot2BasicHeader::length`]. + #[must_use] pub const fn length(&self) -> u32 { self.0.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() } /// Wrapper around [`Multiboot2BasicHeader::calc_checksum`]. + #[must_use] pub const fn calc_checksum(magic: u32, arch: HeaderTagISA, length: u32) -> u32 { Multiboot2BasicHeader::calc_checksum(magic, arch, length) } /// Search for the address header tag. + #[must_use] pub fn address_tag(&self) -> Option<&AddressHeaderTag> { self.get_tag(HeaderTagType::Address) - .map(|tag| unsafe { &*(tag as *const HeaderTag as *const AddressHeaderTag) }) + .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const AddressHeaderTag) }) } /// Search for the entry address header tag. + #[must_use] pub fn entry_address_tag(&self) -> Option<&EntryAddressHeaderTag> { self.get_tag(HeaderTagType::EntryAddress) - .map(|tag| unsafe { &*(tag as *const HeaderTag as *const EntryAddressHeaderTag) }) + .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const EntryAddressHeaderTag) }) } /// Search for the EFI32 entry address header tag. + #[must_use] pub fn entry_address_efi32_tag(&self) -> Option<&EntryEfi32HeaderTag> { self.get_tag(HeaderTagType::EntryAddressEFI32) - .map(|tag| unsafe { &*(tag as *const HeaderTag as *const EntryEfi32HeaderTag) }) + .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const EntryEfi32HeaderTag) }) } /// Search for the EFI64 entry address header tag. + #[must_use] pub fn entry_address_efi64_tag(&self) -> Option<&EntryEfi64HeaderTag> { self.get_tag(HeaderTagType::EntryAddressEFI64) - .map(|tag| unsafe { &*(tag as *const HeaderTag as *const EntryEfi64HeaderTag) }) + .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const EntryEfi64HeaderTag) }) } /// Search for the console flags header tag. + #[must_use] pub fn console_flags_tag(&self) -> Option<&ConsoleHeaderTag> { self.get_tag(HeaderTagType::ConsoleFlags) - .map(|tag| unsafe { &*(tag as *const HeaderTag as *const ConsoleHeaderTag) }) + .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const ConsoleHeaderTag) }) } /// Search for the framebuffer header tag. + #[must_use] pub fn framebuffer_tag(&self) -> Option<&FramebufferHeaderTag> { self.get_tag(HeaderTagType::Framebuffer) - .map(|tag| unsafe { &*(tag as *const HeaderTag as *const FramebufferHeaderTag) }) + .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const FramebufferHeaderTag) }) } /// Search for the module align header tag. + #[must_use] pub fn module_align_tag(&self) -> Option<&ModuleAlignHeaderTag> { self.get_tag(HeaderTagType::ModuleAlign) - .map(|tag| unsafe { &*(tag as *const HeaderTag as *const ModuleAlignHeaderTag) }) + .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const ModuleAlignHeaderTag) }) } /// Search for the EFI Boot Services header tag. + #[must_use] pub fn efi_boot_services_tag(&self) -> Option<&EfiBootServiceHeaderTag> { - self.get_tag(HeaderTagType::EfiBS) - .map(|tag| unsafe { &*(tag as *const HeaderTag as *const EfiBootServiceHeaderTag) }) + self.get_tag(HeaderTagType::EfiBS).map(|tag| unsafe { + &*(tag as *const HeaderTagHeader as *const EfiBootServiceHeaderTag) + }) } /// Search for the EFI32 entry address header tag. + #[must_use] pub fn relocatable_tag(&self) -> Option<&RelocatableHeaderTag> { self.get_tag(HeaderTagType::Relocatable) - .map(|tag| unsafe { &*(tag as *const HeaderTag as *const RelocatableHeaderTag) }) + .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const RelocatableHeaderTag) }) } - fn get_tag(&self, typ: HeaderTagType) -> Option<&HeaderTag> { + fn get_tag(&self, typ: HeaderTagType) -> Option<&HeaderTagHeader> { self.iter() .map(|tag| unsafe { tag.as_ref() }.unwrap()) .find(|tag| tag.typ() == typ) @@ -229,7 +246,7 @@ impl Multiboot2BasicHeader { pub(crate) const fn new(arch: HeaderTagISA, length: u32) -> Self { let magic = MAGIC; let checksum = Self::calc_checksum(magic, arch, length); - Multiboot2BasicHeader { + Self { header_magic: magic, arch, length, @@ -238,25 +255,38 @@ impl Multiboot2BasicHeader { } /// Verifies that a Multiboot2 header is valid. + #[must_use] pub const fn verify_checksum(&self) -> bool { let check = Self::calc_checksum(self.header_magic, self.arch, self.length); check == self.checksum } /// Calculates the checksum as described in the spec. + #[must_use] pub const fn calc_checksum(magic: u32, arch: HeaderTagISA, length: u32) -> u32 { (0x100000000 - magic as u64 - arch as u64 - length as u64) as u32 } + /// Returns the header magic. + #[must_use] pub const fn header_magic(&self) -> u32 { self.header_magic } + + /// Returns the [`HeaderTagISA`]. + #[must_use] pub const fn arch(&self) -> HeaderTagISA { self.arch } + + /// Returns the length. + #[must_use] pub const fn length(&self) -> u32 { self.length } + + /// Returns the checksum. + #[must_use] pub const fn checksum(&self) -> u32 { self.checksum } @@ -265,12 +295,13 @@ impl Multiboot2BasicHeader { /// /// # Panics /// See doc of [`Multiboot2HeaderTagIter`]. + #[must_use] pub fn tag_iter(&self) -> Multiboot2HeaderTagIter { - let base_hdr_size = size_of::(); + 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 Multiboot2BasicHeader; + 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 @@ -278,7 +309,7 @@ impl Multiboot2BasicHeader { // 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 HeaderTag; + 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) } @@ -307,7 +338,7 @@ impl Debug for Multiboot2BasicHeader { #[derive(Clone)] pub struct Multiboot2HeaderTagIter { /// 8-byte aligned base address - base: *const HeaderTag, + base: *const HeaderTagHeader, /// Offset in bytes from the base address. /// Always <= than size. n: u32, @@ -324,11 +355,11 @@ pub struct Multiboot2HeaderTagIter { } impl Multiboot2HeaderTagIter { - fn new(base: *const HeaderTag, size: u32) -> Self { + 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 HeaderTag; + let base = base as *const HeaderTagHeader; Self { base, n: 0, @@ -340,7 +371,7 @@ impl Multiboot2HeaderTagIter { } impl Iterator for Multiboot2HeaderTagIter { - type Item = *const HeaderTag; + type Item = *const HeaderTagHeader; fn next(&mut self) -> Option { // no more bytes left to check; length reached @@ -351,7 +382,7 @@ impl Iterator for Multiboot2HeaderTagIter { // 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 HeaderTag; + let ptr = ptr as *const HeaderTagHeader; assert_eq!(ptr as usize % 8, 0, "must be 8-byte aligned"); let tag = unsafe { &*ptr }; assert!( diff --git a/multiboot2-header/src/information_request.rs b/multiboot2-header/src/information_request.rs index c5bb1d6c..f19e15bc 100644 --- a/multiboot2-header/src/information_request.rs +++ b/multiboot2-header/src/information_request.rs @@ -1,4 +1,4 @@ -use crate::{HeaderTagFlag, MbiTagType}; +use crate::{HeaderTagFlag, HeaderTagHeader, MbiTagType}; use crate::{HeaderTagType, MbiTagTypeId}; use core::fmt; use core::fmt::{Debug, Formatter}; @@ -11,9 +11,7 @@ use multiboot2::TagType; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct InformationRequestHeaderTag { - typ: HeaderTagType, - flags: HeaderTagFlag, - size: u32, + header: HeaderTagHeader, // Length is determined by size. // Must be parsed during runtime with unsafe pointer magic and the size field. requests: [MbiTagTypeId; N], @@ -23,28 +21,38 @@ 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. + #[must_use] pub fn new(flags: HeaderTagFlag, requests: [MbiTagTypeId; N], size: Option) -> Self { - InformationRequestHeaderTag { - typ: HeaderTagType::InformationRequest, + let header = HeaderTagHeader::new( + HeaderTagType::InformationRequest, flags, - size: size.unwrap_or(size_of::() as u32), - requests, - } + size.unwrap_or(size_of::() as u32), + ); + Self { header, requests } } + /// Returns the [`HeaderTagType`]. + #[must_use] pub const fn typ(&self) -> HeaderTagType { - self.typ + self.header.typ() } + + /// Returns the [`HeaderTagFlag`]s. + #[must_use] pub const fn flags(&self) -> HeaderTagFlag { - self.flags + self.header.flags() } + + /// Returns the size. + #[must_use] pub const fn size(&self) -> u32 { - self.size + 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 @@ -54,9 +62,10 @@ impl InformationRequestHeaderTag { /// 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. + #[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; + let size_diff = self.size() - base_struct_size as u32; if size_diff > 0 { size_diff / size_of::() as u32 } else { @@ -65,10 +74,11 @@ impl InformationRequestHeaderTag { } /// 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 InformationRequestHeaderTag; + 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; @@ -79,10 +89,10 @@ impl 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("type", &self.typ()) + .field("flags", &self.flags()) + .field("size", &self.size()) + .field("requests", &self.req_iter()) .finish() } } diff --git a/multiboot2-header/src/lib.rs b/multiboot2-header/src/lib.rs index 456f4682..06b6b726 100644 --- a/multiboot2-header/src/lib.rs +++ b/multiboot2-header/src/lib.rs @@ -38,10 +38,22 @@ #![no_std] #![cfg_attr(feature = "unstable", feature(error_in_core))] -#![deny(rustdoc::all)] -#![deny(clippy::all)] -#![deny(clippy::missing_const_for_fn)] +// --- BEGIN STYLE CHECKS --- +#![deny( + clippy::all, + clippy::cargo, + clippy::nursery, + clippy::must_use_candidate, + // clippy::restriction, + // clippy::pedantic +)] +// now allow a few rules which are denied by the above statement +// --> They are either ridiculous, not necessary, or we can't fix them. +#![allow(clippy::multiple_crate_versions)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] +#![deny(rustdoc::all)] +// --- END STYLE CHECKS --- #[cfg(feature = "builder")] extern crate alloc; diff --git a/multiboot2-header/src/module_align.rs b/multiboot2-header/src/module_align.rs index 0ec2eb4b..c67acc58 100644 --- a/multiboot2-header/src/module_align.rs +++ b/multiboot2-header/src/module_align.rs @@ -1,32 +1,38 @@ -use crate::{HeaderTagFlag, HeaderTagType}; +use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::mem::size_of; /// If this tag is present, provided boot modules must be page aligned. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct ModuleAlignHeaderTag { - typ: HeaderTagType, - flags: HeaderTagFlag, - size: u32, + header: HeaderTagHeader, } impl ModuleAlignHeaderTag { + /// Constructs a new tag. + #[must_use] pub const fn new(flags: HeaderTagFlag) -> Self { - ModuleAlignHeaderTag { - typ: HeaderTagType::ModuleAlign, - flags, - size: size_of::() as u32, - } + let header = + HeaderTagHeader::new(HeaderTagType::ModuleAlign, flags, size_of::() as u32); + Self { header } } + /// Returns the [`HeaderTagType`]. + #[must_use] pub const fn typ(&self) -> HeaderTagType { - self.typ + self.header.typ() } + + /// Returns the [`HeaderTagFlag`]s. + #[must_use] pub const fn flags(&self) -> HeaderTagFlag { - self.flags + self.header.flags() } + + /// Returns the size. + #[must_use] pub const fn size(&self) -> u32 { - self.size + self.header.size() } } diff --git a/multiboot2-header/src/relocatable.rs b/multiboot2-header/src/relocatable.rs index ca8aea49..80cfaf96 100644 --- a/multiboot2-header/src/relocatable.rs +++ b/multiboot2-header/src/relocatable.rs @@ -1,4 +1,4 @@ -use crate::{HeaderTagFlag, HeaderTagType}; +use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::fmt; use core::fmt::{Debug, Formatter}; use core::mem::size_of; @@ -22,9 +22,7 @@ pub enum RelocatableHeaderTagPreference { #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct RelocatableHeaderTag { - typ: HeaderTagType, - flags: HeaderTagFlag, - size: u32, + header: HeaderTagHeader, /// Lowest possible physical address at which image should be loaded. The bootloader cannot load any part of image below this address min_addr: u32, /// Highest possible physical address at which loaded image should end. The bootloader cannot load any part of image above this address. @@ -35,6 +33,8 @@ pub struct RelocatableHeaderTag { } impl RelocatableHeaderTag { + /// Constructs a new tag. + #[must_use] pub const fn new( flags: HeaderTagFlag, min_addr: u32, @@ -42,10 +42,10 @@ impl RelocatableHeaderTag { align: u32, preference: RelocatableHeaderTagPreference, ) -> Self { - RelocatableHeaderTag { - typ: HeaderTagType::Relocatable, - flags, - size: size_of::() as u32, + let header = + HeaderTagHeader::new(HeaderTagType::Relocatable, flags, size_of::() as u32); + Self { + header, min_addr, max_addr, align, @@ -53,24 +53,44 @@ impl RelocatableHeaderTag { } } + /// Returns the [`HeaderTagType`]. + #[must_use] pub const fn typ(&self) -> HeaderTagType { - self.typ + self.header.typ() } + + /// Returns the [`HeaderTagFlag`]s. + #[must_use] pub const fn flags(&self) -> HeaderTagFlag { - self.flags + self.header.flags() } + + /// Returns the size. + #[must_use] pub const fn size(&self) -> u32 { - self.size + self.header.size() } + + /// Return the minimum address. + #[must_use] pub const fn min_addr(&self) -> u32 { self.min_addr } + + /// Return the maximum address. + #[must_use] pub const fn max_addr(&self) -> u32 { self.max_addr } + + /// Return the alignment. + #[must_use] pub const fn align(&self) -> u32 { self.align } + + /// Return the preference. + #[must_use] pub const fn preference(&self) -> RelocatableHeaderTagPreference { self.preference } @@ -79,9 +99,9 @@ impl RelocatableHeaderTag { impl Debug for RelocatableHeaderTag { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("RelocatableHeaderTag") - .field("type", &{ self.typ }) - .field("flags", &{ self.flags }) - .field("size", &{ self.size }) + .field("type", &self.typ()) + .field("flags", &self.flags()) + .field("size", &self.size()) // trick to print this as hexadecimal pointer .field("min_addr", &(self.min_addr as *const u32)) .field("max_addr", &(self.max_addr as *const u32)) diff --git a/multiboot2-header/src/tags.rs b/multiboot2-header/src/tags.rs index 51242697..c51aedc0 100644 --- a/multiboot2-header/src/tags.rs +++ b/multiboot2-header/src/tags.rs @@ -17,7 +17,7 @@ pub enum HeaderTagISA { /// Possible types for header tags of a Multiboot2 header. The names and values are taken /// from the example C code at the bottom of the Multiboot2 specification. This value -/// stands in the `typ` property of [`crate::tags::HeaderTag`]. +/// stands in the `typ` property of [`HeaderTagHeader`]. #[repr(u16)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum HeaderTagType { @@ -47,6 +47,7 @@ pub enum HeaderTagType { impl HeaderTagType { /// Returns the number of possible variants. + #[must_use] pub const fn count() -> u32 { 11 } @@ -56,31 +57,46 @@ impl HeaderTagType { #[repr(u16)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum HeaderTagFlag { + /// Bootloader must provide this tag. If this is not possible, the + /// bootloader will fail loading the kernel. Required = 0, + /// Bootloader should provide the tag if possible. Optional = 1, } -/// Common properties for all header tags. Other tags may have additional fields -/// that depend on the `typ` and the `size` field. All tags share the same beginning of the -/// struct. +/// The common header that all header tags share. Specific tags may have +/// additional fields that depend on the `typ` and the `size` field. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] -pub struct HeaderTag { +pub struct HeaderTagHeader { + typ: HeaderTagType, /* u16 */ // u16 value - typ: HeaderTagType, - // u16 value - flags: HeaderTagFlag, + flags: HeaderTagFlag, /* u16 */ size: u32, - // maybe additional fields (tag specific) + // Followed by optional additional tag specific fields. } -impl HeaderTag { +impl HeaderTagHeader { + /// Creates a new header. + #[must_use] + pub const fn new(typ: HeaderTagType, flags: HeaderTagFlag, size: u32) -> Self { + Self { typ, flags, size } + } + + /// Returns the [`HeaderTagType`]. + #[must_use] pub const fn typ(&self) -> HeaderTagType { self.typ } + + /// Returns the [`HeaderTagFlag`]s. + #[must_use] pub const fn flags(&self) -> HeaderTagFlag { self.flags } + + /// Returns the size. + #[must_use] pub const fn size(&self) -> u32 { self.size } @@ -88,10 +104,10 @@ impl HeaderTag { #[cfg(test)] mod tests { - use crate::HeaderTag; + use crate::HeaderTagHeader; #[test] fn test_assert_size() { - assert_eq!(core::mem::size_of::(), 2 + 2 + 4); + assert_eq!(core::mem::size_of::(), 2 + 2 + 4); } } diff --git a/multiboot2-header/src/uefi_bs.rs b/multiboot2-header/src/uefi_bs.rs index 93204b3f..ce8b0a32 100644 --- a/multiboot2-header/src/uefi_bs.rs +++ b/multiboot2-header/src/uefi_bs.rs @@ -1,4 +1,4 @@ -use crate::{HeaderTagFlag, HeaderTagType}; +use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use core::mem::size_of; /// This tag indicates that payload supports starting without terminating UEFI boot services. @@ -6,28 +6,33 @@ use core::mem::size_of; #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct EfiBootServiceHeaderTag { - typ: HeaderTagType, - flags: HeaderTagFlag, - size: u32, + header: HeaderTagHeader, } impl EfiBootServiceHeaderTag { + /// Constructs a new tag. + #[must_use] pub const fn new(flags: HeaderTagFlag) -> Self { - EfiBootServiceHeaderTag { - typ: HeaderTagType::EfiBS, - flags, - size: size_of::() as u32, - } + let header = HeaderTagHeader::new(HeaderTagType::EfiBS, flags, size_of::() as u32); + Self { header } } + /// Returns the [`HeaderTagType`]. + #[must_use] pub const fn typ(&self) -> HeaderTagType { - self.typ + self.header.typ() } + + /// Returns the [`HeaderTagFlag`]s. + #[must_use] pub const fn flags(&self) -> HeaderTagFlag { - self.flags + self.header.flags() } + + /// Returns the size. + #[must_use] pub const fn size(&self) -> u32 { - self.size + self.header.size() } } diff --git a/multiboot2/Cargo.toml b/multiboot2/Cargo.toml index 152d34d2..989a91a7 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.20.2" +version = "0.21.0" authors = [ "Philipp Oppermann ", "Calvin Lee ", @@ -45,12 +45,14 @@ bitflags.workspace = true derive_more.workspace = true log.workspace = true +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 } -ptr_meta = { version = "~0.2", default-features = false } + +[dev-dependencies] [package.metadata.docs.rs] all-features = true diff --git a/multiboot2/Changelog.md b/multiboot2/Changelog.md index 89055385..a8379a4e 100644 --- a/multiboot2/Changelog.md +++ b/multiboot2/Changelog.md @@ -1,5 +1,40 @@ # CHANGELOG for crate `multiboot2` +## Unreleased + +- + +## 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 +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. + +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 +release and you'll be fine!** + +All previous releases on crates.io have been yanked. + +- **Breaking:** All functions that returns something useful are + now `#[must_use]` +- **Breaking:** More public fields in tags were replaced by public getters, such + as `SmbiosTag::major()` +- **Breaking:** Methods of `InformationBuilder` to add tags now consume + references instead of owned values +- **Breaking:** The `BoxedDst` has been removed in favor of a normal Rust `Box`. + This only affects you if you use the `builder` feature. +- **Breaking:** MSRV is 1.75 +- **Breaking:** Introduced new `TagHeader` type as replacement for the `Tag` + type that will be changed in the next step. `Tag` has been renamed to an + internal-only `GenericTag` type. +- Added missing `InformationBuilder::vbe_info_tag` +- documentation enhancements +- updated dependencies + ## 0.20.2 (2024-05-26) - fix Debug implementation of `EfiMemoryMapTag` diff --git a/multiboot2/src/boot_information.rs b/multiboot2/src/boot_information.rs new file mode 100644 index 00000000..8ed11b6b --- /dev/null +++ b/multiboot2/src/boot_information.rs @@ -0,0 +1,463 @@ +//! Module for [`BootInformation`]. + +#[cfg(feature = "builder")] +use crate::builder::AsBytes; +use crate::framebuffer::UnknownFramebufferType; +use crate::tag::{TagHeader, TagIter}; +use crate::{ + module, BasicMemoryInfoTag, BootLoaderNameTag, CommandLineTag, EFIBootServicesNotExitedTag, + EFIImageHandle32Tag, EFIImageHandle64Tag, EFIMemoryMapTag, EFISdt32Tag, EFISdt64Tag, + ElfSectionIter, ElfSectionsTag, EndTag, FramebufferTag, ImageLoadPhysAddrTag, MemoryMapTag, + ModuleIter, RsdpV1Tag, RsdpV2Tag, SmbiosTag, TagTrait, VBEInfoTag, +}; +use core::fmt; +use core::mem; +use core::ptr; +use derive_more::Display; + +/// Error type that describes errors while loading/parsing a multiboot2 information structure +/// from a given address. +#[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")] + NoEndTag, +} + +#[cfg(feature = "unstable")] +impl core::error::Error for MbiLoadError {} + +/// The basic header of a [`BootInformation`] as sized Rust type. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(C, align(8))] +pub struct BootInformationHeader { + // size is multiple of 8 + total_size: u32, + _reserved: u32, + // Followed by the boot information tags. +} + +impl BootInformationHeader { + #[cfg(feature = "builder")] + pub(crate) const fn new(total_size: u32) -> Self { + Self { + total_size, + _reserved: 0, + } + } + + /// Returns the total size of the structure. + #[must_use] + pub const fn total_size(&self) -> u32 { + self.total_size + } +} + +#[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 }; + + end_tag.typ == EndTag::ID && end_tag.size as usize == mem::size_of::() + } +} + +/// A Multiboot 2 Boot Information (MBI) accessor. +#[repr(transparent)] +pub struct BootInformation<'a>(&'a BootInformationInner); + +impl<'a> BootInformation<'a> { + /// Loads the [`BootInformation`] from a pointer. The pointer must be valid + /// and aligned to an 8-byte boundary, as defined by the spec. + /// + /// ## Example + /// + /// ```rust + /// use multiboot2::{BootInformation, BootInformationHeader}; + /// + /// fn kernel_entry(mb_magic: u32, mbi_ptr: u32) { + /// if mb_magic == multiboot2::MAGIC { + /// let boot_info = unsafe { BootInformation::load(mbi_ptr as *const BootInformationHeader).unwrap() }; + /// let _cmd = boot_info.command_line_tag(); + /// } else { /* Panic or use multiboot1 flow. */ } + /// } + /// ``` + /// + /// ## Safety + /// * `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; + + // 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 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); + } + + Ok(Self(mbi)) + } + + /// Get the start address of the boot info. + #[must_use] + pub fn start_address(&self) -> usize { + self.as_ptr() as usize + } + + /// Get the start address of the boot info as pointer. + #[must_use] + pub const fn as_ptr(&self) -> *const () { + core::ptr::addr_of!(*self.0).cast() + } + + /// Get the end address of the boot info. + /// + /// This is the same as doing: + /// + /// ```rust,no_run + /// # use multiboot2::{BootInformation, BootInformationHeader}; + /// # let ptr = 0xdeadbeef as *const BootInformationHeader; + /// # let boot_info = unsafe { BootInformation::load(ptr).unwrap() }; + /// let end_addr = boot_info.start_address() + boot_info.total_size(); + /// ``` + #[must_use] + pub fn end_address(&self) -> usize { + self.start_address() + self.total_size() + } + + /// 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 + } + + // ###################################################### + // ### BEGIN OF TAG GETTERS (in alphabetical order) + + /*fn apm(&self) { + // also add to debug output + todo!() + }*/ + + /// Search for the basic memory info tag. + #[must_use] + pub fn basic_memory_info_tag(&self) -> Option<&BasicMemoryInfoTag> { + self.get_tag::() + } + + /// Search for the BootLoader name tag. + #[must_use] + pub fn boot_loader_name_tag(&self) -> Option<&BootLoaderNameTag> { + self.get_tag::() + } + + /*fn bootdev(&self) { + // also add to debug output + todo!() + }*/ + + /// Search for the Command line tag. + #[must_use] + pub fn command_line_tag(&self) -> Option<&CommandLineTag> { + self.get_tag::() + } + + /// Search for the EFI boot services not exited tag. + #[must_use] + pub fn efi_bs_not_exited_tag(&self) -> Option<&EFIBootServicesNotExitedTag> { + self.get_tag::() + } + + /// Search for the EFI Memory map tag, if the boot services were exited. + /// Otherwise, if the [`TagType::EfiBs`] tag is present, this returns `None` + /// as it is strictly recommended to get the memory map from the `uefi` + /// services. + /// + /// [`TagType::EfiBs`]: crate::TagType::EfiBs + #[must_use] + pub fn efi_memory_map_tag(&self) -> Option<&EFIMemoryMapTag> { + // If the EFIBootServicesNotExited is present, then we should not use + // the memory map, as it could still be in use. + self.get_tag::().map_or_else( + || self.get_tag::(), |_tag| { + log::debug!("The EFI memory map is present but the UEFI Boot Services Not Existed Tag is present. Returning None."); + None + }) + } + + /// Search for the EFI 32-bit SDT tag. + #[must_use] + pub fn efi_sdt32_tag(&self) -> Option<&EFISdt32Tag> { + self.get_tag::() + } + + /// Search for the EFI 64-bit SDT tag. + #[must_use] + pub fn efi_sdt64_tag(&self) -> Option<&EFISdt64Tag> { + self.get_tag::() + } + + /// Search for the EFI 32-bit image handle pointer tag. + #[must_use] + pub fn efi_ih32_tag(&self) -> Option<&EFIImageHandle32Tag> { + self.get_tag::() + } + + /// Search for the EFI 64-bit image handle pointer tag. + #[must_use] + pub fn efi_ih64_tag(&self) -> Option<&EFIImageHandle64Tag> { + self.get_tag::() + } + + /// Returns an [`ElfSectionIter`] iterator over the ELF Sections, if the + /// [`ElfSectionsTag`] is present. + /// + /// # Examples + /// + /// ```rust,no_run + /// # use multiboot2::{BootInformation, BootInformationHeader}; + /// # let ptr = 0xdeadbeef as *const BootInformationHeader; + /// # let boot_info = unsafe { BootInformation::load(ptr).unwrap() }; + /// if let Some(sections) = boot_info.elf_sections() { + /// let mut total = 0; + /// for section in sections { + /// println!("Section: {:?}", section); + /// total += 1; + /// } + /// } + /// ``` + #[must_use] + pub fn elf_sections(&self) -> Option { + let tag = self.get_tag::(); + tag.map(|t| { + assert!((t.entry_size() * t.shndx()) <= t.size() as u32); + t.sections_iter() + }) + } + + /// Search for the VBE framebuffer tag. The result is `Some(Err(e))`, if the + /// framebuffer type is unknown, while the framebuffer tag is present. + #[must_use] + pub fn framebuffer_tag(&self) -> Option> { + self.get_tag::() + .map(|tag| match tag.buffer_type() { + Ok(_) => Ok(tag), + Err(e) => Err(e), + }) + } + + /// Search for the Image Load Base Physical Address tag. + #[must_use] + pub fn load_base_addr_tag(&self) -> Option<&ImageLoadPhysAddrTag> { + self.get_tag::() + } + + /// Search for the Memory map tag. + #[must_use] + pub fn memory_map_tag(&self) -> Option<&MemoryMapTag> { + self.get_tag::() + } + + /// Get an iterator of all module tags. + #[must_use] + pub fn module_tags(&self) -> ModuleIter { + module::module_iter(self.tags()) + } + + /*fn network_tag(&self) { + // also add to debug output + todo!() + }*/ + + /// Search for the (ACPI 1.0) RSDP tag. + #[must_use] + pub fn rsdp_v1_tag(&self) -> Option<&RsdpV1Tag> { + self.get_tag::() + } + + /// Search for the (ACPI 2.0 or later) RSDP tag. + #[must_use] + pub fn rsdp_v2_tag(&self) -> Option<&RsdpV2Tag> { + self.get_tag::() + } + + /// Search for the SMBIOS tag. + #[must_use] + pub fn smbios_tag(&self) -> Option<&SmbiosTag> { + self.get_tag::() + } + + /// Search for the VBE information tag. + #[must_use] + pub fn vbe_info_tag(&self) -> Option<&VBEInfoTag> { + self.get_tag::() + } + + // ### END OF TAG GETTERS + // ###################################################### + + /// Public getter to find any Multiboot tag by its type, including + /// specified and custom ones. + /// + /// # Specified or Custom Tags + /// The Multiboot2 specification specifies a list of tags, see [`TagType`]. + /// However, it doesn't forbid to use custom tags. Because of this, there + /// exists the [`TagType`] abstraction. It is recommended to use this + /// getter only for custom tags. For specified tags, use getters, such as + /// [`Self::efi_ih64_tag`]. + /// + /// ## Use Custom Tags + /// The following example shows how you may use this interface to parse + /// custom tags from the MBI. If they are dynamically sized (DST), a few more + /// 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}; + /// + /// #[repr(C)] + /// #[derive(multiboot2::Pointee)] // Only needed for DSTs. + /// struct CustomTag { + /// tag: TagTypeId, + /// size: u32, + /// // begin of inline string + /// name: [u8], + /// } + /// + /// // This implementation is only necessary for tags that are DSTs. + /// impl TagTrait for CustomTag { + /// const ID: TagType = TagType::Custom(0x1337); + /// + /// 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 + /// } + /// } + /// + /// impl CustomTag { + /// fn name(&self) -> Result<&str, StringError> { + /// parse_slice_as_string(&self.name) + /// } + /// } + /// let mbi_ptr = 0xdeadbeef as *const BootInformationHeader; + /// let mbi = unsafe { BootInformation::load(mbi_ptr).unwrap() }; + /// + /// let tag = mbi + /// .get_tag::() + /// .unwrap(); + /// assert_eq!(tag.name(), Ok("name")); + /// ``` + /// + /// [`TagType`]: crate::TagType + #[must_use] + pub fn get_tag(&'a self) -> Option<&'a TagT> { + self.tags() + .find(|tag| tag.header().typ == TagT::ID) + .map(|tag| tag.cast::()) + } + + /// Returns an iterator over all tags. + fn tags(&self) -> TagIter { + TagIter::new(&self.0.tags) + } +} + +impl fmt::Debug for BootInformation<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// Limit how many Elf-Sections should be debug-formatted. + /// Can be thousands of sections for a Rust binary => this is useless output. + /// If the user really wants this, they should debug-format the field directly. + const ELF_SECTIONS_LIMIT: usize = 7; + + let mut debug = f.debug_struct("Multiboot2BootInformation"); + debug + .field("start_address", &self.start_address()) + .field("end_address", &self.end_address()) + .field("total_size", &self.total_size()) + // now tags in alphabetical order + .field("basic_memory_info", &(self.basic_memory_info_tag())) + .field("boot_loader_name", &self.boot_loader_name_tag()) + // .field("bootdev", &self.bootdev_tag()) + .field("command_line", &self.command_line_tag()) + .field("efi_bs_not_exited", &self.efi_bs_not_exited_tag()) + .field("efi_memory_map", &self.efi_memory_map_tag()) + .field("efi_sdt32", &self.efi_sdt32_tag()) + .field("efi_sdt64", &self.efi_sdt64_tag()) + .field("efi_ih32", &self.efi_ih32_tag()) + .field("efi_ih64", &self.efi_ih64_tag()); + + // usually this is REALLY big (thousands of tags) => skip it here + { + let elf_sections_tag_entries_count = + self.elf_sections().map(|x| x.count()).unwrap_or(0); + + if elf_sections_tag_entries_count > ELF_SECTIONS_LIMIT { + debug.field("elf_sections (count)", &elf_sections_tag_entries_count); + } else { + debug.field("elf_sections", &self.elf_sections()); + } + } + + debug + .field("framebuffer", &self.framebuffer_tag()) + .field("load_base_addr", &self.load_base_addr_tag()) + .field("memory_map", &self.memory_map_tag()) + .field("modules", &self.module_tags()) + // .field("network", &self.network_tag()) + .field("rsdp_v1", &self.rsdp_v1_tag()) + .field("rsdp_v2", &self.rsdp_v2_tag()) + .field("smbios_tag", &self.smbios_tag()) + .field("vbe_info_tag", &self.vbe_info_tag()) + .finish() + } +} diff --git a/multiboot2/src/boot_loader_name.rs b/multiboot2/src/boot_loader_name.rs index 93b12e0d..60a2cc74 100644 --- a/multiboot2/src/boot_loader_name.rs +++ b/multiboot2/src/boot_loader_name.rs @@ -1,33 +1,46 @@ //! Module for [`BootLoaderNameTag`]. -use crate::tag::StringError; -use crate::{Tag, TagTrait, TagType, TagTypeId}; +use crate::tag::TagHeader; +use crate::{parse_slice_as_string, StringError, TagTrait, TagType}; use core::fmt::{Debug, Formatter}; -use core::mem::size_of; +use core::mem; #[cfg(feature = "builder")] -use {crate::builder::BoxedDst, alloc::vec::Vec}; +use {crate::new_boxed, alloc::boxed::Box}; -const METADATA_SIZE: usize = size_of::() + size_of::(); +const METADATA_SIZE: usize = mem::size_of::(); /// The bootloader name tag. #[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct BootLoaderNameTag { - typ: TagTypeId, - size: u32, + header: TagHeader, /// Null-terminated UTF-8 string name: [u8], } impl BootLoaderNameTag { + /// Constructs a new tag. #[cfg(feature = "builder")] - pub fn new(name: &str) -> BoxedDst { - let mut bytes: Vec<_> = name.bytes().collect(); - if !bytes.ends_with(&[0]) { - // terminating null-byte - bytes.push(0); + #[must_use] + pub fn new(name: &str) -> Box { + let bytes = name.as_bytes(); + if bytes.ends_with(&[0]) { + new_boxed(&[bytes]) + } else { + new_boxed(&[bytes, &[0]]) } - BoxedDst::new(&bytes) + } + + /// Returns the underlying [`TagType`]. + #[must_use] + pub fn typ(&self) -> TagType { + self.header.typ.into() + } + + /// Returns the underlying tag size. + #[must_use] + pub const fn size(&self) -> usize { + self.header.size as usize } /// Reads the name of the bootloader that is booting the kernel as Rust @@ -48,15 +61,15 @@ impl BootLoaderNameTag { /// } /// ``` pub fn name(&self) -> Result<&str, StringError> { - Tag::parse_slice_as_string(&self.name) + parse_slice_as_string(&self.name) } } impl Debug for BootLoaderNameTag { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("BootLoaderNameTag") - .field("typ", &{ self.typ }) - .field("size", &{ self.size }) + .field("typ", &self.header.typ) + .field("size", &self.header.size) .field("name", &self.name()) .finish() } @@ -65,54 +78,55 @@ impl Debug for BootLoaderNameTag { impl TagTrait for BootLoaderNameTag { const ID: TagType = TagType::BootLoaderName; - fn dst_size(base_tag: &Tag) -> usize { - assert!(base_tag.size as usize >= METADATA_SIZE); - base_tag.size as usize - METADATA_SIZE + fn dst_len(header: &TagHeader) -> usize { + assert!(header.size as usize >= METADATA_SIZE); + header.size as usize - METADATA_SIZE } } #[cfg(test)] mod tests { - use crate::{BootLoaderNameTag, Tag, TagTrait, TagType}; - - const MSG: &str = "hello"; - - /// Returns the tag structure in bytes in little endian format. - fn get_bytes() -> std::vec::Vec { - // size is: 4 bytes for tag + 4 bytes for size + length of null-terminated string - let size = (4 + 4 + MSG.as_bytes().len() + 1) as u32; - [ - &((TagType::BootLoaderName.val()).to_le_bytes()), - &size.to_le_bytes(), - MSG.as_bytes(), - // Null Byte - &[0], - ] - .iter() - .flat_map(|bytes| bytes.iter()) - .copied() - .collect() + use super::*; + use crate::tag::{GenericTag, TagBytesRef}; + use crate::test_util::AlignedBytes; + use core::borrow::Borrow; + + #[rustfmt::skip] + fn get_bytes() -> AlignedBytes<16> { + AlignedBytes::new([ + TagType::BootLoaderName.val() as u8, 0, 0, 0, + 14, 0, 0, 0, + b'h', b'e', b'l', b'l', b'o', b'\0', + /* padding */ + 0, 0 + ]) } /// Tests to parse a string with a terminating null byte from the tag (as the spec defines). #[test] - #[cfg_attr(miri, ignore)] fn test_parse_str() { - let tag = get_bytes(); - let tag = unsafe { &*tag.as_ptr().cast::() }; - let tag = tag.cast_tag::(); - assert_eq!({ tag.typ }, TagType::BootLoaderName); - assert_eq!(tag.name().expect("must be valid UTF-8"), MSG); + let bytes = get_bytes(); + let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); + let tag = GenericTag::ref_from(bytes); + let tag = tag.cast::(); + assert_eq!(tag.header.typ, TagType::BootLoaderName); + assert_eq!(tag.name(), Ok("hello")); } /// Test to generate a tag from a given string. #[test] #[cfg(feature = "builder")] fn test_build_str() { - let tag = BootLoaderNameTag::new(MSG); + let tag = BootLoaderNameTag::new("hello"); + let bytes = tag.as_bytes(); + assert_eq!(bytes, &get_bytes()[..tag.size()]); + 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()); - assert_eq!(tag.name(), Ok(MSG)); + assert_eq!(bytes, &get_bytes()[..tag.size()]); + assert_eq!(tag.name(), Ok("hello")); // test also some bigger message let tag = BootLoaderNameTag::new("AbCdEfGhUjK YEAH"); diff --git a/multiboot2/src/builder/boxed_dst.rs b/multiboot2/src/builder/boxed_dst.rs deleted file mode 100644 index bafb8df3..00000000 --- a/multiboot2/src/builder/boxed_dst.rs +++ /dev/null @@ -1,156 +0,0 @@ -//! Module for [`BoxedDst`]. - -use crate::{Tag, TagTrait, TagTypeId}; -use alloc::alloc::alloc; -use core::alloc::Layout; -use core::marker::PhantomData; -use core::mem::size_of; -use core::ops::Deref; -use core::ptr::NonNull; - -/// A helper type to create boxed DST, i.e., tags with a dynamic size for the -/// builder. This is tricky in Rust. This type behaves similar to the regular -/// `Box` type except that it ensure the same layout is used for the (explicit) -/// allocation and the (implicit) deallocation of memory. Otherwise, I didn't -/// find any way to figure out the right layout for a DST. Miri always reported -/// issues that the deallocation used a wrong layout. -/// -/// Technically, I'm certain this code is memory safe. But with this type, I -/// also can convince miri that it is. -#[derive(Debug, Eq)] -pub struct BoxedDst { - ptr: core::ptr::NonNull, - layout: Layout, - // marker: I used this only as the regular Box impl also does it. - _marker: PhantomData, -} - -impl + ?Sized> BoxedDst { - /// Create a boxed tag with the given content. - /// - /// # Parameters - /// - `content` - All payload bytes of the DST tag without the tag type or - /// the size. The memory is only read and can be discarded - /// afterwards. - pub(crate) fn new(content: &[u8]) -> Self { - // Currently, I do not find a nice way of making this dynamic so that - // also miri is guaranteed to be happy. But it seems that 4 is fine - // here. I do have control over allocation and deallocation. - const ALIGN: usize = 4; - - let tag_size = size_of::() + size_of::() + content.len(); - - // By using miri, I could figure out that there often are problems where - // miri thinks an allocation is smaller then necessary. Most probably - // due to not packed structs. Using packed structs however - // (especially with DSTs), is a crazy ass pain and unusable :/ Therefore, - // the best solution I can think of is to allocate a few byte more than - // necessary. I think that during runtime, everything works fine and - // that no memory issues are present. - let alloc_size = (tag_size + 7) & !7; // align to next 8 byte boundary - let layout = Layout::from_size_align(alloc_size, ALIGN).unwrap(); - let ptr = unsafe { alloc(layout) }; - assert!(!ptr.is_null()); - - // write tag content to memory - unsafe { - // write tag type - let ptrx = ptr.cast::(); - ptrx.write(T::ID.into()); - - // write tag size - let ptrx = ptrx.add(1).cast::(); - ptrx.write(tag_size as u32); - - // write rest of content - let ptrx = ptrx.add(1).cast::(); - let tag_content_slice = core::slice::from_raw_parts_mut(ptrx, content.len()); - for (i, &byte) in content.iter().enumerate() { - tag_content_slice[i] = byte; - } - } - - let base_tag = unsafe { &*ptr.cast::() }; - let raw: *mut T = ptr_meta::from_raw_parts_mut(ptr.cast(), T::dst_size(base_tag)); - - Self { - ptr: NonNull::new(raw).unwrap(), - layout, - _marker: PhantomData, - } - } -} - -impl Drop for BoxedDst { - fn drop(&mut self) { - unsafe { alloc::alloc::dealloc(self.ptr.as_ptr().cast(), self.layout) } - } -} - -impl Deref for BoxedDst { - type Target = T; - fn deref(&self) -> &Self::Target { - unsafe { self.ptr.as_ref() } - } -} - -impl PartialEq for BoxedDst { - fn eq(&self, other: &Self) -> bool { - self.deref().eq(other.deref()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tag::StringError; - use crate::TagType; - - const METADATA_SIZE: usize = 8; - - #[derive(ptr_meta::Pointee)] - #[repr(C)] - struct CustomTag { - typ: TagTypeId, - size: u32, - string: [u8], - } - - impl CustomTag { - fn string(&self) -> Result<&str, StringError> { - Tag::parse_slice_as_string(&self.string) - } - } - - impl TagTrait for CustomTag { - const ID: TagType = TagType::Custom(0x1337); - - fn dst_size(base_tag: &Tag) -> usize { - assert!(base_tag.size as usize >= METADATA_SIZE); - base_tag.size as usize - METADATA_SIZE - } - } - - #[test] - fn test_boxed_dst_tag() { - let content = b"hallo\0"; - let content_rust_str = "hallo"; - - let tag = BoxedDst::::new(content); - assert_eq!(tag.typ, CustomTag::ID); - assert_eq!(tag.size as usize, METADATA_SIZE + content.len()); - assert_eq!(tag.string(), Ok(content_rust_str)); - } - - #[test] - fn can_hold_tag_trait() { - fn consume(_: &T) {} - let content = b"hallo\0"; - - let tag = BoxedDst::::new(content); - consume(tag.deref()); - consume(&*tag); - // Compiler not smart enough? - // consume(&tag); - } -} diff --git a/multiboot2/src/builder/information.rs b/multiboot2/src/builder/information.rs index 8d16a903..a52a48da 100644 --- a/multiboot2/src/builder/information.rs +++ b/multiboot2/src/builder/information.rs @@ -1,10 +1,12 @@ //! Exports item [`InformationBuilder`]. -use crate::builder::{AsBytes, BoxedDst}; +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, + MemoryMapTag, ModuleTag, RsdpV1Tag, RsdpV2Tag, SmbiosTag, TagTrait, TagType, VBEInfoTag, + ALIGNMENT, }; use alloc::vec::Vec; use core::fmt::{Display, Formatter}; @@ -73,27 +75,21 @@ impl Default for InformationBuilder { impl InformationBuilder { /// Creates a new builder. + #[must_use] pub const fn new() -> Self { Self(Vec::new()) } - /// Returns the provided number or the next multiple of 8. This is helpful - /// to ensure that the following tag starts at a 8-byte aligned boundary. - const fn size_or_up_aligned(size: usize) -> usize { - (size + 7) & !7 - } - /// 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| { - // size_or_up_aligned: make sure next tag is 8-byte aligned - acc + Self::size_or_up_aligned(tag_size) - }); + 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::() } @@ -110,7 +106,7 @@ impl InformationBuilder { if tag_type != TagType::End { let size = tag_serialized.len(); - let size_to_8_align = Self::size_or_up_aligned(size); + 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)); @@ -118,9 +114,8 @@ impl InformationBuilder { } /// Constructs the bytes for a valid Multiboot2 information with the given properties. + #[must_use] pub fn build(self) -> BootInformationBytes { - const ALIGN: usize = 8; - // PHASE 1/2: Prepare Vector // We allocate more than necessary so that we can ensure an correct @@ -138,7 +133,7 @@ impl InformationBuilder { // 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); + let offset = bytes.as_ptr().align_offset(ALIGNMENT); bytes.extend([0].repeat(offset)); // ----------------------------------------------- @@ -186,6 +181,8 @@ impl InformationBuilder { .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 { @@ -202,92 +199,116 @@ impl InformationBuilder { } /// Adds a 'basic memory information' tag (represented by [`BasicMemoryInfoTag`]) to the builder. - pub fn basic_memory_info_tag(self, tag: BasicMemoryInfoTag) -> Self { - self.add_tag(&tag).unwrap() + #[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. - pub fn bootloader_name_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + #[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. - pub fn command_line_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + #[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. - pub fn efisdt32_tag(self, tag: EFISdt32Tag) -> Self { - self.add_tag(&tag).unwrap() + #[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. - pub fn efisdt64_tag(self, tag: EFISdt64Tag) -> Self { - self.add_tag(&tag).unwrap() + #[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. - pub fn efi_image_handle32(self, tag: EFIImageHandle32Tag) -> Self { - self.add_tag(&tag).unwrap() + #[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. - pub fn efi_image_handle64(self, tag: EFIImageHandle64Tag) -> Self { - self.add_tag(&tag).unwrap() + #[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. - pub fn efi_memory_map_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + #[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. - pub fn elf_sections_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + #[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. - pub fn framebuffer_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + #[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. - pub fn image_load_addr(self, tag: ImageLoadPhysAddrTag) -> Self { - self.add_tag(&tag).unwrap() + #[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. - pub fn memory_map_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + #[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. - pub fn add_module_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + #[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. - pub fn rsdp_v1_tag(self, tag: RsdpV1Tag) -> Self { - self.add_tag(&tag).unwrap() + #[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. - pub fn rsdp_v2_tag(self, tag: RsdpV2Tag) -> Self { - self.add_tag(&tag).unwrap() + #[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. - pub fn smbios_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + #[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() } - fn tag_is_allowed_multiple_times(tag_type: TagType) -> bool { + #[must_use] + const fn tag_is_allowed_multiple_times(tag_type: TagType) -> bool { matches!( tag_type, TagType::Module | TagType::Smbios | TagType::Custom(_) @@ -308,18 +329,18 @@ mod tests { assert_eq!(builder.expected_len(), expected_len); // the most simple tag - builder = builder.basic_memory_info_tag(BasicMemoryInfoTag::new(640, 7 * 1024)); + 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")); + 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")); + 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")); + builder = builder.add_module_tag(&ModuleTag::new(5678, 6789, "module2")); expected_len += 16 + 8; assert_eq!(builder.expected_len(), expected_len); @@ -329,14 +350,6 @@ mod tests { builder } - #[test] - fn test_size_or_up_aligned() { - assert_eq!(0, InformationBuilder::size_or_up_aligned(0)); - assert_eq!(8, InformationBuilder::size_or_up_aligned(1)); - assert_eq!(8, InformationBuilder::size_or_up_aligned(8)); - assert_eq!(16, InformationBuilder::size_or_up_aligned(9)); - } - /// Test of the `build` method in isolation specifically for miri to check /// for memory issues. #[test] @@ -347,7 +360,6 @@ mod tests { } #[test] - #[cfg_attr(miri, ignore)] fn test_builder() { // Step 1/2: Build MBI let mb2i_data = create_builder().build(); diff --git a/multiboot2/src/builder/mod.rs b/multiboot2/src/builder/mod.rs index 93b68528..4fd00932 100644 --- a/multiboot2/src/builder/mod.rs +++ b/multiboot2/src/builder/mod.rs @@ -1,15 +1,15 @@ //! Module for the builder-feature. -mod boxed_dst; mod information; -// This must be public to support external people to create boxed DSTs. -pub use boxed_dst::BoxedDst; pub use information::InformationBuilder; /// Helper trait for all structs that need to be serialized that do not -/// implement `TagTrait`. +/// 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::(); diff --git a/multiboot2/src/command_line.rs b/multiboot2/src/command_line.rs index e2b168f8..cd387f25 100644 --- a/multiboot2/src/command_line.rs +++ b/multiboot2/src/command_line.rs @@ -1,25 +1,23 @@ //! Module for [`CommandLineTag`]. -use crate::{Tag, TagTrait, TagType, TagTypeId}; - -use crate::tag::StringError; +use crate::tag::TagHeader; +use crate::{parse_slice_as_string, StringError, TagTrait, TagType}; use core::fmt::{Debug, Formatter}; use core::mem; use core::str; #[cfg(feature = "builder")] -use {crate::builder::BoxedDst, alloc::vec::Vec}; +use {crate::new_boxed, alloc::boxed::Box}; -pub(crate) const METADATA_SIZE: usize = mem::size_of::() + mem::size_of::(); +const METADATA_SIZE: usize = mem::size_of::(); /// This tag contains the command line string. /// /// The string is a normal C-style UTF-8 zero-terminated string that can be /// obtained via the `command_line` method. #[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct CommandLineTag { - typ: TagTypeId, - size: u32, + header: TagHeader, /// Null-terminated UTF-8 string cmdline: [u8], } @@ -27,13 +25,14 @@ pub struct CommandLineTag { impl CommandLineTag { /// Create a new command line tag from the given string. #[cfg(feature = "builder")] - pub fn new(command_line: &str) -> BoxedDst { - let mut bytes: Vec<_> = command_line.bytes().collect(); - if !bytes.ends_with(&[0]) { - // terminating null-byte - bytes.push(0); + #[must_use] + pub fn new(command_line: &str) -> Box { + let bytes = command_line.as_bytes(); + if bytes.ends_with(&[0]) { + new_boxed(&[bytes]) + } else { + new_boxed(&[bytes, &[0]]) } - BoxedDst::new(&bytes) } /// Reads the command line of the kernel as Rust string slice without @@ -56,15 +55,15 @@ impl CommandLineTag { /// } /// ``` pub fn cmdline(&self) -> Result<&str, StringError> { - Tag::parse_slice_as_string(&self.cmdline) + parse_slice_as_string(&self.cmdline) } } impl Debug for CommandLineTag { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("CommandLineTag") - .field("typ", &{ self.typ }) - .field("size", &{ self.size }) + .field("typ", &self.header.typ) + .field("size", &self.header.size) .field("cmdline", &self.cmdline()) .finish() } @@ -73,54 +72,55 @@ impl Debug for CommandLineTag { impl TagTrait for CommandLineTag { const ID: TagType = TagType::Cmdline; - fn dst_size(base_tag: &Tag) -> usize { - assert!(base_tag.size as usize >= METADATA_SIZE); - base_tag.size as usize - METADATA_SIZE + fn dst_len(header: &TagHeader) -> usize { + assert!(header.size as usize >= METADATA_SIZE); + header.size as usize - METADATA_SIZE } } #[cfg(test)] mod tests { use super::*; + use crate::tag::{GenericTag, TagBytesRef}; + use crate::test_util::AlignedBytes; + use core::borrow::Borrow; - const MSG: &str = "hello"; - - /// Returns the tag structure in bytes in little endian format. - fn get_bytes() -> std::vec::Vec { - // size is: 4 bytes for tag + 4 bytes for size + length of null-terminated string - let size = (4 + 4 + MSG.as_bytes().len() + 1) as u32; - [ - &((TagType::Cmdline.val()).to_le_bytes()), - &size.to_le_bytes(), - MSG.as_bytes(), - // Null Byte - &[0], - ] - .iter() - .flat_map(|bytes| bytes.iter()) - .copied() - .collect() + #[rustfmt::skip] + fn get_bytes() -> AlignedBytes<16> { + AlignedBytes::new([ + TagType::Cmdline.val() as u8, 0, 0, 0, + 14, 0, 0, 0, + b'h', b'e', b'l', b'l', b'o', b'\0', + /* padding */ + 0, 0 + ]) } /// Tests to parse a string with a terminating null byte from the tag (as the spec defines). #[test] - #[cfg_attr(miri, ignore)] fn test_parse_str() { - let tag = get_bytes(); - let tag = unsafe { &*tag.as_ptr().cast::() }; - let tag = tag.cast_tag::(); - assert_eq!({ tag.typ }, TagType::Cmdline); - assert_eq!(tag.cmdline().expect("must be valid UTF-8"), MSG); + let bytes = get_bytes(); + let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); + let tag = GenericTag::ref_from(bytes); + let tag = tag.cast::(); + assert_eq!(tag.header.typ, TagType::Cmdline); + assert_eq!(tag.cmdline(), Ok("hello")); } /// Test to generate a tag from a given string. #[test] #[cfg(feature = "builder")] fn test_build_str() { - let tag = CommandLineTag::new(MSG); + let tag = CommandLineTag::new("hello"); + let bytes = tag.as_bytes(); + assert_eq!(bytes, &get_bytes()[..tag.size()]); + 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()); - assert_eq!(tag.cmdline(), Ok(MSG)); + assert_eq!(bytes, &get_bytes()[..tag.size()]); + assert_eq!(tag.cmdline(), Ok("hello")); // test also some bigger message let tag = CommandLineTag::new("AbCdEfGhUjK YEAH"); diff --git a/multiboot2/src/efi.rs b/multiboot2/src/efi.rs index 230b1bd8..4c8d727a 100644 --- a/multiboot2/src/efi.rs +++ b/multiboot2/src/efi.rs @@ -6,31 +6,31 @@ //! - [`EFIImageHandle64Tag`] //! - [`EFIBootServicesNotExitedTag`] -use crate::TagTypeId; -use crate::{Tag, TagTrait, TagType}; +use crate::tag::TagHeader; +use crate::{TagTrait, TagType}; use core::mem::size_of; /// EFI system table in 32 bit mode tag. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct EFISdt32Tag { - typ: TagTypeId, - size: u32, + header: TagHeader, pointer: u32, } impl EFISdt32Tag { /// Create a new tag to pass the EFI32 System Table pointer. + #[must_use] pub fn new(pointer: u32) -> Self { Self { - typ: Self::ID.into(), - size: size_of::().try_into().unwrap(), + header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), pointer, } } /// The physical address of a i386 EFI system table. - pub fn sdt_address(&self) -> usize { + #[must_use] + pub const fn sdt_address(&self) -> usize { self.pointer as usize } } @@ -38,30 +38,30 @@ impl EFISdt32Tag { impl TagTrait for EFISdt32Tag { const ID: TagType = TagType::Efi32; - fn dst_size(_base_tag: &Tag) {} + fn dst_len(_: &TagHeader) {} } /// EFI system table in 64 bit mode tag. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct EFISdt64Tag { - typ: TagTypeId, - size: u32, + header: TagHeader, pointer: u64, } impl EFISdt64Tag { /// Create a new tag to pass the EFI64 System Table pointer. + #[must_use] pub fn new(pointer: u64) -> Self { Self { - typ: Self::ID.into(), - size: size_of::().try_into().unwrap(), + header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), pointer, } } /// The physical address of a x86_64 EFI system table. - pub fn sdt_address(&self) -> usize { + #[must_use] + pub const fn sdt_address(&self) -> usize { self.pointer as usize } } @@ -69,31 +69,32 @@ impl EFISdt64Tag { impl TagTrait for EFISdt64Tag { const ID: TagType = TagType::Efi64; - fn dst_size(_base_tag: &Tag) {} + fn dst_len(_: &TagHeader) {} } /// Tag that contains the pointer to the boot loader's UEFI image handle /// (32-bit). #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct EFIImageHandle32Tag { - typ: TagTypeId, - size: u32, + header: TagHeader, pointer: u32, } impl EFIImageHandle32Tag { + /// Constructs a new tag. #[cfg(feature = "builder")] + #[must_use] pub fn new(pointer: u32) -> Self { Self { - typ: Self::ID.into(), - size: size_of::().try_into().unwrap(), + header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), pointer, } } /// Returns the physical address of the EFI image handle. - pub fn image_handle(&self) -> usize { + #[must_use] + pub const fn image_handle(&self) -> usize { self.pointer as usize } } @@ -101,31 +102,32 @@ impl EFIImageHandle32Tag { impl TagTrait for EFIImageHandle32Tag { const ID: TagType = TagType::Efi32Ih; - fn dst_size(_base_tag: &Tag) {} + fn dst_len(_: &TagHeader) {} } /// Tag that contains the pointer to the boot loader's UEFI image handle /// (64-bit). #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct EFIImageHandle64Tag { - typ: TagTypeId, - size: u32, + header: TagHeader, pointer: u64, } impl EFIImageHandle64Tag { + /// Constructs a new tag. #[cfg(feature = "builder")] + #[must_use] pub fn new(pointer: u64) -> Self { Self { - typ: Self::ID.into(), - size: size_of::().try_into().unwrap(), + header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), pointer, } } /// Returns the physical address of the EFI image handle. - pub fn image_handle(&self) -> usize { + #[must_use] + pub const fn image_handle(&self) -> usize { self.pointer as usize } } @@ -133,19 +135,21 @@ impl EFIImageHandle64Tag { impl TagTrait for EFIImageHandle64Tag { const ID: TagType = TagType::Efi64Ih; - fn dst_size(_base_tag: &Tag) {} + fn dst_len(_: &TagHeader) {} } -/// EFI ExitBootServices was not called tag. +/// 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)] -#[repr(C)] +#[repr(C, align(8))] pub struct EFIBootServicesNotExitedTag { - typ: TagTypeId, - size: u32, + header: TagHeader, } impl EFIBootServicesNotExitedTag { + /// Constructs a new tag. #[cfg(feature = "builder")] + #[must_use] pub fn new() -> Self { Self::default() } @@ -155,8 +159,7 @@ impl EFIBootServicesNotExitedTag { impl Default for EFIBootServicesNotExitedTag { fn default() -> Self { Self { - typ: TagType::EfiBs.into(), - size: core::mem::size_of::().try_into().unwrap(), + header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), } } } @@ -164,7 +167,7 @@ impl Default for EFIBootServicesNotExitedTag { impl TagTrait for EFIBootServicesNotExitedTag { const ID: TagType = TagType::EfiBs; - fn dst_size(_base_tag: &Tag) {} + fn dst_len(_: &TagHeader) {} } #[cfg(all(test, feature = "builder"))] diff --git a/multiboot2/src/elf_sections.rs b/multiboot2/src/elf_sections.rs index 00579359..decbe734 100644 --- a/multiboot2/src/elf_sections.rs +++ b/multiboot2/src/elf_sections.rs @@ -1,105 +1,115 @@ //! Module for [`ElfSectionsTag`]. -#[cfg(feature = "builder")] -use crate::builder::BoxedDst; -use crate::{Tag, TagTrait, TagType, TagTypeId}; +use crate::{TagHeader, TagTrait, TagType}; use core::fmt::{Debug, Formatter}; -use core::mem::size_of; +use core::marker::PhantomData; +use core::mem; use core::str::Utf8Error; +#[cfg(feature = "builder")] +use {crate::new_boxed, alloc::boxed::Box}; -const METADATA_SIZE: usize = size_of::() + 4 * size_of::(); +const METADATA_SIZE: usize = mem::size_of::() + 3 * mem::size_of::(); /// This tag contains the section header table from an ELF binary. // The sections iterator is provided via the [`ElfSectionsTag::sections`] // method. #[derive(ptr_meta::Pointee, PartialEq, Eq)] -#[repr(C)] +#[repr(C, align(8))] pub struct ElfSectionsTag { - typ: TagTypeId, - pub(crate) size: u32, + header: TagHeader, number_of_sections: u32, - pub(crate) entry_size: u32, - pub(crate) shndx: u32, // string table + entry_size: u32, + shndx: u32, sections: [u8], } impl ElfSectionsTag { /// Create a new ElfSectionsTag with the given data. #[cfg(feature = "builder")] - pub fn new( - number_of_sections: u32, - entry_size: u32, - shndx: u32, - sections: &[u8], - ) -> BoxedDst { - let mut bytes = [ - number_of_sections.to_le_bytes(), - entry_size.to_le_bytes(), - shndx.to_le_bytes(), - ] - .concat(); - bytes.extend_from_slice(sections); - BoxedDst::new(&bytes) + #[must_use] + pub fn new(number_of_sections: u32, entry_size: u32, shndx: u32, sections: &[u8]) -> Box { + 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]) } /// Get an iterator of loaded ELF sections. - pub(crate) fn sections(&self) -> ElfSectionIter { + #[must_use] + pub(crate) const fn sections_iter(&self) -> ElfSectionIter { let string_section_offset = (self.shndx * self.entry_size) as isize; let string_section_ptr = - unsafe { self.first_section().offset(string_section_offset) as *const _ }; + unsafe { self.sections.as_ptr().offset(string_section_offset) as *const _ }; ElfSectionIter { - current_section: self.first_section(), + current_section: self.sections.as_ptr(), remaining_sections: self.number_of_sections, entry_size: self.entry_size, string_section: string_section_ptr, + _phantom_data: PhantomData, } } - fn first_section(&self) -> *const u8 { - &(self.sections[0]) as *const _ + /// Returns the amount of sections. + #[must_use] + pub const fn number_of_sections(&self) -> u32 { + self.number_of_sections + } + + /// Returns the size of each entry. + #[must_use] + pub const fn entry_size(&self) -> u32 { + self.entry_size + } + + /// Returns the index of the section header string table. + #[must_use] + pub const fn shndx(&self) -> u32 { + self.shndx } } impl TagTrait for ElfSectionsTag { const ID: TagType = TagType::ElfSections; - fn dst_size(base_tag: &Tag) -> usize { - assert!(base_tag.size as usize >= METADATA_SIZE); - base_tag.size as usize - METADATA_SIZE + fn dst_len(header: &TagHeader) -> usize { + assert!(header.size as usize >= METADATA_SIZE); + header.size as usize - METADATA_SIZE } } impl Debug for ElfSectionsTag { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("ElfSectionsTag") - .field("typ", &{ self.typ }) - .field("size", &{ self.size }) - .field("number_of_sections", &{ self.number_of_sections }) - .field("entry_size", &{ self.entry_size }) - .field("shndx", &{ self.shndx }) - .field("sections", &self.sections()) + .field("typ", &self.header.typ) + .field("size", &self.header.size) + .field("number_of_sections", &self.number_of_sections) + .field("entry_size", &self.entry_size) + .field("shndx", &self.shndx) + .field("sections", &self.sections_iter()) .finish() } } /// An iterator over some ELF sections. #[derive(Clone)] -pub struct ElfSectionIter { +pub struct ElfSectionIter<'a> { current_section: *const u8, remaining_sections: u32, entry_size: u32, string_section: *const u8, + _phantom_data: PhantomData<&'a ()>, } -impl Iterator for ElfSectionIter { - type Item = ElfSection; +impl<'a> Iterator for ElfSectionIter<'a> { + type Item = ElfSection<'a>; - fn next(&mut self) -> Option { + fn next(&mut self) -> Option> { while self.remaining_sections != 0 { let section = ElfSection { inner: self.current_section, string_section: self.string_section, entry_size: self.entry_size, + _phantom: PhantomData, }; self.current_section = unsafe { self.current_section.offset(self.entry_size as isize) }; @@ -113,7 +123,7 @@ impl Iterator for ElfSectionIter { } } -impl Debug for ElfSectionIter { +impl<'a> Debug for ElfSectionIter<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { let mut debug = f.debug_list(); self.clone().for_each(|ref e| { @@ -123,23 +133,13 @@ impl Debug for ElfSectionIter { } } -impl Default for ElfSectionIter { - fn default() -> Self { - Self { - current_section: core::ptr::null(), - remaining_sections: 0, - entry_size: 0, - string_section: core::ptr::null(), - } - } -} - /// A single generic ELF Section. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ElfSection { +pub struct ElfSection<'a> { inner: *const u8, string_section: *const u8, entry_size: u32, + _phantom: PhantomData<&'a ()>, } #[derive(Clone, Copy, Debug)] @@ -172,8 +172,9 @@ struct ElfSectionInner64 { entry_size: u64, } -impl ElfSection { +impl<'a> ElfSection<'a> { /// Get the section type as a `ElfSectionType` enum variant. + #[must_use] pub fn section_type(&self) -> ElfSectionType { match self.get().typ() { 0 => ElfSectionType::Unused, @@ -201,6 +202,7 @@ impl ElfSection { } /// Get the "raw" section type as a `u32` + #[must_use] pub fn section_type_raw(&self) -> u32 { self.get().typ() } @@ -224,6 +226,7 @@ impl ElfSection { } /// Get the physical start address of the section. + #[must_use] pub fn start_address(&self) -> u64 { self.get().addr() } @@ -231,11 +234,13 @@ impl ElfSection { /// Get the physical end address of the section. /// /// This is the same as doing `section.start_address() + section.size()` + #[must_use] pub fn end_address(&self) -> u64 { self.get().addr() + self.get().size() } /// Get the section's size in bytes. + #[must_use] pub fn size(&self) -> u64 { self.get().size() } @@ -246,16 +251,19 @@ impl ElfSection { /// modulo the value of `addrlign`. Currently, only 0 and positive /// integral powers of two are allowed. Values 0 and 1 mean the section has no /// alignment constraints. + #[must_use] pub fn addralign(&self) -> u64 { self.get().addralign() } /// Get the section's flags. + #[must_use] pub fn flags(&self) -> ElfSectionFlags { ElfSectionFlags::from_bits_truncate(self.get().flags()) } /// Check if the `ALLOCATED` flag is set in the section flags. + #[must_use] pub fn is_allocated(&self) -> bool { self.flags().contains(ElfSectionFlags::ALLOCATED) } diff --git a/multiboot2/src/end.rs b/multiboot2/src/end.rs index f32351cf..746ba271 100644 --- a/multiboot2/src/end.rs +++ b/multiboot2/src/end.rs @@ -1,13 +1,13 @@ //! Module for [`EndTag`]. -use crate::{Tag, TagTrait, TagType, TagTypeId}; +use crate::{TagHeader, TagTrait, TagType, TagTypeId}; /// The end tag ends the information struct. -#[repr(C)] #[derive(Debug)] +#[repr(C, align(8))] pub struct EndTag { - pub typ: TagTypeId, - pub size: u32, + typ: TagTypeId, + size: u32, } impl Default for EndTag { @@ -22,7 +22,7 @@ impl Default for EndTag { impl TagTrait for EndTag { const ID: TagType = TagType::End; - fn dst_size(_base_tag: &Tag) {} + fn dst_len(_: &TagHeader) {} } #[cfg(test)] diff --git a/multiboot2/src/framebuffer.rs b/multiboot2/src/framebuffer.rs index bb80dc25..d1370ee8 100644 --- a/multiboot2/src/framebuffer.rs +++ b/multiboot2/src/framebuffer.rs @@ -1,12 +1,13 @@ //! Module for [`FramebufferTag`]. -use crate::{Tag, TagTrait, TagType, TagTypeId}; +use crate::tag::TagHeader; +use crate::{TagTrait, TagType, TagTypeId}; use core::fmt::Debug; -use core::mem::size_of; +use core::mem; use core::slice; use derive_more::Display; #[cfg(feature = "builder")] -use {crate::builder::AsBytes, crate::builder::BoxedDst, alloc::vec::Vec}; +use {crate::builder::AsBytes, crate::new_boxed, alloc::boxed::Box, alloc::vec::Vec}; /// Helper struct to read bytes from a raw pointer and increase the pointer /// automatically. @@ -16,8 +17,8 @@ struct Reader { } impl Reader { - fn new(ptr: *const T) -> Reader { - Reader { + const fn new(ptr: *const T) -> Self { + Self { ptr: ptr as *const u8, off: 0, } @@ -41,18 +42,17 @@ impl Reader { } } -const METADATA_SIZE: usize = size_of::() - + 4 * size_of::() - + size_of::() - + size_of::() - + 2 * size_of::(); +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)] +#[repr(C, align(8))] pub struct FramebufferTag { - typ: TagTypeId, - size: u32, + header: TagHeader, /// Contains framebuffer physical address. /// @@ -84,7 +84,9 @@ pub struct FramebufferTag { } impl FramebufferTag { + /// Constructs a new tag. #[cfg(feature = "builder")] + #[must_use] pub fn new( address: u64, pitch: u32, @@ -92,14 +94,14 @@ impl FramebufferTag { height: u32, bpp: u8, buffer_type: FramebufferType, - ) -> BoxedDst { - let mut bytes: Vec = address.to_le_bytes().into(); - bytes.extend(pitch.to_le_bytes()); - bytes.extend(width.to_le_bytes()); - bytes.extend(height.to_le_bytes()); - bytes.extend(bpp.to_le_bytes()); - bytes.extend(buffer_type.to_bytes()); - BoxedDst::new(&bytes) + ) -> Box { + 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]) } /// Contains framebuffer physical address. @@ -107,27 +109,32 @@ impl FramebufferTag { /// This field is 64-bit wide but bootloader should set it under 4GiB if /// possible for compatibility with payloads which aren’t aware of PAE or /// amd64. - pub fn address(&self) -> u64 { + #[must_use] + pub const fn address(&self) -> u64 { self.address } /// Contains the pitch in bytes. - pub fn pitch(&self) -> u32 { + #[must_use] + pub const fn pitch(&self) -> u32 { self.pitch } /// Contains framebuffer width in pixels. - pub fn width(&self) -> u32 { + #[must_use] + pub const fn width(&self) -> u32 { self.width } /// Contains framebuffer height in pixels. - pub fn height(&self) -> u32 { + #[must_use] + pub const fn height(&self) -> u32 { self.height } /// Contains number of bits per pixel. - pub fn bpp(&self) -> u8 { + #[must_use] + pub const fn bpp(&self) -> u8 { self.bpp } @@ -138,6 +145,7 @@ impl FramebufferTag { match typ { 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, @@ -176,22 +184,22 @@ impl FramebufferTag { impl TagTrait for FramebufferTag { const ID: TagType = TagType::Framebuffer; - fn dst_size(base_tag: &Tag) -> usize { - assert!(base_tag.size as usize >= METADATA_SIZE); - base_tag.size as usize - METADATA_SIZE + fn dst_len(header: &TagHeader) -> usize { + assert!(header.size as usize >= METADATA_SIZE); + header.size as usize - METADATA_SIZE } } impl Debug for FramebufferTag { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("FramebufferTag") - .field("typ", &{ self.typ }) - .field("size", &{ self.size }) + .field("typ", &self.header.typ) + .field("size", &self.header.size) .field("buffer_type", &self.buffer_type()) - .field("address", &{ self.address }) - .field("pitch", &{ self.pitch }) - .field("width", &{ self.width }) - .field("height", &{ self.height }) + .field("address", &self.address) + .field("pitch", &self.pitch) + .field("width", &self.width) + .field("height", &self.height) .field("bpp", &self.bpp) .finish() } @@ -199,15 +207,14 @@ impl Debug for FramebufferTag { impl PartialEq for FramebufferTag { fn eq(&self, other: &Self) -> bool { - ({ self.typ } == { other.typ } - && { self.size } == { other.size } - && { self.address } == { other.address } - && { self.pitch } == { other.pitch } - && { self.width } == { other.width } - && { self.height } == { other.height } - && { self.bpp } == { other.bpp } - && { self.type_no } == { other.type_no } - && self.buffer == other.buffer) + self.header == other.header + && self.address == { other.address } + && self.pitch == { other.pitch } + && self.width == { other.width } + && self.height == { other.height } + && self.bpp == { other.bpp } + && self.type_no == { other.type_no } + && self.buffer == other.buffer } } @@ -268,23 +275,23 @@ impl<'a> FramebufferType<'a> { let mut v = Vec::new(); match self { FramebufferType::Indexed { palette } => { - v.extend(0u8.to_le_bytes()); // type - v.extend(0u16.to_le_bytes()); // reserved - v.extend((palette.len() as u32).to_le_bytes()); + 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()); } } FramebufferType::RGB { red, green, blue } => { - v.extend(1u8.to_le_bytes()); // type - v.extend(0u16.to_le_bytes()); // reserved + 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_le_bytes()); // type - v.extend(0u16.to_le_bytes()); // reserved + v.extend(2u8.to_ne_bytes()); // type + v.extend(0u16.to_ne_bytes()); // reserved } } v @@ -337,6 +344,6 @@ mod tests { // Compile time test #[test] fn test_size() { - assert_eq!(size_of::(), 3) + assert_eq!(mem::size_of::(), 3) } } diff --git a/multiboot2/src/image_load_addr.rs b/multiboot2/src/image_load_addr.rs index fbc6645d..7248d782 100644 --- a/multiboot2/src/image_load_addr.rs +++ b/multiboot2/src/image_load_addr.rs @@ -1,6 +1,7 @@ //! Module for [`ImageLoadPhysAddrTag`]. -use crate::{Tag, TagTrait, TagType, TagTypeId}; +use crate::tag::TagHeader; +use crate::{TagTrait, TagType}; #[cfg(feature = "builder")] use core::mem::size_of; @@ -8,25 +9,26 @@ use core::mem::size_of; /// binary was relocated, for example if the relocatable header tag was /// specified. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct ImageLoadPhysAddrTag { - typ: TagTypeId, - size: u32, + header: TagHeader, load_base_addr: u32, } impl ImageLoadPhysAddrTag { + /// Constructs a new tag. #[cfg(feature = "builder")] + #[must_use] pub fn new(load_base_addr: u32) -> Self { Self { - typ: Self::ID.into(), - size: size_of::().try_into().unwrap(), + header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), load_base_addr, } } /// Returns the load base address. - pub fn load_base_addr(&self) -> u32 { + #[must_use] + pub const fn load_base_addr(&self) -> u32 { self.load_base_addr } } @@ -34,7 +36,7 @@ impl ImageLoadPhysAddrTag { impl TagTrait for ImageLoadPhysAddrTag { const ID: TagType = TagType::LoadBaseAddr; - fn dst_size(_base_tag: &Tag) {} + fn dst_len(_: &TagHeader) {} } #[cfg(all(test, feature = "builder"))] diff --git a/multiboot2/src/lib.rs b/multiboot2/src/lib.rs index af37b5b7..f43f9cfe 100644 --- a/multiboot2/src/lib.rs +++ b/multiboot2/src/lib.rs @@ -1,12 +1,20 @@ #![no_std] #![cfg_attr(feature = "unstable", feature(error_in_core))] -#![deny(missing_debug_implementations)] // --- BEGIN STYLE CHECKS --- -// These checks are optional in CI for PRs, as discussed in -// https://github.com/rust-osdev/multiboot2/pull/92 -#![deny(clippy::all)] +#![deny( + clippy::all, + clippy::cargo, + clippy::nursery, + clippy::must_use_candidate, + // clippy::restriction, + // clippy::pedantic +)] +// now allow a few rules which are denied by the above statement +// --> They are either ridiculous, not necessary, or we can't fix them. +#![allow(clippy::multiple_crate_versions)] +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] #![deny(rustdoc::all)] -#![allow(rustdoc::private_doc_tests)] // --- END STYLE CHECKS --- //! Library that assists parsing the Multiboot2 Information Structure (MBI) from @@ -48,7 +56,10 @@ extern crate bitflags; #[cfg(feature = "builder")] pub mod builder; +#[cfg(test)] +pub(crate) mod test_util; +mod boot_information; mod boot_loader_name; mod command_line; mod efi; @@ -63,8 +74,10 @@ 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 boot_loader_name::BootLoaderNameTag; pub use command_line::CommandLineTag; pub use efi::{ @@ -84,461 +97,38 @@ pub use module::{ModuleIter, ModuleTag}; pub use ptr_meta::Pointee; pub use rsdp::{RsdpV1Tag, RsdpV2Tag}; pub use smbios::SmbiosTag; -pub use tag::{StringError, Tag}; +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, VBEMemoryModel, VBEModeAttributes, VBEModeInfo, VBEWindowAttributes, }; -use core::fmt; -use core::mem::size_of; -use derive_more::Display; -// Must be public so that custom tags can be DSTs. -#[cfg(feature = "builder")] -use crate::builder::AsBytes; -use crate::framebuffer::UnknownFramebufferType; -use tag::TagIter; - /// Magic number that a Multiboot2-compliant boot loader will use to identify /// the handoff. The location depends on the architecture and the targeted /// machine state. pub const MAGIC: u32 = 0x36d76289; -/// Error type that describes errors while loading/parsing a multiboot2 information structure -/// from a given address. -#[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")] - NoEndTag, -} - -#[cfg(feature = "unstable")] -impl core::error::Error for MbiLoadError {} - -/// The basic header of a boot information. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] -pub struct BootInformationHeader { - // size is multiple of 8 - pub total_size: u32, - _reserved: u32, - // Followed by the boot information tags. -} - -#[cfg(feature = "builder")] -impl BootInformationHeader { - fn new(total_size: u32) -> Self { - Self { - total_size, - _reserved: 0, - } - } -} - -#[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)] -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 end_tag_prototype = EndTag::default(); - - let self_ptr = unsafe { self.tags.as_ptr().sub(size_of::()) }; - - let end_tag_ptr = unsafe { - self_ptr - .add(self.header.total_size as usize) - .sub(size_of::()) - }; - let end_tag = unsafe { &*(end_tag_ptr as *const EndTag) }; - - end_tag.typ == end_tag_prototype.typ && end_tag.size == end_tag_prototype.size - } -} - -/// A Multiboot 2 Boot Information (MBI) accessor. -#[repr(transparent)] -pub struct BootInformation<'a>(&'a BootInformationInner); - -impl<'a> BootInformation<'a> { - /// Loads the [`BootInformation`] from a pointer. The pointer must be valid - /// and aligned to an 8-byte boundary, as defined by the spec. - /// - /// ## Example - /// - /// ```rust - /// use multiboot2::{BootInformation, BootInformationHeader}; - /// - /// fn kernel_entry(mb_magic: u32, mbi_ptr: u32) { - /// if mb_magic == multiboot2::MAGIC { - /// let boot_info = unsafe { BootInformation::load(mbi_ptr as *const BootInformationHeader).unwrap() }; - /// let _cmd = boot_info.command_line_tag(); - /// } else { /* Panic or use multiboot1 flow. */ } - /// } - /// ``` - /// - /// ## Safety - /// * `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; - - // 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 slice_size = mbi.total_size as usize - 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); - } - - Ok(Self(mbi)) - } - - /// Get the start address of the boot info. - pub fn start_address(&self) -> usize { - self.as_ptr() as usize - } - - /// Get the start address of the boot info as pointer. - pub fn as_ptr(&self) -> *const () { - core::ptr::addr_of!(*self.0).cast() - } - - /// Get the end address of the boot info. - /// - /// This is the same as doing: - /// - /// ```rust,no_run - /// # use multiboot2::{BootInformation, BootInformationHeader}; - /// # let ptr = 0xdeadbeef as *const BootInformationHeader; - /// # let boot_info = unsafe { BootInformation::load(ptr).unwrap() }; - /// let end_addr = boot_info.start_address() + boot_info.total_size(); - /// ``` - pub fn end_address(&self) -> usize { - self.start_address() + self.total_size() - } - - /// Get the total size of the boot info struct. - pub fn total_size(&self) -> usize { - self.0.header.total_size as usize - } - - // ###################################################### - // ### BEGIN OF TAG GETTERS (in alphabetical order) - - /*fn apm(&self) { - // also add to debug output - todo!() - }*/ - - /// Search for the basic memory info tag. - pub fn basic_memory_info_tag(&self) -> Option<&BasicMemoryInfoTag> { - self.get_tag::() - } - - /// Search for the BootLoader name tag. - pub fn boot_loader_name_tag(&self) -> Option<&BootLoaderNameTag> { - self.get_tag::() - } - - /*fn bootdev(&self) { - // also add to debug output - todo!() - }*/ - - /// Search for the Command line tag. - pub fn command_line_tag(&self) -> Option<&CommandLineTag> { - self.get_tag::() - } - - /// Search for the EFI boot services not exited tag. - pub fn efi_bs_not_exited_tag(&self) -> Option<&EFIBootServicesNotExitedTag> { - self.get_tag::() - } - - /// Search for the EFI Memory map tag, if the boot services were exited. - /// Otherwise, if the [`TagType::EfiBs`] tag is present, this returns `None` - /// as it is strictly recommended to get the memory map from the `uefi` - /// services. - pub fn efi_memory_map_tag(&self) -> Option<&EFIMemoryMapTag> { - // If the EFIBootServicesNotExited is present, then we should not use - // the memory map, as it could still be in use. - match self.get_tag::() { - Some(_tag) => { - log::debug!("The EFI memory map is present but the UEFI Boot Services Not Existed Tag is present. Returning None."); - None - } - None => self.get_tag::(), - } - } - - /// Search for the EFI 32-bit SDT tag. - pub fn efi_sdt32_tag(&self) -> Option<&EFISdt32Tag> { - self.get_tag::() - } - - /// Search for the EFI 64-bit SDT tag. - pub fn efi_sdt64_tag(&self) -> Option<&EFISdt64Tag> { - self.get_tag::() - } - - /// Search for the EFI 32-bit image handle pointer tag. - pub fn efi_ih32_tag(&self) -> Option<&EFIImageHandle32Tag> { - self.get_tag::() - } - - /// Search for the EFI 64-bit image handle pointer tag. - pub fn efi_ih64_tag(&self) -> Option<&EFIImageHandle64Tag> { - self.get_tag::() - } - - /// Returns an [`ElfSectionIter`] iterator over the ELF Sections, if the - /// [`ElfSectionsTag`] is present. - /// - /// # Examples - /// - /// ```rust,no_run - /// # use multiboot2::{BootInformation, BootInformationHeader}; - /// # let ptr = 0xdeadbeef as *const BootInformationHeader; - /// # let boot_info = unsafe { BootInformation::load(ptr).unwrap() }; - /// if let Some(sections) = boot_info.elf_sections() { - /// let mut total = 0; - /// for section in sections { - /// println!("Section: {:?}", section); - /// total += 1; - /// } - /// } - /// ``` - pub fn elf_sections(&self) -> Option { - let tag = self.get_tag::(); - tag.map(|t| { - assert!((t.entry_size * t.shndx) <= t.size); - t.sections() - }) - } - - /// Search for the VBE framebuffer tag. The result is `Some(Err(e))`, if the - /// framebuffer type is unknown, while the framebuffer tag is present. - pub fn framebuffer_tag(&self) -> Option> { - self.get_tag::() - .map(|tag| match tag.buffer_type() { - Ok(_) => Ok(tag), - Err(e) => Err(e), - }) - } - - /// Search for the Image Load Base Physical Address tag. - pub fn load_base_addr_tag(&self) -> Option<&ImageLoadPhysAddrTag> { - self.get_tag::() - } - - /// Search for the Memory map tag. - pub fn memory_map_tag(&self) -> Option<&MemoryMapTag> { - self.get_tag::() - } - - /// Get an iterator of all module tags. - pub fn module_tags(&self) -> ModuleIter { - module::module_iter(self.tags()) - } - - /*fn network_tag(&self) { - // also add to debug output - todo!() - }*/ - - /// Search for the (ACPI 1.0) RSDP tag. - pub fn rsdp_v1_tag(&self) -> Option<&RsdpV1Tag> { - self.get_tag::() - } - - /// Search for the (ACPI 2.0 or later) RSDP tag. - pub fn rsdp_v2_tag(&self) -> Option<&RsdpV2Tag> { - self.get_tag::() - } - - /// Search for the SMBIOS tag. - pub fn smbios_tag(&self) -> Option<&SmbiosTag> { - self.get_tag::() - } - - /// Search for the VBE information tag. - pub fn vbe_info_tag(&self) -> Option<&VBEInfoTag> { - self.get_tag::() - } - - // ### END OF TAG GETTERS - // ###################################################### - - /// Public getter to find any Multiboot tag by its type, including - /// specified and custom ones. - /// - /// # Specified or Custom Tags - /// The Multiboot2 specification specifies a list of tags, see [`TagType`]. - /// However, it doesn't forbid to use custom tags. Because of this, there - /// exists the [`TagType`] abstraction. It is recommended to use this - /// getter only for custom tags. For specified tags, use getters, such as - /// [`Self::efi_ih64_tag`]. - /// - /// ## Use Custom Tags - /// The following example shows how you may use this interface to parse - /// custom tags from the MBI. If they are dynamically sized (DST), a few more - /// special handling is required. This is reflected by code-comments. - /// - /// ```no_run - /// use std::str::Utf8Error; - /// use multiboot2::{BootInformation, BootInformationHeader, Tag, TagTrait, TagType, TagTypeId}; - /// - /// #[repr(C)] - /// #[derive(multiboot2::Pointee)] // Only needed for DSTs. - /// struct CustomTag { - /// tag: TagTypeId, - /// size: u32, - /// // begin of inline string - /// name: [u8], - /// } - /// - /// // This implementation is only necessary for tags that are DSTs. - /// impl TagTrait for CustomTag { - /// const ID: TagType = TagType::Custom(0x1337); - /// - /// fn dst_size(base_tag: &Tag) -> usize { - /// // The size of the sized portion of the custom tag. - /// let tag_base_size = 8; // id + size is 8 byte in size - /// assert!(base_tag.size >= 8); - /// base_tag.size as usize - tag_base_size - /// } - /// } - /// - /// impl CustomTag { - /// fn name(&self) -> Result<&str, Utf8Error> { - /// Tag::parse_slice_as_string(&self.name) - /// } - /// } - /// let mbi_ptr = 0xdeadbeef as *const BootInformationHeader; - /// let mbi = unsafe { BootInformation::load(mbi_ptr).unwrap() }; - /// - /// let tag = mbi - /// .get_tag::() - /// .unwrap(); - /// assert_eq!(tag.name(), Ok("name")); - /// ``` - pub fn get_tag(&'a self) -> Option<&'a TagT> { - self.tags() - .find(|tag| tag.typ == TagT::ID) - .map(|tag| tag.cast_tag::()) - } - - /// Returns an iterator over all tags. - fn tags(&self) -> TagIter { - TagIter::new(&self.0.tags) - } -} - -impl fmt::Debug for BootInformation<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - /// Limit how many Elf-Sections should be debug-formatted. - /// Can be thousands of sections for a Rust binary => this is useless output. - /// If the user really wants this, they should debug-format the field directly. - const ELF_SECTIONS_LIMIT: usize = 7; - - let mut debug = f.debug_struct("Multiboot2BootInformation"); - debug - .field("start_address", &self.start_address()) - .field("end_address", &self.end_address()) - .field("total_size", &self.total_size()) - // now tags in alphabetical order - .field("basic_memory_info", &(self.basic_memory_info_tag())) - .field("boot_loader_name", &self.boot_loader_name_tag()) - // .field("bootdev", &self.bootdev_tag()) - .field("command_line", &self.command_line_tag()) - .field("efi_bs_not_exited", &self.efi_bs_not_exited_tag()) - .field("efi_memory_map", &self.efi_memory_map_tag()) - .field("efi_sdt32", &self.efi_sdt32_tag()) - .field("efi_sdt64", &self.efi_sdt64_tag()) - .field("efi_ih32", &self.efi_ih32_tag()) - .field("efi_ih64", &self.efi_ih64_tag()); - - // usually this is REALLY big (thousands of tags) => skip it here - { - let elf_sections_tag_entries_count = - self.elf_sections().map(|x| x.count()).unwrap_or(0); - - if elf_sections_tag_entries_count > ELF_SECTIONS_LIMIT { - debug.field("elf_sections (count)", &elf_sections_tag_entries_count); - } else { - debug.field("elf_sections", &self.elf_sections().unwrap_or_default()); - } - } - - debug - .field("framebuffer", &self.framebuffer_tag()) - .field("load_base_addr", &self.load_base_addr_tag()) - .field("memory_map", &self.memory_map_tag()) - .field("modules", &self.module_tags()) - // .field("network", &self.network_tag()) - .field("rsdp_v1", &self.rsdp_v1_tag()) - .field("rsdp_v2", &self.rsdp_v2_tag()) - .field("smbios_tag", &self.smbios_tag()) - .field("vbe_info_tag", &self.vbe_info_tag()) - .finish() - } -} +/// The required alignment for tags and the boot information. +pub const ALIGNMENT: usize = 8; #[cfg(test)] mod tests { use super::*; - use crate::memory_map::MemoryAreaType; - use crate::tag::StringError; + use crate::test_util::AlignedBytes; /// 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 /// struct around. #[test] + #[allow(clippy::missing_const_for_fn)] // only in Rust 1.70 necessary fn boot_information_is_send_and_sync() { fn accept(_: T) {} - - #[repr(C, align(8))] - struct Bytes([u8; 16]); - let bytes: Bytes = Bytes([ + let bytes = AlignedBytes([ 16, 0, 0, 0, // total_size 0, 0, 0, 0, // reserved 0, 0, 0, 0, // end tag type @@ -554,9 +144,7 @@ mod tests { #[test] fn no_tags() { - #[repr(C, align(8))] - struct Bytes([u8; 16]); - let bytes: Bytes = Bytes([ + let bytes = AlignedBytes([ 16, 0, 0, 0, // total_size 0, 0, 0, 0, // reserved 0, 0, 0, 0, // end tag type @@ -579,9 +167,7 @@ mod tests { #[test] #[should_panic] fn invalid_total_size() { - #[repr(C, align(8))] - struct Bytes([u8; 15]); - let bytes: Bytes = Bytes([ + let bytes = AlignedBytes([ 15, 0, 0, 0, // total_size 0, 0, 0, 0, // reserved 0, 0, 0, 0, // end tag type @@ -604,9 +190,7 @@ mod tests { #[test] #[should_panic] fn invalid_end_tag() { - #[repr(C, align(8))] - struct Bytes([u8; 16]); - let bytes: Bytes = Bytes([ + let bytes = AlignedBytes([ 16, 0, 0, 0, // total_size 0, 0, 0, 0, // reserved 0, 0, 0, 0, // end tag type @@ -627,11 +211,8 @@ mod tests { } #[test] - #[cfg_attr(miri, ignore)] fn name_tag() { - #[repr(C, align(8))] - struct Bytes([u8; 32]); - let bytes: Bytes = Bytes([ + let bytes = AlignedBytes([ 32, 0, 0, 0, // total_size 0, 0, 0, 0, // reserved 2, 0, 0, 0, // boot loader name tag type @@ -662,14 +243,11 @@ mod tests { } #[test] - #[cfg_attr(miri, ignore)] fn framebuffer_tag_rgb() { // direct RGB mode test: // taken from GRUB2 running in QEMU at // 1280x720 with 32bpp in BGRA format. - #[repr(C, align(8))] - struct Bytes([u8; 56]); - let bytes: Bytes = Bytes([ + let bytes = AlignedBytes([ 56, 0, 0, 0, // total size 0, 0, 0, 0, // reserved 8, 0, 0, 0, // framebuffer tag type @@ -728,9 +306,7 @@ mod tests { // indexed mode test: // this is synthetic, as I can't get QEMU // to run in indexed color mode. - #[repr(C, align(8))] - struct Bytes([u8; 64]); - let bytes: Bytes = Bytes([ + let bytes = AlignedBytes([ 64, 0, 0, 0, // total size 0, 0, 0, 0, // reserved 8, 0, 0, 0, // framebuffer tag type @@ -795,12 +371,10 @@ mod tests { } #[test] - #[cfg_attr(miri, ignore)] + #[allow(clippy::cognitive_complexity)] fn vbe_info_tag() { //Taken from GRUB2 running in QEMU. - #[repr(C, align(8))] - struct Bytes([u8; 800]); - let bytes = Bytes([ + let bytes = AlignedBytes([ 32, 3, 0, 0, // Total size. 0, 0, 0, 0, // Reserved 7, 0, 0, 0, // Tag type. @@ -875,83 +449,83 @@ mod tests { let vbe = bi.vbe_info_tag().unwrap(); use vbe_info::*; - assert_eq!({ vbe.mode }, 16762); - assert_eq!({ vbe.interface_segment }, 65535); - assert_eq!({ vbe.interface_offset }, 24576); - assert_eq!({ vbe.interface_length }, 79); - assert_eq!({ vbe.control_info.signature }, [86, 69, 83, 65]); - assert_eq!({ vbe.control_info.version }, 768); - assert_eq!({ vbe.control_info.oem_string_ptr }, 3221247964); + assert_eq!({ vbe.mode() }, 16762); + assert_eq!({ vbe.interface_segment() }, 65535); + assert_eq!({ vbe.interface_offset() }, 24576); + assert_eq!({ vbe.interface_length() }, 79); + assert_eq!({ vbe.control_info().signature }, [86, 69, 83, 65]); + assert_eq!({ vbe.control_info().version }, 768); + assert_eq!({ vbe.control_info().oem_string_ptr }, 3221247964); assert_eq!( - { vbe.control_info.capabilities }, + { vbe.control_info().capabilities }, VBECapabilities::SWITCHABLE_DAC ); - assert_eq!({ vbe.control_info.mode_list_ptr }, 1610645538); - assert_eq!({ vbe.control_info.total_memory }, 256); - assert_eq!({ vbe.control_info.oem_software_revision }, 0); - assert_eq!({ vbe.control_info.oem_vendor_name_ptr }, 3221247984); - assert_eq!({ vbe.control_info.oem_product_name_ptr }, 3221248003); - assert_eq!({ vbe.control_info.oem_product_revision_ptr }, 3221248023); - assert!({ vbe.mode_info.mode_attributes }.contains( + assert_eq!({ vbe.control_info().mode_list_ptr }, 1610645538); + assert_eq!({ vbe.control_info().total_memory }, 256); + assert_eq!({ vbe.control_info().oem_software_revision }, 0); + assert_eq!({ vbe.control_info().oem_vendor_name_ptr }, 3221247984); + assert_eq!({ vbe.control_info().oem_product_name_ptr }, 3221248003); + assert_eq!({ vbe.control_info().oem_product_revision_ptr }, 3221248023); + assert!({ vbe.mode_info().mode_attributes }.contains( VBEModeAttributes::SUPPORTED | VBEModeAttributes::COLOR | VBEModeAttributes::GRAPHICS | VBEModeAttributes::NOT_VGA_COMPATIBLE | VBEModeAttributes::LINEAR_FRAMEBUFFER )); - assert!(vbe.mode_info.window_a_attributes.contains( + assert!(vbe.mode_info().window_a_attributes.contains( VBEWindowAttributes::RELOCATABLE | VBEWindowAttributes::READABLE | VBEWindowAttributes::WRITEABLE )); - assert_eq!({ vbe.mode_info.window_granularity }, 64); - assert_eq!({ vbe.mode_info.window_size }, 64); - assert_eq!({ vbe.mode_info.window_a_segment }, 40960); - assert_eq!({ vbe.mode_info.window_function_ptr }, 3221247162); - assert_eq!({ vbe.mode_info.pitch }, 5120); - assert_eq!({ vbe.mode_info.resolution }, (1280, 800)); - assert_eq!(vbe.mode_info.character_size, (8, 16)); - assert_eq!(vbe.mode_info.number_of_planes, 1); - assert_eq!(vbe.mode_info.bpp, 32); - assert_eq!(vbe.mode_info.number_of_banks, 1); - assert_eq!(vbe.mode_info.memory_model, VBEMemoryModel::DirectColor); - assert_eq!(vbe.mode_info.bank_size, 0); - assert_eq!(vbe.mode_info.number_of_image_pages, 3); + assert_eq!({ vbe.mode_info().window_granularity }, 64); + assert_eq!({ vbe.mode_info().window_size }, 64); + assert_eq!({ vbe.mode_info().window_a_segment }, 40960); + assert_eq!({ vbe.mode_info().window_function_ptr }, 3221247162); + assert_eq!({ vbe.mode_info().pitch }, 5120); + assert_eq!({ vbe.mode_info().resolution }, (1280, 800)); + assert_eq!(vbe.mode_info().character_size, (8, 16)); + assert_eq!(vbe.mode_info().number_of_planes, 1); + assert_eq!(vbe.mode_info().bpp, 32); + assert_eq!(vbe.mode_info().number_of_banks, 1); + assert_eq!(vbe.mode_info().memory_model, VBEMemoryModel::DirectColor); + assert_eq!(vbe.mode_info().bank_size, 0); + assert_eq!(vbe.mode_info().number_of_image_pages, 3); assert_eq!( - vbe.mode_info.red_field, + vbe.mode_info().red_field, VBEField { position: 16, size: 8, } ); assert_eq!( - vbe.mode_info.green_field, + vbe.mode_info().green_field, VBEField { position: 8, size: 8, } ); assert_eq!( - vbe.mode_info.blue_field, + vbe.mode_info().blue_field, VBEField { position: 0, size: 8, } ); assert_eq!( - vbe.mode_info.reserved_field, + vbe.mode_info().reserved_field, VBEField { position: 24, size: 8, } ); assert_eq!( - vbe.mode_info.direct_color_attributes, + vbe.mode_info().direct_color_attributes, VBEDirectColorAttributes::RESERVED_USABLE ); - assert_eq!({ vbe.mode_info.framebuffer_base_ptr }, 4244635648); - assert_eq!({ vbe.mode_info.offscreen_memory_offset }, 0); - assert_eq!({ vbe.mode_info.offscreen_memory_size }, 0); + assert_eq!({ vbe.mode_info().framebuffer_base_ptr }, 4244635648); + assert_eq!({ vbe.mode_info().offscreen_memory_offset }, 0); + assert_eq!({ vbe.mode_info().offscreen_memory_size }, 0); } #[test] @@ -966,11 +540,8 @@ mod tests { /// Tests to parse a MBI that was statically extracted from a test run with /// GRUB as bootloader. #[test] - #[cfg_attr(miri, ignore)] fn grub2() { - #[repr(C, align(8))] - struct Bytes([u8; 960]); - let mut bytes: Bytes = Bytes([ + let mut bytes = AlignedBytes([ 192, 3, 0, 0, // total_size 0, 0, 0, 0, // reserved 1, 0, 0, 0, // boot command tag type @@ -1237,6 +808,7 @@ mod tests { } /// Helper for [`grub2`]. + #[allow(clippy::cognitive_complexity)] fn test_grub2_boot_info( bi: &BootInformation, addr: usize, @@ -1373,15 +945,12 @@ mod tests { } #[test] - #[cfg_attr(miri, ignore)] fn elf_sections() { - #[repr(C, align(8))] - struct Bytes([u8; 168]); - let mut bytes: Bytes = Bytes([ + let mut bytes = AlignedBytes([ 168, 0, 0, 0, // total_size 0, 0, 0, 0, // reserved 9, 0, 0, 0, // elf symbols tag type - 20, 2, 0, 0, // elf symbols tag size + 148, 0, 0, 0, // elf symbols tag size 2, 0, 0, 0, // elf symbols num 64, 0, 0, 0, // elf symbols entsize 1, 0, 0, 0, // elf symbols shndx @@ -1450,12 +1019,9 @@ mod tests { } #[test] - #[cfg_attr(miri, ignore)] fn efi_memory_map() { - #[repr(C, align(8))] - struct Bytes([u8; 80]); // test that the EFI memory map is detected. - let bytes: Bytes = Bytes([ + let bytes = AlignedBytes([ 80, 0, 0, 0, // size 0, 0, 0, 0, // reserved 17, 0, 0, 0, // EFI memory map type @@ -1490,10 +1056,7 @@ mod tests { assert_eq!(desc.phys_start, 0x100000); assert_eq!(desc.page_count, 4); assert_eq!(desc.ty, EFIMemoryAreaType::CONVENTIONAL); - // test that the EFI memory map is not detected if the boot services - // are not exited. - struct Bytes2([u8; 80]); - let bytes2: Bytes2 = Bytes2([ + let bytes2 = AlignedBytes([ 80, 0, 0, 0, // size 0, 0, 0, 0, // reserved 17, 0, 0, 0, // EFI memory map type @@ -1515,7 +1078,7 @@ mod tests { 0, 0, 0, 0, // end tag type. 8, 0, 0, 0, // end tag size. ]); - let bi = unsafe { BootInformation::load(bytes2.0.as_ptr().cast()) }; + let bi = unsafe { BootInformation::load(bytes2.as_ptr().cast()) }; let bi = bi.unwrap(); let efi_mmap = bi.efi_memory_map_tag(); assert!(efi_mmap.is_none()); @@ -1531,7 +1094,6 @@ mod tests { /// Example for a custom tag. #[test] - #[cfg_attr(miri, ignore)] fn get_custom_tag_from_mbi() { #[repr(C, align(8))] struct CustomTag { @@ -1543,13 +1105,10 @@ mod tests { impl TagTrait for CustomTag { const ID: TagType = TagType::Custom(0x1337); - fn dst_size(_base_tag: &Tag) {} + fn dst_len(_tag_header: &TagHeader) {} } - - #[repr(C, align(8))] - struct AlignedBytes([u8; 32]); // Raw bytes of a MBI that only contains the custom tag. - let bytes: AlignedBytes = AlignedBytes([ + let bytes = AlignedBytes([ 32, 0, 0, @@ -1597,7 +1156,6 @@ mod tests { /// Example for a custom DST tag. #[test] - #[cfg_attr(miri, ignore)] fn get_custom_dst_tag_from_mbi() { #[repr(C)] #[derive(crate::Pointee)] @@ -1609,25 +1167,22 @@ mod tests { impl CustomTag { fn name(&self) -> Result<&str, StringError> { - Tag::parse_slice_as_string(&self.name) + parse_slice_as_string(&self.name) } } impl TagTrait for CustomTag { const ID: TagType = TagType::Custom(0x1337); - fn dst_size(base_tag: &Tag) -> usize { + fn dst_len(header: &TagHeader) -> usize { // The size of the sized portion of the command line tag. let tag_base_size = 8; - assert!(base_tag.size >= 8); - base_tag.size as usize - tag_base_size + assert!(header.size >= 8); + header.size as usize - tag_base_size } } - - #[repr(C, align(8))] - struct AlignedBytes([u8; 32]); // Raw bytes of a MBI that only contains the custom tag. - let bytes: AlignedBytes = AlignedBytes([ + let bytes = AlignedBytes([ 32, 0, 0, @@ -1675,11 +1230,8 @@ mod tests { /// Tests that `get_tag` can consume multiple types that implement `Into` #[test] - #[cfg_attr(miri, ignore)] fn get_tag_into_variants() { - #[repr(C, align(8))] - struct Bytes([u8; 32]); - let bytes: Bytes = Bytes([ + let bytes = AlignedBytes([ 32, 0, 0, diff --git a/multiboot2/src/memory_map.rs b/multiboot2/src/memory_map.rs index 3b005c09..c7663d72 100644 --- a/multiboot2/src/memory_map.rs +++ b/multiboot2/src/memory_map.rs @@ -5,14 +5,15 @@ pub use uefi_raw::table::boot::MemoryAttribute as EFIMemoryAttribute; pub use uefi_raw::table::boot::MemoryDescriptor as EFIMemoryDesc; pub use uefi_raw::table::boot::MemoryType as EFIMemoryAreaType; -use crate::{Tag, TagTrait, TagType, TagTypeId}; +use crate::tag::TagHeader; +use crate::{TagTrait, TagType, TagTypeId}; use core::fmt::{Debug, Formatter}; use core::marker::PhantomData; use core::mem; #[cfg(feature = "builder")] -use {crate::builder::AsBytes, crate::builder::BoxedDst}; +use {crate::new_boxed, alloc::boxed::Box, core::slice}; -const METADATA_SIZE: usize = mem::size_of::() + 3 * mem::size_of::(); +const METADATA_SIZE: usize = mem::size_of::() + 2 * mem::size_of::(); /// This tag provides an initial host memory map (legacy boot, not UEFI). /// @@ -25,34 +26,38 @@ const METADATA_SIZE: usize = mem::size_of::() + 3 * mem::size_of:: BoxedDst { - let entry_size: u32 = mem::size_of::().try_into().unwrap(); - let entry_version: u32 = 0; - let mut bytes = [entry_size.to_le_bytes(), entry_version.to_le_bytes()].concat(); - for area in areas { - bytes.extend(area.as_bytes()); - } - BoxedDst::new(bytes.as_slice()) + #[must_use] + pub fn new(areas: &[MemoryArea]) -> Box { + let entry_size = mem::size_of::().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]) } /// Returns the entry size. - pub fn entry_size(&self) -> u32 { + #[must_use] + pub const fn entry_size(&self) -> u32 { self.entry_size } /// Returns the entry version. - pub fn entry_version(&self) -> u32 { + #[must_use] + pub const fn entry_version(&self) -> u32 { self.entry_version } @@ -60,6 +65,7 @@ impl MemoryMapTag { /// /// Usually, this should already reflect the memory consumed by the /// code running this. + #[must_use] pub fn memory_areas(&self) -> &[MemoryArea] { // If this ever fails, we need to model this differently in this crate. assert_eq!(self.entry_size as usize, mem::size_of::()); @@ -70,9 +76,9 @@ impl MemoryMapTag { impl TagTrait for MemoryMapTag { const ID: TagType = TagType::Mmap; - fn dst_size(base_tag: &Tag) -> usize { - assert!(base_tag.size as usize >= METADATA_SIZE); - let size = base_tag.size as usize - METADATA_SIZE; + fn dst_len(header: &TagHeader) -> usize { + assert!(header.size as usize >= METADATA_SIZE); + let size = header.size as usize - METADATA_SIZE; assert_eq!(size % mem::size_of::(), 0); size / mem::size_of::() } @@ -100,22 +106,26 @@ impl MemoryArea { } /// The start address of the memory region. - pub fn start_address(&self) -> u64 { + #[must_use] + pub const fn start_address(&self) -> u64 { self.base_addr } /// The end address of the memory region. - pub fn end_address(&self) -> u64 { + #[must_use] + pub const fn end_address(&self) -> u64 { self.base_addr + self.length } /// The size, in bytes, of the memory region. - pub fn size(&self) -> u64 { + #[must_use] + pub const fn size(&self) -> u64 { self.length } /// The type of the memory region. - pub fn typ(&self) -> MemoryAreaTypeId { + #[must_use] + pub const fn typ(&self) -> MemoryAreaTypeId { self.typ } } @@ -130,9 +140,6 @@ impl Debug for MemoryArea { } } -#[cfg(feature = "builder")] -impl AsBytes for MemoryArea {} - /// ABI-friendly version of [`MemoryAreaType`]. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] @@ -215,7 +222,7 @@ impl From for MemoryAreaTypeId { impl PartialEq for MemoryAreaTypeId { fn eq(&self, other: &MemoryAreaType) -> bool { - let val: MemoryAreaTypeId = (*other).into(); + let val: Self = (*other).into(); let val: u32 = val.0; self.0.eq(&val) } @@ -246,27 +253,31 @@ impl PartialEq for MemoryAreaType { #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct BasicMemoryInfoTag { - typ: TagTypeId, - size: u32, + header: TagHeader, memory_lower: u32, memory_upper: u32, } impl BasicMemoryInfoTag { + /// Constructs a new tag. + #[must_use] pub fn new(memory_lower: u32, memory_upper: u32) -> Self { Self { - typ: Self::ID.into(), - size: mem::size_of::().try_into().unwrap(), + header: TagHeader::new(Self::ID, mem::size_of::().try_into().unwrap()), memory_lower, memory_upper, } } - pub fn memory_lower(&self) -> u32 { + #[must_use] + /// Returns the lower memory bound. + pub const fn memory_lower(&self) -> u32 { self.memory_lower } - pub fn memory_upper(&self) -> u32 { + #[must_use] + /// Returns the upper memory bound. + pub const fn memory_upper(&self) -> u32 { self.memory_upper } } @@ -274,21 +285,17 @@ impl BasicMemoryInfoTag { impl TagTrait for BasicMemoryInfoTag { const ID: TagType = TagType::BasicMeminfo; - fn dst_size(_base_tag: &Tag) {} + fn dst_len(_: &TagHeader) {} } const EFI_METADATA_SIZE: usize = mem::size_of::() + 3 * mem::size_of::(); -#[cfg(feature = "builder")] -impl AsBytes for EFIMemoryDesc {} - /// EFI memory map tag. The embedded [`EFIMemoryDesc`]s follows the EFI /// specification. #[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub struct EFIMemoryMapTag { - typ: TagTypeId, - size: u32, + header: TagHeader, /// Most likely a little more than the size of a [`EFIMemoryDesc`]. /// This is always the reference, and `size_of` never. /// See . @@ -309,59 +316,38 @@ pub struct EFIMemoryMapTag { } impl EFIMemoryMapTag { - #[cfg(feature = "builder")] /// Create a new EFI memory map tag with the given memory descriptors. - /// Version and size can't be set because you're passing a slice of - /// EFIMemoryDescs, not the ones you might have gotten from the firmware. - pub fn new_from_descs(descs: &[EFIMemoryDesc]) -> BoxedDst { - // TODO replace this EfiMemorydesc::uefi_desc_size() in the next uefi_raw - // release. - - let size_base = mem::size_of::(); - // Taken from https://github.com/tianocore/edk2/blob/7142e648416ff5d3eac6c6d607874805f5de0ca8/MdeModulePkg/Core/PiSmmCore/Page.c#L1059 - let desc_size_diff = mem::size_of::() - size_base % mem::size_of::(); - let desc_size = size_base + desc_size_diff; - - assert!(desc_size >= size_base); - - let mut efi_mmap = alloc::vec::Vec::with_capacity(descs.len() * desc_size); - for desc in descs { - efi_mmap.extend(desc.as_bytes()); - // fill with zeroes - efi_mmap.extend([0].repeat(desc_size_diff)); - } + #[cfg(feature = "builder")] + #[must_use] + pub fn new_from_descs(descs: &[EFIMemoryDesc]) -> Box { + let efi_mmap = { + let ptr = descs.as_ptr().cast::(); + let len = mem::size_of_val(descs); + unsafe { slice::from_raw_parts(ptr, len) } + }; Self::new_from_map( - desc_size as u32, + mem::size_of::() as u32, EFIMemoryDesc::VERSION, - efi_mmap.as_slice(), + efi_mmap, ) } - #[cfg(feature = "builder")] /// Create a new EFI memory map tag from the given EFI memory map. - pub fn new_from_map(desc_size: u32, desc_version: u32, efi_mmap: &[u8]) -> BoxedDst { - assert!(desc_size > 0); - assert_eq!(efi_mmap.len() % desc_size as usize, 0); - assert_eq!( - efi_mmap - .as_ptr() - .align_offset(mem::align_of::()), - 0 - ); - let bytes = [ - &desc_size.to_le_bytes(), - &desc_version.to_le_bytes(), - efi_mmap, - ] - .concat(); - BoxedDst::new(&bytes) + #[cfg(feature = "builder")] + #[must_use] + pub fn new_from_map(desc_size: u32, desc_version: u32, efi_mmap: &[u8]) -> Box { + 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]) } /// Returns an iterator over the provided memory areas. /// /// Usually, this should already reflect the memory consumed by the /// code running this. + #[must_use] pub fn memory_areas(&self) -> EFIMemoryAreaIter { // If this ever fails, this needs to be refactored in a joint-effort // with the uefi-rs project to have all corresponding typings. @@ -380,8 +366,8 @@ impl EFIMemoryMapTag { impl Debug for EFIMemoryMapTag { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("EFIMemoryMapTag") - .field("typ", &self.typ) - .field("size", &self.size) + .field("typ", &self.header.typ) + .field("size", &self.header.size) .field("desc_size", &self.desc_size) .field("buf", &self.memory_map.as_ptr()) .field("buf_len", &self.memory_map.len()) @@ -393,9 +379,9 @@ impl Debug for EFIMemoryMapTag { impl TagTrait for EFIMemoryMapTag { const ID: TagType = TagType::EfiMmap; - fn dst_size(base_tag: &Tag) -> usize { - assert!(base_tag.size as usize >= EFI_METADATA_SIZE); - base_tag.size as usize - EFI_METADATA_SIZE + fn dst_len(header: &TagHeader) -> usize { + assert!(header.size as usize >= EFI_METADATA_SIZE); + header.size as usize - EFI_METADATA_SIZE } } @@ -451,7 +437,7 @@ impl<'a> ExactSizeIterator for EFIMemoryAreaIter<'a> { } } -#[cfg(all(test, feature = "builder", not(miri)))] +#[cfg(all(test, feature = "builder"))] mod tests { use super::*; use std::mem::size_of; @@ -476,8 +462,6 @@ mod tests { ]; let efi_mmap_tag = EFIMemoryMapTag::new_from_descs(&descs); - assert_eq!(efi_mmap_tag.desc_size, 48 /* 40 + 8 */); - let mut iter = efi_mmap_tag.memory_areas(); assert_eq!(iter.next(), Some(&descs[0])); diff --git a/multiboot2/src/module.rs b/multiboot2/src/module.rs index b5098beb..c77eb7e7 100644 --- a/multiboot2/src/module.rs +++ b/multiboot2/src/module.rs @@ -1,22 +1,21 @@ //! Module for [`ModuleTag`]. -use crate::tag::StringError; -use crate::{Tag, TagIter, TagTrait, TagType, TagTypeId}; +use crate::tag::{TagHeader, TagIter}; +use crate::{parse_slice_as_string, StringError, TagTrait, TagType}; use core::fmt::{Debug, Formatter}; -use core::mem::size_of; +use core::mem; #[cfg(feature = "builder")] -use {crate::builder::BoxedDst, alloc::vec::Vec}; +use {crate::new_boxed, alloc::boxed::Box}; -const METADATA_SIZE: usize = size_of::() + 3 * size_of::(); +const METADATA_SIZE: usize = mem::size_of::() + 2 * mem::size_of::(); /// 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 /// its location. #[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct ModuleTag { - typ: TagTypeId, - size: u32, + header: TagHeader, mod_start: u32, mod_end: u32, /// Null-terminated UTF-8 string @@ -24,20 +23,21 @@ pub struct ModuleTag { } impl ModuleTag { + /// Constructs a new tag. #[cfg(feature = "builder")] - pub fn new(start: u32, end: u32, cmdline: &str) -> BoxedDst { + #[must_use] + pub fn new(start: u32, end: u32, cmdline: &str) -> Box { assert!(end > start, "must have a size"); - let mut cmdline_bytes: Vec<_> = cmdline.bytes().collect(); - if !cmdline_bytes.ends_with(&[0]) { - // terminating null-byte - cmdline_bytes.push(0); + let start = start.to_ne_bytes(); + let end = end.to_ne_bytes(); + let cmdline = cmdline.as_bytes(); + + if cmdline.ends_with(&[0]) { + new_boxed(&[&start, &end, cmdline]) + } else { + new_boxed(&[&start, &end, cmdline, &[0]]) } - let start_bytes = start.to_le_bytes(); - let end_bytes = end.to_le_bytes(); - let mut content_bytes = [start_bytes, end_bytes].concat(); - content_bytes.extend_from_slice(&cmdline_bytes); - BoxedDst::new(&content_bytes) } /// Reads the command line of the boot module as Rust string slice without @@ -50,21 +50,24 @@ impl ModuleTag { /// /// If the function returns `Err` then perhaps the memory is invalid. pub fn cmdline(&self) -> Result<&str, StringError> { - Tag::parse_slice_as_string(&self.cmdline) + parse_slice_as_string(&self.cmdline) } /// Start address of the module. - pub fn start_address(&self) -> u32 { + #[must_use] + pub const fn start_address(&self) -> u32 { self.mod_start } /// End address of the module - pub fn end_address(&self) -> u32 { + #[must_use] + pub const fn end_address(&self) -> u32 { self.mod_end } /// The size of the module/the BLOB in memory. - pub fn module_size(&self) -> u32 { + #[must_use] + pub const fn module_size(&self) -> u32 { self.mod_end - self.mod_start } } @@ -72,17 +75,17 @@ impl ModuleTag { impl TagTrait for ModuleTag { const ID: TagType = TagType::Module; - fn dst_size(base_tag: &Tag) -> usize { - assert!(base_tag.size as usize >= METADATA_SIZE); - base_tag.size as usize - METADATA_SIZE + fn dst_len(header: &TagHeader) -> usize { + assert!(header.size as usize >= METADATA_SIZE); + header.size as usize - METADATA_SIZE } } impl Debug for ModuleTag { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("ModuleTag") - .field("type", &{ self.typ }) - .field("size", &{ self.size }) + .field("type", &self.header.typ) + .field("size", &self.header.size) // Trick to print as hex. .field("mod_start", &self.mod_start) .field("mod_end", &self.mod_end) @@ -92,7 +95,7 @@ impl Debug for ModuleTag { } } -pub fn module_iter(iter: TagIter) -> ModuleIter { +pub const fn module_iter(iter: TagIter) -> ModuleIter { ModuleIter { iter } } @@ -107,8 +110,8 @@ impl<'a> Iterator for ModuleIter<'a> { fn next(&mut self) -> Option<&'a ModuleTag> { self.iter - .find(|tag| tag.typ == TagType::Module) - .map(|tag| tag.cast_tag()) + .find(|tag| tag.header().typ == TagType::Module) + .map(|tag| tag.cast()) } } @@ -124,49 +127,51 @@ impl<'a> Debug for ModuleIter<'a> { #[cfg(test)] mod tests { - use crate::{ModuleTag, Tag, TagTrait, TagType}; - - const MSG: &str = "hello"; - - /// Returns the tag structure in bytes in little endian format. - fn get_bytes() -> std::vec::Vec { - // size is: 4 bytes for tag + 4 bytes for size + length of null-terminated string - // 4 bytes mod_start + 4 bytes mod_end - let size = (4 + 4 + 4 + 4 + MSG.as_bytes().len() + 1) as u32; - [ - &((TagType::Module.val()).to_le_bytes()), - &size.to_le_bytes(), - &0_u32.to_le_bytes(), - &1_u32.to_le_bytes(), - MSG.as_bytes(), - // Null Byte - &[0], - ] - .iter() - .flat_map(|bytes| bytes.iter()) - .copied() - .collect() + use super::*; + use crate::tag::{GenericTag, TagBytesRef}; + use crate::test_util::AlignedBytes; + use core::borrow::Borrow; + + #[rustfmt::skip] + fn get_bytes() -> AlignedBytes<24> { + AlignedBytes::new([ + TagType::Module.val() as u8, 0, 0, 0, + 22, 0, 0, 0, + /* mod start */ + 0x00, 0xff, 0, 0, + /* mod end */ + 0xff, 0xff, 0, 0, + b'h', b'e', b'l', b'l', b'o', b'\0', + /* padding */ + 0, 0, + ]) } /// Tests to parse a string with a terminating null byte from the tag (as the spec defines). #[test] - #[cfg_attr(miri, ignore)] fn test_parse_str() { - let tag = get_bytes(); - let tag = unsafe { &*tag.as_ptr().cast::() }; - let tag = tag.cast_tag::(); - assert_eq!({ tag.typ }, TagType::Module); - assert_eq!(tag.cmdline().expect("must be valid UTF-8"), MSG); + let bytes = get_bytes(); + let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); + let tag = GenericTag::ref_from(bytes); + let tag = tag.cast::(); + assert_eq!(tag.header.typ, TagType::Module); + assert_eq!(tag.cmdline(), Ok("hello")); } /// Test to generate a tag from a given string. #[test] #[cfg(feature = "builder")] fn test_build_str() { - let tag = ModuleTag::new(0, 1, MSG); + let tag = ModuleTag::new(0xff00, 0xffff, "hello"); + let bytes = tag.as_bytes(); + assert_eq!(bytes, &get_bytes()[..tag.size()]); + 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()); - assert_eq!(tag.cmdline(), Ok(MSG)); + assert_eq!(bytes, &get_bytes()[..tag.size()]); + assert_eq!(tag.cmdline(), Ok("hello")); // test also some bigger message let tag = ModuleTag::new(0, 1, "AbCdEfGhUjK YEAH"); diff --git a/multiboot2/src/rsdp.rs b/multiboot2/src/rsdp.rs index d8526157..7653ad61 100644 --- a/multiboot2/src/rsdp.rs +++ b/multiboot2/src/rsdp.rs @@ -12,7 +12,8 @@ //! signature should be manually verified. //! -use crate::{Tag, TagTrait, TagType, TagTypeId}; +use crate::tag::TagHeader; +use crate::{TagTrait, TagType}; #[cfg(feature = "builder")] use core::mem::size_of; use core::slice; @@ -23,10 +24,9 @@ const RSDPV1_LENGTH: usize = 20; /// This tag contains a copy of RSDP as defined per ACPI 1.0 specification. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct RsdpV1Tag { - typ: TagTypeId, - size: u32, + header: TagHeader, signature: [u8; 8], checksum: u8, oem_id: [u8; 6], @@ -35,7 +35,9 @@ pub struct RsdpV1Tag { } impl RsdpV1Tag { + /// Constructs a new tag. #[cfg(feature = "builder")] + #[must_use] pub fn new( signature: [u8; 8], checksum: u8, @@ -44,8 +46,7 @@ impl RsdpV1Tag { rsdt_address: u32, ) -> Self { Self { - typ: Self::ID.into(), - size: size_of::().try_into().unwrap(), + header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), signature, checksum, oem_id, @@ -57,11 +58,12 @@ impl RsdpV1Tag { /// The "RSD PTR " marker signature. /// /// This is originally a 8-byte C string (not null terminated!) that must contain "RSD PTR " - pub fn signature(&self) -> Result<&str, Utf8Error> { + pub const fn signature(&self) -> Result<&str, Utf8Error> { str::from_utf8(&self.signature) } /// Validation of the RSDPv1 checksum + #[must_use] pub fn checksum_is_valid(&self) -> bool { let bytes = unsafe { slice::from_raw_parts(self as *const _ as *const u8, RSDPV1_LENGTH + 8) }; @@ -72,17 +74,19 @@ impl RsdpV1Tag { } /// An OEM-supplied string that identifies the OEM. - pub fn oem_id(&self) -> Result<&str, Utf8Error> { + pub const fn oem_id(&self) -> Result<&str, Utf8Error> { str::from_utf8(&self.oem_id) } /// The revision of the ACPI. - pub fn revision(&self) -> u8 { + #[must_use] + pub const fn revision(&self) -> u8 { self.revision } /// The physical (I repeat: physical) address of the RSDT table. - pub fn rsdt_address(&self) -> usize { + #[must_use] + pub const fn rsdt_address(&self) -> usize { self.rsdt_address as usize } } @@ -90,15 +94,14 @@ impl RsdpV1Tag { impl TagTrait for RsdpV1Tag { const ID: TagType = TagType::AcpiV1; - fn dst_size(_base_tag: &Tag) {} + fn dst_len(_: &TagHeader) {} } /// 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)] +#[repr(C, align(8))] pub struct RsdpV2Tag { - typ: TagTypeId, - size: u32, + header: TagHeader, signature: [u8; 8], checksum: u8, oem_id: [u8; 6], @@ -112,8 +115,10 @@ pub struct RsdpV2Tag { } impl RsdpV2Tag { + /// Constructs a new tag. #[cfg(feature = "builder")] #[allow(clippy::too_many_arguments)] + #[must_use] pub fn new( signature: [u8; 8], checksum: u8, @@ -125,8 +130,7 @@ impl RsdpV2Tag { ext_checksum: u8, ) -> Self { Self { - typ: Self::ID.into(), - size: size_of::().try_into().unwrap(), + header: TagHeader::new(Self::ID, size_of::().try_into().unwrap()), signature, checksum, oem_id, @@ -142,11 +146,12 @@ impl RsdpV2Tag { /// The "RSD PTR " marker signature. /// /// This is originally a 8-byte C string (not null terminated!) that must contain "RSD PTR ". - pub fn signature(&self) -> Result<&str, Utf8Error> { + pub const fn signature(&self) -> Result<&str, Utf8Error> { str::from_utf8(&self.signature) } /// Validation of the RSDPv2 extended checksum + #[must_use] pub fn checksum_is_valid(&self) -> bool { let bytes = unsafe { slice::from_raw_parts(self as *const _ as *const u8, self.length as usize + 8) @@ -158,24 +163,27 @@ impl RsdpV2Tag { } /// An OEM-supplied string that identifies the OEM. - pub fn oem_id(&self) -> Result<&str, Utf8Error> { + pub const fn oem_id(&self) -> Result<&str, Utf8Error> { str::from_utf8(&self.oem_id) } /// The revision of the ACPI. - pub fn revision(&self) -> u8 { + #[must_use] + pub const fn revision(&self) -> u8 { self.revision } /// Physical address of the XSDT table. /// /// On x86, this is truncated from 64-bit to 32-bit. - pub fn xsdt_address(&self) -> usize { + #[must_use] + pub const fn xsdt_address(&self) -> usize { self.xsdt_address as usize } /// This field is used to calculate the checksum of the entire table, including both checksum fields. - pub fn ext_checksum(&self) -> u8 { + #[must_use] + pub const fn ext_checksum(&self) -> u8 { self.ext_checksum } } @@ -183,5 +191,5 @@ impl RsdpV2Tag { impl TagTrait for RsdpV2Tag { const ID: TagType = TagType::AcpiV2; - fn dst_size(_base_tag: &Tag) {} + fn dst_len(_: &TagHeader) {} } diff --git a/multiboot2/src/smbios.rs b/multiboot2/src/smbios.rs index 5c34c7d8..abd0e842 100644 --- a/multiboot2/src/smbios.rs +++ b/multiboot2/src/smbios.rs @@ -1,51 +1,69 @@ //! Module for [`SmbiosTag`]. -#[cfg(feature = "builder")] -use crate::builder::BoxedDst; -use crate::{Tag, TagTrait, TagType, TagTypeId}; +use crate::tag::TagHeader; +use crate::{TagTrait, TagType}; use core::fmt::Debug; +use core::mem; +#[cfg(feature = "builder")] +use {crate::new_boxed, alloc::boxed::Box}; -const METADATA_SIZE: usize = core::mem::size_of::() - + core::mem::size_of::() - + core::mem::size_of::() * 8; +const METADATA_SIZE: usize = mem::size_of::() + mem::size_of::() * 8; /// This tag contains a copy of SMBIOS tables as well as their version. #[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct SmbiosTag { - typ: TagTypeId, - size: u32, - pub major: u8, - pub minor: u8, + header: TagHeader, + major: u8, + minor: u8, _reserved: [u8; 6], - pub tables: [u8], + tables: [u8], } impl SmbiosTag { + /// Constructs a new tag. #[cfg(feature = "builder")] - pub fn new(major: u8, minor: u8, tables: &[u8]) -> BoxedDst { - let mut bytes = [major, minor, 0, 0, 0, 0, 0, 0].to_vec(); - bytes.extend(tables); - BoxedDst::new(&bytes) + #[must_use] + pub fn new(major: u8, minor: u8, tables: &[u8]) -> Box { + let reserved = [0, 0, 0, 0, 0, 0]; + new_boxed(&[&[major, minor], &reserved, tables]) + } + + /// Returns the major number. + #[must_use] + pub const fn major(&self) -> u8 { + self.major + } + + /// Returns the major number. + #[must_use] + pub const fn minor(&self) -> u8 { + self.minor + } + + /// Returns the raw tables. + #[must_use] + pub const fn tables(&self) -> &[u8] { + &self.tables } } impl TagTrait for SmbiosTag { const ID: TagType = TagType::Smbios; - fn dst_size(base_tag: &Tag) -> usize { - assert!(base_tag.size as usize >= METADATA_SIZE); - base_tag.size as usize - METADATA_SIZE + fn dst_len(header: &TagHeader) -> usize { + assert!(header.size as usize >= METADATA_SIZE); + header.size as usize - METADATA_SIZE } } impl Debug for SmbiosTag { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("BootLoaderNameTag") - .field("typ", &{ self.typ }) - .field("size", &{ self.size }) - .field("major", &{ self.major }) - .field("minor", &{ self.minor }) + .field("typ", &self.header.typ) + .field("size", &self.header.size) + .field("major", &self.major) + .field("minor", &self.minor) .finish() } } @@ -53,41 +71,47 @@ impl Debug for SmbiosTag { #[cfg(test)] mod tests { use super::*; + use crate::tag::{GenericTag, TagBytesRef}; + use crate::test_util::AlignedBytes; + use core::borrow::Borrow; - /// Returns the tag structure in bytes in little endian format. - fn get_bytes() -> std::vec::Vec { - let tables = [0xabu8; 24]; - // size is: 4 bytes for tag + 4 bytes for size + 1 byte for major and minor - // + 6 bytes reserved + the actual tables - let size = (4 + 4 + 1 + 1 + 6 + tables.len()) as u32; - let typ: u32 = TagType::Smbios.into(); - let mut bytes = [typ.to_le_bytes(), size.to_le_bytes()].concat(); - bytes.push(3); - bytes.push(0); - bytes.extend([0; 6]); - bytes.extend(tables); - bytes + #[rustfmt::skip] + fn get_bytes() -> AlignedBytes<32> { + AlignedBytes::new([ + TagType::Smbios.val() as u8, 0, 0, 0, + 25, 0, 0, 0, + /* major */ + 7, + /* minor */ + 42, + /* reserved */ + 0, 0, 0, 0, 0, 0, + /* table data */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, + /* padding */ + 0, 0, 0, 0, 0, 0, 0 + ]) } /// Test to parse a given tag. #[test] - #[cfg_attr(miri, ignore)] fn test_parse() { - let tag = get_bytes(); - let tag = unsafe { &*tag.as_ptr().cast::() }; - let tag = tag.cast_tag::(); - assert_eq!({ tag.typ }, TagType::Smbios); - assert_eq!(tag.major, 3); - assert_eq!(tag.minor, 0); - assert_eq!(tag.tables, [0xabu8; 24]); + let bytes = get_bytes(); + let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); + let tag = GenericTag::ref_from(bytes); + let tag = tag.cast::(); + assert_eq!(tag.header.typ, TagType::Smbios); + assert_eq!(tag.major, 7); + assert_eq!(tag.minor, 42); + assert_eq!(&tag.tables, [0, 1, 2, 3, 4, 5, 6, 7, 8]); } /// Test to generate a tag. #[test] #[cfg(feature = "builder")] fn test_build() { - let tag = SmbiosTag::new(3, 0, &[0xabu8; 24]); + 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()); + assert_eq!(bytes, &get_bytes()[..tag.size()]); } } diff --git a/multiboot2/src/tag.rs b/multiboot2/src/tag.rs index 0002d59f..3943456d 100644 --- a/multiboot2/src/tag.rs +++ b/multiboot2/src/tag.rs @@ -1,171 +1,442 @@ //! Module for the base tag definitions and helper types. //! -//! The relevant exports of this module is [`Tag`]. - -use crate::{TagTrait, TagType, TagTypeId}; -use core::fmt; -use core::fmt::{Debug, Display, Formatter}; -use core::marker::PhantomData; -use core::str::Utf8Error; - -/// Error type describing failures when parsing the string from a tag. -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum StringError { - /// There is no terminating NUL character, although the specification - /// requires one. - MissingNul(core::ffi::FromBytesUntilNulError), - /// The sequence until the first NUL character is not valid UTF-8. - Utf8(Utf8Error), -} +//! 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 -impl Display for StringError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } +use crate::util::increase_to_alignment; +use crate::{TagTrait, TagType, TagTypeId, ALIGNMENT}; +use core::fmt::{Debug, Formatter}; +use core::mem; +use core::ops::Deref; +use core::ptr; + +/// The common header that all tags have in common. This type is ABI compatible. +/// +/// Not to be confused with Multiboot header tags, which are something +/// different. +/// +/// It is the sized counterpart of `GenericTag`, an internal type. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[repr(C, align(8))] // Alignment also propagates to all tag types using this. +pub struct TagHeader { + /// The ABI-compatible [`TagType`]. + pub typ: TagTypeId, /* u32 */ + /// The total size of the tag including the header. + pub size: u32, + // Followed by optional additional tag specific fields. } -#[cfg(feature = "unstable")] -impl core::error::Error for StringError { - fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { - match self { - StringError::MissingNul(e) => Some(e), - StringError::Utf8(e) => Some(e), +impl TagHeader { + /// Creates a new header. + pub fn new(typ: impl Into, size: u32) -> Self { + Self { + typ: typ.into(), + size, } } } -/// Common base structure for all tags that can be passed via the Multiboot2 -/// Information Structure (MBI) to a Multiboot2 payload/program/kernel. +/// Wraps a byte slice representing a tag, but guarantees that the memory +/// requirements are fulfilled. /// -/// Can be transformed to any other tag (sized or unsized/DST) via -/// [`Tag::cast_tag`]. +/// This is the only type that can be used to construct a [`GenericTag`]. /// -/// Do not confuse them with the Multiboot2 header tags. They are something -/// different. -#[derive(Clone, Copy)] +/// 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 Tag { - pub typ: TagTypeId, // u32 - pub size: u32, - // followed by additional, tag specific fields +pub struct GenericTag { + header: TagHeader, + /// Payload of the tag that is reflected in the `size` attribute, thus, no + /// padding bytes! + payload: [u8], } -impl Tag { - /// Returns the underlying type of the tag. - pub fn typ(&self) -> TagType { - self.typ.into() +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 } } - /// Casts the base tag to the specific tag type. - pub fn cast_tag<'a, T: TagTrait + ?Sized + 'a>(&'a self) -> &'a T { - assert_eq!(self.typ, T::ID); - // Safety: At this point, we trust that "self.size" and the size hint - // for DST tags are sane. - unsafe { TagTrait::from_base_tag(self) } + pub const fn header(&self) -> &TagHeader { + &self.header } - /// Parses the provided byte sequence as Multiboot string, which maps to a - /// [`str`]. - pub fn parse_slice_as_string(bytes: &[u8]) -> Result<&str, StringError> { - let cstr = core::ffi::CStr::from_bytes_until_nul(bytes).map_err(StringError::MissingNul)?; + #[cfg(all(test, feature = "builder"))] + pub const fn payload(&self) -> &[u8] { + &self.payload + } - cstr.to_str().map_err(StringError::Utf8) + /// 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 Tag { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let tag_type = TagType::from(self.typ); - - let mut debug = f.debug_struct("Tag"); - debug.field("typ", &tag_type); - - if !matches!(tag_type, TagType::Custom(_)) { - debug.field("typ (numeric)", &(u32::from(self.typ))); - } +impl Debug for GenericTag { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("GenericTag") + .field("header", &self.header) + .field("payload", &"") + .finish() + } +} - debug.field("size", &(self.size)); +impl TagTrait for GenericTag { + const ID: TagType = TagType::Custom(0xffffffff); - debug.finish() + fn dst_len(header: &TagHeader) -> usize { + assert!(header.size as usize >= Self::BASE_SIZE); + header.size as usize - Self::BASE_SIZE } } -/// Iterates the MBI's tags from the first tag to the end tag. +/// 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> { - /// Pointer to the next tag. Updated in each iteration. - pub current: *const Tag, - /// The pointer right after the MBI. Used for additional bounds checking. - end_ptr_exclusive: *const u8, - /// Lifetime capture of the MBI's memory. - _mem: PhantomData<&'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 { - current: mem.as_ptr().cast(), - end_ptr_exclusive: unsafe { mem.as_ptr().add(mem.len()) }, - _mem: PhantomData, + next_tag_offset: 0, + buffer: mem, + exclusive_end, } } } impl<'a> Iterator for TagIter<'a> { - type Item = &'a Tag; + type Item = &'a GenericTag; + + fn next(&mut self) -> Option { + let next_ptr = unsafe { self.buffer.as_ptr().add(self.next_tag_offset) }; - fn next(&mut self) -> Option<&'a Tag> { - // This never failed so far. But better be safe. - assert!(self.current.cast::() < self.end_ptr_exclusive); + if next_ptr == self.exclusive_end { + return None; + } + assert!(next_ptr < self.exclusive_end); - let tag = unsafe { &*self.current }; - match tag.typ() { - TagType::End => None, // end tag - _ => { - // We return the tag and update self.current already to the next - // tag. + let next_tag_ptr = next_ptr.cast::(); - // next pointer (rounded up to 8-byte alignment) - let ptr_offset = (tag.size as usize + 7) & !7; + let tag_hdr = unsafe { &*next_tag_ptr }; - // go to next tag - self.current = unsafe { self.current.cast::().add(ptr_offset).cast::() }; + // 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); - Some(tag) - } - } + // 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 parse_slice_as_string() { - // empty slice is invalid - assert!(matches!( - Tag::parse_slice_as_string(&[]), - Err(StringError::MissingNul(_)) - )); - // empty string is fine - assert_eq!(Tag::parse_slice_as_string(&[0x00]), Ok("")); - // reject invalid utf8 - assert!(matches!( - Tag::parse_slice_as_string(&[0xff, 0x00]), - Err(StringError::Utf8(_)) - )); - // reject missing null - assert!(matches!( - Tag::parse_slice_as_string(b"hello"), - Err(StringError::MissingNul(_)) - )); - // must not include final null - assert_eq!(Tag::parse_slice_as_string(b"hello\0"), Ok("hello")); - assert_eq!(Tag::parse_slice_as_string(b"hello\0\0"), Ok("hello")); - // must skip everytihng after first null - assert_eq!(Tag::parse_slice_as_string(b"hello\0foo"), Ok("hello")); + 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); + } + + #[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); } } diff --git a/multiboot2/src/tag_trait.rs b/multiboot2/src/tag_trait.rs index 2a073da7..1d44d72f 100644 --- a/multiboot2/src/tag_trait.rs +++ b/multiboot2/src/tag_trait.rs @@ -1,10 +1,11 @@ //! Module for [`TagTrait`]. -use crate::{Tag, TagType}; +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_size`] implementation +/// 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. /// @@ -22,12 +23,12 @@ pub trait TagTrait: Pointee { /// /// For sized tags, this just returns `()`. For DSTs, this returns an /// `usize`. - fn dst_size(base_tag: &Tag) -> Self::Metadata; + fn dst_len(header: &TagHeader) -> Self::Metadata; /// Returns the tag as the common base tag structure. - fn as_base_tag(&self) -> &Tag { + fn as_base_tag(&self) -> &TagHeader { let ptr = core::ptr::addr_of!(*self); - unsafe { &*ptr.cast::() } + unsafe { &*ptr.cast::() } } /// Returns the total size of the tag. The depends on the `size` field of @@ -43,17 +44,4 @@ pub trait TagTrait: Pointee { let ptr = core::ptr::addr_of!(*self); unsafe { core::slice::from_raw_parts(ptr.cast(), self.size()) } } - - /// Creates a reference to a (dynamically sized) tag type in a safe way. - /// DST tags need to implement a proper [`Self::dst_size`] implementation. - /// - /// # Safety - /// Callers must be sure that the "size" field of the provided [`Tag`] is - /// sane and the underlying memory valid. The implementation of this trait - /// **must have** a correct [`Self::dst_size`] implementation. - unsafe fn from_base_tag(tag: &Tag) -> &Self { - let ptr = core::ptr::addr_of!(*tag); - let ptr = ptr_meta::from_raw_parts(ptr.cast(), Self::dst_size(tag)); - &*ptr - } } diff --git a/multiboot2/src/tag_type.rs b/multiboot2/src/tag_type.rs index 49d32d25..f7ae50df 100644 --- a/multiboot2/src/tag_type.rs +++ b/multiboot2/src/tag_type.rs @@ -7,17 +7,18 @@ use core::hash::Hash; /// Serialized form of [`TagType`] that matches the binary representation /// (`u32`). The abstraction corresponds to the `typ`/`type` field of a -/// Multiboot2 [`Tag`]. This type can easily be created from or converted to +/// Multiboot2 [`TagHeader`]. This type can easily be created from or converted to /// [`TagType`]. /// -/// [`Tag`]: crate::Tag +/// [`TagHeader`]: crate::TagHeader #[repr(transparent)] #[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Ord, Hash)] pub struct TagTypeId(u32); impl TagTypeId { /// Constructor. - pub fn new(val: u32) -> Self { + #[must_use] + pub const fn new(val: u32) -> Self { Self(val) } } @@ -135,6 +136,7 @@ pub enum TagType { impl TagType { /// Convenient wrapper to get the underlying `u32` representation of the tag. + #[must_use] pub fn val(&self) -> u32 { u32::from(*self) } @@ -161,29 +163,29 @@ mod primitive_conversion_impls { impl From for TagType { fn from(value: u32) -> Self { match value { - 0 => TagType::End, - 1 => TagType::Cmdline, - 2 => TagType::BootLoaderName, - 3 => TagType::Module, - 4 => TagType::BasicMeminfo, - 5 => TagType::Bootdev, - 6 => TagType::Mmap, - 7 => TagType::Vbe, - 8 => TagType::Framebuffer, - 9 => TagType::ElfSections, - 10 => TagType::Apm, - 11 => TagType::Efi32, - 12 => TagType::Efi64, - 13 => TagType::Smbios, - 14 => TagType::AcpiV1, - 15 => TagType::AcpiV2, - 16 => TagType::Network, - 17 => TagType::EfiMmap, - 18 => TagType::EfiBs, - 19 => TagType::Efi32Ih, - 20 => TagType::Efi64Ih, - 21 => TagType::LoadBaseAddr, - c => TagType::Custom(c), + 0 => Self::End, + 1 => Self::Cmdline, + 2 => Self::BootLoaderName, + 3 => Self::Module, + 4 => Self::BasicMeminfo, + 5 => Self::Bootdev, + 6 => Self::Mmap, + 7 => Self::Vbe, + 8 => Self::Framebuffer, + 9 => Self::ElfSections, + 10 => Self::Apm, + 11 => Self::Efi32, + 12 => Self::Efi64, + 13 => Self::Smbios, + 14 => Self::AcpiV1, + 15 => Self::AcpiV2, + 16 => Self::Network, + 17 => Self::EfiMmap, + 18 => Self::EfiBs, + 19 => Self::Efi32Ih, + 20 => Self::Efi64Ih, + 21 => Self::LoadBaseAddr, + c => Self::Custom(c), } } } @@ -226,14 +228,14 @@ mod intermediate_conversion_impls { impl From for TagType { fn from(value: TagTypeId) -> Self { let value = u32::from(value); - TagType::from(value) + Self::from(value) } } impl From for TagTypeId { fn from(value: TagType) -> Self { let value = u32::from(value); - TagTypeId::from(value) + Self::from(value) } } } diff --git a/multiboot2/src/test_util.rs b/multiboot2/src/test_util.rs new file mode 100644 index 00000000..2a983dc9 --- /dev/null +++ b/multiboot2/src/test_util.rs @@ -0,0 +1,87 @@ +//! Various test utilities. + +use crate::ALIGNMENT; +use core::borrow::Borrow; +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)] +#[repr(C, align(8))] +pub struct AlignedBytes(pub [u8; N]); + +impl AlignedBytes { + pub const fn new(bytes: [u8; N]) -> Self { + Self(bytes) + } +} + +impl Borrow<[u8; N]> for AlignedBytes { + fn borrow(&self) -> &[u8; N] { + &self.0 + } +} + +impl Borrow<[u8]> for AlignedBytes { + fn borrow(&self) -> &[u8] { + &self.0 + } +} + +impl Deref for AlignedBytes { + type Target = [u8; N]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// The tests down below are all Miri-approved. +#[cfg(test)] +mod tests { + use super::*; + use crate::tag::TagBytesRef; + use core::mem; + use core::ptr::addr_of; + + #[test] + fn abi() { + 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/src/util.rs b/multiboot2/src/util.rs new file mode 100644 index 00000000..c9ed3fa6 --- /dev/null +++ b/multiboot2/src/util.rs @@ -0,0 +1,161 @@ +//! 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)] +pub enum StringError { + /// There is no terminating NUL character, although the specification + /// requires one. + MissingNul(core::ffi::FromBytesUntilNulError), + /// The sequence until the first NUL character is not valid UTF-8. + Utf8(Utf8Error), +} + +impl Display for StringError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(feature = "unstable")] +impl core::error::Error for StringError { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + match self { + Self::MissingNul(e) => Some(e), + Self::Utf8(e) => Some(e), + } + } +} + +/// 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> { + let cstr = core::ffi::CStr::from_bytes_until_nul(bytes).map_err(StringError::MissingNul)?; + 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() { + // empty slice is invalid + assert!(matches!( + parse_slice_as_string(&[]), + Err(StringError::MissingNul(_)) + )); + // empty string is fine + assert_eq!(parse_slice_as_string(&[0x00]), Ok("")); + // reject invalid utf8 + assert!(matches!( + parse_slice_as_string(&[0xff, 0x00]), + Err(StringError::Utf8(_)) + )); + // reject missing null + assert!(matches!( + parse_slice_as_string(b"hello"), + Err(StringError::MissingNul(_)) + )); + // must not include final null + assert_eq!(parse_slice_as_string(b"hello\0"), Ok("hello")); + assert_eq!(parse_slice_as_string(b"hello\0\0"), Ok("hello")); + // 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 a5973f4c..8670c0ae 100644 --- a/multiboot2/src/vbe_info.rs +++ b/multiboot2/src/vbe_info.rs @@ -1,48 +1,92 @@ //! Module for [`VBEInfoTag`]. -use crate::{Tag, TagTrait, TagType, TagTypeId}; +use crate::{TagHeader, TagTrait, TagType}; use core::fmt; +use core::mem; /// This tag contains VBE metadata, VBE controller information returned by the /// VBE Function 00h and VBE mode information returned by the VBE Function 01h. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct VBEInfoTag { - typ: TagTypeId, - length: u32, + header: TagHeader, + mode: u16, + interface_segment: u16, + interface_offset: u16, + interface_length: u16, + control_info: VBEControlInfo, + mode_info: VBEModeInfo, +} + +impl VBEInfoTag { + /// Constructs a new tag. + #[cfg(feature = "builder")] + #[must_use] + pub fn new( + mode: u16, + interface_segment: u16, + interface_offset: u16, + interface_length: u16, + control_info: VBEControlInfo, + mode_info: VBEModeInfo, + ) -> Self { + Self { + header: TagHeader::new(Self::ID, mem::size_of::().try_into().unwrap()), + mode, + interface_segment, + interface_offset, + interface_length, + control_info, + mode_info, + } + } /// Indicates current video mode in the format specified in VBE 3.0. - pub mode: u16, + #[must_use] + pub const fn mode(&self) -> u16 { + self.mode + } - /// Contain the segment of the table of a protected mode interface defined in VBE 2.0+. + /// Returns the segment of the table of a protected mode interface defined in VBE 2.0+. /// /// If the information for a protected mode interface is not available /// this field is set to zero. - pub interface_segment: u16, - - /// Contain the segment offset of the table of a protected mode interface defined in VBE 2.0+. + #[must_use] + pub const fn interface_segment(&self) -> u16 { + self.interface_segment + } + /// Returns the segment offset of the table of a protected mode interface defined in VBE 2.0+. /// /// If the information for a protected mode interface is not available /// this field is set to zero. - pub interface_offset: u16, - - /// Contain the segment length of the table of a protected mode interface defined in VBE 2.0+. + #[must_use] + pub const fn interface_offset(&self) -> u16 { + self.interface_offset + } + /// Returns the segment length of the table of a protected mode interface defined in VBE 2.0+. /// /// If the information for a protected mode interface is not available /// this field is set to zero. - pub interface_length: u16, - - /// Contains VBE controller information returned by the VBE Function `00h`. - pub control_info: VBEControlInfo, - - /// Contains VBE mode information returned by the VBE Function `01h`. - pub mode_info: VBEModeInfo, + #[must_use] + pub const fn interface_length(&self) -> u16 { + self.interface_length + } + /// Returns VBE controller information returned by the VBE Function `00h`. + #[must_use] + pub const fn control_info(&self) -> VBEControlInfo { + self.control_info + } + /// Returns VBE mode information returned by the VBE Function `01h`. + #[must_use] + pub const fn mode_info(&self) -> VBEModeInfo { + self.mode_info + } } impl TagTrait for VBEInfoTag { const ID: TagType = TagType::Vbe; - fn dst_size(_base_tag: &Tag) {} + fn dst_len(_: &TagHeader) {} } /// VBE controller information.