Skip to main content

vmi_core/ctx/
session.rs

1use std::{io::ErrorKind, time::Duration};
2
3use super::{VmiState, context::VmiContext};
4use crate::{
5    Architecture, VcpuId, VmiCore, VmiError, VmiHandler,
6    driver::{VmiDriver, VmiEventControl, VmiQueryRegisters, VmiVmControl},
7    os::{NoOS, VmiOs},
8};
9
10/// A VMI session.
11///
12/// The session combines a [`VmiCore`] with an OS-specific [`VmiOs`]
13/// implementation to provide unified access to both low-level VMI operations
14/// and higher-level OS abstractions.
15pub struct VmiSession<'a, Os>
16where
17    Os: VmiOs,
18{
19    /// The VMI core providing low-level VM introspection capabilities.
20    core: &'a VmiCore<Os::Driver>,
21
22    /// The OS-specific operations and abstractions.
23    os: &'a Os,
24}
25
26impl<Os> Clone for VmiSession<'_, Os>
27where
28    Os: VmiOs,
29{
30    fn clone(&self) -> Self {
31        *self
32    }
33}
34
35impl<Os> Copy for VmiSession<'_, Os> where Os: VmiOs {}
36
37impl<Os> std::ops::Deref for VmiSession<'_, Os>
38where
39    Os: VmiOs,
40{
41    type Target = VmiCore<Os::Driver>;
42
43    fn deref(&self) -> &Self::Target {
44        self.core
45    }
46}
47
48impl<'a, Os> VmiSession<'a, Os>
49where
50    Os: VmiOs,
51{
52    /// Creates a new VMI session.
53    pub fn new(core: &'a VmiCore<Os::Driver>, os: &'a Os) -> Self {
54        Self { core, os }
55    }
56
57    /// Creates a new VMI state with the specified registers.
58    pub fn with_registers(
59        &'a self,
60        registers: &'a <Os::Architecture as Architecture>::Registers,
61    ) -> VmiState<'a, Os> {
62        VmiState::new(self, registers)
63    }
64
65    /// Creates a new VMI session without an OS-specific implementation.
66    pub fn without_os(&self) -> VmiSession<'a, NoOS<Os::Driver>> {
67        VmiSession {
68            core: self.core,
69            os: const { &NoOS(std::marker::PhantomData) },
70        }
71    }
72
73    /// Returns the VMI core.
74    pub fn core(&self) -> &'a VmiCore<Os::Driver> {
75        self.core
76    }
77
78    /// Returns the underlying OS-specific implementation.
79    pub fn underlying_os(&self) -> &'a Os {
80        self.os
81    }
82}
83
84///////////////////////////////////////////////////////////////////////////////
85// VmiEventControl
86///////////////////////////////////////////////////////////////////////////////
87
88impl<'a, Os> VmiSession<'a, Os>
89where
90    Os: VmiOs,
91    Os::Driver: VmiEventControl,
92{
93    /// Waits for an event to occur and processes it with the provided handler.
94    ///
95    /// This method blocks until an event occurs or the specified timeout is
96    /// reached. When an event occurs, it is passed to the provided callback
97    /// function for processing.
98    pub fn wait_for_event(
99        &self,
100        timeout: Duration,
101        handler: &mut impl VmiHandler<Os>,
102    ) -> Result<(), VmiError> {
103        self.core.wait_for_event(timeout, |event| {
104            let state = VmiState::new(self, event.registers());
105            handler.handle_event(VmiContext::new(&state, event))
106        })
107    }
108}
109
110///////////////////////////////////////////////////////////////////////////////
111// VmiEventControl + VmiVmControl
112///////////////////////////////////////////////////////////////////////////////
113
114impl<'a, Os> VmiSession<'a, Os>
115where
116    Os: VmiOs,
117    Os::Driver: VmiEventControl + VmiVmControl,
118{
119    /// Enters the main event handling loop that processes VMI events until
120    /// finished.
121    pub fn handle<Handler>(
122        &self,
123        handler_factory: impl FnOnce(&VmiSession<Os>) -> Result<Handler, VmiError>,
124    ) -> Result<Option<Handler::Output>, VmiError>
125    where
126        Handler: VmiHandler<Os>,
127    {
128        self.handle_with_timeout(Duration::from_millis(5000), handler_factory)
129    }
130
131    /// Enters the main event handling loop that processes VMI events until
132    /// finished, with a timeout for each event.
133    pub fn handle_with_timeout<Handler>(
134        &self,
135        timeout: Duration,
136        handler_factory: impl FnOnce(&VmiSession<Os>) -> Result<Handler, VmiError>,
137    ) -> Result<Option<Handler::Output>, VmiError>
138    where
139        Handler: VmiHandler<Os>,
140    {
141        let mut result;
142        let mut interrupted = false;
143        let mut handler = handler_factory(self)?;
144
145        loop {
146            result = handler.poll();
147
148            if result.is_some() {
149                break;
150            }
151
152            match self.wait_for_event(timeout, &mut handler) {
153                Ok(_) => {}
154                Err(VmiError::Timeout) => {
155                    tracing::trace!("timeout");
156                    handler.handle_timeout(self);
157                }
158                Err(VmiError::Io(err)) if err.kind() == ErrorKind::Interrupted => {
159                    tracing::trace!("interrupted");
160
161                    // A second signal ends the loop without waiting for the
162                    // handler.
163                    if interrupted {
164                        tracing::warn!("interrupted again");
165                    }
166
167                    if interrupted || handler.handle_interrupted(self).is_break() {
168                        result = handler.poll();
169                        break;
170                    }
171
172                    interrupted = true;
173                }
174                Err(err) => return Err(err),
175            }
176        }
177
178        tracing::trace!("disabling monitor");
179
180        // Pause the guest before the handler cleans up, and keep it paused
181        // until all pending events are answered. If the guest kept running,
182        // a vCPU could hit a breakpoint that is still in memory after the
183        // breakpoint monitor is turned off. The guest would then handle the
184        // breakpoint itself and crash.
185        //
186        // If the pause fails, clean up anyway and return the error after
187        // the reset.
188        let pause_guard = self.core().pause_guard();
189        handler.cleanup(self);
190        self.core.reset_state()?;
191        let _pause_guard = pause_guard?;
192
193        tracing::trace!(pending_events = self.events_pending());
194
195        if self.events_pending() > 0 {
196            match self.wait_for_event(Duration::from_millis(0), &mut handler) {
197                Err(VmiError::Timeout) => {
198                    tracing::trace!("timeout");
199                }
200                Err(err) => return Err(err),
201                Ok(_) => {}
202            }
203
204            // The responses to the drained events can re-enable single-step,
205            // either by toggling it or by requesting a fast single-step.
206            // With the monitor disabled, nothing would ever turn it off
207            // again, and Xen does not inject interrupts into a vCPU that is
208            // single-stepping, so the guest would hang. Reset again while
209            // the domain is still paused.
210            self.core.reset_state()?;
211        }
212
213        Ok(result)
214    }
215}
216
217///////////////////////////////////////////////////////////////////////////////
218// VmiQueryRegisters + VmiVmControl
219///////////////////////////////////////////////////////////////////////////////
220
221impl<'a, Os> VmiSession<'a, Os>
222where
223    Os: VmiOs,
224    Os::Driver: VmiQueryRegisters + VmiVmControl,
225{
226    /// Pauses the virtual machine, snapshots the boot CPU registers, and
227    /// returns a guard that resumes the VM when dropped.
228    pub fn pause_guard(&self) -> Result<VmiSessionPauseGuard<'_, Os>, VmiError> {
229        VmiSessionPauseGuard::new(self)
230    }
231}
232
233/// A guard that pauses the virtual machine and snapshots the boot CPU
234/// registers on creation, then resumes the VM on drop.
235///
236/// Unlike [`VmiPauseGuard`], this guard carries enough register context to
237/// build a [`VmiState`] via [`state`], so callers can introspect the paused
238/// guest without separately querying registers.
239///
240/// [`VmiPauseGuard`]: crate::VmiPauseGuard
241/// [`state`]: Self::state
242pub struct VmiSessionPauseGuard<'a, Os>
243where
244    Os: VmiOs,
245    Os::Driver: VmiQueryRegisters + VmiVmControl,
246{
247    session: &'a VmiSession<'a, Os>,
248    registers: <<Os::Driver as VmiDriver>::Architecture as Architecture>::Registers,
249}
250
251impl<'a, Os> VmiSessionPauseGuard<'a, Os>
252where
253    Os: VmiOs,
254    Os::Driver: VmiQueryRegisters + VmiVmControl,
255{
256    /// Creates a new pause guard.
257    pub fn new(session: &'a VmiSession<'a, Os>) -> Result<Self, VmiError> {
258        session.driver().pause()?;
259
260        let registers = match session.registers(VcpuId(0)) {
261            Ok(registers) => registers,
262            Err(err) => {
263                if let Err(resume_err) = session.driver().resume() {
264                    tracing::error!(
265                        err = %resume_err,
266                        "failed to resume after register-query failure"
267                    );
268                }
269                return Err(err);
270            }
271        };
272
273        Ok(Self { session, registers })
274    }
275
276    /// Returns the VMI session.
277    pub fn session(&self) -> &VmiSession<'a, Os> {
278        self.session
279    }
280
281    /// Returns the boot CPU (`VcpuId(0)`) registers captured when
282    /// the guard was created.
283    pub fn registers(&self) -> &<Os::Architecture as Architecture>::Registers {
284        &self.registers
285    }
286
287    /// Returns the state captured when the guard was created, bound to
288    /// the boot CPU (`VcpuId(0)`) registers.
289    pub fn state(&self) -> VmiState<'_, Os> {
290        VmiState::new(self.session, &self.registers)
291    }
292}
293
294impl<Os> Drop for VmiSessionPauseGuard<'_, Os>
295where
296    Os: VmiOs,
297    Os::Driver: VmiQueryRegisters + VmiVmControl,
298{
299    fn drop(&mut self) {
300        if let Err(err) = self.session.driver().resume() {
301            tracing::error!(%err, "failed to resume the virtual machine");
302        }
303    }
304}