Skip to content

Commit 419ba3b

Browse files
committed
Modify guest physical page allocator to allocate from the scratch region
Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
1 parent 1f94fb3 commit 419ba3b

11 files changed

Lines changed: 207 additions & 59 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2025 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
pub const MAIN_STACK_TOP_GVA: usize = 0xffff_feff_ffff_f000;
18+
19+
pub fn scratch_size() -> u64 {
20+
let addr = crate::layout::scratch_size_gva();
21+
let x: u64;
22+
unsafe {
23+
core::arch::asm!("mov {x}, [{addr}]", x = out(reg) x, addr = in(reg) addr);
24+
}
25+
x
26+
}
27+
28+
pub fn scratch_base_gpa() -> u64 {
29+
hyperlight_common::layout::scratch_base_gpa(scratch_size() as usize)
30+
}
31+
32+
pub fn scratch_base_gva() -> u64 {
33+
hyperlight_common::layout::scratch_base_gva(scratch_size() as usize)
34+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2025 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// There are no notable architecture-specific safety considerations
18+
// here, and the general conditions are documented in the
19+
// architecture-independent re-export in prim_alloc.rs
20+
#[allow(clippy::missing_safety_doc)]
21+
pub unsafe fn alloc_phys_pages(n: u64) -> u64 {
22+
let addr = crate::layout::allocator_gva();
23+
let nbytes = n * hyperlight_common::vmem::PAGE_SIZE as u64;
24+
let mut x = nbytes;
25+
unsafe {
26+
core::arch::asm!(
27+
"lock xadd qword ptr [{addr}], {x}",
28+
addr = in(reg) addr,
29+
x = inout(reg) x
30+
);
31+
}
32+
if x.checked_add(nbytes).is_none() {
33+
panic!("Out of physical memory!")
34+
}
35+
x
36+
}

src/hyperlight_guest/src/layout.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2025 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#[cfg_attr(target_arch = "x86_64", path = "arch/amd64/layout.rs")]
18+
mod arch;
19+
20+
pub use arch::MAIN_STACK_TOP_GVA;
21+
pub fn scratch_size_gva() -> *mut u64 {
22+
use hyperlight_common::layout::{MAX_GVA, SCRATCH_TOP_SIZE_OFFSET};
23+
(MAX_GVA as u64 - SCRATCH_TOP_SIZE_OFFSET + 1) as *mut u64
24+
}
25+
pub fn allocator_gva() -> *mut u64 {
26+
use hyperlight_common::layout::{MAX_GVA, SCRATCH_TOP_ALLOCATOR_OFFSET};
27+
(MAX_GVA as u64 - SCRATCH_TOP_ALLOCATOR_OFFSET + 1) as *mut u64
28+
}
29+
pub use arch::{scratch_base_gpa, scratch_base_gva};

src/hyperlight_guest/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ extern crate alloc;
2323
// Modules
2424
pub mod error;
2525
pub mod exit;
26+
pub mod layout;
27+
pub mod prim_alloc;
2628

2729
pub mod guest_handle {
2830
pub mod handle;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2025 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#[cfg_attr(target_arch = "x86_64", path = "arch/amd64/prim_alloc.rs")]
18+
mod arch;
19+
20+
/// Allocate n contiguous physical pages and return the physical
21+
/// addresses of the pages in question.
22+
/// # Safety
23+
/// Since this reads and writes specific allocator state addresses, it
24+
/// is only safe when the allocator has been set up properly. It may
25+
/// become less safe in the future.
26+
///
27+
/// # Panics
28+
/// This function will panic if memory allocation fails
29+
///
30+
/// This is defined in an arch-specific module because it reads and
31+
/// writes the actual allocator state with inline assembly in order to
32+
/// access it atomically according to the architecture memory model
33+
/// rather than the Rust memory model: the stronger constraints of the
34+
/// latter cannot be perfectly satisfied due to the lack of per-byte
35+
/// atomic memcpy in the host.
36+
pub use arch::alloc_phys_pages;

src/hyperlight_guest_bin/src/paging.rs

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
use alloc::alloc::Layout;
1817
use core::arch::asm;
1918

19+
use hyperlight_guest::prim_alloc::alloc_phys_pages;
2020
use tracing::{Span, instrument};
2121

22-
use crate::OS_PAGE_SIZE;
2322

2423
/// Convert a physical address in main memory to a virtual address
2524
/// through the pysmap
@@ -137,28 +136,6 @@ pub unsafe fn map_region(phys_base: u64, virt_base: *mut u8, len: u64) {
137136
}
138137
}
139138

140-
/// Allocate n contiguous physical pages and return the physical
141-
/// addresses of the pages in question.
142-
/// # Safety
143-
/// This function is not inherently unsafe but will likely become so in the future
144-
/// when a real physical page allocator is implemented.
145-
/// # Panics
146-
/// This function will panic if:
147-
/// - The Layout creation fails
148-
/// - Memory allocation fails
149-
pub unsafe fn alloc_phys_pages(n: u64) -> u64 {
150-
// Currently, since all of main memory is idmap'd, we can just
151-
// allocate any appropriately aligned section of memory.
152-
unsafe {
153-
let v = alloc::alloc::alloc_zeroed(
154-
Layout::from_size_align(n as usize * OS_PAGE_SIZE as usize, OS_PAGE_SIZE as usize)
155-
.expect("could not create physical page allocation layout"),
156-
);
157-
if v.is_null() {
158-
panic!("could not allocate a physical page");
159-
}
160-
v as u64
161-
}
162139
}
163140

164141
pub fn flush_tlb() {

src/hyperlight_host/src/hypervisor/gdb/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ mod tests {
497497
.inspect_err(|_| unsafe {
498498
libc::munmap(mapped_mem, size);
499499
})?;
500-
let (mem_mgr, _) = sandbox.mgr.build();
500+
let (mem_mgr, _) = sandbox.mgr.build()?;
501501

502502
// Create the memory access struct
503503
let mem_access = DebugMemoryAccess {

src/hyperlight_host/src/hypervisor/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ pub(crate) mod tests {
525525
let rt_cfg: SandboxRuntimeConfig = Default::default();
526526
let sandbox =
527527
UninitializedSandbox::new(GuestBinary::FilePath(filename.clone()), Some(config))?;
528-
let (mut mem_mgr, gshm) = sandbox.mgr.build();
528+
let (mut mem_mgr, gshm) = sandbox.mgr.build().unwrap();
529529
let mut vm = set_up_hypervisor_partition(
530530
gshm,
531531
&config,

src/hyperlight_host/src/mem/mgr.rs

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,18 @@ impl vmem::TableOps for GuestPageTableBuffer {
9595
.unwrap_or(0)
9696
}
9797

98-
unsafe fn write_entry(&self, addr: (usize, usize), x: PageTableEntry) {
98+
unsafe fn write_entry(
99+
&self,
100+
addr: (usize, usize),
101+
entry: PageTableEntry,
102+
) -> Option<(usize, usize)> {
99103
let mut b = self.buffer.borrow_mut();
100104
let byte_offset =
101105
(addr.0 - self.phys_base / PAGE_TABLE_SIZE) * PAGE_TABLE_SIZE + addr.1 * 8;
102106
if let Some(slice) = b.get_mut(byte_offset..byte_offset + 8) {
103-
slice.copy_from_slice(&x.to_ne_bytes());
107+
slice.copy_from_slice(&entry.to_ne_bytes());
104108
}
109+
None
105110
}
106111

107112
fn to_phys(addr: (usize, usize)) -> PhysAddr {
@@ -229,36 +234,45 @@ impl SandboxMemoryManager<ExclusiveSharedMemory> {
229234
}
230235

231236
/// Wraps ExclusiveSharedMemory::build
237+
// Morally, this should not have to be a Result: this operation is
238+
// infallible. The source of the Result is
239+
// update_scratch_bookkeeping(), which calls functions that can
240+
// fail due to bounds checks (which are statically known to be ok
241+
// in this situation) or due to failing to take the scratch shared
242+
// memory lock, but the scratch shared memory is built in this
243+
// function, its lock does not escape before the end of the
244+
// function, and the lock is taken by no other code path, so we
245+
// know it is not contended.
232246
pub fn build(
233247
self,
234-
) -> (
248+
) -> Result<(
235249
SandboxMemoryManager<HostSharedMemory>,
236250
SandboxMemoryManager<GuestSharedMemory>,
237-
) {
251+
)> {
238252
let (hshm, gshm) = self.shared_mem.build();
239253
let (hscratch, gscratch) = self.scratch_mem.build();
240-
(
241-
SandboxMemoryManager {
242-
shared_mem: hshm,
243-
scratch_mem: hscratch,
244-
layout: self.layout,
245-
load_addr: self.load_addr.clone(),
246-
entrypoint_offset: self.entrypoint_offset,
247-
mapped_rgns: self.mapped_rgns,
248-
stack_cookie: self.stack_cookie,
249-
abort_buffer: self.abort_buffer,
250-
},
251-
SandboxMemoryManager {
252-
shared_mem: gshm,
253-
scratch_mem: gscratch,
254-
layout: self.layout,
255-
load_addr: self.load_addr.clone(),
256-
entrypoint_offset: self.entrypoint_offset,
257-
mapped_rgns: self.mapped_rgns,
258-
stack_cookie: self.stack_cookie,
259-
abort_buffer: Vec::new(), // Guest doesn't need abort buffer
260-
},
261-
)
254+
let mut host_mgr = SandboxMemoryManager {
255+
shared_mem: hshm,
256+
scratch_mem: hscratch,
257+
layout: self.layout,
258+
load_addr: self.load_addr.clone(),
259+
entrypoint_offset: self.entrypoint_offset,
260+
mapped_rgns: self.mapped_rgns,
261+
stack_cookie: self.stack_cookie,
262+
abort_buffer: self.abort_buffer,
263+
};
264+
let guest_mgr = SandboxMemoryManager {
265+
shared_mem: gshm,
266+
scratch_mem: gscratch,
267+
layout: self.layout,
268+
load_addr: self.load_addr.clone(),
269+
entrypoint_offset: self.entrypoint_offset,
270+
mapped_rgns: self.mapped_rgns,
271+
stack_cookie: self.stack_cookie,
272+
abort_buffer: Vec::new(), // Guest doesn't need abort buffer
273+
};
274+
host_mgr.update_scratch_bookkeeping()?;
275+
Ok((host_mgr, guest_mgr))
262276
}
263277
}
264278

@@ -400,9 +414,9 @@ impl SandboxMemoryManager<HostSharedMemory> {
400414
}
401415
self.shared_mem.restore_from_snapshot(snapshot)?;
402416
let new_scratch_size = snapshot.layout().get_scratch_size();
403-
if new_scratch_size == self.scratch_mem.mem_size() {
417+
let gscratch = if new_scratch_size == self.scratch_mem.mem_size() {
404418
self.scratch_mem.zero()?;
405-
Ok(None)
419+
None
406420
} else {
407421
let new_scratch_mem = ExclusiveSharedMemory::new(new_scratch_size)?;
408422
let (hscratch, gscratch) = new_scratch_mem.build();
@@ -413,8 +427,28 @@ impl SandboxMemoryManager<HostSharedMemory> {
413427
// has been unmapped from the VM.
414428
self.scratch_mem = hscratch;
415429

416-
Ok(Some(gscratch))
417-
}
430+
Some(gscratch)
431+
};
432+
self.update_scratch_bookkeeping()?;
433+
Ok(gscratch)
434+
}
435+
436+
fn update_scratch_bookkeeping(&mut self) -> Result<()> {
437+
let scratch_size = self.scratch_mem.mem_size();
438+
439+
let size_offset =
440+
scratch_size - hyperlight_common::layout::SCRATCH_TOP_SIZE_OFFSET as usize;
441+
self.scratch_mem
442+
.write::<u64>(size_offset, scratch_size as u64)?;
443+
444+
let alloc_offset =
445+
scratch_size - hyperlight_common::layout::SCRATCH_TOP_ALLOCATOR_OFFSET as usize;
446+
self.scratch_mem.write::<u64>(
447+
alloc_offset,
448+
hyperlight_common::layout::scratch_base_gpa(scratch_size),
449+
)?;
450+
451+
Ok(())
418452
}
419453
}
420454

src/hyperlight_host/src/sandbox/outb.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ mod tests {
287287
layout
288288
.write(shared_mem, SandboxMemoryLayout::BASE_ADDRESS, mem_size)
289289
.unwrap();
290-
let (hmgr, _) = mgr.build();
290+
let (hmgr, _) = mgr.build().unwrap();
291291
hmgr
292292
};
293293
{
@@ -399,7 +399,7 @@ mod tests {
399399
layout
400400
.write(shared_mem, SandboxMemoryLayout::BASE_ADDRESS, mem_size)
401401
.unwrap();
402-
let (hmgr, _) = mgr.build();
402+
let (hmgr, _) = mgr.build().unwrap();
403403
hmgr
404404
};
405405

0 commit comments

Comments
 (0)