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}