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

xd009642 / tarpaulin / #644

20 May 2025 06:46AM UTC coverage: 76.57% (-1.9%) from 78.457%
#644

push

xd009642
Release 0.32.6

3925 of 5126 relevant lines covered (76.57%)

134559.92 hits per line

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

50.57
/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>(
184✔
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,104✔
73
    let handle = test.into();
552✔
74
    match handle {
184✔
75
        TestHandle::Id(test) => {
184✔
76
            data.parent = test;
×
77
        }
78
        _ => unreachable!("Test handle must be a PID for ptrace engine"),
79
    }
80
    (TestState::start_state(), data)
×
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,720✔
93
        Self { pid, signal }
94
    }
95
}
96

97
impl From<Pid> for ProcessInfo {
98
    fn from(pid: Pid) -> Self {
3,492✔
99
        ProcessInfo::new(pid, None)
10,476✔
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 {
192✔
110
    if rust_flags(config).contains("dynamic-no-pic") {
192✔
111
        0
×
112
    } else if let Ok(proc) = Process::new(pid.as_raw()) {
384✔
113
        let exe = proc.exe().ok();
×
114
        if let Ok(maps) = proc.maps() {
192✔
115
            let offset_info = maps.iter().find(|x| match (&x.pathname, exe.as_ref()) {
576✔
116
                (MMapPath::Path(p), Some(e)) => p == e,
576✔
117
                (MMapPath::Path(_), None) => true,
×
118
                _ => false,
×
119
            });
120
            if let Some(first) = offset_info {
192✔
121
                first.address.0
×
122
            } else {
123
                0
×
124
            }
125
        } else {
126
            0
×
127
        }
128
    } else {
129
        0
×
130
    }
131
}
132

133
impl<'a> StateData for LinuxData<'a> {
134
    fn start(&mut self) -> Result<Option<TestState>, RunError> {
184✔
135
        match waitpid(self.current, Some(WaitPidFlag::WNOHANG)) {
552✔
136
            Ok(WaitStatus::StillAlive) => Ok(None),
×
137
            Ok(sig @ WaitStatus::Stopped(_, Signal::SIGTRAP)) => {
184✔
138
                if let WaitStatus::Stopped(child, _) = sig {
184✔
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> {
184✔
154
        let mut traced_process = self.init_process(self.current, None)?;
920✔
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");
184✔
159
            self.processes.insert(self.current, traced_process);
736✔
160
            Ok(TestState::wait_state())
184✔
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> {
1,483,356✔
183
        let mut result = Ok(None);
2,966,712✔
184
        let mut running = true;
2,966,712✔
185
        while running {
2,969,564✔
186
            let wait = waitpid(
187
                Pid::from_raw(-1),
×
188
                Some(WaitPidFlag::WNOHANG | WaitPidFlag::__WALL),
×
189
            );
190
            match wait {
1,486,208✔
191
                Ok(WaitStatus::StillAlive) => {
1,482,320✔
192
                    running = false;
1,482,320✔
193
                }
194
                Ok(WaitStatus::Exited(_, _)) => {
408✔
195
                    self.wait_queue.push(wait.unwrap());
408✔
196
                    result = Ok(Some(TestState::Stopped));
408✔
197
                    running = false;
408✔
198
                }
199
                Ok(WaitStatus::PtraceEvent(_, _, _)) => {
628✔
200
                    self.wait_queue.push(wait.unwrap());
3,140✔
201
                    result = Ok(Some(TestState::Stopped));
1,256✔
202
                    running = false;
628✔
203
                }
204
                Ok(s) => {
5,704✔
205
                    self.wait_queue.push(s);
11,408✔
206
                    result = Ok(Some(TestState::Stopped));
2,852✔
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() {
1,483,356✔
221
            trace!("Result queue is {:?}", self.wait_queue);
3,550✔
222
        } else {
223
            self.apply_pending_actions(..);
2,959,612✔
224
        }
225
        result
1,483,356✔
226
    }
227

228
    fn stop(&mut self) -> Result<TestState, RunError> {
3,550✔
229
        let mut actions = Vec::new();
7,100✔
230
        let mut pcs = HashMap::new();
7,100✔
231
        let mut result = Ok(TestState::wait_state());
7,100✔
232
        let pending = self.wait_queue.clone();
10,650✔
233
        let pending_action_len = self.pending_actions.len();
10,650✔
234
        self.wait_queue.clear();
7,100✔
235
        for status in &pending {
11,324✔
236
            let span = trace_span!("pending", event=?status.pid());
7,776✔
237
            let _enter = span.enter();
11,664✔
238
            if let Some(log) = self.event_log.as_ref() {
3,888✔
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 {
6,738✔
252
                WaitStatus::PtraceEvent(c, s, e) => match self.handle_ptrace_event(*c, *s, *e) {
4,396✔
253
                    Ok(s) => Ok(s),
628✔
254
                    Err(e) => Err(RunError::TestRuntime(format!(
×
255
                        "Error occurred when handling ptrace event: {e}"
×
256
                    ))),
257
                },
258
                WaitStatus::Stopped(c, Signal::SIGTRAP) => {
2,624✔
259
                    self.current = *c;
2,624✔
260
                    match self.collect_coverage_data(&mut pcs) {
5,248✔
261
                        Ok(s) => Ok(s),
2,624✔
262
                        Err(e) => Err(RunError::TestRuntime(format!(
×
263
                            "Error when collecting coverage: {e}"
×
264
                        ))),
265
                    }
266
                }
267
                WaitStatus::Stopped(child, Signal::SIGSTOP) => Ok((
432✔
268
                    TestState::wait_state(),
432✔
269
                    TracerAction::Continue(child.into()),
216✔
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) => {
12✔
282
                    Ok((TestState::wait_state(), TracerAction::Continue(c.into())))
24✔
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) => {
408✔
303
                    let mut parent = Pid::from_raw(0);
×
304
                    if let Some(proc) = self.get_traced_process_mut(*child) {
190✔
305
                        for ref mut value in proc.breakpoints.values_mut() {
1,820✔
306
                            value.thread_killed(*child);
×
307
                        }
308
                        parent = proc.parent;
×
309
                    }
310
                    if &parent == child {
×
311
                        if let Some(removed) = self.processes.remove(&parent) {
570✔
312
                            if parent != self.parent {
6✔
313
                                let traces = removed.traces.unwrap();
24✔
314
                                self.traces.merge(&traces);
12✔
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 {
370✔
321
                            Ok((TestState::End(*ec), TracerAction::Nothing))
182✔
322
                        } else {
323
                            self.exit_code = Some(*ec);
2✔
324
                            info!("Test process exited, but spawned processes still running. Continuing tracing");
2✔
325
                            Ok((TestState::wait_state(), TracerAction::Nothing))
2✔
326
                        }
327
                    } else {
328
                        match self.exit_code {
2✔
329
                            Some(ec) if self.processes.is_empty() => return Ok(TestState::End(ec)),
6✔
330
                            _ => {
×
331
                                // Process may have already been destroyed. This is just in case
332
                                Ok((
222✔
333
                                    TestState::wait_state(),
222✔
334
                                    TracerAction::TryContinue(self.parent.into()),
222✔
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 {
3,886✔
345
                Ok((TestState::Waiting { .. }, action)) => {
3,704✔
346
                    actions.push(action);
×
347
                }
348
                Ok((state, action)) => {
546✔
349
                    result = Ok(state);
546✔
350
                    actions.push(action);
364✔
351
                }
352
                Err(e) => result = Err(e),
×
353
            }
354
        }
355
        let mut continued = false;
3,548✔
356
        let mut actioned_pids = HashSet::new();
×
357

358
        for a in &actions {
11,320✔
359
            if let Some(d) = a.get_data() {
7,588✔
360
                if actioned_pids.contains(&d.pid) {
×
361
                    trace!("Skipping action '{:?}', pid already sent command", a);
2✔
362
                    continue;
2✔
363
                } else {
364
                    trace!("No action for {} yet", d.pid);
3,700✔
365
                }
366
            } else {
367
                trace!("No process info for action");
184✔
368
            }
369
            trace!("Action: {:?}", a);
3,884✔
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) => {
624✔
376
                    continued = true;
×
377
                    actioned_pids.insert(t.pid);
×
378
                    let _ = continue_exec(t.pid, t.signal);
×
379
                }
380
                TracerAction::Continue(t) => {
1,762✔
381
                    continued = true;
1,762✔
382
                    actioned_pids.insert(t.pid);
5,286✔
383
                    continue_exec(t.pid, t.signal)?;
5,286✔
384
                }
385
                TracerAction::Step(t) => {
1,312✔
386
                    continued = true;
1,312✔
387
                    actioned_pids.insert(t.pid);
3,936✔
388
                    single_step(t.pid)?;
2,624✔
389
                }
390
                TracerAction::Detach(t) => {
4✔
391
                    continued = true;
4✔
392
                    actioned_pids.insert(t.pid);
8✔
393
                    let _ = detach_child(t.pid);
2✔
394
                }
395
                TracerAction::Nothing => {}
184✔
396
            }
397
        }
398
        // Here we assume that no pending actions will exist for things that have currently
399
        // signaled as stopped in this iteration. Currently, the pending actions are just fork
400
        // parents that ptrace will stall until child returns so this will hold true. But if that
401
        // behaviour changes in future the pending action list may need to be pruned more
402
        // thoroughly
403
        self.apply_pending_actions(..pending_action_len);
3,548✔
404

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

413
impl<'a> LinuxData<'a> {
414
    pub fn new(
184✔
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(),
368✔
422
            pending_actions: Vec::new(),
368✔
423
            processes: HashMap::new(),
368✔
424
            current: Pid::from_raw(0),
368✔
425
            parent: Pid::from_raw(0),
368✔
426
            traces,
427
            analysis,
428
            config,
429
            event_log,
430
            pid_map: HashMap::new(),
184✔
431
            exit_code: None,
432
        }
433
    }
434

435
    fn get_parent(&self, pid: Pid) -> Option<Pid> {
6,268✔
436
        self.pid_map.get(&pid).copied().or_else(|| {
31,758✔
437
            let mut parent_pid = None;
836✔
438
            'outer: for k in self.processes.keys() {
1,254✔
439
                let proc = Process::new(k.as_raw()).ok()?;
2,090✔
440
                if let Ok(tasks) = proc.tasks() {
418✔
441
                    for task in tasks.filter_map(Result::ok) {
1,058✔
442
                        if task.tid == pid.as_raw() {
×
443
                            parent_pid = Some(*k);
200✔
444
                            break 'outer;
×
445
                        }
446
                    }
447
                }
448
            }
449
            parent_pid
418✔
450
        })
451
    }
452

453
    fn get_traced_process_mut(&mut self, pid: Pid) -> Option<&mut TracedProcess> {
3,644✔
454
        let parent = self.get_parent(pid)?;
14,576✔
455
        self.processes.get_mut(&parent)
×
456
    }
457

458
    fn get_active_trace_map_mut(&mut self, pid: Pid) -> Option<&mut TraceMap> {
2,624✔
459
        let parent = self.get_parent(pid)?;
10,496✔
460
        let process = self.processes.get_mut(&parent)?;
2,624✔
461
        if process.traces.is_some() {
×
462
            process.traces.as_mut()
104✔
463
        } else {
464
            Some(self.traces)
2,572✔
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(
192✔
475
        &mut self,
476
        pid: Pid,
477
        trace_map: Option<TraceMap>,
478
    ) -> Result<TracedProcess, RunError> {
479
        let traces = match trace_map.as_ref() {
384✔
480
            Some(s) => s,
16✔
481
            None => self.traces,
184✔
482
        };
483
        let mut breakpoints = HashMap::new();
384✔
484
        trace_children(pid)?;
384✔
485
        let offset = get_offset(pid, self.config);
192✔
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() {
1,172✔
493
            for addr in &trace.address {
4,876✔
494
                if clashes.contains(&align_address(*addr)) {
×
495
                    trace!(
2✔
496
                        "Skipping {} as it clashes with previously disabled breakpoints",
×
497
                        addr
×
498
                    );
499
                    continue;
2✔
500
                }
501
                match Breakpoint::new(pid, *addr + offset) {
×
502
                    Ok(bp) => {
1,834✔
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);
16✔
513
                        // Now to avoid weird false positives lets get rid of the other breakpoint
514
                        // at this address.
515
                        let aligned = align_address(*addr);
×
516
                        clashes.insert(aligned);
×
517
                        breakpoints.retain(|address, breakpoint| {
78✔
518
                            if align_address(*address - offset) == aligned {
156✔
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
78✔
526
                            }
527
                        });
528
                    }
529
                    Err(_) => {
×
530
                        return Err(RunError::TestRuntime(
×
531
                            "Failed to instrument test executable".to_string(),
×
532
                        ));
533
                    }
534
                }
535
            }
536
        }
537
        // a processes pid is it's own parent
538
        match self.pid_map.insert(pid, pid) {
192✔
539
            Some(old) if old != pid => {
2✔
540
                debug!("{} being promoted to parent. Old parent {}", pid, old)
×
541
            }
542
            _ => {}
192✔
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(
16✔
555
        &mut self,
556
        pid: Pid,
557
    ) -> Result<(TestState, TracerAction<ProcessInfo>), RunError> {
558
        trace!("Handling process exec");
16✔
559
        let res = Ok((TestState::wait_state(), TracerAction::Continue(pid.into())));
48✔
560

561
        if let Ok(proc) = Process::new(pid.into()) {
48✔
562
            let exe = match proc.exe() {
14✔
563
                Ok(e) if !e.starts_with(self.config.target_dir()) => {
18✔
564
                    return Ok((TestState::wait_state(), TracerAction::Detach(pid.into())));
×
565
                }
566
                Ok(e) => e,
28✔
567
                _ => return Ok((TestState::wait_state(), TracerAction::Detach(pid.into()))),
×
568
            };
569
            match generate_tracemap(&exe, self.analysis, self.config) {
56✔
570
                Ok(tm) if !tm.is_empty() => match self.init_process(pid, Some(tm)) {
36✔
571
                    Ok(tp) => {
8✔
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");
6✔
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(
628✔
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 {
628✔
600
            match event {
628✔
601
                PTRACE_EVENT_CLONE => match get_event_data(child) {
204✔
602
                    Ok(t) => {
204✔
603
                        trace!("New thread spawned {}", t);
×
604
                        let mut parent = None;
×
605
                        if let Some(proc) = self.get_traced_process_mut(child) {
204✔
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 {
204✔
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) {
12✔
628
                        trace!("Caught fork event. Child {}", fork_child);
×
629
                        let parent = if let Some(process) = self.get_traced_process_mut(child) {
6✔
630
                            // Counting a fork as a new thread ?
631
                            process.thread_count += 1;
×
632
                            Some(process.parent)
×
633
                        } else {
634
                            None
×
635
                        };
636
                        if let Some(parent) = parent {
6✔
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((
6✔
643
                        TestState::wait_state(),
12✔
644
                        TracerAction::Continue(child.into()),
6✔
645
                    ))
646
                }
647
                PTRACE_EVENT_VFORK => {
×
648
                    // So VFORK used to be handled the same place as FORK however, from the man
649
                    // page for posix_spawn:
650
                    //
651
                    // > [The] posix_spawn() function commences by calling clone with CLONE_VM and CLONE_VFORK flags.
652
                    //
653
                    // This suggests that Command::new().spawn() will result in a
654
                    // `PTRACE_EVENT_VFORK` not `PTRACE_EVENT_EXEC`
655
                    if let Ok(fork_child) = get_event_data(child) {
16✔
656
                        let fork_child = Pid::from_raw(fork_child as _);
×
657
                        if self.config.follow_exec {
×
658
                            // So I've seen some recursive bin calls with vforks... Maybe just assume
659
                            // every vfork is an exec :thinking:
660
                            let (state, action) = self.handle_exec(fork_child)?;
32✔
661
                            if self.config.forward_signals {
8✔
662
                                self.pending_actions
16✔
663
                                    .push(TracerAction::Continue(child.into()));
16✔
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 {
8✔
681
                        self.handle_exec(child)
24✔
682
                    } else {
683
                        Ok((TestState::wait_state(), TracerAction::Detach(child.into())))
×
684
                    }
685
                }
686
                PTRACE_EVENT_EXIT => {
×
687
                    trace!("Child exiting");
402✔
688
                    let mut is_parent = false;
804✔
689
                    if let Some(proc) = self.get_traced_process_mut(child) {
1,206✔
690
                        proc.thread_count -= 1;
×
691
                        is_parent |= proc.parent == child;
×
692
                    }
693
                    if !is_parent {
620✔
694
                        self.pid_map.remove(&child);
218✔
695
                    }
696
                    Ok((
402✔
697
                        TestState::wait_state(),
804✔
698
                        TracerAction::TryContinue(child.into()),
402✔
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(
2,624✔
713
        &mut self,
714
        visited_pcs: &mut HashMap<Pid, HashSet<u64>>,
715
    ) -> Result<UpdateContext, RunError> {
716
        let mut action = None;
5,248✔
717
        let current = self.current;
5,248✔
718
        let enable = self.config.count;
5,248✔
719
        let mut hits_to_increment = HashSet::new();
5,248✔
720
        if let Some(process) = self.get_traced_process_mut(current) {
7,872✔
721
            let visited = visited_pcs.entry(process.parent).or_default();
×
722
            if let Ok(pc) = current_instruction_pointer(current) {
2,624✔
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();
6,810✔
727
                    let updated = if visited.contains(&pc) {
5,448✔
728
                        let _ = bp.jump_to(current);
×
729
                        (true, TracerAction::Continue(current.into()))
×
730
                    } else {
731
                        // Don't re-enable if multithreaded as can't yet sort out segfault issue
732
                        if let Ok(x) = bp.process(current, enable) {
2,724✔
733
                            x
×
734
                        } else {
735
                            // So failed to process a breakpoint.. Still continue to avoid
736
                            // stalling
737
                            (false, TracerAction::Continue(current.into()))
×
738
                        }
739
                    };
740
                    if updated.0 {
2,674✔
741
                        hits_to_increment.insert(pc - process.offset);
3,936✔
742
                    }
743
                    action = Some(updated.1);
1,362✔
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) {
7,872✔
750
            for addr in &hits_to_increment {
5,248✔
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()));
10,396✔
757
        Ok((TestState::wait_state(), action))
2,624✔
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>) {
1,483,354✔
795
        for a in self.pending_actions.drain(range) {
4,450,070✔
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) => {
16✔
802
                    let _ = continue_exec(t.pid, t.signal);
8✔
803
                }
804
                e => {
×
805
                    error!("Pending actions should only be continues: {:?}", e);
×
806
                }
807
            }
808
        }
809
    }
810
}
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