@@ -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 { }
@@ -316,6 +323,7 @@ impl ExclusiveSharedMemory {
316323 } ;
317324
318325 use crate :: error:: HyperlightError :: { MemoryRequestTooBig , MmapFailed , MprotectFailed } ;
326+ use crate :: mem:: bitmap:: new_page_bitmap;
319327
320328 if min_size_bytes == 0 {
321329 return Err ( new_error ! ( "Cannot create shared memory with size 0" ) ) ;
@@ -370,30 +378,48 @@ impl ExclusiveSharedMemory {
370378 return Err ( MprotectFailed ( Error :: last_os_error ( ) . raw_os_error ( ) ) ) ;
371379 }
372380
381+ // HostMapping is only non-Send/Sync because raw pointers
382+ // are not ("as a lint", as the Rust docs say). We don't
383+ // want to mark HostMapping Send/Sync immediately, because
384+ // that could socially imply that it's "safe" to use
385+ // unsafe accesses from multiple threads at once. Instead, we
386+ // directly impl Send and Sync on this type. Since this
387+ // type does have Send and Sync manually impl'd, the Arc
388+ // is not pointless as the lint suggests.
389+ #[ allow( clippy:: arc_with_non_send_sync) ]
390+ let host_mapping = Arc :: new ( HostMapping {
391+ ptr : addr as * mut u8 ,
392+ size : total_size,
393+ } ) ;
394+
395+ let dirty_page_tracker = new_page_bitmap ( min_size_bytes, false ) ?;
396+
373397 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- } ) ,
398+ region : host_mapping,
399+ dirty_page_tracker : Arc :: new ( Mutex :: new ( dirty_page_tracker) ) ,
387400 } )
388401 }
389402
403+ /// Gets the dirty bitmap and then clears it in self.
404+ pub ( crate ) fn get_and_clear_dirty_pages ( & mut self ) -> Result < Vec < u64 > > {
405+ let mut guard = self
406+ . dirty_page_tracker
407+ . try_lock ( )
408+ . map_err ( |_| new_error ! ( "Failed to acquire lock on dirty page tracker" ) ) ?;
409+ let bitmap = guard. clone ( ) ;
410+ guard. fill ( 0 ) ;
411+ Ok ( bitmap)
412+ }
413+
390414 /// Create a new region of shared memory with the given minimum
391415 /// size in bytes. The region will be surrounded by guard pages.
392416 ///
393417 /// Return `Err` if shared memory could not be allocated.
394418 #[ cfg( target_os = "windows" ) ]
395419 #[ instrument( skip_all, parent = Span :: current( ) , level= "Trace" ) ]
396420 pub fn new ( min_size_bytes : usize ) -> Result < Self > {
421+ use super :: bitmap:: new_page_bitmap;
422+
397423 if min_size_bytes == 0 {
398424 return Err ( new_error ! ( "Cannot create shared memory with size 0" ) ) ;
399425 }
@@ -484,21 +510,26 @@ 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 = new_page_bitmap ( min_size_bytes, false ) ?;
529+
487530 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- } ) ,
531+ region : host_mapping,
532+ dirty_page_tracker : Arc :: new ( Mutex :: new ( dirty_page_tracker) ) ,
502533 } )
503534 }
504535
@@ -613,6 +644,16 @@ impl ExclusiveSharedMemory {
613644 let data = self . as_mut_slice ( ) ;
614645 bounds_check ! ( offset, src. len( ) , data. len( ) ) ;
615646 data[ offset..offset + src. len ( ) ] . copy_from_slice ( src) ;
647+ self . mark_pages_dirty ( offset, src. len ( ) ) ?;
648+ Ok ( ( ) )
649+ }
650+
651+ /// Copies bytes from `self` to `dst` starting at offset
652+ #[ instrument( err( Debug ) , skip_all, parent = Span :: current( ) , level= "Trace" ) ]
653+ pub fn copy_to_slice ( & self , dst : & mut [ u8 ] , offset : usize ) -> Result < ( ) > {
654+ let data = self . as_slice ( ) ;
655+ bounds_check ! ( offset, dst. len( ) , data. len( ) ) ;
656+ dst. copy_from_slice ( & data[ offset..offset + dst. len ( ) ] ) ;
616657 Ok ( ( ) )
617658 }
618659
@@ -624,6 +665,16 @@ impl ExclusiveSharedMemory {
624665 Ok ( self . base_addr ( ) + offset)
625666 }
626667
668+ /// Fill the memory in the range `[offset, offset + len)` with `value`
669+ #[ instrument( err( Debug ) , skip_all, parent = Span :: current( ) , level= "Trace" ) ]
670+ pub fn zero_fill ( & mut self , offset : usize , len : usize ) -> Result < ( ) > {
671+ bounds_check ! ( offset, len, self . mem_size( ) ) ;
672+ let data = self . as_mut_slice ( ) ;
673+ data[ offset..offset + len] . fill ( 0 ) ;
674+ self . mark_pages_dirty ( offset, len) ?;
675+ Ok ( ( ) )
676+ }
677+
627678 generate_reader ! ( read_u8, u8 ) ;
628679 generate_reader ! ( read_i8, i8 ) ;
629680 generate_reader ! ( read_u16, u16 ) ;
@@ -657,15 +708,35 @@ impl ExclusiveSharedMemory {
657708 (
658709 HostSharedMemory {
659710 region : self . region . clone ( ) ,
711+ dirty_page_tracker : self . dirty_page_tracker . clone ( ) ,
660712 lock : lock. clone ( ) ,
661713 } ,
662714 GuestSharedMemory {
663715 region : self . region . clone ( ) ,
716+ dirty_page_tracker : self . dirty_page_tracker . clone ( ) ,
664717 lock : lock. clone ( ) ,
665718 } ,
666719 )
667720 }
668721
722+ /// Marks pages that cover bytes [offset, offset + size) as dirty
723+ pub ( super ) fn mark_pages_dirty ( & mut self , offset : usize , size : usize ) -> Result < ( ) > {
724+ bounds_check ! ( offset, size, self . mem_size( ) ) ;
725+ let mut bitmap = self
726+ . dirty_page_tracker
727+ . try_lock ( )
728+ . map_err ( |_| new_error ! ( "Failed to lock dirty page tracker" ) ) ?;
729+
730+ let start_page = offset / PAGE_SIZE_USIZE ;
731+ let end_page = ( offset + size - 1 ) / PAGE_SIZE_USIZE ; // offset + size - 1 is the last affected byte.
732+ for page_idx in start_page..=end_page {
733+ let block_idx = page_idx / PAGES_IN_BLOCK ;
734+ let bit_idx = page_idx % PAGES_IN_BLOCK ;
735+ bitmap[ block_idx] |= 1 << bit_idx;
736+ }
737+ Ok ( ( ) )
738+ }
739+
669740 /// Gets the file handle of the shared memory region for this Sandbox
670741 #[ cfg( target_os = "windows" ) ]
671742 pub fn get_mmap_file_handle ( & self ) -> HANDLE {
@@ -743,6 +814,7 @@ impl SharedMemory for GuestSharedMemory {
743814 fn region ( & self ) -> & HostMapping {
744815 & self . region
745816 }
817+
746818 fn with_exclusivity < T , F : FnOnce ( & mut ExclusiveSharedMemory ) -> T > (
747819 & mut self ,
748820 f : F ,
@@ -753,6 +825,7 @@ impl SharedMemory for GuestSharedMemory {
753825 . map_err ( |e| new_error ! ( "Error locking at {}:{}: {}" , file!( ) , line!( ) , e) ) ?;
754826 let mut excl = ExclusiveSharedMemory {
755827 region : self . region . clone ( ) ,
828+ dirty_page_tracker : self . dirty_page_tracker . clone ( ) ,
756829 } ;
757830 let ret = f ( & mut excl) ;
758831 drop ( excl) ;
@@ -803,7 +876,7 @@ impl HostSharedMemory {
803876 /// Write a value of type T, whose representation is the same
804877 /// between the sandbox and the host, and which has no invalid bit
805878 /// patterns
806- pub fn write < T : AllValid > ( & self , offset : usize , data : T ) -> Result < ( ) > {
879+ pub fn write < T : AllValid > ( & mut self , offset : usize , data : T ) -> Result < ( ) > {
807880 bounds_check ! ( offset, std:: mem:: size_of:: <T >( ) , self . mem_size( ) ) ;
808881 unsafe {
809882 let slice: & [ u8 ] = core:: slice:: from_raw_parts (
@@ -812,6 +885,7 @@ impl HostSharedMemory {
812885 ) ;
813886 self . copy_from_slice ( slice, offset) ?;
814887 }
888+ self . mark_pages_dirty ( offset, std:: mem:: size_of :: < T > ( ) ) ?;
815889 Ok ( ( ) )
816890 }
817891
@@ -834,9 +908,8 @@ impl HostSharedMemory {
834908 Ok ( ( ) )
835909 }
836910
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 < ( ) > {
911+ /// Copy the contents of the given slice into self
912+ pub fn copy_from_slice ( & mut self , slice : & [ u8 ] , offset : usize ) -> Result < ( ) > {
840913 bounds_check ! ( offset, slice. len( ) , self . mem_size( ) ) ;
841914 let base = self . base_ptr ( ) . wrapping_add ( offset) ;
842915 let guard = self
@@ -850,6 +923,7 @@ impl HostSharedMemory {
850923 }
851924 }
852925 drop ( guard) ;
926+ self . mark_pages_dirty ( offset, slice. len ( ) ) ?;
853927 Ok ( ( ) )
854928 }
855929
@@ -867,6 +941,7 @@ impl HostSharedMemory {
867941 unsafe { base. wrapping_add ( i) . write_volatile ( value) } ;
868942 }
869943 drop ( guard) ;
944+ self . mark_pages_dirty ( offset, len) ?;
870945 Ok ( ( ) )
871946 }
872947
@@ -979,12 +1054,31 @@ impl HostSharedMemory {
9791054
9801055 Ok ( to_return)
9811056 }
1057+
1058+ /// Marks pages that cover bytes [offset, offset + size) as dirty
1059+ pub ( super ) fn mark_pages_dirty ( & mut self , offset : usize , size : usize ) -> Result < ( ) > {
1060+ bounds_check ! ( offset, size, self . mem_size( ) ) ;
1061+ let mut bitmap = self
1062+ . dirty_page_tracker
1063+ . try_lock ( )
1064+ . map_err ( |_| new_error ! ( "Failed to lock dirty page tracker" ) ) ?;
1065+
1066+ let start_page = offset / PAGE_SIZE_USIZE ;
1067+ let end_page = ( offset + size - 1 ) / PAGE_SIZE_USIZE ; // offset + size - 1 is the last affected byte.
1068+ for page_idx in start_page..=end_page {
1069+ let block_idx = page_idx / PAGES_IN_BLOCK ;
1070+ let bit_idx = page_idx % PAGES_IN_BLOCK ;
1071+ bitmap[ block_idx] |= 1 << bit_idx;
1072+ }
1073+ Ok ( ( ) )
1074+ }
9821075}
9831076
9841077impl SharedMemory for HostSharedMemory {
9851078 fn region ( & self ) -> & HostMapping {
9861079 & self . region
9871080 }
1081+
9881082 fn with_exclusivity < T , F : FnOnce ( & mut ExclusiveSharedMemory ) -> T > (
9891083 & mut self ,
9901084 f : F ,
@@ -995,6 +1089,7 @@ impl SharedMemory for HostSharedMemory {
9951089 . map_err ( |e| new_error ! ( "Error locking at {}:{}: {}" , file!( ) , line!( ) , e) ) ?;
9961090 let mut excl = ExclusiveSharedMemory {
9971091 region : self . region . clone ( ) ,
1092+ dirty_page_tracker : self . dirty_page_tracker . clone ( ) ,
9981093 } ;
9991094 let ret = f ( & mut excl) ;
10001095 drop ( excl) ;
@@ -1048,7 +1143,7 @@ mod tests {
10481143 let mem_size: usize = 4096 ;
10491144 let vec_len = 10 ;
10501145 let eshm = ExclusiveSharedMemory :: new ( mem_size) ?;
1051- let ( hshm, _) = eshm. build ( ) ;
1146+ let ( mut hshm, _) = eshm. build ( ) ;
10521147 let vec = vec ! [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] ;
10531148 // write the value to the memory at the beginning.
10541149 hshm. copy_from_slice ( & vec, 0 ) ?;
@@ -1135,8 +1230,8 @@ mod tests {
11351230 #[ test]
11361231 fn clone ( ) {
11371232 let eshm = ExclusiveSharedMemory :: new ( PAGE_SIZE_USIZE ) . unwrap ( ) ;
1138- let ( hshm1, _) = eshm. build ( ) ;
1139- let hshm2 = hshm1. clone ( ) ;
1233+ let ( mut hshm1, _) = eshm. build ( ) ;
1234+ let mut hshm2 = hshm1. clone ( ) ;
11401235
11411236 // after hshm1 is cloned, hshm1 and hshm2 should have identical
11421237 // memory sizes and pointers.
0 commit comments