From a30ecd2a5240d1658ba62f1f5a975ab5870c44ee Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 19 Aug 2026 07:57:15 +0200 Subject: [PATCH 1/4] Revert "uefi-raw: fix MemoryDescriptor layout on 32-bit targets" This reverts commit 6424087aa5e886f70c2533680b1cb6271128a46d. --- uefi-raw/CHANGELOG.md | 2 -- uefi-raw/src/table/boot.rs | 3 --- uefi/src/mem/memory_map/impl_.rs | 3 --- uefi/src/mem/memory_map/mod.rs | 12 ------------ uefi/tests/memory_map.rs | 3 --- 5 files changed, 23 deletions(-) diff --git a/uefi-raw/CHANGELOG.md b/uefi-raw/CHANGELOG.md index 37b2eae0..b4000bd5 100644 --- a/uefi-raw/CHANGELOG.md +++ b/uefi-raw/CHANGELOG.md @@ -3,8 +3,6 @@ ## Added ## Changed -- **Breaking**: `MemoryDescriptor` now has a new member to ensure correct - padding on 32-bit targets. ## Removed diff --git a/uefi-raw/src/table/boot.rs b/uefi-raw/src/table/boot.rs index 08c2e8fe..dd1a3d8f 100644 --- a/uefi-raw/src/table/boot.rs +++ b/uefi-raw/src/table/boot.rs @@ -364,8 +364,6 @@ bitflags! { pub struct MemoryDescriptor { /// Type of memory occupying this range. pub ty: MemoryType, - /// Reserved field that must be set to 0. - pub reserved: u32, // Implicit 32-bit padding. /// Starting physical address. pub phys_start: PhysicalAddress, @@ -386,7 +384,6 @@ impl Default for MemoryDescriptor { fn default() -> Self { Self { ty: MemoryType::RESERVED, - reserved: 0, phys_start: 0, virt_start: 0, page_count: 0, diff --git a/uefi/src/mem/memory_map/impl_.rs b/uefi/src/mem/memory_map/impl_.rs index 671ac96f..74cfb197 100644 --- a/uefi/src/mem/memory_map/impl_.rs +++ b/uefi/src/mem/memory_map/impl_.rs @@ -471,7 +471,6 @@ mod tests { const BASE_MMAP_UNSORTED: [MemoryDescriptor; 3] = [ MemoryDescriptor { ty: MemoryType::CONVENTIONAL, - reserved: 0, phys_start: 0x3000, virt_start: 0x3000, page_count: 1, @@ -479,7 +478,6 @@ mod tests { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, - reserved: 0, phys_start: 0x2000, virt_start: 0x2000, page_count: 1, @@ -487,7 +485,6 @@ mod tests { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, - reserved: 0, phys_start: 0x1000, virt_start: 0x1000, page_count: 1, diff --git a/uefi/src/mem/memory_map/mod.rs b/uefi/src/mem/memory_map/mod.rs index 837cec05..0539ac6a 100644 --- a/uefi/src/mem/memory_map/mod.rs +++ b/uefi/src/mem/memory_map/mod.rs @@ -133,7 +133,6 @@ mod tests_mmap_artificial { const BASE: MemoryDescriptor = MemoryDescriptor { ty: TY, - reserved: 0, phys_start: 0, virt_start: 0, page_count: 0, @@ -171,7 +170,6 @@ mod tests_mmap_artificial { const TY: MemoryType = MemoryType::RESERVED; const BASE: MemoryDescriptor = MemoryDescriptor { - reserved: 0, ty: TY, phys_start: 0, virt_start: 0, @@ -276,7 +274,6 @@ mod tests_mmap_real { let expected = [ MemoryDescriptor { ty: MemoryType::BOOT_SERVICES_CODE, - reserved: 0, phys_start: 0x0, virt_start: 0x0, page_count: 0x1, @@ -287,7 +284,6 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, - reserved: 0, phys_start: 0x1000, virt_start: 0x0, page_count: 0x86, @@ -298,7 +294,6 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::BOOT_SERVICES_DATA, - reserved: 0, phys_start: 0x87000, virt_start: 0x0, page_count: 0x1, @@ -309,7 +304,6 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, - reserved: 0, phys_start: 0x88000, virt_start: 0x0, page_count: 0x18, @@ -320,7 +314,6 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, - reserved: 0, phys_start: 0x100000, virt_start: 0x0, page_count: 0x700, @@ -331,7 +324,6 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::ACPI_NON_VOLATILE, - reserved: 0, phys_start: 0x800000, virt_start: 0x0, page_count: 0x8, @@ -342,7 +334,6 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, - reserved: 0, phys_start: 0x808000, virt_start: 0x0, page_count: 0x3, @@ -353,7 +344,6 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::ACPI_NON_VOLATILE, - reserved: 0, phys_start: 0x80b000, virt_start: 0x0, page_count: 0x1, @@ -364,7 +354,6 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, - reserved: 0, phys_start: 0x80c000, virt_start: 0x0, page_count: 0x4, @@ -375,7 +364,6 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::ACPI_NON_VOLATILE, - reserved: 0, phys_start: 0x810000, virt_start: 0x0, page_count: 0xf0, diff --git a/uefi/tests/memory_map.rs b/uefi/tests/memory_map.rs index 35368d74..ea510f60 100644 --- a/uefi/tests/memory_map.rs +++ b/uefi/tests/memory_map.rs @@ -10,7 +10,6 @@ fn parse_boot_information_efi_mmap() { let mut mmap_source = [ MemoryDescriptor { ty: MemoryType::CONVENTIONAL, - reserved: 0, phys_start: 0x3000, virt_start: 0x3000, page_count: 1, @@ -18,7 +17,6 @@ fn parse_boot_information_efi_mmap() { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, - reserved: 0, phys_start: 0x2000, virt_start: 0x2000, page_count: 1, @@ -26,7 +24,6 @@ fn parse_boot_information_efi_mmap() { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, - reserved: 0, phys_start: 0x1000, virt_start: 0x1000, page_count: 1, From e72d1269c92c04bd5ec45d4254f4186f144f8bd9 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 19 Aug 2026 07:57:19 +0200 Subject: [PATCH 2/4] uefi-raw: make MemoryDescriptor layout portable across x86 targets TL;DR: Add an explicit 32-bit padding field after `MemoryDescriptor::ty` so UEFI memory maps can be parsed correctly from non-UEFI targets such as kernels and bootloaders compiled for generic i686 targets. UEFI's `EFI_MEMORY_DESCRIPTOR` places `PhysicalStart` at offset 8. This happens implicitly on x86_64 and also on IA32 UEFI, where 64-bit values are 8-byte aligned. EDK II enforces this for GCC IA32 builds with `-malign-double` [0], and Rust's `i686-unknown-uefi` target explicitly uses `i64:64` in its LLVM data layout [1]. This differs from the standard x86-32 ABI. GCC normally aligns `long long` to one word on x86-32 and documents `-malign-double` as an ABI-changing option [2]. Rust/LLVM follows the standard x86-32 layout for targets such as `i686-unknown-linux-gnu`, where `u64` has 4-byte ABI alignment [3]. Consequently, `#[repr(C)]` alone places `phys_start` at offset 4 instead of the UEFI-required offset 8. This matters outside UEFI applications. Kernels and bootloaders may receive and parse a UEFI memory map while being compiled for a generic target such as a bare-metal i686 target. For example, the multiboot2 crate re-exports this type as `EFIMemoryDesc` and uses it to parse EFI memory-map data [4]. The type therefore needs to describe the UEFI binary layout independently of the consumer's target ABI. Reproducer without the explicit padding: ```rust #[repr(C)] pub struct MemoryDescriptor { pub ty: u32, pub phys_start: u64, pub virt_start: u64, pub page_count: u64, pub att: u64, } const _: () = { assert!(core::mem::offset_of!(MemoryDescriptor, phys_start) == 8); }; ``` ```console # works cargo check --target x86_64-unknown-linux-gnu # fails: phys_start is at offset 4 cargo check --target i686-unknown-linux-gnu # both UEFI targets already have the required layout cargo check --target x86_64-unknown-uefi cargo check --target i686-unknown-uefi ``` The explicit padding makes the UEFI-defined field offsets independent of whether the compilation target itself uses the UEFI ABI. [0]: https://github.com/tianocore/edk2/blob/2970e5699ba6267f3384ffab20f96647578aebc8/BaseTools/Conf/tools_def.template#L620 [1]: https://github.com/rust-lang/rust/blob/9ba81c26fe1be0c8730083a1b6da070bc7c51726/compiler/rustc_target/src/spec/targets/i686_unknown_uefi.rs#L96 [2]: https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html [3]: https://github.com/rust-lang/rust/blob/9ba81c26fe1be0c8730083a1b6da070bc7c51726/compiler/rustc_target/src/spec/targets/i686_unknown_linux_gnu.rs#L36 [4]: https://github.com/rust-osdev/multiboot2/blob/5ccb9629df25d4373dd0efd9a50d0431c477a61f/multiboot2/src/memory_map.rs#L5 --- uefi-raw/CHANGELOG.md | 2 ++ uefi-raw/src/table/boot.rs | 21 ++++++++++++++++++++- uefi/src/mem/memory_map/impl_.rs | 3 +++ uefi/src/mem/memory_map/mod.rs | 12 ++++++++++++ uefi/tests/memory_map.rs | 3 +++ 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/uefi-raw/CHANGELOG.md b/uefi-raw/CHANGELOG.md index b4000bd5..3b656954 100644 --- a/uefi-raw/CHANGELOG.md +++ b/uefi-raw/CHANGELOG.md @@ -3,6 +3,8 @@ ## Added ## Changed +- **Breaking**: `MemoryDescriptor` now has a new member to ensure correct + layout on all non-UEFI 32-bit targets. ## Removed diff --git a/uefi-raw/src/table/boot.rs b/uefi-raw/src/table/boot.rs index dd1a3d8f..f46e5f88 100644 --- a/uefi-raw/src/table/boot.rs +++ b/uefi-raw/src/table/boot.rs @@ -9,6 +9,7 @@ use crate::{ }; use bitflags::bitflags; use core::ffi::c_void; +use core::mem::offset_of; use core::ops::RangeInclusive; newtype_enum! { @@ -360,10 +361,15 @@ bitflags! { /// [version]: MemoryDescriptor::VERSION /// [0]: https://github.com/tianocore/edk2/blob/7142e648416ff5d3eac6c6d607874805f5de0ca8/MdeModulePkg/Core/PiSmmCore/Page.c#L1059 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct MemoryDescriptor { /// Type of memory occupying this range. pub ty: MemoryType, + /// Implicit padding that should be set to 0. + /// + /// This field is required for a correct layout on non-UEFI targets to + /// properly parse a memory map. + pub padding: u32, // Implicit 32-bit padding. /// Starting physical address. pub phys_start: PhysicalAddress, @@ -375,6 +381,18 @@ pub struct MemoryDescriptor { pub att: MemoryAttribute, } +// Ensure ABI guarantees for MemoryDescriptor. +const _: () = { + assert!(size_of::() == 40); + assert!(align_of::() == 8); + + assert!(offset_of!(MemoryDescriptor, ty) == 0); + assert!(offset_of!(MemoryDescriptor, phys_start) == 8); + assert!(offset_of!(MemoryDescriptor, virt_start) == 16); + assert!(offset_of!(MemoryDescriptor, page_count) == 24); + assert!(offset_of!(MemoryDescriptor, att) == 32); +}; + impl MemoryDescriptor { /// Memory descriptor version number. pub const VERSION: u32 = 1; @@ -384,6 +402,7 @@ impl Default for MemoryDescriptor { fn default() -> Self { Self { ty: MemoryType::RESERVED, + padding: 0, phys_start: 0, virt_start: 0, page_count: 0, diff --git a/uefi/src/mem/memory_map/impl_.rs b/uefi/src/mem/memory_map/impl_.rs index 74cfb197..5f1ee46e 100644 --- a/uefi/src/mem/memory_map/impl_.rs +++ b/uefi/src/mem/memory_map/impl_.rs @@ -471,6 +471,7 @@ mod tests { const BASE_MMAP_UNSORTED: [MemoryDescriptor; 3] = [ MemoryDescriptor { ty: MemoryType::CONVENTIONAL, + padding: 0, phys_start: 0x3000, virt_start: 0x3000, page_count: 1, @@ -478,6 +479,7 @@ mod tests { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, + padding: 0, phys_start: 0x2000, virt_start: 0x2000, page_count: 1, @@ -485,6 +487,7 @@ mod tests { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, + padding: 0, phys_start: 0x1000, virt_start: 0x1000, page_count: 1, diff --git a/uefi/src/mem/memory_map/mod.rs b/uefi/src/mem/memory_map/mod.rs index 0539ac6a..14987ac6 100644 --- a/uefi/src/mem/memory_map/mod.rs +++ b/uefi/src/mem/memory_map/mod.rs @@ -133,6 +133,7 @@ mod tests_mmap_artificial { const BASE: MemoryDescriptor = MemoryDescriptor { ty: TY, + padding: 0, phys_start: 0, virt_start: 0, page_count: 0, @@ -170,6 +171,7 @@ mod tests_mmap_artificial { const TY: MemoryType = MemoryType::RESERVED; const BASE: MemoryDescriptor = MemoryDescriptor { + padding: 0, ty: TY, phys_start: 0, virt_start: 0, @@ -274,6 +276,7 @@ mod tests_mmap_real { let expected = [ MemoryDescriptor { ty: MemoryType::BOOT_SERVICES_CODE, + padding: 0, phys_start: 0x0, virt_start: 0x0, page_count: 0x1, @@ -284,6 +287,7 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, + padding: 0, phys_start: 0x1000, virt_start: 0x0, page_count: 0x86, @@ -294,6 +298,7 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::BOOT_SERVICES_DATA, + padding: 0, phys_start: 0x87000, virt_start: 0x0, page_count: 0x1, @@ -304,6 +309,7 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, + padding: 0, phys_start: 0x88000, virt_start: 0x0, page_count: 0x18, @@ -314,6 +320,7 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, + padding: 0, phys_start: 0x100000, virt_start: 0x0, page_count: 0x700, @@ -324,6 +331,7 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::ACPI_NON_VOLATILE, + padding: 0, phys_start: 0x800000, virt_start: 0x0, page_count: 0x8, @@ -334,6 +342,7 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, + padding: 0, phys_start: 0x808000, virt_start: 0x0, page_count: 0x3, @@ -344,6 +353,7 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::ACPI_NON_VOLATILE, + padding: 0, phys_start: 0x80b000, virt_start: 0x0, page_count: 0x1, @@ -354,6 +364,7 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, + padding: 0, phys_start: 0x80c000, virt_start: 0x0, page_count: 0x4, @@ -364,6 +375,7 @@ mod tests_mmap_real { }, MemoryDescriptor { ty: MemoryType::ACPI_NON_VOLATILE, + padding: 0, phys_start: 0x810000, virt_start: 0x0, page_count: 0xf0, diff --git a/uefi/tests/memory_map.rs b/uefi/tests/memory_map.rs index ea510f60..708b831d 100644 --- a/uefi/tests/memory_map.rs +++ b/uefi/tests/memory_map.rs @@ -10,6 +10,7 @@ fn parse_boot_information_efi_mmap() { let mut mmap_source = [ MemoryDescriptor { ty: MemoryType::CONVENTIONAL, + padding: 0, phys_start: 0x3000, virt_start: 0x3000, page_count: 1, @@ -17,6 +18,7 @@ fn parse_boot_information_efi_mmap() { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, + padding: 0, phys_start: 0x2000, virt_start: 0x2000, page_count: 1, @@ -24,6 +26,7 @@ fn parse_boot_information_efi_mmap() { }, MemoryDescriptor { ty: MemoryType::CONVENTIONAL, + padding: 0, phys_start: 0x1000, virt_start: 0x1000, page_count: 1, From 0cfe38cf30be4f00a68eb3206f91df612fec9726 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 19 Aug 2026 08:35:29 +0200 Subject: [PATCH 3/4] xtask: allow align 8 in check-raw We need this for the layout fix for MemoryDescriptor. --- xtask/src/check_raw.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/xtask/src/check_raw.rs b/xtask/src/check_raw.rs index 6f638ff4..d5c326eb 100644 --- a/xtask/src/check_raw.rs +++ b/xtask/src/check_raw.rs @@ -323,7 +323,12 @@ fn check_fields(fields: &Punctuated, src: &Path) -> Result<(), Err } /// List with allowed combinations of representations (see [`Repr`]). -const ALLOWED_REPRS: &[&[Repr]] = &[&[Repr::C], &[Repr::C, Repr::Packed], &[Repr::Transparent]]; +const ALLOWED_REPRS: &[&[Repr]] = &[ + &[Repr::C], + &[Repr::C, Repr::Packed], + &[Repr::Align(8), Repr::C], + &[Repr::Transparent], +]; fn check_type_attrs(attrs: &[Attribute], spanned: &dyn Spanned, src: &Path) -> Result<(), Error> { let attrs = parse_attrs(attrs, src)?; @@ -606,6 +611,20 @@ mod tests { .is_ok() ); + // Valid `repr(C, align(8))` struct. + assert!( + check_struct( + &parse_quote! { + #[repr(C, align(8))] + pub struct S { + pub f: u32, + } + }, + src(), + ) + .is_ok() + ); + // Missing `pub` on struct. check_item_err( parse_quote! { From 049cb57a09f79f6ef9b57d4ab9b3bc18585f1c09 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 19 Aug 2026 17:34:16 +0200 Subject: [PATCH 4/4] xtask: allow anonymous unit constant Helpful for checks such as: ```rust const _: () = { assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(MemoryDescriptor, ty) == 0); assert!(offset_of!(MemoryDescriptor, phys_start) == 8); assert!(offset_of!(MemoryDescriptor, virt_start) == 16); assert!(offset_of!(MemoryDescriptor, page_count) == 24); assert!(offset_of!(MemoryDescriptor, att) == 32); }; ``` --- xtask/src/check_raw.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/xtask/src/check_raw.rs b/xtask/src/check_raw.rs index d5c326eb..0eb6ffe0 100644 --- a/xtask/src/check_raw.rs +++ b/xtask/src/check_raw.rs @@ -403,9 +403,17 @@ fn check_macro(item: &ItemMacro, src: &Path) -> Result<(), Error> { Ok(()) } +/// True if the item is an anonymous compile-time assertion. +fn is_anonymous_unit_const(item: &ItemConst) -> bool { + item.ident == "_" && matches!(&*item.ty, Type::Tuple(tuple) if tuple.elems.is_empty()) +} + /// Validate a top-level item. fn check_item(item: &Item, src: &Path) -> Result<(), Error> { match item { + Item::Const(item) if is_anonymous_unit_const(item) => { + // Allow compile-time assertions such as ABI layout checks. + } Item::Const(ItemConst { vis, ty, .. }) => { if !is_pub(vis) { return Err(Error::new(ErrorKind::MissingPub, src, item)); @@ -518,6 +526,30 @@ mod tests { ); } + #[test] + fn test_anonymous_unit_const() { + // Compile-time assertions do not form part of the public API. + assert!( + check_item( + &parse_quote! { + const _: () = { + assert!(true); + }; + }, + src(), + ) + .is_ok() + ); + + // Named constants must remain public. + check_item_err( + parse_quote! { + const PRIVATE: () = (); + }, + ErrorKind::MissingPub, + ); + } + #[test] fn test_macro() { // bitflags `repr` must be transparent.