Skip to content

Commit a9486f4

Browse files
committed
Make snapshot region aware
- Snapshot now contains the memory region that were mapped at the time of snapshot - On restore, unmap regions that were not mapped at time of snapshot. Map regions that were - Add simple test Signed-off-by: Ludvig Liljenberg <[email protected]>
1 parent d2f4b98 commit a9486f4

File tree

3 files changed

+102
-26
lines changed

3 files changed

+102
-26
lines changed

src/hyperlight_host/src/mem/mgr.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -259,26 +259,25 @@ where
259259
}
260260
}
261261

262-
pub(crate) fn snapshot(&mut self) -> Result<SharedMemorySnapshot> {
263-
SharedMemorySnapshot::new(&mut self.shared_mem, self.mapped_rgns)
262+
/// Create a snapshot with the given mapped regions
263+
pub(crate) fn snapshot(
264+
&mut self,
265+
mapped_regions: Vec<MemoryRegion>,
266+
) -> Result<SharedMemorySnapshot> {
267+
SharedMemorySnapshot::new(&mut self.shared_mem, mapped_regions)
264268
}
265269

266270
/// This function restores a memory snapshot from a given snapshot.
267-
///
268-
/// Returns the number of memory regions mapped into the sandbox
269-
/// that need to be unmapped in order for the restore to be
270-
/// completed.
271-
pub(crate) fn restore_snapshot(&mut self, snapshot: &SharedMemorySnapshot) -> Result<u64> {
271+
pub(crate) fn restore_snapshot(&mut self, snapshot: &SharedMemorySnapshot) -> Result<()> {
272272
if self.shared_mem.mem_size() != snapshot.mem_size() {
273273
return Err(new_error!(
274274
"Snapshot size does not match current memory size: {} != {}",
275275
self.shared_mem.raw_mem_size(),
276276
snapshot.mem_size()
277277
));
278278
}
279-
let old_rgns = self.mapped_rgns;
280-
self.mapped_rgns = snapshot.restore_from_snapshot(&mut self.shared_mem)?;
281-
Ok(old_rgns - self.mapped_rgns)
279+
snapshot.restore_from_snapshot(&mut self.shared_mem)?;
280+
Ok(())
282281
}
283282

284283
/// Sets `addr` to the correct offset in the memory referenced by

src/hyperlight_host/src/mem/shared_mem_snapshot.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616

1717
use tracing::{Span, instrument};
1818

19+
use super::memory_region::MemoryRegion;
1920
use super::shared_mem::SharedMemory;
2021
use crate::Result;
2122

@@ -24,21 +25,21 @@ use crate::Result;
2425
#[derive(Clone)]
2526
pub(crate) struct SharedMemorySnapshot {
2627
snapshot: Vec<u8>,
27-
/// How many non-main-RAM regions were mapped when this snapshot was taken?
28-
mapped_rgns: u64,
28+
/// The memory regions that were mapped when this snapshot was taken (excluding initial sandbox regions)
29+
regions: Vec<MemoryRegion>,
2930
}
3031

3132
impl SharedMemorySnapshot {
3233
/// Take a snapshot of the memory in `shared_mem`, then create a new
3334
/// instance of `Self` with the snapshot stored therein.
3435
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
35-
pub(super) fn new<S: SharedMemory>(shared_mem: &mut S, mapped_rgns: u64) -> Result<Self> {
36+
pub(super) fn new<S: SharedMemory>(
37+
shared_mem: &mut S,
38+
regions: Vec<MemoryRegion>,
39+
) -> Result<Self> {
3640
// TODO: Track dirty pages instead of copying entire memory
3741
let snapshot = shared_mem.with_exclusivity(|e| e.copy_all_to_vec())??;
38-
Ok(Self {
39-
snapshot,
40-
mapped_rgns,
41-
})
42+
Ok(Self { snapshot, regions })
4243
}
4344

4445
/// Take another snapshot of the internally-stored `SharedMemory`,
@@ -51,11 +52,16 @@ impl SharedMemorySnapshot {
5152
}
5253

5354
/// Copy the memory from the internally-stored memory snapshot
54-
/// into the internally-stored `SharedMemory`
55+
/// into the internally-stored `SharedMemory`.
5556
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
56-
pub(super) fn restore_from_snapshot<S: SharedMemory>(&self, shared_mem: &mut S) -> Result<u64> {
57+
pub(super) fn restore_from_snapshot<S: SharedMemory>(&self, shared_mem: &mut S) -> Result<()> {
5758
shared_mem.with_exclusivity(|e| e.copy_from_slice(self.snapshot.as_slice(), 0))??;
58-
Ok(self.mapped_rgns)
59+
Ok(())
60+
}
61+
62+
/// Get the mapped regions from this snapshot
63+
pub(crate) fn regions(&self) -> &[MemoryRegion] {
64+
&self.regions
5965
}
6066

6167
/// Return the size of the snapshot in bytes.
@@ -78,7 +84,7 @@ mod tests {
7884
let data2 = data1.iter().map(|b| b + 1).collect::<Vec<u8>>();
7985
let mut gm = ExclusiveSharedMemory::new(PAGE_SIZE_USIZE).unwrap();
8086
gm.copy_from_slice(data1.as_slice(), 0).unwrap();
81-
let mut snap = super::SharedMemorySnapshot::new(&mut gm, 0).unwrap();
87+
let mut snap = super::SharedMemorySnapshot::new(&mut gm, Vec::new()).unwrap();
8288
{
8389
// after the first snapshot is taken, make sure gm has the equivalent
8490
// of data1

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
use std::collections::HashSet;
1718
#[cfg(unix)]
1819
use std::os::fd::AsRawFd;
1920
#[cfg(unix)]
@@ -95,18 +96,35 @@ impl MultiUseSandbox {
9596
/// Create a snapshot of the current state of the sandbox's memory.
9697
#[instrument(err(Debug), skip_all, parent = Span::current())]
9798
pub fn snapshot(&mut self) -> Result<Snapshot> {
98-
let snapshot = self.mem_mgr.unwrap_mgr_mut().snapshot()?;
99-
Ok(Snapshot { inner: snapshot })
99+
let mapped_regions_iter = self.vm.get_mapped_regions();
100+
let mapped_regions_vec: Vec<MemoryRegion> = mapped_regions_iter.cloned().collect();
101+
let memory_snapshot = self.mem_mgr.unwrap_mgr_mut().snapshot(mapped_regions_vec)?;
102+
Ok(Snapshot {
103+
inner: memory_snapshot,
104+
})
100105
}
101106

102107
/// Restore the sandbox's memory to the state captured in the given snapshot.
103108
#[instrument(err(Debug), skip_all, parent = Span::current())]
104109
pub fn restore(&mut self, snapshot: &Snapshot) -> Result<()> {
105-
let rgns_to_unmap = self
106-
.mem_mgr
110+
self.mem_mgr
107111
.unwrap_mgr_mut()
108112
.restore_snapshot(&snapshot.inner)?;
109-
unsafe { self.vm.unmap_regions(rgns_to_unmap)? };
113+
114+
let current_regions: HashSet<_> = self.vm.get_mapped_regions().cloned().collect();
115+
let snapshot_regions: HashSet<_> = snapshot.inner.regions().iter().cloned().collect();
116+
117+
let regions_to_unmap = current_regions.difference(&snapshot_regions);
118+
let regions_to_map = snapshot_regions.difference(&current_regions);
119+
120+
for region in regions_to_unmap {
121+
unsafe { self.vm.unmap_region(region)? };
122+
}
123+
124+
for region in regions_to_map {
125+
unsafe { self.vm.map_region(region)? };
126+
}
127+
110128
Ok(())
111129
}
112130

@@ -645,4 +663,57 @@ mod tests {
645663
region_type: MemoryRegionType::Heap,
646664
}
647665
}
666+
667+
#[cfg(target_os = "linux")]
668+
fn allocate_guest_memory() -> GuestSharedMemory {
669+
page_aligned_memory(b"test data for snapshot")
670+
}
671+
672+
#[test]
673+
#[cfg(target_os = "linux")]
674+
fn snapshot_restore_handles_remapping_correctly() {
675+
let mut sbox: MultiUseSandbox = {
676+
let path = simple_guest_as_string().unwrap();
677+
let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
678+
u_sbox.evolve().unwrap()
679+
};
680+
681+
// 1. Take snapshot 1 with no additional regions mapped
682+
let snapshot1 = sbox.snapshot().unwrap();
683+
assert_eq!(sbox.vm.get_mapped_regions().len(), 0);
684+
685+
// 2. Map a memory region
686+
let map_mem = allocate_guest_memory();
687+
let guest_base = 0x200000000_usize;
688+
let region = region_for_memory(&map_mem, guest_base);
689+
690+
unsafe { sbox.map_region(&region).unwrap() };
691+
assert_eq!(sbox.vm.get_mapped_regions().len(), 1);
692+
693+
// 3. Take snapshot 2 with 1 region mapped
694+
let snapshot2 = sbox.snapshot().unwrap();
695+
assert_eq!(sbox.vm.get_mapped_regions().len(), 1);
696+
697+
// 4. Restore to snapshot 1 (should unmap the region)
698+
sbox.restore(&snapshot1).unwrap();
699+
assert_eq!(sbox.vm.get_mapped_regions().len(), 0);
700+
701+
// 5. Restore forward to snapshot 2 (should remap the region)
702+
sbox.restore(&snapshot2).unwrap();
703+
assert_eq!(sbox.vm.get_mapped_regions().len(), 1);
704+
705+
// Verify the region is the same
706+
let mut restored_regions = sbox.vm.get_mapped_regions();
707+
assert_eq!(*restored_regions.next().unwrap(), region);
708+
assert!(restored_regions.next().is_none());
709+
drop(restored_regions);
710+
711+
// 6. Try map the region again (should fail since already mapped)
712+
let err = unsafe { sbox.map_region(&region) };
713+
assert!(
714+
err.is_err(),
715+
"Expected error when remapping existing region: {:?}",
716+
err
717+
);
718+
}
648719
}

0 commit comments

Comments
 (0)