vmi_core/ctx/prober.rs
1use std::cell::RefCell;
2
3use indexmap::IndexSet;
4
5use crate::{AddressContext, VmiError};
6
7/// Batches the page faults raised by a sequence of memory reads so they can be
8/// injected together, while skipping pages already known to be unserviceable.
9///
10/// A read that touches a non-resident page returns [`VmiError::Translation`].
11/// Instead of aborting an operation on the first such fault, the prober lets
12/// you run a sequence of reads: each probed read that faults yields `Ok(None)`
13/// and its faulting address is recorded, unless that address is in the
14/// `suppressed` set, in which case it is ignored.
15///
16/// # Protocol
17///
18/// 1. Construct a prober with [`new`], passing the set of pages already known
19/// to be unserviceable (the `suppressed` set).
20/// 2. Run one or more reads through [`probe`] or the [`vmi_probe!`] macro. Each
21/// returns `Ok(Some(value))` when the read succeeded, or `Ok(None)` when it
22/// faulted (the fault is recorded unless it is suppressed).
23/// 3. Call [`error_for_page_faults`]. It returns `Err(VmiError::Translation)`
24/// carrying every recorded, non-suppressed fault, or `Ok(())` if there were
25/// none. Propagate that error to an event loop that injects page faults: for
26/// example, returning it from a `vmi-utils` reactor handler makes the
27/// reactor inject the fault so the guest pages the memory in, after which
28/// the operation is retried.
29/// 4. Faults that turn out to be permanently unserviceable are added to the
30/// `suppressed` set by the caller (for instance from a `KiDispatchException`
31/// handler), so a later attempt skips them instead of re-injecting forever.
32///
33/// [`new`]: Self::new
34/// [`probe`]: Self::probe
35/// [`error_for_page_faults`]: Self::error_for_page_faults
36/// [`vmi_probe!`]: crate::vmi_probe
37///
38/// # Examples
39///
40/// ```ignore
41/// // `suppressed` holds pages a previous round found unserviceable.
42/// let prober = VmiProber::new(&suppressed);
43///
44/// // Each probed read is `Ok(Some(_))` if resident, `Ok(None)` if it faulted.
45/// let header = vmi_probe!(prober, vmi.read_struct::<Header>(address))?;
46/// let name = vmi_probe!(prober, vmi.read_string(name_address))?;
47///
48/// // Surface the accumulated, non-suppressed faults. Returning this error from
49/// // a reactor handler injects the fault and retries the operation.
50/// prober.error_for_page_faults()?;
51///
52/// // No faults pending: `header` and `name` are `Some` (or `None` for a
53/// // suppressed page) and safe to use.
54/// ```
55pub struct VmiProber {
56 /// Pages to suppress.
57 ///
58 /// Addresses already known to be unserviceable, so a probed read that
59 /// faults on one is skipped instead of being reported for injection.
60 suppressed: IndexSet<AddressContext>,
61
62 /// Non-suppressed page faults recorded by the probed reads so far.
63 page_faults: RefCell<IndexSet<AddressContext>>,
64}
65
66impl VmiProber {
67 /// Creates a prober that suppresses faults on the given pages.
68 ///
69 /// `suppressed` is the set of pages already known to be unserviceable
70 /// (typically discovered by an earlier round and recorded by an exception
71 /// handler). Faults on them are ignored rather than reported by
72 /// [`error_for_page_faults`].
73 ///
74 /// [`error_for_page_faults`]: Self::error_for_page_faults
75 pub fn new(suppressed: &IndexSet<AddressContext>) -> Self {
76 Self {
77 suppressed: suppressed.clone(),
78 page_faults: RefCell::new(IndexSet::new()),
79 }
80 }
81
82 /// Runs `f` and records any page fault it raises, returning `Ok(None)` on a
83 /// fault instead of propagating it.
84 ///
85 /// Equivalent to [`check_result`] applied to `f()`. See the type-level
86 /// protocol for how to surface the recorded faults.
87 ///
88 /// [`check_result`]: Self::check_result
89 pub fn probe<T, F>(&self, f: F) -> Result<Option<T>, VmiError>
90 where
91 F: FnOnce() -> Result<T, VmiError>,
92 {
93 self.check_result(f())
94 }
95
96 /// Records a page fault carried by `result` and returns `Ok(None)` instead
97 /// of propagating it, so the caller can keep probing further reads.
98 ///
99 /// A successful `result` becomes `Ok(Some(value))`. A
100 /// [`VmiError::Translation`] is recorded (unless its address is suppressed)
101 /// and becomes `Ok(None)`. Any other error is propagated unchanged. Call
102 /// [`error_for_page_faults`] afterwards to surface the recorded faults for
103 /// injection.
104 ///
105 /// [`error_for_page_faults`]: Self::error_for_page_faults
106 pub fn check_result<T>(&self, result: Result<T, VmiError>) -> Result<Option<T>, VmiError> {
107 match result {
108 Ok(value) => Ok(Some(value)),
109 Err(VmiError::Translation(pf)) => {
110 self.record(pf);
111 Ok(None)
112 }
113 Err(err) => Err(err),
114 }
115 }
116
117 /// Records the page faults that are not in the `suppressed` set.
118 fn record(&self, pf: AddressContext) {
119 let mut page_faults = self.page_faults.borrow_mut();
120 if !self.suppressed.contains(&pf) {
121 tracing::trace!(va = %pf.va, "page fault");
122 page_faults.insert(pf);
123 }
124 else {
125 tracing::trace!(va = %pf.va, "page fault (suppressed)");
126 }
127 }
128
129 /// Returns the recorded page faults that are not in the `suppressed` set.
130 pub fn page_faults(&self) -> IndexSet<AddressContext> {
131 &*self.page_faults.borrow() - &self.suppressed
132 }
133
134 /// Returns `Err(VmiError::Translation)` carrying the recorded, non-suppressed
135 /// page faults, or `Ok(())` if none occurred.
136 ///
137 /// The returned error is meant to be propagated to a page-fault-injecting
138 /// event loop (such as the `vmi-utils` reactor). Injecting these faults
139 /// pages the memory in so the probed operation can be retried.
140 pub fn error_for_page_faults(&self) -> Result<(), VmiError> {
141 let pfs = self.page_faults();
142 if !pfs.is_empty() {
143 tracing::trace!(?pfs);
144 return Err(VmiError::Translation(pfs[0]));
145 }
146
147 Ok(())
148 }
149}
150
151/// Probes a single read expression through a [`VmiProber`], returning
152/// `Ok(None)` if it page-faults.
153///
154/// Shorthand for [`VmiProber::check_result`] over the expression's result.
155///
156/// [`VmiProber`]: crate::VmiProber
157/// [`VmiProber::check_result`]: crate::VmiProber::check_result
158#[macro_export]
159macro_rules! vmi_probe {
160 ($prober:expr, $expr:expr) => {
161 $prober.check_result(|| -> Result<_, $crate::VmiError> { $expr }())
162 };
163}