@@ -19,9 +19,9 @@ use std::ffi::c_void;
19
19
use std:: io:: Error ;
20
20
#[ cfg( target_os = "linux" ) ]
21
21
use std:: ptr:: null_mut;
22
- use std:: sync:: { Arc , RwLock } ;
22
+ use std:: sync:: { Arc , Mutex , RwLock } ;
23
23
24
- use hyperlight_common:: mem:: PAGE_SIZE_USIZE ;
24
+ use hyperlight_common:: mem:: { PAGE_SIZE_USIZE , PAGES_IN_BLOCK } ;
25
25
use tracing:: { Span , instrument} ;
26
26
#[ cfg( target_os = "windows" ) ]
27
27
use windows:: Win32 :: Foundation :: { CloseHandle , HANDLE , INVALID_HANDLE_VALUE } ;
@@ -78,8 +78,10 @@ macro_rules! generate_writer {
78
78
#[ allow( dead_code) ]
79
79
pub ( crate ) fn $fname( & mut self , offset: usize , value: $ty) -> Result <( ) > {
80
80
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) ?;
83
85
Ok ( ( ) )
84
86
}
85
87
} ;
@@ -133,6 +135,7 @@ impl Drop for HostMapping {
133
135
#[ derive( Debug ) ]
134
136
pub struct ExclusiveSharedMemory {
135
137
region : Arc < HostMapping > ,
138
+ dirty_page_tracker : Arc < Mutex < Vec < u64 > > > ,
136
139
}
137
140
unsafe impl Send for ExclusiveSharedMemory { }
138
141
@@ -147,6 +150,8 @@ unsafe impl Send for ExclusiveSharedMemory {}
147
150
#[ derive( Debug ) ]
148
151
pub struct GuestSharedMemory {
149
152
region : Arc < HostMapping > ,
153
+ dirty_page_tracker : Arc < Mutex < Vec < u64 > > > ,
154
+
150
155
/// The lock that indicates this shared memory is being used by non-Rust code
151
156
///
152
157
/// This lock _must_ be held whenever the guest is executing,
@@ -298,6 +303,8 @@ unsafe impl Send for GuestSharedMemory {}
298
303
#[ derive( Clone , Debug ) ]
299
304
pub struct HostSharedMemory {
300
305
region : Arc < HostMapping > ,
306
+ dirty_page_tracker : Arc < Mutex < Vec < u64 > > > ,
307
+
301
308
lock : Arc < RwLock < ( ) > > ,
302
309
}
303
310
unsafe impl Send for HostSharedMemory { }
@@ -316,6 +323,7 @@ impl ExclusiveSharedMemory {
316
323
} ;
317
324
318
325
use crate :: error:: HyperlightError :: { MemoryRequestTooBig , MmapFailed , MprotectFailed } ;
326
+ use crate :: mem:: bitmap:: new_page_bitmap;
319
327
320
328
if min_size_bytes == 0 {
321
329
return Err ( new_error ! ( "Cannot create shared memory with size 0" ) ) ;
@@ -370,30 +378,48 @@ impl ExclusiveSharedMemory {
370
378
return Err ( MprotectFailed ( Error :: last_os_error ( ) . raw_os_error ( ) ) ) ;
371
379
}
372
380
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
+
373
397
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) ) ,
387
400
} )
388
401
}
389
402
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
+
390
414
/// Create a new region of shared memory with the given minimum
391
415
/// size in bytes. The region will be surrounded by guard pages.
392
416
///
393
417
/// Return `Err` if shared memory could not be allocated.
394
418
#[ cfg( target_os = "windows" ) ]
395
419
#[ instrument( skip_all, parent = Span :: current( ) , level= "Trace" ) ]
396
420
pub fn new ( min_size_bytes : usize ) -> Result < Self > {
421
+ use super :: bitmap:: new_page_bitmap;
422
+
397
423
if min_size_bytes == 0 {
398
424
return Err ( new_error ! ( "Cannot create shared memory with size 0" ) ) ;
399
425
}
@@ -484,21 +510,26 @@ impl ExclusiveSharedMemory {
484
510
log_then_return ! ( WindowsAPIError ( e. clone( ) ) ) ;
485
511
}
486
512
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
+
487
530
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) ) ,
502
533
} )
503
534
}
504
535
@@ -613,6 +644,16 @@ impl ExclusiveSharedMemory {
613
644
let data = self . as_mut_slice ( ) ;
614
645
bounds_check ! ( offset, src. len( ) , data. len( ) ) ;
615
646
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 ( ) ] ) ;
616
657
Ok ( ( ) )
617
658
}
618
659
@@ -624,6 +665,16 @@ impl ExclusiveSharedMemory {
624
665
Ok ( self . base_addr ( ) + offset)
625
666
}
626
667
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
+
627
678
generate_reader ! ( read_u8, u8 ) ;
628
679
generate_reader ! ( read_i8, i8 ) ;
629
680
generate_reader ! ( read_u16, u16 ) ;
@@ -657,15 +708,35 @@ impl ExclusiveSharedMemory {
657
708
(
658
709
HostSharedMemory {
659
710
region : self . region . clone ( ) ,
711
+ dirty_page_tracker : self . dirty_page_tracker . clone ( ) ,
660
712
lock : lock. clone ( ) ,
661
713
} ,
662
714
GuestSharedMemory {
663
715
region : self . region . clone ( ) ,
716
+ dirty_page_tracker : self . dirty_page_tracker . clone ( ) ,
664
717
lock : lock. clone ( ) ,
665
718
} ,
666
719
)
667
720
}
668
721
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
+
669
740
/// Gets the file handle of the shared memory region for this Sandbox
670
741
#[ cfg( target_os = "windows" ) ]
671
742
pub fn get_mmap_file_handle ( & self ) -> HANDLE {
@@ -743,6 +814,7 @@ impl SharedMemory for GuestSharedMemory {
743
814
fn region ( & self ) -> & HostMapping {
744
815
& self . region
745
816
}
817
+
746
818
fn with_exclusivity < T , F : FnOnce ( & mut ExclusiveSharedMemory ) -> T > (
747
819
& mut self ,
748
820
f : F ,
@@ -753,6 +825,7 @@ impl SharedMemory for GuestSharedMemory {
753
825
. map_err ( |e| new_error ! ( "Error locking at {}:{}: {}" , file!( ) , line!( ) , e) ) ?;
754
826
let mut excl = ExclusiveSharedMemory {
755
827
region : self . region . clone ( ) ,
828
+ dirty_page_tracker : self . dirty_page_tracker . clone ( ) ,
756
829
} ;
757
830
let ret = f ( & mut excl) ;
758
831
drop ( excl) ;
@@ -803,7 +876,7 @@ impl HostSharedMemory {
803
876
/// Write a value of type T, whose representation is the same
804
877
/// between the sandbox and the host, and which has no invalid bit
805
878
/// 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 < ( ) > {
807
880
bounds_check ! ( offset, std:: mem:: size_of:: <T >( ) , self . mem_size( ) ) ;
808
881
unsafe {
809
882
let slice: & [ u8 ] = core:: slice:: from_raw_parts (
@@ -812,6 +885,7 @@ impl HostSharedMemory {
812
885
) ;
813
886
self . copy_from_slice ( slice, offset) ?;
814
887
}
888
+ self . mark_pages_dirty ( offset, std:: mem:: size_of :: < T > ( ) ) ?;
815
889
Ok ( ( ) )
816
890
}
817
891
@@ -834,9 +908,8 @@ impl HostSharedMemory {
834
908
Ok ( ( ) )
835
909
}
836
910
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 < ( ) > {
840
913
bounds_check ! ( offset, slice. len( ) , self . mem_size( ) ) ;
841
914
let base = self . base_ptr ( ) . wrapping_add ( offset) ;
842
915
let guard = self
@@ -850,6 +923,7 @@ impl HostSharedMemory {
850
923
}
851
924
}
852
925
drop ( guard) ;
926
+ self . mark_pages_dirty ( offset, slice. len ( ) ) ?;
853
927
Ok ( ( ) )
854
928
}
855
929
@@ -867,6 +941,7 @@ impl HostSharedMemory {
867
941
unsafe { base. wrapping_add ( i) . write_volatile ( value) } ;
868
942
}
869
943
drop ( guard) ;
944
+ self . mark_pages_dirty ( offset, len) ?;
870
945
Ok ( ( ) )
871
946
}
872
947
@@ -979,12 +1054,31 @@ impl HostSharedMemory {
979
1054
980
1055
Ok ( to_return)
981
1056
}
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
+ }
982
1075
}
983
1076
984
1077
impl SharedMemory for HostSharedMemory {
985
1078
fn region ( & self ) -> & HostMapping {
986
1079
& self . region
987
1080
}
1081
+
988
1082
fn with_exclusivity < T , F : FnOnce ( & mut ExclusiveSharedMemory ) -> T > (
989
1083
& mut self ,
990
1084
f : F ,
@@ -995,6 +1089,7 @@ impl SharedMemory for HostSharedMemory {
995
1089
. map_err ( |e| new_error ! ( "Error locking at {}:{}: {}" , file!( ) , line!( ) , e) ) ?;
996
1090
let mut excl = ExclusiveSharedMemory {
997
1091
region : self . region . clone ( ) ,
1092
+ dirty_page_tracker : self . dirty_page_tracker . clone ( ) ,
998
1093
} ;
999
1094
let ret = f ( & mut excl) ;
1000
1095
drop ( excl) ;
@@ -1048,7 +1143,7 @@ mod tests {
1048
1143
let mem_size: usize = 4096 ;
1049
1144
let vec_len = 10 ;
1050
1145
let eshm = ExclusiveSharedMemory :: new ( mem_size) ?;
1051
- let ( hshm, _) = eshm. build ( ) ;
1146
+ let ( mut hshm, _) = eshm. build ( ) ;
1052
1147
let vec = vec ! [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] ;
1053
1148
// write the value to the memory at the beginning.
1054
1149
hshm. copy_from_slice ( & vec, 0 ) ?;
@@ -1135,8 +1230,8 @@ mod tests {
1135
1230
#[ test]
1136
1231
fn clone ( ) {
1137
1232
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 ( ) ;
1140
1235
1141
1236
// after hshm1 is cloned, hshm1 and hshm2 should have identical
1142
1237
// memory sizes and pointers.
0 commit comments