1 // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
3 
4 //! This module holds abstractions that enable tracking the areas dirtied by writes of a specified
5 //! length to a given offset. In particular, this is used to track write accesses within a
6 //! `GuestMemoryRegion` object, and the resulting bitmaps can then be aggregated to build the
7 //! global view for an entire `GuestMemory` object.
8 
9 #[cfg(any(test, feature = "backend-bitmap"))]
10 mod backend;
11 
12 use std::fmt::Debug;
13 
14 use crate::{GuestMemory, GuestMemoryRegion};
15 
16 #[cfg(any(test, feature = "backend-bitmap"))]
17 pub use backend::{ArcSlice, AtomicBitmap, RefSlice};
18 
19 /// Trait implemented by types that support creating `BitmapSlice` objects.
20 pub trait WithBitmapSlice<'a> {
21     /// Type of the bitmap slice.
22     type S: BitmapSlice;
23 }
24 
25 /// Trait used to represent that a `BitmapSlice` is a `Bitmap` itself, but also satisfies the
26 /// restriction that slices created from it have the same type as `Self`.
27 pub trait BitmapSlice: Bitmap + Clone + Debug + for<'a> WithBitmapSlice<'a, S = Self> {}
28 
29 /// Common bitmap operations. Using Higher-Rank Trait Bounds (HRTBs) to effectively define
30 /// an associated type that has a lifetime parameter, without tagging the `Bitmap` trait with
31 /// a lifetime as well.
32 ///
33 /// Using an associated type allows implementing the `Bitmap` and `BitmapSlice` functionality
34 /// as a zero-cost abstraction when providing trivial implementations such as the one
35 /// defined for `()`.
36 // These methods represent the core functionality that's required by `vm-memory` abstractions
37 // to implement generic tracking logic, as well as tests that can be reused by different backends.
38 pub trait Bitmap: for<'a> WithBitmapSlice<'a> {
39     /// Mark the memory range specified by the given `offset` and `len` as dirtied.
mark_dirty(&self, offset: usize, len: usize)40     fn mark_dirty(&self, offset: usize, len: usize);
41 
42     /// Check whether the specified `offset` is marked as dirty.
dirty_at(&self, offset: usize) -> bool43     fn dirty_at(&self, offset: usize) -> bool;
44 
45     /// Return a `<Self as WithBitmapSlice>::S` slice of the current bitmap, starting at
46     /// the specified `offset`.
slice_at(&self, offset: usize) -> <Self as WithBitmapSlice>::S47     fn slice_at(&self, offset: usize) -> <Self as WithBitmapSlice>::S;
48 }
49 
50 /// A no-op `Bitmap` implementation that can be provided for backends that do not actually
51 /// require the tracking functionality.
52 
53 impl<'a> WithBitmapSlice<'a> for () {
54     type S = Self;
55 }
56 
57 impl BitmapSlice for () {}
58 
59 impl Bitmap for () {
mark_dirty(&self, _offset: usize, _len: usize)60     fn mark_dirty(&self, _offset: usize, _len: usize) {}
61 
dirty_at(&self, _offset: usize) -> bool62     fn dirty_at(&self, _offset: usize) -> bool {
63         false
64     }
65 
slice_at(&self, _offset: usize) -> Self66     fn slice_at(&self, _offset: usize) -> Self {}
67 }
68 
69 /// A `Bitmap` and `BitmapSlice` implementation for `Option<B>`.
70 
71 impl<'a, B> WithBitmapSlice<'a> for Option<B>
72 where
73     B: WithBitmapSlice<'a>,
74 {
75     type S = Option<B::S>;
76 }
77 
78 impl<B: BitmapSlice> BitmapSlice for Option<B> {}
79 
80 impl<B: Bitmap> Bitmap for Option<B> {
mark_dirty(&self, offset: usize, len: usize)81     fn mark_dirty(&self, offset: usize, len: usize) {
82         if let Some(inner) = self {
83             inner.mark_dirty(offset, len)
84         }
85     }
86 
dirty_at(&self, offset: usize) -> bool87     fn dirty_at(&self, offset: usize) -> bool {
88         if let Some(inner) = self {
89             return inner.dirty_at(offset);
90         }
91         false
92     }
93 
slice_at(&self, offset: usize) -> Option<<B as WithBitmapSlice>::S>94     fn slice_at(&self, offset: usize) -> Option<<B as WithBitmapSlice>::S> {
95         if let Some(inner) = self {
96             return Some(inner.slice_at(offset));
97         }
98         None
99     }
100 }
101 
102 /// Helper type alias for referring to the `BitmapSlice` concrete type associated with
103 /// an object `B: WithBitmapSlice<'a>`.
104 pub type BS<'a, B> = <B as WithBitmapSlice<'a>>::S;
105 
106 /// Helper type alias for referring to the `BitmapSlice` concrete type associated with
107 /// the memory regions of an object `M: GuestMemory`.
108 pub type MS<'a, M> = BS<'a, <<M as GuestMemory>::R as GuestMemoryRegion>::B>;
109 
110 #[cfg(test)]
111 pub(crate) mod tests {
112     use super::*;
113 
114     use std::io::Cursor;
115     use std::marker::PhantomData;
116     use std::mem::size_of_val;
117     use std::result::Result;
118     use std::sync::atomic::Ordering;
119 
120     use crate::{Bytes, VolatileMemory};
121     #[cfg(feature = "backend-mmap")]
122     use crate::{GuestAddress, MemoryRegionAddress};
123 
124     // Helper method to check whether a specified range is clean.
range_is_clean<B: Bitmap>(b: &B, start: usize, len: usize) -> bool125     pub fn range_is_clean<B: Bitmap>(b: &B, start: usize, len: usize) -> bool {
126         (start..start + len).all(|offset| !b.dirty_at(offset))
127     }
128 
129     // Helper method to check whether a specified range is dirty.
range_is_dirty<B: Bitmap>(b: &B, start: usize, len: usize) -> bool130     pub fn range_is_dirty<B: Bitmap>(b: &B, start: usize, len: usize) -> bool {
131         (start..start + len).all(|offset| b.dirty_at(offset))
132     }
133 
check_range<B: Bitmap>(b: &B, start: usize, len: usize, clean: bool) -> bool134     pub fn check_range<B: Bitmap>(b: &B, start: usize, len: usize, clean: bool) -> bool {
135         if clean {
136             range_is_clean(b, start, len)
137         } else {
138             range_is_dirty(b, start, len)
139         }
140     }
141 
142     // Helper method that tests a generic `B: Bitmap` implementation. It assumes `b` covers
143     // an area of length at least 0x2000.
test_bitmap<B: Bitmap>(b: &B)144     pub fn test_bitmap<B: Bitmap>(b: &B) {
145         let len = 0x2000;
146         let dirty_offset = 0x1000;
147         let dirty_len = 0x100;
148 
149         // Some basic checks.
150         let s = b.slice_at(dirty_offset);
151 
152         assert!(range_is_clean(b, 0, len));
153         assert!(range_is_clean(&s, 0, dirty_len));
154 
155         b.mark_dirty(dirty_offset, dirty_len);
156         assert!(range_is_dirty(b, dirty_offset, dirty_len));
157         assert!(range_is_dirty(&s, 0, dirty_len));
158     }
159 
160     #[derive(Debug)]
161     pub enum TestAccessError {
162         RangeCleanCheck,
163         RangeDirtyCheck,
164     }
165 
166     // A helper object that implements auxiliary operations for testing `Bytes` implementations
167     // in the context of dirty bitmap tracking.
168     struct BytesHelper<F, G, M> {
169         check_range_fn: F,
170         address_fn: G,
171         phantom: PhantomData<*const M>,
172     }
173 
174     // `F` represents a closure the checks whether a specified range associated with the `Bytes`
175     // object that's being tested is marked as dirty or not (depending on the value of the last
176     // parameter). It has the following parameters:
177     // - A reference to a `Bytes` implementations that's subject to testing.
178     // - The offset of the range.
179     // - The length of the range.
180     // - Whether we are checking if the range is clean (when `true`) or marked as dirty.
181     //
182     // `G` represents a closure that translates an offset into an address value that's
183     // relevant for the `Bytes` implementation being tested.
184     impl<F, G, M, A> BytesHelper<F, G, M>
185     where
186         F: Fn(&M, usize, usize, bool) -> bool,
187         G: Fn(usize) -> A,
188         M: Bytes<A>,
189     {
check_range(&self, m: &M, start: usize, len: usize, clean: bool) -> bool190         fn check_range(&self, m: &M, start: usize, len: usize, clean: bool) -> bool {
191             (self.check_range_fn)(m, start, len, clean)
192         }
193 
address(&self, offset: usize) -> A194         fn address(&self, offset: usize) -> A {
195             (self.address_fn)(offset)
196         }
197 
test_access<Op>( &self, bytes: &M, dirty_offset: usize, dirty_len: usize, op: Op, ) -> Result<(), TestAccessError> where Op: Fn(&M, A),198         fn test_access<Op>(
199             &self,
200             bytes: &M,
201             dirty_offset: usize,
202             dirty_len: usize,
203             op: Op,
204         ) -> Result<(), TestAccessError>
205         where
206             Op: Fn(&M, A),
207         {
208             if !self.check_range(bytes, dirty_offset, dirty_len, true) {
209                 return Err(TestAccessError::RangeCleanCheck);
210             }
211 
212             op(bytes, self.address(dirty_offset));
213 
214             if !self.check_range(bytes, dirty_offset, dirty_len, false) {
215                 return Err(TestAccessError::RangeDirtyCheck);
216             }
217 
218             Ok(())
219         }
220     }
221 
222     // `F` and `G` stand for the same closure types as described in the `BytesHelper` comment.
223     // The `step` parameter represents the offset that's added the the current address after
224     // performing each access. It provides finer grained control when testing tracking
225     // implementations that aggregate entire ranges for accounting purposes (for example, doing
226     // tracking at the page level).
test_bytes<F, G, M, A>(bytes: &M, check_range_fn: F, address_fn: G, step: usize) where F: Fn(&M, usize, usize, bool) -> bool, G: Fn(usize) -> A, A: Copy, M: Bytes<A>, <M as Bytes<A>>::E: Debug,227     pub fn test_bytes<F, G, M, A>(bytes: &M, check_range_fn: F, address_fn: G, step: usize)
228     where
229         F: Fn(&M, usize, usize, bool) -> bool,
230         G: Fn(usize) -> A,
231         A: Copy,
232         M: Bytes<A>,
233         <M as Bytes<A>>::E: Debug,
234     {
235         const BUF_SIZE: usize = 1024;
236         let buf = vec![1u8; 1024];
237 
238         let val = 1u64;
239 
240         let h = BytesHelper {
241             check_range_fn,
242             address_fn,
243             phantom: PhantomData,
244         };
245 
246         let mut dirty_offset = 0x1000;
247 
248         // Test `write`.
249         h.test_access(bytes, dirty_offset, BUF_SIZE, |m, addr| {
250             assert_eq!(m.write(buf.as_slice(), addr).unwrap(), BUF_SIZE)
251         })
252         .unwrap();
253         dirty_offset += step;
254 
255         // Test `write_slice`.
256         h.test_access(bytes, dirty_offset, BUF_SIZE, |m, addr| {
257             m.write_slice(buf.as_slice(), addr).unwrap()
258         })
259         .unwrap();
260         dirty_offset += step;
261 
262         // Test `write_obj`.
263         h.test_access(bytes, dirty_offset, size_of_val(&val), |m, addr| {
264             m.write_obj(val, addr).unwrap()
265         })
266         .unwrap();
267         dirty_offset += step;
268 
269         // Test `read_from`.
270         h.test_access(bytes, dirty_offset, BUF_SIZE, |m, addr| {
271             assert_eq!(
272                 m.read_from(addr, &mut Cursor::new(&buf), BUF_SIZE).unwrap(),
273                 BUF_SIZE
274             )
275         })
276         .unwrap();
277         dirty_offset += step;
278 
279         // Test `read_exact_from`.
280         h.test_access(bytes, dirty_offset, BUF_SIZE, |m, addr| {
281             m.read_exact_from(addr, &mut Cursor::new(&buf), BUF_SIZE)
282                 .unwrap()
283         })
284         .unwrap();
285         dirty_offset += step;
286 
287         // Test `store`.
288         h.test_access(bytes, dirty_offset, size_of_val(&val), |m, addr| {
289             m.store(val, addr, Ordering::Relaxed).unwrap()
290         })
291         .unwrap();
292     }
293 
294     // This function and the next are currently conditionally compiled because we only use
295     // them to test the mmap-based backend implementations for now. Going forward, the generic
296     // test functions defined here can be placed in a separate module (i.e. `test_utilities`)
297     // which is gated by a feature and can be used for testing purposes by other crates as well.
298     #[cfg(feature = "backend-mmap")]
test_guest_memory_region<R: GuestMemoryRegion>(region: &R)299     fn test_guest_memory_region<R: GuestMemoryRegion>(region: &R) {
300         let dirty_addr = MemoryRegionAddress(0x0);
301         let val = 123u64;
302         let dirty_len = size_of_val(&val);
303 
304         let slice = region.get_slice(dirty_addr, dirty_len).unwrap();
305 
306         assert!(range_is_clean(region.bitmap(), 0, region.len() as usize));
307         assert!(range_is_clean(slice.bitmap(), 0, dirty_len));
308 
309         region.write_obj(val, dirty_addr).unwrap();
310 
311         assert!(range_is_dirty(
312             region.bitmap(),
313             dirty_addr.0 as usize,
314             dirty_len
315         ));
316 
317         assert!(range_is_dirty(slice.bitmap(), 0, dirty_len));
318 
319         // Finally, let's invoke the generic tests for `R: Bytes`. It's ok to pass the same
320         // `region` handle because `test_bytes` starts performing writes after the range that's
321         // been already dirtied in the first part of this test.
322         test_bytes(
323             region,
324             |r: &R, start: usize, len: usize, clean: bool| {
325                 check_range(r.bitmap(), start, len, clean)
326             },
327             |offset| MemoryRegionAddress(offset as u64),
328             0x1000,
329         );
330     }
331 
332     #[cfg(feature = "backend-mmap")]
333     // Assumptions about M generated by f ...
test_guest_memory_and_region<M, F>(f: F) where M: GuestMemory, F: Fn() -> M,334     pub fn test_guest_memory_and_region<M, F>(f: F)
335     where
336         M: GuestMemory,
337         F: Fn() -> M,
338     {
339         let m = f();
340         let dirty_addr = GuestAddress(0x1000);
341         let val = 123u64;
342         let dirty_len = size_of_val(&val);
343 
344         let (region, region_addr) = m.to_region_addr(dirty_addr).unwrap();
345         let slice = m.get_slice(dirty_addr, dirty_len).unwrap();
346 
347         assert!(range_is_clean(region.bitmap(), 0, region.len() as usize));
348         assert!(range_is_clean(slice.bitmap(), 0, dirty_len));
349 
350         m.write_obj(val, dirty_addr).unwrap();
351 
352         assert!(range_is_dirty(
353             region.bitmap(),
354             region_addr.0 as usize,
355             dirty_len
356         ));
357 
358         assert!(range_is_dirty(slice.bitmap(), 0, dirty_len));
359 
360         // Now let's invoke the tests for the inner `GuestMemoryRegion` type.
361         test_guest_memory_region(f().find_region(GuestAddress(0)).unwrap());
362 
363         // Finally, let's invoke the generic tests for `Bytes`.
364         let check_range_closure = |m: &M, start: usize, len: usize, clean: bool| -> bool {
365             let mut check_result = true;
366             m.try_access(len, GuestAddress(start as u64), |_, size, reg_addr, reg| {
367                 if !check_range(reg.bitmap(), reg_addr.0 as usize, size, clean) {
368                     check_result = false;
369                 }
370                 Ok(size)
371             })
372             .unwrap();
373 
374             check_result
375         };
376 
377         test_bytes(
378             &f(),
379             check_range_closure,
380             |offset| GuestAddress(offset as u64),
381             0x1000,
382         );
383     }
384 
test_volatile_memory<M: VolatileMemory>(m: &M)385     pub fn test_volatile_memory<M: VolatileMemory>(m: &M) {
386         assert!(m.len() >= 0x8000);
387 
388         let dirty_offset = 0x1000;
389         let val = 123u64;
390         let dirty_len = size_of_val(&val);
391 
392         let get_ref_offset = 0x2000;
393         let array_ref_offset = 0x3000;
394 
395         let s1 = m.as_volatile_slice();
396         let s2 = m.get_slice(dirty_offset, dirty_len).unwrap();
397 
398         assert!(range_is_clean(s1.bitmap(), 0, s1.len()));
399         assert!(range_is_clean(s2.bitmap(), 0, s2.len()));
400 
401         s1.write_obj(val, dirty_offset).unwrap();
402 
403         assert!(range_is_dirty(s1.bitmap(), dirty_offset, dirty_len));
404         assert!(range_is_dirty(s2.bitmap(), 0, dirty_len));
405 
406         let v_ref = m.get_ref::<u64>(get_ref_offset).unwrap();
407         assert!(range_is_clean(s1.bitmap(), get_ref_offset, dirty_len));
408         v_ref.store(val);
409         assert!(range_is_dirty(s1.bitmap(), get_ref_offset, dirty_len));
410 
411         let arr_ref = m.get_array_ref::<u64>(array_ref_offset, 1).unwrap();
412         assert!(range_is_clean(s1.bitmap(), array_ref_offset, dirty_len));
413         arr_ref.store(0, val);
414         assert!(range_is_dirty(s1.bitmap(), array_ref_offset, dirty_len));
415     }
416 }
417