Skip to content

Commit 466df5b

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 480651e commit 466df5b

File tree

8 files changed

+300
-4
lines changed

8 files changed

+300
-4
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: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
2929
use std::sync::{Arc, Mutex};
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::{
@@ -89,6 +91,9 @@ use crate::sandbox::outb::handle_outb;
8991
use crate::sandbox::uninitialized::SandboxRuntimeConfig;
9092
use crate::{Result, log_then_return, new_error};
9193

94+
#[cfg(mshv2)]
95+
const CLEAR_DIRTY_BIT_FLAG: u64 = 0b100;
96+
9297
#[cfg(gdb)]
9398
mod debug {
9499
use std::sync::{Arc, Mutex};
@@ -319,6 +324,7 @@ pub(crate) struct HypervLinuxDriver {
319324
mem_mgr: Option<MemMgrWrapper<HostSharedMemory>>,
320325
host_funcs: Option<Arc<Mutex<FunctionRegistry>>>,
321326

327+
sandbox_size: usize,
322328
sandbox_regions: Vec<MemoryRegion>, // Initially mapped regions when sandbox is created
323329
mmap_regions: Vec<MemoryRegion>, // Later mapped regions
324330

@@ -374,6 +380,7 @@ impl HypervLinuxDriver {
374380
vm_fd.initialize()?;
375381
vm_fd
376382
};
383+
vm_fd.enable_dirty_page_tracking()?;
377384

378385
let mut vcpu_fd = vm_fd.create_vcpu(0)?;
379386

@@ -414,13 +421,31 @@ impl HypervLinuxDriver {
414421
(None, None)
415422
};
416423

424+
let mut base_pfn = u64::MAX;
425+
let mut total_size: usize = 0;
426+
417427
mem_regions.iter().try_for_each(|region| {
418-
let mshv_region = region.to_owned().into();
428+
let mshv_region: mshv_user_mem_region = region.to_owned().into();
429+
if base_pfn == u64::MAX {
430+
base_pfn = mshv_region.guest_pfn;
431+
}
432+
total_size += mshv_region.size as usize;
419433
vm_fd.map_user_memory(mshv_region)
420434
})?;
421435

422436
Self::setup_initial_sregs(&mut vcpu_fd, pml4_ptr.absolute()?)?;
423437

438+
// get/clear the dirty page bitmap, mshv sets all the bit dirty at initialization
439+
// if we dont clear them then we end up taking a complete snapsot of memory page by page which gets
440+
// progressively slower as the sandbox size increases
441+
// the downside of doing this here is that the call to get_dirty_log will takes longer as the number of pages increase
442+
// but for larger sandboxes its easily cheaper than copying all the pages
443+
444+
#[cfg(mshv2)]
445+
vm_fd.get_dirty_log(base_pfn, total_size, CLEAR_DIRTY_BIT_FLAG)?;
446+
#[cfg(mshv3)]
447+
vm_fd.get_dirty_log(base_pfn, total_size, MSHV_GPAP_ACCESS_OP_CLEAR as u8)?;
448+
424449
let interrupt_handle = Arc::new(LinuxInterruptHandle {
425450
running: AtomicU64::new(0),
426451
cancel_requested: AtomicBool::new(false),
@@ -453,6 +478,7 @@ impl HypervLinuxDriver {
453478
vcpu_fd,
454479
sandbox_regions: mem_regions,
455480
mmap_regions: Vec::new(),
481+
sandbox_size: total_size,
456482
entrypoint: entrypoint_ptr.absolute()?,
457483
orig_rsp: rsp_ptr,
458484
interrupt_handle: interrupt_handle.clone(),
@@ -964,6 +990,42 @@ impl Hypervisor for HypervLinuxDriver {
964990
self.interrupt_handle.clone()
965991
}
966992

993+
// TODO: Implement getting additional host-mapped dirty pages.
994+
fn get_and_clear_dirty_pages(&mut self) -> Result<Vec<u64>> {
995+
let first_mshv_region: mshv_user_mem_region = self
996+
.sandbox_regions
997+
.first()
998+
.ok_or(new_error!(
999+
"tried to get dirty page bitmap of 0-sized region"
1000+
))?
1001+
.to_owned()
1002+
.into();
1003+
1004+
let mut sandbox_dirty_pages = self.vm_fd.get_dirty_log(
1005+
first_mshv_region.guest_pfn,
1006+
self.sandbox_size,
1007+
#[cfg(mshv2)]
1008+
CLEAR_DIRTY_BIT_FLAG,
1009+
#[cfg(mshv3)]
1010+
(MSHV_GPAP_ACCESS_OP_CLEAR as u8),
1011+
)?;
1012+
1013+
// Sanitize bits beyond sandbox
1014+
//
1015+
// TODO: remove this once bug in mshv is fixed. The bug makes it possible
1016+
// for non-mapped memory to incorrectly be marked dirty. To fix this, we just zero out
1017+
// any bits that are not within the sandbox size.
1018+
let sandbox_pages = self.sandbox_size / self.page_size;
1019+
if let Some(last_block) = sandbox_dirty_pages.last_mut() {
1020+
let mask = match sandbox_pages % 64 {
1021+
0 => u64::MAX,
1022+
tail_bits => (1u64 << tail_bits) - 1,
1023+
};
1024+
*last_block &= mask;
1025+
}
1026+
Ok(sandbox_dirty_pages)
1027+
}
1028+
9671029
#[cfg(crashdump)]
9681030
fn crashdump_context(&self) -> Result<Option<super::crashdump::CrashDumpContext>> {
9691031
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
@@ -59,6 +59,7 @@ use super::{HyperlightExit, Hypervisor, InterruptHandle, VirtualCPU};
5959
use crate::hypervisor::fpu::FP_CONTROL_WORD_DEFAULT;
6060
use crate::hypervisor::get_memory_access_violation;
6161
use crate::hypervisor::wrappers::WHvGeneralRegisters;
62+
use crate::mem::bitmap::new_page_bitmap;
6263
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
6364
use crate::mem::ptr::{GuestPtr, RawPtr};
6465
use crate::mem::shared_mem::HostSharedMemory;
@@ -638,6 +639,12 @@ impl Hypervisor for HypervWindowsDriver {
638639
)
639640
}
640641

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

src/hyperlight_host/src/hypervisor/kvm.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use std::fmt::Debug;
1919
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
2020
use std::sync::{Arc, Mutex};
2121

22+
use hyperlight_common::mem::{PAGE_SIZE_USIZE, PAGES_IN_BLOCK};
2223
use kvm_bindings::{kvm_fpu, kvm_regs, kvm_userspace_memory_region};
2324
use kvm_ioctls::Cap::UserMemory;
2425
use kvm_ioctls::{Kvm, VcpuExit, VcpuFd, VmFd};
@@ -43,7 +44,8 @@ use super::{HyperlightExit, Hypervisor, InterruptHandle, LinuxInterruptHandle, V
4344
#[cfg(gdb)]
4445
use crate::HyperlightError;
4546
use crate::hypervisor::get_memory_access_violation;
46-
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
47+
use crate::mem::bitmap::{bit_index_iterator, new_page_bitmap};
48+
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
4749
use crate::mem::ptr::{GuestPtr, RawPtr};
4850
use crate::mem::shared_mem::HostSharedMemory;
4951
use crate::sandbox::SandboxConfiguration;
@@ -835,6 +837,47 @@ impl Hypervisor for KVMDriver {
835837
self.interrupt_handle.clone()
836838
}
837839

840+
// TODO: Implement getting additional host-mapped dirty pages.
841+
fn get_and_clear_dirty_pages(&mut self) -> Result<Vec<u64>> {
842+
let mut page_indices = vec![];
843+
let mut current_page = 0;
844+
845+
// Iterate over all memory regions and get the dirty pages for each region ignoring guard pages which cannot be dirty
846+
for (i, mem_region) in self.sandbox_regions.iter().enumerate() {
847+
let num_pages = mem_region.guest_region.len() / PAGE_SIZE_USIZE;
848+
let bitmap = match mem_region.flags {
849+
MemoryRegionFlags::READ => {
850+
// read-only page. It can never be dirty so return zero dirty pages.
851+
new_page_bitmap(mem_region.guest_region.len(), false)?
852+
}
853+
_ => {
854+
if mem_region.region_type == MemoryRegionType::GuardPage {
855+
// Trying to get dirty pages for a guard page region results in a VMMSysError(2)
856+
new_page_bitmap(mem_region.guest_region.len(), false)?
857+
} else {
858+
// Get the dirty bitmap for the memory region
859+
self.vm_fd
860+
.get_dirty_log(i as u32, mem_region.guest_region.len())?
861+
}
862+
}
863+
};
864+
for page_idx in bit_index_iterator(&bitmap) {
865+
page_indices.push(current_page + page_idx);
866+
}
867+
current_page += num_pages;
868+
}
869+
870+
// convert vec of page indices to vec of blocks
871+
let mut sandbox_dirty_pages = new_page_bitmap(current_page * PAGE_SIZE_USIZE, false)?;
872+
for page_idx in page_indices {
873+
let block_idx = page_idx / PAGES_IN_BLOCK;
874+
let bit_idx = page_idx % PAGES_IN_BLOCK;
875+
sandbox_dirty_pages[block_idx] |= 1 << bit_idx;
876+
}
877+
878+
Ok(sandbox_dirty_pages)
879+
}
880+
838881
#[cfg(crashdump)]
839882
fn crashdump_context(&self) -> Result<Option<crashdump::CrashDumpContext>> {
840883
if self.rt_cfg.guest_core_dump {

src/hyperlight_host/src/hypervisor/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ pub(crate) trait Hypervisor: Debug + Send {
190190
/// Run the vCPU
191191
fn run(&mut self) -> Result<HyperlightExit>;
192192

193+
/// Get dirty pages as a bitmap (Vec<u64>).
194+
/// Each bit in a u64 represents a page.
195+
/// This also clears the bitflags, marking the pages as non-dirty.
196+
/// TODO: Implement getting additional host-mapped dirty pages.
197+
fn get_and_clear_dirty_pages(&mut self) -> Result<Vec<u64>>;
198+
193199
/// Get InterruptHandle to underlying VM
194200
fn interrupt_handle(&self) -> Arc<dyn InterruptHandle>;
195201

0 commit comments

Comments
 (0)