cargo_tarpaulin/statemachine/
linux.rs

1use crate::breakpoint::*;
2use crate::cargo::rust_flags;
3use crate::config::Config;
4use crate::errors::RunError;
5use crate::generate_tracemap;
6use crate::ptrace_control::*;
7use crate::source_analysis::LineAnalysis;
8use crate::statemachine::*;
9use crate::TestHandle;
10use nix::errno::Errno;
11use nix::sys::signal::Signal;
12use nix::sys::wait::*;
13use nix::unistd::Pid;
14use nix::Error as NixErr;
15use procfs::process::{MMapPath, Process};
16use std::collections::{HashMap, HashSet};
17use std::ops::RangeBounds;
18use std::path::PathBuf;
19use tracing::{debug, info, trace, trace_span, warn};
20
21/// Handle to linux process state
22pub struct LinuxData<'a> {
23    /// Recent results from waitpid to be handled by statemachine
24    wait_queue: Vec<WaitStatus>,
25    /// Pending action queue, this is for actions where we need to wait one cycle before we can
26    /// apply them :sobs:
27    pending_actions: Vec<TracerAction<ProcessInfo>>,
28    /// Parent pid of the test
29    parent: Pid,
30    /// Current Pid to process
31    current: Pid,
32    /// Program config
33    config: &'a Config,
34    /// Optional event log to update as the test progresses
35    event_log: &'a Option<EventLog>,
36    /// Instrumentation points in code with associated coverage data
37    traces: &'a mut TraceMap,
38    /// Source analysis, needed in case we need to follow any executables
39    analysis: &'a HashMap<PathBuf, LineAnalysis>,
40    /// Processes we're tracing
41    processes: HashMap<Pid, TracedProcess>,
42    /// Map from pids to their parent
43    pid_map: HashMap<Pid, Pid>,
44    /// So if we have the exit code but we're also waiting for all the spawned processes to end
45    exit_code: Option<i32>,
46}
47
48#[derive(Debug)]
49pub struct TracedProcess {
50    /// Map of addresses to breakpoints
51    breakpoints: HashMap<u64, Breakpoint>,
52    /// Thread count. Hopefully getting rid of in future
53    thread_count: isize,
54    /// Breakpoint offset
55    offset: u64,
56    /// Instrumentation points in code with associated coverage data
57    /// If this is the root tracemap we don't use it...
58    traces: Option<TraceMap>,
59    /// Parent pid of the process
60    parent: Pid,
61    /// Whether the process is part of the test binary, or the result of an exec or fork
62    is_test_proc: bool,
63}
64
65pub fn create_state_machine<'a>(
66    test: impl Into<TestHandle>,
67    traces: &'a mut TraceMap,
68    source_analysis: &'a HashMap<PathBuf, LineAnalysis>,
69    config: &'a Config,
70    event_log: &'a Option<EventLog>,
71) -> (TestState, LinuxData<'a>) {
72    let mut data = LinuxData::new(traces, source_analysis, config, event_log);
73    let handle = test.into();
74    match handle {
75        TestHandle::Id(test) => {
76            data.parent = test;
77        }
78        _ => unreachable!("Test handle must be a PID for ptrace engine"),
79    }
80    (TestState::start_state(), data)
81}
82
83pub type UpdateContext = (TestState, TracerAction<ProcessInfo>);
84
85#[derive(Debug, Copy, Clone, Eq, PartialEq)]
86pub struct ProcessInfo {
87    pub(crate) pid: Pid,
88    pub(crate) signal: Option<Signal>,
89}
90
91impl ProcessInfo {
92    fn new(pid: Pid, signal: Option<Signal>) -> Self {
93        Self { pid, signal }
94    }
95}
96
97impl From<Pid> for ProcessInfo {
98    fn from(pid: Pid) -> Self {
99        ProcessInfo::new(pid, None)
100    }
101}
102
103impl From<&Pid> for ProcessInfo {
104    fn from(pid: &Pid) -> Self {
105        ProcessInfo::new(*pid, None)
106    }
107}
108
109fn get_offset(pid: Pid, config: &Config) -> u64 {
110    if rust_flags(config).contains("dynamic-no-pic") {
111        0
112    } else if let Ok(proc) = Process::new(pid.as_raw()) {
113        let exe = proc.exe().ok();
114        if let Ok(maps) = proc.maps() {
115            let offset_info = maps.iter().find(|x| match (&x.pathname, exe.as_ref()) {
116                (MMapPath::Path(p), Some(e)) => p == e,
117                (MMapPath::Path(_), None) => true,
118                _ => false,
119            });
120            if let Some(first) = offset_info {
121                first.address.0
122            } else {
123                0
124            }
125        } else {
126            0
127        }
128    } else {
129        0
130    }
131}
132
133impl<'a> StateData for LinuxData<'a> {
134    fn start(&mut self) -> Result<Option<TestState>, RunError> {
135        match waitpid(self.current, Some(WaitPidFlag::WNOHANG)) {
136            Ok(WaitStatus::StillAlive) => Ok(None),
137            Ok(sig @ WaitStatus::Stopped(_, Signal::SIGTRAP)) => {
138                if let WaitStatus::Stopped(child, _) = sig {
139                    self.current = child;
140                }
141                trace!("Caught inferior transitioning to Initialise state");
142                Ok(Some(TestState::Initialise))
143            }
144            Ok(_) => Err(RunError::TestRuntime(
145                "Unexpected signal when starting test".to_string(),
146            )),
147            Err(e) => Err(RunError::TestRuntime(format!(
148                "Error when starting test: {e}"
149            ))),
150        }
151    }
152
153    fn init(&mut self) -> Result<TestState, RunError> {
154        let mut traced_process = self.init_process(self.current, None)?;
155        traced_process.is_test_proc = true;
156
157        if continue_exec(traced_process.parent, None).is_ok() {
158            trace!("Initialised inferior, transitioning to wait state");
159            self.processes.insert(self.current, traced_process);
160            Ok(TestState::wait_state())
161        } else {
162            Err(RunError::TestRuntime(
163                "Test didn't launch correctly".to_string(),
164            ))
165        }
166    }
167
168    fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError> {
169        if let Some(ec) = self.exit_code {
170            let parent = self.parent;
171            for (_, process) in self.processes.iter().filter(|(k, _)| **k != parent) {
172                if let Some(tm) = process.traces.as_ref() {
173                    self.traces.merge(tm);
174                }
175            }
176            Ok(Some(TestState::End(ec)))
177        } else {
178            Ok(None)
179        }
180    }
181
182    fn wait(&mut self) -> Result<Option<TestState>, RunError> {
183        let mut result = Ok(None);
184        let mut running = true;
185        while running {
186            let wait = waitpid(
187                Pid::from_raw(-1),
188                Some(WaitPidFlag::WNOHANG | WaitPidFlag::__WALL),
189            );
190            match wait {
191                Ok(WaitStatus::StillAlive) => {
192                    running = false;
193                }
194                Ok(WaitStatus::Exited(_, _)) => {
195                    self.wait_queue.push(wait.unwrap());
196                    result = Ok(Some(TestState::Stopped));
197                    running = false;
198                }
199                Ok(WaitStatus::PtraceEvent(_, _, _)) => {
200                    self.wait_queue.push(wait.unwrap());
201                    result = Ok(Some(TestState::Stopped));
202                    running = false;
203                }
204                Ok(s) => {
205                    self.wait_queue.push(s);
206                    result = Ok(Some(TestState::Stopped));
207                }
208                Err(_) if self.exit_code.is_some() => {
209                    running = false;
210                    result = self.last_wait_attempt();
211                }
212                Err(e) => {
213                    running = false;
214                    result = Err(RunError::TestRuntime(format!(
215                        "An error occurred while waiting for response from test: {e}"
216                    )));
217                }
218            }
219        }
220        if !self.wait_queue.is_empty() {
221            trace!("Result queue is {:?}", self.wait_queue);
222        } else {
223            self.apply_pending_actions(..);
224        }
225        result
226    }
227
228    fn stop(&mut self) -> Result<TestState, RunError> {
229        let mut actions = Vec::new();
230        let mut pcs = HashMap::new();
231        let mut result = Ok(TestState::wait_state());
232        let pending = self.wait_queue.clone();
233        let pending_action_len = self.pending_actions.len();
234        self.wait_queue.clear();
235        for status in &pending {
236            let span = trace_span!("pending", event=?status.pid());
237            let _enter = span.enter();
238            if let Some(log) = self.event_log.as_ref() {
239                let offset = if let Some(process) = self.get_traced_process_mut(self.current) {
240                    process.offset
241                } else {
242                    0
243                };
244                let traces = match self.get_active_trace_map(self.current) {
245                    Some(tm) => tm,
246                    None => self.traces,
247                };
248                let event = TraceEvent::new_from_wait(status, offset, traces);
249                log.push_trace(event);
250            }
251            let state = match status {
252                WaitStatus::PtraceEvent(c, s, e) => match self.handle_ptrace_event(*c, *s, *e) {
253                    Ok(s) => Ok(s),
254                    Err(e) => Err(RunError::TestRuntime(format!(
255                        "Error occurred when handling ptrace event: {e}"
256                    ))),
257                },
258                WaitStatus::Stopped(c, Signal::SIGTRAP) => {
259                    self.current = *c;
260                    match self.collect_coverage_data(&mut pcs) {
261                        Ok(s) => Ok(s),
262                        Err(e) => Err(RunError::TestRuntime(format!(
263                            "Error when collecting coverage: {e}"
264                        ))),
265                    }
266                }
267                WaitStatus::Stopped(child, Signal::SIGSTOP) => Ok((
268                    TestState::wait_state(),
269                    TracerAction::Continue(child.into()),
270                )),
271                WaitStatus::Stopped(_, Signal::SIGSEGV) => Err(RunError::TestRuntime(
272                    "A segfault occurred while executing tests".to_string(),
273                )),
274                WaitStatus::Stopped(child, Signal::SIGILL) => {
275                    let pc = current_instruction_pointer(*child).unwrap_or(1) - 1;
276                    trace!("SIGILL raised. Child program counter is: 0x{:x}", pc);
277                    Err(RunError::TestRuntime(format!(
278                        "Error running test - SIGILL raised in {child}"
279                    )))
280                }
281                WaitStatus::Stopped(c, Signal::SIGCHLD) => {
282                    Ok((TestState::wait_state(), TracerAction::Continue(c.into())))
283                }
284                WaitStatus::Stopped(c, s) => {
285                    let sig = if self.config.forward_signals {
286                        Some(*s)
287                    } else {
288                        None
289                    };
290                    let info = ProcessInfo::new(*c, sig);
291                    Ok((TestState::wait_state(), TracerAction::TryContinue(info)))
292                }
293                WaitStatus::Signaled(c, s, f) => {
294                    if let Ok(s) = self.handle_signaled(c, s, *f) {
295                        Ok(s)
296                    } else {
297                        Err(RunError::TestRuntime(
298                            "Attempting to handle tarpaulin being signaled".to_string(),
299                        ))
300                    }
301                }
302                WaitStatus::Exited(child, ec) => {
303                    let mut parent = Pid::from_raw(0);
304                    if let Some(proc) = self.get_traced_process_mut(*child) {
305                        for ref mut value in proc.breakpoints.values_mut() {
306                            value.thread_killed(*child);
307                        }
308                        parent = proc.parent;
309                    }
310                    if &parent == child {
311                        if let Some(removed) = self.processes.remove(&parent) {
312                            if parent != self.parent {
313                                let traces = removed.traces.unwrap();
314                                self.traces.merge(&traces);
315                            }
316                        }
317                    }
318                    trace!("Exited {:?} parent {:?}", child, self.parent);
319                    if child == &self.parent {
320                        if self.processes.is_empty() || !self.config.follow_exec {
321                            Ok((TestState::End(*ec), TracerAction::Nothing))
322                        } else {
323                            self.exit_code = Some(*ec);
324                            info!("Test process exited, but spawned processes still running. Continuing tracing");
325                            Ok((TestState::wait_state(), TracerAction::Nothing))
326                        }
327                    } else {
328                        match self.exit_code {
329                            Some(ec) if self.processes.is_empty() => return Ok(TestState::End(ec)),
330                            _ => {
331                                // Process may have already been destroyed. This is just in case
332                                Ok((
333                                    TestState::wait_state(),
334                                    TracerAction::TryContinue(self.parent.into()),
335                                ))
336                            }
337                        }
338                    }
339                }
340                _ => Err(RunError::TestRuntime(
341                    "An unexpected signal has been caught by tarpaulin!".to_string(),
342                )),
343            };
344            match state {
345                Ok((TestState::Waiting { .. }, action)) => {
346                    actions.push(action);
347                }
348                Ok((state, action)) => {
349                    result = Ok(state);
350                    actions.push(action);
351                }
352                Err(e) => result = Err(e),
353            }
354        }
355        let mut continued = false;
356        let mut actioned_pids = HashSet::new();
357
358        for a in &actions {
359            if let Some(d) = a.get_data() {
360                if actioned_pids.contains(&d.pid) {
361                    trace!("Skipping action '{:?}', pid already sent command", a);
362                    continue;
363                } else {
364                    trace!("No action for {} yet", d.pid);
365                }
366            } else {
367                trace!("No process info for action");
368            }
369            trace!("Action: {:?}", a);
370            if let Some(log) = self.event_log.as_ref() {
371                let event = TraceEvent::new_from_action(a);
372                log.push_trace(event);
373            }
374            match a {
375                TracerAction::TryContinue(t) => {
376                    continued = true;
377                    actioned_pids.insert(t.pid);
378                    let _ = continue_exec(t.pid, t.signal);
379                }
380                TracerAction::Continue(t) => {
381                    continued = true;
382                    actioned_pids.insert(t.pid);
383                    continue_exec(t.pid, t.signal)?;
384                }
385                TracerAction::Step(t) => {
386                    continued = true;
387                    actioned_pids.insert(t.pid);
388                    single_step(t.pid)?;
389                }
390                TracerAction::Detach(t) => {
391                    continued = true;
392                    actioned_pids.insert(t.pid);
393                    let _ = detach_child(t.pid);
394                }
395                TracerAction::Nothing => {}
396            }
397        }
398        // Here we assume that no pending actions will exist for things that have currently
399        // signaled as stopped in this iteration. Currently, the pending actions are just fork
400        // parents that ptrace will stall until child returns so this will hold true. But if that
401        // behaviour changes in future the pending action list may need to be pruned more
402        // thoroughly
403        self.apply_pending_actions(..pending_action_len);
404
405        if !continued && self.exit_code.is_none() {
406            trace!("No action suggested to continue tracee. Attempting a continue");
407            let _ = continue_exec(self.parent, None);
408        }
409        result
410    }
411}
412
413impl<'a> LinuxData<'a> {
414    pub fn new(
415        traces: &'a mut TraceMap,
416        analysis: &'a HashMap<PathBuf, LineAnalysis>,
417        config: &'a Config,
418        event_log: &'a Option<EventLog>,
419    ) -> LinuxData<'a> {
420        LinuxData {
421            wait_queue: Vec::new(),
422            pending_actions: Vec::new(),
423            processes: HashMap::new(),
424            current: Pid::from_raw(0),
425            parent: Pid::from_raw(0),
426            traces,
427            analysis,
428            config,
429            event_log,
430            pid_map: HashMap::new(),
431            exit_code: None,
432        }
433    }
434
435    fn get_parent(&self, pid: Pid) -> Option<Pid> {
436        self.pid_map.get(&pid).copied().or_else(|| {
437            let mut parent_pid = None;
438            'outer: for k in self.processes.keys() {
439                let proc = Process::new(k.as_raw()).ok()?;
440                if let Ok(tasks) = proc.tasks() {
441                    for task in tasks.filter_map(Result::ok) {
442                        if task.tid == pid.as_raw() {
443                            parent_pid = Some(*k);
444                            break 'outer;
445                        }
446                    }
447                }
448            }
449            parent_pid
450        })
451    }
452
453    fn get_traced_process_mut(&mut self, pid: Pid) -> Option<&mut TracedProcess> {
454        let parent = self.get_parent(pid)?;
455        self.processes.get_mut(&parent)
456    }
457
458    fn get_active_trace_map_mut(&mut self, pid: Pid) -> Option<&mut TraceMap> {
459        let parent = self.get_parent(pid)?;
460        let process = self.processes.get_mut(&parent)?;
461        if process.traces.is_some() {
462            process.traces.as_mut()
463        } else {
464            Some(self.traces)
465        }
466    }
467
468    fn get_active_trace_map(&mut self, pid: Pid) -> Option<&TraceMap> {
469        let parent = self.get_parent(pid)?;
470        let process = self.processes.get(&parent)?;
471        Some(process.traces.as_ref().unwrap_or(self.traces))
472    }
473
474    fn init_process(
475        &mut self,
476        pid: Pid,
477        trace_map: Option<TraceMap>,
478    ) -> Result<TracedProcess, RunError> {
479        let traces = match trace_map.as_ref() {
480            Some(s) => s,
481            None => self.traces,
482        };
483        let mut breakpoints = HashMap::new();
484        trace_children(pid)?;
485        let offset = get_offset(pid, self.config);
486        trace!(
487            "Initialising process: {}, address offset: 0x{:x}",
488            pid,
489            offset
490        );
491        let mut clashes = HashSet::new();
492        for trace in traces.all_traces() {
493            for addr in &trace.address {
494                if clashes.contains(&align_address(*addr)) {
495                    trace!(
496                        "Skipping {} as it clashes with previously disabled breakpoints",
497                        addr
498                    );
499                    continue;
500                }
501                match Breakpoint::new(pid, *addr + offset) {
502                    Ok(bp) => {
503                        let _ = breakpoints.insert(*addr + offset, bp);
504                    }
505                    Err(Errno::EIO) => {
506                        return Err(RunError::TestRuntime(
507                            "Tarpaulin cannot find code addresses check your linker settings."
508                                .to_string(),
509                        ));
510                    }
511                    Err(NixErr::UnknownErrno) => {
512                        debug!("Instrumentation address clash, ignoring 0x{:x}", addr);
513                        // Now to avoid weird false positives lets get rid of the other breakpoint
514                        // at this address.
515                        let aligned = align_address(*addr);
516                        clashes.insert(aligned);
517                        breakpoints.retain(|address, breakpoint| {
518                            if align_address(*address - offset) == aligned {
519                                trace!("Disabling clashing breakpoint");
520                                if let Err(e) = breakpoint.disable(pid) {
521                                    error!("Unable to disable breakpoint: {}", e);
522                                }
523                                false
524                            } else {
525                                true
526                            }
527                        });
528                    }
529                    Err(_) => {
530                        return Err(RunError::TestRuntime(
531                            "Failed to instrument test executable".to_string(),
532                        ));
533                    }
534                }
535            }
536        }
537        // a processes pid is it's own parent
538        match self.pid_map.insert(pid, pid) {
539            Some(old) if old != pid => {
540                debug!("{} being promoted to parent. Old parent {}", pid, old)
541            }
542            _ => {}
543        }
544        Ok(TracedProcess {
545            parent: pid,
546            breakpoints,
547            thread_count: 0,
548            offset,
549            is_test_proc: false,
550            traces: trace_map,
551        })
552    }
553
554    fn handle_exec(
555        &mut self,
556        pid: Pid,
557    ) -> Result<(TestState, TracerAction<ProcessInfo>), RunError> {
558        trace!("Handling process exec");
559        let res = Ok((TestState::wait_state(), TracerAction::Continue(pid.into())));
560
561        if let Ok(proc) = Process::new(pid.into()) {
562            let exe = match proc.exe() {
563                Ok(e) if !e.starts_with(self.config.target_dir()) => {
564                    return Ok((TestState::wait_state(), TracerAction::Detach(pid.into())));
565                }
566                Ok(e) => e,
567                _ => return Ok((TestState::wait_state(), TracerAction::Detach(pid.into()))),
568            };
569            match generate_tracemap(&exe, self.analysis, self.config) {
570                Ok(tm) if !tm.is_empty() => match self.init_process(pid, Some(tm)) {
571                    Ok(tp) => {
572                        self.processes.insert(pid, tp);
573                        Ok((TestState::wait_state(), TracerAction::Continue(pid.into())))
574                    }
575                    Err(e) => {
576                        error!("Failed to init process (attempting continue): {}", e);
577                        res
578                    }
579                },
580                _ => {
581                    trace!("Failed to create trace map for executable, continuing");
582                    res
583                }
584            }
585        } else {
586            trace!("Failed to get process info from PID");
587            res
588        }
589    }
590
591    fn handle_ptrace_event(
592        &mut self,
593        child: Pid,
594        sig: Signal,
595        event: i32,
596    ) -> Result<(TestState, TracerAction<ProcessInfo>), RunError> {
597        use nix::libc::*;
598
599        if sig == Signal::SIGTRAP {
600            match event {
601                PTRACE_EVENT_CLONE => match get_event_data(child) {
602                    Ok(t) => {
603                        trace!("New thread spawned {}", t);
604                        let mut parent = None;
605                        if let Some(proc) = self.get_traced_process_mut(child) {
606                            proc.thread_count += 1;
607                            parent = Some(proc.parent);
608                        } else {
609                            warn!("Couldn't find parent for {}", child);
610                        }
611                        if let Some(p) = parent {
612                            self.pid_map.insert(Pid::from_raw(t as _), p);
613                        }
614                        Ok((
615                            TestState::wait_state(),
616                            TracerAction::Continue(child.into()),
617                        ))
618                    }
619                    Err(e) => {
620                        trace!("Error in clone event {:?}", e);
621                        Err(RunError::TestRuntime(
622                            "Error occurred upon test executable thread creation".to_string(),
623                        ))
624                    }
625                },
626                PTRACE_EVENT_FORK => {
627                    if let Ok(fork_child) = get_event_data(child) {
628                        trace!("Caught fork event. Child {}", fork_child);
629                        let parent = if let Some(process) = self.get_traced_process_mut(child) {
630                            // Counting a fork as a new thread ?
631                            process.thread_count += 1;
632                            Some(process.parent)
633                        } else {
634                            None
635                        };
636                        if let Some(parent) = parent {
637                            self.pid_map.insert(Pid::from_raw(fork_child as _), parent);
638                        }
639                    } else {
640                        trace!("No event data for child");
641                    }
642                    Ok((
643                        TestState::wait_state(),
644                        TracerAction::Continue(child.into()),
645                    ))
646                }
647                PTRACE_EVENT_VFORK => {
648                    // So VFORK used to be handled the same place as FORK however, from the man
649                    // page for posix_spawn:
650                    //
651                    // > [The] posix_spawn() function commences by calling clone with CLONE_VM and CLONE_VFORK flags.
652                    //
653                    // This suggests that Command::new().spawn() will result in a
654                    // `PTRACE_EVENT_VFORK` not `PTRACE_EVENT_EXEC`
655                    if let Ok(fork_child) = get_event_data(child) {
656                        let fork_child = Pid::from_raw(fork_child as _);
657                        if self.config.follow_exec {
658                            // So I've seen some recursive bin calls with vforks... Maybe just assume
659                            // every vfork is an exec :thinking:
660                            let (state, action) = self.handle_exec(fork_child)?;
661                            if self.config.forward_signals {
662                                self.pending_actions
663                                    .push(TracerAction::Continue(child.into()));
664                            }
665                            Ok((state, action))
666                        } else {
667                            Ok((
668                                TestState::wait_state(),
669                                TracerAction::Continue(child.into()),
670                            ))
671                        }
672                    } else {
673                        Ok((
674                            TestState::wait_state(),
675                            TracerAction::Continue(child.into()),
676                        ))
677                    }
678                }
679                PTRACE_EVENT_EXEC => {
680                    if self.config.follow_exec {
681                        self.handle_exec(child)
682                    } else {
683                        Ok((TestState::wait_state(), TracerAction::Detach(child.into())))
684                    }
685                }
686                PTRACE_EVENT_EXIT => {
687                    trace!("Child exiting");
688                    let mut is_parent = false;
689                    if let Some(proc) = self.get_traced_process_mut(child) {
690                        proc.thread_count -= 1;
691                        is_parent |= proc.parent == child;
692                    }
693                    if !is_parent {
694                        self.pid_map.remove(&child);
695                    }
696                    Ok((
697                        TestState::wait_state(),
698                        TracerAction::TryContinue(child.into()),
699                    ))
700                }
701                _ => Err(RunError::TestRuntime(format!(
702                    "Unrecognised ptrace event {event}"
703                ))),
704            }
705        } else {
706            trace!("Unexpected signal with ptrace event {event}");
707            trace!("Signal: {:?}", sig);
708            Err(RunError::TestRuntime("Unexpected signal".to_string()))
709        }
710    }
711
712    fn collect_coverage_data(
713        &mut self,
714        visited_pcs: &mut HashMap<Pid, HashSet<u64>>,
715    ) -> Result<UpdateContext, RunError> {
716        let mut action = None;
717        let current = self.current;
718        let enable = self.config.count;
719        let mut hits_to_increment = HashSet::new();
720        if let Some(process) = self.get_traced_process_mut(current) {
721            let visited = visited_pcs.entry(process.parent).or_default();
722            if let Ok(pc) = current_instruction_pointer(current) {
723                let pc = (pc - 1) as u64;
724                trace!("Hit address {:#x}", pc);
725                if process.breakpoints.contains_key(&pc) {
726                    let bp = process.breakpoints.get_mut(&pc).unwrap();
727                    let updated = if visited.contains(&pc) {
728                        let _ = bp.jump_to(current);
729                        (true, TracerAction::Continue(current.into()))
730                    } else {
731                        // Don't re-enable if multithreaded as can't yet sort out segfault issue
732                        if let Ok(x) = bp.process(current, enable) {
733                            x
734                        } else {
735                            // So failed to process a breakpoint.. Still continue to avoid
736                            // stalling
737                            (false, TracerAction::Continue(current.into()))
738                        }
739                    };
740                    if updated.0 {
741                        hits_to_increment.insert(pc - process.offset);
742                    }
743                    action = Some(updated.1);
744                }
745            }
746        } else {
747            warn!("Failed to find process for pid: {}", current);
748        }
749        if let Some(traces) = self.get_active_trace_map_mut(current) {
750            for addr in &hits_to_increment {
751                traces.increment_hit(*addr);
752            }
753        } else {
754            warn!("Failed to find traces for pid: {}", current);
755        }
756        let action = action.unwrap_or_else(|| TracerAction::Continue(current.into()));
757        Ok((TestState::wait_state(), action))
758    }
759
760    fn handle_signaled(
761        &mut self,
762        pid: &Pid,
763        sig: &Signal,
764        flag: bool,
765    ) -> Result<UpdateContext, RunError> {
766        let parent = self.get_parent(*pid);
767        if let Some(p) = parent {
768            if let Some(proc) = self.processes.get(&p) {
769                if !proc.is_test_proc {
770                    let info = ProcessInfo::new(*pid, Some(*sig));
771                    return Ok((TestState::wait_state(), TracerAction::TryContinue(info)));
772                }
773            }
774        }
775        match (sig, flag) {
776            (Signal::SIGKILL, _) => Ok((TestState::wait_state(), TracerAction::Detach(pid.into()))),
777            (Signal::SIGTRAP, true) => {
778                Ok((TestState::wait_state(), TracerAction::Continue(pid.into())))
779            }
780            (Signal::SIGCHLD, _) => {
781                Ok((TestState::wait_state(), TracerAction::Continue(pid.into())))
782            }
783            (Signal::SIGTERM, _) => {
784                let info = ProcessInfo {
785                    pid: *pid,
786                    signal: Some(Signal::SIGTERM),
787                };
788                Ok((TestState::wait_state(), TracerAction::TryContinue(info)))
789            }
790            _ => Err(RunError::StateMachine("Unexpected stop".to_string())),
791        }
792    }
793
794    fn apply_pending_actions(&mut self, range: impl RangeBounds<usize>) {
795        for a in self.pending_actions.drain(range) {
796            if let Some(log) = self.event_log.as_ref() {
797                let event = TraceEvent::new_from_action(&a);
798                log.push_trace(event);
799            }
800            match a {
801                TracerAction::Continue(t) | TracerAction::TryContinue(t) => {
802                    let _ = continue_exec(t.pid, t.signal);
803                }
804                e => {
805                    error!("Pending actions should only be continues: {:?}", e);
806                }
807            }
808        }
809    }
810}