Skip to content

Commit 0371258

Browse files
committed
Track dirty pages from host side
Signed-off-by: Ludvig Liljenberg <[email protected]>
1 parent 1afc8aa commit 0371258

File tree

1 file changed

+136
-39
lines changed

1 file changed

+136
-39
lines changed

src/hyperlight_host/src/mem/shared_mem.rs

Lines changed: 136 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ use std::ffi::c_void;
1919
use std::io::Error;
2020
#[cfg(target_os = "linux")]
2121
use 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};
2525
use tracing::{Span, instrument};
2626
#[cfg(target_os = "windows")]
2727
use 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)]
134136
pub struct ExclusiveSharedMemory {
135137
region: Arc<HostMapping>,
138+
dirty_page_tracker: Arc<Mutex<Vec<u64>>>,
136139
}
137140
unsafe impl Send for ExclusiveSharedMemory {}
138141

@@ -147,6 +150,8 @@ unsafe impl Send for ExclusiveSharedMemory {}
147150
#[derive(Debug)]
148151
pub 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)]
299304
pub struct HostSharedMemory {
300305
region: Arc<HostMapping>,
306+
dirty_page_tracker: Arc<Mutex<Vec<u64>>>,
307+
301308
lock: Arc<RwLock<()>>,
302309
}
303310
unsafe 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

9841079
impl 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

Comments
 (0)