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 position = buffer
532            .chunks_exact(2)
533            .position(|chunk| chunk[0] == 0 && chunk[1] == 0);
534
535        if let Some(position) = position {
536            buffer.truncate(limit.min(position * 2));
537            return Ok(buffer
538                .chunks_exact(2)
539                .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
540                .collect());
541        }
542
543        let mut page = [0u8; 4096_usize]; // FIXME: Driver::Architecture::PAGE_SIZE
544        loop {
545            ctx.address += buffer.len() as u64;
546            self.read(ctx, &mut page)?;
547
548            let position = page
549                .chunks_exact(2)
550                .position(|chunk| chunk[0] == 0 && chunk[1] == 0);
551
552            if let Some(position) = position {
553                buffer.extend_from_slice(&page[..position * 2]);
554
555                if buffer.len() >= limit {
556                    buffer.truncate(limit);
557                }
558
559                break;
560            }
561
562            buffer.extend_from_slice(&page);
563
564            if buffer.len() >= limit {
565                buffer.truncate(limit);
566                break;
567            }
568        }
569
570        Ok(buffer
571            .chunks_exact(2)
572            .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
573            .collect())
574    }
575
576    /// Reads a null-terminated wide string (UTF-16) from the virtual machine.
577    pub fn read_string_utf16_bytes(
578        &self,
579        ctx: impl Into<AccessContext>,
580    ) -> Result<Vec<u16>, VmiError> {
581        self.read_string_utf16_bytes_limited(
582            ctx,
583            self.read_string_length_limit.borrow().unwrap_or(usize::MAX),
584        )
585    }
586
587    /// Reads a null-terminated string from the virtual machine with a specified
588    /// limit.
589    pub fn read_string_limited(
590        &self,
591        ctx: impl Into<AccessContext>,
592        limit: usize,
593    ) -> Result<String, VmiError> {
594        Ok(String::from_utf8_lossy(&self.read_string_bytes_limited(ctx, limit)?).into())
595    }
596
597    /// Reads a null-terminated string from the virtual machine.
598    pub fn read_string(&self, ctx: impl Into<AccessContext>) -> Result<String, VmiError> {
599        self.read_string_limited(
600            ctx,
601            self.read_string_length_limit.borrow().unwrap_or(usize::MAX),
602        )
603    }
604
605    /// Reads a null-terminated wide string (UTF-16) from the virtual machine
606    /// with a specified limit.
607    pub fn read_string_utf16_limited(
608        &self,
609        ctx: impl Into<AccessContext>,
610        limit: usize,
611    ) -> Result<String, VmiError> {
612        Ok(String::from_utf16_lossy(
613            &self.read_string_utf16_bytes_limited(ctx, limit)?,
614        ))
615    }
616
617    /// Reads a null-terminated wide string (UTF-16) from the virtual machine.
618    pub fn read_string_utf16(&self, ctx: impl Into<AccessContext>) -> Result<String, VmiError> {
619        self.read_string_utf16_limited(
620            ctx,
621            self.read_string_length_limit.borrow().unwrap_or(usize::MAX),
622        )
623    }
624
625    /// Reads a struct from the virtual machine.
626    pub fn read_struct<T>(&self, ctx: impl Into<AccessContext>) -> Result<T, VmiError>
627    where
628        T: FromBytes + IntoBytes,
629    {
630        let mut result = T::new_zeroed();
631        self.read(ctx, result.as_mut_bytes())?;
632        Ok(result)
633    }
634
635    /// Translates a virtual address to a physical address.
636    pub fn translate_address(&self, ctx: impl Into<AddressContext>) -> Result<Pa, VmiError> {
637        self.translate_access_context(AccessContext::from(ctx.into()))
638    }
639
640    /// Translates an access context to a physical address.
641    pub fn translate_access_context(&self, ctx: AccessContext) -> Result<Pa, VmiError> {
642        (self.translate_access_context_fn)(self, ctx)
643    }
644
645    /// Reads a page of memory from the virtual machine.
646    pub fn read_page(&self, gfn: Gfn) -> Result<VmiMappedPage, VmiError> {
647        (self.read_page_fn)(self, gfn)
648    }
649
650    /// Reads a page of memory from the virtual machine without using the cache.
651    fn read_page_nocache(&self, gfn: Gfn) -> Result<VmiMappedPage, VmiError> {
652        self.driver.read_page(gfn)
653    }
654
655    /// Reads a page of memory from the virtual machine, using the cache if
656    /// enabled.
657    fn read_page_cache(&self, gfn: Gfn) -> Result<VmiMappedPage, VmiError> {
658        let mut cache = self.cache.gfn.borrow_mut();
659        let value = cache.try_get_or_insert(gfn, || self.read_page_nocache(gfn))?;
660
661        // Mapped pages are reference counted, so cloning it is cheap.
662        Ok(value.clone())
663    }
664
665    /// Translates an access context to a physical address without using the
666    /// cache.
667    ///
668    /// # Notes
669    ///
670    /// If [`TranslationMechanism::Paging`] is used, the `root` must be present.
671    /// In case the root is not present, a [`VmiError::RootNotPresent`] error is
672    /// returned.
673    fn translate_access_context_nocache(&self, ctx: AccessContext) -> Result<Pa, VmiError> {
674        Ok(match ctx.mechanism {
675            TranslationMechanism::Direct => Pa(ctx.address),
676            TranslationMechanism::Paging { root } => match root {
677                Some(root) => <Driver::Architecture as Architecture>::translate_address(
678                    self,
679                    ctx.address.into(),
680                    root,
681                )?,
682                None => return Err(VmiError::RootNotPresent),
683            },
684        })
685    }
686
687    /// Translates an access context to a physical address, using the cache if
688    /// enabled.
689    fn translate_access_context_cache(&self, ctx: AccessContext) -> Result<Pa, VmiError> {
690        let mut cache = self.cache.v2p.borrow_mut();
691        let value = cache.try_get_or_insert(ctx, || self.translate_access_context_nocache(ctx))?;
692        Ok(*value)
693    }
694}
695
696///////////////////////////////////////////////////////////////////////////////
697// VmiRead + VmiWrite
698///////////////////////////////////////////////////////////////////////////////
699
700impl<Driver> VmiCore<Driver>
701where
702    Driver: VmiRead + VmiWrite,
703{
704    /// Writes memory to the virtual machine.
705    pub fn write(&self, ctx: impl Into<AccessContext>, buffer: &[u8]) -> Result<(), VmiError> {
706        let ctx = ctx.into();
707        let mut position = 0usize;
708        let mut remaining = buffer.len();
709
710        while remaining > 0 {
711            let address = self.translate_access_context(ctx + position as u64)?;
712            let gfn = Driver::Architecture::gfn_from_pa(address);
713            let offset = Driver::Architecture::pa_offset(address);
714
715            let size = std::cmp::min(
716                remaining,
717                (Driver::Architecture::PAGE_SIZE - offset) as usize,
718            );
719            let content = &buffer[position..position + size];
720
721            self.driver.write_page(gfn, offset, content)?;
722
723            position += size;
724            remaining -= size;
725        }
726
727        Ok(())
728    }
729
730    /// Writes a single byte to the virtual machine.
731    pub fn write_u8(&self, ctx: impl Into<AccessContext>, value: u8) -> Result<(), VmiError> {
732        self.write(ctx, &value.to_le_bytes())
733    }
734
735    /// Writes a 16-bit unsigned integer to the virtual machine.
736    pub fn write_u16(&self, ctx: impl Into<AccessContext>, value: u16) -> Result<(), VmiError> {
737        self.write(ctx, &value.to_le_bytes())
738    }
739
740    /// Writes a 32-bit unsigned integer to the virtual machine.
741    pub fn write_u32(&self, ctx: impl Into<AccessContext>, value: u32) -> Result<(), VmiError> {
742        self.write(ctx, &value.to_le_bytes())
743    }
744
745    /// Writes a 64-bit unsigned integer to the virtual machine.
746    pub fn write_u64(&self, ctx: impl Into<AccessContext>, value: u64) -> Result<(), VmiError> {
747        self.write(ctx, &value.to_le_bytes())
748    }
749
750    /// Writes a struct to the virtual machine.
751    pub fn write_struct<T>(&self, ctx: impl Into<AccessContext>, value: T) -> Result<(), VmiError>
752    where
753        T: IntoBytes + Immutable,
754    {
755        self.write(ctx, value.as_bytes())
756    }
757}
758
759///////////////////////////////////////////////////////////////////////////////
760// VmiQueryProtection
761///////////////////////////////////////////////////////////////////////////////
762
763impl<Driver> VmiCore<Driver>
764where
765    Driver: VmiQueryProtection,
766{
767    /// Retrieves the memory access permissions for a specific guest frame
768    /// number (GFN).
769    ///
770    /// The returned `MemoryAccess` indicates the current read, write, and
771    /// execute permissions for the specified memory page in the given view.
772    pub fn memory_access(&self, gfn: Gfn, view: View) -> Result<MemoryAccess, VmiError> {
773        self.driver.memory_access(gfn, view)
774    }
775}
776
777///////////////////////////////////////////////////////////////////////////////
778// VmiSetProtection
779///////////////////////////////////////////////////////////////////////////////
780
781impl<Driver> VmiCore<Driver>
782where
783    Driver: VmiSetProtection,
784{
785    /// Sets the memory access permissions for a specific guest frame number
786    /// (GFN).
787    ///
788    /// This method allows you to modify the read, write, and execute
789    /// permissions for a given memory page in the specified view.
790    pub fn set_memory_access(
791        &self,
792        gfn: Gfn,
793        view: View,
794        access: MemoryAccess,
795    ) -> Result<(), VmiError> {
796        self.driver.set_memory_access(gfn, view, access)
797    }
798
799    /// Sets the memory access permissions for a specific guest frame number
800    /// (GFN) with additional options.
801    ///
802    /// In addition to the basic read, write, and execute permissions, this
803    /// method allows you to specify additional options for the memory access.
804    pub fn set_memory_access_with_options(
805        &self,
806        gfn: Gfn,
807        view: View,
808        access: MemoryAccess,
809        options: MemoryAccessOptions,
810    ) -> Result<(), VmiError> {
811        self.driver
812            .set_memory_access_with_options(gfn, view, access, options)
813    }
814}
815
816///////////////////////////////////////////////////////////////////////////////
817// VmiQueryRegisters
818///////////////////////////////////////////////////////////////////////////////
819
820impl<Driver> VmiCore<Driver>
821where
822    Driver: VmiQueryRegisters,
823{
824    /// Retrieves the current state of CPU registers for a specified virtual
825    /// CPU.
826    ///
827    /// This method allows you to access the current values of CPU registers,
828    /// which is crucial for understanding the state of the virtual machine
829    /// at a given point in time.
830    ///
831    /// # Notes
832    ///
833    /// The exact structure and content of the returned registers depend on the
834    /// specific architecture of the VM being introspected. Refer to the
835    /// documentation of your [`Architecture`] implementation for details on
836    /// how to interpret the register values.
837    pub fn registers(
838        &self,
839        vcpu: VcpuId,
840    ) -> Result<<Driver::Architecture as Architecture>::Registers, VmiError> {
841        self.driver.registers(vcpu)
842    }
843}
844
845///////////////////////////////////////////////////////////////////////////////
846// VmiSetRegisters
847///////////////////////////////////////////////////////////////////////////////
848
849impl<Driver> VmiCore<Driver>
850where
851    Driver: VmiSetRegisters,
852{
853    /// Sets the registers of a virtual CPU.
854    pub fn set_registers(
855        &self,
856        vcpu: VcpuId,
857        registers: <Driver::Architecture as Architecture>::Registers,
858    ) -> Result<(), VmiError> {
859        self.driver.set_registers(vcpu, registers)
860    }
861}
862
863///////////////////////////////////////////////////////////////////////////////
864// VmiViewControl
865///////////////////////////////////////////////////////////////////////////////
866
867impl<Driver> VmiCore<Driver>
868where
869    Driver: VmiViewControl,
870{
871    /// Returns the default view for the virtual machine.
872    ///
873    /// The default view typically represents the normal, unmodified state of
874    /// the VM's memory.
875    pub fn default_view(&self) -> View {
876        self.driver.default_view()
877    }
878
879    /// Creates a new view with the specified default access permissions.
880    ///
881    /// Views allow for creating different perspectives of the VM's memory,
882    /// which can be useful for analysis or isolation purposes. The default
883    /// access permissions apply to memory pages not explicitly modified
884    /// within this view.
885    pub fn create_view(&self, default_access: MemoryAccess) -> Result<View, VmiError> {
886        self.driver.create_view(default_access)
887    }
888
889    /// Destroys a previously created view.
890    ///
891    /// This method removes a view and frees associated resources. It should be
892    /// called when a view is no longer needed to prevent resource leaks.
893    pub fn destroy_view(&self, view: View) -> Result<(), VmiError> {
894        self.driver.destroy_view(view)
895    }
896
897    /// Switches to a different view for all virtual CPUs.
898    ///
899    /// This method changes the current active view for all vCPUs, affecting
900    /// subsequent memory operations across the entire VM. It allows for
901    /// quick transitions between different memory perspectives globally.
902    ///
903    /// Note the difference between this method and
904    /// [`VmiEventResponse::with_view()`]:
905    /// - `switch_to_view()` changes the view for all vCPUs immediately.
906    /// - `VmiEventResponse::with_view()` sets the view only for the specific
907    ///   vCPU that received the event, and the change is applied when the event
908    ///   handler returns.
909    ///
910    /// Use `switch_to_view()` for global view changes, and
911    /// `VmiEventResponse::with_view()` for targeted, event-specific view
912    /// modifications on individual vCPUs.
913    pub fn switch_to_view(&self, view: View) -> Result<(), VmiError> {
914        self.driver.switch_to_view(view)
915    }
916
917    /// Changes the mapping of a guest frame number (GFN) in a specific view.
918    ///
919    /// This method allows for remapping a GFN to a different physical frame
920    /// within a view, enabling fine-grained control over memory layout in
921    /// different views.
922    ///
923    /// A notable use case for this method is implementing "stealth hooks":
924    /// 1. Create a new GFN and copy the contents of the original page to it.
925    /// 2. Modify the new page by installing a breakpoint (e.g., 0xcc on AMD64)
926    ///    at a strategic location.
927    /// 3. Use this method to change the mapping of the original GFN to the new
928    ///    one.
929    /// 4. Set the memory access of the new GFN to non-readable.
930    ///
931    /// When a read access occurs:
932    /// - The handler should enable single-stepping.
933    /// - Switch to an unmodified view (e.g., `default_view`) to execute the
934    ///   read instruction, which will read the original non-breakpoint byte.
935    /// - Re-enable single-stepping afterwards.
936    ///
937    /// This technique allows for transparent breakpoints that are difficult to
938    /// detect by the guest OS or applications.
939    pub fn change_view_gfn(&self, view: View, old_gfn: Gfn, new_gfn: Gfn) -> Result<(), VmiError> {
940        self.driver.change_view_gfn(view, old_gfn, new_gfn)
941    }
942
943    /// Resets the mapping of a guest frame number (GFN) in a specific view to
944    /// its original state.
945    ///
946    /// This method reverts any custom mapping for the specified GFN in the
947    /// given view, restoring it to the default mapping.
948    pub fn reset_view_gfn(&self, view: View, gfn: Gfn) -> Result<(), VmiError> {
949        self.driver.reset_view_gfn(view, gfn)
950    }
951}
952
953///////////////////////////////////////////////////////////////////////////////
954// VmiEventControl
955///////////////////////////////////////////////////////////////////////////////
956
957impl<Driver> VmiCore<Driver>
958where
959    Driver: VmiEventControl,
960{
961    /// Enables monitoring of specific events.
962    ///
963    /// This method allows you to enable monitoring of specific events, such as
964    /// control register writes, interrupts, or single-step execution.
965    /// Monitoring events can be useful for tracking specific guest behavior or
966    /// for implementing custom analysis tools.
967    ///
968    /// The type of event to monitor is defined by the architecture-specific
969    /// [`Architecture::EventMonitor`] type.
970    ///
971    /// When an event occurs, it will be passed to the event callback function
972    /// for processing.
973    pub fn monitor_enable(
974        &self,
975        option: <Driver::Architecture as Architecture>::EventMonitor,
976    ) -> Result<(), VmiError> {
977        self.driver.monitor_enable(option)
978    }
979
980    /// Disables monitoring of specific events.
981    ///
982    /// This method allows you to disable monitoring of specific events that
983    /// were previously enabled. It can be used to stop tracking certain
984    /// hardware events or to reduce the overhead of event processing.
985    ///
986    /// The type of event to disable is defined by the architecture-specific
987    /// [`Architecture::EventMonitor`] type.
988    pub fn monitor_disable(
989        &self,
990        option: <Driver::Architecture as Architecture>::EventMonitor,
991    ) -> Result<(), VmiError> {
992        self.driver.monitor_disable(option)
993    }
994
995    /// Returns the number of pending events.
996    ///
997    /// This method provides a count of events that have occurred but have not
998    /// yet been processed.
999    pub fn events_pending(&self) -> usize {
1000        self.driver.events_pending()
1001    }
1002
1003    /// Returns the time spent processing events by the driver.
1004    ///
1005    /// This method provides a measure of the overhead introduced by event
1006    /// processing. It can be useful for performance tuning and
1007    /// understanding the impact of VMI operations on overall system
1008    /// performance.
1009    pub fn event_processing_overhead(&self) -> Duration {
1010        self.driver.event_processing_overhead()
1011    }
1012
1013    /// Waits for an event to occur and processes it with the provided handler.
1014    ///
1015    /// This method blocks until an event occurs or the specified timeout is
1016    /// reached. When an event occurs, it is passed to the provided callback
1017    /// function for processing.
1018    pub fn wait_for_event(
1019        &self,
1020        timeout: Duration,
1021        handler: impl FnMut(&VmiEvent<Driver::Architecture>) -> VmiEventResponse<Driver::Architecture>,
1022    ) -> Result<(), VmiError> {
1023        self.driver.wait_for_event(timeout, handler)
1024    }
1025}
1026
1027///////////////////////////////////////////////////////////////////////////////
1028// VmiVmControl
1029///////////////////////////////////////////////////////////////////////////////
1030
1031impl<Driver> VmiCore<Driver>
1032where
1033    Driver: VmiVmControl,
1034{
1035    /// Pauses the virtual machine.
1036    pub fn pause(&self) -> Result<(), VmiError> {
1037        self.driver.pause()
1038    }
1039
1040    /// Resumes the virtual machine.
1041    pub fn resume(&self) -> Result<(), VmiError> {
1042        self.driver.resume()
1043    }
1044
1045    /// Pauses the virtual machine and returns a guard that will resume it when
1046    /// dropped.
1047    pub fn pause_guard(&self) -> Result<VmiPauseGuard<'_, Driver>, VmiError> {
1048        VmiPauseGuard::new(&self.driver)
1049    }
1050
1051    /// Allocates a guest frame number (GFN).
1052    ///
1053    /// This method allocates a new GFN, with the driver responsible for
1054    /// choosing the specific frame to allocate. It's useful when you need
1055    /// to allocate new memory pages for the VM without caring about the
1056    /// specific location.
1057    pub fn allocate_gfn(&self) -> Result<Gfn, VmiError> {
1058        self.driver.allocate_gfn()
1059    }
1060
1061    /// Allocates a guest frame number (GFN) at a specific location.
1062    ///
1063    /// This method allows you to allocate a particular GFN. It's useful
1064    /// when you need to allocate a specific memory page for the VM.
1065    pub fn allocate_gfn_at(&self, gfn: Gfn) -> Result<(), VmiError> {
1066        self.driver.allocate_gfn_at(gfn)
1067    }
1068
1069    /// Frees a previously allocated guest frame number (GFN).
1070    ///
1071    /// This method deallocates a GFN that was previously allocated. It's
1072    /// important to free GFNs when they're no longer needed to prevent
1073    /// memory leaks in the VM.
1074    pub fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError> {
1075        self.driver.free_gfn(gfn)
1076    }
1077
1078    /// Injects an interrupt into a specific virtual CPU.
1079    ///
1080    /// This method allows for the injection of architecture-specific interrupts
1081    /// into a given vCPU. It can be used to simulate hardware events or to
1082    /// manipulate the guest's execution flow for analysis purposes.
1083    ///
1084    /// The type of interrupt and its parameters are defined by the
1085    /// architecture-specific [`Architecture::Interrupt`] type.
1086    pub fn inject_interrupt(
1087        &self,
1088        vcpu: VcpuId,
1089        interrupt: <Driver::Architecture as Architecture>::Interrupt,
1090    ) -> Result<(), VmiError> {
1091        self.driver.inject_interrupt(vcpu, interrupt)
1092    }
1093
1094    /// Resets the state of the VMI system.
1095    ///
1096    /// This method clears all event monitors, caches, and any other stateful
1097    /// data maintained by the VMI system. It's useful for bringing the VMI
1098    /// system back to a known clean state, which can be necessary when
1099    /// switching between different analysis tasks or recovering from error
1100    /// conditions.
1101    pub fn reset_state(&self) -> Result<(), VmiError> {
1102        self.driver.reset_state()
1103    }
1104}
1105
1106/// A guard that pauses the virtual machine on creation and resumes it on drop.
1107pub struct VmiPauseGuard<'a, Driver>
1108where
1109    Driver: VmiVmControl,
1110{
1111    driver: &'a Driver,
1112}
1113
1114impl<'a, Driver> VmiPauseGuard<'a, Driver>
1115where
1116    Driver: VmiVmControl,
1117{
1118    /// Creates a new pause guard.
1119    pub fn new(driver: &'a Driver) -> Result<Self, VmiError> {
1120        driver.pause()?;
1121        Ok(Self { driver })
1122    }
1123}
1124
1125impl<Driver> Drop for VmiPauseGuard<'_, Driver>
1126where
1127    Driver: VmiVmControl,
1128{
1129    fn drop(&mut self) {
1130        if let Err(err) = self.driver.resume() {
1131            tracing::error!(%err, "failed to resume the virtual machine");
1132        }
1133    }
1134}