Skip to content

Commit 5d71e2a

Browse files
committed
Add new SnapshotManager and Snapshot structs.
Signed-off-by: Ludvig Liljenberg <[email protected]>
1 parent 96754b8 commit 5d71e2a

File tree

5 files changed

+1423
-3
lines changed

5 files changed

+1423
-3
lines changed

src/hyperlight_host/src/mem/layout.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ impl SandboxMemoryLayout {
246246
pub(crate) const BASE_ADDRESS: usize = 0x0;
247247

248248
// the offset into a sandbox's input/output buffer where the stack starts
249-
const STACK_POINTER_SIZE_BYTES: u64 = 8;
249+
pub(crate) const STACK_POINTER_SIZE_BYTES: u64 = 8;
250250

251251
/// Create a new `SandboxMemoryLayout` with the given
252252
/// `SandboxConfiguration`, code size and stack/heap size.
@@ -397,7 +397,7 @@ impl SandboxMemoryLayout {
397397

398398
/// Get the offset in guest memory to the output data pointer.
399399
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
400-
fn get_output_data_pointer_offset(&self) -> usize {
400+
pub(super) fn get_output_data_pointer_offset(&self) -> usize {
401401
// This field is immediately after the output data size field,
402402
// which is a `u64`.
403403
self.get_output_data_size_offset() + size_of::<u64>()
@@ -429,7 +429,7 @@ impl SandboxMemoryLayout {
429429

430430
/// Get the offset in guest memory to the input data pointer.
431431
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
432-
fn get_input_data_pointer_offset(&self) -> usize {
432+
pub(super) fn get_input_data_pointer_offset(&self) -> usize {
433433
// The input data pointer is immediately after the input
434434
// data size field in the input data `GuestMemoryRegion` struct which is a `u64`.
435435
self.get_input_data_size_offset() + size_of::<u64>()

src/hyperlight_host/src/mem/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ pub mod memory_region;
3131
/// Functionality that wraps a `SandboxMemoryLayout` and a
3232
/// `SandboxMemoryConfig` to mutate a sandbox's memory as necessary.
3333
pub mod mgr;
34+
/// A compact snapshot representation for memory pages
35+
pub(crate) mod page_snapshot;
3436
/// Structures to represent pointers into guest and host memory
3537
pub mod ptr;
3638
/// Structures to represent memory address spaces into which pointers
@@ -47,5 +49,7 @@ pub mod shared_mem_snapshot;
4749
/// Utilities for writing shared memory tests
4850
#[cfg(test)]
4951
pub(crate) mod shared_mem_tests;
52+
/// A wrapper around a `SharedMemory` to manage snapshots of the memory
53+
pub mod shared_memory_snapshot_manager;
5054
#[cfg(target_os = "windows")]
5155
mod windows_dirty_page_tracker;
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
use std::collections::HashMap;
18+
19+
use hyperlight_common::mem::PAGE_SIZE_USIZE;
20+
21+
/// A compact snapshot representation that stores pages in a contiguous buffer
22+
/// with an index for efficient lookup.
23+
///
24+
/// This struct is designed to efficiently store and retrieve memory snapshots
25+
/// by using a contiguous buffer for all page data combined with a HashMap index
26+
/// for page lookups. This approach reduces memory overhead
27+
/// compared to storing pages individually.
28+
///
29+
/// # Clone Derivation
30+
///
31+
/// This struct derives `Clone` because it's stored in `Vec<PageSnapshot>` within
32+
/// `SharedMemorySnapshotManager`, which itself derives `Clone`.
33+
#[derive(Clone)]
34+
pub(super) struct PageSnapshot {
35+
/// Maps page numbers to their offset within the buffer (in page units)
36+
page_index: HashMap<usize, usize>, // page_number -> buffer_offset_in_pages
37+
/// Contiguous buffer containing all the page data
38+
buffer: Vec<u8>,
39+
/// How many non-main-RAM regions were mapped when this snapshot was taken?
40+
mapped_rgns: u64,
41+
}
42+
43+
impl PageSnapshot {
44+
/// Create a snapshot from a list of page numbers with pre-allocated buffer
45+
pub(super) fn with_pages_and_buffer(
46+
page_numbers: Vec<usize>,
47+
buffer: Vec<u8>,
48+
mapped_rgns: u64,
49+
) -> Self {
50+
let page_count = page_numbers.len();
51+
let mut page_index = HashMap::with_capacity(page_count);
52+
53+
// Map each page number to its offset in the buffer
54+
for (buffer_offset, page_num) in page_numbers.into_iter().enumerate() {
55+
page_index.insert(page_num, buffer_offset);
56+
}
57+
58+
Self {
59+
page_index,
60+
buffer,
61+
mapped_rgns,
62+
}
63+
}
64+
65+
/// Get page data by page number, returns None if page is not in snapshot
66+
pub(super) fn get_page(&self, page_num: usize) -> Option<&[u8]> {
67+
self.page_index.get(&page_num).map(|&buffer_offset| {
68+
let start = buffer_offset * PAGE_SIZE_USIZE;
69+
let end = start + PAGE_SIZE_USIZE;
70+
&self.buffer[start..end]
71+
})
72+
}
73+
74+
/// Get an iterator over all page numbers in this snapshot
75+
pub(super) fn page_numbers(&self) -> impl Iterator<Item = usize> + '_ {
76+
self.page_index.keys().copied()
77+
}
78+
79+
/// Get the maximum page number in this snapshot, or None if empty
80+
pub(super) fn max_page(&self) -> Option<usize> {
81+
self.page_index.keys().max().copied()
82+
}
83+
84+
/// Get the number of mapped regions when this snapshot was taken
85+
pub(super) fn mapped_rgns(&self) -> u64 {
86+
self.mapped_rgns
87+
}
88+
}

src/hyperlight_host/src/mem/shared_mem.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,15 @@ impl ExclusiveSharedMemory {
627627
Ok(())
628628
}
629629

630+
/// Copies bytes from `self` to `dst` starting at offset
631+
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
632+
pub fn copy_to_slice(&self, dst: &mut [u8], offset: usize) -> Result<()> {
633+
let data = self.as_slice();
634+
bounds_check!(offset, dst.len(), data.len());
635+
dst.copy_from_slice(&data[offset..offset + dst.len()]);
636+
Ok(())
637+
}
638+
630639
/// Return the address of memory at an offset to this `SharedMemory` checking
631640
/// that the memory is within the bounds of the `SharedMemory`.
632641
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
@@ -635,6 +644,15 @@ impl ExclusiveSharedMemory {
635644
Ok(self.base_addr() + offset)
636645
}
637646

647+
/// Fill the memory in the range `[offset, offset + len)` with `value`
648+
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
649+
pub fn zero_fill(&mut self, offset: usize, len: usize) -> Result<()> {
650+
bounds_check!(offset, len, self.mem_size());
651+
let data = self.as_mut_slice();
652+
data[offset..offset + len].fill(0);
653+
Ok(())
654+
}
655+
638656
generate_reader!(read_u8, u8);
639657
generate_reader!(read_i8, i8);
640658
generate_reader!(read_u16, u16);

0 commit comments

Comments
 (0)