Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 4ca4daa

Browse files
authoredJun 23, 2024
Merge pull request #1175 from phip1611/mmap
memory map: improvements to fight pitfalls (MemoryMap now owns the memory)
2 parents 99e74c7 + e47e36c commit 4ca4daa

File tree

7 files changed

+489
-164
lines changed

7 files changed

+489
-164
lines changed
 

‎uefi-services/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# uefi-services
22

33
WARNING: `uefi-services` is deprecated. Functionality was moved to
4-
`uefi::helpers::init` in `uefi` ´v0.28.0`.
4+
`uefi::helpers::init` in `uefi@v0.28.0`.

‎uefi-test-runner/src/boot/memory.rs

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -63,45 +63,41 @@ fn alloc_alignment() {
6363
fn memory_map(bt: &BootServices) {
6464
info!("Testing memory map functions");
6565

66-
// Get the memory descriptor size and an estimate of the memory map size
67-
let sizes = bt.memory_map_size();
66+
// Ensure that the memory map is freed after each iteration (on drop).
67+
// Otherwise, we will have an OOM.
68+
for _ in 0..200000 {
69+
let mut memory_map = bt
70+
.memory_map(MemoryType::LOADER_DATA)
71+
.expect("Failed to retrieve UEFI memory map");
6872

69-
// 2 extra descriptors should be enough.
70-
let buf_sz = sizes.map_size + 2 * sizes.entry_size;
73+
memory_map.sort();
7174

72-
// We will use vectors for convenience.
73-
let mut buffer = vec![0_u8; buf_sz];
75+
// Collect the descriptors into a vector
76+
let descriptors = memory_map.entries().copied().collect::<Vec<_>>();
7477

75-
let mut memory_map = bt
76-
.memory_map(&mut buffer)
77-
.expect("Failed to retrieve UEFI memory map");
78+
// Ensured we have at least one entry.
79+
// Real memory maps usually have dozens of entries.
80+
assert!(!descriptors.is_empty(), "Memory map is empty");
7881

79-
memory_map.sort();
82+
let mut curr_value = descriptors[0];
8083

81-
// Collect the descriptors into a vector
82-
let descriptors = memory_map.entries().copied().collect::<Vec<_>>();
83-
84-
// Ensured we have at least one entry.
85-
// Real memory maps usually have dozens of entries.
86-
assert!(!descriptors.is_empty(), "Memory map is empty");
87-
88-
let mut curr_value = descriptors[0];
89-
90-
for value in descriptors.iter().skip(1) {
91-
if value.phys_start <= curr_value.phys_start {
92-
panic!("memory map sorting failed");
84+
for value in descriptors.iter().skip(1) {
85+
if value.phys_start <= curr_value.phys_start {
86+
panic!("memory map sorting failed");
87+
}
88+
curr_value = *value;
9389
}
94-
curr_value = *value;
95-
}
9690

97-
// This is pretty much a sanity test to ensure returned memory isn't filled with random values.
98-
let first_desc = descriptors[0];
91+
// This is pretty much a basic sanity test to ensure returned memory
92+
// isn't filled with random values.
93+
let first_desc = descriptors[0];
9994

100-
#[cfg(target_arch = "x86_64")]
101-
{
102-
let phys_start = first_desc.phys_start;
103-
assert_eq!(phys_start, 0, "Memory does not start at address 0");
95+
#[cfg(target_arch = "x86_64")]
96+
{
97+
let phys_start = first_desc.phys_start;
98+
assert_eq!(phys_start, 0, "Memory does not start at address 0");
99+
}
100+
let page_count = first_desc.page_count;
101+
assert!(page_count != 0, "Memory map entry has size zero");
104102
}
105-
let page_count = first_desc.page_count;
106-
assert!(page_count != 0, "Memory map entry has zero size");
107103
}

‎uefi/CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,25 @@
1616
- Added `ByteConversionError`.
1717
- Re-exported `CapsuleFlags`.
1818
- One can now specify in `TimeError` what fields of `Time` are outside its valid
19-
range. `Time::is_valid` has been updated accordingly.
19+
range. `Time::is_valid` has been updated accordingly.
20+
- `MemoryMap::as_raw` which provides raw access to the memory map. This is for
21+
example useful if you create your own Multiboot2 bootloader that embeds the
22+
EFI mmap in a Multiboot2 boot information structure.
2023

2124
## Changed
2225
- `SystemTable::exit_boot_services` is now `unsafe`. See that method's
2326
documentation for details of obligations for callers.
2427
- `BootServices::allocate_pool` now returns `NonZero<u8>` instead of
2528
`*mut u8`.
2629
- `helpers::system_table` is deprecated, use `table::system_table_boot` instead.
30+
- `BootServices::memory_map` changed its signature from \
31+
`pub fn memory_map<'buf>(&self, buffer: &'buf mut [u8]) -> Result<MemoryMap<'buf>> {` \
32+
to \
33+
`pub fn memory_map(&self, mt: MemoryType) -> Result<MemoryMap>`
34+
- Allocations now happen automatically internally on the UEFI heap. Also, the
35+
returned type is automatically freed on the UEFI heap, as long as boot
36+
services are not excited. By removing the need for that explicit buffer and
37+
the lifetime, the API is simpler.
2738

2839
## Removed
2940
- Removed the `panic-on-logger-errors` feature of the `uefi` crate. Logger

‎uefi/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
//! the Rust standard library. For example, methods that return a
4949
//! `Vec` rather than filling a statically-sized array. This requires
5050
//! a global allocator; you can use the `global_allocator` feature or
51-
//! provide your own.
51+
//! provide your own. This is independent of internal direct usages of the
52+
//! UEFI boot service allocator which may happen anyway, where necessary.
5253
//! - `global_allocator`: Set [`allocator::Allocator`] as the global Rust
5354
//! allocator. This is a simple allocator that relies on the UEFI pool
5455
//! allocator. You can choose to provide your own allocator instead of

‎uefi/src/mem.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,14 @@ use {core::alloc::Allocator, core::ptr::NonNull};
2323
/// success.
2424
///
2525
/// # Feature `unstable` / `allocator_api`
26-
/// By default, this function works with Rust's default allocation mechanism. If you activate the
27-
/// `unstable`-feature, it uses the `allocator_api` instead. In that case, the function takes an
28-
/// additional parameter describing the specific [`Allocator`]. You can use [`alloc::alloc::Global`]
29-
/// as default.
26+
/// By default, this function works with the allocator that is set as
27+
/// `#[global_allocator]`. This might be UEFI allocator but depends on your
28+
/// use case and how you set up the environment.
29+
///
30+
/// If you activate the `unstable`-feature, all allocations uses the provided
31+
/// allocator (via `allocator_api`) instead. In that case, the function takes an
32+
/// additional parameter describing the specific [`Allocator`]. You can use
33+
/// [`alloc::alloc::Global`] which defaults to the `#[global_allocator]`.
3034
///
3135
/// [`Allocator`]: https://doc.rust-lang.org/alloc/alloc/trait.Allocator.html
3236
/// [`alloc::alloc::Global`]: https://doc.rust-lang.org/alloc/alloc/struct.Global.html

‎uefi/src/table/boot.rs

Lines changed: 420 additions & 83 deletions
Large diffs are not rendered by default.

‎uefi/src/table/system.rs

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use core::ffi::c_void;
22
use core::marker::PhantomData;
33
use core::ptr::NonNull;
44
use core::slice;
5+
use uefi::table::boot::{MemoryMapBackingMemory, MemoryMapMeta};
56

67
use crate::proto::console::text;
78
use crate::{CStr16, Result, Status, StatusExt};
@@ -151,41 +152,23 @@ impl SystemTable<Boot> {
151152
unsafe { &*(*self.table).boot_services.cast_const().cast() }
152153
}
153154

154-
/// Get the size in bytes of the buffer to allocate for storing the memory
155-
/// map in `exit_boot_services`.
156-
///
157-
/// This map contains some extra room to avoid needing to allocate more than
158-
/// once.
159-
///
160-
/// Returns `None` on overflow.
161-
fn memory_map_size_for_exit_boot_services(&self) -> Option<usize> {
162-
// Allocate space for extra entries beyond the current size of the
163-
// memory map. The value of 8 matches the value in the Linux kernel:
164-
// https://github.com/torvalds/linux/blob/e544a07438/drivers/firmware/efi/libstub/efistub.h#L173
165-
let extra_entries = 8;
166-
167-
let memory_map_size = self.boot_services().memory_map_size();
168-
let extra_size = memory_map_size.entry_size.checked_mul(extra_entries)?;
169-
memory_map_size.map_size.checked_add(extra_size)
170-
}
171-
172155
/// Get the current memory map and exit boot services.
173156
unsafe fn get_memory_map_and_exit_boot_services(
174157
&self,
175-
buf: &'static mut [u8],
176-
) -> Result<MemoryMap<'static>> {
158+
buf: &mut [u8],
159+
) -> Result<MemoryMapMeta> {
177160
let boot_services = self.boot_services();
178161

179162
// Get the memory map.
180-
let memory_map = boot_services.memory_map(buf)?;
163+
let memory_map = boot_services.get_memory_map(buf)?;
181164

182165
// Try to exit boot services using the memory map key. Note that after
183166
// the first call to `exit_boot_services`, there are restrictions on
184167
// what boot services functions can be called. In UEFI 2.8 and earlier,
185168
// only `get_memory_map` and `exit_boot_services` are allowed. Starting
186169
// in UEFI 2.9 other memory allocation functions may also be called.
187170
boot_services
188-
.exit_boot_services(boot_services.image_handle(), memory_map.key())
171+
.exit_boot_services(boot_services.image_handle(), memory_map.map_key)
189172
.map(move |()| memory_map)
190173
}
191174

@@ -247,44 +230,37 @@ impl SystemTable<Boot> {
247230
pub unsafe fn exit_boot_services(
248231
self,
249232
memory_type: MemoryType,
250-
) -> (SystemTable<Runtime>, MemoryMap<'static>) {
233+
) -> (SystemTable<Runtime>, MemoryMap) {
251234
crate::helpers::exit();
252235

253-
let boot_services = self.boot_services();
254-
255236
// Reboot the device.
256-
let reset = |status| -> ! { self.runtime_services().reset(ResetType::COLD, status, None) };
257-
258-
// Get the size of the buffer to allocate. If that calculation
259-
// overflows treat it as an unrecoverable error.
260-
let buf_size = match self.memory_map_size_for_exit_boot_services() {
261-
Some(buf_size) => buf_size,
262-
None => reset(Status::ABORTED),
237+
let reset = |status| -> ! {
238+
{
239+
log::warn!("Resetting the machine");
240+
self.runtime_services().reset(ResetType::COLD, status, None)
241+
}
263242
};
264243

265-
// Allocate a byte slice to hold the memory map. If the
266-
// allocation fails treat it as an unrecoverable error.
267-
let buf: *mut u8 = match boot_services.allocate_pool(memory_type, buf_size) {
268-
Ok(buf) => buf.as_ptr(),
269-
Err(err) => reset(err.status()),
270-
};
244+
let mut buf = MemoryMapBackingMemory::new(memory_type).expect("Failed to allocate memory");
271245

272246
// Calling `exit_boot_services` can fail if the memory map key is not
273247
// current. Retry a second time if that occurs. This matches the
274248
// behavior of the Linux kernel:
275249
// https://github.com/torvalds/linux/blob/e544a0743/drivers/firmware/efi/libstub/efi-stub-helper.c#L375
276250
let mut status = Status::ABORTED;
277251
for _ in 0..2 {
278-
let buf: &mut [u8] = unsafe { slice::from_raw_parts_mut(buf, buf_size) };
279-
match unsafe { self.get_memory_map_and_exit_boot_services(buf) } {
252+
match unsafe { self.get_memory_map_and_exit_boot_services(buf.as_mut_slice()) } {
280253
Ok(memory_map) => {
281254
let st = SystemTable {
282255
table: self.table,
283256
_marker: PhantomData,
284257
};
285-
return (st, memory_map);
258+
return (st, MemoryMap::from_initialized_mem(buf, memory_map));
259+
}
260+
Err(err) => {
261+
log::error!("Error retrieving the memory map for exiting the boot services");
262+
status = err.status()
286263
}
287-
Err(err) => status = err.status(),
288264
}
289265
}
290266

0 commit comments

Comments
 (0)
Please sign in to comment.