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}