bugstalker/debugger/
watchpoint.rs

1use crate::debugger::Error::Hook;
2use crate::debugger::address::{GlobalAddress, RelocatedAddress};
3use crate::debugger::breakpoint::{Breakpoint, BreakpointRegistry};
4use crate::debugger::debugee::dwarf::r#type::TypeIdentity;
5use crate::debugger::debugee::tracee::TraceeCtl;
6use crate::debugger::debugee::tracer::WatchpointHitType;
7use crate::debugger::debugee::{Debugee, Location};
8use crate::debugger::register::debug::{
9    BreakCondition, BreakSize, DebugRegisterNumber, HardwareDebugState,
10};
11use crate::debugger::unwind::FrameID;
12use crate::debugger::variable::dqe::Dqe;
13use crate::debugger::variable::execute::{DqeExecutor, QueryResult};
14use crate::debugger::variable::value::{ScalarValue, SupportedScalar, Value};
15use crate::debugger::{Debugger, Error, ExplorationContext, Tracee};
16use crate::{debugger, disable_when_not_stared, weak_error};
17use log::error;
18use nix::unistd::Pid;
19use std::borrow::Cow;
20use std::mem;
21use std::sync::atomic::{AtomicU32, Ordering};
22
23#[derive(Debug)]
24struct ExpressionTarget {
25    /// Original DQE string.
26    source_string: String,
27    /// Address DQE.
28    dqe: Dqe,
29    /// Last evaluated underlying DQE result.
30    last_value: Option<Value>,
31    /// ID of in-focus frame at the time when watchpoint was created.
32    /// Whether `None` when underlying expression has a global or undefined scope.
33    frame_id: Option<FrameID>,
34    /// ID of in-focus thread at the time when watchpoint was created.
35    tid: Pid,
36    /// Contains breakpoint number if watchpoint DQE is scoped (have a limited lifetime).
37    /// This breakpoint points to the end of DQE scope.
38    companion: Option<u32>,
39}
40
41impl ExpressionTarget {
42    fn underlying_dqe(&self) -> &Dqe {
43        let Dqe::Address(ref underlying_dqe) = self.dqe else {
44            unreachable!("infallible: watchpoint always contains an address DQE");
45        };
46        underlying_dqe
47    }
48}
49
50#[derive(Debug)]
51struct AddressTarget {
52    /// Last seen dereferenced value.
53    /// This is [`Value::Scalar`] with one of u8, u16, u32 or u64 underlying value.
54    last_value: Option<Value>,
55}
56
57impl AddressTarget {
58    fn refresh_last_value(&mut self, pid: Pid, hw: &HardwareBreakpoint) -> Option<Value> {
59        let read_size = match hw.size {
60            BreakSize::Bytes1 => 1,
61            BreakSize::Bytes2 => 2,
62            BreakSize::Bytes8 => 8,
63            BreakSize::Bytes4 => 4,
64        };
65
66        let maybe_data = weak_error!(debugger::read_memory_by_pid(
67            pid,
68            hw.address.as_usize(),
69            read_size
70        ));
71        let new_val = maybe_data.map(|data| {
72            let (t, u) = match hw.size {
73                BreakSize::Bytes1 => (
74                    "u8",
75                    SupportedScalar::U8(u8::from_ne_bytes(
76                        data.try_into().expect("unexpected size"),
77                    )),
78                ),
79                BreakSize::Bytes2 => (
80                    "u16",
81                    SupportedScalar::U16(u16::from_ne_bytes(
82                        data.try_into().expect("unexpected size"),
83                    )),
84                ),
85                BreakSize::Bytes8 => (
86                    "u64",
87                    SupportedScalar::U64(u64::from_ne_bytes(
88                        data.try_into().expect("unexpected size"),
89                    )),
90                ),
91                BreakSize::Bytes4 => (
92                    "u32",
93                    SupportedScalar::U32(u32::from_ne_bytes(
94                        data.try_into().expect("unexpected size"),
95                    )),
96                ),
97            };
98            Value::Scalar(ScalarValue {
99                value: Some(u),
100                type_ident: TypeIdentity::no_namespace(t),
101                type_id: None,
102                raw_address: None,
103            })
104        });
105
106        mem::replace(&mut self.last_value, new_val)
107    }
108}
109
110#[derive(Debug)]
111enum Subject {
112    /// Watchpoint with DQE result as an observed subject.
113    Expression(ExpressionTarget),
114    /// Watchpoint with a memory location as an observed subject.
115    Address(AddressTarget),
116}
117
118#[derive(Debug)]
119struct HardwareBreakpoint {
120    /// Address in debugee memory where hardware breakpoint is set.
121    address: RelocatedAddress,
122    /// Size of watch location at the address.
123    size: BreakSize,
124    /// Hardware register.
125    register: Option<DebugRegisterNumber>,
126    /// Associated condition.
127    condition: BreakCondition,
128}
129
130impl HardwareBreakpoint {
131    fn new(address: RelocatedAddress, size: BreakSize, condition: BreakCondition) -> Self {
132        Self {
133            address,
134            size,
135            register: None,
136            condition,
137        }
138    }
139
140    fn enable(&mut self, tracee_ctl: &TraceeCtl) -> Result<HardwareDebugState, Error> {
141        let mut state = HardwareDebugState::current(tracee_ctl.proc_pid())?;
142
143        // trying to find free debug register
144        let free_register = [
145            DebugRegisterNumber::DR0,
146            DebugRegisterNumber::DR1,
147            DebugRegisterNumber::DR2,
148            DebugRegisterNumber::DR3,
149        ]
150        .into_iter()
151        .find(|&dr_num| !state.dr7.dr_enabled(dr_num, false))
152        .ok_or(Error::WatchpointLimitReached)?;
153
154        // set hardware breakpoint
155        state.address_regs[free_register as usize] = self.address.as_usize();
156        state
157            .dr7
158            .configure_bp(free_register, self.condition, self.size);
159        state.dr7.set_dr(free_register, false, true);
160        tracee_ctl.tracee_iter().for_each(|t| {
161            if let Err(e) = state.sync(t.pid) {
162                error!("set hardware breakpoint for thread {}: {e}", t.pid)
163            }
164        });
165        self.register = Some(free_register);
166
167        Ok(state)
168    }
169
170    fn disable(&mut self, tracee_ctl: &TraceeCtl) -> Result<HardwareDebugState, Error> {
171        let mut state = HardwareDebugState::current(tracee_ctl.proc_pid())?;
172        let register = self.register.expect("should exist");
173        state.dr7.set_dr(register, false, false);
174        tracee_ctl.tracee_iter().for_each(|t| {
175            if let Err(e) = state.sync(t.pid) {
176                error!("remove hardware breakpoint for thread {}: {e}", t.pid)
177            }
178        });
179        self.register = None;
180        Ok(state)
181    }
182
183    fn address_already_observed(
184        tracee_ctl: &TraceeCtl,
185        address: RelocatedAddress,
186    ) -> Result<bool, Error> {
187        let state = HardwareDebugState::current(tracee_ctl.proc_pid())?;
188        Ok(state
189            .address_regs
190            .iter()
191            .enumerate()
192            .any(|(dr, in_use_addr)| {
193                let enabled = state.dr7.dr_enabled(
194                    DebugRegisterNumber::from_repr(dr).expect("infallible"),
195                    false,
196                );
197                enabled && *in_use_addr == address.as_usize()
198            }))
199    }
200}
201
202static GLOBAL_WP_COUNTER: AtomicU32 = AtomicU32::new(1);
203
204/// Watchpoint representation.
205#[derive(Debug)]
206pub struct Watchpoint {
207    /// Watchpoint number, started from 1.
208    number: u32,
209    /// Underlying hardware breakpoint.
210    hw: HardwareBreakpoint,
211    /// Subject for observation.
212    subject: Subject,
213    /// Temporary watchpoint may be created by step's algorithms
214    temporary: bool,
215}
216
217fn call_with_context<F, T>(debugger: &mut Debugger, ctx: ExplorationContext, f: F) -> T
218where
219    F: FnOnce(&Debugger) -> T,
220{
221    let old_ctx = mem::replace(&mut debugger.expl_context, ctx);
222    let result = f(debugger);
223    debugger.expl_context = old_ctx;
224    result
225}
226
227impl Watchpoint {
228    pub fn is_temporary(&self) -> bool {
229        self.temporary
230    }
231
232    fn execute_dqe(debugger: &Debugger, dqe: Dqe) -> Result<QueryResult<'_>, Error> {
233        let executor = DqeExecutor::new(debugger);
234
235        // trying to evaluate at variables first,
236        // if a result is empty, try to evaluate at function arguments
237        let mut evaluation_on_vars_results = executor.query(&dqe)?;
238        let mut evaluation_on_args_results;
239        let expr_result = match evaluation_on_vars_results.len() {
240            0 => {
241                evaluation_on_args_results = executor.query_arguments(&dqe)?;
242                match evaluation_on_args_results.len() {
243                    0 => return Err(Error::WatchSubjectNotFound),
244                    1 => evaluation_on_args_results.pop().expect("infallible"),
245                    _ => return Err(Error::WatchpointCollision),
246                }
247            }
248            1 => evaluation_on_vars_results.pop().expect("infallible"),
249            _ => return Err(Error::WatchpointCollision),
250        };
251        Ok(expr_result)
252    }
253
254    /// Create watchpoint using a result of DQE as a subject for observation.
255    ///
256    /// # Arguments
257    ///
258    /// * `debugger`: debugger instance
259    /// * `expr_source`: DQE string representation
260    /// * `dqe`: DQE
261    /// * `condition`: condition for activating a watchpoint
262    pub fn from_dqe(
263        debugger: &mut Debugger,
264        expr_source: &str,
265        dqe: Dqe,
266        condition: BreakCondition,
267    ) -> Result<(HardwareDebugState, Self), Error> {
268        // wrap expression with address operation
269        let dqe = Dqe::Address(dqe.boxed());
270
271        let address_dqe_result = Self::execute_dqe(debugger, dqe.clone())?;
272        let Value::Pointer(ptr) = address_dqe_result.value() else {
273            unreachable!("infallible: address DQE always return a pointer")
274        };
275
276        let address = RelocatedAddress::from(ptr.value.ok_or(Error::WatchpointNoAddress)? as usize);
277        if HardwareBreakpoint::address_already_observed(debugger.debugee.tracee_ctl(), address)? {
278            return Err(Error::AddressAlreadyObserved);
279        }
280
281        let size = ptr.target_type_size.ok_or(Error::WatchpointUndefinedSize)?;
282        if size > u8::MAX as u64 {
283            return Err(Error::WatchpointWrongSize);
284        };
285        let size = BreakSize::try_from(size as u8)?;
286
287        let mut end_of_scope_brkpt = None;
288        let mut frame_id = None;
289        if let Some(scope) = address_dqe_result.scope() {
290            // take a current frame id
291            let expl_ctx = debugger.exploration_ctx();
292            let frame_num = expl_ctx.frame_num();
293            let backtrace = debugger.backtrace(expl_ctx.pid_on_focus())?;
294            frame_id = backtrace
295                .get(frame_num as usize)
296                .ok_or(Error::FrameNotFound(frame_num))?
297                .id();
298
299            let pc = expl_ctx.location().pc;
300            let dwarf = debugger.debugee.debug_info(pc)?;
301
302            // from all expression ranges take end-address with maximum line number -
303            // this will be an address of a companion breakpoint
304            let mut best_place = None;
305
306            for range in scope.iter() {
307                let maybe_place = dwarf.find_exact_place_from_pc(GlobalAddress::from(range.end))?;
308                if let Some(range_end_place) = maybe_place {
309                    let mut place = range_end_place.clone();
310
311                    // find a suitable place algorithm:
312                    // 1) DOWN phase: for each next place from range_end_place:
313                    // 1.1) if place is a statement - suitable place found
314                    // 1.2) if no next place, or next place is ET or EB - go to UP phase
315                    // 2) UP phase: for each previous place from range_end_place:
316                    // 2.1) if place is a statement - suitable place found
317                    // 2.2) if place address <= range.begin - suitable place not found
318                    // 2.3) if no next place, or previous place is ET or PE - suitable place not found
319
320                    let mut suitable_place = loop {
321                        if place.is_stmt {
322                            break Some(place);
323                        }
324                        if place.epilog_begin || place.end_sequence {
325                            break None;
326                        }
327                        match place.next() {
328                            None => break None,
329                            Some(p) => place = p,
330                        }
331                    };
332
333                    if suitable_place.is_none()
334                        && let Some(mut place) = range_end_place.prev()
335                    {
336                        suitable_place = loop {
337                            if place.address <= GlobalAddress::from(range.begin) {
338                                break None;
339                            }
340                            if place.is_stmt {
341                                break Some(place);
342                            }
343                            if place.prolog_end || place.end_sequence {
344                                break None;
345                            }
346                            match place.prev() {
347                                None => break None,
348                                Some(p) => place = p,
349                            }
350                        };
351                    }
352
353                    if let Some(suitable_place) = suitable_place {
354                        match best_place {
355                            None => best_place = Some(suitable_place),
356                            Some(max) if max.line_number <= suitable_place.line_number => {
357                                best_place = Some(suitable_place)
358                            }
359                            _ => {}
360                        }
361                    }
362                }
363            }
364
365            let best_place = best_place.ok_or(Error::UnknownScope)?;
366
367            let end_of_scope = best_place
368                .address
369                .relocate_to_segment(&debugger.debugee, dwarf)?;
370            let next_wp_num = GLOBAL_WP_COUNTER.load(Ordering::Relaxed);
371            let brkpt = Breakpoint::new_watchpoint_companion(
372                &debugger.breakpoints,
373                next_wp_num,
374                end_of_scope,
375                expl_ctx.pid_on_focus(),
376            );
377            let brkpt_view = debugger.breakpoints.add_and_enable(brkpt)?;
378            end_of_scope_brkpt = Some(brkpt_view.number);
379        }
380
381        let mut target = ExpressionTarget {
382            source_string: expr_source.to_string(),
383            dqe,
384            last_value: None,
385            frame_id,
386            tid: debugger.exploration_ctx().pid_on_focus(),
387            companion: end_of_scope_brkpt,
388        };
389        let underlying_dqe = target.underlying_dqe().clone();
390        let var = Self::execute_dqe(debugger, underlying_dqe)
391            .map(|ev| ev.into_value())
392            .ok();
393        target.last_value = var;
394
395        let mut hw_brkpt = HardwareBreakpoint::new(address, size, condition);
396        let state = hw_brkpt.enable(debugger.debugee.tracee_ctl())?;
397
398        let this = Self {
399            number: GLOBAL_WP_COUNTER.fetch_add(1, Ordering::Relaxed),
400            hw: hw_brkpt,
401            subject: Subject::Expression(target),
402            temporary: false,
403        };
404        Ok((state, this))
405    }
406
407    /// Create watchpoint using a memory location address and size.
408    ///
409    /// # Arguments
410    ///
411    /// * `tracee_ctl`: a source of information about tracee's
412    /// * `addr`: memory location address
413    /// * `size`: memory location size
414    /// * `condition`: condition for activating a watchpoint
415    /// * `temporary`: set temporary flag, temporary watchpoint ignores in hooks
416    fn from_raw_addr(
417        tracee_ctl: &TraceeCtl,
418        addr: RelocatedAddress,
419        size: BreakSize,
420        condition: BreakCondition,
421        temporary: bool,
422    ) -> Result<(HardwareDebugState, Self), Error> {
423        debug_assert!(
424            condition == BreakCondition::DataWrites || condition == BreakCondition::DataReadsWrites
425        );
426        if HardwareBreakpoint::address_already_observed(tracee_ctl, addr)? {
427            return Err(Error::AddressAlreadyObserved);
428        }
429        let mut hw_brkpt = HardwareBreakpoint::new(addr, size, condition);
430        let state = hw_brkpt.enable(tracee_ctl)?;
431
432        let mut target = AddressTarget { last_value: None };
433        target.refresh_last_value(tracee_ctl.proc_pid(), &hw_brkpt);
434
435        let this = Self {
436            number: GLOBAL_WP_COUNTER.fetch_add(1, Ordering::Relaxed),
437            hw: hw_brkpt,
438            subject: Subject::Address(target),
439            temporary,
440        };
441
442        Ok((state, this))
443    }
444
445    /// Return underlying hardware register.
446    pub fn register(&self) -> Option<DebugRegisterNumber> {
447        self.hw.register
448    }
449
450    /// Return watchpoint number.
451    pub fn number(&self) -> u32 {
452        self.number
453    }
454
455    fn last_value(&self) -> Option<&Value> {
456        match &self.subject {
457            Subject::Expression(e) => e.last_value.as_ref(),
458            Subject::Address(_) => None,
459        }
460    }
461
462    fn scoped(&self) -> bool {
463        matches!(
464            self.subject,
465            Subject::Expression(ExpressionTarget {
466                companion: Some(_),
467                ..
468            })
469        )
470    }
471
472    /// Disable watchpoint at all tracees, return new hardware debug registers state.
473    ///
474    /// # Arguments
475    ///
476    /// * `tracee_ctl`: a source of information about tracees
477    /// * `breakpoints`: breakpoint registry, used for a remove a companion
478    pub fn disable(
479        &mut self,
480        tracee_ctl: &TraceeCtl,
481        breakpoints: &mut BreakpointRegistry,
482    ) -> Result<HardwareDebugState, Error> {
483        // disable hardware breakpoint
484        let state = self.hw.disable(tracee_ctl)?;
485
486        if let Subject::Expression(ref e) = self.subject {
487            // decrease companion breakpoint refcount
488            if let Some(brkpt) = e.companion {
489                breakpoints.decrease_companion_rc(brkpt, self.number)?;
490            }
491        }
492        Ok(state)
493    }
494
495    /// Enable watchpoint from disabled state.
496    fn refresh(&mut self, tracee_ctl: &TraceeCtl) -> Result<HardwareDebugState, Error> {
497        if let Subject::Expression(ref mut expr_t) = self.subject {
498            expr_t.last_value = None;
499        }
500        let state = self.hw.enable(tracee_ctl)?;
501        Ok(state)
502    }
503}
504
505/// Watchpoint information struct.
506pub struct WatchpointView<'a> {
507    pub number: u32,
508    pub address: RelocatedAddress,
509    pub condition: BreakCondition,
510    pub source_dqe: Option<Cow<'a, str>>,
511    pub size: BreakSize,
512}
513
514impl<'a> From<&'a Watchpoint> for WatchpointView<'a> {
515    fn from(wp: &'a Watchpoint) -> Self {
516        Self {
517            number: wp.number,
518            address: wp.hw.address,
519            condition: wp.hw.condition,
520            source_dqe: if let Subject::Expression(ref t) = wp.subject {
521                Some(Cow::Borrowed(&t.source_string))
522            } else {
523                None
524            },
525            size: wp.hw.size,
526        }
527    }
528}
529
530impl From<Watchpoint> for WatchpointView<'_> {
531    fn from(mut wp: Watchpoint) -> Self {
532        Self {
533            number: wp.number,
534            address: wp.hw.address,
535            condition: wp.hw.condition,
536            source_dqe: if let Subject::Expression(ref mut t) = wp.subject {
537                let s = mem::take(&mut t.source_string);
538                Some(Cow::Owned(s))
539            } else {
540                None
541            },
542            size: wp.hw.size,
543        }
544    }
545}
546
547impl WatchpointView<'_> {
548    pub fn to_owned(&self) -> WatchpointViewOwned {
549        WatchpointViewOwned {
550            number: self.number,
551            address: self.address,
552            condition: self.condition,
553            source_dqe: self.source_dqe.as_ref().map(|dqe| dqe.to_string()),
554            size: self.size,
555        }
556    }
557}
558
559#[derive(Clone, Debug, PartialEq)]
560pub struct WatchpointViewOwned {
561    pub number: u32,
562    pub address: RelocatedAddress,
563    pub condition: BreakCondition,
564    pub source_dqe: Option<String>,
565    pub size: BreakSize,
566}
567
568/// Container for application watchpoints.
569#[derive(Default)]
570pub struct WatchpointRegistry {
571    /// Watchpoints list.
572    watchpoints: Vec<Watchpoint>,
573    /// Last used state of hardware debug registers. Update at inserting and removing watchpoints
574    /// from registry.
575    last_seen_state: Option<HardwareDebugState>,
576}
577
578impl WatchpointRegistry {
579    fn add(&mut self, state: HardwareDebugState, wp: Watchpoint) -> WatchpointView<'_> {
580        self.last_seen_state = Some(state);
581        self.watchpoints.push(wp);
582
583        (&self.watchpoints[self.watchpoints.len() - 1]).into()
584    }
585
586    /// Return watchpoint by number.
587    #[inline(always)]
588    pub fn get(&self, number: u32) -> Option<&Watchpoint> {
589        self.watchpoints.iter().find(|wp| wp.number() == number)
590    }
591
592    /// Return all watchpoints.
593    #[inline(always)]
594    pub fn all(&self) -> &[Watchpoint] {
595        self.watchpoints.as_slice()
596    }
597
598    /// Return all watchpoints (mutable).
599    #[inline(always)]
600    pub fn all_mut(&mut self) -> &mut [Watchpoint] {
601        self.watchpoints.as_mut()
602    }
603
604    fn remove(
605        &mut self,
606        tracee_ctl: &TraceeCtl,
607        brkpts: &mut BreakpointRegistry,
608        idx: usize,
609    ) -> Result<Option<WatchpointView<'_>>, Error> {
610        let mut wp = self.watchpoints.remove(idx);
611        let state = wp.disable(tracee_ctl, brkpts)?;
612        self.last_seen_state = Some(state);
613        Ok(Some(wp.into()))
614    }
615
616    /// Remove watchpoint by number.
617    fn remove_by_num(
618        &mut self,
619        tracee_ctl: &TraceeCtl,
620        breakpoints: &mut BreakpointRegistry,
621        num: u32,
622    ) -> Result<Option<WatchpointView<'_>>, Error> {
623        let Some(to_remove) = self.watchpoints.iter().position(|wp| wp.number == num) else {
624            return Ok(None);
625        };
626        self.remove(tracee_ctl, breakpoints, to_remove)
627    }
628
629    /// Remove watchpoint by memory location address.
630    fn remove_by_addr(
631        &mut self,
632        tracee_ctl: &TraceeCtl,
633        breakpoints: &mut BreakpointRegistry,
634        addr: RelocatedAddress,
635    ) -> Result<Option<WatchpointView<'_>>, Error> {
636        let Some(to_remove) = self.watchpoints.iter().position(|wp| wp.hw.address == addr) else {
637            return Ok(None);
638        };
639        self.remove(tracee_ctl, breakpoints, to_remove)
640    }
641
642    /// Remove watchpoint by underlying DQE.
643    fn remove_by_dqe(
644        &mut self,
645        tracee_ctl: &TraceeCtl,
646        breakpoints: &mut BreakpointRegistry,
647        dqe: Dqe,
648    ) -> Result<Option<WatchpointView<'_>>, Error> {
649        let needle = Dqe::Address(dqe.boxed());
650        let Some(to_remove) = self.watchpoints.iter().position(|wp| {
651            if let Subject::Expression(ExpressionTarget { dqe: wp_dqe, .. }) = &wp.subject {
652                &needle == wp_dqe
653            } else {
654                false
655            }
656        }) else {
657            return Ok(None);
658        };
659        self.remove(tracee_ctl, breakpoints, to_remove)
660    }
661
662    /// Remove all watchpoints.
663    pub fn clear_all(&mut self, tracee_ctl: &TraceeCtl, breakpoints: &mut BreakpointRegistry) {
664        let wp_count = self.watchpoints.len();
665        for _ in 0..wp_count {
666            weak_error!(self.remove(tracee_ctl, breakpoints, 0));
667        }
668        self.last_seen_state = None;
669    }
670
671    /// Remove all scoped watchpoints (typically it is watchpoints at local variables)
672    /// and disable non-scoped.
673    pub fn clear_local_disable_global(
674        &mut self,
675        tracee_ctl: &TraceeCtl,
676        breakpoints: &mut BreakpointRegistry,
677    ) -> Vec<Error> {
678        let wp_count = self.watchpoints.len();
679        let mut result = vec![];
680
681        let mut j = 0;
682        for _ in 0..wp_count {
683            if self.watchpoints[j].scoped() {
684                if let Err(e) = self.remove(tracee_ctl, breakpoints, j) {
685                    result.push(e);
686                }
687            } else {
688                if let Err(e) = self.watchpoints[j].disable(tracee_ctl, breakpoints) {
689                    result.push(e);
690                }
691                j += 1;
692            }
693        }
694        self.last_seen_state = None;
695        result
696    }
697
698    /// Distribute all existed watchpoints to a new tracee (thread).
699    pub fn distribute_to_tracee(&self, tracee: &Tracee) -> Result<(), Error> {
700        if let Some(ref state) = self.last_seen_state
701            && let Err(e) = state.sync(tracee.pid)
702        {
703            error!("set hardware breakpoint for thread {}: {e}", tracee.pid)
704        }
705        Ok(())
706    }
707
708    /// Enable all previously disabled watchpoints.
709    pub fn refresh(&mut self, debugee: &Debugee) -> Vec<Error> {
710        self.watchpoints
711            .iter_mut()
712            .filter_map(|wp| {
713                // local breakpoints must be removed when the registry is hibernated
714                debug_assert!(!wp.scoped());
715                match wp.refresh(debugee.tracee_ctl()) {
716                    Ok(state) => {
717                        self.last_seen_state = Some(state);
718                        None
719                    }
720                    Err(e) => Some(e),
721                }
722            })
723            .collect()
724    }
725}
726
727impl Debugger {
728    /// Set a new watchpoint on a result of DQE.
729    ///
730    /// # Arguments
731    ///
732    /// * `expr_source`: expression string
733    /// * `dqe`: DQE
734    /// * `condition`: condition for activating a watchpoint
735    pub fn set_watchpoint_on_expr(
736        &mut self,
737        expr_source: &str,
738        dqe: Dqe,
739        condition: BreakCondition,
740    ) -> Result<WatchpointView<'_>, Error> {
741        disable_when_not_stared!(self);
742        let (hw_state, wp) = Watchpoint::from_dqe(self, expr_source, dqe, condition)?;
743        Ok(self.watchpoints.add(hw_state, wp))
744    }
745
746    /// Set a new watchpoint on a memory location
747    ///
748    /// # Arguments
749    ///
750    /// * `addr`: address in debugee memory
751    /// * `size`: size of debugee memory location
752    /// * `condition`: condition for activating a watchpoint
753    pub fn set_watchpoint_on_memory(
754        &mut self,
755        addr: RelocatedAddress,
756        size: BreakSize,
757        condition: BreakCondition,
758        temporary: bool,
759    ) -> Result<WatchpointView<'_>, Error> {
760        disable_when_not_stared!(self);
761        let (hw_state, wp) =
762            Watchpoint::from_raw_addr(self.debugee.tracee_ctl(), addr, size, condition, temporary)?;
763        Ok(self.watchpoints.add(hw_state, wp))
764    }
765
766    /// Remove watchpoint by its number
767    ///
768    /// # Arguments
769    ///
770    /// * `num`: watchpoint number
771    pub fn remove_watchpoint_by_number(
772        &mut self,
773        num: u32,
774    ) -> Result<Option<WatchpointView<'_>>, Error> {
775        let breakpoints = &mut self.breakpoints;
776        self.watchpoints
777            .remove_by_num(self.debugee.tracee_ctl(), breakpoints, num)
778    }
779
780    /// Remove watchpoint by observed address in debugee memory.
781    ///
782    /// # Arguments
783    ///
784    /// * `addr`: address in debugee memory
785    pub fn remove_watchpoint_by_addr(
786        &mut self,
787        addr: RelocatedAddress,
788    ) -> Result<Option<WatchpointView<'_>>, Error> {
789        let breakpoints = &mut self.breakpoints;
790        self.watchpoints
791            .remove_by_addr(self.debugee.tracee_ctl(), breakpoints, addr)
792    }
793
794    /// Remove watchpoint by DQE, which result observed.
795    ///
796    /// # Arguments
797    ///
798    /// * `dqe`: DQE
799    pub fn remove_watchpoint_by_expr(
800        &mut self,
801        dqe: Dqe,
802    ) -> Result<Option<WatchpointView<'_>>, Error> {
803        let breakpoints = &mut self.breakpoints;
804        self.watchpoints
805            .remove_by_dqe(self.debugee.tracee_ctl(), breakpoints, dqe)
806    }
807
808    /// Return a list of all watchpoints.
809    pub fn watchpoint_list(&self) -> Vec<WatchpointView<'_>> {
810        self.watchpoints.all().iter().map(|wp| wp.into()).collect()
811    }
812
813    pub(super) fn execute_on_watchpoint_hook(
814        &mut self,
815        tid: Pid,
816        pc: RelocatedAddress,
817        ty: &WatchpointHitType,
818    ) -> Result<(), Error> {
819        match ty {
820            WatchpointHitType::DebugRegister(reg) => {
821                let maybe_wp = self
822                    .watchpoints
823                    .all()
824                    .iter()
825                    .find(|wp| wp.register() == Some(*reg) && !wp.temporary);
826
827                if let Some(wp) = maybe_wp {
828                    let number = wp.number();
829
830                    match &wp.subject {
831                        Subject::Expression(target) => {
832                            let dqe = target.underlying_dqe().clone();
833                            let current_tid = self.exploration_ctx().pid_on_focus();
834
835                            let new_value = match target.frame_id {
836                                None => {
837                                    Watchpoint::execute_dqe(self, dqe).map(|qr| qr.into_value())
838                                }
839                                // frame_id is actual if current tid and expression tid are equals,
840                                // otherwise evaluate as is
841                                Some(_) if target.tid != current_tid => {
842                                    Watchpoint::execute_dqe(self, dqe).map(|qr| qr.into_value())
843                                }
844                                Some(frame_id) => {
845                                    let bt = self.backtrace(current_tid)?;
846                                    let (num, frame) = bt
847                                        .iter()
848                                        .enumerate()
849                                        .find(|(_, frame)| frame.id() == Some(frame_id))
850                                        .ok_or(Error::VarFrameNotFound)?;
851
852                                    let loc = Location::new(
853                                        frame.ip,
854                                        frame.ip.into_global(&self.debugee).unwrap(),
855                                        current_tid,
856                                    );
857                                    let ctx = ExplorationContext::new(loc, num as u32);
858                                    call_with_context(self, ctx, |debugger| {
859                                        Watchpoint::execute_dqe(debugger, dqe)
860                                            .map(|qr| qr.into_value())
861                                    })
862                                }
863                            };
864
865                            let new_value = new_value.ok();
866
867                            let wp_mut = self
868                                .watchpoints
869                                .all_mut()
870                                .iter_mut()
871                                .find(|wp| wp.register() == Some(*reg))
872                                .expect("infallible");
873                            let Subject::Expression(t) = &mut wp_mut.subject else {
874                                unreachable!()
875                            };
876                            let old = mem::replace(&mut t.last_value, new_value);
877
878                            let dwarf = self.debugee.debug_info(pc)?;
879                            let place = weak_error!(
880                                dwarf.find_place_from_pc(pc.into_global(&self.debugee)?)
881                            )
882                            .flatten();
883
884                            self.hooks
885                                .on_watchpoint(
886                                    pc,
887                                    number,
888                                    place,
889                                    wp_mut.hw.condition,
890                                    Some(&t.source_string),
891                                    old.as_ref(),
892                                    t.last_value.as_ref(),
893                                    false,
894                                )
895                                .map_err(Hook)?;
896                        }
897                        Subject::Address(_) => {
898                            let wp_mut = self
899                                .watchpoints
900                                .all_mut()
901                                .iter_mut()
902                                .find(|wp| wp.register() == Some(*reg))
903                                .expect("infallible");
904                            let Subject::Address(t) = &mut wp_mut.subject else {
905                                unreachable!()
906                            };
907                            let old = t.refresh_last_value(tid, &wp_mut.hw);
908
909                            let dwarf = self.debugee.debug_info(pc)?;
910                            let place = weak_error!(
911                                dwarf.find_place_from_pc(pc.into_global(&self.debugee)?)
912                            )
913                            .flatten();
914                            self.hooks
915                                .on_watchpoint(
916                                    pc,
917                                    number,
918                                    place,
919                                    wp_mut.hw.condition,
920                                    None,
921                                    old.as_ref(),
922                                    t.last_value.as_ref(),
923                                    false,
924                                )
925                                .map_err(Hook)?;
926                        }
927                    }
928                }
929            }
930            WatchpointHitType::EndOfScope(wps) => {
931                let watchpoints = wps
932                    .iter()
933                    .filter_map(|&num| self.watchpoints.get(num))
934                    .collect::<Vec<_>>();
935                debug_assert_eq!(watchpoints.len(), wps.len());
936
937                let dwarf = self.debugee.debug_info(pc)?;
938                let place =
939                    weak_error!(dwarf.find_place_from_pc(pc.into_global(&self.debugee)?)).flatten();
940
941                for wp in watchpoints {
942                    let dqe_string =
943                        if let Subject::Expression(ExpressionTarget { source_string, .. }) =
944                            &wp.subject
945                        {
946                            Some(source_string.as_str())
947                        } else {
948                            None
949                        };
950
951                    self.hooks
952                        .on_watchpoint(
953                            pc,
954                            wp.number(),
955                            place.clone(),
956                            wp.hw.condition,
957                            dqe_string,
958                            wp.last_value(),
959                            None,
960                            true,
961                        )
962                        .map_err(Hook)?;
963                }
964                for number in wps {
965                    self.remove_watchpoint_by_number(*number)?;
966                }
967            }
968        }
969
970        Ok(())
971    }
972}