Skip to content

Commit ea6be2e

Browse files
committed
Add API for getting VM's dirty pages, and add some bitmap utility functions.
Signed-off-by: Ludvig Liljenberg <[email protected]>
1 parent ea6fa8f commit ea6be2e

File tree

7 files changed

+307
-6
lines changed

7 files changed

+307
-6
lines changed

src/hyperlight_common/src/mem.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
pub const PAGE_SHIFT: u64 = 12;
1818
pub const PAGE_SIZE: u64 = 1 << 12;
1919
pub const PAGE_SIZE_USIZE: usize = 1 << 12;
20+
// The number of pages in 1 "block". A single u64 can be used as bitmap to keep track of all dirty pages in a block.
21+
pub const PAGES_IN_BLOCK: usize = 64;
2022

2123
/// A memory region in the guest address space
2224
#[derive(Debug, Clone, Copy)]

src/hyperlight_host/src/hypervisor/hyperv_linux.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ use std::sync::Arc;
2929
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
3030

3131
use log::{LevelFilter, error};
32+
#[cfg(mshv3)]
33+
use mshv_bindings::MSHV_GPAP_ACCESS_OP_CLEAR;
3234
#[cfg(mshv2)]
3335
use mshv_bindings::hv_message;
3436
use mshv_bindings::{
@@ -76,6 +78,9 @@ use crate::sandbox::SandboxConfiguration;
7678
use crate::sandbox::uninitialized::SandboxRuntimeConfig;
7779
use crate::{Result, log_then_return, new_error};
7880

81+
#[cfg(mshv2)]
82+
const CLEAR_DIRTY_BIT_FLAG: u64 = 0b100;
83+
7984
#[cfg(gdb)]
8085
mod debug {
8186
use std::sync::{Arc, Mutex};
@@ -351,6 +356,7 @@ impl HypervLinuxDriver {
351356
vm_fd.initialize()?;
352357
vm_fd
353358
};
359+
vm_fd.enable_dirty_page_tracking()?;
354360

355361
let mut vcpu_fd = vm_fd.create_vcpu(0)?;
356362

@@ -391,13 +397,31 @@ impl HypervLinuxDriver {
391397
(None, None)
392398
};
393399

400+
let mut base_pfn = u64::MAX;
401+
let mut total_size: usize = 0;
402+
394403
mem_regions.iter().try_for_each(|region| {
395-
let mshv_region = region.to_owned().into();
404+
let mshv_region: mshv_user_mem_region = region.to_owned().into();
405+
if base_pfn == u64::MAX {
406+
base_pfn = mshv_region.guest_pfn;
407+
}
408+
total_size += mshv_region.size as usize;
396409
vm_fd.map_user_memory(mshv_region)
397410
})?;
398411

399412
Self::setup_initial_sregs(&mut vcpu_fd, pml4_ptr.absolute()?)?;
400413

414+
// get/clear the dirty page bitmap, mshv sets all the bit dirty at initialization
415+
// if we dont clear them then we end up taking a complete snapsot of memory page by page which gets
416+
// progressively slower as the sandbox size increases
417+
// the downside of doing this here is that the call to get_dirty_log will takes longer as the number of pages increase
418+
// but for larger sandboxes its easily cheaper than copying all the pages
419+
420+
#[cfg(mshv2)]
421+
vm_fd.get_dirty_log(base_pfn, total_size, CLEAR_DIRTY_BIT_FLAG)?;
422+
#[cfg(mshv3)]
423+
vm_fd.get_dirty_log(base_pfn, total_size, MSHV_GPAP_ACCESS_OP_CLEAR as u8)?;
424+
401425
let interrupt_handle = Arc::new(LinuxInterruptHandle {
402426
running: AtomicU64::new(0),
403427
cancel_requested: AtomicBool::new(false),
@@ -863,6 +887,27 @@ impl Hypervisor for HypervLinuxDriver {
863887
self.interrupt_handle.clone()
864888
}
865889

890+
fn get_and_clear_dirty_pages(&mut self) -> Result<Vec<u64>> {
891+
let first_mshv_region: mshv_user_mem_region = self
892+
.mem_regions
893+
.first()
894+
.ok_or(new_error!(
895+
"tried to get dirty page bitmap of 0-sized region"
896+
))?
897+
.to_owned()
898+
.into();
899+
let total_size = self.mem_regions.iter().map(|r| r.guest_region.len()).sum();
900+
let res = self.vm_fd.get_dirty_log(
901+
first_mshv_region.guest_pfn,
902+
total_size,
903+
#[cfg(mshv2)]
904+
CLEAR_DIRTY_BIT_FLAG,
905+
#[cfg(mshv3)]
906+
(MSHV_GPAP_ACCESS_OP_CLEAR as u8),
907+
)?;
908+
Ok(res)
909+
}
910+
866911
#[cfg(crashdump)]
867912
fn crashdump_context(&self) -> Result<Option<super::crashdump::CrashDumpContext>> {
868913
if self.rt_cfg.guest_core_dump {

src/hyperlight_host/src/hypervisor/hyperv_windows.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ use super::{
5555
use super::{HyperlightExit, Hypervisor, InterruptHandle, VirtualCPU};
5656
use crate::hypervisor::fpu::FP_CONTROL_WORD_DEFAULT;
5757
use crate::hypervisor::wrappers::WHvGeneralRegisters;
58+
use crate::mem::bitmap::new_page_bitmap;
5859
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
5960
use crate::mem::ptr::{GuestPtr, RawPtr};
6061
#[cfg(crashdump)]
@@ -606,6 +607,12 @@ impl Hypervisor for HypervWindowsDriver {
606607
Ok(())
607608
}
608609

610+
fn get_and_clear_dirty_pages(&mut self) -> Result<Vec<u64>> {
611+
// For now we just mark all pages dirty which is the equivalent of taking a full snapshot
612+
let total_size = self.mem_regions.iter().map(|r| r.guest_region.len()).sum();
613+
new_page_bitmap(total_size, true)
614+
}
615+
609616
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
610617
unsafe fn map_region(&mut self, _rgn: &MemoryRegion) -> Result<()> {
611618
log_then_return!("Mapping host memory into the guest not yet supported on this platform");

src/hyperlight_host/src/hypervisor/kvm.rs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ use std::sync::Arc;
2121
use std::sync::Mutex;
2222
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
2323

24-
use kvm_bindings::{KVM_MEM_READONLY, kvm_fpu, kvm_regs, kvm_userspace_memory_region};
24+
use hyperlight_common::mem::{PAGE_SIZE_USIZE, PAGES_IN_BLOCK};
25+
use kvm_bindings::{
26+
KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, kvm_fpu, kvm_regs, kvm_userspace_memory_region,
27+
};
2528
use kvm_ioctls::Cap::UserMemory;
2629
use kvm_ioctls::{Kvm, VcpuExit, VcpuFd, VmFd};
2730
use log::LevelFilter;
@@ -43,7 +46,8 @@ use super::{
4346
use super::{HyperlightExit, Hypervisor, InterruptHandle, LinuxInterruptHandle, VirtualCPU};
4447
#[cfg(gdb)]
4548
use crate::HyperlightError;
46-
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
49+
use crate::mem::bitmap::{bit_index_iterator, new_page_bitmap};
50+
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
4751
use crate::mem::ptr::{GuestPtr, RawPtr};
4852
use crate::sandbox::SandboxConfiguration;
4953
#[cfg(crashdump)]
@@ -284,7 +288,7 @@ mod debug {
284288
/// A Hypervisor driver for KVM on Linux
285289
pub(crate) struct KVMDriver {
286290
_kvm: Kvm,
287-
_vm_fd: VmFd,
291+
vm_fd: VmFd,
288292
vcpu_fd: VcpuFd,
289293
entrypoint: u64,
290294
orig_rsp: GuestPtr,
@@ -329,7 +333,7 @@ impl KVMDriver {
329333
userspace_addr: region.host_region.start as u64,
330334
flags: match perm_flags {
331335
MemoryRegionFlags::READ => KVM_MEM_READONLY,
332-
_ => 0, // normal, RWX
336+
_ => KVM_MEM_LOG_DIRTY_PAGES, // normal, RWX
333337
},
334338
};
335339
unsafe { vm_fd.set_user_memory_region(kvm_region) }
@@ -378,7 +382,7 @@ impl KVMDriver {
378382
#[allow(unused_mut)]
379383
let mut hv = Self {
380384
_kvm: kvm,
381-
_vm_fd: vm_fd,
385+
vm_fd,
382386
vcpu_fd,
383387
entrypoint,
384388
orig_rsp: rsp_gp,
@@ -734,6 +738,45 @@ impl Hypervisor for KVMDriver {
734738
self.interrupt_handle.clone()
735739
}
736740

741+
fn get_and_clear_dirty_pages(&mut self) -> Result<Vec<u64>> {
742+
let mut page_indices = vec![];
743+
let mut current_page = 0;
744+
// Iterate over all memory regions and get the dirty pages for each region ignoring guard pages which cannot be dirty
745+
for (i, mem_region) in self.mem_regions.iter().enumerate() {
746+
let num_pages = mem_region.guest_region.len() / PAGE_SIZE_USIZE;
747+
let bitmap = match mem_region.flags {
748+
MemoryRegionFlags::READ => {
749+
// read-only page. It can never be dirty so return zero dirty pages.
750+
new_page_bitmap(mem_region.guest_region.len(), false)?
751+
}
752+
_ => {
753+
if mem_region.region_type == MemoryRegionType::GuardPage {
754+
// Trying to get dirty pages for a guard page region results in a VMMSysError(2)
755+
new_page_bitmap(mem_region.guest_region.len(), false)?
756+
} else {
757+
// Get the dirty bitmap for the memory region
758+
self.vm_fd
759+
.get_dirty_log(i as u32, mem_region.guest_region.len())?
760+
}
761+
}
762+
};
763+
for page_idx in bit_index_iterator(&bitmap) {
764+
page_indices.push(current_page + page_idx);
765+
}
766+
current_page += num_pages;
767+
}
768+
769+
// covert vec of page indices to vec of blocks
770+
let mut res = new_page_bitmap(current_page * PAGE_SIZE_USIZE, false)?;
771+
for page_idx in page_indices {
772+
let block_idx = page_idx / PAGES_IN_BLOCK;
773+
let bit_idx = page_idx % PAGES_IN_BLOCK;
774+
res[block_idx] |= 1 << bit_idx;
775+
}
776+
777+
Ok(res)
778+
}
779+
737780
#[cfg(crashdump)]
738781
fn crashdump_context(&self) -> Result<Option<crashdump::CrashDumpContext>> {
739782
if self.rt_cfg.guest_core_dump {

src/hyperlight_host/src/hypervisor/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,11 @@ pub(crate) trait Hypervisor: Debug + Sync + Send {
196196
None
197197
}
198198

199+
/// Get dirty pages as a bitmap (Vec<u64>).
200+
/// Each bit in a u64 represents a page.
201+
/// This also clears the bitflags, marking the pages as non-dirty.
202+
fn get_and_clear_dirty_pages(&mut self) -> Result<Vec<u64>>;
203+
199204
/// Get InterruptHandle to underlying VM
200205
fn interrupt_handle(&self) -> Arc<dyn InterruptHandle>;
201206

@@ -506,6 +511,7 @@ pub(crate) mod tests {
506511
use super::handlers::{MemAccessHandler, OutBHandler};
507512
#[cfg(gdb)]
508513
use crate::hypervisor::DbgMemAccessHandlerCaller;
514+
use crate::mem::dirty_page_tracking::DirtyPageTracking;
509515
use crate::mem::ptr::RawPtr;
510516
use crate::sandbox::uninitialized::GuestBinary;
511517
#[cfg(any(crashdump, gdb))]
@@ -557,7 +563,10 @@ pub(crate) mod tests {
557563
let rt_cfg: SandboxRuntimeConfig = Default::default();
558564
let sandbox =
559565
UninitializedSandbox::new(GuestBinary::FilePath(filename.clone()), Some(config))?;
566+
let tracker = sandbox.tracker.unwrap();
560567
let (_hshm, mut gshm) = sandbox.mgr.build();
568+
// we need to undo the mprotect(readonly) before mapping memory into vm, and that is done by getting dirty pages
569+
let _ = tracker.get_dirty_pages()?;
561570
let mut vm = set_up_hypervisor_partition(
562571
&mut gshm,
563572
&config,

0 commit comments

Comments
 (0)