@@ -29,6 +29,8 @@ use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
2929use std:: sync:: { Arc , Mutex } ;
3030
3131use log:: { LevelFilter , error} ;
32+ #[ cfg( mshv3) ]
33+ use mshv_bindings:: MSHV_GPAP_ACCESS_OP_CLEAR ;
3234#[ cfg( mshv2) ]
3335use mshv_bindings:: hv_message;
3436use mshv_bindings:: {
@@ -89,6 +91,9 @@ use crate::sandbox::outb::handle_outb;
8991use crate :: sandbox:: uninitialized:: SandboxRuntimeConfig ;
9092use crate :: { Result , log_then_return, new_error} ;
9193
94+ #[ cfg( mshv2) ]
95+ const CLEAR_DIRTY_BIT_FLAG : u64 = 0b100 ;
96+
9297#[ cfg( gdb) ]
9398mod 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 ( ) ,
@@ -965,6 +991,42 @@ impl Hypervisor for HypervLinuxDriver {
965991 self . interrupt_handle . clone ( )
966992 }
967993
994+ // TODO: Implement getting additional host-mapped dirty pages.
995+ fn get_and_clear_dirty_pages ( & mut self ) -> Result < Vec < u64 > > {
996+ let first_mshv_region: mshv_user_mem_region = self
997+ . sandbox_regions
998+ . first ( )
999+ . ok_or ( new_error ! (
1000+ "tried to get dirty page bitmap of 0-sized region"
1001+ ) ) ?
1002+ . to_owned ( )
1003+ . into ( ) ;
1004+
1005+ let mut sandbox_dirty_pages = self . vm_fd . get_dirty_log (
1006+ first_mshv_region. guest_pfn ,
1007+ self . sandbox_size ,
1008+ #[ cfg( mshv2) ]
1009+ CLEAR_DIRTY_BIT_FLAG ,
1010+ #[ cfg( mshv3) ]
1011+ ( MSHV_GPAP_ACCESS_OP_CLEAR as u8 ) ,
1012+ ) ?;
1013+
1014+ // Sanitize bits beyond sandbox
1015+ //
1016+ // TODO: remove this once bug in mshv is fixed. The bug makes it possible
1017+ // for non-mapped memory to incorrectly be marked dirty. To fix this, we just zero out
1018+ // any bits that are not within the sandbox size.
1019+ let sandbox_pages = self . sandbox_size / self . page_size ;
1020+ if let Some ( last_block) = sandbox_dirty_pages. last_mut ( ) {
1021+ let mask = match sandbox_pages % 64 {
1022+ 0 => u64:: MAX ,
1023+ tail_bits => ( 1u64 << tail_bits) - 1 ,
1024+ } ;
1025+ * last_block &= mask;
1026+ }
1027+ Ok ( sandbox_dirty_pages)
1028+ }
1029+
9681030 #[ cfg( crashdump) ]
9691031 fn crashdump_context ( & self ) -> Result < Option < super :: crashdump:: CrashDumpContext > > {
9701032 if self . rt_cfg . guest_core_dump {
0 commit comments