vmi_utils/bpm/mod.rs
1//! Breakpoint management.
2//!
3//! Provides breakpoint management capabilities, including handling of page-in
4//! and page-out events, and support for both active and pending breakpoints.
5//! The [`BreakpointManager`] is designed to work in conjunction with the
6//! [`PageTableMonitor`].
7//!
8//! When a page-out event occurs, active breakpoints on that page are
9//! automatically removed and preserved as pending entries. When a page-in
10//! event occurs, pending breakpoints are restored.
11//!
12//! The breakpoint manager also supports setting breakpoints for virtual
13//! addresses that aren't currently mapped to physical memory - these will be
14//! automatically activated once the address translation becomes available.
15//!
16//! # Controllers
17//!
18//! The breakpoint manager works with controllers that implement the
19//! [`TapController`] trait. Two primary implementations are provided:
20//!
21//! ## [`BreakpointController`]
22//!
23#![doc = include_str!("./controller/breakpoint.md")]
24//!
25//! ## [`MemoryController`]
26//!
27#![doc = include_str!("./controller/memory.md")]
28//!
29//! [`PageTableMonitor`]: crate::ptm::PageTableMonitor
30
31mod breakpoint;
32use self::breakpoint::{ActiveBreakpoints, PendingBreakpoints};
33pub use self::breakpoint::{
34 Breakpoint, BreakpointBuilder, BreakpointBuilderWithKey, BreakpointBuilderWithKeyTag,
35 BreakpointBuilderWithTag, KeyType, TagType,
36};
37
38mod controller;
39use std::collections::{HashMap, HashSet, hash_map::Entry};
40
41use vmi_core::{
42 AddressContext, Architecture as _, Gfn, Pa, Registers as _, Va, View, VmiCore, VmiDriver,
43 VmiError, VmiEvent,
44};
45
46pub use self::controller::{BreakpointController, MemoryController, TapController};
47use crate::ptm::{PageEntryUpdate, PageTableMonitorEvent};
48
49/// Breakpoint manager.
50pub struct BreakpointManager<Controller, Key = (), Tag = &'static str>
51where
52 Controller: TapController,
53 Controller::Driver: VmiDriver,
54 Key: KeyType,
55 Tag: TagType,
56{
57 /// Stores active breakpoints for addresses currently in physical memory.
58 ///
59 /// * Key: (View, GFN)
60 /// * Value: Map of breakpoints, where each breakpoint is identified by
61 /// (Key, AddressContext) and associated with a set of Breakpoints
62 ///
63 /// This map is synchronized with `active_global_breakpoints`, `active_locations`,
64 /// and `active_gfns_by_view`.
65 ///
66 /// Breakpoints move between `active_breakpoints` and `pending_breakpoints`
67 /// based on page-in and page-out events.
68 active_breakpoints: HashMap<(View, Gfn), ActiveBreakpoints<Key, Tag>>,
69
70 /// Stores global breakpoints indexed by their virtual addresses.
71 ///
72 /// * Key: (View, Virtual Address)
73 /// * Value: Global Breakpoint information, including the root address (e.g.,
74 /// `CR3` on x86 architectures) for which the global breakpoint was
75 /// initially registered.
76 ///
77 /// Note: Each VA is associated with a single root address. If support for
78 /// multiple roots per VA is needed, this structure may need to be adjusted.
79 active_global_breakpoints: HashMap<(View, Va), GlobalBreakpoint>,
80
81 /// Maps breakpoint identifiers to their locations across views and GFNs.
82 ///
83 /// * Key: (Key, AddressContext)
84 /// * Value: Set of (View, GFN) pairs where this breakpoint is active
85 ///
86 /// This map is kept in sync with `active_breakpoints` to allow efficient
87 /// lookup of breakpoint locations.
88 active_locations: HashMap<(Key, AddressContext), HashSet<(View, Gfn)>>,
89
90 /// Tracks which GFNs are monitored in each view.
91 ///
92 /// * Key: View
93 /// * Value: Set of GFNs monitored in this view
94 ///
95 /// This map is kept in sync with `active_breakpoints` and `monitored_gfns_by_view`.
96 active_gfns_by_view: HashMap<View, HashSet<Gfn>>,
97
98 /// Stores pending breakpoints for addresses not currently in physical memory.
99 ///
100 /// * Key: AddressContext (Virtual Address, Root)
101 /// * Value: Set of pending breakpoints for that address
102 ///
103 /// Breakpoints move between `active_breakpoints` and `pending_breakpoints`
104 /// based on page-in and page-out events.
105 pending_breakpoints: HashMap<(View, AddressContext), PendingBreakpoints<Key, Tag>>,
106
107 /// Maps pending breakpoints to their views.
108 /// This map is used to quickly remove pending breakpoints when a view is
109 /// removed.
110 ///
111 /// * Key: View
112 /// * Value: Set of pending breakpoints for that view
113 ///
114 /// This map is kept in sync with `pending_breakpoints`.
115 pending_ctx_by_view: HashMap<View, HashSet<AddressContext>>,
116
117 /// Controller used to insert and remove breakpoints.
118 controller: Controller,
119}
120
121#[derive(Debug)]
122struct GlobalBreakpoint {
123 root: Pa,
124 gfns: HashSet<Gfn>,
125}
126
127/*
128impl<Controller, Key, Tag> Drop for Tap<Controller, Key, Tag>
129where
130 Controller: TapController,
131 Controller::Driver: VmiDriver,
132 Key: KeyType,
133 Tag: TagType,
134{
135 fn drop(&mut self) {
136 println!("dropping breakpoint manager");
137 println!("active_breakpoints: {:#?}", self.active_breakpoints);
138 println!(
139 "active_global_breakpoints: {:#?}",
140 self.active_global_breakpoints
141 );
142 println!("active_locations: {:#?}", self.active_locations);
143 println!("active_gfns_by_view: {:#?}", self.active_gfns_by_view);
144 println!("pending_breakpoints: {:#?}", self.pending_breakpoints);
145 println!("pending_ctx_by_view: {:#?}", self.pending_ctx_by_view);
146 }
147}
148*/
149
150impl<Interface, Key, Tag> BreakpointManager<Interface, Key, Tag>
151where
152 Interface: TapController,
153 Interface::Driver: VmiDriver,
154 Key: KeyType,
155 Tag: TagType,
156{
157 #[expect(clippy::new_without_default)]
158 /// Creates a new breakpoint manager.
159 pub fn new() -> Self {
160 Self {
161 active_breakpoints: HashMap::new(),
162 active_global_breakpoints: HashMap::new(),
163 active_locations: HashMap::new(),
164 active_gfns_by_view: HashMap::new(),
165 pending_breakpoints: HashMap::new(),
166 pending_ctx_by_view: HashMap::new(),
167 controller: Interface::new(),
168 }
169 }
170
171 /// Inserts a breakpoint.
172 ///
173 /// The breakpoint is registered as pending when the translation for the
174 /// virtual address is not present. When the translation is present, the
175 /// breakpoint is immediately inserted.
176 ///
177 /// Consider using [`insert_with_hint`] when the physical address is known.
178 ///
179 /// Returns `true` if the breakpoint was newly inserted, `false` if it was
180 /// already present.
181 ///
182 /// [`insert_with_hint`]: Self::insert_with_hint
183 pub fn insert(
184 &mut self,
185 vmi: &VmiCore<Interface::Driver>,
186 breakpoint: impl Into<Breakpoint<Key, Tag>>,
187 ) -> Result<bool, VmiError> {
188 let breakpoint = breakpoint.into();
189
190 //
191 // Check if the translation for the virtual address is present.
192 // If it is, insert the breakpoint.
193 // If it is not, register the breakpoint as pending.
194 //
195
196 match vmi.translate_address(breakpoint.ctx) {
197 Ok(pa) => self.insert_with_hint(vmi, breakpoint, Some(pa)),
198 Err(VmiError::Translation(_)) => self.insert_with_hint(vmi, breakpoint, None),
199 Err(err) => Err(err),
200 }
201 }
202
203 /// Inserts a breakpoint with a hint for the physical address.
204 /// If the physical address is `None`, the breakpoint is registered as
205 /// pending.
206 ///
207 /// This function is useful when the physical address is known in advance,
208 /// for example, when the breakpoint is inserted in response to a page
209 /// table update.
210 ///
211 /// The user is responsible for ensuring that the physical address is
212 /// correct.
213 ///
214 /// Returns `true` if the breakpoint was newly inserted, `false` if it was
215 /// already present.
216 pub fn insert_with_hint(
217 &mut self,
218 vmi: &VmiCore<Interface::Driver>,
219 breakpoint: impl Into<Breakpoint<Key, Tag>>,
220 pa: Option<Pa>,
221 ) -> Result<bool, VmiError> {
222 let breakpoint = breakpoint.into();
223
224 //
225 // Check if the physical address is provided.
226 // If it is, insert the breakpoint.
227 // If it is not, register the breakpoint as pending.
228 //
229
230 let pa = match pa {
231 Some(pa) => pa,
232 None => return Ok(self.insert_pending_breakpoint(breakpoint)),
233 };
234
235 self.insert_active_breakpoint(vmi, breakpoint, pa)
236 }
237
238 /// Removes a breakpoint.
239 ///
240 /// When a translation for the virtual address is not present, the breakpoint
241 /// is removed from the pending breakpoints. When the translation is present,
242 /// the breakpoint is removed from the active breakpoints.
243 ///
244 /// Returns `true` if the breakpoint was removed, `false` if it was not
245 /// found.
246 pub fn remove(
247 &mut self,
248 vmi: &VmiCore<Interface::Driver>,
249 breakpoint: impl Into<Breakpoint<Key, Tag>>,
250 ) -> Result<bool, VmiError> {
251 let breakpoint = breakpoint.into();
252
253 match vmi.translate_address(breakpoint.ctx) {
254 Ok(pa) => self.remove_with_hint(vmi, breakpoint, Some(pa)),
255 Err(VmiError::Translation(_)) => self.remove_with_hint(vmi, breakpoint, None),
256 Err(err) => Err(err),
257 }
258 }
259
260 /// Removes a breakpoint with a hint for the physical address.
261 ///
262 /// If the physical address is `None`, the breakpoint is removed from
263 /// the pending breakpoints. If the physical address is provided, the
264 /// breakpoint is removed from the active breakpoints.
265 ///
266 /// Returns `true` if the breakpoint was removed, `false` if it was not
267 /// found.
268 pub fn remove_with_hint(
269 &mut self,
270 vmi: &VmiCore<Interface::Driver>,
271 breakpoint: impl Into<Breakpoint<Key, Tag>>,
272 pa: Option<Pa>,
273 ) -> Result<bool, VmiError> {
274 let breakpoint = breakpoint.into();
275 let Breakpoint { ctx, view, key, .. } = breakpoint;
276
277 if self
278 .remove_pending_breakpoints_by_address(ctx, view)
279 .is_some()
280 {
281 //
282 // TODO: assert that there are no active breakpoints for this (view, ctx)
283 //
284
285 return Ok(true);
286 }
287
288 let pa = match pa {
289 Some(pa) => pa,
290 None => return Ok(false),
291 };
292
293 let breakpoint_was_removed = self.remove_active_breakpoint(vmi, ctx, pa, key, view)?;
294 Ok(breakpoint_was_removed.is_some())
295 }
296
297 /// Removes a breakpoint by event that caused the breakpoint.
298 ///
299 /// Returns either:
300 /// - `Some(true)` if the breakpoint was removed and it was the last
301 /// breakpoint for the `(view, GFN)` pair.
302 /// - `Some(false)` if the breakpoint was removed but there are still
303 /// other breakpoints for the `(view, GFN)` pair.
304 /// - `None` if the breakpoint was not found.
305 pub fn remove_by_event(
306 &mut self,
307 vmi: &VmiCore<Interface::Driver>,
308 event: &VmiEvent<<Interface::Driver as VmiDriver>::Architecture>,
309 key: Key,
310 ) -> Result<Option<bool>, VmiError> {
311 let (ctx, pa, view) = match self.address_for_event(event) {
312 Some((ctx, pa, view)) => (ctx, pa, view),
313 None => return Ok(None),
314 };
315
316 let result = self.remove_active_breakpoint(vmi, ctx, pa, key, view)?;
317
318 //
319 // Remove active breakpoints for all views.
320 //
321
322 let views = match self.active_locations.get(&(key, ctx)) {
323 Some(views) => views.clone(),
324 None => return Ok(result),
325 };
326
327 for (view, gfn) in views {
328 let pa = self.pa_from_gfn_and_va(gfn, ctx.va);
329
330 //
331 // If breakpoints are being removed by event, there should be no
332 // pending breakpoints for this address (because the address caused
333 // this event, it should be in physical memory).
334 //
335 //self.unregister_pending_breakpoints(ctx, view);
336
337 self.remove_active_breakpoint(vmi, ctx, pa, key, view)?;
338 }
339
340 Ok(result)
341 }
342
343 /// Removes all breakpoints for a given view.
344 ///
345 /// Returns `true` if any breakpoints were removed, `false` otherwise.
346 pub fn remove_by_view(
347 &mut self,
348 vmi: &VmiCore<Interface::Driver>,
349 view: View,
350 ) -> Result<bool, VmiError> {
351 //
352 // First remove all pending breakpoints for this view (if any).
353 //
354
355 if let Some(pending_ctxs) = self.pending_ctx_by_view.remove(&view) {
356 for ctx in pending_ctxs {
357 self.remove_pending_breakpoints_by_address(ctx, view);
358 }
359 };
360
361 //
362 // Then remove all active breakpoints for this view.
363 //
364
365 let gfns = match self.active_gfns_by_view.remove(&view) {
366 Some(gfns) => gfns,
367 None => return Ok(false),
368 };
369
370 //
371 // Set of GFNs should never be empty.
372 //
373
374 debug_assert!(!gfns.is_empty(), "active_gfns_by_view is empty");
375
376 for gfn in gfns {
377 self.remove_active_breakpoints_by_location(vmi, gfn, view)?;
378 }
379
380 Ok(true)
381 }
382
383 /// Returns an iterator over the breakpoints for the given event.
384 pub fn get_by_event(
385 &mut self,
386 event: &VmiEvent<<Interface::Driver as VmiDriver>::Architecture>,
387 key: Key,
388 ) -> Option<impl Iterator<Item = Breakpoint<Key, Tag>> + '_> {
389 let (ctx, pa, view) = self.address_for_event(event)?;
390 let gfn = <Interface::Driver as VmiDriver>::Architecture::gfn_from_pa(pa);
391
392 let breakpoints_by_ctx = self.active_breakpoints.get(&(view, gfn))?;
393 let breakpoints = breakpoints_by_ctx.get(&(key, ctx))?;
394
395 Some(breakpoints.iter().copied())
396 }
397
398 /// Checks if the given event was caused by a breakpoint.
399 pub fn contains_by_event(
400 &self,
401 event: &VmiEvent<<Interface::Driver as VmiDriver>::Architecture>,
402 key: Key,
403 ) -> bool {
404 let (ctx, pa, view) = match self.address_for_event(event) {
405 Some((ctx, pa, view)) => (ctx, pa, view),
406 None => return false,
407 };
408
409 let gfn = <Interface::Driver as VmiDriver>::Architecture::gfn_from_pa(pa);
410 let breakpoints = match self.active_breakpoints.get(&(view, gfn)) {
411 Some(breakpoints) => breakpoints,
412 None => return false,
413 };
414
415 breakpoints.contains_key(&(key, ctx))
416 }
417
418 /// Checks if a breakpoint is active for the given address.
419 pub fn contains_by_address(&self, ctx: impl Into<AddressContext>, key: Key) -> bool {
420 let ctx = ctx.into();
421
422 self.active_locations.contains_key(&(key, ctx))
423 }
424
425 /// Clears all breakpoints.
426 ///
427 /// This function removes all active and pending breakpoints.
428 pub fn clear(&mut self, vmi: &VmiCore<Interface::Driver>) -> Result<(), VmiError> {
429 let mut to_remove = Vec::new();
430
431 for (&(view, gfn), breakpoints) in &self.active_breakpoints {
432 for &(key, ctx) in breakpoints.keys() {
433 let pa = self.pa_from_gfn_and_va(gfn, ctx.va);
434 to_remove.push((key, view, pa, ctx));
435 }
436 }
437
438 self.pending_breakpoints.clear();
439
440 for (key, view, pa, ctx) in to_remove {
441 if let Err(err) = self.remove_active_breakpoint(vmi, ctx, pa, key, view) {
442 tracing::error!(
443 %err, %pa, %ctx, %view, ?key,
444 "failed to remove breakpoint"
445 );
446 }
447 }
448
449 debug_assert!(self.active_breakpoints.is_empty());
450 debug_assert!(self.active_global_breakpoints.is_empty());
451 debug_assert!(self.active_locations.is_empty());
452 debug_assert!(self.active_gfns_by_view.is_empty());
453 debug_assert!(self.pending_breakpoints.is_empty());
454 debug_assert!(self.pending_ctx_by_view.is_empty());
455
456 Ok(())
457 }
458
459 /// Handles a page table monitor event.
460 ///
461 /// This function should be called when a page table monitor event is
462 /// received. It will update the internal state of the breakpoint
463 /// manager accordingly.
464 ///
465 /// On page-in events, the function will check if there are any pending
466 /// breakpoints that can be made active.
467 ///
468 /// On page-out events, the function will check if there are any active
469 /// breakpoints that need to made pending.
470 ///
471 /// Returns `true` if any breakpoints were updated, `false` otherwise.
472 pub fn handle_ptm_event(
473 &mut self,
474 vmi: &VmiCore<Interface::Driver>,
475 event: &PageTableMonitorEvent,
476 ) -> Result<bool, VmiError> {
477 match event {
478 PageTableMonitorEvent::PageIn(update) => self.handle_page_in(vmi, update),
479 PageTableMonitorEvent::PageOut(update) => self.handle_page_out(vmi, update),
480 }
481 }
482
483 /// Handles a page-in event.
484 ///
485 /// This function should be called when a page-in event is received.
486 /// It will check if there are any pending breakpoints that can be made
487 /// active.
488 ///
489 /// Returns `true` if any breakpoints were updated, `false` otherwise.
490 fn handle_page_in(
491 &mut self,
492 vmi: &VmiCore<Interface::Driver>,
493 update: &PageEntryUpdate,
494 ) -> Result<bool, VmiError> {
495 let ctx = update.ctx;
496 let view = update.view;
497 let pa = update.pa;
498
499 let breakpoints = match self.remove_pending_breakpoints_by_address(ctx, view) {
500 Some(breakpoints) => breakpoints,
501 None => return Ok(false),
502 };
503
504 for breakpoint in breakpoints {
505 self.insert_active_breakpoint(vmi, breakpoint, pa)?;
506 }
507
508 Ok(true)
509 }
510
511 /// Handles a page-out event.
512 ///
513 /// This function should be called when a page-out event is received.
514 /// It will check if there are any active breakpoints that need to be made
515 /// pending.
516 ///
517 /// Returns `true` if any breakpoints were updated, `false` otherwise.
518 fn handle_page_out(
519 &mut self,
520 vmi: &VmiCore<Interface::Driver>,
521 update: &PageEntryUpdate,
522 ) -> Result<bool, VmiError> {
523 let gfn = <Interface::Driver as VmiDriver>::Architecture::gfn_from_pa(update.pa);
524 let view = update.view;
525
526 let breakpoints_by_ctx = match self.remove_active_breakpoints_by_location(vmi, gfn, view)? {
527 Some(breakpoints_by_ctx) => breakpoints_by_ctx,
528 None => return Ok(false),
529 };
530
531 for breakpoint in breakpoints_by_ctx.into_values().flatten() {
532 self.insert_pending_breakpoint(breakpoint);
533 }
534
535 Ok(true)
536 }
537
538 /// Inserts an active breakpoint.
539 ///
540 /// This function is used to register a breakpoint that can be immediately
541 /// inserted. The breakpoint is inserted into the active breakpoints map
542 /// and the monitored views map.
543 ///
544 /// Returns `true` if the breakpoint was newly inserted, `false` if it was
545 /// already present.
546 fn insert_active_breakpoint(
547 &mut self,
548 vmi: &VmiCore<Interface::Driver>,
549 breakpoint: Breakpoint<Key, Tag>,
550 pa: Pa,
551 ) -> Result<bool, VmiError> {
552 //
553 // The code in this function roughly follows the following logic:
554 //
555 // let breakpoint_was_inserted = self
556 // .active_breakpoints
557 // .entry((view, gfn))
558 // .or_default()
559 // .entry((key, ctx))
560 // .or_default()
561 // .insert(tag);
562 //
563 // self.gfns_for_address.insert((key, ctx), (view, gfn))
564 //
565 // Except that:
566 // - breakpoint_was_inserted is true ONLY if the breakpoint was inserted
567 // (i.e., not just updated with a new tag)
568 // - asserts are used to ensure that the internal state is consistent
569 //
570
571 let Breakpoint {
572 mut ctx,
573 view,
574 global,
575 key,
576 tag,
577 } = breakpoint;
578 let gfn = <Interface::Driver as VmiDriver>::Architecture::gfn_from_pa(pa);
579
580 //
581 // If this breakpoint should be global, update the global breakpoints.
582 // Also, verify that global breakpoint for this address is was not
583 // already registered, or that it was registered with the same root.
584 //
585
586 if global {
587 self.register_global_breakpoint(gfn, view, &mut ctx);
588 }
589
590 //
591 // page_was_inserted is true if a new `(view, GFN)` pair was inserted
592 // breakpoint_was_inserted is true if a new `(key, ctx)` pair was inserted
593 //
594
595 let (breakpoint_was_inserted, page_was_inserted) =
596 match self.active_breakpoints.entry((view, gfn)) {
597 Entry::Occupied(mut entry) => {
598 let breakpoints = entry.get_mut();
599
600 match breakpoints.entry((key, ctx)) {
601 Entry::Occupied(mut entry) => {
602 let breakpoints = entry.get_mut();
603 breakpoints.insert(breakpoint);
604 (false, false)
605 }
606 Entry::Vacant(entry) => {
607 entry.insert(HashSet::from([breakpoint]));
608 (true, false)
609 }
610 }
611 }
612 Entry::Vacant(entry) => {
613 entry.insert(HashMap::from([((key, ctx), HashSet::from([breakpoint]))]));
614 (true, true)
615 }
616 };
617
618 if breakpoint_was_inserted {
619 tracing::debug!(
620 active = self.active_breakpoints.len(),
621 %gfn, %ctx, %view, %global, ?key, ?tag,
622 "active breakpoint inserted"
623 );
624
625 //
626 // REVIEW:
627 // Should monitor + insert_breakpoint be atomic? (i.e., should we
628 // consider vmi.pause() + vmi.resume()?)
629 //
630
631 //
632 // Update the monitored GFNs.
633 // Also, verify that the monitored GFNs are consistent with the
634 // active breakpoints.
635 //
636
637 self.install_breakpoint(vmi, pa, view, key, ctx)?;
638
639 //
640 // If this is a new `(view, GFN)` pair, it needs to be monitored.
641 //
642 // IMPORTANT: It is important to monitor the page AFTER the
643 // breakpoint is inserted. Otherwise, the page access
644 // might change during the GFN remapping.
645 //
646
647 if page_was_inserted {
648 self.monitor_page_for_changes(vmi, gfn, view)?;
649 }
650 }
651 else {
652 //
653 // If the breakpoint was not inserted, it means it is already
654 // in the active breakpoints.
655 //
656 // Verify that the monitored GFNs are consistent.
657 //
658
659 debug_assert!(
660 self.active_locations.contains_key(&(key, ctx)),
661 "desynchronized active breakpoints and monitored gfns"
662 );
663 }
664
665 Ok(breakpoint_was_inserted)
666 }
667
668 /// Removes an active breakpoint.
669 ///
670 /// Returns either:
671 /// - `Some(true)` if the breakpoint was removed and it was the last
672 /// breakpoint for the `(view, GFN)` pair.
673 /// - `Some(false)` if the breakpoint was removed but there are still
674 /// other breakpoints for the `(view, GFN)` pair.
675 /// - `None` if the breakpoint was not found.
676 fn remove_active_breakpoint(
677 &mut self,
678 vmi: &VmiCore<Interface::Driver>,
679 ctx: impl Into<AddressContext>,
680 pa: Pa,
681 key: Key,
682 view: View,
683 ) -> Result<Option<bool>, VmiError> {
684 //
685 // First check if this `(view, GFN)` already has a breakpoint.
686 //
687
688 let gfn = <Interface::Driver as VmiDriver>::Architecture::gfn_from_pa(pa);
689
690 let mut gfn_entry = match self.active_breakpoints.entry((view, gfn)) {
691 Entry::Occupied(gfn_entry) => gfn_entry,
692 Entry::Vacant(_) => return Ok(None),
693 };
694
695 //
696 // If this `(view, GFN)` has a breakpoint, verify that the monitored
697 // locations are consistent with the active breakpoints.
698 //
699
700 debug_assert!(
701 self.active_gfns_by_view.contains_key(&view)
702 && self.active_gfns_by_view[&view].contains(&gfn),
703 "desynchronized active_breakpoints and active_gfns_by_view"
704 );
705
706 let ctx = ctx.into();
707
708 //
709 // This `(view, GFN)` has a breakpoint.
710 // Check if the specified `(key, ctx)` has a breakpoint.
711 //
712
713 let breakpoints_by_ctx = gfn_entry.get_mut();
714
715 let breakpoints = match breakpoints_by_ctx.remove(&(key, ctx)) {
716 Some(breakpoints) => breakpoints,
717 None => {
718 //
719 // This `(key, ctx)` doesn't have a breakpoint for this `(view, GFN)`.
720 // Keep the breakpoint for the current `(view, GFN)`.
721 //
722 // Also, verify that the active locations are consistent with the
723 // active breakpoints.
724 //
725
726 if !self.active_locations.contains_key(&(key, ctx)) {
727 tracing::debug!(
728 %gfn, %ctx, %view, ?key,
729 "breakpoint not found for key"
730 );
731 }
732 else {
733 tracing::error!(
734 %gfn, %ctx, %view, ?key,
735 "breakpoint not found for key"
736 );
737 }
738
739 debug_assert!(
740 !self.active_locations.contains_key(&(key, ctx)),
741 "desynchronized active_breakpoints and active_locations"
742 );
743
744 return Ok(Some(false));
745 }
746 };
747
748 let last_breakpoint_removed = breakpoints_by_ctx.is_empty();
749
750 if last_breakpoint_removed {
751 //
752 // There are no more breakpoints registered for this `(view, GFN)`.
753 // Remove the breakpoint for the current `(view, GFN)`.
754 //
755
756 tracing::debug!(
757 %gfn, %ctx, %view, ?key, ?breakpoints,
758 "breakpoint removed"
759 );
760
761 gfn_entry.remove();
762 }
763 else {
764 //
765 // There are still other breakpoints registered for this `(view, GFN)`.
766 // Keep the breakpoint for the current `(view, GFN)`.
767 //
768
769 tracing::debug!(
770 %gfn, %ctx, %view, ?key,
771 remaining = breakpoints_by_ctx.len(),
772 "breakpoint still in use"
773 );
774 }
775
776 self.uninstall_breakpoint(vmi, pa, view, key, ctx)?;
777
778 if last_breakpoint_removed {
779 //
780 // Because there are no more breakpoints for this `(view, GFN)`,
781 // unmonitor the `(view, GFN)` pair.
782 //
783 // IMPORTANT: It is important to unmonitor the page AFTER the
784 // breakpoint is removed. Otherwise, the page access
785 // might change during the GFN remapping.
786 //
787
788 self.unmonitor_page_for_changes(vmi, gfn, view)?;
789 }
790
791 Ok(Some(last_breakpoint_removed))
792 }
793
794 /// Removes all active breakpoints for a given `(view, GFN)` pair.
795 fn remove_active_breakpoints_by_location(
796 &mut self,
797 vmi: &VmiCore<Interface::Driver>,
798 gfn: Gfn,
799 view: View,
800 ) -> Result<Option<ActiveBreakpoints<Key, Tag>>, VmiError> {
801 let breakpoints = match self.active_breakpoints.remove(&(view, gfn)) {
802 Some(breakpoints) => breakpoints,
803 None => return Ok(None),
804 };
805
806 //
807 // REVIEW:
808 // Should remove_breakpoint + unmonitor be atomic? (i.e., should we
809 // consider vmi.pause() + vmi.resume()?)
810 //
811
812 for &(key, ctx) in breakpoints.keys() {
813 let pa = self.pa_from_gfn_and_va(gfn, ctx.va);
814 self.uninstall_breakpoint(vmi, pa, view, key, ctx)?;
815 }
816
817 tracing::debug!(
818 active = self.active_breakpoints.len(),
819 %gfn,
820 %view,
821 ?breakpoints,
822 "active breakpoints removed"
823 );
824
825 self.unmonitor_page_for_changes(vmi, gfn, view)?;
826
827 Ok(Some(breakpoints))
828 }
829
830 /// Inserts a pending breakpoint.
831 ///
832 /// Returns `true` if the breakpoint was newly inserted, `false` if it was
833 /// already present.
834 fn insert_pending_breakpoint(&mut self, breakpoint: Breakpoint<Key, Tag>) -> bool {
835 let Breakpoint {
836 ctx,
837 view,
838 global,
839 key,
840 tag,
841 ..
842 } = breakpoint;
843
844 let result = self
845 .pending_breakpoints
846 .entry((view, ctx))
847 .or_default()
848 .insert(breakpoint);
849
850 self.pending_ctx_by_view
851 .entry(view)
852 .or_default()
853 .insert(ctx);
854
855 tracing::debug!(
856 pending = self.pending_breakpoints.len(),
857 %ctx,
858 %view,
859 %global,
860 ?key,
861 ?tag,
862 "pending breakpoint inserted"
863 );
864
865 result
866 }
867
868 /// Removes all pending breakpoints for a given `(view, ctx)` pair.
869 ///
870 /// Returns the pending breakpoints if they were removed, `None` otherwise.
871 fn remove_pending_breakpoints_by_address(
872 &mut self,
873 ctx: AddressContext,
874 view: View,
875 ) -> Option<PendingBreakpoints<Key, Tag>> {
876 let breakpoints = self.pending_breakpoints.remove(&(view, ctx))?;
877
878 match self.pending_ctx_by_view.entry(view) {
879 Entry::Occupied(mut entry) => {
880 let addresses = entry.get_mut();
881 let address_was_removed = addresses.remove(&ctx);
882 debug_assert!(
883 address_was_removed,
884 "desynchronized pending_breakpoints and pending_ctx_by_view"
885 );
886
887 if addresses.is_empty() {
888 entry.remove();
889 }
890 }
891 Entry::Vacant(_) => {
892 //
893 // `remove_breakpoints_by_view()` removes the entry before
894 // calling this function.
895 //
896 }
897 }
898
899 tracing::debug!(
900 pending = self.pending_breakpoints.len(),
901 %ctx,
902 ?breakpoints,
903 "pending breakpoints removed"
904 );
905
906 Some(breakpoints)
907 }
908
909 fn register_global_breakpoint(&mut self, gfn: Gfn, view: View, ctx: &mut AddressContext) {
910 match self.active_global_breakpoints.entry((view, ctx.va)) {
911 Entry::Occupied(mut entry) => {
912 let global_breakpoint = entry.get_mut();
913 let gfn_was_inserted = global_breakpoint.gfns.insert(gfn);
914 debug_assert!(
915 gfn_was_inserted,
916 "trying to register a global breakpoint that is already registered"
917 );
918
919 ctx.root = global_breakpoint.root;
920 }
921 Entry::Vacant(entry) => {
922 entry.insert(GlobalBreakpoint {
923 root: ctx.root,
924 gfns: HashSet::from([gfn]),
925 });
926 }
927 }
928 }
929
930 fn unregister_global_breakpoint(
931 &mut self,
932 gfn: Gfn,
933 view: View,
934 ctx: AddressContext,
935 ) -> Option<bool> {
936 match self.active_global_breakpoints.entry((view, ctx.va)) {
937 Entry::Occupied(mut entry) => {
938 let global_breakpoint = entry.get_mut();
939 let page_was_removed = global_breakpoint.gfns.remove(&gfn);
940 debug_assert!(
941 page_was_removed,
942 "trying to unregister a global breakpoint that is not registered"
943 );
944
945 if !global_breakpoint.gfns.is_empty() {
946 return Some(false);
947 }
948
949 entry.remove();
950 Some(true)
951 }
952 Entry::Vacant(_) => None,
953 }
954 }
955
956 fn insert_monitored_location(&mut self, gfn: Gfn, view: View) {
957 //
958 // Verify that the active breakpoint is inserted before monitoring the
959 // `(view, GFN)` pair.
960 //
961
962 debug_assert!(
963 self.active_breakpoints.contains_key(&(view, gfn)),
964 "breakpoint must be in active_breakpoints before monitoring"
965 );
966
967 let gfn_was_inserted = self
968 .active_gfns_by_view
969 .entry(view)
970 .or_default()
971 .insert(gfn);
972
973 //
974 // The GFN should not have been monitored before.
975 //
976
977 debug_assert!(
978 gfn_was_inserted,
979 "trying to monitor an already monitored GFN"
980 );
981 }
982
983 fn remove_monitored_location(&mut self, gfn: Gfn, view: View) {
984 //
985 // Verify that the active breakpoint is removed before unmonitoring the
986 // `(view, GFN)` pair.
987 //
988
989 debug_assert!(
990 !self.active_breakpoints.contains_key(&(view, gfn)),
991 "breakpoint must be removed from active_breakpoints before unmonitoring"
992 );
993
994 match self.active_gfns_by_view.entry(view) {
995 Entry::Occupied(mut entry) => {
996 let gfns = entry.get_mut();
997 let gfn_was_present = gfns.remove(&gfn);
998 debug_assert!(gfn_was_present, "trying to unmonitor a non-monitored gfn");
999
1000 if gfns.is_empty() {
1001 entry.remove();
1002 }
1003 }
1004 Entry::Vacant(_) => {
1005 //
1006 // `remove_breakpoints_by_view()` removes the entry before
1007 // calling this function.
1008 //
1009 }
1010 }
1011 }
1012
1013 fn monitor_page_for_changes(
1014 &mut self,
1015 vmi: &VmiCore<Interface::Driver>,
1016 gfn: Gfn,
1017 view: View,
1018 ) -> Result<(), VmiError> {
1019 self.insert_monitored_location(gfn, view);
1020 self.controller.monitor(vmi, gfn, view)
1021 }
1022
1023 fn unmonitor_page_for_changes(
1024 &mut self,
1025 vmi: &VmiCore<Interface::Driver>,
1026 gfn: Gfn,
1027 view: View,
1028 ) -> Result<(), VmiError> {
1029 self.remove_monitored_location(gfn, view);
1030 match self.controller.unmonitor(vmi, gfn, view) {
1031 Ok(()) => Ok(()),
1032 Err(VmiError::ViewNotFound) => {
1033 //
1034 // The view was not found. This can happen if the view was
1035 // destroyed before the breakpoint was removed.
1036 //
1037 // In this case, we can safely ignore the error.
1038 //
1039 Ok(())
1040 }
1041 Err(err) => Err(err),
1042 }
1043 }
1044
1045 fn install_breakpoint(
1046 &mut self,
1047 vmi: &VmiCore<Interface::Driver>,
1048 pa: Pa,
1049 view: View,
1050 key: Key,
1051 ctx: AddressContext,
1052 ) -> Result<(), VmiError> {
1053 let gfn = <Interface::Driver as VmiDriver>::Architecture::gfn_from_pa(pa);
1054 let view_gfn_was_inserted = self
1055 .active_locations
1056 .entry((key, ctx))
1057 .or_default()
1058 .insert((view, gfn));
1059
1060 debug_assert!(
1061 view_gfn_was_inserted,
1062 "trying to install a breakpoint that is already installed"
1063 );
1064
1065 self.controller.insert_breakpoint(vmi, pa, view)
1066 }
1067
1068 fn uninstall_breakpoint(
1069 &mut self,
1070 vmi: &VmiCore<Interface::Driver>,
1071 pa: Pa,
1072 view: View,
1073 key: Key,
1074 ctx: AddressContext,
1075 ) -> Result<(), VmiError> {
1076 let gfn = <Interface::Driver as VmiDriver>::Architecture::gfn_from_pa(pa);
1077 self.unregister_global_breakpoint(gfn, view, ctx);
1078
1079 match self.active_locations.entry((key, ctx)) {
1080 Entry::Occupied(mut entry) => {
1081 let view_gfns = entry.get_mut();
1082 let view_gfn_was_removed = view_gfns.remove(&(view, gfn));
1083 debug_assert!(
1084 view_gfn_was_removed,
1085 "trying to uninstall a breakpoint that is not installed"
1086 );
1087
1088 if view_gfns.is_empty() {
1089 entry.remove();
1090 }
1091 }
1092 Entry::Vacant(_) => {
1093 panic!("trying to uninstall a breakpoint that is not installed");
1094 }
1095 }
1096
1097 match self.controller.remove_breakpoint(vmi, pa, view) {
1098 Ok(()) => Ok(()),
1099 Err(VmiError::ViewNotFound) => {
1100 //
1101 // The view was not found. This can happen if the view was
1102 // destroyed before the breakpoint was removed.
1103 //
1104 // In this case, we can safely ignore the error.
1105 //
1106 Ok(())
1107 }
1108 Err(err) => Err(err),
1109 }
1110 }
1111
1112 fn pa_from_gfn_and_va(&self, gfn: Gfn, va: Va) -> Pa {
1113 <Interface::Driver as VmiDriver>::Architecture::pa_from_gfn(gfn)
1114 + <Interface::Driver as VmiDriver>::Architecture::va_offset(va)
1115 }
1116
1117 fn address_for_event(
1118 &self,
1119 event: &VmiEvent<<Interface::Driver as VmiDriver>::Architecture>,
1120 ) -> Option<(AddressContext, Pa, View)> {
1121 let (view, gfn) = match self.controller.check_event(event) {
1122 Some((view, gfn)) => (view, gfn),
1123 None => return None,
1124 };
1125
1126 let ip = Va(event.registers().instruction_pointer());
1127 let pa = self.pa_from_gfn_and_va(gfn, ip);
1128
1129 //
1130 // If there is a global breakpoint for this address, fix the root
1131 // with the one it was registered with.
1132 //
1133
1134 let root = match self.active_global_breakpoints.get(&(view, ip)) {
1135 Some(global_breakpoint) => global_breakpoint.root,
1136 None => event.registers().translation_root(ip),
1137 };
1138
1139 let ctx = AddressContext::new(ip, root);
1140
1141 Some((ctx, pa, view))
1142 }
1143}