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}