• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

xd009642 / tarpaulin / #746

23 Apr 2026 05:30AM UTC coverage: 85.719% (+0.3%) from 85.46%
#746

push

web-flow
Bump rand from 0.8.5 to 0.8.6 in /tests/data/kill_proc (#1845)

Bumps [rand](https://github.com/rust-random/rand) from 0.8.5 to 0.8.6.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/0.8.6/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.8.5...0.8.6)

---
updated-dependencies:
- dependency-name: rand
  dependency-version: 0.8.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

4898 of 5714 relevant lines covered (85.72%)

250732.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.59
/src/statemachine/linux.rs
1
use crate::breakpoint::*;
2
use crate::config::Config;
3
use crate::errors::RunError;
4
use crate::generate_tracemap;
5
use crate::process_handling::event_source::EventSource;
6
use crate::process_handling::event_source::PtraceEventSource;
7
use crate::source_analysis::LineAnalysis;
8
use crate::statemachine::*;
9
use crate::TestHandle;
10
use nix::errno::Errno;
11
use nix::sys::signal::Signal;
12
use nix::sys::wait::*;
13
use nix::unistd::Pid;
14
use nix::Error as NixErr;
15
use procfs::process::*;
16
use std::collections::{HashMap, HashSet};
17
use std::ops::RangeBounds;
18
use std::path::PathBuf;
19
use std::rc::Rc;
20
use tracing::{debug, info, trace, trace_span, warn};
21

22
/// Handle to linux process state
23
pub struct LinuxData<'a> {
24
    /// Recent results from waitpid to be handled by statemachine
25
    wait_queue: Vec<WaitStatus>,
26
    /// Pending action queue, this is for actions where we need to wait one cycle before we can
27
    /// apply them :sobs:
28
    pending_actions: Vec<TracerAction<ProcessInfo>>,
29
    /// Parent pid of the test
30
    parent: Pid,
31
    /// Current Pid to process
32
    current: Pid,
33
    /// Program config
34
    config: &'a Config,
35
    /// Optional event log to update as the test progresses
36
    event_log: &'a Option<EventLog>,
37
    /// Instrumentation points in code with associated coverage data
38
    traces: &'a mut TraceMap,
39
    /// Source analysis, needed in case we need to follow any executables
40
    analysis: &'a HashMap<PathBuf, LineAnalysis>,
41
    /// Processes we're tracing
42
    processes: HashMap<Pid, TracedProcess>,
43
    /// Map from pids to their parent
44
    pid_map: HashMap<Pid, Pid>,
45
    event_source: Rc<dyn EventSource>,
46
    /// So if we have the exit code but we're also waiting for all the spawned processes to end
47
    exit_code: Option<i32>,
48
}
49

50
#[derive(Debug)]
51
pub struct TracedProcess {
52
    /// Map of addresses to breakpoints
53
    breakpoints: HashMap<u64, Breakpoint>,
54
    /// Thread count. Hopefully getting rid of in future
55
    thread_count: isize,
56
    /// Breakpoint offset
57
    offset: u64,
58
    /// Instrumentation points in code with associated coverage data
59
    /// If this is the root tracemap we don't use it...
60
    traces: Option<TraceMap>,
61
    /// Parent pid of the process
62
    parent: Pid,
63
    /// Whether the process is part of the test binary, or the result of an exec or fork
64
    is_test_proc: bool,
65
}
66

67
pub fn create_state_machine<'a>(
190✔
68
    test: impl Into<TestHandle>,
69
    traces: &'a mut TraceMap,
70
    source_analysis: &'a HashMap<PathBuf, LineAnalysis>,
71
    config: &'a Config,
72
    event_log: &'a Option<EventLog>,
73
) -> (TestState, LinuxData<'a>) {
74
    let mut data = LinuxData::new(traces, source_analysis, config, event_log);
1,140✔
75
    let handle = test.into();
570✔
76
    match handle {
190✔
77
        TestHandle::Id(test) => {
380✔
78
            data.parent = test;
190✔
79
        }
80
        _ => unreachable!("Test handle must be a PID for ptrace engine"),
81
    }
82
    (TestState::start_state(), data)
190✔
83
}
84

85
pub type UpdateContext = (TestState, TracerAction<ProcessInfo>);
86

87
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
88
pub struct ProcessInfo {
89
    pub(crate) pid: Pid,
90
    pub(crate) signal: Option<Signal>,
91
}
92

93
impl ProcessInfo {
94
    fn new(pid: Pid, signal: Option<Signal>) -> Self {
4,992✔
95
        Self { pid, signal }
96
    }
97
}
98

99
impl From<Pid> for ProcessInfo {
100
    fn from(pid: Pid) -> Self {
4,746✔
101
        ProcessInfo::new(pid, None)
14,238✔
102
    }
103
}
104

105
impl From<&Pid> for ProcessInfo {
106
    fn from(pid: &Pid) -> Self {
246✔
107
        ProcessInfo::new(*pid, None)
738✔
108
    }
109
}
110

111
impl<'a> StateData for LinuxData<'a> {
112
    fn start(&mut self) -> Result<Option<TestState>, RunError> {
190✔
113
        match self
190✔
114
            .event_source
190✔
115
            .waitpid(self.current, Some(WaitPidFlag::WNOHANG))
570✔
116
        {
117
            Ok(WaitStatus::StillAlive) => Ok(None),
×
118
            Ok(sig @ WaitStatus::Stopped(_, Signal::SIGTRAP)) => {
190✔
119
                if let WaitStatus::Stopped(child, _) = sig {
570✔
120
                    self.current = child;
190✔
121
                }
122
                trace!("Caught inferior transitioning to Initialise state");
190✔
123
                Ok(Some(TestState::Initialise))
190✔
124
            }
125
            Ok(_) => Err(RunError::TestRuntime(
×
126
                "Unexpected signal when starting test".to_string(),
×
127
            )),
128
            Err(e) => Err(RunError::TestRuntime(format!(
×
129
                "Error when starting test: {e}"
×
130
            ))),
131
        }
132
    }
133

134
    fn init(&mut self) -> Result<TestState, RunError> {
190✔
135
        let mut traced_process = self.init_process(self.current, None)?;
950✔
136
        traced_process.is_test_proc = true;
190✔
137

138
        if self
380✔
139
            .event_source
380✔
140
            .continue_pid(traced_process.parent, None)
380✔
141
            .is_ok()
142
        {
143
            trace!("Initialised inferior, transitioning to wait state");
190✔
144
            self.processes.insert(self.current, traced_process);
760✔
145
            Ok(TestState::wait_state())
190✔
146
        } else {
147
            Err(RunError::TestRuntime(
×
148
                "Test didn't launch correctly".to_string(),
×
149
            ))
150
        }
151
    }
152

153
    fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError> {
2✔
154
        if let Some(ec) = self.exit_code {
4✔
155
            let parent = self.parent;
4✔
156
            for (_, process) in self.processes.iter().filter(|(k, _)| **k != parent) {
12✔
157
                if let Some(tm) = process.traces.as_ref() {
6✔
158
                    self.traces.merge(tm);
4✔
159
                }
160
            }
161
            Ok(Some(TestState::End(ec)))
2✔
162
        } else {
163
            Ok(None)
×
164
        }
165
    }
166

167
    fn wait(&mut self) -> Result<Option<TestState>, RunError> {
1,263,522✔
168
        let mut result = self.event_source.next_events(&mut self.wait_queue);
3,790,566✔
169
        if result.is_err() && self.exit_code.is_some() {
2,527,050✔
170
            result = self.last_wait_attempt();
4✔
171
        }
172
        if !self.wait_queue.is_empty() {
1,263,522✔
173
            trace!("Result queue is {:?}", self.wait_queue);
4,500✔
174
        } else {
175
            self.apply_pending_actions(..);
2,518,044✔
176
        }
177
        result
1,263,522✔
178
    }
179

180
    fn stop(&mut self) -> Result<TestState, RunError> {
4,500✔
181
        let mut actions = Vec::new();
9,000✔
182
        let mut pcs = HashMap::new();
9,000✔
183
        let mut result = Ok(TestState::wait_state());
9,000✔
184
        let pending = self.wait_queue.clone();
13,500✔
185
        let pending_action_len = self.pending_actions.len();
13,500✔
186
        self.wait_queue.clear();
9,000✔
187
        for status in &pending {
9,660✔
188
            let span = trace_span!("pending", event=?status.pid());
10,320✔
189
            let _enter = span.enter();
15,480✔
190
            if let Some(log) = self.event_log.as_ref() {
5,160✔
191
                let offset = if let Some(process) = self.get_traced_process_mut(self.current) {
×
192
                    process.offset
×
193
                } else {
194
                    0
×
195
                };
196
                let traces = match self.get_active_trace_map(self.current) {
×
197
                    Some(tm) => tm,
×
198
                    None => self.traces,
×
199
                };
200
                let event = TraceEvent::new_from_wait(status, offset, traces);
×
201
                log.push_trace(event);
×
202
            }
203
            let state = match status {
9,212✔
204
                WaitStatus::PtraceEvent(c, s, e) => match self.handle_ptrace_event(*c, *s, *e) {
4,732✔
205
                    Ok(s) => Ok(s),
1,352✔
206
                    Err(e) => Err(RunError::TestRuntime(format!(
×
207
                        "Error occurred when handling ptrace event: {e}"
×
208
                    ))),
209
                },
210
                WaitStatus::Stopped(c, Signal::SIGTRAP) => {
3,808✔
211
                    self.current = *c;
3,808✔
212
                    match self.collect_coverage_data(&mut pcs) {
7,616✔
213
                        Ok(s) => Ok(s),
7,616✔
214
                        Err(e) => Err(RunError::TestRuntime(format!(
×
215
                            "Error when collecting coverage: {e}"
×
216
                        ))),
217
                    }
218
                }
219
                WaitStatus::Stopped(child, Signal::SIGSTOP) => Ok((
468✔
220
                    TestState::wait_state(),
468✔
221
                    TracerAction::Continue(child.into()),
234✔
222
                )),
223
                WaitStatus::Stopped(_, Signal::SIGSEGV) => Err(RunError::TestRuntime(
×
224
                    "A segfault occurred while executing tests".to_string(),
×
225
                )),
226
                WaitStatus::Stopped(child, Signal::SIGILL) => {
×
227
                    let pc = self
×
228
                        .event_source
×
229
                        .current_instruction_pointer(*child)
×
230
                        .unwrap_or(1)
×
231
                        - 1;
×
232
                    trace!("SIGILL raised. Child program counter is: 0x{:x}", pc);
×
233
                    Err(RunError::TestRuntime(format!(
×
234
                        "Error running test - SIGILL raised in {child}"
×
235
                    )))
236
                }
237
                WaitStatus::Stopped(c, Signal::SIGCHLD) => {
12✔
238
                    Ok((TestState::wait_state(), TracerAction::Continue(c.into())))
24✔
239
                }
240
                WaitStatus::Stopped(c, s) => {
×
241
                    let sig = if self.config.forward_signals {
×
242
                        Some(*s)
×
243
                    } else {
244
                        None
×
245
                    };
246
                    let info = ProcessInfo::new(*c, sig);
×
247
                    Ok((TestState::wait_state(), TracerAction::TryContinue(info)))
×
248
                }
249
                WaitStatus::Signaled(c, s, f) => {
×
250
                    if let Ok(s) = self.handle_signaled(c, s, *f) {
×
251
                        Ok(s)
×
252
                    } else {
253
                        Err(RunError::TestRuntime(
×
254
                            "Attempting to handle tarpaulin being signaled".to_string(),
×
255
                        ))
256
                    }
257
                }
258
                WaitStatus::Exited(child, ec) => {
860✔
259
                    let mut parent = Pid::from_raw(0);
860✔
260
                    if let Some(proc) = self.get_traced_process_mut(*child) {
1,058✔
261
                        for ref mut value in proc.breakpoints.values_mut() {
5,572✔
262
                            value.thread_killed(*child);
5,176✔
263
                        }
264
                        parent = proc.parent;
198✔
265
                    }
266
                    if &parent == child {
430✔
267
                        if let Some(removed) = self.processes.remove(&parent) {
588✔
268
                            if parent != self.parent {
202✔
269
                                let traces = removed.traces.unwrap();
24✔
270
                                self.traces.merge(&traces);
12✔
271
                            }
272
                        }
273
                    }
274
                    trace!("Exited {:?} parent {:?}", child, self.parent);
430✔
275
                    if child == &self.parent {
430✔
276
                        if self.processes.is_empty() || !self.config.follow_exec {
384✔
277
                            Ok((TestState::End(*ec), TracerAction::Nothing))
186✔
278
                        } else {
279
                            self.exit_code = Some(*ec);
4✔
280
                            info!("Test process exited, but spawned processes still running. Continuing tracing");
4✔
281
                            Ok((TestState::wait_state(), TracerAction::Nothing))
4✔
282
                        }
283
                    } else {
284
                        match self.exit_code {
2✔
285
                            Some(ec) if self.processes.is_empty() => return Ok(TestState::End(ec)),
10✔
286
                            _ => {
287
                                // Process may have already been destroyed. This is just in case
288
                                Ok((
238✔
289
                                    TestState::wait_state(),
476✔
290
                                    TracerAction::TryContinue(self.parent.into()),
238✔
291
                                ))
292
                            }
293
                        }
294
                    }
295
                }
296
                _ => Err(RunError::TestRuntime(
×
297
                    "An unexpected signal has been caught by tarpaulin!".to_string(),
×
298
                )),
299
            };
300
            match state {
5,158✔
301
                Ok((TestState::Waiting { .. }, action)) => {
9,944✔
302
                    actions.push(action);
9,944✔
303
                }
304
                Ok((state, action)) => {
558✔
305
                    result = Ok(state);
558✔
306
                    actions.push(action);
372✔
307
                }
308
                Err(e) => result = Err(e),
×
309
            }
310
        }
311
        let mut continued = false;
8,996✔
312
        let mut actioned_pids = HashSet::new();
8,996✔
313

314
        for a in &actions {
9,656✔
315
            if let Some(d) = a.get_data() {
10,126✔
316
                if actioned_pids.contains(&d.pid) {
14,904✔
317
                    trace!("Skipping action '{:?}', pid already sent command", a);
2✔
318
                    continue;
2✔
319
                } else {
320
                    trace!("No action for {} yet", d.pid);
4,966✔
321
                }
322
            } else {
323
                trace!("No process info for action");
190✔
324
            }
325
            trace!("Action: {:?}", a);
5,156✔
326
            if let Some(log) = self.event_log.as_ref() {
5,156✔
327
                let event = TraceEvent::new_from_action(a);
×
328
                log.push_trace(event);
×
329
            }
330
            match a {
5,156✔
331
                TracerAction::TryContinue(t) => {
1,328✔
332
                    continued = true;
1,328✔
333
                    actioned_pids.insert(t.pid);
2,656✔
334
                    let _ = self.event_source.continue_pid(t.pid, t.signal);
1,992✔
335
                }
336
                TracerAction::Continue(t) => {
2,396✔
337
                    continued = true;
2,396✔
338
                    actioned_pids.insert(t.pid);
7,188✔
339
                    self.event_source.continue_pid(t.pid, t.signal)?;
7,188✔
340
                }
341
                TracerAction::Step(t) => {
1,904✔
342
                    continued = true;
1,904✔
343
                    actioned_pids.insert(t.pid);
5,712✔
344
                    self.event_source.single_step(t.pid)?;
3,808✔
345
                }
346
                TracerAction::Detach(t) => {
4✔
347
                    continued = true;
4✔
348
                    actioned_pids.insert(t.pid);
8✔
349
                    let _ = self.event_source.detach_child(t.pid);
4✔
350
                }
351
                TracerAction::Nothing => {}
190✔
352
            }
353
        }
354
        // Here we assume that no pending actions will exist for things that have currently
355
        // signaled as stopped in this iteration. Currently, the pending actions are just fork
356
        // parents that ptrace will stall until child returns so this will hold true. But if that
357
        // behaviour changes in future the pending action list may need to be pruned more
358
        // thoroughly
359
        self.apply_pending_actions(..pending_action_len);
13,494✔
360

361
        if !continued && self.exit_code.is_none() {
4,878✔
362
            trace!("No action suggested to continue tracee. Attempting a continue");
186✔
363
            let _ = self.event_source.continue_pid(self.parent, None);
558✔
364
        }
365
        result
4,498✔
366
    }
367
}
368

369
impl<'a> LinuxData<'a> {
370
    pub fn new(
191✔
371
        traces: &'a mut TraceMap,
372
        analysis: &'a HashMap<PathBuf, LineAnalysis>,
373
        config: &'a Config,
374
        event_log: &'a Option<EventLog>,
375
    ) -> LinuxData<'a> {
376
        LinuxData {
377
            wait_queue: Vec::new(),
382✔
378
            pending_actions: Vec::new(),
382✔
379
            processes: HashMap::new(),
382✔
380
            current: Pid::from_raw(0),
382✔
381
            parent: Pid::from_raw(0),
382✔
382
            traces,
383
            analysis,
384
            config,
385
            event_log,
386
            pid_map: HashMap::new(),
382✔
387
            event_source: Rc::new(PtraceEventSource),
191✔
388
            exit_code: None,
389
        }
390
    }
391

392
    fn get_parent(&self, pid: Pid) -> Option<Pid> {
8,707✔
393
        self.pid_map.get(&pid).copied().or_else(|| {
43,768✔
394
            let mut parent_pid = None;
466✔
395
            'outer: for k in self.processes.keys() {
702✔
396
                // TODO should be in the event source stuff
397
                for tid in self.event_source.get_tids(*k) {
1,042✔
398
                    if tid == pid {
570✔
399
                        parent_pid = Some(*k);
×
400
                        break 'outer;
×
401
                    }
402
                }
403
            }
404
            if parent_pid.is_none() {
699✔
405
                parent_pid = self.event_source.get_ppid(pid);
466✔
406
            }
407
            parent_pid
233✔
408
        })
409
    }
410

411
    fn get_traced_process_mut(&mut self, pid: Pid) -> Option<&mut TracedProcess> {
4,898✔
412
        let parent = self.get_parent(pid)?;
19,592✔
413
        self.processes.get_mut(&parent)
13,998✔
414
    }
415

416
    fn get_active_trace_map_mut(&mut self, pid: Pid) -> Option<&mut TraceMap> {
3,808✔
417
        let parent = self.get_parent(pid)?;
15,232✔
418
        let process = self.processes.get_mut(&parent)?;
15,232✔
419
        if process.traces.is_some() {
7,616✔
420
            process.traces.as_mut()
104✔
421
        } else {
422
            Some(self.traces)
3,756✔
423
        }
424
    }
425

426
    fn get_active_trace_map(&mut self, pid: Pid) -> Option<&TraceMap> {
×
427
        let parent = self.get_parent(pid)?;
×
428
        let process = self.processes.get(&parent)?;
×
429
        Some(process.traces.as_ref().unwrap_or(self.traces))
×
430
    }
431

432
    fn init_process(
204✔
433
        &mut self,
434
        pid: Pid,
435
        trace_map: Option<TraceMap>,
436
    ) -> Result<TracedProcess, RunError> {
437
        let traces = match trace_map.as_ref() {
408✔
438
            Some(s) => s,
28✔
439
            None => self.traces,
190✔
440
        };
441
        let mut breakpoints = HashMap::new();
408✔
442
        self.event_source.trace_children(pid)?;
408✔
443
        let offset = self.event_source.get_offset(pid, self.config);
816✔
444
        trace!(
204✔
445
            "Initialising process: {}, address offset: 0x{:x}",
×
446
            pid,
×
447
            offset
×
448
        );
449
        let mut clashes = HashSet::new();
408✔
450
        for trace in traces.all_traces() {
1,910✔
451
            for addr in &trace.address {
4,198✔
452
                if clashes.contains(&align_address(*addr)) {
8,088✔
453
                    trace!(
32✔
454
                        "Skipping {} as it clashes with previously disabled breakpoints",
×
455
                        addr
×
456
                    );
457
                    continue;
32✔
458
                }
459
                match Breakpoint::new(pid, *addr + offset, self.event_source.clone()) {
10,656✔
460
                    Ok(bp) => {
5,188✔
461
                        let _ = breakpoints.insert(*addr + offset, bp);
7,782✔
462
                    }
463
                    Err(Errno::EIO) => {
464
                        return Err(RunError::TestRuntime(
×
465
                            "Tarpaulin cannot find code addresses check your linker settings."
×
466
                                .to_string(),
×
467
                        ));
468
                    }
469
                    Err(NixErr::UnknownErrno) => {
470
                        debug!("Instrumentation address clash, ignoring 0x{:x}", addr);
70✔
471
                        // Now to avoid weird false positives lets get rid of the other breakpoint
472
                        // at this address.
473
                        let aligned = align_address(*addr);
210✔
474
                        clashes.insert(aligned);
210✔
475
                        breakpoints.retain(|address, breakpoint| {
324✔
476
                            if align_address(*address - offset) == aligned {
368✔
477
                                trace!("Disabling clashing breakpoint");
×
478
                                if let Err(e) = breakpoint.disable(pid) {
×
479
                                    error!("Unable to disable breakpoint: {}", e);
×
480
                                }
481
                                false
×
482
                            } else {
483
                                true
184✔
484
                            }
485
                        });
486
                    }
487
                    Err(_) => {
488
                        return Err(RunError::TestRuntime(
×
489
                            "Failed to instrument test executable".to_string(),
×
490
                        ));
491
                    }
492
                }
493
            }
494
        }
495
        // a processes pid is it's own parent
496
        match self.pid_map.insert(pid, pid) {
816✔
497
            Some(old) if old != pid => {
6✔
498
                debug!("{} being promoted to parent. Old parent {}", pid, old)
×
499
            }
500
            _ => {}
204✔
501
        }
502
        Ok(TracedProcess {
204✔
503
            parent: pid,
408✔
504
            breakpoints,
408✔
505
            thread_count: 0,
204✔
506
            offset,
204✔
507
            is_test_proc: false,
204✔
508
            traces: trace_map,
204✔
509
        })
510
    }
511

512
    fn handle_exec(
16✔
513
        &mut self,
514
        pid: Pid,
515
    ) -> Result<(TestState, TracerAction<ProcessInfo>), RunError> {
516
        trace!("Handling process exec");
16✔
517
        let res = Ok((TestState::wait_state(), TracerAction::Continue(pid.into())));
48✔
518

519
        if let Ok(proc) = Process::new(pid.into()) {
48✔
520
            let exe = match proc.exe() {
46✔
521
                Ok(e) if !e.starts_with(self.config.target_dir()) => {
68✔
522
                    return Ok((TestState::wait_state(), TracerAction::Detach(pid.into())));
4✔
523
                }
524
                Ok(e) => e,
28✔
525
                _ => return Ok((TestState::wait_state(), TracerAction::Detach(pid.into()))),
×
526
            };
527
            match generate_tracemap(&exe, self.analysis, self.config) {
56✔
528
                Ok(tm) if !tm.is_empty() => match self.init_process(pid, Some(tm)) {
98✔
529
                    Ok(tp) => {
14✔
530
                        self.processes.insert(pid, tp);
56✔
531
                        Ok((TestState::wait_state(), TracerAction::Continue(pid.into())))
28✔
532
                    }
533
                    Err(e) => {
×
534
                        error!("Failed to init process (attempting continue): {}", e);
×
535
                        res
×
536
                    }
537
                },
538
                _ => {
539
                    trace!("Failed to create trace map for executable, continuing");
×
540
                    res
×
541
                }
542
            }
543
        } else {
544
            trace!("Failed to get process info from PID");
×
545
            res
×
546
        }
547
    }
548

549
    fn handle_ptrace_event(
676✔
550
        &mut self,
551
        child: Pid,
552
        sig: Signal,
553
        event: i32,
554
    ) -> Result<(TestState, TracerAction<ProcessInfo>), RunError> {
555
        use nix::libc::*;
556

557
        if sig == Signal::SIGTRAP {
676✔
558
            match event {
676✔
559
                PTRACE_EVENT_CLONE => match self.event_source.get_event_data(child) {
456✔
560
                    Ok(t) => {
228✔
561
                        trace!("New thread spawned {}", t);
228✔
562
                        let mut parent = None;
456✔
563
                        if let Some(proc) = self.get_traced_process_mut(child) {
912✔
564
                            proc.thread_count += 1;
228✔
565
                            parent = Some(proc.parent);
228✔
566
                        } else {
567
                            warn!("Couldn't find parent for {}", child);
×
568
                        }
569
                        if let Some(p) = parent {
684✔
570
                            self.pid_map.insert(Pid::from_raw(t as _), p);
912✔
571
                        }
572
                        Ok((
228✔
573
                            TestState::wait_state(),
456✔
574
                            TracerAction::Continue(child.into()),
228✔
575
                        ))
576
                    }
577
                    Err(e) => {
×
578
                        trace!("Error in clone event {:?}", e);
×
579
                        Err(RunError::TestRuntime(
×
580
                            "Error occurred upon test executable thread creation".to_string(),
×
581
                        ))
582
                    }
583
                },
584
                PTRACE_EVENT_FORK => {
585
                    if let Ok(fork_child) = self.event_source.get_event_data(child) {
18✔
586
                        trace!("Caught fork event. Child {}", fork_child);
6✔
587
                        let parent = if let Some(process) = self.get_traced_process_mut(child) {
24✔
588
                            // Counting a fork as a new thread ?
589
                            process.thread_count += 1;
6✔
590
                            Some(process.parent)
6✔
591
                        } else {
592
                            None
×
593
                        };
594
                        if let Some(parent) = parent {
18✔
595
                            self.pid_map.insert(Pid::from_raw(fork_child as _), parent);
24✔
596
                        }
597
                    } else {
598
                        trace!("No event data for child");
×
599
                    }
600
                    Ok((
6✔
601
                        TestState::wait_state(),
12✔
602
                        TracerAction::Continue(child.into()),
6✔
603
                    ))
604
                }
605
                PTRACE_EVENT_VFORK => {
606
                    // So VFORK used to be handled the same place as FORK however, from the man
607
                    // page for posix_spawn:
608
                    //
609
                    // > [The] posix_spawn() function commences by calling clone with CLONE_VM and CLONE_VFORK flags.
610
                    //
611
                    // This suggests that Command::new().spawn() will result in a
612
                    // `PTRACE_EVENT_VFORK` not `PTRACE_EVENT_EXEC`
613
                    if let Ok(fork_child) = self.event_source.get_event_data(child) {
24✔
614
                        let fork_child = Pid::from_raw(fork_child as _);
24✔
615
                        if self.config.follow_exec {
8✔
616
                            // So I've seen some recursive bin calls with vforks... Maybe just assume
617
                            // every vfork is an exec :thinking:
618
                            let (state, action) = self.handle_exec(fork_child)?;
40✔
619
                            if self.config.forward_signals {
16✔
620
                                self.pending_actions
16✔
621
                                    .push(TracerAction::Continue(child.into()));
16✔
622
                            }
623
                            Ok((state, action))
8✔
624
                        } else {
625
                            Ok((
×
626
                                TestState::wait_state(),
×
627
                                TracerAction::Continue(child.into()),
×
628
                            ))
629
                        }
630
                    } else {
631
                        Ok((
×
632
                            TestState::wait_state(),
×
633
                            TracerAction::Continue(child.into()),
×
634
                        ))
635
                    }
636
                }
637
                PTRACE_EVENT_EXEC => {
638
                    if self.config.follow_exec {
8✔
639
                        self.handle_exec(child)
24✔
640
                    } else {
641
                        Ok((TestState::wait_state(), TracerAction::Detach(child.into())))
×
642
                    }
643
                }
644
                PTRACE_EVENT_EXIT => {
645
                    trace!("Child exiting");
426✔
646
                    let mut is_parent = false;
852✔
647
                    if let Some(proc) = self.get_traced_process_mut(child) {
1,704✔
648
                        proc.thread_count -= 1;
852✔
649
                        is_parent |= proc.parent == child;
426✔
650
                    }
651
                    if !is_parent {
658✔
652
                        self.pid_map.remove(&child);
464✔
653
                    }
654
                    Ok((
426✔
655
                        TestState::wait_state(),
852✔
656
                        TracerAction::TryContinue(child.into()),
426✔
657
                    ))
658
                }
659
                _ => Err(RunError::TestRuntime(format!(
×
660
                    "Unrecognised ptrace event {event}"
×
661
                ))),
662
            }
663
        } else {
664
            trace!("Unexpected signal with ptrace event {event}");
×
665
            trace!("Signal: {:?}", sig);
×
666
            Err(RunError::TestRuntime("Unexpected signal".to_string()))
×
667
        }
668
    }
669

670
    fn collect_coverage_data(
3,808✔
671
        &mut self,
672
        visited_pcs: &mut HashMap<Pid, HashSet<u64>>,
673
    ) -> Result<UpdateContext, RunError> {
674
        let mut action = None;
7,616✔
675
        let current = self.current;
7,616✔
676
        let enable = self.config.count;
7,616✔
677
        let mut hits_to_increment = HashSet::new();
7,616✔
678
        let current_rip = self.event_source.current_instruction_pointer(current);
11,424✔
679
        if let Some(process) = self.get_traced_process_mut(current) {
11,424✔
680
            let visited = visited_pcs.entry(process.parent).or_default();
19,040✔
681
            if let Ok(pc) = current_rip {
7,616✔
682
                let pc = (pc - 1) as u64;
7,616✔
683
                trace!("Hit address {:#x}", pc);
3,808✔
684
                if process.breakpoints.contains_key(&pc) {
11,424✔
685
                    let bp = process.breakpoints.get_mut(&pc).unwrap();
10,040✔
686
                    let updated = if visited.contains(&pc) {
8,032✔
687
                        let _ = bp.jump_to(current);
×
688
                        (true, TracerAction::Continue(current.into()))
×
689
                    } else {
690
                        // Don't re-enable if multithreaded as can't yet sort out segfault issue
691
                        if let Ok(x) = bp.process(current, enable) {
8,032✔
692
                            x
2,008✔
693
                        } else {
694
                            // So failed to process a breakpoint.. Still continue to avoid
695
                            // stalling
696
                            (false, TracerAction::Continue(current.into()))
×
697
                        }
698
                    };
699
                    if updated.0 {
3,912✔
700
                        hits_to_increment.insert(pc - process.offset);
5,712✔
701
                    }
702
                    action = Some(updated.1);
2,008✔
703
                }
704
            }
705
        } else {
706
            warn!("Failed to find process for pid: {}", current);
×
707
        }
708
        if let Some(traces) = self.get_active_trace_map_mut(current) {
11,424✔
709
            for addr in &hits_to_increment {
7,616✔
710
                traces.increment_hit(*addr);
3,808✔
711
            }
712
        } else {
713
            warn!("Failed to find traces for pid: {}", current);
×
714
        }
715
        let action = action.unwrap_or_else(|| TracerAction::Continue(current.into()));
15,024✔
716
        Ok((TestState::wait_state(), action))
3,808✔
717
    }
718

719
    fn handle_signaled(
×
720
        &mut self,
721
        pid: &Pid,
722
        sig: &Signal,
723
        flag: bool,
724
    ) -> Result<UpdateContext, RunError> {
725
        let parent = self.get_parent(*pid);
×
726
        if let Some(p) = parent {
×
727
            if let Some(proc) = self.processes.get(&p) {
×
728
                if !proc.is_test_proc {
×
729
                    let info = ProcessInfo::new(*pid, Some(*sig));
×
730
                    return Ok((TestState::wait_state(), TracerAction::TryContinue(info)));
×
731
                }
732
            }
733
        }
734
        match (sig, flag) {
×
735
            (Signal::SIGKILL, _) => Ok((TestState::wait_state(), TracerAction::Detach(pid.into()))),
×
736
            (Signal::SIGTRAP, true) => {
737
                Ok((TestState::wait_state(), TracerAction::Continue(pid.into())))
×
738
            }
739
            (Signal::SIGCHLD, _) => {
740
                Ok((TestState::wait_state(), TracerAction::Continue(pid.into())))
×
741
            }
742
            (Signal::SIGTERM, _) => {
743
                let info = ProcessInfo {
744
                    pid: *pid,
×
745
                    signal: Some(Signal::SIGTERM),
×
746
                };
747
                Ok((TestState::wait_state(), TracerAction::TryContinue(info)))
×
748
            }
749
            _ => Err(RunError::StateMachine("Unexpected stop".to_string())),
×
750
        }
751
    }
752

753
    fn apply_pending_actions(&mut self, range: impl RangeBounds<usize>) {
1,263,520✔
754
        for a in self.pending_actions.drain(range) {
3,790,568✔
755
            if let Some(log) = self.event_log.as_ref() {
8✔
756
                let event = TraceEvent::new_from_action(&a);
×
757
                log.push_trace(event);
×
758
            }
759
            match a {
8✔
760
                TracerAction::Continue(t) | TracerAction::TryContinue(t) => {
16✔
761
                    let _ = self.event_source.continue_pid(t.pid, t.signal);
24✔
762
                }
763
                e => {
×
764
                    error!("Pending actions should only be continues: {:?}", e);
×
765
                }
766
            }
767
        }
768
    }
769
}
770

771
#[cfg(test)]
772
mod tests {
773
    use super::*;
774
    use nix::unistd::*;
775
    use std::time::Duration;
776

777
    #[test]
778
    fn fork_parent_identified() {
2✔
779
        match unsafe { fork() } {
2✔
780
            Ok(ForkResult::Parent { child }) => {
1✔
781
                let mut tracemap = TraceMap::new();
2✔
782
                let analysis = HashMap::new();
2✔
783
                let config = Config::default();
2✔
784
                let data = LinuxData::new(&mut tracemap, &analysis, &config, &None);
6✔
785

786
                assert_eq!(data.get_parent(child), Some(Pid::this()));
5✔
787
                //child
788
            }
789
            Ok(ForkResult::Child) => {
1✔
790
                std::thread::sleep(Duration::from_secs(5));
1✔
791
            }
792
            Err(e) => panic!("Fork failed: {}", e),
×
793
        }
794
    }
795
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc