Skip to main content

vmi_core/
lib.rs

1//! Core VMI functionality.
2
3pub mod arch;
4mod core;
5mod ctx;
6pub mod driver;
7mod error;
8mod event;
9mod handler;
10pub mod os;
11mod page;
12pub mod trace;
13
14use std::{cell::RefCell, num::NonZeroUsize, time::Duration};
15
16use isr_macros::Field;
17use lru::LruCache;
18use zerocopy::{FromBytes, Immutable, IntoBytes};
19
20pub use self::{
21    arch::{Architecture, Registers},
22    core::{
23        AccessContext, AddressContext, Gfn, MemoryAccess, MemoryAccessOptions, Pa,
24        TranslationMechanism, Va, VcpuId, View, VmiInfo, VmiVa,
25    },
26    ctx::{
27        VmiContext, VmiOsContext, VmiOsState, VmiProber, VmiSession, VmiSessionPauseGuard, VmiState,
28    },
29    driver::{
30        VmiDriver, VmiEventControl, VmiFullDriver, VmiMemory, VmiProtection, VmiQueryProtection,
31        VmiQueryRegisters, VmiRead, VmiReadAccess, VmiRegisters, VmiSetProtection, VmiSetRegisters,
32        VmiViewControl, VmiVmControl, VmiWrite, VmiWriteAccess,
33    },
34    error::VmiError,
35    event::{VmiEvent, VmiEventAction, VmiEventFlags, VmiEventResponse},
36    handler::VmiHandler,
37    os::{VmiOs, VmiOsExt},
38    page::VmiMappedPage,
39};
40
41struct Cache {
42    gfn: RefCell<LruCache<Gfn, VmiMappedPage>>,
43    v2p: RefCell<LruCache<AccessContext, Pa>>,
44}
45
46impl Cache {
47    const DEFAULT_SIZE: NonZeroUsize = NonZeroUsize::new(8192).unwrap();
48
49    pub fn new() -> Self {
50        Self {
51            gfn: RefCell::new(LruCache::new(Self::DEFAULT_SIZE)),
52            v2p: RefCell::new(LruCache::new(Self::DEFAULT_SIZE)),
53        }
54    }
55}
56
57/// The core functionality for Virtual Machine Introspection (VMI).
58pub struct VmiCore<Driver>
59where
60    Driver: VmiDriver,
61{
62    driver: Driver,
63    cache: Cache,
64
65    read_page_fn: fn(&Self, Gfn) -> Result<VmiMappedPage, VmiError>,
66    translate_access_context_fn: fn(&Self, AccessContext) -> Result<Pa, VmiError>,
67
68    read_string_length_limit: RefCell<Option<usize>>,
69}
70
71///////////////////////////////////////////////////////////////////////////////
72// VmiDriver
73///////////////////////////////////////////////////////////////////////////////
74
75impl<Driver> VmiCore<Driver>
76where
77    Driver: VmiDriver,
78{
79    /// Returns the driver used by this `VmiCore` instance.
80    pub fn driver(&self) -> &Driver {
81        &self.driver
82    }
83
84    /// Retrieves information about the virtual machine.
85    pub fn info(&self) -> Result<VmiInfo, VmiError> {
86        self.driver.info()
87    }
88}
89
90///////////////////////////////////////////////////////////////////////////////
91// VmiRead
92///////////////////////////////////////////////////////////////////////////////
93
94impl<Driver> VmiCore<Driver>
95where
96    Driver: VmiRead,
97{
98    /// Creates a new `VmiCore` instance with the given driver.
99    ///
100    /// Both the GFN cache and the V2P cache are enabled by default,
101    /// each with a capacity of 8192 entries.
102    pub fn new(driver: Driver) -> Result<Self, VmiError> {
103        Ok(Self {
104            driver,
105            cache: Cache::new(),
106            read_page_fn: Self::read_page_cache,
107            translate_access_context_fn: Self::translate_access_context_cache,
108            read_string_length_limit: RefCell::new(None),
109        })
110    }
111
112    /// Enables the Guest Frame Number (GFN) cache.
113    ///
114    /// The GFN cache stores the contents of recently accessed memory pages,
115    /// indexed by their GFN. This can significantly improve performance when
116    /// repeatedly accessing the same memory regions, as it avoids redundant
117    /// reads from the virtual machine.
118    ///
119    /// When enabled, subsequent calls to [`read_page`] will first check
120    /// the cache before querying the driver.
121    ///
122    /// # Panics
123    ///
124    /// Panics if `size` is zero.
125    ///
126    /// [`read_page`]: Self::read_page
127    pub fn with_gfn_cache(self, size: usize) -> Self {
128        Self {
129            cache: Cache {
130                gfn: RefCell::new(LruCache::new(NonZeroUsize::new(size).unwrap())),
131                ..self.cache
132            },
133            read_page_fn: Self::read_page_cache,
134            ..self
135        }
136    }
137
138    /// Enables the GFN cache.
139    ///
140    /// See [`with_gfn_cache`] for more details.
141    ///
142    /// [`with_gfn_cache`]: Self::with_gfn_cache
143    pub fn enable_gfn_cache(&mut self) {
144        self.read_page_fn = Self::read_page_cache;
145    }
146
147    /// Disables the GFN cache.
148    ///
149    /// Subsequent calls to [`read_page`] will bypass the cache and read
150    /// directly from the virtual machine.
151    ///
152    /// [`read_page`]: Self::read_page
153    pub fn disable_gfn_cache(&mut self) {
154        self.read_page_fn = Self::read_page_nocache;
155    }
156
157    /// Resizes the GFN cache.
158    ///
159    /// This allows you to adjust the cache size dynamically based on your
160    /// performance needs. A larger cache can improve performance for
161    /// workloads with high memory locality, but consumes more memory.
162    ///
163    /// # Panics
164    ///
165    /// Panics if `size` is zero.
166    pub fn resize_gfn_cache(&mut self, size: usize) {
167        self.cache
168            .gfn
169            .borrow_mut()
170            .resize(NonZeroUsize::new(size).unwrap());
171    }
172
173    /// Removes a specific entry from the GFN cache.
174    ///
175    /// Returns the removed entry if it was present.
176    /// This is useful for invalidating cached data that might have
177    /// become stale.
178    pub fn flush_gfn_cache_entry(&self, gfn: Gfn) -> Option<VmiMappedPage> {
179        self.cache.gfn.borrow_mut().pop(&gfn)
180    }
181
182    /// Clears the entire GFN cache.
183    pub fn flush_gfn_cache(&self) {
184        self.cache.gfn.borrow_mut().clear();
185    }
186
187    ///// Retrieves metrics about the GFN cache.
188    //pub fn gfn_cache_metrics(&self) -> CacheMetrics {
189    //    let cache = self.cache.gfn.borrow();
190    //    CacheMetrics {
191    //        hits: ...,
192    //        misses: ...,
193    //    }
194    //}
195
196    /// Enables the virtual-to-physical (V2P) address translation cache.
197    ///
198    /// The V2P cache stores the results of recent address translations,
199    /// mapping virtual addresses (represented by [`AccessContext`]) to their
200    /// corresponding physical addresses ([`Pa`]). This can significantly
201    /// speed up memory access operations, as address translation can be a
202    /// relatively expensive operation.
203    ///
204    /// When enabled, [`translate_access_context`] will consult the cache
205    /// before performing a full translation.
206    ///
207    /// # Panics
208    ///
209    /// Panics if `size` is zero.
210    ///
211    /// [`translate_access_context`]: Self::translate_access_context
212    pub fn with_v2p_cache(self, size: usize) -> Self {
213        Self {
214            cache: Cache {
215                v2p: RefCell::new(LruCache::new(NonZeroUsize::new(size).unwrap())),
216                ..self.cache
217            },
218            translate_access_context_fn: Self::translate_access_context_cache,
219            ..self
220        }
221    }
222
223    /// Enables the V2P cache.
224    ///
225    /// See [`with_v2p_cache`] for more details.
226    ///
227    /// [`with_v2p_cache`]: Self::with_v2p_cache
228    pub fn enable_v2p_cache(&mut self) {
229        self.translate_access_context_fn = Self::translate_access_context_cache;
230    }
231
232    /// Disables the V2P cache.
233    ///
234    /// Subsequent calls to [`translate_access_context`] will bypass the cache
235    /// and perform a full address translation every time.
236    ///
237    /// [`translate_access_context`]: Self::translate_access_context
238    pub fn disable_v2p_cache(&mut self) {
239        self.translate_access_context_fn = Self::translate_access_context_nocache;
240    }
241
242    /// Resizes the V2P cache.
243    ///
244    /// This allows dynamic adjustment of the cache size to balance
245    /// performance and memory usage. A larger cache can lead to better
246    /// performance if address translations are frequent and exhibit
247    /// good locality.
248    ///
249    /// # Panics
250    ///
251    /// Panics if `size` is zero.
252    pub fn resize_v2p_cache(&mut self, size: usize) {
253        self.cache
254            .v2p
255            .borrow_mut()
256            .resize(NonZeroUsize::new(size).unwrap());
257    }
258
259    /// Removes a specific entry from the V2P cache.
260    ///
261    /// Returns the removed entry if it was present.
262    /// This can be used to invalidate cached translations that may have
263    /// become stale due to changes in the guest's memory mapping.
264    pub fn flush_v2p_cache_entry(&self, ctx: AccessContext) -> Option<Pa> {
265        self.cache.v2p.borrow_mut().pop(&ctx)
266    }
267
268    /// Clears the entire V2P cache.
269    ///
270    /// This method is crucial for maintaining consistency when handling events.
271    /// The guest operating system can modify page tables or other structures
272    /// related to address translation between events. Using stale translations
273    /// can lead to incorrect memory access and unexpected behavior.
274    /// It is recommended to call this method at the beginning of each
275    /// [`VmiHandler::handle_event`] loop to ensure that you are working with
276    /// the most up-to-date address mappings.
277    pub fn flush_v2p_cache(&self) {
278        self.cache.v2p.borrow_mut().clear();
279    }
280
281    ///// Retrieves metrics about the V2P cache.
282    //pub fn v2p_cache_metrics(&self) -> CacheMetrics {
283    //    let cache = self.cache.v2p.borrow();
284    //    CacheMetrics {
285    //        hits: ...,
286    //        misses: ...,
287    //    }
288    //}
289
290    /// Sets a limit on the length of strings read by the `read_string` methods.
291    /// If the limit is reached, the string will be truncated.
292    pub fn with_read_string_length_limit(self, limit_in_bytes: usize) -> Self {
293        Self {
294            read_string_length_limit: RefCell::new(Some(limit_in_bytes)),
295            ..self
296        }
297    }
298
299    /// Returns the current limit on the length of strings read by the
300    /// `read_string` methods.
301    pub fn read_string_length_limit(&self) -> Option<usize> {
302        *self.read_string_length_limit.borrow()
303    }
304
305    /// Sets a limit on the length of strings read by the `read_string` methods.
306    ///
307    /// This method allows you to set a maximum length (in bytes) for strings
308    /// read from the virtual machine's memory. When set, string reading
309    /// operations will truncate their results to this limit. This can be
310    /// useful for preventing excessively long string reads, which might
311    /// impact performance or consume too much memory.
312    ///
313    /// If the limit is reached during a string read operation, the resulting
314    /// string will be truncated to the specified length.
315    ///
316    /// To remove the limit, call this method with `None`.
317    pub fn set_read_string_length_limit(&self, limit: usize) {
318        *self.read_string_length_limit.borrow_mut() = Some(limit);
319    }
320
321    /// Reads memory from the virtual machine.
322    pub fn read(&self, ctx: impl Into<AccessContext>, buffer: &mut [u8]) -> Result<(), VmiError> {
323        let ctx = ctx.into();
324        let mut position = 0usize;
325        let mut remaining = buffer.len();
326
327        while remaining > 0 {
328            let address = self.translate_access_context(ctx + position as u64)?;
329            let gfn = Driver::Architecture::gfn_from_pa(address);
330            let offset = Driver::Architecture::pa_offset(address) as usize;
331
332            let page = self.read_page(gfn)?;
333            let page = &page[offset..];
334
335            let size = std::cmp::min(remaining, page.len());
336            buffer[position..position + size].copy_from_slice(&page[..size]);
337
338            position += size;
339            remaining -= size;
340        }
341
342        Ok(())
343    }
344
345    /// Reads a single byte from the virtual machine.
346    pub fn read_u8(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError> {
347        let mut buffer = [0u8; 1];
348        self.read(ctx, &mut buffer)?;
349        Ok(buffer[0])
350    }
351
352    /// Reads a 16-bit unsigned integer from the virtual machine.
353    pub fn read_u16(&self, ctx: impl Into<AccessContext>) -> Result<u16, VmiError> {
354        let mut buffer = [0u8; 2];
355        self.read(ctx, &mut buffer)?;
356        Ok(u16::from_le_bytes(buffer))
357    }
358
359    /// Reads a 32-bit unsigned integer from the virtual machine.
360    pub fn read_u32(&self, ctx: impl Into<AccessContext>) -> Result<u32, VmiError> {
361        let mut buffer = [0u8; 4];
362        self.read(ctx, &mut buffer)?;
363        Ok(u32::from_le_bytes(buffer))
364    }
365
366    /// Reads a 64-bit unsigned integer from the virtual machine.
367    pub fn read_u64(&self, ctx: impl Into<AccessContext>) -> Result<u64, VmiError> {
368        let mut buffer = [0u8; 8];
369        self.read(ctx, &mut buffer)?;
370        Ok(u64::from_le_bytes(buffer))
371    }
372
373    /// Reads an unsigned integer of the specified size from the virtual machine.
374    ///
375    /// This method reads an unsigned integer of the specified size (in bytes)
376    /// from the virtual machine. Note that the size must be 1, 2, 4, or 8.
377    ///
378    /// The result is returned as a [`u64`] to accommodate the widest possible
379    /// integer size.
380    pub fn read_uint(&self, ctx: impl Into<AccessContext>, size: usize) -> Result<u64, VmiError> {
381        match size {
382            1 => self.read_u8(ctx).map(u64::from),
383            2 => self.read_u16(ctx).map(u64::from),
384            4 => self.read_u32(ctx).map(u64::from),
385            8 => self.read_u64(ctx),
386            _ => Err(VmiError::InvalidAddressWidth),
387        }
388    }
389
390    /// Reads a field of a structure from the virtual machine.
391    ///
392    /// This method reads a field from the virtual machine. The field is
393    /// defined by the provided [`Field`] structure, which specifies the
394    /// offset and size of the field within the memory region.
395    ///
396    /// The result is returned as a [`u64`] to accommodate the widest possible
397    /// integer size.
398    pub fn read_field(
399        &self,
400        ctx: impl Into<AccessContext>,
401        field: &Field,
402    ) -> Result<u64, VmiError> {
403        self.read_uint(ctx.into() + field.offset(), field.size() as usize)
404    }
405
406    /// Reads an address-sized unsigned integer from the virtual machine.
407    ///
408    /// This method reads an address of the specified width (in bytes) from
409    /// the given access context. It's useful when dealing with architectures
410    /// that can operate in different address modes.
411    pub fn read_address(
412        &self,
413        ctx: impl Into<AccessContext>,
414        address_width: usize,
415    ) -> Result<u64, VmiError> {
416        match address_width {
417            4 => self.read_address32(ctx),
418            8 => self.read_address64(ctx),
419            _ => Err(VmiError::InvalidAddressWidth),
420        }
421    }
422
423    /// Reads a 32-bit address from the virtual machine.
424    pub fn read_address32(&self, ctx: impl Into<AccessContext>) -> Result<u64, VmiError> {
425        Ok(self.read_u32(ctx)? as u64)
426    }
427
428    /// Reads a 64-bit address from the virtual machine.
429    pub fn read_address64(&self, ctx: impl Into<AccessContext>) -> Result<u64, VmiError> {
430        self.read_u64(ctx)
431    }
432
433    /// Reads a virtual address from the virtual machine.
434    pub fn read_va(
435        &self,
436        ctx: impl Into<AccessContext>,
437        address_width: usize,
438    ) -> Result<Va, VmiError> {
439        Ok(Va(self.read_address(ctx, address_width)?))
440    }
441
442    /// Reads a 32-bit virtual address from the virtual machine.
443    pub fn read_va32(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError> {
444        Ok(Va(self.read_address32(ctx)?))
445    }
446
447    /// Reads a 64-bit virtual address from the virtual machine.
448    pub fn read_va64(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError> {
449        Ok(Va(self.read_address64(ctx)?))
450    }
451
452    /// Reads a null-terminated string of bytes from the virtual machine with a
453    /// specified limit.
454    pub fn read_string_bytes_limited(
455        &self,
456        ctx: impl Into<AccessContext>,
457        limit: usize,
458    ) -> Result<Vec<u8>, VmiError> {
459        let mut ctx = ctx.into();
460
461        // read until the end of page
462        let mut buffer = vec![
463            0u8;
464            (Driver::Architecture::PAGE_SIZE - (ctx.address & !Driver::Architecture::PAGE_MASK))
465                as usize
466        ];
467        self.read(ctx, &mut buffer)?;
468
469        // try to find the null terminator
470        let position = buffer.iter().position(|&b| b == 0);
471
472        if let Some(position) = position {
473            buffer.truncate(limit.min(position));
474            return Ok(buffer);
475        }
476
477        let mut page = [0u8; 4096_usize]; // FIXME: Driver::Architecture::PAGE_SIZE
478        loop {
479            ctx.address += buffer.len() as u64;
480            self.read(ctx, &mut page)?;
481
482            let position = page.iter().position(|&b| b == 0);
483
484            if let Some(position) = position {
485                buffer.extend_from_slice(&page[..position]);
486
487                if buffer.len() >= limit {
488                    buffer.truncate(limit);
489                }
490
491                break;
492            }
493
494            buffer.extend_from_slice(&page);
495
496            if buffer.len() >= limit {
497                buffer.truncate(limit);
498                break;
499            }
500        }
501
502        Ok(buffer)
503    }
504
505    /// Reads a null-terminated string of bytes from the virtual machine.
506    pub fn read_string_bytes(&self, ctx: impl Into<AccessContext>) -> Result<Vec<u8>, VmiError> {
507        self.read_string_bytes_limited(
508            ctx,
509            self.read_string_length_limit.borrow().unwrap_or(usize::MAX),
510        )
511    }
512
513    /// Reads a null-terminated wide string (UTF-16) from the virtual machine
514    /// with a specified limit.
515    pub fn read_string_utf16_bytes_limited(
516        &self,
517        ctx: impl Into<AccessContext>,
518        limit: usize,
519    ) -> Result<Vec<u16>, VmiError> {
520        let mut ctx = ctx.into();
521
522        // read until the end of page
523        let mut buffer = vec![
524            0u8;
525            (Driver::Architecture::PAGE_SIZE - (ctx.address & !Driver::Architecture::PAGE_MASK))
526                as usize
527        ];
528        self.read(ctx, &mut buffer)?;
529
530        // try to find the null terminator
531        let (chunks, _) = buffer.as_chunks::<2>();
532        let position = chunks
533            .iter()
534            .position(|chunk| chunk[0] == 0 && chunk[1] == 0);
535
536        if let Some(position) = position {
537            buffer.truncate(limit.min(position * 2));
538
539            let (chunks, _) = buffer.as_chunks::<2>();
540            return Ok(chunks
541                .iter()
542                .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
543                .collect());
544        }
545
546        let mut page = [0u8; 4096_usize]; // FIXME: Driver::Architecture::PAGE_SIZE
547        loop {
548            ctx.address += buffer.len() as u64;
549            self.read(ctx, &mut page)?;
550
551            let (chunks, _) = page.as_chunks::<2>();
552            let position = chunks
553                .iter()
554                .position(|chunk| chunk[0] == 0 && chunk[1] == 0);
555
556            if let Some(position) = position {
557                buffer.extend_from_slice(&page[..position * 2]);
558
559                if buffer.len() >= limit {
560                    buffer.truncate(limit);
561                }
562
563                break;
564            }
565
566            buffer.extend_from_slice(&page);
567
568            if buffer.len() >= limit {
569                buffer.truncate(limit);
570                break;
571            }
572        }
573
574        let (chunks, _) = buffer.as_chunks::<2>();
575        Ok(chunks
576            .iter()
577            .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
578            .collect())
579    }
580
581    /// Reads a null-terminated wide string (UTF-16) from the virtual machine.
582    pub fn read_string_utf16_bytes(
583        &self,
584        ctx: impl Into<AccessContext>,
585    ) -> Result<Vec<u16>, VmiError> {
586        self.read_string_utf16_bytes_limited(
587            ctx,
588            self.read_string_length_limit.borrow().unwrap_or(usize::MAX),
589        )
590    }
591
592    /// Reads a null-terminated string from the virtual machine with a specified
593    /// limit.
594    pub fn read_string_limited(
595        &self,
596        ctx: impl Into<AccessContext>,
597        limit: usize,
598    ) -> Result<String, VmiError> {
599        Ok(String::from_utf8_lossy(&self.read_string_bytes_limited(ctx, limit)?).into())
600    }
601
602    /// Reads a null-terminated string from the virtual machine.
603    pub fn read_string(&self, ctx: impl Into<AccessContext>) -> Result<String, VmiError> {
604        self.read_string_limited(
605            ctx,
606            self.read_string_length_limit.borrow().unwrap_or(usize::MAX),
607        )
608    }
609
610    /// Reads a null-terminated wide string (UTF-16) from the virtual machine
611    /// with a specified limit.
612    pub fn read_string_utf16_limited(
613        &self,
614        ctx: impl Into<AccessContext>,
615        limit: usize,
616    ) -> Result<String, VmiError> {
617        Ok(String::from_utf16_lossy(
618            &self.read_string_utf16_bytes_limited(ctx, limit)?,
619        ))
620    }
621
622    /// Reads a null-terminated wide string (UTF-16) from the virtual machine.
623    pub fn read_string_utf16(&self, ctx: impl Into<AccessContext>) -> Result<String, VmiError> {
624        self.read_string_utf16_limited(
625            ctx,
626            self.read_string_length_limit.borrow().unwrap_or(usize::MAX),
627        )
628    }
629
630    /// Reads a struct from the virtual machine.
631    pub fn read_struct<T>(&self, ctx: impl Into<AccessContext>) -> Result<T, VmiError>
632    where
633        T: FromBytes + IntoBytes,
634    {
635        let mut result = T::new_zeroed();
636        self.read(ctx, result.as_mut_bytes())?;
637        Ok(result)
638    }
639
640    /// Translates a virtual address to a physical address.
641    pub fn translate_address(&self, ctx: impl Into<AddressContext>) -> Result<Pa, VmiError> {
642        self.translate_access_context(AccessContext::from(ctx.into()))
643    }
644
645    /// Translates an access context to a physical address.
646    pub fn translate_access_context(&self, ctx: AccessContext) -> Result<Pa, VmiError> {
647        (self.translate_access_context_fn)(self, ctx)
648    }
649
650    /// Reads a page of memory from the virtual machine.
651    pub fn read_page(&self, gfn: Gfn) -> Result<VmiMappedPage, VmiError> {
652        (self.read_page_fn)(self, gfn)
653    }
654
655    /// Reads a page of memory from the virtual machine without using the cache.
656    fn read_page_nocache(&self, gfn: Gfn) -> Result<VmiMappedPage, VmiError> {
657        self.driver.read_page(gfn)
658    }
659
660    /// Reads a page of memory from the virtual machine, using the cache if
661    /// enabled.
662    fn read_page_cache(&self, gfn: Gfn) -> Result<VmiMappedPage, VmiError> {
663        let mut cache = self.cache.gfn.borrow_mut();
664        let value = cache.try_get_or_insert(gfn, || self.read_page_nocache(gfn))?;
665
666        // Mapped pages are reference counted, so cloning it is cheap.
667        Ok(value.clone())
668    }
669
670    /// Translates an access context to a physical address without using the
671    /// cache.
672    ///
673    /// # Notes
674    ///
675    /// If [`TranslationMechanism::Paging`] is used, the `root` must be present.
676    /// In case the root is not present, a [`VmiError::RootNotPresent`] error is
677    /// returned.
678    fn translate_access_context_nocache(&self, ctx: AccessContext) -> Result<Pa, VmiError> {
679        Ok(match ctx.mechanism {
680            TranslationMechanism::Direct => Pa(ctx.address),
681            TranslationMechanism::Paging { root } => match root {
682                Some(root) => <Driver::Architecture as Architecture>::translate_address(
683                    self,
684                    ctx.address.into(),
685                    root,
686                )?,
687                None => return Err(VmiError::RootNotPresent),
688            },
689        })
690    }
691
692    /// Translates an access context to a physical address, using the cache if
693    /// enabled.
694    fn translate_access_context_cache(&self, ctx: AccessContext) -> Result<Pa, VmiError> {
695        let mut cache = self.cache.v2p.borrow_mut();
696        let value = cache.try_get_or_insert(ctx, || self.translate_access_context_nocache(ctx))?;
697        Ok(*value)
698    }
699}
700
701///////////////////////////////////////////////////////////////////////////////
702// VmiRead + VmiWrite
703///////////////////////////////////////////////////////////////////////////////
704
705impl<Driver> VmiCore<Driver>
706where
707    Driver: VmiRead + VmiWrite,
708{
709    /// Writes memory to the virtual machine.
710    pub fn write(&self, ctx: impl Into<AccessContext>, buffer: &[u8]) -> Result<(), VmiError> {
711        let ctx = ctx.into();
712        let mut position = 0usize;
713        let mut remaining = buffer.len();
714
715        while remaining > 0 {
716            let address = self.translate_access_context(ctx + position as u64)?;
717            let gfn = Driver::Architecture::gfn_from_pa(address);
718            let offset = Driver::Architecture::pa_offset(address);
719
720            let size = std::cmp::min(
721                remaining,
722                (Driver::Architecture::PAGE_SIZE - offset) as usize,
723            );
724            let content = &buffer[position..position + size];
725
726            self.driver.write_page(gfn, offset, content)?;
727
728            position += size;
729            remaining -= size;
730        }
731
732        Ok(())
733    }
734
735    /// Writes a single byte to the virtual machine.
736    pub fn write_u8(&self, ctx: impl Into<AccessContext>, value: u8) -> Result<(), VmiError> {
737        self.write(ctx, &value.to_le_bytes())
738    }
739
740    /// Writes a 16-bit unsigned integer to the virtual machine.
741    pub fn write_u16(&self, ctx: impl Into<AccessContext>, value: u16) -> Result<(), VmiError> {
742        self.write(ctx, &value.to_le_bytes())
743    }
744
745    /// Writes a 32-bit unsigned integer to the virtual machine.
746    pub fn write_u32(&self, ctx: impl Into<AccessContext>, value: u32) -> Result<(), VmiError> {
747        self.write(ctx, &value.to_le_bytes())
748    }
749
750    /// Writes a 64-bit unsigned integer to the virtual machine.
751    pub fn write_u64(&self, ctx: impl Into<AccessContext>, value: u64) -> Result<(), VmiError> {
752        self.write(ctx, &value.to_le_bytes())
753    }
754
755    /// Writes a struct to the virtual machine.
756    pub fn write_struct<T>(&self, ctx: impl Into<AccessContext>, value: T) -> Result<(), VmiError>
757    where
758        T: IntoBytes + Immutable,
759    {
760        self.write(ctx, value.as_bytes())
761    }
762}
763
764///////////////////////////////////////////////////////////////////////////////
765// VmiQueryProtection
766///////////////////////////////////////////////////////////////////////////////
767
768impl<Driver> VmiCore<Driver>
769where
770    Driver: VmiQueryProtection,
771{
772    /// Retrieves the memory access permissions for a specific guest frame
773    /// number (GFN).
774    ///
775    /// The returned `MemoryAccess` indicates the current read, write, and
776    /// execute permissions for the specified memory page in the given view.
777    pub fn memory_access(&self, gfn: Gfn, view: View) -> Result<MemoryAccess, VmiError> {
778        self.driver.memory_access(gfn, view)
779    }
780}
781
782///////////////////////////////////////////////////////////////////////////////
783// VmiSetProtection
784///////////////////////////////////////////////////////////////////////////////
785
786impl<Driver> VmiCore<Driver>
787where
788    Driver: VmiSetProtection,
789{
790    /// Sets the memory access permissions for a specific guest frame number
791    /// (GFN).
792    ///
793    /// This method allows you to modify the read, write, and execute
794    /// permissions for a given memory page in the specified view.
795    pub fn set_memory_access(
796        &self,
797        gfn: Gfn,
798        view: View,
799        access: MemoryAccess,
800    ) -> Result<(), VmiError> {
801        self.driver.set_memory_access(gfn, view, access)
802    }
803
804    /// Sets the memory access permissions for a specific guest frame number
805    /// (GFN) with additional options.
806    ///
807    /// In addition to the basic read, write, and execute permissions, this
808    /// method allows you to specify additional options for the memory access.
809    pub fn set_memory_access_with_options(
810        &self,
811        gfn: Gfn,
812        view: View,
813        access: MemoryAccess,
814        options: MemoryAccessOptions,
815    ) -> Result<(), VmiError> {
816        self.driver
817            .set_memory_access_with_options(gfn, view, access, options)
818    }
819}
820
821///////////////////////////////////////////////////////////////////////////////
822// VmiQueryRegisters
823///////////////////////////////////////////////////////////////////////////////
824
825impl<Driver> VmiCore<Driver>
826where
827    Driver: VmiQueryRegisters,
828{
829    /// Retrieves the current state of CPU registers for a specified virtual
830    /// CPU.
831    ///
832    /// This method allows you to access the current values of CPU registers,
833    /// which is crucial for understanding the state of the virtual machine
834    /// at a given point in time.
835    ///
836    /// # Notes
837    ///
838    /// The exact structure and content of the returned registers depend on the
839    /// specific architecture of the VM being introspected. Refer to the
840    /// documentation of your [`Architecture`] implementation for details on
841    /// how to interpret the register values.
842    pub fn registers(
843        &self,
844        vcpu: VcpuId,
845    ) -> Result<<Driver::Architecture as Architecture>::Registers, VmiError> {
846        self.driver.registers(vcpu)
847    }
848}
849
850///////////////////////////////////////////////////////////////////////////////
851// VmiSetRegisters
852///////////////////////////////////////////////////////////////////////////////
853
854impl<Driver> VmiCore<Driver>
855where
856    Driver: VmiSetRegisters,
857{
858    /// Sets the registers of a virtual CPU.
859    pub fn set_registers(
860        &self,
861        vcpu: VcpuId,
862        registers: <Driver::Architecture as Architecture>::Registers,
863    ) -> Result<(), VmiError> {
864        self.driver.set_registers(vcpu, registers)
865    }
866}
867
868///////////////////////////////////////////////////////////////////////////////
869// VmiViewControl
870///////////////////////////////////////////////////////////////////////////////
871
872impl<Driver> VmiCore<Driver>
873where
874    Driver: VmiViewControl,
875{
876    /// Returns the default view for the virtual machine.
877    ///
878    /// The default view typically represents the normal, unmodified state of
879    /// the VM's memory.
880    pub fn default_view(&self) -> View {
881        self.driver.default_view()
882    }
883
884    /// Creates a new view with the specified default access permissions.
885    ///
886    /// Views allow for creating different perspectives of the VM's memory,
887    /// which can be useful for analysis or isolation purposes. The default
888    /// access permissions apply to memory pages not explicitly modified
889    /// within this view.
890    pub fn create_view(&self, default_access: MemoryAccess) -> Result<View, VmiError> {
891        self.driver.create_view(default_access)
892    }
893
894    /// Destroys a previously created view.
895    ///
896    /// This method removes a view and frees associated resources. It should be
897    /// called when a view is no longer needed to prevent resource leaks.
898    pub fn destroy_view(&self, view: View) -> Result<(), VmiError> {
899        self.driver.destroy_view(view)
900    }
901
902    /// Switches to a different view for all virtual CPUs.
903    ///
904    /// This method changes the current active view for all vCPUs, affecting
905    /// subsequent memory operations across the entire VM. It allows for
906    /// quick transitions between different memory perspectives globally.
907    ///
908    /// Note the difference between this method and
909    /// [`VmiEventResponse::with_view()`]:
910    /// - `switch_to_view()` changes the view for all vCPUs immediately.
911    /// - `VmiEventResponse::with_view()` sets the view only for the specific
912    ///   vCPU that received the event, and the change is applied when the event
913    ///   handler returns.
914    ///
915    /// Use `switch_to_view()` for global view changes, and
916    /// `VmiEventResponse::with_view()` for targeted, event-specific view
917    /// modifications on individual vCPUs.
918    pub fn switch_to_view(&self, view: View) -> Result<(), VmiError> {
919        self.driver.switch_to_view(view)
920    }
921
922    /// Changes the mapping of a guest frame number (GFN) in a specific view.
923    ///
924    /// This method allows for remapping a GFN to a different physical frame
925    /// within a view, enabling fine-grained control over memory layout in
926    /// different views.
927    ///
928    /// A notable use case for this method is implementing "stealth hooks":
929    /// 1. Create a new GFN and copy the contents of the original page to it.
930    /// 2. Modify the new page by installing a breakpoint (e.g., 0xcc on AMD64)
931    ///    at a strategic location.
932    /// 3. Use this method to change the mapping of the original GFN to the new
933    ///    one.
934    /// 4. Set the memory access of the new GFN to non-readable.
935    ///
936    /// When a read access occurs:
937    /// - The handler should enable single-stepping.
938    /// - Switch to an unmodified view (e.g., `default_view`) to execute the
939    ///   read instruction, which will read the original non-breakpoint byte.
940    /// - Re-enable single-stepping afterwards.
941    ///
942    /// This technique allows for transparent breakpoints that are difficult to
943    /// detect by the guest OS or applications.
944    pub fn change_view_gfn(&self, view: View, old_gfn: Gfn, new_gfn: Gfn) -> Result<(), VmiError> {
945        self.driver.change_view_gfn(view, old_gfn, new_gfn)
946    }
947
948    /// Resets the mapping of a guest frame number (GFN) in a specific view to
949    /// its original state.
950    ///
951    /// This method reverts any custom mapping for the specified GFN in the
952    /// given view, restoring it to the default mapping.
953    pub fn reset_view_gfn(&self, view: View, gfn: Gfn) -> Result<(), VmiError> {
954        self.driver.reset_view_gfn(view, gfn)
955    }
956}
957
958///////////////////////////////////////////////////////////////////////////////
959// VmiEventControl
960///////////////////////////////////////////////////////////////////////////////
961
962impl<Driver> VmiCore<Driver>
963where
964    Driver: VmiEventControl,
965{
966    /// Enables monitoring of specific events.
967    ///
968    /// This method allows you to enable monitoring of specific events, such as
969    /// control register writes, interrupts, or single-step execution.
970    /// Monitoring events can be useful for tracking specific guest behavior or
971    /// for implementing custom analysis tools.
972    ///
973    /// The type of event to monitor is defined by the architecture-specific
974    /// [`Architecture::EventMonitor`] type.
975    ///
976    /// When an event occurs, it will be passed to the event callback function
977    /// for processing.
978    pub fn monitor_enable(
979        &self,
980        option: <Driver::Architecture as Architecture>::EventMonitor,
981    ) -> Result<(), VmiError> {
982        self.driver.monitor_enable(option)
983    }
984
985    /// Disables monitoring of specific events.
986    ///
987    /// This method allows you to disable monitoring of specific events that
988    /// were previously enabled. It can be used to stop tracking certain
989    /// hardware events or to reduce the overhead of event processing.
990    ///
991    /// The type of event to disable is defined by the architecture-specific
992    /// [`Architecture::EventMonitor`] type.
993    pub fn monitor_disable(
994        &self,
995        option: <Driver::Architecture as Architecture>::EventMonitor,
996    ) -> Result<(), VmiError> {
997        self.driver.monitor_disable(option)
998    }
999
1000    /// Returns the number of pending events.
1001    ///
1002    /// This method provides a count of events that have occurred but have not
1003    /// yet been processed.
1004    pub fn events_pending(&self) -> usize {
1005        self.driver.events_pending()
1006    }
1007
1008    /// Returns the time spent processing events by the driver.
1009    ///
1010    /// This method provides a measure of the overhead introduced by event
1011    /// processing. It can be useful for performance tuning and
1012    /// understanding the impact of VMI operations on overall system
1013    /// performance.
1014    pub fn event_processing_overhead(&self) -> Duration {
1015        self.driver.event_processing_overhead()
1016    }
1017
1018    /// Waits for an event to occur and processes it with the provided handler.
1019    ///
1020    /// This method blocks until an event occurs or the specified timeout is
1021    /// reached. When an event occurs, it is passed to the provided callback
1022    /// function for processing.
1023    pub fn wait_for_event(
1024        &self,
1025        timeout: Duration,
1026        handler: impl FnMut(&VmiEvent<Driver::Architecture>) -> VmiEventResponse<Driver::Architecture>,
1027    ) -> Result<(), VmiError> {
1028        self.driver.wait_for_event(timeout, handler)
1029    }
1030}
1031
1032///////////////////////////////////////////////////////////////////////////////
1033// VmiVmControl
1034///////////////////////////////////////////////////////////////////////////////
1035
1036impl<Driver> VmiCore<Driver>
1037where
1038    Driver: VmiVmControl,
1039{
1040    /// Pauses the virtual machine.
1041    pub fn pause(&self) -> Result<(), VmiError> {
1042        self.driver.pause()
1043    }
1044
1045    /// Resumes the virtual machine.
1046    pub fn resume(&self) -> Result<(), VmiError> {
1047        self.driver.resume()
1048    }
1049
1050    /// Pauses the virtual machine and returns a guard that will resume it when
1051    /// dropped.
1052    pub fn pause_guard(&self) -> Result<VmiPauseGuard<'_, Driver>, VmiError> {
1053        VmiPauseGuard::new(&self.driver)
1054    }
1055
1056    /// Allocates a guest frame number (GFN).
1057    ///
1058    /// This method allocates a new GFN, with the driver responsible for
1059    /// choosing the specific frame to allocate. It's useful when you need
1060    /// to allocate new memory pages for the VM without caring about the
1061    /// specific location.
1062    pub fn allocate_gfn(&self) -> Result<Gfn, VmiError> {
1063        self.driver.allocate_gfn()
1064    }
1065
1066    /// Allocates a guest frame number (GFN) at a specific location.
1067    ///
1068    /// This method allows you to allocate a particular GFN. It's useful
1069    /// when you need to allocate a specific memory page for the VM.
1070    pub fn allocate_gfn_at(&self, gfn: Gfn) -> Result<(), VmiError> {
1071        self.driver.allocate_gfn_at(gfn)
1072    }
1073
1074    /// Frees a previously allocated guest frame number (GFN).
1075    ///
1076    /// This method deallocates a GFN that was previously allocated. It's
1077    /// important to free GFNs when they're no longer needed to prevent
1078    /// memory leaks in the VM.
1079    pub fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError> {
1080        self.driver.free_gfn(gfn)
1081    }
1082
1083    /// Injects an interrupt into a specific virtual CPU.
1084    ///
1085    /// This method allows for the injection of architecture-specific interrupts
1086    /// into a given vCPU. It can be used to simulate hardware events or to
1087    /// manipulate the guest's execution flow for analysis purposes.
1088    ///
1089    /// The type of interrupt and its parameters are defined by the
1090    /// architecture-specific [`Architecture::Interrupt`] type.
1091    pub fn inject_interrupt(
1092        &self,
1093        vcpu: VcpuId,
1094        interrupt: <Driver::Architecture as Architecture>::Interrupt,
1095    ) -> Result<(), VmiError> {
1096        self.driver.inject_interrupt(vcpu, interrupt)
1097    }
1098
1099    /// Resets the state of the VMI system.
1100    ///
1101    /// This method clears all event monitors, caches, and any other stateful
1102    /// data maintained by the VMI system. It's useful for bringing the VMI
1103    /// system back to a known clean state, which can be necessary when
1104    /// switching between different analysis tasks or recovering from error
1105    /// conditions.
1106    pub fn reset_state(&self) -> Result<(), VmiError> {
1107        self.driver.reset_state()
1108    }
1109}
1110
1111/// A guard that pauses the virtual machine on creation and resumes it on drop.
1112pub struct VmiPauseGuard<'a, Driver>
1113where
1114    Driver: VmiVmControl,
1115{
1116    driver: &'a Driver,
1117}
1118
1119impl<'a, Driver> VmiPauseGuard<'a, Driver>
1120where
1121    Driver: VmiVmControl,
1122{
1123    /// Creates a new pause guard.
1124    pub fn new(driver: &'a Driver) -> Result<Self, VmiError> {
1125        driver.pause()?;
1126        Ok(Self { driver })
1127    }
1128}
1129
1130impl<Driver> Drop for VmiPauseGuard<'_, Driver>
1131where
1132    Driver: VmiVmControl,
1133{
1134    fn drop(&mut self) {
1135        if let Err(err) = self.driver.resume() {
1136            tracing::error!(%err, "failed to resume the virtual machine");
1137        }
1138    }
1139}