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
21pub struct LinuxData<'a> {
23 wait_queue: Vec<WaitStatus>,
25 pending_actions: Vec<TracerAction<ProcessInfo>>,
28 parent: Pid,
30 current: Pid,
32 config: &'a Config,
34 event_log: &'a Option<EventLog>,
36 traces: &'a mut TraceMap,
38 analysis: &'a HashMap<PathBuf, LineAnalysis>,
40 processes: HashMap<Pid, TracedProcess>,
42 pid_map: HashMap<Pid, Pid>,
44 exit_code: Option<i32>,
46}
47
48#[derive(Debug)]
49pub struct TracedProcess {
50 breakpoints: HashMap<u64, Breakpoint>,
52 thread_count: isize,
54 offset: u64,
56 traces: Option<TraceMap>,
59 parent: Pid,
61 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 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 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 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 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 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 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 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 if let Ok(x) = bp.process(current, enable) {
733 x
734 } else {
735 (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}