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

xd009642 / tarpaulin / #695

24 Oct 2025 10:24PM UTC coverage: 84.84% (+0.5%) from 84.378%
#695

push

web-flow
Remove some dead tests (benchmark coverage) (#1789)

4600 of 5422 relevant lines covered (84.84%)

256056.06 hits per line

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

66.36
/src/statemachine/linux.rs
1
use crate::breakpoint::*;
2
use crate::cargo::rust_flags;
3
use crate::config::Config;
4
use crate::errors::RunError;
5
use crate::generate_tracemap;
6
use crate::ptrace_control::*;
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::{MMapPath, Process};
16
use std::collections::{HashMap, HashSet};
17
use std::ops::RangeBounds;
18
use std::path::PathBuf;
19
use tracing::{debug, info, trace, trace_span, warn};
20

21
/// Handle to linux process state
22
pub 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)]
49
pub 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

65
pub fn create_state_machine<'a>(
186✔
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);
1,116✔
73
    let handle = test.into();
558✔
74
    match handle {
186✔
75
        TestHandle::Id(test) => {
372✔
76
            data.parent = test;
186✔
77
        }
78
        _ => unreachable!("Test handle must be a PID for ptrace engine"),
79
    }
80
    (TestState::start_state(), data)
186✔
81
}
82

83
pub type UpdateContext = (TestState, TracerAction<ProcessInfo>);
84

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

91
impl ProcessInfo {
92
    fn new(pid: Pid, signal: Option<Signal>) -> Self {
3,772✔
93
        Self { pid, signal }
94
    }
95
}
96

97
impl From<Pid> for ProcessInfo {
98
    fn from(pid: Pid) -> Self {
3,544✔
99
        ProcessInfo::new(pid, None)
10,632✔
100
    }
101
}
102

103
impl From<&Pid> for ProcessInfo {
104
    fn from(pid: &Pid) -> Self {
228✔
105
        ProcessInfo::new(*pid, None)
684✔
106
    }
107
}
108

109
fn get_offset(pid: Pid, config: &Config) -> u64 {
194✔
110
    // TODO I don't think this would be an issue... but I'll test it
111
    if rust_flags(config, &Default::default()).contains("dynamic-no-pic") {
388✔
112
        0
×
113
    } else if let Ok(proc) = Process::new(pid.as_raw()) {
582✔
114
        let exe = proc.exe().ok();
776✔
115
        if let Ok(maps) = proc.maps() {
388✔
116
            let offset_info = maps.iter().find(|x| match (&x.pathname, exe.as_ref()) {
1,164✔
117
                (MMapPath::Path(p), Some(e)) => p == e,
582✔
118
                (MMapPath::Path(_), None) => true,
×
119
                _ => false,
×
120
            });
121
            if let Some(first) = offset_info {
388✔
122
                first.address.0
194✔
123
            } else {
124
                0
×
125
            }
126
        } else {
127
            0
×
128
        }
129
    } else {
130
        0
×
131
    }
132
}
133

134
impl<'a> StateData for LinuxData<'a> {
135
    fn start(&mut self) -> Result<Option<TestState>, RunError> {
186✔
136
        match waitpid(self.current, Some(WaitPidFlag::WNOHANG)) {
558✔
137
            Ok(WaitStatus::StillAlive) => Ok(None),
×
138
            Ok(sig @ WaitStatus::Stopped(_, Signal::SIGTRAP)) => {
186✔
139
                if let WaitStatus::Stopped(child, _) = sig {
558✔
140
                    self.current = child;
186✔
141
                }
142
                trace!("Caught inferior transitioning to Initialise state");
186✔
143
                Ok(Some(TestState::Initialise))
186✔
144
            }
145
            Ok(_) => Err(RunError::TestRuntime(
×
146
                "Unexpected signal when starting test".to_string(),
×
147
            )),
148
            Err(e) => Err(RunError::TestRuntime(format!(
×
149
                "Error when starting test: {e}"
×
150
            ))),
151
        }
152
    }
153

154
    fn init(&mut self) -> Result<TestState, RunError> {
186✔
155
        let mut traced_process = self.init_process(self.current, None)?;
930✔
156
        traced_process.is_test_proc = true;
186✔
157

158
        if continue_exec(traced_process.parent, None).is_ok() {
558✔
159
            trace!("Initialised inferior, transitioning to wait state");
186✔
160
            self.processes.insert(self.current, traced_process);
744✔
161
            Ok(TestState::wait_state())
186✔
162
        } else {
163
            Err(RunError::TestRuntime(
×
164
                "Test didn't launch correctly".to_string(),
×
165
            ))
166
        }
167
    }
168

169
    fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError> {
×
170
        if let Some(ec) = self.exit_code {
×
171
            let parent = self.parent;
×
172
            for (_, process) in self.processes.iter().filter(|(k, _)| **k != parent) {
×
173
                if let Some(tm) = process.traces.as_ref() {
×
174
                    self.traces.merge(tm);
×
175
                }
176
            }
177
            Ok(Some(TestState::End(ec)))
×
178
        } else {
179
            Ok(None)
×
180
        }
181
    }
182

183
    fn wait(&mut self) -> Result<Option<TestState>, RunError> {
1,832,902✔
184
        let mut result = Ok(None);
3,665,804✔
185
        let mut running = true;
3,665,804✔
186
        while running {
3,668,692✔
187
            let wait = waitpid(
188
                Pid::from_raw(-1),
1,835,790✔
189
                Some(WaitPidFlag::WNOHANG | WaitPidFlag::__WALL),
1,835,790✔
190
            );
191
            match wait {
1,835,790✔
192
                Ok(WaitStatus::StillAlive) => {
1,831,848✔
193
                    running = false;
1,831,848✔
194
                }
195
                Ok(WaitStatus::Exited(_, _)) => {
412✔
196
                    self.wait_queue.push(wait.unwrap());
2,060✔
197
                    result = Ok(Some(TestState::Stopped));
824✔
198
                    running = false;
412✔
199
                }
200
                Ok(WaitStatus::PtraceEvent(_, _, _)) => {
642✔
201
                    self.wait_queue.push(wait.unwrap());
3,210✔
202
                    result = Ok(Some(TestState::Stopped));
1,284✔
203
                    running = false;
642✔
204
                }
205
                Ok(s) => {
5,776✔
206
                    self.wait_queue.push(s);
11,552✔
207
                    result = Ok(Some(TestState::Stopped));
2,888✔
208
                }
209
                Err(_) if self.exit_code.is_some() => {
×
210
                    running = false;
×
211
                    result = self.last_wait_attempt();
×
212
                }
213
                Err(e) => {
×
214
                    running = false;
×
215
                    result = Err(RunError::TestRuntime(format!(
×
216
                        "An error occurred while waiting for response from test: {e}"
×
217
                    )));
218
                }
219
            }
220
        }
221
        if !self.wait_queue.is_empty() {
1,832,902✔
222
            trace!("Result queue is {:?}", self.wait_queue);
3,704✔
223
        } else {
224
            self.apply_pending_actions(..);
3,658,396✔
225
        }
226
        result
1,832,902✔
227
    }
228

229
    fn stop(&mut self) -> Result<TestState, RunError> {
3,704✔
230
        let mut actions = Vec::new();
7,408✔
231
        let mut pcs = HashMap::new();
7,408✔
232
        let mut result = Ok(TestState::wait_state());
7,408✔
233
        let pending = self.wait_queue.clone();
11,112✔
234
        let pending_action_len = self.pending_actions.len();
11,112✔
235
        self.wait_queue.clear();
7,408✔
236
        for status in &pending {
11,586✔
237
            let span = trace_span!("pending", event=?status.pid());
7,884✔
238
            let _enter = span.enter();
11,826✔
239
            if let Some(log) = self.event_log.as_ref() {
3,942✔
240
                let offset = if let Some(process) = self.get_traced_process_mut(self.current) {
×
241
                    process.offset
×
242
                } else {
243
                    0
×
244
                };
245
                let traces = match self.get_active_trace_map(self.current) {
×
246
                    Some(tm) => tm,
×
247
                    None => self.traces,
×
248
                };
249
                let event = TraceEvent::new_from_wait(status, offset, traces);
×
250
                log.push_trace(event);
×
251
            }
252
            let state = match status {
6,828✔
253
                WaitStatus::PtraceEvent(c, s, e) => match self.handle_ptrace_event(*c, *s, *e) {
4,494✔
254
                    Ok(s) => Ok(s),
1,284✔
255
                    Err(e) => Err(RunError::TestRuntime(format!(
×
256
                        "Error occurred when handling ptrace event: {e}"
×
257
                    ))),
258
                },
259
                WaitStatus::Stopped(c, Signal::SIGTRAP) => {
2,660✔
260
                    self.current = *c;
2,660✔
261
                    match self.collect_coverage_data(&mut pcs) {
5,320✔
262
                        Ok(s) => Ok(s),
5,320✔
263
                        Err(e) => Err(RunError::TestRuntime(format!(
×
264
                            "Error when collecting coverage: {e}"
×
265
                        ))),
266
                    }
267
                }
268
                WaitStatus::Stopped(child, Signal::SIGSTOP) => Ok((
432✔
269
                    TestState::wait_state(),
432✔
270
                    TracerAction::Continue(child.into()),
216✔
271
                )),
272
                WaitStatus::Stopped(_, Signal::SIGSEGV) => Err(RunError::TestRuntime(
×
273
                    "A segfault occurred while executing tests".to_string(),
×
274
                )),
275
                WaitStatus::Stopped(child, Signal::SIGILL) => {
×
276
                    let pc = current_instruction_pointer(*child).unwrap_or(1) - 1;
×
277
                    trace!("SIGILL raised. Child program counter is: 0x{:x}", pc);
×
278
                    Err(RunError::TestRuntime(format!(
×
279
                        "Error running test - SIGILL raised in {child}"
×
280
                    )))
281
                }
282
                WaitStatus::Stopped(c, Signal::SIGCHLD) => {
12✔
283
                    Ok((TestState::wait_state(), TracerAction::Continue(c.into())))
24✔
284
                }
285
                WaitStatus::Stopped(c, s) => {
×
286
                    let sig = if self.config.forward_signals {
×
287
                        Some(*s)
×
288
                    } else {
289
                        None
×
290
                    };
291
                    let info = ProcessInfo::new(*c, sig);
×
292
                    Ok((TestState::wait_state(), TracerAction::TryContinue(info)))
×
293
                }
294
                WaitStatus::Signaled(c, s, f) => {
×
295
                    if let Ok(s) = self.handle_signaled(c, s, *f) {
×
296
                        Ok(s)
×
297
                    } else {
298
                        Err(RunError::TestRuntime(
×
299
                            "Attempting to handle tarpaulin being signaled".to_string(),
×
300
                        ))
301
                    }
302
                }
303
                WaitStatus::Exited(child, ec) => {
824✔
304
                    let mut parent = Pid::from_raw(0);
824✔
305
                    if let Some(proc) = self.get_traced_process_mut(*child) {
1,016✔
306
                        for ref mut value in proc.breakpoints.values_mut() {
4,080✔
307
                            value.thread_killed(*child);
3,696✔
308
                        }
309
                        parent = proc.parent;
192✔
310
                    }
311
                    if &parent == child {
412✔
312
                        if let Some(removed) = self.processes.remove(&parent) {
576✔
313
                            if parent != self.parent {
198✔
314
                                let traces = removed.traces.unwrap();
24✔
315
                                self.traces.merge(&traces);
12✔
316
                            }
317
                        }
318
                    }
319
                    trace!("Exited {:?} parent {:?}", child, self.parent);
412✔
320
                    if child == &self.parent {
412✔
321
                        if self.processes.is_empty() || !self.config.follow_exec {
374✔
322
                            Ok((TestState::End(*ec), TracerAction::Nothing))
184✔
323
                        } else {
324
                            self.exit_code = Some(*ec);
2✔
325
                            info!("Test process exited, but spawned processes still running. Continuing tracing");
4✔
326
                            Ok((TestState::wait_state(), TracerAction::Nothing))
2✔
327
                        }
328
                    } else {
329
                        match self.exit_code {
2✔
330
                            Some(ec) if self.processes.is_empty() => return Ok(TestState::End(ec)),
10✔
331
                            _ => {
×
332
                                // Process may have already been destroyed. This is just in case
333
                                Ok((
224✔
334
                                    TestState::wait_state(),
448✔
335
                                    TracerAction::TryContinue(self.parent.into()),
224✔
336
                                ))
337
                            }
338
                        }
339
                    }
340
                }
341
                _ => Err(RunError::TestRuntime(
×
342
                    "An unexpected signal has been caught by tarpaulin!".to_string(),
×
343
                )),
344
            };
345
            match state {
3,940✔
346
                Ok((TestState::Waiting { .. }, action)) => {
7,512✔
347
                    actions.push(action);
7,512✔
348
                }
349
                Ok((state, action)) => {
552✔
350
                    result = Ok(state);
552✔
351
                    actions.push(action);
368✔
352
                }
353
                Err(e) => result = Err(e),
×
354
            }
355
        }
356
        let mut continued = false;
7,404✔
357
        let mut actioned_pids = HashSet::new();
7,404✔
358

359
        for a in &actions {
11,582✔
360
            if let Some(d) = a.get_data() {
7,694✔
361
                if actioned_pids.contains(&d.pid) {
11,262✔
362
                    trace!("Skipping action '{:?}', pid already sent command", a);
×
363
                    continue;
×
364
                } else {
365
                    trace!("No action for {} yet", d.pid);
3,754✔
366
                }
367
            } else {
368
                trace!("No process info for action");
186✔
369
            }
370
            trace!("Action: {:?}", a);
3,940✔
371
            if let Some(log) = self.event_log.as_ref() {
3,940✔
372
                let event = TraceEvent::new_from_action(a);
×
373
                log.push_trace(event);
×
374
            }
375
            match a {
3,940✔
376
                TracerAction::TryContinue(t) => {
1,260✔
377
                    continued = true;
1,260✔
378
                    actioned_pids.insert(t.pid);
2,520✔
379
                    let _ = continue_exec(t.pid, t.signal);
1,260✔
380
                }
381
                TracerAction::Continue(t) => {
1,792✔
382
                    continued = true;
1,792✔
383
                    actioned_pids.insert(t.pid);
5,376✔
384
                    continue_exec(t.pid, t.signal)?;
5,376✔
385
                }
386
                TracerAction::Step(t) => {
1,330✔
387
                    continued = true;
1,330✔
388
                    actioned_pids.insert(t.pid);
3,990✔
389
                    single_step(t.pid)?;
2,660✔
390
                }
391
                TracerAction::Detach(t) => {
4✔
392
                    continued = true;
4✔
393
                    actioned_pids.insert(t.pid);
8✔
394
                    let _ = detach_child(t.pid);
2✔
395
                }
396
                TracerAction::Nothing => {}
186✔
397
            }
398
        }
399
        // Here we assume that no pending actions will exist for things that have currently
400
        // signaled as stopped in this iteration. Currently, the pending actions are just fork
401
        // parents that ptrace will stall until child returns so this will hold true. But if that
402
        // behaviour changes in future the pending action list may need to be pruned more
403
        // thoroughly
404
        self.apply_pending_actions(..pending_action_len);
11,106✔
405

406
        if !continued && self.exit_code.is_none() {
4,074✔
407
            trace!("No action suggested to continue tracee. Attempting a continue");
184✔
408
            let _ = continue_exec(self.parent, None);
368✔
409
        }
410
        result
3,702✔
411
    }
412
}
413

414
impl<'a> LinuxData<'a> {
415
    pub fn new(
186✔
416
        traces: &'a mut TraceMap,
417
        analysis: &'a HashMap<PathBuf, LineAnalysis>,
418
        config: &'a Config,
419
        event_log: &'a Option<EventLog>,
420
    ) -> LinuxData<'a> {
421
        LinuxData {
422
            wait_queue: Vec::new(),
372✔
423
            pending_actions: Vec::new(),
372✔
424
            processes: HashMap::new(),
372✔
425
            current: Pid::from_raw(0),
372✔
426
            parent: Pid::from_raw(0),
372✔
427
            traces,
428
            analysis,
429
            config,
430
            event_log,
431
            pid_map: HashMap::new(),
186✔
432
            exit_code: None,
433
        }
434
    }
435

436
    fn get_parent(&self, pid: Pid) -> Option<Pid> {
6,358✔
437
        self.pid_map.get(&pid).copied().or_else(|| {
32,010✔
438
            let mut parent_pid = None;
440✔
439
            'outer: for k in self.processes.keys() {
660✔
440
                let proc = Process::new(k.as_raw()).ok()?;
1,100✔
441
                if let Ok(tasks) = proc.tasks() {
440✔
442
                    for task in tasks.filter_map(Result::ok) {
956✔
443
                        if task.tid == pid.as_raw() {
1,032✔
444
                            parent_pid = Some(*k);
×
445
                            break 'outer;
×
446
                        }
447
                    }
448
                }
449
            }
450
            parent_pid
220✔
451
        })
452
    }
453

454
    fn get_traced_process_mut(&mut self, pid: Pid) -> Option<&mut TracedProcess> {
3,698✔
455
        let parent = self.get_parent(pid)?;
14,792✔
456
        self.processes.get_mut(&parent)
10,434✔
457
    }
458

459
    fn get_active_trace_map_mut(&mut self, pid: Pid) -> Option<&mut TraceMap> {
2,660✔
460
        let parent = self.get_parent(pid)?;
10,640✔
461
        let process = self.processes.get_mut(&parent)?;
10,640✔
462
        if process.traces.is_some() {
5,320✔
463
            process.traces.as_mut()
104✔
464
        } else {
465
            Some(self.traces)
2,608✔
466
        }
467
    }
468

469
    fn get_active_trace_map(&mut self, pid: Pid) -> Option<&TraceMap> {
×
470
        let parent = self.get_parent(pid)?;
×
471
        let process = self.processes.get(&parent)?;
×
472
        Some(process.traces.as_ref().unwrap_or(self.traces))
×
473
    }
474

475
    fn init_process(
194✔
476
        &mut self,
477
        pid: Pid,
478
        trace_map: Option<TraceMap>,
479
    ) -> Result<TracedProcess, RunError> {
480
        let traces = match trace_map.as_ref() {
388✔
481
            Some(s) => s,
16✔
482
            None => self.traces,
186✔
483
        };
484
        let mut breakpoints = HashMap::new();
388✔
485
        trace_children(pid)?;
388✔
486
        let offset = get_offset(pid, self.config);
776✔
487
        trace!(
194✔
488
            "Initialising process: {}, address offset: 0x{:x}",
×
489
            pid,
×
490
            offset
×
491
        );
492
        let mut clashes = HashSet::new();
388✔
493
        for trace in traces.all_traces() {
1,582✔
494
            for addr in &trace.address {
4,954✔
495
                if clashes.contains(&align_address(*addr)) {
5,640✔
496
                    trace!(
2✔
497
                        "Skipping {} as it clashes with previously disabled breakpoints",
×
498
                        addr
×
499
                    );
500
                    continue;
2✔
501
                }
502
                match Breakpoint::new(pid, *addr + offset) {
5,634✔
503
                    Ok(bp) => {
3,724✔
504
                        let _ = breakpoints.insert(*addr + offset, bp);
5,586✔
505
                    }
506
                    Err(Errno::EIO) => {
×
507
                        return Err(RunError::TestRuntime(
×
508
                            "Tarpaulin cannot find code addresses check your linker settings."
×
509
                                .to_string(),
×
510
                        ));
511
                    }
512
                    Err(NixErr::UnknownErrno) => {
×
513
                        debug!("Instrumentation address clash, ignoring 0x{:x}", addr);
16✔
514
                        // Now to avoid weird false positives lets get rid of the other breakpoint
515
                        // at this address.
516
                        let aligned = align_address(*addr);
48✔
517
                        clashes.insert(aligned);
48✔
518
                        breakpoints.retain(|address, breakpoint| {
110✔
519
                            if align_address(*address - offset) == aligned {
156✔
520
                                trace!("Disabling clashing breakpoint");
×
521
                                if let Err(e) = breakpoint.disable(pid) {
×
522
                                    error!("Unable to disable breakpoint: {}", e);
×
523
                                }
524
                                false
×
525
                            } else {
526
                                true
78✔
527
                            }
528
                        });
529
                    }
530
                    Err(_) => {
×
531
                        return Err(RunError::TestRuntime(
×
532
                            "Failed to instrument test executable".to_string(),
×
533
                        ));
534
                    }
535
                }
536
            }
537
        }
538
        // a processes pid is it's own parent
539
        match self.pid_map.insert(pid, pid) {
776✔
540
            Some(old) if old != pid => {
2✔
541
                debug!("{} being promoted to parent. Old parent {}", pid, old)
×
542
            }
543
            _ => {}
194✔
544
        }
545
        Ok(TracedProcess {
194✔
546
            parent: pid,
388✔
547
            breakpoints,
388✔
548
            thread_count: 0,
194✔
549
            offset,
194✔
550
            is_test_proc: false,
194✔
551
            traces: trace_map,
194✔
552
        })
553
    }
554

555
    fn handle_exec(
16✔
556
        &mut self,
557
        pid: Pid,
558
    ) -> Result<(TestState, TracerAction<ProcessInfo>), RunError> {
559
        trace!("Handling process exec");
16✔
560
        let res = Ok((TestState::wait_state(), TracerAction::Continue(pid.into())));
48✔
561

562
        if let Ok(proc) = Process::new(pid.into()) {
48✔
563
            let exe = match proc.exe() {
46✔
564
                Ok(e) if !e.starts_with(self.config.target_dir()) => {
68✔
565
                    return Ok((TestState::wait_state(), TracerAction::Detach(pid.into())));
4✔
566
                }
567
                Ok(e) => e,
28✔
568
                _ => return Ok((TestState::wait_state(), TracerAction::Detach(pid.into()))),
×
569
            };
570
            match generate_tracemap(&exe, self.analysis, self.config) {
56✔
571
                Ok(tm) if !tm.is_empty() => match self.init_process(pid, Some(tm)) {
68✔
572
                    Ok(tp) => {
8✔
573
                        self.processes.insert(pid, tp);
32✔
574
                        Ok((TestState::wait_state(), TracerAction::Continue(pid.into())))
16✔
575
                    }
576
                    Err(e) => {
×
577
                        error!("Failed to init process (attempting continue): {}", e);
×
578
                        res
×
579
                    }
580
                },
581
                _ => {
×
582
                    trace!("Failed to create trace map for executable, continuing");
6✔
583
                    res
6✔
584
                }
585
            }
586
        } else {
587
            trace!("Failed to get process info from PID");
×
588
            res
×
589
        }
590
    }
591

592
    fn handle_ptrace_event(
642✔
593
        &mut self,
594
        child: Pid,
595
        sig: Signal,
596
        event: i32,
597
    ) -> Result<(TestState, TracerAction<ProcessInfo>), RunError> {
598
        use nix::libc::*;
599

600
        if sig == Signal::SIGTRAP {
642✔
601
            match event {
642✔
602
                PTRACE_EVENT_CLONE => match get_event_data(child) {
214✔
603
                    Ok(t) => {
214✔
604
                        trace!("New thread spawned {}", t);
214✔
605
                        let mut parent = None;
428✔
606
                        if let Some(proc) = self.get_traced_process_mut(child) {
856✔
607
                            proc.thread_count += 1;
214✔
608
                            parent = Some(proc.parent);
214✔
609
                        } else {
610
                            warn!("Couldn't find parent for {}", child);
×
611
                        }
612
                        if let Some(p) = parent {
642✔
613
                            self.pid_map.insert(Pid::from_raw(t as _), p);
856✔
614
                        }
615
                        Ok((
214✔
616
                            TestState::wait_state(),
428✔
617
                            TracerAction::Continue(child.into()),
214✔
618
                        ))
619
                    }
620
                    Err(e) => {
×
621
                        trace!("Error in clone event {:?}", e);
×
622
                        Err(RunError::TestRuntime(
×
623
                            "Error occurred upon test executable thread creation".to_string(),
×
624
                        ))
625
                    }
626
                },
627
                PTRACE_EVENT_FORK => {
×
628
                    if let Ok(fork_child) = get_event_data(child) {
12✔
629
                        trace!("Caught fork event. Child {}", fork_child);
6✔
630
                        let parent = if let Some(process) = self.get_traced_process_mut(child) {
24✔
631
                            // Counting a fork as a new thread ?
632
                            process.thread_count += 1;
6✔
633
                            Some(process.parent)
6✔
634
                        } else {
635
                            None
×
636
                        };
637
                        if let Some(parent) = parent {
18✔
638
                            self.pid_map.insert(Pid::from_raw(fork_child as _), parent);
24✔
639
                        }
640
                    } else {
641
                        trace!("No event data for child");
×
642
                    }
643
                    Ok((
6✔
644
                        TestState::wait_state(),
12✔
645
                        TracerAction::Continue(child.into()),
6✔
646
                    ))
647
                }
648
                PTRACE_EVENT_VFORK => {
×
649
                    // So VFORK used to be handled the same place as FORK however, from the man
650
                    // page for posix_spawn:
651
                    //
652
                    // > [The] posix_spawn() function commences by calling clone with CLONE_VM and CLONE_VFORK flags.
653
                    //
654
                    // This suggests that Command::new().spawn() will result in a
655
                    // `PTRACE_EVENT_VFORK` not `PTRACE_EVENT_EXEC`
656
                    if let Ok(fork_child) = get_event_data(child) {
16✔
657
                        let fork_child = Pid::from_raw(fork_child as _);
24✔
658
                        if self.config.follow_exec {
8✔
659
                            // So I've seen some recursive bin calls with vforks... Maybe just assume
660
                            // every vfork is an exec :thinking:
661
                            let (state, action) = self.handle_exec(fork_child)?;
40✔
662
                            if self.config.forward_signals {
16✔
663
                                self.pending_actions
16✔
664
                                    .push(TracerAction::Continue(child.into()));
16✔
665
                            }
666
                            Ok((state, action))
8✔
667
                        } else {
668
                            Ok((
×
669
                                TestState::wait_state(),
×
670
                                TracerAction::Continue(child.into()),
×
671
                            ))
672
                        }
673
                    } else {
674
                        Ok((
×
675
                            TestState::wait_state(),
×
676
                            TracerAction::Continue(child.into()),
×
677
                        ))
678
                    }
679
                }
680
                PTRACE_EVENT_EXEC => {
×
681
                    if self.config.follow_exec {
8✔
682
                        self.handle_exec(child)
24✔
683
                    } else {
684
                        Ok((TestState::wait_state(), TracerAction::Detach(child.into())))
×
685
                    }
686
                }
687
                PTRACE_EVENT_EXIT => {
×
688
                    trace!("Child exiting");
406✔
689
                    let mut is_parent = false;
812✔
690
                    if let Some(proc) = self.get_traced_process_mut(child) {
1,624✔
691
                        proc.thread_count -= 1;
812✔
692
                        is_parent |= proc.parent == child;
406✔
693
                    }
694
                    if !is_parent {
626✔
695
                        self.pid_map.remove(&child);
440✔
696
                    }
697
                    Ok((
406✔
698
                        TestState::wait_state(),
812✔
699
                        TracerAction::TryContinue(child.into()),
406✔
700
                    ))
701
                }
702
                _ => Err(RunError::TestRuntime(format!(
×
703
                    "Unrecognised ptrace event {event}"
×
704
                ))),
705
            }
706
        } else {
707
            trace!("Unexpected signal with ptrace event {event}");
×
708
            trace!("Signal: {:?}", sig);
×
709
            Err(RunError::TestRuntime("Unexpected signal".to_string()))
×
710
        }
711
    }
712

713
    fn collect_coverage_data(
2,660✔
714
        &mut self,
715
        visited_pcs: &mut HashMap<Pid, HashSet<u64>>,
716
    ) -> Result<UpdateContext, RunError> {
717
        let mut action = None;
5,320✔
718
        let current = self.current;
5,320✔
719
        let enable = self.config.count;
5,320✔
720
        let mut hits_to_increment = HashSet::new();
5,320✔
721
        if let Some(process) = self.get_traced_process_mut(current) {
7,980✔
722
            let visited = visited_pcs.entry(process.parent).or_default();
13,300✔
723
            if let Ok(pc) = current_instruction_pointer(current) {
5,320✔
724
                let pc = (pc - 1) as u64;
5,320✔
725
                trace!("Hit address {:#x}", pc);
2,660✔
726
                if process.breakpoints.contains_key(&pc) {
7,980✔
727
                    let bp = process.breakpoints.get_mut(&pc).unwrap();
6,920✔
728
                    let updated = if visited.contains(&pc) {
5,536✔
729
                        let _ = bp.jump_to(current);
×
730
                        (true, TracerAction::Continue(current.into()))
×
731
                    } else {
732
                        // Don't re-enable if multithreaded as can't yet sort out segfault issue
733
                        if let Ok(x) = bp.process(current, enable) {
5,536✔
734
                            x
1,384✔
735
                        } else {
736
                            // So failed to process a breakpoint.. Still continue to avoid
737
                            // stalling
738
                            (false, TracerAction::Continue(current.into()))
×
739
                        }
740
                    };
741
                    if updated.0 {
2,714✔
742
                        hits_to_increment.insert(pc - process.offset);
3,990✔
743
                    }
744
                    action = Some(updated.1);
1,384✔
745
                }
746
            }
747
        } else {
748
            warn!("Failed to find process for pid: {}", current);
×
749
        }
750
        if let Some(traces) = self.get_active_trace_map_mut(current) {
7,980✔
751
            for addr in &hits_to_increment {
6,650✔
752
                traces.increment_hit(*addr);
2,660✔
753
            }
754
        } else {
755
            warn!("Failed to find traces for pid: {}", current);
×
756
        }
757
        let action = action.unwrap_or_else(|| TracerAction::Continue(current.into()));
10,532✔
758
        Ok((TestState::wait_state(), action))
2,660✔
759
    }
760

761
    fn handle_signaled(
×
762
        &mut self,
763
        pid: &Pid,
764
        sig: &Signal,
765
        flag: bool,
766
    ) -> Result<UpdateContext, RunError> {
767
        let parent = self.get_parent(*pid);
×
768
        if let Some(p) = parent {
×
769
            if let Some(proc) = self.processes.get(&p) {
×
770
                if !proc.is_test_proc {
×
771
                    let info = ProcessInfo::new(*pid, Some(*sig));
×
772
                    return Ok((TestState::wait_state(), TracerAction::TryContinue(info)));
×
773
                }
774
            }
775
        }
776
        match (sig, flag) {
×
777
            (Signal::SIGKILL, _) => Ok((TestState::wait_state(), TracerAction::Detach(pid.into()))),
×
778
            (Signal::SIGTRAP, true) => {
×
779
                Ok((TestState::wait_state(), TracerAction::Continue(pid.into())))
×
780
            }
781
            (Signal::SIGCHLD, _) => {
×
782
                Ok((TestState::wait_state(), TracerAction::Continue(pid.into())))
×
783
            }
784
            (Signal::SIGTERM, _) => {
×
785
                let info = ProcessInfo {
786
                    pid: *pid,
×
787
                    signal: Some(Signal::SIGTERM),
×
788
                };
789
                Ok((TestState::wait_state(), TracerAction::TryContinue(info)))
×
790
            }
791
            _ => Err(RunError::StateMachine("Unexpected stop".to_string())),
×
792
        }
793
    }
794

795
    fn apply_pending_actions(&mut self, range: impl RangeBounds<usize>) {
1,832,900✔
796
        for a in self.pending_actions.drain(range) {
5,498,708✔
797
            if let Some(log) = self.event_log.as_ref() {
8✔
798
                let event = TraceEvent::new_from_action(&a);
×
799
                log.push_trace(event);
×
800
            }
801
            match a {
8✔
802
                TracerAction::Continue(t) | TracerAction::TryContinue(t) => {
16✔
803
                    let _ = continue_exec(t.pid, t.signal);
16✔
804
                }
805
                e => {
×
806
                    error!("Pending actions should only be continues: {:?}", e);
×
807
                }
808
            }
809
        }
810
    }
811
}
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