Skip to content

Commit 9f25a1b

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 2143b3c commit 9f25a1b

12 files changed

Lines changed: 541 additions & 453 deletions

File tree

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-
tracing::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-
tracing::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-
tracing::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-
tracing::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-
tracing::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-
tracing::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-
tracing::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-
tracing::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,
@@ -891,7 +891,7 @@ mod tests {
891891
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegionFlags};
892892
use crate::mem::mgr::{GuestPageTableBuffer, SandboxMemoryManager};
893893
use crate::mem::ptr::RawPtr;
894-
use crate::mem::shared_mem::ExclusiveSharedMemory;
894+
use crate::mem::shared_mem::{ExclusiveSharedMemory, ReadonlySharedMemory};
895895
use crate::sandbox::SandboxConfiguration;
896896
use crate::sandbox::host_funcs::FunctionRegistry;
897897
#[cfg(any(crashdump, gdb))]
@@ -1485,20 +1485,22 @@ mod tests {
14851485
layout.set_pt_size(pt_bytes.len()).unwrap();
14861486

14871487
let mem_size = layout.get_memory_size().unwrap();
1488-
let mut eshm = ExclusiveSharedMemory::new(mem_size).unwrap();
1488+
let mut snapshot_contents = vec![0u8; mem_size];
14891489
let snapshot_pt_start = mem_size - layout.get_pt_size();
1490-
eshm.copy_from_slice(&pt_bytes, snapshot_pt_start).unwrap();
1491-
eshm.copy_from_slice(code, layout.get_guest_code_offset())
1492-
.unwrap();
1490+
snapshot_contents[snapshot_pt_start..].copy_from_slice(&pt_bytes);
1491+
snapshot_contents
1492+
[layout.get_guest_code_offset()..layout.get_guest_code_offset() + code.len()]
1493+
.copy_from_slice(code);
1494+
layout.write_peb(&mut snapshot_contents).unwrap();
1495+
let ro_mem = ReadonlySharedMemory::from_bytes(&snapshot_contents).unwrap();
14931496

14941497
let scratch_mem = ExclusiveSharedMemory::new(config.get_scratch_size()).unwrap();
1495-
let mut mem_mgr = SandboxMemoryManager::new(
1498+
let mem_mgr = SandboxMemoryManager::new(
14961499
layout,
1497-
eshm,
1500+
ro_mem.to_mgr_snapshot_mem().unwrap(),
14981501
scratch_mem,
14991502
NextAction::Initialise(layout.get_guest_code_address() as u64),
15001503
);
1501-
mem_mgr.write_memory_layout().unwrap();
15021504

15031505
let (mut hshm, gshm) = mem_mgr.build().unwrap();
15041506

0 commit comments

Comments
 (0)