Skip to content

Commit f22ebe7

Browse files
committed
Avoid copying memory on snapshot restore, when possible
Whenever we are not running with certain features that still require legacy writable access to the snapshot region (currently: nanvix-unstable, gdb), map the snapshot region directly into the VM. This lays the groundwork for sharing a single snapshot memory between multiple VMs. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
1 parent 31ea550 commit f22ebe7

File tree

12 files changed

+547
-458
lines changed

12 files changed

+547
-458
lines changed

src/hyperlight_host/build.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ fn main() -> Result<()> {
105105
crashdump: { all(feature = "crashdump", target_arch = "x86_64") },
106106
// print_debug feature is aliased with debug_assertions to make it only available in debug-builds.
107107
print_debug: { all(feature = "print_debug", debug_assertions) },
108+
// the nanvix-unstable and gdb features both (only
109+
// temporarily!) need to use writable/un-shared snapshot
110+
// memories, and so can't share
111+
unshared_snapshot_mem: { any(feature = "nanvix-unstable", feature = "gdb") },
108112
}
109113

110114
#[cfg(feature = "build-metadata")]

src/hyperlight_host/src/error.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,6 @@ pub enum HyperlightError {
240240
#[error("Failed To Convert Return Value {0:?} to {1:?}")]
241241
ReturnValueConversionFailure(ReturnValue, &'static str),
242242

243-
/// Attempted to process a snapshot but the snapshot size does not match the current memory size
244-
#[error("Snapshot Size Mismatch: Memory Size {0:?} Snapshot Size {1:?}")]
245-
SnapshotSizeMismatch(usize, usize),
246-
247243
/// Tried to restore snapshot to a sandbox that is not the same as the one the snapshot was taken from
248244
#[error("Snapshot was taken from a different sandbox")]
249245
SnapshotSandboxMismatch,
@@ -335,7 +331,6 @@ impl HyperlightError {
335331
| HyperlightError::PoisonedSandbox
336332
| HyperlightError::ExecutionAccessViolation(_)
337333
| HyperlightError::MemoryAccessViolation(_, _, _)
338-
| HyperlightError::SnapshotSizeMismatch(_, _)
339334
| HyperlightError::MemoryRegionSizeMismatch(_, _, _)
340335
// HyperlightVmError::Restore is already handled manually in restore(), but we mark it
341336
// as poisoning here too for defense in depth.

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

Lines changed: 40 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ mod x86_64_target;
2121
use std::io::{self, ErrorKind};
2222
use std::net::TcpListener;
2323
use std::sync::{Arc, Mutex};
24-
use std::{slice, thread};
24+
use std::thread;
2525

2626
use crossbeam_channel::{Receiver, Sender, TryRecvError};
2727
use event_loop::event_loop_thread;
@@ -36,10 +36,10 @@ use super::regs::CommonRegisters;
3636
use crate::HyperlightError;
3737
use crate::hypervisor::regs::CommonFpu;
3838
use crate::hypervisor::virtual_machine::{HypervisorError, RegisterError, VirtualMachine};
39-
use crate::mem::layout::SandboxMemoryLayout;
39+
use crate::mem::layout::BaseGpaRegion;
4040
use crate::mem::memory_region::MemoryRegion;
4141
use crate::mem::mgr::SandboxMemoryManager;
42-
use crate::mem::shared_mem::{HostSharedMemory, SharedMemory};
42+
use crate::mem::shared_mem::HostSharedMemory;
4343

4444
#[derive(Debug, Error)]
4545
pub enum GdbTargetError {
@@ -95,18 +95,11 @@ pub enum DebugMemoryAccessError {
9595
LockFailed(&'static str, u32, String),
9696
#[error("Failed to translate guest address {0:#x}")]
9797
TranslateGuestAddress(u64),
98+
#[error("Failed to write to read-only region")]
99+
WriteToReadOnly,
98100
}
99101

100102
impl DebugMemoryAccess {
101-
// TODO: There is a lot of common logic between both of these
102-
// functions, as well as guest_page/access_gpa in snapshot.rs. It
103-
// would be nice to factor that out at some point, but the
104-
// snapshot versions deal with ExclusiveSharedMemory, since we
105-
// never expect a guest to be running concurrent with a snapshot,
106-
// and doesn't want to make unnecessary copies, since it runs over
107-
// relatively large volumes of data, so it's not clear if it's
108-
// terribly easy to combine them
109-
110103
/// Reads memory from the guest's address space with a maximum length of a PAGE_SIZE
111104
///
112105
/// # Arguments
@@ -120,74 +113,17 @@ impl DebugMemoryAccess {
120113
data: &mut [u8],
121114
gpa: u64,
122115
) -> std::result::Result<(), DebugMemoryAccessError> {
123-
let read_len = data.len();
124-
125-
let mem_offset = (gpa as usize)
126-
.checked_sub(SandboxMemoryLayout::BASE_ADDRESS)
127-
.ok_or_else(|| {
128-
log::warn!(
129-
"gpa={:#X} causes subtract with underflow: \"gpa - BASE_ADDRESS={:#X}-{:#X}\"",
130-
gpa,
131-
gpa,
132-
SandboxMemoryLayout::BASE_ADDRESS
133-
);
134-
DebugMemoryAccessError::TranslateGuestAddress(gpa)
135-
})?;
136-
137-
// First check the mapped memory regions to see if the address is within any of them
138-
let mut region_found = false;
139-
for reg in self.guest_mmap_regions.iter() {
140-
if reg.guest_region.contains(&mem_offset) {
141-
log::debug!("Found mapped region containing {:X}: {:#?}", gpa, reg);
142-
143-
// Region found - calculate the offset within the region
144-
let region_offset = mem_offset.checked_sub(reg.guest_region.start).ok_or_else(|| {
145-
log::warn!(
146-
"Cannot calculate offset in memory region: mem_offset={:#X}, base={:#X}",
147-
mem_offset,
148-
reg.guest_region.start,
149-
);
150-
DebugMemoryAccessError::TranslateGuestAddress(mem_offset as u64)
151-
})?;
152-
153-
let host_start_ptr = <_ as Into<usize>>::into(reg.host_region.start);
154-
let bytes: &[u8] = unsafe {
155-
slice::from_raw_parts(host_start_ptr as *const u8, reg.guest_region.len())
156-
};
157-
data[..read_len].copy_from_slice(&bytes[region_offset..region_offset + read_len]);
158-
159-
region_found = true;
160-
break;
161-
}
162-
}
163-
164-
if !region_found {
165-
let mut mgr = self
166-
.dbg_mem_access_fn
167-
.try_lock()
168-
.map_err(|e| DebugMemoryAccessError::LockFailed(file!(), line!(), e.to_string()))?;
169-
let scratch_base =
170-
hyperlight_common::layout::scratch_base_gpa(mgr.scratch_mem.mem_size());
171-
let (mem, offset, name): (&mut HostSharedMemory, _, _) = if gpa >= scratch_base {
172-
(
173-
&mut mgr.scratch_mem,
174-
(gpa - scratch_base) as usize,
175-
"scratch",
176-
)
177-
} else {
178-
(&mut mgr.shared_mem, mem_offset, "snapshot")
179-
};
180-
log::debug!(
181-
"No mapped region found containing {:X}. Trying {} memory at offset {:X} ...",
182-
gpa,
183-
name,
184-
offset
185-
);
186-
mem.copy_to_slice(&mut data[..read_len], offset)
187-
.map_err(|e| DebugMemoryAccessError::CopyFailed(Box::new(e)))?;
188-
}
189-
190-
Ok(())
116+
let mgr = self
117+
.dbg_mem_access_fn
118+
.try_lock()
119+
.map_err(|e| DebugMemoryAccessError::LockFailed(file!(), line!(), e.to_string()))?;
120+
121+
mgr.layout
122+
.resolve_gpa(gpa, &self.guest_mmap_regions)
123+
.ok_or(DebugMemoryAccessError::TranslateGuestAddress(gpa))?
124+
.with_memories(&mgr.shared_mem, &mgr.scratch_mem)
125+
.copy_to_slice(data)
126+
.map_err(|e| DebugMemoryAccessError::CopyFailed(Box::new(e)))
191127
}
192128

193129
/// Writes memory from the guest's address space with a maximum length of a PAGE_SIZE
@@ -203,74 +139,30 @@ impl DebugMemoryAccess {
203139
data: &[u8],
204140
gpa: u64,
205141
) -> std::result::Result<(), DebugMemoryAccessError> {
206-
let write_len = data.len();
207-
208-
let mem_offset = (gpa as usize)
209-
.checked_sub(SandboxMemoryLayout::BASE_ADDRESS)
210-
.ok_or_else(|| {
211-
log::warn!(
212-
"gpa={:#X} causes subtract with underflow: \"gpa - BASE_ADDRESS={:#X}-{:#X}\"",
213-
gpa,
214-
gpa,
215-
SandboxMemoryLayout::BASE_ADDRESS
216-
);
217-
DebugMemoryAccessError::TranslateGuestAddress(gpa)
218-
})?;
219-
220-
// First check the mapped memory regions to see if the address is within any of them
221-
let mut region_found = false;
222-
for reg in self.guest_mmap_regions.iter() {
223-
if reg.guest_region.contains(&mem_offset) {
224-
log::debug!("Found mapped region containing {:X}: {:#?}", gpa, reg);
225-
226-
// Region found - calculate the offset within the region
227-
let region_offset = mem_offset.checked_sub(reg.guest_region.start).ok_or_else(|| {
228-
log::warn!(
229-
"Cannot calculate offset in memory region: mem_offset={:#X}, base={:#X}",
230-
mem_offset,
231-
reg.guest_region.start,
232-
);
233-
DebugMemoryAccessError::TranslateGuestAddress(mem_offset as u64)
234-
})?;
235-
236-
let host_start_ptr = <_ as Into<usize>>::into(reg.host_region.start);
237-
let bytes: &mut [u8] = unsafe {
238-
slice::from_raw_parts_mut(host_start_ptr as *mut u8, reg.guest_region.len())
239-
};
240-
bytes[region_offset..region_offset + write_len].copy_from_slice(&data[..write_len]);
241-
242-
region_found = true;
243-
break;
244-
}
245-
}
246-
247-
if !region_found {
248-
let mut mgr = self
249-
.dbg_mem_access_fn
250-
.try_lock()
251-
.map_err(|e| DebugMemoryAccessError::LockFailed(file!(), line!(), e.to_string()))?;
252-
let scratch_base =
253-
hyperlight_common::layout::scratch_base_gpa(mgr.scratch_mem.mem_size());
254-
let (mem, offset, name): (&mut HostSharedMemory, _, _) = if gpa >= scratch_base {
255-
(
256-
&mut mgr.scratch_mem,
257-
(gpa - scratch_base) as usize,
258-
"scratch",
259-
)
260-
} else {
261-
(&mut mgr.shared_mem, mem_offset, "snapshot")
262-
};
263-
log::debug!(
264-
"No mapped region found containing {:X}. Trying {} memory at offset {:X} ...",
265-
gpa,
266-
name,
267-
offset
268-
);
269-
mem.copy_from_slice(&data[..write_len], offset)
270-
.map_err(|e| DebugMemoryAccessError::CopyFailed(Box::new(e)))?;
142+
let mgr = self
143+
.dbg_mem_access_fn
144+
.try_lock()
145+
.map_err(|e| DebugMemoryAccessError::LockFailed(file!(), line!(), e.to_string()))?;
146+
147+
let resolved = mgr
148+
.layout
149+
.resolve_gpa(gpa, &self.guest_mmap_regions)
150+
.ok_or(DebugMemoryAccessError::TranslateGuestAddress(gpa))?;
151+
152+
// We can only safely write (without causing UB in the host
153+
// process) if the address is in the scratch region
154+
match resolved.base {
155+
#[cfg(unshared_snapshot_mem)]
156+
BaseGpaRegion::Snapshot(()) => mgr
157+
.shared_mem
158+
.copy_from_slice(data, resolved.offset)
159+
.map_err(|e| DebugMemoryAccessError::CopyFailed(Box::new(e))),
160+
BaseGpaRegion::Scratch(()) => mgr
161+
.scratch_mem
162+
.copy_from_slice(data, resolved.offset)
163+
.map_err(|e| DebugMemoryAccessError::CopyFailed(Box::new(e))),
164+
_ => Err(DebugMemoryAccessError::WriteToReadOnly),
271165
}
272-
273-
Ok(())
274166
}
275167
}
276168

@@ -490,6 +382,7 @@ mod tests {
490382
use hyperlight_testing::dummy_guest_as_string;
491383

492384
use super::*;
385+
use crate::mem::layout::SandboxMemoryLayout;
493386
use crate::mem::memory_region::{MemoryRegionFlags, MemoryRegionType};
494387
use crate::sandbox::UninitializedSandbox;
495388
use crate::sandbox::uninitialized::GuestBinary;

src/hyperlight_host/src/hypervisor/hyperlight_vm/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use crate::hypervisor::virtual_machine::{
4747
};
4848
use crate::hypervisor::{InterruptHandle, InterruptHandleImpl};
4949
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
50-
use crate::mem::mgr::SandboxMemoryManager;
50+
use crate::mem::mgr::{SandboxMemoryManager, SnapshotSharedMemory};
5151
use crate::mem::shared_mem::{GuestSharedMemory, HostSharedMemory, SharedMemory};
5252
use crate::metrics::{METRIC_ERRONEOUS_VCPU_KICKS, METRIC_GUEST_CANCELLATION};
5353
use crate::sandbox::host_funcs::FunctionRegistry;
@@ -375,7 +375,7 @@ pub(crate) struct HyperlightVm {
375375
pub(super) snapshot_slot: u32,
376376
// The current snapshot region, used to keep it alive as long as
377377
// it is used & when unmapping
378-
pub(super) snapshot_memory: Option<GuestSharedMemory>,
378+
pub(super) snapshot_memory: Option<SnapshotSharedMemory<GuestSharedMemory>>,
379379
pub(super) scratch_slot: u32, // The slot number used for the scratch region
380380
// The current scratch region, used to keep it alive as long as it
381381
// is used & when unmapping
@@ -460,7 +460,7 @@ impl HyperlightVm {
460460
/// Update the snapshot mapping to point to a new GuestSharedMemory
461461
pub(crate) fn update_snapshot_mapping(
462462
&mut self,
463-
snapshot: GuestSharedMemory,
463+
snapshot: SnapshotSharedMemory<GuestSharedMemory>,
464464
) -> Result<(), UpdateRegionError> {
465465
let guest_base = crate::mem::layout::SandboxMemoryLayout::BASE_ADDRESS as u64;
466466
let rgn = snapshot.mapping_at(guest_base, MemoryRegionType::Snapshot);

src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ impl HyperlightVm {
7474
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
7575
#[allow(clippy::too_many_arguments)]
7676
pub(crate) fn new(
77-
snapshot_mem: GuestSharedMemory,
77+
snapshot_mem: SnapshotSharedMemory<GuestSharedMemory>,
7878
scratch_mem: GuestSharedMemory,
7979
_pml4_addr: u64,
8080
entrypoint: NextAction,
@@ -885,7 +885,7 @@ mod tests {
885885
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegionFlags};
886886
use crate::mem::mgr::{GuestPageTableBuffer, SandboxMemoryManager};
887887
use crate::mem::ptr::RawPtr;
888-
use crate::mem::shared_mem::ExclusiveSharedMemory;
888+
use crate::mem::shared_mem::{ExclusiveSharedMemory, ReadonlySharedMemory};
889889
use crate::sandbox::SandboxConfiguration;
890890
use crate::sandbox::host_funcs::FunctionRegistry;
891891
#[cfg(any(crashdump, gdb))]
@@ -1479,20 +1479,22 @@ mod tests {
14791479
layout.set_pt_size(pt_bytes.len()).unwrap();
14801480

14811481
let mem_size = layout.get_memory_size().unwrap();
1482-
let mut eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
1482+
let mut snapshot_contents = vec![0u8; mem_size];
14831483
let snapshot_pt_start = mem_size - layout.get_pt_size();
1484-
eshm.copy_from_slice(&pt_bytes, snapshot_pt_start).unwrap();
1485-
eshm.copy_from_slice(code, layout.get_guest_code_offset())
1486-
.unwrap();
1484+
snapshot_contents[snapshot_pt_start..].copy_from_slice(&pt_bytes);
1485+
snapshot_contents
1486+
[layout.get_guest_code_offset()..layout.get_guest_code_offset() + code.len()]
1487+
.copy_from_slice(code);
1488+
layout.write_peb(&mut snapshot_contents).unwrap();
1489+
let ro_mem = ReadonlySharedMemory::from_bytes(&snapshot_contents).unwrap();
14871490

14881491
let scratch_mem = ExclusiveSharedMemory::new(config.get_scratch_size()).unwrap();
1489-
let mut mem_mgr = SandboxMemoryManager::new(
1492+
let mem_mgr = SandboxMemoryManager::new(
14901493
layout,
1491-
eshm,
1494+
ro_mem.to_mgr_snapshot_mem().unwrap(),
14921495
scratch_mem,
14931496
NextAction::Initialise(layout.get_guest_code_address() as u64),
14941497
);
1495-
mem_mgr.write_memory_layout().unwrap();
14961498

14971499
let (mut hshm, gshm) = mem_mgr.build().unwrap();
14981500

0 commit comments

Comments
 (0)