@@ -19,9 +19,9 @@ use std::ffi::c_void;
1919use std:: io:: Error ;
2020#[ cfg( target_os = "linux" ) ]
2121use std:: ptr:: null_mut;
22- use std:: sync:: { Arc , RwLock } ;
22+ use std:: sync:: { Arc , Mutex , RwLock } ;
2323
24- use hyperlight_common:: mem:: PAGE_SIZE_USIZE ;
24+ use hyperlight_common:: mem:: { PAGE_SIZE_USIZE , PAGES_IN_BLOCK } ;
2525use tracing:: { Span , instrument} ;
2626#[ cfg( target_os = "windows" ) ]
2727use windows:: Win32 :: Foundation :: { CloseHandle , HANDLE , INVALID_HANDLE_VALUE } ;
@@ -78,8 +78,10 @@ macro_rules! generate_writer {
7878 #[ allow( dead_code) ]
7979 pub ( crate ) fn $fname( & mut self , offset: usize , value: $ty) -> Result <( ) > {
8080 let data = self . as_mut_slice( ) ;
81- bounds_check!( offset, std:: mem:: size_of:: <$ty>( ) , data. len( ) ) ;
82- data[ offset..offset + std:: mem:: size_of:: <$ty>( ) ] . copy_from_slice( & value. to_le_bytes( ) ) ;
81+ let size = std:: mem:: size_of:: <$ty>( ) ;
82+ bounds_check!( offset, size, data. len( ) ) ;
83+ data[ offset..offset + size] . copy_from_slice( & value. to_le_bytes( ) ) ;
84+ self . mark_pages_dirty( offset, size) ?;
8385 Ok ( ( ) )
8486 }
8587 } ;
@@ -133,6 +135,7 @@ impl Drop for HostMapping {
133135#[ derive( Debug ) ]
134136pub struct ExclusiveSharedMemory {
135137 region : Arc < HostMapping > ,
138+ dirty_page_tracker : Arc < Mutex < Vec < u64 > > > ,
136139}
137140unsafe impl Send for ExclusiveSharedMemory { }
138141
@@ -147,6 +150,8 @@ unsafe impl Send for ExclusiveSharedMemory {}
147150#[ derive( Debug ) ]
148151pub struct GuestSharedMemory {
149152 region : Arc < HostMapping > ,
153+ dirty_page_tracker : Arc < Mutex < Vec < u64 > > > ,
154+
150155 /// The lock that indicates this shared memory is being used by non-Rust code
151156 ///
152157 /// This lock _must_ be held whenever the guest is executing,
@@ -298,6 +303,8 @@ unsafe impl Send for GuestSharedMemory {}
298303#[ derive( Clone , Debug ) ]
299304pub struct HostSharedMemory {
300305 region : Arc < HostMapping > ,
306+ dirty_page_tracker : Arc < Mutex < Vec < u64 > > > ,
307+
301308 lock : Arc < RwLock < ( ) > > ,
302309}
303310unsafe impl Send for HostSharedMemory { }
@@ -315,7 +322,10 @@ impl ExclusiveSharedMemory {
315322 c_int, mmap, mprotect, off_t, size_t,
316323 } ;
317324
318- use crate :: error:: HyperlightError :: { MemoryRequestTooBig , MmapFailed , MprotectFailed } ;
325+ use crate :: {
326+ error:: HyperlightError :: { MemoryRequestTooBig , MmapFailed , MprotectFailed } ,
327+ mem:: bitmap:: new_page_bitmap,
328+ } ;
319329
320330 if min_size_bytes == 0 {
321331 return Err ( new_error ! ( "Cannot create shared memory with size 0" ) ) ;
@@ -370,23 +380,39 @@ impl ExclusiveSharedMemory {
370380 return Err ( MprotectFailed ( Error :: last_os_error ( ) . raw_os_error ( ) ) ) ;
371381 }
372382
383+ // HostMapping is only non-Send/Sync because raw pointers
384+ // are not ("as a lint", as the Rust docs say). We don't
385+ // want to mark HostMapping Send/Sync immediately, because
386+ // that could socially imply that it's "safe" to use
387+ // unsafe accesses from multiple threads at once. Instead, we
388+ // directly impl Send and Sync on this type. Since this
389+ // type does have Send and Sync manually impl'd, the Arc
390+ // is not pointless as the lint suggests.
391+ #[ allow( clippy:: arc_with_non_send_sync) ]
392+ let host_mapping = Arc :: new ( HostMapping {
393+ ptr : addr as * mut u8 ,
394+ size : total_size,
395+ } ) ;
396+
397+ let dirty_page_tracker = new_page_bitmap ( min_size_bytes, false ) ?;
398+
373399 Ok ( Self {
374- // HostMapping is only non-Send/Sync because raw pointers
375- // are not ("as a lint", as the Rust docs say). We don't
376- // want to mark HostMapping Send/Sync immediately, because
377- // that could socially imply that it's "safe" to use
378- // unsafe accesses from multiple threads at once. Instead, we
379- // directly impl Send and Sync on this type. Since this
380- // type does have Send and Sync manually impl'd, the Arc
381- // is not pointless as the lint suggests.
382- #[ allow( clippy:: arc_with_non_send_sync) ]
383- region : Arc :: new ( HostMapping {
384- ptr : addr as * mut u8 ,
385- size : total_size,
386- } ) ,
400+ region : host_mapping,
401+ dirty_page_tracker : Arc :: new ( Mutex :: new ( dirty_page_tracker) ) ,
387402 } )
388403 }
389404
405+ /// Gets the dirty bitmap and then clears it in self.
406+ pub ( crate ) fn get_and_clear_dirty_pages ( & mut self ) -> Result < Vec < u64 > > {
407+ let mut guard = self
408+ . dirty_page_tracker
409+ . try_lock ( )
410+ . map_err ( |_| new_error ! ( "Failed to acquire lock on dirty page tracker" ) ) ?;
411+ let bitmap = guard. clone ( ) ;
412+ guard. fill ( 0 ) ;
413+ Ok ( bitmap)
414+ }
415+
390416 /// Create a new region of shared memory with the given minimum
391417 /// size in bytes. The region will be surrounded by guard pages.
392418 ///
@@ -484,21 +510,28 @@ impl ExclusiveSharedMemory {
484510 log_then_return ! ( WindowsAPIError ( e. clone( ) ) ) ;
485511 }
486512
513+ // HostMapping is only non-Send/Sync because raw pointers
514+ // are not ("as a lint", as the Rust docs say). We don't
515+ // want to mark HostMapping Send/Sync immediately, because
516+ // that could socially imply that it's "safe" to use
517+ // unsafe accesses from multiple threads at once. Instead, we
518+ // directly impl Send and Sync on this type. Since this
519+ // type does have Send and Sync manually impl'd, the Arc
520+ // is not pointless as the lint suggests.
521+ #[ allow( clippy:: arc_with_non_send_sync) ]
522+ let host_mapping = Arc :: new ( HostMapping {
523+ ptr : addr. Value as * mut u8 ,
524+ size : total_size,
525+ handle,
526+ } ) ;
527+
528+ let dirty_page_tracker = Arc :: new ( Mutex :: new ( Some ( DirtyPageTracker :: new ( Arc :: clone (
529+ & host_mapping,
530+ ) ) ?) ) ) ;
531+
487532 Ok ( Self {
488- // HostMapping is only non-Send/Sync because raw pointers
489- // are not ("as a lint", as the Rust docs say). We don't
490- // want to mark HostMapping Send/Sync immediately, because
491- // that could socially imply that it's "safe" to use
492- // unsafe accesses from multiple threads at once. Instead, we
493- // directly impl Send and Sync on this type. Since this
494- // type does have Send and Sync manually impl'd, the Arc
495- // is not pointless as the lint suggests.
496- #[ allow( clippy:: arc_with_non_send_sync) ]
497- region : Arc :: new ( HostMapping {
498- ptr : addr. Value as * mut u8 ,
499- size : total_size,
500- handle,
501- } ) ,
533+ region : host_mapping,
534+ signal_dirty_bitmap_tracker : dirty_page_tracker,
502535 } )
503536 }
504537
@@ -613,6 +646,16 @@ impl ExclusiveSharedMemory {
613646 let data = self . as_mut_slice ( ) ;
614647 bounds_check ! ( offset, src. len( ) , data. len( ) ) ;
615648 data[ offset..offset + src. len ( ) ] . copy_from_slice ( src) ;
649+ self . mark_pages_dirty ( offset, src. len ( ) ) ?;
650+ Ok ( ( ) )
651+ }
652+
653+ /// Copies bytes from `self` to `dst` starting at offset
654+ #[ instrument( err( Debug ) , skip_all, parent = Span :: current( ) , level= "Trace" ) ]
655+ pub fn copy_to_slice ( & self , dst : & mut [ u8 ] , offset : usize ) -> Result < ( ) > {
656+ let data = self . as_slice ( ) ;
657+ bounds_check ! ( offset, dst. len( ) , data. len( ) ) ;
658+ dst. copy_from_slice ( & data[ offset..offset + dst. len ( ) ] ) ;
616659 Ok ( ( ) )
617660 }
618661
@@ -624,6 +667,16 @@ impl ExclusiveSharedMemory {
624667 Ok ( self . base_addr ( ) + offset)
625668 }
626669
670+ /// Fill the memory in the range `[offset, offset + len)` with `value`
671+ #[ instrument( err( Debug ) , skip_all, parent = Span :: current( ) , level= "Trace" ) ]
672+ pub fn zero_fill ( & mut self , offset : usize , len : usize ) -> Result < ( ) > {
673+ bounds_check ! ( offset, len, self . mem_size( ) ) ;
674+ let data = self . as_mut_slice ( ) ;
675+ data[ offset..offset + len] . fill ( 0 ) ;
676+ self . mark_pages_dirty ( offset, len) ?;
677+ Ok ( ( ) )
678+ }
679+
627680 generate_reader ! ( read_u8, u8 ) ;
628681 generate_reader ! ( read_i8, i8 ) ;
629682 generate_reader ! ( read_u16, u16 ) ;
@@ -657,15 +710,35 @@ impl ExclusiveSharedMemory {
657710 (
658711 HostSharedMemory {
659712 region : self . region . clone ( ) ,
713+ dirty_page_tracker : self . dirty_page_tracker . clone ( ) ,
660714 lock : lock. clone ( ) ,
661715 } ,
662716 GuestSharedMemory {
663717 region : self . region . clone ( ) ,
718+ dirty_page_tracker : self . dirty_page_tracker . clone ( ) ,
664719 lock : lock. clone ( ) ,
665720 } ,
666721 )
667722 }
668723
724+ /// Marks pages that cover bytes [offset, offset + size) as dirty
725+ pub ( super ) fn mark_pages_dirty ( & mut self , offset : usize , size : usize ) -> Result < ( ) > {
726+ bounds_check ! ( offset, size, self . mem_size( ) ) ;
727+ let mut bitmap = self
728+ . dirty_page_tracker
729+ . try_lock ( )
730+ . map_err ( |_| new_error ! ( "Failed to lock dirty page tracker" ) ) ?;
731+
732+ let start_page = offset / PAGE_SIZE_USIZE ;
733+ let end_page = ( offset + size - 1 ) / PAGE_SIZE_USIZE ; // offset + size - 1 is the last affected byte.
734+ for page_idx in start_page..=end_page {
735+ let block_idx = page_idx / PAGES_IN_BLOCK ;
736+ let bit_idx = page_idx % PAGES_IN_BLOCK ;
737+ bitmap[ block_idx] |= 1 << bit_idx;
738+ }
739+ Ok ( ( ) )
740+ }
741+
669742 /// Gets the file handle of the shared memory region for this Sandbox
670743 #[ cfg( target_os = "windows" ) ]
671744 pub fn get_mmap_file_handle ( & self ) -> HANDLE {
@@ -743,6 +816,7 @@ impl SharedMemory for GuestSharedMemory {
743816 fn region ( & self ) -> & HostMapping {
744817 & self . region
745818 }
819+
746820 fn with_exclusivity < T , F : FnOnce ( & mut ExclusiveSharedMemory ) -> T > (
747821 & mut self ,
748822 f : F ,
@@ -753,6 +827,7 @@ impl SharedMemory for GuestSharedMemory {
753827 . map_err ( |e| new_error ! ( "Error locking at {}:{}: {}" , file!( ) , line!( ) , e) ) ?;
754828 let mut excl = ExclusiveSharedMemory {
755829 region : self . region . clone ( ) ,
830+ dirty_page_tracker : self . dirty_page_tracker . clone ( ) ,
756831 } ;
757832 let ret = f ( & mut excl) ;
758833 drop ( excl) ;
@@ -803,7 +878,7 @@ impl HostSharedMemory {
803878 /// Write a value of type T, whose representation is the same
804879 /// between the sandbox and the host, and which has no invalid bit
805880 /// patterns
806- pub fn write < T : AllValid > ( & self , offset : usize , data : T ) -> Result < ( ) > {
881+ pub fn write < T : AllValid > ( & mut self , offset : usize , data : T ) -> Result < ( ) > {
807882 bounds_check ! ( offset, std:: mem:: size_of:: <T >( ) , self . mem_size( ) ) ;
808883 unsafe {
809884 let slice: & [ u8 ] = core:: slice:: from_raw_parts (
@@ -812,6 +887,7 @@ impl HostSharedMemory {
812887 ) ;
813888 self . copy_from_slice ( slice, offset) ?;
814889 }
890+ self . mark_pages_dirty ( offset, std:: mem:: size_of :: < T > ( ) ) ?;
815891 Ok ( ( ) )
816892 }
817893
@@ -834,9 +910,8 @@ impl HostSharedMemory {
834910 Ok ( ( ) )
835911 }
836912
837- /// Copy the contents of the sandbox at the specified offset into
838- /// the slice
839- pub fn copy_from_slice ( & self , slice : & [ u8 ] , offset : usize ) -> Result < ( ) > {
913+ /// Copy the contents of the given slice into self
914+ pub fn copy_from_slice ( & mut self , slice : & [ u8 ] , offset : usize ) -> Result < ( ) > {
840915 bounds_check ! ( offset, slice. len( ) , self . mem_size( ) ) ;
841916 let base = self . base_ptr ( ) . wrapping_add ( offset) ;
842917 let guard = self
@@ -850,6 +925,7 @@ impl HostSharedMemory {
850925 }
851926 }
852927 drop ( guard) ;
928+ self . mark_pages_dirty ( offset, slice. len ( ) ) ?;
853929 Ok ( ( ) )
854930 }
855931
@@ -867,6 +943,7 @@ impl HostSharedMemory {
867943 unsafe { base. wrapping_add ( i) . write_volatile ( value) } ;
868944 }
869945 drop ( guard) ;
946+ self . mark_pages_dirty ( offset, len) ?;
870947 Ok ( ( ) )
871948 }
872949
@@ -979,12 +1056,31 @@ impl HostSharedMemory {
9791056
9801057 Ok ( to_return)
9811058 }
1059+
1060+ /// Marks pages that cover bytes [offset, offset + size) as dirty
1061+ pub ( super ) fn mark_pages_dirty ( & mut self , offset : usize , size : usize ) -> Result < ( ) > {
1062+ bounds_check ! ( offset, size, self . mem_size( ) ) ;
1063+ let mut bitmap = self
1064+ . dirty_page_tracker
1065+ . try_lock ( )
1066+ . map_err ( |_| new_error ! ( "Failed to lock dirty page tracker" ) ) ?;
1067+
1068+ let start_page = offset / PAGE_SIZE_USIZE ;
1069+ let end_page = ( offset + size - 1 ) / PAGE_SIZE_USIZE ; // offset + size - 1 is the last affected byte.
1070+ for page_idx in start_page..=end_page {
1071+ let block_idx = page_idx / PAGES_IN_BLOCK ;
1072+ let bit_idx = page_idx % PAGES_IN_BLOCK ;
1073+ bitmap[ block_idx] |= 1 << bit_idx;
1074+ }
1075+ Ok ( ( ) )
1076+ }
9821077}
9831078
9841079impl SharedMemory for HostSharedMemory {
9851080 fn region ( & self ) -> & HostMapping {
9861081 & self . region
9871082 }
1083+
9881084 fn with_exclusivity < T , F : FnOnce ( & mut ExclusiveSharedMemory ) -> T > (
9891085 & mut self ,
9901086 f : F ,
@@ -995,6 +1091,7 @@ impl SharedMemory for HostSharedMemory {
9951091 . map_err ( |e| new_error ! ( "Error locking at {}:{}: {}" , file!( ) , line!( ) , e) ) ?;
9961092 let mut excl = ExclusiveSharedMemory {
9971093 region : self . region . clone ( ) ,
1094+ dirty_page_tracker : self . dirty_page_tracker . clone ( ) ,
9981095 } ;
9991096 let ret = f ( & mut excl) ;
10001097 drop ( excl) ;
@@ -1048,7 +1145,7 @@ mod tests {
10481145 let mem_size: usize = 4096 ;
10491146 let vec_len = 10 ;
10501147 let eshm = ExclusiveSharedMemory :: new ( mem_size) ?;
1051- let ( hshm, _) = eshm. build ( ) ;
1148+ let ( mut hshm, _) = eshm. build ( ) ;
10521149 let vec = vec ! [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] ;
10531150 // write the value to the memory at the beginning.
10541151 hshm. copy_from_slice ( & vec, 0 ) ?;
@@ -1135,8 +1232,8 @@ mod tests {
11351232 #[ test]
11361233 fn clone ( ) {
11371234 let eshm = ExclusiveSharedMemory :: new ( PAGE_SIZE_USIZE ) . unwrap ( ) ;
1138- let ( hshm1, _) = eshm. build ( ) ;
1139- let hshm2 = hshm1. clone ( ) ;
1235+ let ( mut hshm1, _) = eshm. build ( ) ;
1236+ let mut hshm2 = hshm1. clone ( ) ;
11401237
11411238 // after hshm1 is cloned, hshm1 and hshm2 should have identical
11421239 // memory sizes and pointers.
0 commit comments