diff --git a/.github/workflows/_build-rust.yml b/.github/workflows/_build-rust.yml index 27c01a2f..d855a822 100644 --- a/.github/workflows/_build-rust.yml +++ b/.github/workflows/_build-rust.yml @@ -98,8 +98,11 @@ jobs: 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/rust.yml b/.github/workflows/rust.yml index 3c914f55..70715b72 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -24,7 +24,7 @@ jobs: name: build (msrv) uses: ./.github/workflows/_build-rust.yml with: - rust-version: 1.75.0 # MSRV + rust-version: 1.70.0 # MSRV do-style-check: false features: builder @@ -50,7 +50,7 @@ jobs: needs: build_msrv uses: ./.github/workflows/_build-rust.yml with: - rust-version: 1.75.0 # MSRV + rust-version: 1.70.0 # MSRV do-style-check: false rust-target: thumbv7em-none-eabihf features: builder @@ -107,7 +107,7 @@ jobs: needs: build_msrv uses: ./.github/workflows/_build-rust.yml with: - rust-version: 1.75.0 # MSRV + rust-version: 1.70.0 # MSRV do-style-check: true do-test: false features: builder diff --git a/Cargo.lock b/Cargo.lock index 6e378ffe..a17cb6d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,23 +10,13 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "derive_more" -version = "1.0.0" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", "syn 2.0.74", - "unicode-xid", ] [[package]] @@ -136,9 +126,3 @@ name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" diff --git a/Cargo.toml b/Cargo.toml index e5eacb6c..59e8e1e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ exclude = [ [workspace.dependencies] bitflags = "2.6.0" -derive_more = { version = "1.0.0", default-features = false, features = ["display"] } +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 d87a36f5..ec2c77f7 100644 --- a/integration-test/bins/Cargo.lock +++ b/integration-test/bins/Cargo.lock @@ -34,23 +34,13 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "derive_more" -version = "1.0.0" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", "syn 2.0.74", - "unicode-xid", ] [[package]] @@ -277,12 +267,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "util" version = "0.1.0" 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/Cargo.toml b/multiboot2-header/Cargo.toml index e94b20e7..0fd73855 100644 --- a/multiboot2-header/Cargo.toml +++ b/multiboot2-header/Cargo.toml @@ -26,7 +26,7 @@ readme = "README.md" homepage = "https://github.com/rust-osdev/multiboot2-header" repository = "https://github.com/rust-osdev/multiboot2" documentation = "https://docs.rs/multiboot2-header" -rust-version = "1.75" +rust-version = "1.70" [[example]] name = "minimal" diff --git a/multiboot2-header/Changelog.md b/multiboot2-header/Changelog.md index df9f05e8..e37f1b34 100644 --- a/multiboot2-header/Changelog.md +++ b/multiboot2-header/Changelog.md @@ -3,8 +3,6 @@ ## Unreleased - **Breaking** All functions that returns something useful are now `#[must_use]` -- updated dependencies -- documentation enhancements ## 0.4.0 (2024-05-01) diff --git a/multiboot2-header/README.md b/multiboot2-header/README.md index 577a75a8..eebac8d0 100644 --- a/multiboot2-header/README.md +++ b/multiboot2-header/README.md @@ -77,7 +77,7 @@ bytes of the ELF. See Multiboot2 specification. ## MSRV -The MSRV is 1.75.0 stable. +The MSRV is 1.70.0 stable. ## License & Contribution diff --git a/multiboot2-header/src/builder/header.rs b/multiboot2-header/src/builder/header.rs index ff6ba7bb..2bf3ea93 100644 --- a/multiboot2-header/src/builder/header.rs +++ b/multiboot2-header/src/builder/header.rs @@ -255,6 +255,7 @@ impl HeaderBuilder { /// 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, diff --git a/multiboot2-header/src/lib.rs b/multiboot2-header/src/lib.rs index 578511e8..06b6b726 100644 --- a/multiboot2-header/src/lib.rs +++ b/multiboot2-header/src/lib.rs @@ -34,7 +34,7 @@ //! //! ## MSRV //! -//! The MSRV is 1.75.0 stable. +//! The MSRV is 1.70.0 stable. #![no_std] #![cfg_attr(feature = "unstable", feature(error_in_core))] diff --git a/multiboot2/Cargo.toml b/multiboot2/Cargo.toml index 4a7732de..1fd7de2b 100644 --- a/multiboot2/Cargo.toml +++ b/multiboot2/Cargo.toml @@ -31,7 +31,7 @@ readme = "README.md" homepage = "https://github.com/rust-osdev/multiboot2" repository = "https://github.com/rust-osdev/multiboot2" documentation = "https://docs.rs/multiboot2" -rust-version = "1.75" +rust-version = "1.70" [features] default = ["builder"] @@ -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 ecb0b7a4..2e3968c3 100644 --- a/multiboot2/Changelog.md +++ b/multiboot2/Changelog.md @@ -2,14 +2,38 @@ ## Unreleased -- **Breaking** All functions that returns something useful are now `#[must_use]` -- **Breaking** More public fields in tags were replaced by public getters, such +- + +## 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 provide external custom tags, you +should be fine. + +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()` -- updated dependencies -- MSRV is 1.75 +- **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 -- Introduced new `TagHeader` type as replacement for the `Tag` type that will - be changed in the next step. +- updated dependencies ## 0.20.2 (2024-05-26) diff --git a/multiboot2/README.md b/multiboot2/README.md index 52a207e5..4bdd1893 100644 --- a/multiboot2/README.md +++ b/multiboot2/README.md @@ -45,7 +45,7 @@ tag_, which is a tag of type `0` and size `8`. ## MSRV -The MSRV is 1.75.0 stable. +The MSRV is 1.70.0 stable. ## License & Contribution diff --git a/multiboot2/src/boot_information.rs b/multiboot2/src/boot_information.rs index 550842db..8ed11b6b 100644 --- a/multiboot2/src/boot_information.rs +++ b/multiboot2/src/boot_information.rs @@ -21,15 +21,15 @@ use derive_more::Display; pub enum MbiLoadError { /// The address is invalid. Make sure that the address is 8-byte aligned, /// according to the spec. - #[display("The address is invalid")] + #[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("The size of the MBI is unexpected")] + #[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("There is no end tag")] + #[display(fmt = "There is no end tag")] NoEndTag, } @@ -38,7 +38,7 @@ 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)] +#[repr(C, align(8))] pub struct BootInformationHeader { // size is multiple of 8 total_size: u32, @@ -68,7 +68,7 @@ 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)] +#[repr(C, align(8))] struct BootInformationInner { header: BootInformationHeader, tags: [u8], @@ -221,6 +221,8 @@ impl<'a> BootInformation<'a> { /// 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 @@ -277,8 +279,8 @@ impl<'a> BootInformation<'a> { pub fn elf_sections(&self) -> Option { let tag = self.get_tag::(); tag.map(|t| { - assert!((t.entry_size * t.shndx) <= t.size); - t.sections() + assert!((t.entry_size() * t.shndx()) <= t.size() as u32); + t.sections_iter() }) } @@ -359,7 +361,7 @@ impl<'a> BootInformation<'a> { /// special handling is required. This is reflected by code-comments. /// /// ```no_run - /// use multiboot2::{BootInformation, BootInformationHeader, StringError, Tag, TagTrait, TagType, TagTypeId}; + /// use multiboot2::{BootInformation, BootInformationHeader, parse_slice_as_string, StringError, TagHeader, TagTrait, TagType, TagTypeId}; /// /// #[repr(C)] /// #[derive(multiboot2::Pointee)] // Only needed for DSTs. @@ -374,17 +376,17 @@ impl<'a> BootInformation<'a> { /// 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 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 + /// assert!(header.size >= 8); + /// header.size as usize - tag_base_size /// } /// } /// /// impl CustomTag { /// fn name(&self) -> Result<&str, StringError> { - /// Tag::parse_slice_as_string(&self.name) + /// parse_slice_as_string(&self.name) /// } /// } /// let mbi_ptr = 0xdeadbeef as *const BootInformationHeader; @@ -395,11 +397,13 @@ impl<'a> BootInformation<'a> { /// .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.typ == TagT::ID) - .map(|tag| tag.cast_tag::()) + .find(|tag| tag.header().typ == TagT::ID) + .map(|tag| tag.cast::()) } /// Returns an iterator over all tags. @@ -440,7 +444,7 @@ impl fmt::Debug for BootInformation<'_> { 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("elf_sections", &self.elf_sections()); } } diff --git a/multiboot2/src/boot_loader_name.rs b/multiboot2/src/boot_loader_name.rs index e6817d26..60a2cc74 100644 --- a/multiboot2/src/boot_loader_name.rs +++ b/multiboot2/src/boot_loader_name.rs @@ -1,17 +1,17 @@ //! Module for [`BootLoaderNameTag`]. -use crate::tag::{StringError, TagHeader}; -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; #[cfg(feature = "builder")] -use {crate::builder::BoxedDst, alloc::vec::Vec}; +use {crate::new_boxed, alloc::boxed::Box}; -const METADATA_SIZE: usize = mem::size_of::() + mem::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 { header: TagHeader, /// Null-terminated UTF-8 string @@ -22,13 +22,13 @@ impl BootLoaderNameTag { /// Constructs a new tag. #[cfg(feature = "builder")] #[must_use] - pub fn new(name: &str) -> BoxedDst { - let mut bytes: Vec<_> = name.bytes().collect(); - if !bytes.ends_with(&[0]) { - // terminating null-byte - bytes.push(0); + 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`]. @@ -61,7 +61,7 @@ impl BootLoaderNameTag { /// } /// ``` pub fn name(&self) -> Result<&str, StringError> { - Tag::parse_slice_as_string(&self.name) + parse_slice_as_string(&self.name) } } @@ -78,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::(); + 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().expect("must be valid UTF-8"), MSG); + 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()); - assert_eq!(tag.name(), Ok(MSG)); + 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()[..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 f433104e..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() { - const 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 91b70eff..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}; @@ -78,12 +80,6 @@ impl InformationBuilder { 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 @@ -92,10 +88,8 @@ impl InformationBuilder { 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::() } @@ -112,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)); @@ -122,8 +116,6 @@ 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 @@ -141,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)); // ----------------------------------------------- @@ -189,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 { @@ -206,32 +200,32 @@ impl InformationBuilder { /// Adds a 'basic memory information' tag (represented by [`BasicMemoryInfoTag`]) to the builder. #[must_use] - pub fn basic_memory_info_tag(self, tag: BasicMemoryInfoTag) -> Self { - self.add_tag(&tag).unwrap() + pub fn basic_memory_info_tag(self, tag: &BasicMemoryInfoTag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'bootloader name' tag (represented by [`BootLoaderNameTag`]) to the builder. #[must_use] - pub fn bootloader_name_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + pub fn bootloader_name_tag(self, tag: &BootLoaderNameTag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'command line' tag (represented by [`CommandLineTag`]) to the builder. #[must_use] - pub fn command_line_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + pub fn command_line_tag(self, tag: &CommandLineTag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'EFI 32-bit system table pointer' tag (represented by [`EFISdt32Tag`]) to the builder. #[must_use] - pub fn efisdt32_tag(self, tag: EFISdt32Tag) -> Self { - self.add_tag(&tag).unwrap() + pub fn efisdt32_tag(self, tag: &EFISdt32Tag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'EFI 64-bit system table pointer' tag (represented by [`EFISdt64Tag`]) to the builder. #[must_use] - pub fn efisdt64_tag(self, tag: EFISdt64Tag) -> Self { - self.add_tag(&tag).unwrap() + 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. @@ -242,71 +236,78 @@ impl InformationBuilder { /// Adds a 'EFI 32-bit image handle pointer' tag (represented by [`EFIImageHandle32Tag`]) to the builder. #[must_use] - pub fn efi_image_handle32(self, tag: EFIImageHandle32Tag) -> Self { - self.add_tag(&tag).unwrap() + pub fn efi_image_handle32(self, tag: &EFIImageHandle32Tag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'EFI 64-bit image handle pointer' tag (represented by [`EFIImageHandle64Tag`]) to the builder. #[must_use] - pub fn efi_image_handle64(self, tag: EFIImageHandle64Tag) -> Self { - self.add_tag(&tag).unwrap() + pub fn efi_image_handle64(self, tag: &EFIImageHandle64Tag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'EFI Memory map' tag (represented by [`EFIMemoryMapTag`]) to the builder. #[must_use] - pub fn efi_memory_map_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + pub fn efi_memory_map_tag(self, tag: &EFIMemoryMapTag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'ELF-Symbols' tag (represented by [`ElfSectionsTag`]) to the builder. #[must_use] - pub fn elf_sections_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + pub fn elf_sections_tag(self, tag: &ElfSectionsTag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'Framebuffer info' tag (represented by [`FramebufferTag`]) to the builder. #[must_use] - pub fn framebuffer_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + pub fn framebuffer_tag(self, tag: &FramebufferTag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'Image load base physical address' tag (represented by [`ImageLoadPhysAddrTag`]) to the builder. #[must_use] - pub fn image_load_addr(self, tag: ImageLoadPhysAddrTag) -> Self { - self.add_tag(&tag).unwrap() + pub fn image_load_addr(self, tag: &ImageLoadPhysAddrTag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a (*none EFI*) 'memory map' tag (represented by [`MemoryMapTag`]) to the builder. #[must_use] - pub fn memory_map_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + pub fn memory_map_tag(self, tag: &MemoryMapTag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'Modules' tag (represented by [`ModuleTag`]) to the builder. /// This tag can occur multiple times in boot information. #[must_use] - pub fn add_module_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + pub fn add_module_tag(self, tag: &ModuleTag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'ACPI old RSDP' tag (represented by [`RsdpV1Tag`]) to the builder. #[must_use] - pub fn rsdp_v1_tag(self, tag: RsdpV1Tag) -> Self { - self.add_tag(&tag).unwrap() + pub fn rsdp_v1_tag(self, tag: &RsdpV1Tag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'ACPI new RSDP' tag (represented by [`RsdpV2Tag`]) to the builder. #[must_use] - pub fn rsdp_v2_tag(self, tag: RsdpV2Tag) -> Self { - self.add_tag(&tag).unwrap() + pub fn rsdp_v2_tag(self, tag: &RsdpV2Tag) -> Self { + self.add_tag(tag).unwrap() } /// Adds a 'SMBIOS tables' tag (represented by [`SmbiosTag`]) to the builder. #[must_use] - pub fn smbios_tag(self, tag: BoxedDst) -> Self { - self.add_tag(&*tag).unwrap() + pub fn smbios_tag(self, tag: &SmbiosTag) -> Self { + self.add_tag(tag).unwrap() + } + + /// Adds a 'VBE Info' tag (represented by [`VBEInfoTag`]) to the builder. + #[must_use] + pub fn vbe_info_tag(self, tag: &VBEInfoTag) -> Self { + self.add_tag(tag).unwrap() } + #[must_use] const fn tag_is_allowed_multiple_times(tag_type: TagType) -> bool { matches!( tag_type, @@ -328,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); @@ -349,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] @@ -367,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 7d06c425..4fd00932 100644 --- a/multiboot2/src/builder/mod.rs +++ b/multiboot2/src/builder/mod.rs @@ -1,10 +1,7 @@ //! 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 diff --git a/multiboot2/src/command_line.rs b/multiboot2/src/command_line.rs index b48eeddb..cd387f25 100644 --- a/multiboot2/src/command_line.rs +++ b/multiboot2/src/command_line.rs @@ -1,21 +1,21 @@ //! Module for [`CommandLineTag`]. -use crate::tag::{StringError, TagHeader}; -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; use core::str; #[cfg(feature = "builder")] -use {crate::builder::BoxedDst, alloc::vec::Vec}; +use {crate::new_boxed, alloc::boxed::Box}; -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 { header: TagHeader, /// Null-terminated UTF-8 string @@ -26,13 +26,13 @@ impl CommandLineTag { /// Create a new command line tag from the given string. #[cfg(feature = "builder")] #[must_use] - 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); + 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 @@ -55,7 +55,7 @@ impl CommandLineTag { /// } /// ``` pub fn cmdline(&self) -> Result<&str, StringError> { - Tag::parse_slice_as_string(&self.cmdline) + parse_slice_as_string(&self.cmdline) } } @@ -72,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::(); + 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().expect("must be valid UTF-8"), MSG); + 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 863b8535..4c8d727a 100644 --- a/multiboot2/src/efi.rs +++ b/multiboot2/src/efi.rs @@ -7,12 +7,12 @@ //! - [`EFIBootServicesNotExitedTag`] use crate::tag::TagHeader; -use crate::{Tag, TagTrait, TagType}; +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 { header: TagHeader, pointer: u32, @@ -38,12 +38,12 @@ 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 { header: TagHeader, pointer: u64, @@ -69,13 +69,13 @@ 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 { header: TagHeader, pointer: u32, @@ -102,13 +102,13 @@ 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 { header: TagHeader, pointer: u64, @@ -135,12 +135,13 @@ 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 { header: TagHeader, } @@ -166,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 849b08e5..decbe734 100644 --- a/multiboot2/src/elf_sections.rs +++ b/multiboot2/src/elf_sections.rs @@ -1,25 +1,25 @@ //! 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::marker::PhantomData; use core::mem; use core::str::Utf8Error; +#[cfg(feature = "builder")] +use {crate::new_boxed, alloc::boxed::Box}; -const METADATA_SIZE: usize = mem::size_of::() + 4 * mem::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], } @@ -27,80 +27,89 @@ impl ElfSectionsTag { /// Create a new ElfSectionsTag with the given data. #[cfg(feature = "builder")] #[must_use] - 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) + 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) const 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, } } - const 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) }; @@ -114,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| { @@ -124,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)] @@ -173,7 +172,7 @@ 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 { diff --git a/multiboot2/src/end.rs b/multiboot2/src/end.rs index e48b2238..746ba271 100644 --- a/multiboot2/src/end.rs +++ b/multiboot2/src/end.rs @@ -1,10 +1,10 @@ //! 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 { typ: TagTypeId, size: u32, @@ -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 50167b88..d1370ee8 100644 --- a/multiboot2/src/framebuffer.rs +++ b/multiboot2/src/framebuffer.rs @@ -1,13 +1,13 @@ //! Module for [`FramebufferTag`]. use crate::tag::TagHeader; -use crate::{Tag, TagTrait, TagType, TagTypeId}; +use crate::{TagTrait, TagType, TagTypeId}; use core::fmt::Debug; 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. @@ -50,7 +50,7 @@ const METADATA_SIZE: usize = mem::size_of::() /// The VBE Framebuffer information tag. #[derive(ptr_meta::Pointee, Eq)] -#[repr(C)] +#[repr(C, align(8))] pub struct FramebufferTag { header: TagHeader, @@ -94,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. @@ -145,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, @@ -183,9 +184,9 @@ 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 } } @@ -274,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 @@ -330,7 +331,7 @@ impl AsBytes for FramebufferColor {} /// Error when an unknown [`FramebufferTypeId`] is found. #[derive(Debug, Copy, Clone, Display, PartialEq, Eq)] -#[display("Unknown framebuffer type {}", _0)] +#[display(fmt = "Unknown framebuffer type {}", _0)] pub struct UnknownFramebufferType(u8); #[cfg(feature = "unstable")] diff --git a/multiboot2/src/image_load_addr.rs b/multiboot2/src/image_load_addr.rs index b11127d4..7248d782 100644 --- a/multiboot2/src/image_load_addr.rs +++ b/multiboot2/src/image_load_addr.rs @@ -1,7 +1,7 @@ //! Module for [`ImageLoadPhysAddrTag`]. use crate::tag::TagHeader; -use crate::{Tag, TagTrait, TagType}; +use crate::{TagTrait, TagType}; #[cfg(feature = "builder")] use core::mem::size_of; @@ -9,7 +9,7 @@ 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 { header: TagHeader, load_base_addr: u32, @@ -36,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 46a41d5e..f43f9cfe 100644 --- a/multiboot2/src/lib.rs +++ b/multiboot2/src/lib.rs @@ -41,7 +41,7 @@ //! ``` //! //! ## MSRV -//! The MSRV is 1.75.0 stable. +//! The MSRV is 1.70.0 stable. #[cfg(feature = "builder")] extern crate alloc; @@ -56,6 +56,8 @@ extern crate bitflags; #[cfg(feature = "builder")] pub mod builder; +#[cfg(test)] +pub(crate) mod test_util; mod boot_information; mod boot_loader_name; @@ -72,6 +74,7 @@ mod smbios; mod tag; mod tag_trait; mod tag_type; +pub(crate) mod util; mod vbe_info; pub use boot_information::{BootInformation, BootInformationHeader, MbiLoadError}; @@ -94,9 +97,12 @@ 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, @@ -107,22 +113,22 @@ pub use vbe_info::{ /// machine state. pub const MAGIC: u32 = 0x36d76289; +/// The required alignment for tags and the boot information. +pub const ALIGNMENT: usize = 8; + #[cfg(test)] mod tests { use super::*; - use crate::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 @@ -138,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 @@ -163,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 @@ -188,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 @@ -211,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 @@ -246,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 @@ -312,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 @@ -379,13 +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. @@ -460,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] @@ -551,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 @@ -959,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 @@ -1036,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 @@ -1076,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 @@ -1101,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()); @@ -1117,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 { @@ -1129,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, @@ -1183,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)] @@ -1195,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, @@ -1261,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 b246de56..c7663d72 100644 --- a/multiboot2/src/memory_map.rs +++ b/multiboot2/src/memory_map.rs @@ -6,14 +6,14 @@ pub use uefi_raw::table::boot::MemoryDescriptor as EFIMemoryDesc; pub use uefi_raw::table::boot::MemoryType as EFIMemoryAreaType; use crate::tag::TagHeader; -use crate::{Tag, TagTrait, TagType, TagTypeId}; +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). /// @@ -26,7 +26,7 @@ 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()) + 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. @@ -75,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::() } @@ -139,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)] @@ -287,14 +285,11 @@ 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)] @@ -322,54 +317,30 @@ pub struct EFIMemoryMapTag { impl EFIMemoryMapTag { /// 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. #[cfg(feature = "builder")] #[must_use] - 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)); - } + 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, ) } /// Create a new EFI memory map tag from the given EFI memory map. #[cfg(feature = "builder")] #[must_use] - 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) + 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. @@ -408,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 } } @@ -466,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; @@ -491,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 5dd65047..c77eb7e7 100644 --- a/multiboot2/src/module.rs +++ b/multiboot2/src/module.rs @@ -1,19 +1,19 @@ //! Module for [`ModuleTag`]. -use crate::tag::{StringError, TagHeader, TagIter}; -use crate::{Tag, 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; #[cfg(feature = "builder")] -use {crate::builder::BoxedDst, alloc::vec::Vec}; +use {crate::new_boxed, alloc::boxed::Box}; -const METADATA_SIZE: usize = mem::size_of::() + 3 * mem::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 { header: TagHeader, mod_start: u32, @@ -26,19 +26,18 @@ impl ModuleTag { /// Constructs a new tag. #[cfg(feature = "builder")] #[must_use] - pub fn new(start: u32, end: u32, cmdline: &str) -> BoxedDst { + 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 @@ -51,7 +50,7 @@ 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. @@ -76,9 +75,9 @@ 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 } } @@ -111,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()) } } @@ -128,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::(); + 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().expect("must be valid UTF-8"), MSG); + 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 8a4c289b..7653ad61 100644 --- a/multiboot2/src/rsdp.rs +++ b/multiboot2/src/rsdp.rs @@ -13,7 +13,7 @@ //! use crate::tag::TagHeader; -use crate::{Tag, TagTrait, TagType}; +use crate::{TagTrait, TagType}; #[cfg(feature = "builder")] use core::mem::size_of; use core::slice; @@ -24,7 +24,7 @@ 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 { header: TagHeader, signature: [u8; 8], @@ -94,12 +94,12 @@ 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 { header: TagHeader, signature: [u8; 8], @@ -191,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 e66b732e..abd0e842 100644 --- a/multiboot2/src/smbios.rs +++ b/multiboot2/src/smbios.rs @@ -1,18 +1,17 @@ //! Module for [`SmbiosTag`]. -#[cfg(feature = "builder")] -use crate::builder::BoxedDst; use crate::tag::TagHeader; -use crate::{Tag, TagTrait, TagType, TagTypeId}; +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 = - mem::size_of::() + mem::size_of::() + 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 { header: TagHeader, major: u8, @@ -25,10 +24,9 @@ impl SmbiosTag { /// Constructs a new tag. #[cfg(feature = "builder")] #[must_use] - 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) + 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. @@ -53,9 +51,9 @@ impl SmbiosTag { 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 } } @@ -73,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::(); + 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, 3); - assert_eq!(tag.minor, 0); - assert_eq!(tag.tables, [0xabu8; 24]); + 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 7764fc57..3943456d 100644 --- a/multiboot2/src/tag.rs +++ b/multiboot2/src/tag.rs @@ -1,45 +1,29 @@ //! 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) - } -} - -#[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), - } - } -} +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)] +#[repr(C, align(8))] // Alignment also propagates to all tag types using this. pub struct TagHeader { /// The ABI-compatible [`TagType`]. pub typ: TagTypeId, /* u32 */ @@ -58,141 +42,401 @@ impl TagHeader { } } -/// 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)] -#[allow(missing_docs)] // type will be removed soon anyway in its form -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. - #[must_use] - 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. - #[must_use] - 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<&'a Tag> { - // This never failed so far. But better be safe. - assert!(self.current.cast::() < self.end_ptr_exclusive); + fn next(&mut self) -> Option { + let next_ptr = unsafe { self.buffer.as_ptr().add(self.next_tag_offset) }; - 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. + if next_ptr == self.exclusive_end { + return None; + } + assert!(next_ptr < self.exclusive_end); - // next pointer (rounded up to 8-byte alignment) - let ptr_offset = (tag.size as usize + 7) & !7; + let next_tag_ptr = next_ptr.cast::(); - // go to next tag - self.current = unsafe { self.current.cast::().add(ptr_offset).cast::() }; + let tag_hdr = unsafe { &*next_tag_ptr }; - Some(tag) - } - } + // Get relevant byte portion for the next tag. This includes padding + // bytes to fulfill Rust memory guarantees. Otherwise, Miri complains. + // See . + let bytes = { + let from = self.next_tag_offset; + let to = from + tag_hdr.size as usize; + // The size of [the allocation for] a value is always a multiple of its + // alignment. + // https://doc.rust-lang.org/reference/type-layout.html + let to = increase_to_alignment(to); + + // Update ptr for next iteration. + self.next_tag_offset += to - from; + + &self.buffer[from..to] + }; + + // Should never fail at this point. + let tag_bytes = TagBytesRef::try_from(bytes).unwrap(); + + Some(GenericTag::ref_from(tag_bytes)) } } #[cfg(test)] mod tests { use super::*; + use crate::test_util::AlignedBytes; + use core::borrow::Borrow; + use core::mem; + + #[test] + fn test_new_generic_tag() { + let bytes = AlignedBytes::new([ + /* id: 0xffff_ffff */ + 0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */ + 16, 0, 0, 0, /* field a: 0xdead_beef */ + 0xde, 0xad, 0xbe, 0xef, /* field b: 0x1337_1337 */ + 0x13, 0x37, 0x13, 0x37, + ]); + + let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); + let tag = GenericTag::ref_from(bytes); + assert_eq!(tag.header.typ, 0xffff_ffff); + assert_eq!(tag.header.size, 16); + assert_eq!(tag.payload.len(), 8); + } + + #[test] + fn test_cast_generic_tag_to_sized_tag() { + #[repr(C)] + struct CustomTag { + tag_header: TagHeader, + a: u32, + b: u32, + } + + impl TagTrait for CustomTag { + const ID: TagType = TagType::End; + + fn dst_len(_header: &TagHeader) -> Self::Metadata {} + } + + let bytes = AlignedBytes([ + /* id: 0xffff_ffff */ + 0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */ + 16, 0, 0, 0, /* field a: 0xdead_beef */ + 0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */ + 0x37, 0x13, 0x37, 0x13, + ]); + let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap(); + let tag = GenericTag::ref_from(bytes); + let custom_tag = tag.cast::(); + + assert_eq!(mem::size_of_val(custom_tag), 16); + assert_eq!(custom_tag.a, 0xdead_beef); + assert_eq!(custom_tag.b, 0x1337_1337); + } + + #[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 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_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 c13b63d1..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,18 +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. - #[must_use] - 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 9c47c089..f7ae50df 100644 --- a/multiboot2/src/tag_type.rs +++ b/multiboot2/src/tag_type.rs @@ -7,10 +7,10 @@ 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); 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.