@@ -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 ( ) ,
@@ -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 {
0 commit comments