Skip to main content

detcore/
access_trace.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Detcore's side of the access trace (`--record-access-trace-to`); the
10//! record types are `detcore_model::access_trace`.
11//!
12//! The trace is made in three places:
13//! - The guest thread notes the descriptor slots its syscall changes
14//!   (`TracedCall`), and snapshots the descriptors the call names right
15//!   after its prehook event (`FdInfo`). At the posthook, `accesses`, a
16//!   pure function of `CallFacts`, turns these, the arguments and the
17//!   result into the record's accesses.
18//! - The record rides to the scheduler as an optional payload of the
19//!   prehook, posthook and signal-delivery `TraceSchedEvent` requests the
20//!   thread sends anyway ([`AccessPayload`]).
21//! - The scheduler, which numbers the recorded events, completes each record
22//!   and writes it ([`AccessTraceWriter`]).
23//!
24//! Nothing here sends a scheduler request, charges virtual time or changes
25//! state a scheduling decision reads: the trace observes the run.
26
27use std::collections::BTreeMap;
28use std::collections::BTreeSet;
29use std::fs::File;
30use std::io;
31use std::io::BufWriter;
32use std::io::Write;
33use std::path::Path;
34use std::sync::Mutex;
35
36use detcore_model::access_trace::ACCESS_TRACE_DEFAULT_MAX_BYTES;
37use detcore_model::access_trace::Access;
38use detcore_model::access_trace::ByteRange;
39use detcore_model::access_trace::ChannelId;
40use detcore_model::access_trace::ChannelKind;
41use detcore_model::access_trace::ChannelOp;
42use detcore_model::access_trace::CursorOp;
43use detcore_model::access_trace::DataOp;
44use detcore_model::access_trace::EdgeKind;
45use detcore_model::access_trace::EdgeRecord;
46use detcore_model::access_trace::EventIndex;
47use detcore_model::access_trace::EventRef;
48use detcore_model::access_trace::FdOp;
49use detcore_model::access_trace::FutexOp;
50use detcore_model::access_trace::LifetimeOp;
51use detcore_model::access_trace::MemoryOp;
52use detcore_model::access_trace::ObservedSignalDisposition;
53use detcore_model::access_trace::RETURNED_DIGEST_CAP;
54use detcore_model::access_trace::RETURNED_DIGEST_SEED;
55use detcore_model::access_trace::ReturnedBytes;
56use detcore_model::access_trace::SignalRecord;
57use detcore_model::access_trace::SignalSender;
58use detcore_model::access_trace::SyncObject;
59use detcore_model::access_trace::SyscallRecord;
60use detcore_model::access_trace::Task;
61use detcore_model::access_trace::TimerId;
62use detcore_model::access_trace::TimerOp;
63use detcore_model::access_trace::TraceEnd;
64use detcore_model::access_trace::TraceHeader;
65use detcore_model::access_trace::TraceRecord;
66use detcore_model::access_trace::returned_digest;
67use reverie::syscalls::Addr;
68use reverie::syscalls::MemoryAccess;
69use reverie::syscalls::Sysno;
70use serde::Deserialize;
71use serde::Serialize;
72use tracing::warn;
73
74use crate::fd::DetFd;
75use crate::fd::FdType;
76use crate::resources::ResourceID;
77use crate::types::DetInode;
78use crate::types::DetPid;
79use crate::types::DetTid;
80use crate::types::FdSlot;
81use crate::types::FilesId;
82use crate::types::FutexID;
83use crate::types::MmId;
84use crate::types::Op;
85use crate::types::OpenFileId;
86use crate::types::SchedEvent;
87use crate::types::SyscallPhase;
88
89// ---------------------------------------------------------------------------
90// The payload a traced schedule event carries.
91
92/// What a traced schedule event carries to the scheduler, beside the event.
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94pub enum AccessPayload {
95    /// At a syscall's prehook event: what the registers say.
96    SyscallEntry(SyscallEntry),
97    /// At its posthook event: the outcome.
98    SyscallExit(Box<SyscallOutcome>),
99    /// At a signal's `SignalReceived` event.
100    Signal(SignalNote),
101}
102
103/// A syscall as its prehook sees it.
104#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
105pub struct SyscallEntry {
106    /// The calling thread's process.
107    pub pid: DetPid,
108    /// The thread's syscall count including this call.
109    pub nth: u64,
110    /// The syscall.
111    pub sysno: Sysno,
112    /// Raw argument registers.
113    pub args: [u64; 6],
114    /// What a call that may never return (exit, exit_group, execve) does to
115    /// its thread or process (`entry_accesses`); its posthook, if it comes,
116    /// replaces them. JSON, as [`SyscallOutcome::accesses`].
117    #[serde(with = "accesses_as_json")]
118    pub accesses: Vec<Access>,
119}
120
121/// The accesses a call that may not reach its posthook is known for at its
122/// prehook: exit ends its thread, exit_group its process, and execve
123/// replaces its process's image.
124pub(crate) fn entry_accesses(sysno: Sysno, pid: DetPid, tid: DetTid) -> Vec<Access> {
125    let access = |target, op| vec![Access::Lifetime { target, op }];
126    match sysno {
127        Sysno::exit => access(Task::Thread(tid), LifetimeOp::Exit),
128        Sysno::exit_group => access(Task::Process(pid), LifetimeOp::Exit),
129        Sysno::execve | Sysno::execveat => access(Task::Process(pid), LifetimeOp::Exec),
130        _ => Vec::new(),
131    }
132}
133
134/// A syscall's outcome, as its posthook sees it.
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136pub struct SyscallOutcome {
137    /// The value the guest receives: the result or the negated errno.
138    pub result: i64,
139    /// What `accesses` found. Carried as JSON, the trace's own encoding:
140    /// the record types use tagged enums and skipped fields, which RPC's
141    /// positional bincode cannot carry.
142    #[serde(with = "accesses_as_json")]
143    pub accesses: Vec<Access>,
144    /// The bytes returned through guest buffers.
145    pub returned: Option<ReturnedBytes>,
146    /// The first bytes of a write to fd 1 or 2.
147    pub out_bytes: Option<Vec<u8>>,
148}
149
150/// [`SyscallOutcome::accesses`] as a JSON string.
151mod accesses_as_json {
152    use detcore_model::access_trace::Access;
153    use serde::Deserialize;
154    use serde::Deserializer;
155    use serde::Serializer;
156    use serde::de::Error as _;
157    use serde::ser::Error as _;
158
159    pub fn serialize<S: Serializer>(accesses: &[Access], s: S) -> Result<S::Ok, S::Error> {
160        s.serialize_str(&serde_json::to_string(accesses).map_err(S::Error::custom)?)
161    }
162
163    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<Access>, D::Error> {
164        serde_json::from_str(&String::deserialize(d)?).map_err(D::Error::custom)
165    }
166}
167
168/// A signal event and the disposition observed before submitting it.
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
170pub struct SignalNote {
171    /// Signal number.
172    pub signo: i32,
173    /// `si_code`.
174    pub code: i32,
175    /// `si_addr` of a kernel-generated fault signal.
176    pub addr: Option<u64>,
177    /// `si_pid` of a signal sent by kill, sigqueue, tkill or tgkill.
178    pub sender: Option<SignalSender>,
179    /// `si_tid` of a POSIX timer's expiry (`SI_TIMER`): the timer's id.
180    pub timer: Option<i32>,
181    /// Installed disposition sampled at the event, not its eventual outcome.
182    pub observed_disposition: ObservedSignalDisposition,
183}
184
185/// Read the fields the trace keeps from a raw `siginfo_t`
186/// (`include/uapi/asm-generic/siginfo.h`): `si_signo` at byte 0, `si_code`
187/// at byte 8, and the union at byte 16, which holds `si_addr` for a fault and
188/// `si_pid` for a user-sent signal.
189pub(crate) fn signal_note(
190    signo: i32,
191    siginfo: Option<&[u8]>,
192    observed_disposition: ObservedSignalDisposition,
193) -> SignalNote {
194    let field = |info: &[u8], at: usize, len: usize| info.get(at..at + len).map(<[u8]>::to_vec);
195    let int = |info: &[u8], at: usize| {
196        field(info, at, 4).map(|b| i32::from_ne_bytes(b.try_into().expect("four bytes")))
197    };
198    let Some(info) = siginfo else {
199        return SignalNote {
200            signo,
201            code: 0,
202            addr: None,
203            sender: None,
204            timer: None,
205            observed_disposition,
206        };
207    };
208    let code = int(info, 8).unwrap_or(0);
209    let fault = matches!(
210        signo,
211        libc::SIGSEGV | libc::SIGBUS | libc::SIGILL | libc::SIGFPE | libc::SIGTRAP
212    );
213    // A positive si_code is the kernel's (`si_fromuser`, kernel/signal.c).
214    let addr = (code > 0 && fault)
215        .then(|| field(info, 16, 8))
216        .flatten()
217        .map(|b| u64::from_ne_bytes(b.try_into().expect("eight bytes")));
218    // Only kill (SI_USER), sigqueue (SI_QUEUE) and tkill/tgkill (SI_TKILL)
219    // put the sender's pid there; a timer (SI_TIMER) puts its timer id.
220    let sender = matches!(code, libc::SI_USER | libc::SI_QUEUE | libc::SI_TKILL)
221        .then(|| int(info, 16))
222        .flatten()
223        .map(|pid| SignalSender {
224            pid: DetPid::from_raw(pid),
225            tid: None,
226        });
227    let timer = (code == libc::SI_TIMER).then(|| int(info, 16)).flatten();
228    SignalNote {
229        signo,
230        code,
231        addr,
232        sender,
233        timer,
234        observed_disposition,
235    }
236}
237
238// ---------------------------------------------------------------------------
239// The guest thread's side.
240
241/// What the trace needs to know of one descriptor.
242#[derive(Debug, Clone, PartialEq, Eq)]
243pub(crate) struct FdInfo {
244    pub(crate) open_file: OpenFileId,
245    pub(crate) ty: FdType,
246    pub(crate) inode: Option<DetInode>,
247    pub(crate) fifo: bool,
248    pub(crate) pidfd_target: Option<DetPid>,
249}
250
251impl FdInfo {
252    pub(crate) fn of(detfd: &DetFd) -> Self {
253        let inode = match detfd.resource() {
254            Some(ResourceID::FileContents(inode)) => Some(inode),
255            _ => None,
256        };
257        let fifo = detfd
258            .stat()
259            .is_some_and(|stat| stat.mode & libc::S_IFMT == libc::S_IFIFO);
260        FdInfo {
261            open_file: detfd.open_file_id(),
262            ty: detfd.ty(),
263            inode,
264            fifo,
265            pidfd_target: detfd.pidfd_target(),
266        }
267    }
268
269    fn channel(&self) -> Option<ChannelKind> {
270        match self.ty {
271            FdType::Pipe => Some(ChannelKind::Pipe),
272            FdType::Socket => Some(ChannelKind::Socket),
273            FdType::Eventfd => Some(ChannelKind::Eventfd),
274            FdType::Regular if self.fifo => Some(ChannelKind::Fifo),
275            _ => None,
276        }
277    }
278
279    fn channel_id(&self) -> Option<ChannelId> {
280        self.fifo
281            .then_some(self.inode.map(ChannelId::Inode))
282            .flatten()
283    }
284}
285
286/// One change the running syscall made to a descriptor slot, as Detcore's
287/// descriptor table applied it.
288#[derive(Debug, Clone, PartialEq, Eq)]
289pub(crate) struct FdNote {
290    pub(crate) slot: FdSlot,
291    /// The open file the slot held before a close, or holds after an open or
292    /// a dup.
293    pub(crate) open_file: Option<OpenFileId>,
294    pub(crate) op: FdOp,
295    /// For a close: what the slot named, so a close the call's arguments do
296    /// not name (close_range, a dup2 onto an open number) still reaches the
297    /// channel behind it.
298    pub(crate) released: Option<FdInfo>,
299}
300
301/// What a traced syscall has gathered between its prehook and its posthook.
302#[derive(Debug, Default)]
303pub(crate) struct CallState {
304    /// The descriptors the call names, right after its prehook event.
305    pub(crate) fds: BTreeMap<i32, FdInfo>,
306    /// The slot changes the call made, in order.
307    pub(crate) notes: Vec<FdNote>,
308}
309
310/// A thread's traced syscall in progress. Inactive unless the run records
311/// an access trace, and between a traced call's posthook and the next
312/// prehook. Descriptor-table methods take `&ThreadState`, hence the mutex;
313/// only the owning thread uses it.
314#[derive(Debug, Default)]
315pub(crate) struct TracedCall(Mutex<Option<CallState>>);
316
317impl Clone for TracedCall {
318    /// A copied thread state (a new thread, a migration) has no call in
319    /// progress.
320    fn clone(&self) -> Self {
321        Self::default()
322    }
323}
324
325impl TracedCall {
326    /// Start a traced call whose named descriptors were `fds`.
327    pub(crate) fn begin(&self, fds: BTreeMap<i32, FdInfo>) {
328        *self.0.lock().expect("traced call mutex poisoned") = Some(CallState {
329            fds,
330            notes: Vec::new(),
331        });
332    }
333
334    /// Whether a traced call is in progress, so a slot change is noted.
335    pub(crate) fn is_active(&self) -> bool {
336        self.0.lock().expect("traced call mutex poisoned").is_some()
337    }
338
339    /// Note a slot change of the call in progress, if there is one.
340    pub(crate) fn note(&self, note: FdNote) {
341        if let Some(state) = self.0.lock().expect("traced call mutex poisoned").as_mut() {
342            state.notes.push(note);
343        }
344    }
345
346    /// End the call in progress and return what it gathered.
347    pub(crate) fn take(&self) -> Option<CallState> {
348        self.0.lock().expect("traced call mutex poisoned").take()
349    }
350}
351
352/// Values a completed call wrote to guest memory that name resources.
353#[derive(Debug, Default, Clone, PartialEq, Eq)]
354pub(crate) struct Written {
355    /// timer_create's new timer id.
356    pub(crate) timer_id: Option<i32>,
357    /// pipe, pipe2 or socketpair's two descriptors, in array order.
358    pub(crate) fd_pair: Option<[i32; 2]>,
359    /// waitid's `si_pid`.
360    pub(crate) waited_pid: Option<i32>,
361    /// clone3's `clone_args.flags`.
362    pub(crate) clone3_flags: Option<u64>,
363}
364
365/// Where, in guest memory, [`Written`] values are, for a call that succeeded
366/// with `result`: the address and the field to read there.
367pub(crate) fn written_locations(
368    sysno: Sysno,
369    args: [u64; 6],
370    result: i64,
371) -> Vec<(u64, WrittenField)> {
372    if result < 0 {
373        return Vec::new();
374    }
375    match sysno {
376        Sysno::timer_create => vec![(args[2], WrittenField::TimerId)],
377        // aarch64 has only pipe2 (include/uapi/asm-generic/unistd.h).
378        #[cfg(target_arch = "x86_64")]
379        Sysno::pipe => vec![(args[0], WrittenField::FdPair)],
380        Sysno::pipe2 => vec![(args[0], WrittenField::FdPair)],
381        Sysno::socketpair => vec![(args[3], WrittenField::FdPair)],
382        // `si_pid` is the union's first member, at byte 16 of the siginfo.
383        Sysno::waitid if args[2] != 0 => vec![(args[2] + 16, WrittenField::WaitedPid)],
384        Sysno::clone3 => vec![(args[0], WrittenField::Clone3Flags)],
385        _ => Vec::new(),
386    }
387}
388
389/// Read the [`Written`] values of a call that returned `result`. A value the
390/// guest's memory no longer holds readable is left out.
391pub(crate) fn read_written<M: MemoryAccess>(
392    memory: &M,
393    sysno: Sysno,
394    args: [u64; 6],
395    result: i64,
396) -> Written {
397    let mut written = Written::default();
398    for (addr, field) in written_locations(sysno, args, result) {
399        let int =
400            |at: u64| Addr::<i32>::from_raw(at as usize).and_then(|a| memory.read_value(a).ok());
401        match field {
402            WrittenField::TimerId => written.timer_id = int(addr),
403            WrittenField::WaitedPid => written.waited_pid = int(addr),
404            WrittenField::FdPair => {
405                written.fd_pair = int(addr).zip(int(addr + 4)).map(|(a, b)| [a, b]);
406            }
407            WrittenField::Clone3Flags => {
408                written.clone3_flags =
409                    Addr::<u64>::from_raw(addr as usize).and_then(|a| memory.read_value(a).ok());
410            }
411        }
412    }
413    written
414}
415
416/// One value [`written_locations`] names.
417#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418pub(crate) enum WrittenField {
419    TimerId,
420    FdPair,
421    WaitedPid,
422    Clone3Flags,
423}
424
425/// How a call uses one descriptor it names.
426#[derive(Debug, Clone, Copy, PartialEq, Eq)]
427enum Role {
428    /// Any use the trace does not break down further.
429    Use,
430    Read,
431    ReadAt(Option<u64>),
432    Write,
433    WriteAt(Option<u64>),
434    Seek,
435    Truncate,
436    Shutdown,
437    TimerSet,
438    TimerGet,
439}
440
441/// The descriptors a call uses as operands, each with its role. A positional
442/// offset of -1 (preadv2, pwritev2) means the cursor.
443fn operand_fds(sysno: Sysno, args: [u64; 6]) -> Vec<(i32, Role)> {
444    use Sysno::*;
445    let fd = |i: usize| args[i] as i32;
446    let at = |i: usize| (args[i] as i64 >= 0).then_some(args[i]);
447    match sysno {
448        read | readv | recvfrom | recvmsg | recvmmsg => vec![(fd(0), Role::Read)],
449        pread64 | preadv => vec![(fd(0), Role::ReadAt(Some(args[3])))],
450        preadv2 => vec![(fd(0), at(3).map_or(Role::Read, |o| Role::ReadAt(Some(o))))],
451        write | writev | sendto | sendmsg | sendmmsg => vec![(fd(0), Role::Write)],
452        pwrite64 | pwritev => vec![(fd(0), Role::WriteAt(Some(args[3])))],
453        pwritev2 => vec![(fd(0), at(3).map_or(Role::Write, |o| Role::WriteAt(Some(o))))],
454        lseek => vec![(fd(0), Role::Seek)],
455        ftruncate | fallocate => vec![(fd(0), Role::Truncate)],
456        shutdown => vec![(fd(0), Role::Shutdown)],
457        timerfd_settime => vec![(fd(0), Role::TimerSet)],
458        timerfd_gettime => vec![(fd(0), Role::TimerGet)],
459        sendfile => vec![(fd(0), Role::Write), (fd(1), Role::Read)],
460        splice | copy_file_range => vec![(fd(0), Role::ReadAt(None)), (fd(2), Role::WriteAt(None))],
461        tee => vec![(fd(0), Role::Read), (fd(1), Role::Write)],
462        epoll_ctl => vec![(fd(0), Role::Use), (fd(2), Role::Use)],
463        mmap if args[3] & libc::MAP_ANONYMOUS as u64 == 0 => vec![(fd(4), Role::Use)],
464        signalfd4 if fd(0) >= 0 => vec![(fd(0), Role::Use)],
465        fstat | fsync | fdatasync | fchmod | fchown | flock | fcntl | ioctl | getdents64
466        | fadvise64 | fstatfs | fchdir | syncfs | accept | accept4 | connect | bind | listen
467        | getsockname | getpeername | setsockopt | getsockopt | epoll_pwait | epoll_pwait2
468        | inotify_add_watch | inotify_rm_watch | fgetxattr | fsetxattr | flistxattr
469        | fremovexattr | pidfd_send_signal | pidfd_getfd | dup | dup3 => {
470            vec![(fd(0), Role::Use)]
471        }
472        // The legacy calls only x86_64 has. aarch64's generic table
473        // (include/uapi/asm-generic/unistd.h) has getdents64, epoll_pwait and
474        // dup3 above instead, and names sync_file_range sync_file_range2.
475        #[cfg(target_arch = "x86_64")]
476        getdents | sync_file_range | epoll_wait | dup2 => vec![(fd(0), Role::Use)],
477        #[cfg(target_arch = "aarch64")]
478        sync_file_range2 => vec![(fd(0), Role::Use)],
479        _ => Vec::new(),
480    }
481}
482
483/// Every descriptor a call names, whose state before the call the trace
484/// needs: its operands, the descriptor close releases, and the one dup2 or
485/// dup3 replaces.
486pub(crate) fn named_fds(sysno: Sysno, args: [u64; 6]) -> Vec<i32> {
487    let mut fds: Vec<i32> = operand_fds(sysno, args)
488        .into_iter()
489        .map(|(fd, _)| fd)
490        .collect();
491    match sysno {
492        Sysno::close => fds.push(args[0] as i32),
493        #[cfg(target_arch = "x86_64")]
494        Sysno::dup2 => fds.push(args[1] as i32),
495        Sysno::dup3 => fds.push(args[1] as i32),
496        _ => {}
497    }
498    fds.retain(|fd| *fd >= 0);
499    fds.sort_unstable();
500    fds.dedup();
501    fds
502}
503
504/// What a call that reads a clock or a timer returns, for its
505/// [`ReturnedBytes`]: the result itself when it is the time (`time`,
506/// `alarm`, `times`), and the guest buffers the kernel or Detcore filled.
507/// Buffers are x86-64 sizes: timespec and timeval 16 bytes, itimerspec and
508/// itimerval 32, timezone 8, tms 32. A remaining-time buffer counts only
509/// when a sleep was interrupted, the one case Linux writes it: do_nanosleep
510/// copies it out (`nanosleep_copyout`, kernel/time/hrtimer.c:2072 and
511/// :2121 at v6.16) and returns -ERESTART_RESTARTBLOCK, which the posthook
512/// sees as is when the call will restart and as -EINTR when a handler ran.
513/// An absolute clock_nanosleep never writes it; an old-value
514/// buffer only when the guest passed one.
515pub(crate) fn time_outputs(
516    sysno: Sysno,
517    args: [u64; 6],
518    result: i64,
519) -> (Option<i64>, Vec<ByteRange>) {
520    use Sysno::*;
521    const TIMESPEC: u64 = 16;
522    const TIMEVAL: u64 = 16;
523    const ITIMER: u64 = 32;
524    const TIMEZONE: u64 = 8;
525    const TMS: u64 = 32;
526    let at = |addr: u64, len: u64| {
527        (addr != 0).then_some(ByteRange {
528            start: addr,
529            end: addr.saturating_add(len),
530        })
531    };
532    let ok = result >= 0;
533    /// Linux's restart code for an interrupted relative sleep
534    /// (include/linux/errno.h).
535    const ERESTART_RESTARTBLOCK: i64 = 516;
536    let interrupted = result == -(libc::EINTR as i64) || result == -ERESTART_RESTARTBLOCK;
537    let buffers: Vec<Option<ByteRange>> = match sysno {
538        clock_gettime | clock_getres if ok => vec![at(args[1], TIMESPEC)],
539        gettimeofday if ok => vec![at(args[0], TIMEVAL), at(args[1], TIMEZONE)],
540        nanosleep if interrupted => vec![at(args[1], TIMESPEC)],
541        clock_nanosleep if interrupted && args[1] & libc::TIMER_ABSTIME as u64 == 0 => {
542            vec![at(args[3], TIMESPEC)]
543        }
544        timerfd_gettime | timer_gettime | getitimer if ok => vec![at(args[1], ITIMER)],
545        timerfd_settime | timer_settime if ok => vec![at(args[3], ITIMER)],
546        setitimer if ok => vec![at(args[2], ITIMER)],
547        times if ok => vec![at(args[0], TMS)],
548        _ => Vec::new(),
549    };
550    // time and alarm are x86_64-only; aarch64's C library builds them on
551    // clock_gettime and setitimer, whose outputs are above.
552    #[cfg(target_arch = "x86_64")]
553    let returns_time = matches!(sysno, time | alarm | times);
554    #[cfg(not(target_arch = "x86_64"))]
555    let returns_time = sysno == times;
556    let value = (ok && returns_time).then_some(result);
557    (value, buffers.into_iter().flatten().collect())
558}
559
560/// Everything [`accesses`] reads.
561pub(crate) struct CallFacts<'a> {
562    pub(crate) sysno: Sysno,
563    pub(crate) args: [u64; 6],
564    pub(crate) result: i64,
565    pub(crate) pid: DetPid,
566    pub(crate) tid: DetTid,
567    pub(crate) files: FilesId,
568    pub(crate) mm: MmId,
569    /// The descriptors [`named_fds`] lists, right after the prehook event.
570    pub(crate) fds: &'a BTreeMap<i32, FdInfo>,
571    /// The slot changes the call made.
572    pub(crate) notes: &'a [FdNote],
573    /// The guest buffers the call moved, and which way.
574    pub(crate) buffers: &'a [(MemoryOp, ByteRange)],
575    pub(crate) written: &'a Written,
576    /// The thread's key for a futex word (`ThreadState::futex_id`).
577    pub(crate) futex_id: &'a dyn Fn(u64, bool) -> FutexID,
578}
579
580const PAGE: u64 = 4096;
581
582fn page_range(start: u64, len: u64) -> ByteRange {
583    ByteRange {
584        start,
585        end: start.saturating_add(len.div_ceil(PAGE).saturating_mul(PAGE)),
586    }
587}
588
589/// The record's accesses: section 4 of the M5 design applied to one call.
590pub(crate) fn accesses(f: &CallFacts<'_>) -> Vec<Access> {
591    let mut out = Vec::new();
592    let ok = f.result >= 0;
593    let slot = |fd: i32| FdSlot { files: f.files, fd };
594
595    for (fd, role) in operand_fds(f.sysno, f.args) {
596        if fd < 0 {
597            continue;
598        }
599        let info = f.fds.get(&fd);
600        out.push(Access::Fd {
601            slot: slot(fd),
602            open_file: info.map(|info| info.open_file),
603            op: FdOp::Use,
604        });
605        if let Some(info) = info {
606            data_accesses(f, info, role, &mut out);
607        }
608    }
609
610    // A close that released nothing Detcore models (EBADF, a double close)
611    // is still a close of the slot.
612    if f.sysno == Sysno::close {
613        let fd = f.args[0] as i32;
614        let noted = f
615            .notes
616            .iter()
617            .any(|note| note.slot.fd == fd && note.op == FdOp::Close);
618        if !noted {
619            out.push(Access::Fd {
620                slot: slot(fd),
621                open_file: f.fds.get(&fd).map(|info| info.open_file),
622                op: FdOp::Close,
623            });
624        }
625    }
626
627    for note in ordered_notes(f) {
628        out.push(Access::Fd {
629            slot: note.slot,
630            open_file: note.open_file,
631            op: note.op.clone(),
632        });
633        if note.op == FdOp::Close
634            && let Some(info) = &note.released
635            && let Some(channel) = info.channel()
636        {
637            out.push(Access::Channel {
638                open_file: info.open_file,
639                id: info.channel_id(),
640                channel,
641                op: ChannelOp::Close,
642            });
643        }
644    }
645
646    futex_accesses(f, &mut out);
647    if ok {
648        memory_accesses(f, &mut out);
649    }
650    timer_accesses(f, &mut out);
651    lifetime_accesses(f, &mut out);
652    for (op, range) in f.buffers {
653        out.push(Access::Memory {
654            mm: f.mm,
655            range: *range,
656            op: *op,
657        });
658    }
659    out
660}
661
662/// The notes in record order: a pipe's or socketpair's two opens in the
663/// order of its descriptor array, so the first is `fds[0]`.
664fn ordered_notes<'a>(f: &CallFacts<'a>) -> Vec<&'a FdNote> {
665    let mut notes: Vec<&FdNote> = f.notes.iter().collect();
666    if let Some(pair) = f.written.fd_pair {
667        notes.sort_by_key(|note| pair.iter().position(|fd| *fd == note.slot.fd));
668    }
669    notes
670}
671
672fn data_accesses(f: &CallFacts<'_>, info: &FdInfo, role: Role, out: &mut Vec<Access>) {
673    if let Some(channel) = info.channel() {
674        let op = match role {
675            Role::Read | Role::ReadAt(_) => ChannelOp::Read,
676            Role::Write | Role::WriteAt(_) => ChannelOp::Write,
677            Role::Shutdown => ChannelOp::Shutdown,
678            _ => return,
679        };
680        out.push(Access::Channel {
681            open_file: info.open_file,
682            id: info.channel_id(),
683            channel,
684            op,
685        });
686        return;
687    }
688    if info.ty == FdType::Timerfd {
689        let op = match role {
690            Role::Read => TimerOp::Read,
691            Role::TimerSet => TimerOp::Set,
692            Role::TimerGet => TimerOp::Get,
693            _ => return,
694        };
695        out.push(Access::Timer {
696            timer: TimerId::Fd {
697                open_file: info.open_file,
698            },
699            op,
700        });
701        return;
702    }
703    if !matches!(info.ty, FdType::Regular | FdType::Memfd) {
704        return;
705    }
706    let cursor = |op| Access::OpenFile {
707        open_file: info.open_file,
708        op,
709    };
710    // A positional range covers what the call moved, or, when it moved
711    // nothing (end of file, an error), the bytes it asked for.
712    let range = |offset: Option<u64>| {
713        offset.map(|start| {
714            let moved = u64::try_from(f.result).unwrap_or(0);
715            let len = if moved > 0 {
716                moved
717            } else if matches!(f.sysno, Sysno::pread64 | Sysno::pwrite64) {
718                f.args[2]
719            } else {
720                0
721            };
722            ByteRange {
723                start,
724                end: start.saturating_add(len),
725            }
726        })
727    };
728    let contents = |range: Option<ByteRange>, op| {
729        info.inode
730            .map(|inode| Access::FileContents { inode, range, op })
731    };
732    match role {
733        Role::Read => {
734            out.push(cursor(CursorOp::Read));
735            out.extend(contents(None, DataOp::Read));
736        }
737        Role::Write => {
738            out.push(cursor(CursorOp::Write));
739            out.extend(contents(None, DataOp::Write));
740        }
741        Role::ReadAt(offset) => out.extend(contents(range(offset), DataOp::Read)),
742        Role::WriteAt(offset) => out.extend(contents(range(offset), DataOp::Write)),
743        Role::Seek => out.push(cursor(CursorOp::Seek)),
744        Role::Truncate => out.extend(contents(None, DataOp::Write)),
745        Role::Use | Role::Shutdown | Role::TimerSet | Role::TimerGet => {}
746    }
747}
748
749/// `FUTEX_LOCK_PI2`, which the libc crate may not name.
750const FUTEX_LOCK_PI2: i32 = 13;
751
752fn futex_accesses(f: &CallFacts<'_>, out: &mut Vec<Access>) {
753    if f.sysno != Sysno::futex {
754        return;
755    }
756    let op = f.args[1] as i32;
757    let private = op & libc::FUTEX_PRIVATE_FLAG != 0;
758    let cmd = op & !(libc::FUTEX_PRIVATE_FLAG | libc::FUTEX_CLOCK_REALTIME);
759    let (uaddr, uaddr2) = (f.args[0], f.args[4]);
760    let words: &[(u64, FutexOp)] = match cmd {
761        libc::FUTEX_WAIT | libc::FUTEX_WAIT_BITSET | libc::FUTEX_WAIT_REQUEUE_PI => {
762            &[(uaddr, FutexOp::Wait)]
763        }
764        libc::FUTEX_WAKE | libc::FUTEX_WAKE_BITSET => &[(uaddr, FutexOp::Wake)],
765        libc::FUTEX_REQUEUE | libc::FUTEX_CMP_REQUEUE | libc::FUTEX_CMP_REQUEUE_PI => {
766            &[(uaddr, FutexOp::Wake), (uaddr2, FutexOp::RequeueTarget)]
767        }
768        libc::FUTEX_WAKE_OP => &[(uaddr, FutexOp::Wake), (uaddr2, FutexOp::ModifyWake)],
769        libc::FUTEX_LOCK_PI | libc::FUTEX_TRYLOCK_PI | FUTEX_LOCK_PI2 => &[(uaddr, FutexOp::Lock)],
770        libc::FUTEX_UNLOCK_PI => &[(uaddr, FutexOp::Unlock)],
771        _ => &[],
772    };
773    for (addr, op) in words {
774        out.push(Access::Futex {
775            futex: (f.futex_id)(*addr, private),
776            op: *op,
777        });
778    }
779}
780
781fn memory_accesses(f: &CallFacts<'_>, out: &mut Vec<Access>) {
782    let a = f.args;
783    let mut push = |range: ByteRange, op| {
784        out.push(Access::Memory {
785            mm: f.mm,
786            range,
787            op,
788        })
789    };
790    match f.sysno {
791        Sysno::mmap => push(page_range(f.result as u64, a[1]), MemoryOp::Map),
792        Sysno::munmap => push(page_range(a[0], a[1]), MemoryOp::Unmap),
793        Sysno::mprotect | Sysno::pkey_mprotect => push(page_range(a[0], a[1]), MemoryOp::Protect),
794        Sysno::madvise => push(page_range(a[0], a[1]), MemoryOp::Advise),
795        Sysno::mremap => {
796            push(page_range(a[0], a[1]), MemoryOp::Unmap);
797            push(page_range(f.result as u64, a[2]), MemoryOp::Map);
798        }
799        _ => {}
800    }
801}
802
803fn timer_accesses(f: &CallFacts<'_>, out: &mut Vec<Access>) {
804    let posix = |id: i32| TimerId::Posix { process: f.pid, id };
805    let interval = |which: i32| TimerId::Interval {
806        process: f.pid,
807        which,
808    };
809    let ok = f.result >= 0;
810    let access = match f.sysno {
811        Sysno::timer_create if ok => f.written.timer_id.map(|id| (posix(id), TimerOp::Create)),
812        Sysno::timer_settime => Some((posix(f.args[0] as i32), TimerOp::Set)),
813        Sysno::timer_gettime | Sysno::timer_getoverrun => {
814            Some((posix(f.args[0] as i32), TimerOp::Get))
815        }
816        Sysno::timer_delete => Some((posix(f.args[0] as i32), TimerOp::Delete)),
817        Sysno::setitimer => Some((interval(f.args[0] as i32), TimerOp::Set)),
818        Sysno::getitimer => Some((interval(f.args[0] as i32), TimerOp::Get)),
819        // x86_64 only; on aarch64 alarm(3) is a setitimer call.
820        #[cfg(target_arch = "x86_64")]
821        Sysno::alarm => Some((interval(libc::ITIMER_REAL), TimerOp::Set)),
822        Sysno::timerfd_create if ok => f
823            .notes
824            .iter()
825            .find(|note| note.slot.fd as i64 == f.result && matches!(note.op, FdOp::Open { .. }))
826            .and_then(|note| note.open_file)
827            .map(|open_file| (TimerId::Fd { open_file }, TimerOp::Create)),
828        _ => None,
829    };
830    if let Some((timer, op)) = access {
831        out.push(Access::Timer { timer, op });
832    }
833}
834
835fn lifetime_accesses(f: &CallFacts<'_>, out: &mut Vec<Access>) {
836    let a = f.args;
837    let raw = |i: usize| a[i] as i32;
838    let created = |flags: u64| {
839        (f.result > 0).then(|| {
840            let child = DetPid::from_raw(f.result as i32);
841            if flags & libc::CLONE_THREAD as u64 != 0 {
842                Task::Thread(child)
843            } else {
844                Task::Process(child)
845            }
846        })
847    };
848    let signal =
849        |target: Option<Task>, signo: i32| target.map(|t| (t, LifetimeOp::Signal { signo }));
850    let access = match f.sysno {
851        Sysno::clone => created(a[0]).map(|t| (t, LifetimeOp::Create)),
852        Sysno::clone3 => f
853            .written
854            .clone3_flags
855            .and_then(created)
856            .map(|t| (t, LifetimeOp::Create)),
857        // x86_64 only; on aarch64 fork(2) and vfork(2) are clone calls
858        // with SIGCHLD (and CLONE_VM | CLONE_VFORK) as the flags, above.
859        #[cfg(target_arch = "x86_64")]
860        Sysno::fork | Sysno::vfork => created(0).map(|t| (t, LifetimeOp::Create)),
861        Sysno::exit => Some((Task::Thread(f.tid), LifetimeOp::Exit)),
862        Sysno::exit_group => Some((Task::Process(f.pid), LifetimeOp::Exit)),
863        Sysno::execve | Sysno::execveat => Some((Task::Process(f.pid), LifetimeOp::Exec)),
864        Sysno::kill => {
865            let target = match raw(0) {
866                pid if pid > 0 => Some(Task::Process(DetPid::from_raw(pid))),
867                // The caller's own group, which the writer resolves.
868                0 => Some(Task::ProcessGroup(OWN_PROCESS_GROUP)),
869                -1 => None,
870                pid => Some(Task::ProcessGroup(DetPid::from_raw(pid.wrapping_neg()))),
871            };
872            signal(target, raw(1))
873        }
874        Sysno::tkill => signal(Some(Task::Thread(DetPid::from_raw(raw(0)))), raw(1)),
875        Sysno::tgkill | Sysno::rt_tgsigqueueinfo => {
876            signal(Some(Task::Thread(DetPid::from_raw(raw(1)))), raw(2))
877        }
878        Sysno::rt_sigqueueinfo => signal(Some(Task::Process(DetPid::from_raw(raw(0)))), raw(1)),
879        Sysno::pidfd_send_signal => signal(
880            f.fds
881                .get(&raw(0))
882                .and_then(|info| info.pidfd_target)
883                .map(Task::Process),
884            raw(1),
885        ),
886        Sysno::wait4 if f.result > 0 => Some((
887            Task::Process(DetPid::from_raw(f.result as i32)),
888            LifetimeOp::Wait,
889        )),
890        Sysno::waitid if f.result == 0 => f
891            .written
892            .waited_pid
893            .filter(|pid| *pid > 0)
894            .map(|pid| (Task::Process(DetPid::from_raw(pid)), LifetimeOp::Wait)),
895        _ => None,
896    };
897    if let Some((target, op)) = access {
898        out.push(Access::Lifetime { target, op });
899    }
900}
901
902/// kill(0)'s target as the guest thread writes it: the caller's own process
903/// group, which only the scheduler's process table knows.
904pub(crate) const OWN_PROCESS_GROUP: DetPid = DetPid::from_raw(0);
905
906/// The bytes returned through guest buffers, from the buffers' contents in
907/// order. `read` reads at most `want` bytes of a buffer and fails as the
908/// guest read would. For a call whose return value is output too (`time`, `alarm`,
909/// `times`), `value`'s eight little-endian bytes come first.
910pub(crate) fn summarize_output<E>(
911    value: Option<i64>,
912    buffers: &[ByteRange],
913    mut read: impl FnMut(u64, usize) -> Result<Vec<u8>, E>,
914) -> Option<ReturnedBytes> {
915    let value = value.map(i64::to_le_bytes);
916    let value_len = value.map_or(0, |bytes| bytes.len() as u64);
917    let len: u64 = value_len + buffers.iter().map(|b| b.end - b.start).sum::<u64>();
918    if len == 0 {
919        return None;
920    }
921    let mut digest = value.map_or(RETURNED_DIGEST_SEED, |bytes| {
922        returned_digest(RETURNED_DIGEST_SEED, &bytes)
923    });
924    let mut left = RETURNED_DIGEST_CAP - value_len;
925    for buffer in buffers {
926        if left == 0 {
927            break;
928        }
929        let want = (buffer.end - buffer.start).min(left);
930        let bytes = read(buffer.start, want as usize).ok()?;
931        digest = returned_digest(digest, &bytes);
932        left -= want;
933    }
934    Some(ReturnedBytes {
935        len,
936        digest,
937        truncated: len > RETURNED_DIGEST_CAP,
938    })
939}
940
941/// Whether `sysno` creates a thread or process and returns its tid: clone,
942/// clone3, and x86_64's legacy fork and vfork. aarch64's generic table
943/// (include/uapi/asm-generic/unistd.h) has no fork or vfork; its C library
944/// calls clone with SIGCHLD (and CLONE_VM | CLONE_VFORK) instead.
945fn creates_a_task(sysno: Sysno) -> bool {
946    match sysno {
947        Sysno::clone | Sysno::clone3 => true,
948        #[cfg(target_arch = "x86_64")]
949        Sysno::fork | Sysno::vfork => true,
950        _ => false,
951    }
952}
953
954/// Whether `sysno` returns two connected descriptors: pipe2, socketpair,
955/// and x86_64's legacy pipe.
956fn creates_a_channel_pair(sysno: Sysno) -> bool {
957    match sysno {
958        Sysno::pipe2 | Sysno::socketpair => true,
959        #[cfg(target_arch = "x86_64")]
960        Sysno::pipe => true,
961        _ => false,
962    }
963}
964
965// ---------------------------------------------------------------------------
966// The scheduler's side.
967
968/// Writes the trace as the scheduler records events. It lives in the
969/// scheduler, so records are numbered and ordered under the scheduler's lock.
970pub struct AccessTraceWriter {
971    /// `None` once a write failed: the trace then ends without its end line,
972    /// which marks it incomplete.
973    out: Option<Box<dyn Write + Send>>,
974    from: EventIndex,
975    records: u64,
976    /// `--access-trace-max-records`: the records to write at most.
977    max_records: Option<u64>,
978    /// The bytes written so far, the header's included.
979    bytes: u64,
980    /// `--access-trace-max-bytes`, or [`ACCESS_TRACE_DEFAULT_MAX_BYTES`]
981    /// without it: the bytes of header and records to write at most.
982    max_bytes: u64,
983    /// Whether `max_bytes` is the default rather than the option's, for the
984    /// warning that says the trace stopped.
985    default_max_bytes: bool,
986    /// Whether a record was left out for a cap; the trace then ends
987    /// without its end line, which marks it cut short.
988    capped: bool,
989    /// The trace's path, for the warnings that say why it ends early.
990    name: String,
991    /// Each thread's syscall between its prehook and its posthook.
992    open: BTreeMap<DetTid, SyscallRecord>,
993    /// The channel identity of each end of a pipe or socketpair.
994    pairs: BTreeMap<OpenFileId, ChannelId>,
995    /// What the synchronization edges need (design section 7).
996    edges: EdgeState,
997}
998
999/// The writer's view of who synchronized with whom, kept to emit an edge
1000/// once its later end is recorded.
1001#[derive(Debug, Default)]
1002struct EdgeState {
1003    /// Each thread's first and latest recorded event.
1004    first_event: BTreeMap<DetTid, EventIndex>,
1005    last_event: BTreeMap<DetTid, EventIndex>,
1006    /// The process of each thread, from its syscalls.
1007    process_of: BTreeMap<DetTid, DetPid>,
1008    /// Threads whose first event no clone's return has claimed yet: one
1009    /// that ran before its parent's clone returned, or the first thread.
1010    first_unclaimed: BTreeSet<DetTid>,
1011    /// The first event of each such thread that is gone, kept for its
1012    /// parent's clone return (see [`AccessTraceWriter::note_thread_gone`]).
1013    gone_first: BTreeMap<DetTid, EventIndex>,
1014    /// The latest event of any thread of each process.
1015    process_last: BTreeMap<DetPid, EventRef>,
1016    /// Edges that end at their waiter's next posthook: a futex wake, a
1017    /// requeue, an exit's clear-child-tid wake.
1018    at_posthook: BTreeMap<DetTid, Vec<PendingEdge>>,
1019    /// Creation edges whose child has not run yet.
1020    at_first_event: BTreeMap<DetTid, PendingEdge>,
1021    /// Signals sent and not yet delivered, in send order.
1022    signals: Vec<PendingSignal>,
1023    /// The sends and arms the cap evicted (see [`MAX_PENDING_SIGNALS`]),
1024    /// one per distinct target, signal and sender. A delivery one of them
1025    /// could have caused gets no edge from then on: its cause may be the
1026    /// evicted send, and the next pending candidate would claim more order
1027    /// than the run had.
1028    evicted: Vec<PendingSignal>,
1029    /// More distinct sends were evicted than [`MAX_PENDING_SIGNALS`]: no
1030    /// signal edge is written any more.
1031    evictions_overflowed: bool,
1032    /// The open syscalls of threads that are gone, which no posthook will
1033    /// end, written with the open records when the trace finishes.
1034    orphaned: Vec<SyscallRecord>,
1035}
1036
1037/// An edge whose `to` end is not recorded yet.
1038#[derive(Debug, Clone)]
1039struct PendingEdge {
1040    kind: EdgeKind,
1041    from: EventRef,
1042    object: SyncObject,
1043}
1044
1045/// A signal a thread sent (kill, tkill, tgkill, rt_sigqueueinfo,
1046/// rt_tgsigqueueinfo) or a timer it armed (timer_settime), waiting for the
1047/// delivery it causes.
1048#[derive(Debug, Clone, PartialEq)]
1049struct PendingSignal {
1050    from: EventRef,
1051    sender: DetPid,
1052    /// The call that sent it, which decides the `si_code` its delivery
1053    /// carries.
1054    kind: SendKind,
1055    signo: i32,
1056    target: Task,
1057    /// For a timer: its id. A timer's expiry carries it as `si_tid`.
1058    timer: Option<i32>,
1059    /// The call's posthook, once recorded: it succeeded.
1060    post: Option<EventIndex>,
1061    /// For a timer arm: its timer was deleted since.
1062    deleted: bool,
1063    /// For a group target: the members listed at its posthook, less the
1064    /// ones reaped since.
1065    members: Option<Vec<DetPid>>,
1066    /// For a group target: the processes it reached already.
1067    reached: Vec<DetPid>,
1068}
1069
1070/// Sends and arms kept waiting for their delivery, at most. Past it, one
1071/// goes: the oldest arm of a deleted timer, else the oldest send, else the
1072/// oldest arm, because a live timer's arm can cause expiries for the rest
1073/// of the run.
1074const MAX_PENDING_SIGNALS: usize = 4096;
1075
1076/// How a pending signal was sent.
1077#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1078enum SendKind {
1079    /// kill: its delivery carries `SI_USER` and the sender's pid.
1080    Kill,
1081    /// tkill, tgkill: `SI_TKILL` and the sender's pid.
1082    Tkill,
1083    /// rt_sigqueueinfo, rt_tgsigqueueinfo: the `si_code` and `si_pid` the
1084    /// caller chose. Linux refuses only a code of zero or more, or
1085    /// `SI_TKILL`, and only when the caller sends to another task
1086    /// (`do_rt_sigqueueinfo`,
1087    /// [kernel/signal.c:4188-4198 at v6.16](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/signal.c?h=v6.16#n4188),
1088    /// and `do_rt_tgsigqueueinfo`,
1089    /// [kernel/signal.c:4237-4242](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/signal.c?h=v6.16#n4237)),
1090    /// so its delivery's `si_pid` names no sender: Detcore cannot tell
1091    /// which send it was, and gives it no edge. A send to the caller's own
1092    /// thread, or with rt_sigqueueinfo of the caller's own thread id to its
1093    /// whole process, can carry any code, `SI_USER` and `SI_TKILL` among
1094    /// them, and the latter can reach any thread of the process
1095    /// (`complete_signal`,
1096    /// [kernel/signal.c:963-997 at v6.16](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/signal.c?h=v6.16#n963)).
1097    /// So while any thread of a process has a sigqueue send of a signal
1098    /// pending, no delivery of that signal to a thread of that process gets
1099    /// an edge.
1100    Queue,
1101    /// timer_settime: its expiry carries `SI_TIMER` and the timer's id.
1102    Timer,
1103}
1104
1105impl PendingSignal {
1106    /// Whether this send could carry `code` with `si_pid` `pid` to its
1107    /// delivery, where both are the kernel's (see [`SendKind`]).
1108    fn fits(&self, code: i32, pid: DetPid) -> bool {
1109        match self.kind {
1110            SendKind::Kill => code == libc::SI_USER && self.sender == pid,
1111            SendKind::Tkill => code == libc::SI_TKILL && self.sender == pid,
1112            SendKind::Queue | SendKind::Timer => false,
1113        }
1114    }
1115
1116    /// Whether this is a sigqueue send of `signo` by a thread of `process`,
1117    /// the receiving thread's, or of any process where that is unknown:
1118    /// sent to its own thread or process, it may carry any `si_code` and
1119    /// `si_pid` to any thread of that process (see [`SendKind::Queue`]).
1120    fn queued_within(&self, signo: i32, process: Option<DetPid>) -> bool {
1121        self.kind == SendKind::Queue
1122            && self.signo == signo
1123            && process.is_none_or(|process| process == self.sender)
1124    }
1125
1126    /// Whether `other` is the same send as far as a delivery can tell.
1127    fn same_key(&self, other: &PendingSignal) -> bool {
1128        self.kind == other.kind
1129            && self.sender == other.sender
1130            && self.signo == other.signo
1131            && self.target == other.target
1132            && self.timer == other.timer
1133            && (self.kind != SendKind::Queue || self.from.tid == other.from.tid)
1134    }
1135
1136    /// Whether a group send has reached every member listed at its posthook.
1137    fn reached_all(&self) -> bool {
1138        self.members
1139            .as_ref()
1140            .is_some_and(|members| members.iter().all(|m| self.reached.contains(m)))
1141    }
1142}
1143
1144/// The process table facts the writer resolves a group signal with.
1145pub trait ProcessGroups {
1146    /// The process group of `pid`.
1147    fn group_of(&self, pid: DetPid) -> Option<DetPid>;
1148    /// The processes of `group`, in pid order.
1149    fn members(&self, group: DetPid) -> Vec<DetPid>;
1150}
1151
1152impl std::fmt::Debug for AccessTraceWriter {
1153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1154        f.debug_struct("AccessTraceWriter")
1155            .field("writing", &self.out.is_some())
1156            .field("from", &self.from)
1157            .field("records", &self.records)
1158            .field("open", &self.open.len())
1159            .finish_non_exhaustive()
1160    }
1161}
1162
1163impl AccessTraceWriter {
1164    /// Create the trace at `path` and write its header.
1165    pub fn create(
1166        path: &Path,
1167        from: EventIndex,
1168        max_records: Option<u64>,
1169        max_bytes: Option<u64>,
1170    ) -> io::Result<Self> {
1171        let mut writer = Self::new(
1172            Box::new(BufWriter::with_capacity(1 << 20, File::create(path)?)),
1173            from,
1174            max_records,
1175            max_bytes,
1176        )?;
1177        writer.name = path.display().to_string();
1178        Ok(writer)
1179    }
1180
1181    /// Write the header of a trace to `out`.
1182    pub(crate) fn new(
1183        mut out: Box<dyn Write + Send>,
1184        from: EventIndex,
1185        max_records: Option<u64>,
1186        max_bytes: Option<u64>,
1187    ) -> io::Result<Self> {
1188        let header = TraceRecord::Header(TraceHeader::new(from)).to_line()?;
1189        out.write_all(&header)?;
1190        Ok(AccessTraceWriter {
1191            out: Some(out),
1192            from,
1193            records: 0,
1194            max_records,
1195            bytes: header.len() as u64,
1196            max_bytes: max_bytes.unwrap_or(ACCESS_TRACE_DEFAULT_MAX_BYTES),
1197            default_max_bytes: max_bytes.is_none(),
1198            capped: false,
1199            name: "the access trace".to_string(),
1200            open: BTreeMap::new(),
1201            pairs: BTreeMap::new(),
1202            edges: EdgeState::default(),
1203        })
1204    }
1205
1206    /// Take the event the scheduler just recorded at `index`, and its payload.
1207    pub fn observe(
1208        &mut self,
1209        index: EventIndex,
1210        event: &SchedEvent,
1211        payload: Option<AccessPayload>,
1212        groups: &dyn ProcessGroups,
1213    ) {
1214        let tid = event.dettid;
1215        self.note_event(tid, index);
1216        let Some(payload) = payload else {
1217            return;
1218        };
1219        match (payload, &event.op) {
1220            (AccessPayload::SyscallEntry(entry), Op::Syscall(_, SyscallPhase::Prehook)) => {
1221                self.edges.process_of.insert(tid, entry.pid);
1222                self.edges
1223                    .process_last
1224                    .insert(entry.pid, EventRef { tid, event: index });
1225                self.note_signal_send(EventRef { tid, event: index }, &entry);
1226                // A call that never reached its posthook (a restarted wait,
1227                // a successful execve) ends when its thread enters the next.
1228                if let Some(unfinished) = self.open.remove(&tid) {
1229                    self.finish_record(unfinished);
1230                }
1231                self.open.insert(
1232                    tid,
1233                    SyscallRecord {
1234                        pre: index,
1235                        post: None,
1236                        tid,
1237                        pid: entry.pid,
1238                        nth: entry.nth,
1239                        sysno: entry.sysno,
1240                        args: entry.args,
1241                        result: None,
1242                        accesses: entry.accesses,
1243                        returned: None,
1244                        out_bytes: None,
1245                    },
1246                );
1247            }
1248            (AccessPayload::SyscallExit(outcome), Op::Syscall(_, SyscallPhase::Posthook)) => {
1249                let Some(mut record) = self.open.remove(&tid) else {
1250                    return;
1251                };
1252                let outcome = *outcome;
1253                record.post = Some(index);
1254                record.result = Some(outcome.result);
1255                record.accesses = resolve_groups(outcome.accesses, record.pid, groups);
1256                record.returned = outcome.returned;
1257                record.out_bytes = outcome.out_bytes;
1258                let to = EventRef { tid, event: index };
1259                for edge in self.edges.at_posthook.remove(&tid).unwrap_or_default() {
1260                    self.write_edge(edge, to);
1261                }
1262                self.syscall_edges(&record, to);
1263                self.finish_record(record);
1264            }
1265            (AccessPayload::Signal(note), Op::SignalReceived(_)) => {
1266                let to = EventRef { tid, event: index };
1267                let sent = self.take_signal_send(&note, tid, groups);
1268                if let Some(from) = sent {
1269                    self.write_edge(
1270                        PendingEdge {
1271                            kind: EdgeKind::Signal,
1272                            from,
1273                            object: SyncObject::Signal(note.signo),
1274                        },
1275                        to,
1276                    );
1277                }
1278                if index >= self.from {
1279                    let sender = note.sender.map(|sender| SignalSender {
1280                        tid: sent.map(|from| from.tid),
1281                        ..sender
1282                    });
1283                    self.write(TraceRecord::Signal(SignalRecord {
1284                        event: index,
1285                        tid,
1286                        signo: note.signo,
1287                        code: note.code,
1288                        addr: note.addr,
1289                        sender,
1290                        observed_disposition: note.observed_disposition,
1291                    }));
1292                }
1293            }
1294            (payload, op) => warn!(
1295                "[access trace] dropped a payload that does not match its event {:?}: {:?}",
1296                op, payload
1297            ),
1298        }
1299    }
1300
1301    /// A thread's event at `index`: the first one of a new thread ends the
1302    /// creation edge that started it.
1303    fn note_event(&mut self, tid: DetTid, index: EventIndex) {
1304        let to = EventRef { tid, event: index };
1305        if let std::collections::btree_map::Entry::Vacant(first) = self.edges.first_event.entry(tid)
1306        {
1307            first.insert(index);
1308            self.edges.gone_first.remove(&tid);
1309            if let Some(edge) = self.edges.at_first_event.remove(&tid) {
1310                self.write_edge(edge, to);
1311            } else {
1312                self.edges.first_unclaimed.insert(tid);
1313            }
1314        }
1315        self.edges.last_event.insert(tid, index);
1316        if let Some(pid) = self.edges.process_of.get(&tid).copied() {
1317            self.edges.process_last.insert(pid, to);
1318        }
1319    }
1320
1321    /// `waker`'s operation released `waiter` through `object`: a futex wake
1322    /// or requeue (from the waker's syscall) or an exit's wake (from the
1323    /// exiting thread's last event). The edge ends at the waiter's next
1324    /// posthook, the return of the wait it was released from.
1325    pub fn note_wake(&mut self, kind: EdgeKind, waker: DetTid, waiter: DetTid, object: SyncObject) {
1326        let syscall = self.open.get(&waker).map(|record| record.pre);
1327        let last = self.edges.last_event.get(&waker).copied();
1328        let from = match kind {
1329            EdgeKind::ExitJoin => last,
1330            _ => syscall.or(last),
1331        };
1332        let Some(event) = from else {
1333            return;
1334        };
1335        self.edges
1336            .at_posthook
1337            .entry(waiter)
1338            .or_default()
1339            .push(PendingEdge {
1340                kind,
1341                from: EventRef { tid: waker, event },
1342                object,
1343            });
1344    }
1345
1346    /// The edges a finished syscall ends or starts: a clone's child, a
1347    /// reaped child's exit, and a failed send that will deliver nothing.
1348    fn syscall_edges(&mut self, record: &SyscallRecord, to: EventRef) {
1349        let result = record.result.unwrap_or(-1);
1350        let from = EventRef {
1351            tid: record.tid,
1352            event: record.pre,
1353        };
1354        match record.sysno {
1355            sysno if creates_a_task(sysno) && result > 0 => {
1356                let child = DetTid::from_raw(result as i32);
1357                let edge = PendingEdge {
1358                    kind: EdgeKind::Create,
1359                    from,
1360                    object: SyncObject::Thread(child),
1361                };
1362                // The child may have run before its parent's clone returned,
1363                // and even be gone.
1364                self.edges.first_unclaimed.remove(&child);
1365                let first = self.edges.first_event.get(&child).copied();
1366                match first.or_else(|| self.edges.gone_first.remove(&child)) {
1367                    Some(first) if first > record.pre => self.write_edge(
1368                        edge,
1369                        EventRef {
1370                            tid: child,
1371                            event: first,
1372                        },
1373                    ),
1374                    _ => {
1375                        self.edges.first_event.remove(&child);
1376                        self.edges.at_first_event.insert(child, edge);
1377                    }
1378                }
1379            }
1380            Sysno::wait4 | Sysno::waitid => {
1381                let mut reaped = Vec::new();
1382                for access in &record.accesses {
1383                    if let Access::Lifetime {
1384                        target: Task::Process(child),
1385                        op: LifetimeOp::Wait,
1386                    } = access
1387                    {
1388                        reaped.push(*child);
1389                        if let Some(exit) = self.edges.process_last.get(child).copied()
1390                            && exit.event < to.event
1391                        {
1392                            self.write_edge(
1393                                PendingEdge {
1394                                    kind: EdgeKind::ChildExit,
1395                                    from: exit,
1396                                    object: SyncObject::Process(*child),
1397                                },
1398                                to,
1399                            );
1400                        }
1401                    }
1402                }
1403                for child in reaped {
1404                    self.forget_reaped(child);
1405                }
1406            }
1407            _ => {}
1408        }
1409        self.settle_signal_send(record);
1410    }
1411
1412    /// At a send's posthook: drop it if it failed; otherwise mark it
1413    /// completed and fix a group target and its members. A timer_delete
1414    /// marks its timer's arms deleted: one expiry dequeued before the
1415    /// delete can still be delivered after it.
1416    fn settle_signal_send(&mut self, record: &SyscallRecord) {
1417        let result = record.result.unwrap_or(-1);
1418        if record.sysno == Sysno::timer_delete && result == 0 {
1419            let timer = Some(record.args[0] as i32);
1420            for signal in &mut self.edges.signals {
1421                if signal.timer == timer && signal.sender == record.pid {
1422                    signal.deleted = true;
1423                }
1424            }
1425        }
1426        let from = EventRef {
1427            tid: record.tid,
1428            event: record.pre,
1429        };
1430        let Some(position) = self.edges.signals.iter().position(|s| s.from == from) else {
1431            return;
1432        };
1433        if result < 0 {
1434            self.edges.signals.remove(position);
1435            return;
1436        }
1437        let signal = &mut self.edges.signals[position];
1438        signal.post = record.post;
1439        if matches!(signal.target, Task::ProcessGroup(_)) {
1440            let mut members = Vec::new();
1441            for access in &record.accesses {
1442                if let Access::Lifetime { target, .. } = access {
1443                    match target {
1444                        Task::ProcessGroup(group) => signal.target = Task::ProcessGroup(*group),
1445                        Task::Process(member) => members.push(*member),
1446                        Task::Thread(_) => {}
1447                    }
1448                }
1449            }
1450            signal.members = Some(members);
1451            if signal.reached_all() {
1452                self.edges.signals.remove(position);
1453            }
1454        }
1455    }
1456
1457    /// `child` was reaped, so nothing pending can reach it any more: drop
1458    /// the sends to it or its threads and its timers' arms, and take it out
1459    /// of the group sends still waiting for it.
1460    fn forget_reaped(&mut self, child: DetPid) {
1461        let process_of = &self.edges.process_of;
1462        let mut reaches = |signal: &mut PendingSignal| match signal.target {
1463            _ if signal.timer.is_some() => signal.sender != child,
1464            Task::Process(target) => target != child,
1465            Task::Thread(thread) => process_of.get(&thread) != Some(&child),
1466            Task::ProcessGroup(_) => {
1467                if let Some(members) = &mut signal.members {
1468                    members.retain(|member| *member != child);
1469                }
1470                !signal.reached_all()
1471            }
1472        };
1473        self.edges.signals.retain_mut(&mut reaches);
1474        self.edges.evicted.retain_mut(&mut reaches);
1475    }
1476
1477    /// `tid` is gone: the scheduler retired it, after the wakes its exit
1478    /// makes. Forget what the writer keeps for it, so a later task that
1479    /// reuses its id ends none of its edges and is not taken for it: the
1480    /// edges waiting for its next posthook or first event, its first and
1481    /// latest events, its process, and the sends to it alone, which Linux
1482    /// discards with it. Its open syscall, which no posthook will end, is
1483    /// kept for the end of the trace, where every open record is written.
1484    /// So is its first event while no clone's return has claimed it: a
1485    /// child can run and be gone before its parent's clone returns (a
1486    /// forked child that exits at once), and that return still starts its
1487    /// creation edge.
1488    pub fn note_thread_gone(&mut self, tid: DetTid) {
1489        if let Some(record) = self.open.remove(&tid) {
1490            self.edges.orphaned.push(record);
1491        }
1492        let edges = &mut self.edges;
1493        if edges.first_unclaimed.remove(&tid)
1494            && let Some(first) = edges.first_event.get(&tid)
1495        {
1496            edges.gone_first.insert(tid, *first);
1497        }
1498        edges.at_posthook.remove(&tid);
1499        edges.at_first_event.remove(&tid);
1500        edges.first_event.remove(&tid);
1501        edges.last_event.remove(&tid);
1502        edges.process_of.remove(&tid);
1503        let to_it = |signal: &PendingSignal| signal.target == Task::Thread(tid);
1504        edges.signals.retain(|signal| !to_it(signal));
1505        edges.evicted.retain(|signal| !to_it(signal));
1506    }
1507
1508    /// `former` exec'd from a thread other than its process's leader and
1509    /// goes on as `leader`. Linux keeps the exec caller's task and gives it
1510    /// the leader's thread id, and the signals pending for that task alone
1511    /// stay queued on it. So the sends to `former` alone, pending or
1512    /// evicted, now target `leader`. The scheduler calls this after it has
1513    /// retired the old leader, whose own sends Linux discards with it, and
1514    /// before it retires `former`'s id ([`Self::note_thread_gone`]), which
1515    /// would otherwise drop them: a sigqueue send to itself that keeps its
1516    /// process's deliveries of that signal without an edge
1517    /// (`SendKind::Queue`), and a tkill whose delivery to the leader it
1518    /// causes.
1519    pub fn rekey_thread(&mut self, former: DetTid, leader: DetTid) {
1520        let edges = &mut self.edges;
1521        for signal in edges.signals.iter_mut().chain(edges.evicted.iter_mut()) {
1522            if signal.target == Task::Thread(former) {
1523                signal.target = Task::Thread(leader);
1524            }
1525        }
1526    }
1527
1528    /// Make room for one more pending send (see [`MAX_PENDING_SIGNALS`]).
1529    fn evict_pending_signal(&mut self) {
1530        let signals = &self.edges.signals;
1531        let victim = signals
1532            .iter()
1533            .position(|signal| signal.deleted)
1534            .or_else(|| signals.iter().position(|signal| signal.timer.is_none()))
1535            .unwrap_or(0);
1536        let evicted = self.edges.signals.remove(victim);
1537        let edges = &mut self.edges;
1538        if !edges.evicted.iter().any(|known| known.same_key(&evicted)) {
1539            if edges.evicted.len() == MAX_PENDING_SIGNALS {
1540                edges.evictions_overflowed = true;
1541            } else {
1542                edges.evicted.push(evicted);
1543            }
1544        }
1545    }
1546
1547    /// Note a signal a syscall entry sends, or a timer it arms.
1548    fn note_signal_send(&mut self, from: EventRef, entry: &SyscallEntry) {
1549        let raw = |i: usize| entry.args[i] as i32;
1550        let pid = DetPid::from_raw;
1551        let (target, signo, timer, kind) = match entry.sysno {
1552            Sysno::kill => {
1553                let target = match raw(0) {
1554                    p if p > 0 => Task::Process(pid(p)),
1555                    0 => Task::ProcessGroup(OWN_PROCESS_GROUP),
1556                    -1 => return,
1557                    p => Task::ProcessGroup(pid(p.wrapping_neg())),
1558                };
1559                (target, raw(1), None, SendKind::Kill)
1560            }
1561            Sysno::tkill => (Task::Thread(pid(raw(0))), raw(1), None, SendKind::Tkill),
1562            Sysno::tgkill => (Task::Thread(pid(raw(1))), raw(2), None, SendKind::Tkill),
1563            Sysno::rt_tgsigqueueinfo => (Task::Thread(pid(raw(1))), raw(2), None, SendKind::Queue),
1564            Sysno::rt_sigqueueinfo => (Task::Process(pid(raw(0))), raw(1), None, SendKind::Queue),
1565            // The expiry's signal comes from the sigevent timer_create
1566            // named, so a timer arm matches any signal carrying its id.
1567            Sysno::timer_settime => (Task::Process(entry.pid), 0, Some(raw(0)), SendKind::Timer),
1568            _ => return,
1569        };
1570        if signo == 0 && timer.is_none() {
1571            return;
1572        }
1573        if self.edges.signals.len() == MAX_PENDING_SIGNALS {
1574            self.evict_pending_signal();
1575        }
1576        self.edges.signals.push(PendingSignal {
1577            from,
1578            sender: entry.pid,
1579            kind,
1580            signo,
1581            target,
1582            timer,
1583            post: None,
1584            deleted: false,
1585            members: None,
1586            reached: Vec::new(),
1587        });
1588    }
1589
1590    /// The send that caused `note`'s delivery to `tid`, if one is pending.
1591    ///
1592    /// A candidate is a send of that signal, to that thread, its process or
1593    /// its group, by the call the delivery's `si_code` names, from the
1594    /// process `si_pid` names: a kill for `SI_USER`, a tkill or tgkill for
1595    /// `SI_TKILL`. A delivery with any other code, or one to a process a
1596    /// thread of which has queued the signal, gets no edge, because its
1597    /// `si_pid` may be the caller's choice (see [`SendKind::Queue`]). A
1598    /// send the cap evicted counts as the earliest candidate of every
1599    /// delivery it could have caused, and such a delivery gets no edge. The
1600    /// delivery takes the earliest candidate and uses up that one alone (a
1601    /// group send only for this process, until it has reached every member
1602    /// listed at its posthook). That holds for standard signals too, whose sends merge:
1603    /// the k-th delivery is never matched with a send later than the
1604    /// latest cause among the first k deliveries, so no edge claims more
1605    /// order than the run had. Taking the latest send instead is unsound:
1606    /// on ptrace the kernel dequeues a signal before the stop at which its
1607    /// delivery is recorded, so a send recorded in between can queue a
1608    /// second instance, and a send to the process and one to the thread
1609    /// queue separately and deliver twice.
1610    ///
1611    /// The induction needs the candidates' sends to have run in the order
1612    /// of their prehooks. A send runs in its prehook's turn (handle_kill and
1613    /// handle_tgkill request no turn before record_or_replay), except that
1614    /// a `--happens-before` checkpoint requests one between them. So the
1615    /// earliest candidate gives an edge only when its posthook comes before
1616    /// every later candidate's prehook, which a later call of the same
1617    /// thread always satisfies; otherwise it is used up without an edge.
1618    /// The merged sends a delivery leaves pending stay until the cap or
1619    /// reaping removes them: they can give weaker edges, never false ones.
1620    ///
1621    /// A timer's expiry takes the oldest live arm of the timer whose id it
1622    /// carries, for the same reason. A timer holds one queued signal, so
1623    /// once an expiry recorded after a newer arm's posthook has matched the
1624    /// oldest arm, that arm can cause no more; after timer_delete, at most
1625    /// one expiry dequeued before it remains.
1626    fn take_signal_send(
1627        &mut self,
1628        note: &SignalNote,
1629        tid: DetTid,
1630        groups: &dyn ProcessGroups,
1631    ) -> Option<EventRef> {
1632        let edges = &mut self.edges;
1633        if edges.evictions_overflowed {
1634            return None;
1635        }
1636        let process = edges.process_of.get(&tid).copied();
1637        let signals = &mut edges.signals;
1638        let evicted = &edges.evicted;
1639        if let Some(timer) = note.timer {
1640            let process = process?;
1641            let arm =
1642                |signal: &PendingSignal| signal.timer == Some(timer) && signal.sender == process;
1643            if evicted.iter().any(arm) {
1644                return None;
1645            }
1646            let arms: Vec<usize> = (0..signals.len())
1647                .filter(|&i| signals[i].timer == Some(timer) && signals[i].sender == process)
1648                .collect();
1649            let oldest = *arms.first()?;
1650            let from = signals[oldest].from;
1651            if signals[oldest].deleted {
1652                signals.retain(|s| !(s.timer == Some(timer) && s.sender == process && s.deleted));
1653            } else if arms[1..].iter().any(|&i| signals[i].post.is_some()) {
1654                signals.remove(oldest);
1655            }
1656            return Some(from);
1657        }
1658        // Only kill's SI_USER and tkill's SI_TKILL carry a sender the kernel
1659        // filled in, and not even those while a thread of the receiving
1660        // process has queued the signal (see [`SendKind::Queue`]).
1661        if !matches!(note.code, libc::SI_USER | libc::SI_TKILL)
1662            || signals
1663                .iter()
1664                .chain(evicted)
1665                .any(|signal| signal.queued_within(note.signo, process))
1666        {
1667            return None;
1668        }
1669        let sender = note.sender?.pid;
1670        let group = process.and_then(|p| groups.group_of(p));
1671        let candidate = |signal: &PendingSignal| {
1672            let target = match signal.target {
1673                Task::ProcessGroup(OWN_PROCESS_GROUP) => {
1674                    Task::ProcessGroup(groups.group_of(signal.sender).unwrap_or(OWN_PROCESS_GROUP))
1675                }
1676                target => target,
1677            };
1678            signal.timer.is_none()
1679                && signal.signo == note.signo
1680                && signal.fits(note.code, sender)
1681                && match target {
1682                    Task::Thread(thread) => thread == tid,
1683                    Task::Process(target) => Some(target) == process,
1684                    Task::ProcessGroup(target) => {
1685                        Some(target) == group
1686                            && process.is_some_and(|p| !signal.reached.contains(&p))
1687                    }
1688                }
1689        };
1690        // An evicted send is older than every pending one, so it would be
1691        // the earliest candidate.
1692        if evicted.iter().any(&candidate) {
1693            return None;
1694        }
1695        let candidates: Vec<usize> = (0..signals.len())
1696            .filter(|&i| candidate(&signals[i]))
1697            .collect();
1698        let chosen = *candidates.first()?;
1699        let post = signals[chosen].post;
1700        let ran_first = candidates[1..]
1701            .iter()
1702            .all(|&i| post.is_some_and(|post| post < signals[i].from.event));
1703        let signal = &mut signals[chosen];
1704        let from = ran_first.then_some(signal.from);
1705        let used_up = match signal.target {
1706            Task::ProcessGroup(_) => {
1707                signal.reached.extend(process);
1708                signal.reached_all()
1709            }
1710            _ => true,
1711        };
1712        if used_up {
1713            signals.remove(chosen);
1714        }
1715        from
1716    }
1717
1718    /// Write `edge` ending at `to`, unless `to` is before
1719    /// `--access-trace-from`.
1720    fn write_edge(&mut self, edge: PendingEdge, to: EventRef) {
1721        // Every edge source names an event recorded before its later end;
1722        // a backward edge would be a writer bug, not a property of the run.
1723        debug_assert!(
1724            edge.from.event < to.event,
1725            "a backward edge {edge:?} to {to:?}"
1726        );
1727        if to.event >= self.from && edge.from.event < to.event {
1728            self.write(TraceRecord::Edge(EdgeRecord {
1729                kind: edge.kind,
1730                from: edge.from,
1731                to,
1732                object: edge.object,
1733            }));
1734        }
1735    }
1736
1737    /// Name the channels a finished record created, fill in the channel
1738    /// identities of its accesses, and write it unless it ends before
1739    /// `--access-trace-from`.
1740    fn finish_record(&mut self, mut record: SyscallRecord) {
1741        if creates_a_channel_pair(record.sysno) && record.result == Some(0) {
1742            let opened: Vec<OpenFileId> = record
1743                .accesses
1744                .iter()
1745                .filter_map(|access| match access {
1746                    Access::Fd {
1747                        open_file: Some(open_file),
1748                        op: FdOp::Open { .. },
1749                        ..
1750                    } => Some(*open_file),
1751                    _ => None,
1752                })
1753                .collect();
1754            if let [first, second] = opened[..] {
1755                let id = ChannelId::Pair(first);
1756                self.pairs.insert(first, id);
1757                self.pairs.insert(second, id);
1758            }
1759        }
1760        for access in &mut record.accesses {
1761            if let Access::Channel { open_file, id, .. } = access
1762                && id.is_none()
1763            {
1764                *id = self.pairs.get(open_file).copied();
1765            }
1766        }
1767        if record.post.unwrap_or(record.pre) >= self.from {
1768            self.write(TraceRecord::Syscall(record));
1769        }
1770    }
1771
1772    fn write(&mut self, record: TraceRecord) {
1773        let Some(out) = self.out.as_mut() else {
1774            return;
1775        };
1776        let line = record.to_line();
1777        let cap = if let Some(max) = self.max_records.filter(|max| self.records >= *max) {
1778            Some(format!("--access-trace-max-records={max}"))
1779        } else if let Ok(line) = &line
1780            && self.bytes.saturating_add(line.len() as u64) > self.max_bytes
1781        {
1782            Some(format!(
1783                "--access-trace-max-bytes={}{}",
1784                self.max_bytes,
1785                if self.default_max_bytes {
1786                    " (the default)"
1787                } else {
1788                    ""
1789                }
1790            ))
1791        } else {
1792            None
1793        };
1794        if let Some(cap) = cap {
1795            // A cap: stop here, before the first record past it, and leave
1796            // the end line out. Said once, as out is gone after.
1797            warn!(
1798                "[access trace] {} reached {}; it stops here, without its end line",
1799                self.name, cap
1800            );
1801            if let Err(error) = out.flush() {
1802                warn!("[access trace] flushing {} failed ({})", self.name, error);
1803            }
1804            self.out = None;
1805            self.capped = true;
1806            return;
1807        }
1808        match line.and_then(|line| {
1809            out.write_all(&line)?;
1810            Ok(line.len() as u64)
1811        }) {
1812            Ok(written) => {
1813                self.records += 1;
1814                self.bytes += written;
1815            }
1816            Err(error) => {
1817                warn!(
1818                    "[access trace] writing the trace failed ({}); it ends here, without its end line",
1819                    error
1820                );
1821                self.out = None;
1822            }
1823        }
1824    }
1825
1826    /// Write the calls that never returned and the end line, and flush.
1827    /// `events` is the number of events the run recorded.
1828    pub fn finish(mut self, events: u64) -> io::Result<()> {
1829        let mut open: Vec<SyscallRecord> = std::mem::take(&mut self.edges.orphaned);
1830        open.extend(std::mem::take(&mut self.open).into_values());
1831        open.sort_by_key(|record| (record.tid, record.pre));
1832        for record in open {
1833            self.finish_record(record);
1834        }
1835        let records = self.records;
1836        if self.capped {
1837            return Ok(());
1838        }
1839        let Some(mut out) = self.out.take() else {
1840            return Err(io::Error::other(
1841                "an earlier write failed; the trace has no end line",
1842            ));
1843        };
1844        TraceRecord::End(TraceEnd { events, records }).write_line(&mut out)?;
1845        out.flush()
1846    }
1847}
1848
1849/// A writer whose trace stays in memory, and the bytes it wrote, for the
1850/// scheduler's tests.
1851#[cfg(test)]
1852pub(crate) fn in_memory_writer() -> (AccessTraceWriter, std::sync::Arc<Mutex<Vec<u8>>>) {
1853    #[derive(Clone)]
1854    struct Shared(std::sync::Arc<Mutex<Vec<u8>>>);
1855    impl Write for Shared {
1856        fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
1857            self.0.lock().unwrap().extend_from_slice(bytes);
1858            Ok(bytes.len())
1859        }
1860        fn flush(&mut self) -> io::Result<()> {
1861            Ok(())
1862        }
1863    }
1864    let bytes = std::sync::Arc::new(Mutex::new(Vec::new()));
1865    let writer =
1866        AccessTraceWriter::new(Box::new(Shared(bytes.clone())), 0, None, None).expect("in memory");
1867    (writer, bytes)
1868}
1869
1870/// The edges of a finished in-memory trace.
1871#[cfg(test)]
1872pub(crate) fn edges_of(bytes: &Mutex<Vec<u8>>) -> Vec<EdgeRecord> {
1873    let bytes = bytes.lock().unwrap().clone();
1874    detcore_model::access_trace::read_access_trace(&bytes[..])
1875        .expect("trace parses")
1876        .records
1877        .into_iter()
1878        .filter_map(|record| match record {
1879            TraceRecord::Edge(edge) => Some(edge),
1880            _ => None,
1881        })
1882        .collect()
1883}
1884
1885/// Add one process target per member to each group signal, and resolve the
1886/// caller's own group.
1887fn resolve_groups(
1888    accesses: Vec<Access>,
1889    caller: DetPid,
1890    groups: &dyn ProcessGroups,
1891) -> Vec<Access> {
1892    let mut out = Vec::with_capacity(accesses.len());
1893    for access in accesses {
1894        let Access::Lifetime {
1895            target: Task::ProcessGroup(group),
1896            op,
1897        } = access
1898        else {
1899            out.push(access);
1900            continue;
1901        };
1902        let group = if group == OWN_PROCESS_GROUP {
1903            match groups.group_of(caller) {
1904                Some(group) => group,
1905                None => continue,
1906            }
1907        } else {
1908            group
1909        };
1910        out.push(Access::Lifetime {
1911            target: Task::ProcessGroup(group),
1912            op,
1913        });
1914        for member in groups.members(group) {
1915            out.push(Access::Lifetime {
1916                target: Task::Process(member),
1917                op,
1918            });
1919        }
1920    }
1921    out
1922}
1923
1924#[cfg(test)]
1925mod tests {
1926    use std::sync::Arc;
1927
1928    use detcore_model::access_trace::TraceRecord;
1929    use detcore_model::access_trace::read_access_trace;
1930
1931    use super::*;
1932
1933    fn tid(n: i32) -> DetTid {
1934        DetTid::from_raw(n)
1935    }
1936
1937    fn files() -> FilesId {
1938        FilesId::initial(tid(3))
1939    }
1940
1941    fn slot(fd: i32) -> FdSlot {
1942        FdSlot { files: files(), fd }
1943    }
1944
1945    fn file(creator: i32, sequence: u64) -> OpenFileId {
1946        OpenFileId::new(tid(creator), sequence)
1947    }
1948
1949    fn info(open_file: OpenFileId, ty: FdType) -> FdInfo {
1950        FdInfo {
1951            open_file,
1952            ty,
1953            inode: Some(DetInode::mint(40)),
1954            fifo: false,
1955            pidfd_target: None,
1956        }
1957    }
1958
1959    fn mm() -> MmId {
1960        MmId::initial(tid(3))
1961    }
1962
1963    fn private_key(addr: u64, private: bool) -> FutexID {
1964        if private {
1965            FutexID::private(mm(), addr as usize)
1966        } else {
1967            FutexID::private(MmId::initial(tid(99)), addr as usize)
1968        }
1969    }
1970
1971    /// The accesses of one call, with the given descriptors before it and
1972    /// slot changes during it.
1973    fn run(
1974        sysno: Sysno,
1975        args: [u64; 6],
1976        result: i64,
1977        fds: &[(i32, FdInfo)],
1978        notes: &[FdNote],
1979        written: Written,
1980    ) -> Vec<Access> {
1981        let fds: BTreeMap<i32, FdInfo> = fds.iter().cloned().collect();
1982        accesses(&CallFacts {
1983            sysno,
1984            args,
1985            result,
1986            pid: DetPid::from_raw(3),
1987            tid: tid(5),
1988            files: files(),
1989            mm: mm(),
1990            fds: &fds,
1991            notes,
1992            buffers: &[],
1993            written: &written,
1994            futex_id: &private_key,
1995        })
1996    }
1997
1998    fn note(fd: i32, open_file: Option<OpenFileId>, op: FdOp) -> FdNote {
1999        FdNote {
2000            slot: slot(fd),
2001            open_file,
2002            op,
2003            released: None,
2004        }
2005    }
2006
2007    /// The note of a close that released `info`.
2008    fn closed(fd: i32, info: &FdInfo) -> FdNote {
2009        FdNote {
2010            released: Some(info.clone()),
2011            ..note(fd, Some(info.open_file), FdOp::Close)
2012        }
2013    }
2014
2015    /// Every payload crosses RPC as Reverie's positional bincode, which a
2016    /// debug build checks by decoding each request it encodes.
2017    #[test]
2018    fn payloads_round_trip_through_rpc_bincode() {
2019        let payloads = [
2020            entry(Sysno::futex, 7),
2021            AccessPayload::SyscallExit(Box::new(SyscallOutcome {
2022                result: -11,
2023                accesses: vec![
2024                    Access::Fd {
2025                        slot: slot(6),
2026                        open_file: None,
2027                        op: FdOp::Open {
2028                            path: Some("/tmp/x".into()),
2029                        },
2030                    },
2031                    Access::Futex {
2032                        futex: FutexID::private(mm(), 0x1000),
2033                        op: FutexOp::Wait,
2034                    },
2035                ],
2036                returned: Some(ReturnedBytes {
2037                    len: 3,
2038                    digest: 9,
2039                    truncated: false,
2040                }),
2041                out_bytes: Some(b"hi".to_vec()),
2042            })),
2043            AccessPayload::Signal(signal_note(
2044                libc::SIGUSR1,
2045                None,
2046                ObservedSignalDisposition::Handler,
2047            )),
2048            AccessPayload::Signal(signal_note(
2049                libc::SIGUSR1,
2050                None,
2051                ObservedSignalDisposition::Ignored,
2052            )),
2053            AccessPayload::Signal(signal_note(
2054                libc::SIGUSR1,
2055                None,
2056                ObservedSignalDisposition::Default,
2057            )),
2058            AccessPayload::Signal(signal_note(
2059                libc::SIGUSR1,
2060                None,
2061                ObservedSignalDisposition::Unknown,
2062            )),
2063        ];
2064        for payload in payloads.clone() {
2065            let request = crate::tool_global::GlobalRequest::TraceSchedEvent(
2066                prehook(5, Sysno::futex),
2067                DetPid::from_raw(3),
2068                false,
2069                Some(Box::new(payload)),
2070            );
2071            let wire = bincode::serde::encode_to_vec(&request, bincode::config::legacy()).unwrap();
2072            let (back, _): (crate::tool_global::GlobalRequest, usize) =
2073                bincode::serde::decode_from_slice(&wire, bincode::config::legacy()).unwrap();
2074            assert_eq!(back, request);
2075        }
2076        for payload in payloads {
2077            let wire = bincode::serde::encode_to_vec(&payload, bincode::config::legacy()).unwrap();
2078            let (back, read): (AccessPayload, usize) =
2079                bincode::serde::decode_from_slice(&wire, bincode::config::legacy()).unwrap();
2080            assert_eq!(read, wire.len());
2081            assert_eq!(back, payload);
2082        }
2083    }
2084
2085    #[test]
2086    fn close_releases_the_open_file_the_slot_held() {
2087        let pipe = info(file(7, 2), FdType::Pipe);
2088        let got = run(
2089            Sysno::close,
2090            [6, 0, 0, 0, 0, 0],
2091            0,
2092            &[(6, pipe.clone())],
2093            &[closed(6, &pipe)],
2094            Written::default(),
2095        );
2096        assert_eq!(
2097            got,
2098            vec![
2099                Access::Fd {
2100                    slot: slot(6),
2101                    open_file: Some(file(7, 2)),
2102                    op: FdOp::Close
2103                },
2104                Access::Channel {
2105                    open_file: file(7, 2),
2106                    id: None,
2107                    channel: ChannelKind::Pipe,
2108                    op: ChannelOp::Close
2109                },
2110            ]
2111        );
2112
2113        // A second close of the number releases nothing; it is still a close.
2114        let again = run(
2115            Sysno::close,
2116            [6, 0, 0, 0, 0, 0],
2117            -libc::EBADF as i64,
2118            &[],
2119            &[],
2120            Written::default(),
2121        );
2122        assert_eq!(
2123            again,
2124            vec![Access::Fd {
2125                slot: slot(6),
2126                open_file: None,
2127                op: FdOp::Close
2128            }]
2129        );
2130    }
2131
2132    #[test]
2133    fn dup2_onto_an_open_number_closes_it_then_dups() {
2134        let notes = [
2135            closed(5, &info(file(3, 1), FdType::Regular)),
2136            note(5, Some(file(7, 3)), FdOp::Dup { source: 6 }),
2137        ];
2138        let got = run(
2139            Sysno::dup3,
2140            [6, 5, libc::O_CLOEXEC as u64, 0, 0, 0],
2141            5,
2142            &[
2143                (6, info(file(7, 3), FdType::Regular)),
2144                (5, info(file(3, 1), FdType::Regular)),
2145            ],
2146            &notes,
2147            Written::default(),
2148        );
2149        assert_eq!(
2150            got,
2151            vec![
2152                Access::Fd {
2153                    slot: slot(6),
2154                    open_file: Some(file(7, 3)),
2155                    op: FdOp::Use
2156                },
2157                Access::Fd {
2158                    slot: slot(5),
2159                    open_file: Some(file(3, 1)),
2160                    op: FdOp::Close
2161                },
2162                Access::Fd {
2163                    slot: slot(5),
2164                    open_file: Some(file(7, 3)),
2165                    op: FdOp::Dup { source: 6 }
2166                },
2167            ]
2168        );
2169        assert_eq!(named_fds(Sysno::dup3, [6, 5, 0, 0, 0, 0]), vec![5, 6]);
2170        #[cfg(target_arch = "x86_64")]
2171        assert_eq!(named_fds(Sysno::dup2, [6, 5, 0, 0, 0, 0]), vec![5, 6]);
2172    }
2173
2174    /// close_range lists every slot it released, and the pipe end behind
2175    /// one, though its arguments name no descriptor.
2176    #[test]
2177    fn close_range_lists_every_slot_it_released() {
2178        let regular = info(file(3, 1), FdType::Regular);
2179        let pipe = info(file(3, 2), FdType::Pipe);
2180        let got = run(
2181            Sysno::close_range,
2182            [3, u32::MAX as u64, 0, 0, 0, 0],
2183            0,
2184            &[],
2185            &[closed(3, &regular), closed(4, &pipe)],
2186            Written::default(),
2187        );
2188        assert_eq!(
2189            got,
2190            vec![
2191                Access::Fd {
2192                    slot: slot(3),
2193                    open_file: Some(file(3, 1)),
2194                    op: FdOp::Close
2195                },
2196                Access::Fd {
2197                    slot: slot(4),
2198                    open_file: Some(file(3, 2)),
2199                    op: FdOp::Close
2200                },
2201                Access::Channel {
2202                    open_file: file(3, 2),
2203                    id: None,
2204                    channel: ChannelKind::Pipe,
2205                    op: ChannelOp::Close
2206                },
2207            ]
2208        );
2209    }
2210
2211    #[test]
2212    fn a_reused_number_names_the_new_open_file() {
2213        let path = Some("/tmp/misc".to_string());
2214        let open = run(
2215            Sysno::openat,
2216            [libc::AT_FDCWD as u64, 0x2000, libc::O_RDWR as u64, 0, 0, 0],
2217            6,
2218            &[],
2219            &[note(6, Some(file(7, 3)), FdOp::Open { path: path.clone() })],
2220            Written::default(),
2221        );
2222        assert_eq!(
2223            open,
2224            vec![Access::Fd {
2225                slot: slot(6),
2226                open_file: Some(file(7, 3)),
2227                op: FdOp::Open { path }
2228            }]
2229        );
2230        // A later lseek through the number reaches the new open file.
2231        let seek = run(
2232            Sysno::lseek,
2233            [6, 2048, libc::SEEK_SET as u64, 0, 0, 0],
2234            2048,
2235            &[(6, info(file(7, 3), FdType::Regular))],
2236            &[],
2237            Written::default(),
2238        );
2239        assert_eq!(
2240            seek,
2241            vec![
2242                Access::Fd {
2243                    slot: slot(6),
2244                    open_file: Some(file(7, 3)),
2245                    op: FdOp::Use
2246                },
2247                Access::OpenFile {
2248                    open_file: file(7, 3),
2249                    op: CursorOp::Seek
2250                },
2251            ]
2252        );
2253    }
2254
2255    #[test]
2256    fn reads_and_writes_reach_cursor_contents_and_channels() {
2257        let regular = info(file(3, 1), FdType::Regular);
2258        let pread = run(
2259            Sysno::pread64,
2260            [4, 0x1000, 100, 512, 0, 0],
2261            0,
2262            &[(4, regular.clone())],
2263            &[],
2264            Written::default(),
2265        );
2266        assert_eq!(
2267            pread[1],
2268            Access::FileContents {
2269                inode: DetInode::mint(40),
2270                range: Some(ByteRange {
2271                    start: 512,
2272                    end: 612
2273                }),
2274                op: DataOp::Read
2275            },
2276            "an empty pread covers the bytes it asked for"
2277        );
2278        let write = run(
2279            Sysno::write,
2280            [4, 0x1000, 10, 0, 0, 0],
2281            10,
2282            &[(4, regular)],
2283            &[],
2284            Written::default(),
2285        );
2286        assert_eq!(
2287            write[1..],
2288            [
2289                Access::OpenFile {
2290                    open_file: file(3, 1),
2291                    op: CursorOp::Write
2292                },
2293                Access::FileContents {
2294                    inode: DetInode::mint(40),
2295                    range: None,
2296                    op: DataOp::Write
2297                },
2298            ]
2299        );
2300        let fifo = FdInfo {
2301            fifo: true,
2302            ..info(file(3, 9), FdType::Regular)
2303        };
2304        let read = run(
2305            Sysno::read,
2306            [7, 0x1000, 10, 0, 0, 0],
2307            -libc::EAGAIN as i64,
2308            &[(7, fifo)],
2309            &[],
2310            Written::default(),
2311        );
2312        assert_eq!(
2313            read[1],
2314            Access::Channel {
2315                open_file: file(3, 9),
2316                id: Some(ChannelId::Inode(DetInode::mint(40))),
2317                channel: ChannelKind::Fifo,
2318                op: ChannelOp::Read
2319            }
2320        );
2321    }
2322
2323    #[test]
2324    fn futex_operations_name_their_words() {
2325        let wait = |op: i32| {
2326            run(
2327                Sysno::futex,
2328                [0x1000, op as u64, 0, 0, 0x2000, 0],
2329                0,
2330                &[],
2331                &[],
2332                Written::default(),
2333            )
2334        };
2335        let private = libc::FUTEX_PRIVATE_FLAG;
2336        assert_eq!(
2337            wait(libc::FUTEX_WAIT | private),
2338            vec![Access::Futex {
2339                futex: FutexID::private(mm(), 0x1000),
2340                op: FutexOp::Wait
2341            }]
2342        );
2343        assert_eq!(
2344            wait(libc::FUTEX_WAKE),
2345            vec![Access::Futex {
2346                futex: private_key(0x1000, false),
2347                op: FutexOp::Wake
2348            }],
2349            "a shared futex takes the shared key"
2350        );
2351        assert_eq!(
2352            wait(libc::FUTEX_CMP_REQUEUE | private),
2353            vec![
2354                Access::Futex {
2355                    futex: FutexID::private(mm(), 0x1000),
2356                    op: FutexOp::Wake
2357                },
2358                Access::Futex {
2359                    futex: FutexID::private(mm(), 0x2000),
2360                    op: FutexOp::RequeueTarget
2361                },
2362            ]
2363        );
2364        assert_eq!(
2365            wait(libc::FUTEX_WAKE_OP | private)[1],
2366            Access::Futex {
2367                futex: FutexID::private(mm(), 0x2000),
2368                op: FutexOp::ModifyWake
2369            }
2370        );
2371        assert_eq!(
2372            wait(libc::FUTEX_UNLOCK_PI | private),
2373            vec![Access::Futex {
2374                futex: FutexID::private(mm(), 0x1000),
2375                op: FutexOp::Unlock
2376            }]
2377        );
2378        assert_eq!(
2379            wait(libc::FUTEX_FD),
2380            vec![],
2381            "an unknown command names nothing"
2382        );
2383    }
2384
2385    #[test]
2386    fn munmap_and_mremap_change_page_ranges() {
2387        let munmap = run(
2388            Sysno::munmap,
2389            [0x7000, 100, 0, 0, 0, 0],
2390            0,
2391            &[],
2392            &[],
2393            Written::default(),
2394        );
2395        assert_eq!(
2396            munmap,
2397            vec![Access::Memory {
2398                mm: mm(),
2399                range: ByteRange {
2400                    start: 0x7000,
2401                    end: 0x8000
2402                },
2403                op: MemoryOp::Unmap
2404            }]
2405        );
2406        let failed = run(
2407            Sysno::munmap,
2408            [0x7001, 100, 0, 0, 0, 0],
2409            -libc::EINVAL as i64,
2410            &[],
2411            &[],
2412            Written::default(),
2413        );
2414        assert_eq!(failed, vec![], "a failed munmap unmapped nothing");
2415        let mremap = run(
2416            Sysno::mremap,
2417            [0x7000, 0x1000, 0x3000, libc::MREMAP_MAYMOVE as u64, 0, 0],
2418            0x9000,
2419            &[],
2420            &[],
2421            Written::default(),
2422        );
2423        assert_eq!(
2424            mremap,
2425            vec![
2426                Access::Memory {
2427                    mm: mm(),
2428                    range: ByteRange {
2429                        start: 0x7000,
2430                        end: 0x8000
2431                    },
2432                    op: MemoryOp::Unmap
2433                },
2434                Access::Memory {
2435                    mm: mm(),
2436                    range: ByteRange {
2437                        start: 0x9000,
2438                        end: 0xc000
2439                    },
2440                    op: MemoryOp::Map
2441                },
2442            ]
2443        );
2444    }
2445
2446    #[test]
2447    fn timers_are_named_per_process_or_by_their_timerfd() {
2448        let process = DetPid::from_raw(3);
2449        let create = run(
2450            Sysno::timer_create,
2451            [libc::CLOCK_MONOTONIC as u64, 0, 0x3000, 0, 0, 0],
2452            0,
2453            &[],
2454            &[],
2455            Written {
2456                timer_id: Some(2),
2457                ..Written::default()
2458            },
2459        );
2460        assert_eq!(
2461            create,
2462            vec![Access::Timer {
2463                timer: TimerId::Posix { process, id: 2 },
2464                op: TimerOp::Create
2465            }]
2466        );
2467        for (sysno, op) in [
2468            (Sysno::timer_settime, TimerOp::Set),
2469            (Sysno::timer_gettime, TimerOp::Get),
2470            (Sysno::timer_delete, TimerOp::Delete),
2471        ] {
2472            assert_eq!(
2473                run(sysno, [2, 0, 0, 0, 0, 0], 0, &[], &[], Written::default()),
2474                vec![Access::Timer {
2475                    timer: TimerId::Posix { process, id: 2 },
2476                    op
2477                }],
2478                "{sysno}"
2479            );
2480        }
2481        #[cfg(target_arch = "x86_64")]
2482        assert_eq!(
2483            run(
2484                Sysno::alarm,
2485                [5, 0, 0, 0, 0, 0],
2486                0,
2487                &[],
2488                &[],
2489                Written::default()
2490            ),
2491            vec![Access::Timer {
2492                timer: TimerId::Interval {
2493                    process,
2494                    which: libc::ITIMER_REAL
2495                },
2496                op: TimerOp::Set
2497            }]
2498        );
2499        let timerfd = info(file(3, 4), FdType::Timerfd);
2500        let read = run(
2501            Sysno::read,
2502            [9, 0x1000, 8, 0, 0, 0],
2503            8,
2504            &[(9, timerfd)],
2505            &[],
2506            Written::default(),
2507        );
2508        assert_eq!(
2509            read[1],
2510            Access::Timer {
2511                timer: TimerId::Fd {
2512                    open_file: file(3, 4)
2513                },
2514                op: TimerOp::Read
2515            }
2516        );
2517        let created = run(
2518            Sysno::timerfd_create,
2519            [libc::CLOCK_MONOTONIC as u64, 0, 0, 0, 0, 0],
2520            9,
2521            &[],
2522            &[note(9, Some(file(3, 4)), FdOp::Open { path: None })],
2523            Written::default(),
2524        );
2525        assert_eq!(
2526            created[1],
2527            Access::Timer {
2528                timer: TimerId::Fd {
2529                    open_file: file(3, 4)
2530                },
2531                op: TimerOp::Create
2532            }
2533        );
2534    }
2535
2536    #[test]
2537    fn lifetime_operations_name_threads_processes_and_groups() {
2538        let thread = run(
2539            Sysno::clone,
2540            [(libc::CLONE_VM | libc::CLONE_THREAD) as u64, 0, 0, 0, 0, 0],
2541            8,
2542            &[],
2543            &[],
2544            Written::default(),
2545        );
2546        assert_eq!(
2547            thread,
2548            vec![Access::Lifetime {
2549                target: Task::Thread(tid(8)),
2550                op: LifetimeOp::Create
2551            }]
2552        );
2553        let kill = |pid: i64| {
2554            run(
2555                Sysno::kill,
2556                [pid as u64, 9, 0, 0, 0, 0],
2557                0,
2558                &[],
2559                &[],
2560                Written::default(),
2561            )
2562        };
2563        let sigkill = LifetimeOp::Signal { signo: 9 };
2564        assert_eq!(
2565            kill(4),
2566            vec![Access::Lifetime {
2567                target: Task::Process(DetPid::from_raw(4)),
2568                op: sigkill
2569            }]
2570        );
2571        assert_eq!(
2572            kill(-4),
2573            vec![Access::Lifetime {
2574                target: Task::ProcessGroup(DetPid::from_raw(4)),
2575                op: sigkill
2576            }]
2577        );
2578        assert_eq!(kill(-1), vec![]);
2579        assert_eq!(
2580            run(
2581                Sysno::waitid,
2582                [libc::P_ALL as u64, 0, 0x5000, libc::WEXITED as u64, 0, 0],
2583                0,
2584                &[],
2585                &[],
2586                Written {
2587                    waited_pid: Some(4),
2588                    ..Written::default()
2589                },
2590            ),
2591            vec![Access::Lifetime {
2592                target: Task::Process(DetPid::from_raw(4)),
2593                op: LifetimeOp::Wait
2594            }]
2595        );
2596    }
2597
2598    struct Groups;
2599
2600    /// Processes 3 and 4 form group 3.
2601    impl ProcessGroups for Groups {
2602        fn group_of(&self, pid: DetPid) -> Option<DetPid> {
2603            [3, 4]
2604                .contains(&pid.as_raw())
2605                .then_some(DetPid::from_raw(3))
2606        }
2607        fn members(&self, group: DetPid) -> Vec<DetPid> {
2608            if group == DetPid::from_raw(3) {
2609                vec![DetPid::from_raw(3), DetPid::from_raw(4)]
2610            } else {
2611                Vec::new()
2612            }
2613        }
2614    }
2615
2616    #[test]
2617    fn a_group_signal_lists_each_member() {
2618        let op = LifetimeOp::Signal { signo: 15 };
2619        let resolved = resolve_groups(
2620            vec![Access::Lifetime {
2621                target: Task::ProcessGroup(OWN_PROCESS_GROUP),
2622                op,
2623            }],
2624            DetPid::from_raw(3),
2625            &Groups,
2626        );
2627        let target = |t| Access::Lifetime { target: t, op };
2628        assert_eq!(
2629            resolved,
2630            vec![
2631                target(Task::ProcessGroup(DetPid::from_raw(3))),
2632                target(Task::Process(DetPid::from_raw(3))),
2633                target(Task::Process(DetPid::from_raw(4))),
2634            ]
2635        );
2636    }
2637
2638    #[test]
2639    fn returned_bytes_digest_at_most_the_cap() {
2640        let small = [
2641            ByteRange { start: 0, end: 2 },
2642            ByteRange { start: 10, end: 13 },
2643        ];
2644        let summary =
2645            summarize_output::<()>(None, &small, |at, want| Ok(vec![at as u8; want])).unwrap();
2646        assert_eq!(summary.len, 5);
2647        assert!(!summary.truncated);
2648        assert_eq!(
2649            summary.digest,
2650            returned_digest(RETURNED_DIGEST_SEED, &[0, 0, 10, 10, 10])
2651        );
2652        let big = [ByteRange {
2653            start: 0,
2654            end: RETURNED_DIGEST_CAP + 1,
2655        }];
2656        let mut asked = 0;
2657        let summary = summarize_output::<()>(None, &big, |_, want| {
2658            asked += want;
2659            Ok(vec![1; want])
2660        })
2661        .unwrap();
2662        assert!(summary.truncated);
2663        assert_eq!(asked as u64, RETURNED_DIGEST_CAP);
2664        assert_eq!(summarize_output::<()>(None, &[], |_, _| Ok(vec![])), None);
2665        assert_eq!(summarize_output(None, &small, |_, _| Err(())), None);
2666    }
2667
2668    /// `si_code` of a fault at an unmapped address
2669    /// (`include/uapi/asm-generic/siginfo.h`).
2670    const SEGV_MAPERR: i32 = 1;
2671
2672    /// Clock and timer reads carry what they returned: two clock_gettime
2673    /// calls whose timespec differs get different digests, and the same
2674    /// bytes the same digest. A remaining time counts only when a sleep was
2675    /// interrupted, and time's result is its output.
2676    #[test]
2677    fn clock_and_timer_reads_carry_what_they_returned() {
2678        let (value, buffers) = time_outputs(
2679            Sysno::clock_gettime,
2680            [libc::CLOCK_MONOTONIC as u64, 0x1000, 0, 0, 0, 0],
2681            0,
2682        );
2683        assert_eq!(value, None);
2684        assert_eq!(
2685            buffers,
2686            vec![ByteRange {
2687                start: 0x1000,
2688                end: 0x1010
2689            }]
2690        );
2691        let timespec = |sec: i64, nsec: i64| {
2692            let mut bytes = sec.to_le_bytes().to_vec();
2693            bytes.extend(nsec.to_le_bytes());
2694            bytes
2695        };
2696        let read_as = |bytes: Vec<u8>| {
2697            move |_: u64, want: usize| -> Result<Vec<u8>, ()> { Ok(bytes[..want].to_vec()) }
2698        };
2699        let earlier = summarize_output(None, &buffers, read_as(timespec(5, 100))).unwrap();
2700        let later = summarize_output(None, &buffers, read_as(timespec(5, 250))).unwrap();
2701        let again = summarize_output(None, &buffers, read_as(timespec(5, 100))).unwrap();
2702        assert_eq!(earlier.len, 16);
2703        assert_ne!(earlier.digest, later.digest);
2704        assert_eq!(earlier, again);
2705
2706        let failed = time_outputs(Sysno::clock_gettime, [1, 0x1000, 0, 0, 0, 0], -14);
2707        assert_eq!(failed, (None, vec![]), "a failed read returned nothing");
2708        let rem = [1, 0, 0x2000, 0x3000, 0, 0];
2709        assert_eq!(time_outputs(Sysno::clock_nanosleep, rem, 0).1, vec![]);
2710        for interrupted in [-(libc::EINTR as i64), -516] {
2711            assert_eq!(
2712                time_outputs(Sysno::clock_nanosleep, rem, interrupted).1,
2713                vec![ByteRange {
2714                    start: 0x3000,
2715                    end: 0x3010
2716                }],
2717                "{interrupted}"
2718            );
2719        }
2720        let absolute = [1, libc::TIMER_ABSTIME as u64, 0x2000, 0x3000, 0, 0];
2721        assert_eq!(
2722            time_outputs(Sysno::clock_nanosleep, absolute, -(libc::EINTR as i64)).1,
2723            vec![],
2724            "an absolute sleep leaves its remaining time unwritten"
2725        );
2726        assert_eq!(
2727            time_outputs(Sysno::gettimeofday, [0x1000, 0x2000, 0, 0, 0, 0], 0)
2728                .1
2729                .len(),
2730            2
2731        );
2732        for (sysno, args, start) in [
2733            (Sysno::timer_gettime, [0, 0x4000, 0, 0, 0, 0], 0x4000),
2734            (Sysno::timerfd_gettime, [9, 0x4000, 0, 0, 0, 0], 0x4000),
2735            (Sysno::getitimer, [0, 0x4000, 0, 0, 0, 0], 0x4000),
2736            (Sysno::timer_settime, [0, 0, 0x5000, 0x4000, 0, 0], 0x4000),
2737            (Sysno::setitimer, [0, 0x5000, 0x4000, 0, 0, 0], 0x4000),
2738        ] {
2739            assert_eq!(
2740                time_outputs(sysno, args, 0).1,
2741                vec![ByteRange {
2742                    start,
2743                    end: start + 32
2744                }],
2745                "{sysno}"
2746            );
2747        }
2748        // times' result is output on every architecture; time's on x86_64.
2749        #[cfg(target_arch = "x86_64")]
2750        assert_eq!(
2751            time_outputs(Sysno::time, [0, 0, 0, 0, 0, 0], 1_700_000_000),
2752            (Some(1_700_000_000), vec![])
2753        );
2754        let (value, buffers) = time_outputs(Sysno::times, [0, 0, 0, 0, 0, 0], 1_700_000_000);
2755        assert_eq!((value, buffers.clone()), (Some(1_700_000_000), vec![]));
2756        let first = summarize_output::<()>(value, &buffers, |_, _| unreachable!()).unwrap();
2757        let second =
2758            summarize_output::<()>(Some(1_700_000_001), &[], |_, _| unreachable!()).unwrap();
2759        assert_eq!(first.len, 8);
2760        assert_ne!(first.digest, second.digest);
2761    }
2762
2763    #[test]
2764    fn signal_note_reads_fault_address_and_sender() {
2765        let mut info = [0u8; 128];
2766        info[0..4].copy_from_slice(&libc::SIGSEGV.to_ne_bytes());
2767        info[8..12].copy_from_slice(&SEGV_MAPERR.to_ne_bytes());
2768        info[16..24].copy_from_slice(&0x10u64.to_ne_bytes());
2769        let segv = signal_note(
2770            libc::SIGSEGV,
2771            Some(&info),
2772            ObservedSignalDisposition::Default,
2773        );
2774        assert_eq!(
2775            (segv.code, segv.addr, segv.sender),
2776            (SEGV_MAPERR, Some(0x10), None)
2777        );
2778        // A SIGSEGV that kill sent (SI_USER) carries no fault address, even
2779        // though the union's bytes are set: there they are si_pid and si_uid.
2780        let mut info = [0u8; 128];
2781        info[8..12].copy_from_slice(&libc::SI_USER.to_ne_bytes());
2782        info[16..24].copy_from_slice(&0x0000_0007_0000_0004u64.to_ne_bytes());
2783        let sent = signal_note(
2784            libc::SIGSEGV,
2785            Some(&info),
2786            ObservedSignalDisposition::Default,
2787        );
2788        assert_eq!(sent.addr, None, "a user-sent SIGSEGV has no si_addr");
2789        assert_eq!(sent.sender.map(|s| s.pid), Some(DetPid::from_raw(4)));
2790        let mut info = [0u8; 128];
2791        info[8..12].copy_from_slice(&libc::SI_USER.to_ne_bytes());
2792        info[16..20].copy_from_slice(&4i32.to_ne_bytes());
2793        let usr1 = signal_note(
2794            libc::SIGUSR1,
2795            Some(&info),
2796            ObservedSignalDisposition::Handler,
2797        );
2798        assert_eq!(usr1.addr, None);
2799        assert_eq!(usr1.sender.map(|s| s.pid), Some(DetPid::from_raw(4)));
2800        let mut info = [0u8; 128];
2801        info[8..12].copy_from_slice(&libc::SI_TIMER.to_ne_bytes());
2802        info[16..20].copy_from_slice(&3i32.to_ne_bytes());
2803        let timer = signal_note(
2804            libc::SIGUSR1,
2805            Some(&info),
2806            ObservedSignalDisposition::Handler,
2807        );
2808        assert_eq!(timer.sender, None, "a timer expiry has no sending process");
2809        let none = signal_note(libc::SIGUSR1, None, ObservedSignalDisposition::Handler);
2810        assert_eq!((none.code, none.addr, none.sender), (0, None, None));
2811    }
2812
2813    fn prehook(t: i32, sysno: Sysno) -> SchedEvent {
2814        SchedEvent::syscall(tid(t), sysno, SyscallPhase::Prehook)
2815    }
2816
2817    fn posthook(t: i32, sysno: Sysno) -> SchedEvent {
2818        SchedEvent::syscall(tid(t), sysno, SyscallPhase::Posthook)
2819    }
2820
2821    fn entry(sysno: Sysno, nth: u64) -> AccessPayload {
2822        AccessPayload::SyscallEntry(SyscallEntry {
2823            pid: DetPid::from_raw(3),
2824            nth,
2825            sysno,
2826            args: [0; 6],
2827            accesses: entry_accesses(sysno, DetPid::from_raw(3), tid(3)),
2828        })
2829    }
2830
2831    fn exit(result: i64, accesses: Vec<Access>) -> AccessPayload {
2832        AccessPayload::SyscallExit(Box::new(SyscallOutcome {
2833            result,
2834            accesses,
2835            returned: None,
2836            out_bytes: None,
2837        }))
2838    }
2839
2840    /// A trace file in memory.
2841    #[derive(Clone, Default)]
2842    struct Shared(Arc<Mutex<Vec<u8>>>);
2843
2844    impl Write for Shared {
2845        fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
2846            self.0.lock().unwrap().extend_from_slice(bytes);
2847            Ok(bytes.len())
2848        }
2849        fn flush(&mut self) -> io::Result<()> {
2850            Ok(())
2851        }
2852    }
2853
2854    /// A signal observation can precede another thread's sigaction and the
2855    /// eventual delivery. This models that handoff through the production
2856    /// classifier, payload construction and writer, without claiming a live
2857    /// guest replay: the trace must name the earlier disposition explicitly.
2858    #[test]
2859    fn signal_trace_names_the_disposition_observed_before_a_handoff() {
2860        let signal = libc::SIGUSR1;
2861        let bit = crate::syscalls::kernel_sigset_bit(signal);
2862        let shared_dispositions = Mutex::new((0, bit));
2863        let observed =
2864            crate::classify_traced_disposition(signal, Some(*shared_dispositions.lock().unwrap()));
2865        let payload = AccessPayload::Signal(signal_note(signal, None, observed));
2866        let mut event = prehook(3, Sysno::getpid);
2867        event.op = Op::SignalReceived(crate::types::SigWrapper(signal));
2868        let contents = Shared::default();
2869        let mut writer = AccessTraceWriter::new(Box::new(contents.clone()), 0, None, None).unwrap();
2870        writer.observe(0, &event, Some(payload), &Groups);
2871
2872        // Thread B changes the shared disposition while A is held after its
2873        // SignalReceived event. A's earlier observation must not turn into a
2874        // claim about the action Linux will take when A resumes.
2875        *shared_dispositions.lock().unwrap() = (bit, 0);
2876        writer.finish(1).unwrap();
2877        let bytes = contents.0.lock().unwrap().clone();
2878        let trace = read_access_trace(bytes.as_slice()).unwrap();
2879        assert_eq!(trace.records.len(), 1);
2880        let record = serde_json::to_value(&trace.records[0]).unwrap();
2881        assert_eq!(record["observed_disposition"], "handler", "{record}");
2882        assert!(record.get("delivery").is_none(), "{record}");
2883        assert_eq!(
2884            serde_json::to_value(crate::classify_traced_disposition(
2885                signal,
2886                Some(*shared_dispositions.lock().unwrap()),
2887            ))
2888            .unwrap(),
2889            "ignored"
2890        );
2891    }
2892
2893    /// Unreadable state is unknown, while each readable disposition retains
2894    /// its exact meaning through the production signal payload and writer.
2895    #[test]
2896    fn signal_trace_preserves_unknown_and_known_dispositions() {
2897        let signal = libc::SIGUSR1;
2898        let bit = crate::syscalls::kernel_sigset_bit(signal);
2899        for (dispositions, expected) in [
2900            (None, "unknown"),
2901            (Some((0, bit)), "handler"),
2902            (Some((bit, 0)), "ignored"),
2903            (Some((0, 0)), "default"),
2904        ] {
2905            let observed = crate::classify_traced_disposition(signal, dispositions);
2906            let payload = AccessPayload::Signal(signal_note(signal, None, observed));
2907            let mut event = prehook(3, Sysno::getpid);
2908            event.op = Op::SignalReceived(crate::types::SigWrapper(signal));
2909            let contents = Shared::default();
2910            let mut writer =
2911                AccessTraceWriter::new(Box::new(contents.clone()), 0, None, None).unwrap();
2912            writer.observe(0, &event, Some(payload), &Groups);
2913            writer.finish(1).unwrap();
2914            let bytes = contents.0.lock().unwrap().clone();
2915            let trace = read_access_trace(bytes.as_slice()).unwrap();
2916            assert_eq!(trace.records.len(), 1);
2917            let record = serde_json::to_value(&trace.records[0]).unwrap();
2918            assert_eq!(
2919                serde_json::to_value(observed).unwrap(),
2920                expected,
2921                "{record}"
2922            );
2923            assert_eq!(record["observed_disposition"], expected, "{record}");
2924            assert!(record.get("delivery").is_none(), "{record}");
2925        }
2926    }
2927
2928    /// `--access-trace-max-records`: past the cap the trace stops without
2929    /// its end line, so a reader sees it cut short; a run whose records all
2930    /// fit still gets it.
2931    #[test]
2932    fn a_capped_trace_stops_without_its_end_line() {
2933        let run = |max: Option<u64>| {
2934            let file_contents = Shared::default();
2935            let mut writer =
2936                AccessTraceWriter::new(Box::new(file_contents.clone()), 0, max, None).unwrap();
2937            for n in 0..3 {
2938                let getpid = || entry(Sysno::getpid, n + 1);
2939                writer.observe(2 * n, &prehook(3, Sysno::getpid), Some(getpid()), &Groups);
2940                writer.observe(
2941                    2 * n + 1,
2942                    &posthook(3, Sysno::getpid),
2943                    Some(exit(3, vec![])),
2944                    &Groups,
2945                );
2946            }
2947            writer.finish(6).unwrap();
2948            let bytes = file_contents.0.lock().unwrap().clone();
2949            read_access_trace(&bytes[..]).unwrap()
2950        };
2951        let capped = run(Some(2));
2952        assert_eq!(capped.records.len(), 2, "{:?}", capped.records);
2953        assert!(capped.end.is_none(), "{:?}", capped.end);
2954        let fits = run(Some(3));
2955        assert_eq!(fits.records.len(), 3);
2956        assert_eq!(fits.end.map(|end| end.records), Some(3));
2957        let nothing = run(Some(0));
2958        assert!(nothing.records.is_empty() && nothing.end.is_none());
2959    }
2960
2961    /// `--access-trace-max-bytes`: the trace holds at most that many bytes of
2962    /// header and records. The record that would pass it is not written, and
2963    /// the trace stops there without its end line; one that fits exactly is
2964    /// written. Without the option the cap is
2965    /// [`ACCESS_TRACE_DEFAULT_MAX_BYTES`], so no trace is unbounded.
2966    #[test]
2967    fn a_trace_stops_before_its_byte_cap() {
2968        let run = |max_bytes: Option<u64>| {
2969            let file_contents = Shared::default();
2970            let mut writer =
2971                AccessTraceWriter::new(Box::new(file_contents.clone()), 0, None, max_bytes)
2972                    .unwrap();
2973            assert_eq!(
2974                writer.max_bytes,
2975                max_bytes.unwrap_or(ACCESS_TRACE_DEFAULT_MAX_BYTES)
2976            );
2977            for n in 0..3 {
2978                let getpid = || entry(Sysno::getpid, n + 1);
2979                writer.observe(2 * n, &prehook(3, Sysno::getpid), Some(getpid()), &Groups);
2980                writer.observe(
2981                    2 * n + 1,
2982                    &posthook(3, Sysno::getpid),
2983                    Some(exit(3, vec![])),
2984                    &Groups,
2985                );
2986            }
2987            writer.finish(6).unwrap();
2988            file_contents.0.lock().unwrap().clone()
2989        };
2990        let whole = run(None);
2991        let lines: Vec<&[u8]> = whole.split_inclusive(|b| *b == b'\n').collect();
2992        assert_eq!(lines.len(), 5, "header, three records and the end line");
2993        let through = |n: usize| -> u64 { lines[..n].iter().map(|l| l.len() as u64).sum() };
2994        // Room for the header and exactly two records.
2995        let two = run(Some(through(3)));
2996        assert_eq!(two, whole[..through(3) as usize].to_vec());
2997        let trace = read_access_trace(&two[..]).unwrap();
2998        assert_eq!(trace.records.len(), 2);
2999        assert!(trace.end.is_none(), "{:?}", trace.end);
3000        // One byte short of the second record: only the first.
3001        let one = read_access_trace(&run(Some(through(3) - 1))[..]).unwrap();
3002        assert_eq!((one.records.len(), one.end), (1, None));
3003        // Every record fits: the trace is whole, end line included.
3004        assert_eq!(run(Some(through(4))), whole);
3005    }
3006
3007    #[test]
3008    fn writer_numbers_records_names_pairs_and_marks_the_end() {
3009        let file_contents = Shared::default();
3010        let mut writer =
3011            AccessTraceWriter::new(Box::new(file_contents.clone()), 2, None, None).unwrap();
3012        let open = |fd, f| Access::Fd {
3013            slot: slot(fd),
3014            open_file: Some(f),
3015            op: FdOp::Open { path: None },
3016        };
3017        // Before `from`: not written, but its pair is still learned.
3018        writer.observe(
3019            0,
3020            &prehook(3, Sysno::socketpair),
3021            Some(entry(Sysno::socketpair, 1)),
3022            &Groups,
3023        );
3024        writer.observe(
3025            1,
3026            &posthook(3, Sysno::socketpair),
3027            Some(exit(0, vec![open(3, file(3, 1)), open(4, file(3, 2))])),
3028            &Groups,
3029        );
3030        // A write on the second end gets the pair's identity.
3031        writer.observe(
3032            2,
3033            &prehook(5, Sysno::write),
3034            Some(entry(Sysno::write, 1)),
3035            &Groups,
3036        );
3037        let send = Access::Channel {
3038            open_file: file(3, 2),
3039            id: None,
3040            channel: ChannelKind::Socket,
3041            op: ChannelOp::Write,
3042        };
3043        writer.observe(
3044            3,
3045            &posthook(5, Sysno::write),
3046            Some(exit(1, vec![send])),
3047            &Groups,
3048        );
3049        // A call that never returns.
3050        writer.observe(
3051            4,
3052            &prehook(3, Sysno::exit_group),
3053            Some(entry(Sysno::exit_group, 2)),
3054            &Groups,
3055        );
3056        writer.finish(5).unwrap();
3057
3058        let bytes = file_contents.0.lock().unwrap().clone();
3059        let trace = read_access_trace(&bytes[..]).unwrap();
3060        assert_eq!(trace.header.from, 2);
3061        assert_eq!(
3062            trace.end.as_ref().map(|e| (e.events, e.records)),
3063            Some((5, 2))
3064        );
3065        let [TraceRecord::Syscall(write), TraceRecord::Syscall(exit)] = &trace.records[..] else {
3066            panic!("{:?}", trace.records);
3067        };
3068        assert_eq!((write.pre, write.post, write.result), (2, Some(3), Some(1)));
3069        assert_eq!(
3070            write.accesses,
3071            vec![Access::Channel {
3072                open_file: file(3, 2),
3073                id: Some(ChannelId::Pair(file(3, 1))),
3074                channel: ChannelKind::Socket,
3075                op: ChannelOp::Write,
3076            }]
3077        );
3078        assert_eq!((exit.pre, exit.post, exit.result), (4, None, None));
3079        assert_eq!(
3080            exit.accesses,
3081            vec![Access::Lifetime {
3082                target: Task::Process(DetPid::from_raw(3)),
3083                op: LifetimeOp::Exit
3084            }],
3085            "a call that never returns keeps what its prehook knew"
3086        );
3087    }
3088
3089    fn at(t: i32, event: EventIndex) -> EventRef {
3090        EventRef { tid: tid(t), event }
3091    }
3092
3093    fn entry_with(sysno: Sysno, pid: i32, args: [u64; 6]) -> Option<AccessPayload> {
3094        Some(AccessPayload::SyscallEntry(SyscallEntry {
3095            pid: DetPid::from_raw(pid),
3096            nth: 1,
3097            sysno,
3098            args,
3099            accesses: Vec::new(),
3100        }))
3101    }
3102
3103    #[test]
3104    fn a_clone_starts_its_child_whichever_runs_first() {
3105        for child_first in [true, false] {
3106            let (mut writer, bytes) = in_memory_writer();
3107            writer.observe(
3108                10,
3109                &prehook(3, Sysno::clone),
3110                entry_with(Sysno::clone, 3, [0; 6]),
3111                &Groups,
3112            );
3113            let (child_event, parent_post) = if child_first { (11, 12) } else { (12, 11) };
3114            let child = SchedEvent::branches(tid(8), 1);
3115            let parent = posthook(3, Sysno::clone);
3116            if child_first {
3117                writer.observe(child_event, &child, None, &Groups);
3118                writer.observe(parent_post, &parent, Some(exit(8, vec![])), &Groups);
3119            } else {
3120                writer.observe(parent_post, &parent, Some(exit(8, vec![])), &Groups);
3121                writer.observe(child_event, &child, None, &Groups);
3122            }
3123            writer.finish(13).unwrap();
3124            assert_eq!(
3125                edges_of(&bytes),
3126                vec![EdgeRecord {
3127                    kind: EdgeKind::Create,
3128                    from: at(3, 10),
3129                    to: at(8, child_event),
3130                    object: SyncObject::Thread(tid(8)),
3131                }],
3132                "child first: {child_first}"
3133            );
3134        }
3135    }
3136
3137    /// A child that runs and is gone before its parent's clone returns (a
3138    /// forked child that exits at once) keeps its creation edge: the writer
3139    /// holds its first event for that return, though it forgets the rest of
3140    /// the thread.
3141    #[test]
3142    fn a_child_gone_before_its_parents_clone_returns_keeps_its_creation_edge() {
3143        let (mut writer, bytes) = in_memory_writer();
3144        writer.observe(
3145            10,
3146            &prehook(3, Sysno::clone),
3147            entry_with(Sysno::clone, 3, [0; 6]),
3148            &Groups,
3149        );
3150        writer.observe(11, &SchedEvent::branches(tid(8), 1), None, &Groups);
3151        writer.observe(12, &SchedEvent::branches(tid(8), 2), None, &Groups);
3152        writer.note_thread_gone(tid(8));
3153        writer.observe(
3154            13,
3155            &posthook(3, Sysno::clone),
3156            Some(exit(8, vec![])),
3157            &Groups,
3158        );
3159        writer.finish(14).unwrap();
3160        assert_eq!(
3161            edges_of(&bytes),
3162            vec![EdgeRecord {
3163                kind: EdgeKind::Create,
3164                from: at(3, 10),
3165                to: at(8, 11),
3166                object: SyncObject::Thread(tid(8)),
3167            }]
3168        );
3169    }
3170
3171    #[test]
3172    fn an_exit_wake_ends_at_the_joiners_wait_return() {
3173        let (mut writer, bytes) = in_memory_writer();
3174        let futex = FutexID::private(mm(), 0x5000);
3175        writer.observe(
3176            4,
3177            &prehook(5, Sysno::futex),
3178            entry_with(Sysno::futex, 3, [0; 6]),
3179            &Groups,
3180        );
3181        writer.observe(
3182            6,
3183            &prehook(8, Sysno::exit),
3184            entry_with(Sysno::exit, 3, [0; 6]),
3185            &Groups,
3186        );
3187        writer.note_wake(EdgeKind::ExitJoin, tid(8), tid(5), SyncObject::Futex(futex));
3188        writer.observe(
3189            9,
3190            &posthook(5, Sysno::futex),
3191            Some(exit(0, vec![])),
3192            &Groups,
3193        );
3194        writer.finish(10).unwrap();
3195        assert_eq!(
3196            edges_of(&bytes),
3197            vec![EdgeRecord {
3198                kind: EdgeKind::ExitJoin,
3199                from: at(8, 6),
3200                to: at(5, 9),
3201                object: SyncObject::Futex(futex),
3202            }]
3203        );
3204    }
3205
3206    #[test]
3207    fn a_reaped_childs_exit_orders_the_wait() {
3208        let (mut writer, bytes) = in_memory_writer();
3209        writer.observe(
3210            2,
3211            &prehook(9, Sysno::exit_group),
3212            entry_with(Sysno::exit_group, 9, [0; 6]),
3213            &Groups,
3214        );
3215        writer.observe(
3216            3,
3217            &prehook(3, Sysno::wait4),
3218            entry_with(Sysno::wait4, 3, [9, 0, 0, 0, 0, 0]),
3219            &Groups,
3220        );
3221        let reaped = vec![Access::Lifetime {
3222            target: Task::Process(DetPid::from_raw(9)),
3223            op: LifetimeOp::Wait,
3224        }];
3225        writer.observe(
3226            7,
3227            &posthook(3, Sysno::wait4),
3228            Some(exit(9, reaped)),
3229            &Groups,
3230        );
3231        writer.finish(8).unwrap();
3232        assert_eq!(
3233            edges_of(&bytes),
3234            vec![EdgeRecord {
3235                kind: EdgeKind::ChildExit,
3236                from: at(9, 2),
3237                to: at(3, 7),
3238                object: SyncObject::Process(DetPid::from_raw(9)),
3239            }]
3240        );
3241    }
3242
3243    /// Deliveries of `signo` to `tid`, with siginfo naming `code` and
3244    /// `union` (si_pid for a send, si_tid for a timer).
3245    fn delivered(signo: i32, code: i32, union: i32) -> Option<AccessPayload> {
3246        let mut info = [0u8; 128];
3247        info[8..12].copy_from_slice(&code.to_ne_bytes());
3248        info[16..20].copy_from_slice(&union.to_ne_bytes());
3249        Some(AccessPayload::Signal(signal_note(
3250            signo,
3251            Some(&info),
3252            ObservedSignalDisposition::Handler,
3253        )))
3254    }
3255
3256    fn received(t: i32, signo: i32) -> SchedEvent {
3257        SchedEvent {
3258            op: Op::SignalReceived(crate::types::SigWrapper(signo)),
3259            ..SchedEvent::branches(tid(t), 1)
3260        }
3261    }
3262
3263    /// A syscall `sysno` of thread 3 (process 3) with `args`, from its
3264    /// prehook at `pre` to its posthook at `pre + 1` with `result` and
3265    /// `accesses`.
3266    fn call(
3267        writer: &mut AccessTraceWriter,
3268        pre: EventIndex,
3269        sysno: Sysno,
3270        args: [u64; 6],
3271        result: i64,
3272        accesses: Vec<Access>,
3273    ) {
3274        writer.observe(pre, &prehook(3, sysno), entry_with(sysno, 3, args), &Groups);
3275        writer.observe(
3276            pre + 1,
3277            &posthook(3, sysno),
3278            Some(exit(result, accesses)),
3279            &Groups,
3280        );
3281    }
3282
3283    /// Thread 4 of process 4 makes a syscall, so the writer knows its
3284    /// process.
3285    fn thread_4_runs(writer: &mut AccessTraceWriter, index: EventIndex) {
3286        writer.observe(
3287            index,
3288            &prehook(4, Sysno::getpid),
3289            entry_with(Sysno::getpid, 4, [0; 6]),
3290            &Groups,
3291        );
3292    }
3293
3294    fn signal_edges(bytes: &Mutex<Vec<u8>>) -> Vec<(EventRef, EventRef)> {
3295        edges_of(bytes)
3296            .into_iter()
3297            .filter(|edge| edge.kind == EdgeKind::Signal)
3298            .map(|edge| (edge.from, edge.to))
3299            .collect()
3300    }
3301
3302    /// A send that fails delivers nothing; a delivery after a successful
3303    /// one takes it. The record names the sending thread only where a
3304    /// send matched.
3305    #[test]
3306    fn a_failed_send_orders_no_delivery() {
3307        let usr1 = libc::SIGUSR1;
3308        let kill = [4, usr1 as u64, 0, 0, 0, 0];
3309        let (mut writer, bytes) = in_memory_writer();
3310        thread_4_runs(&mut writer, 1);
3311        call(
3312            &mut writer,
3313            2,
3314            Sysno::kill,
3315            kill,
3316            -(libc::EPERM as i64),
3317            vec![],
3318        );
3319        writer.observe(
3320            4,
3321            &received(4, usr1),
3322            delivered(usr1, libc::SI_USER, 3),
3323            &Groups,
3324        );
3325        call(&mut writer, 5, Sysno::kill, kill, 0, vec![]);
3326        writer.observe(
3327            7,
3328            &received(4, usr1),
3329            delivered(usr1, libc::SI_USER, 3),
3330            &Groups,
3331        );
3332        writer.finish(8).unwrap();
3333        assert_eq!(signal_edges(&bytes), vec![(at(3, 5), at(4, 7))]);
3334        let senders: Vec<Option<DetTid>> = read_access_trace(&bytes.lock().unwrap()[..])
3335            .unwrap()
3336            .records
3337            .iter()
3338            .filter_map(|record| match record {
3339                TraceRecord::Signal(signal) => Some(signal.sender.and_then(|s| s.tid)),
3340                _ => None,
3341            })
3342            .collect();
3343        assert_eq!(senders, vec![None, Some(tid(3))]);
3344    }
3345
3346    /// Two sends of a standard signal from one sender, then two
3347    /// deliveries: on ptrace the first delivery can be dequeued before the
3348    /// second send queues another, so each delivery takes the earliest
3349    /// send left, and neither edge claims the later send for the first.
3350    #[test]
3351    fn two_sends_from_one_sender_order_two_deliveries_in_send_order() {
3352        let usr1 = libc::SIGUSR1;
3353        let kill = [4, usr1 as u64, 0, 0, 0, 0];
3354        let (mut writer, bytes) = in_memory_writer();
3355        thread_4_runs(&mut writer, 1);
3356        call(&mut writer, 2, Sysno::kill, kill, 0, vec![]);
3357        call(&mut writer, 5, Sysno::kill, kill, 0, vec![]);
3358        writer.observe(
3359            8,
3360            &received(4, usr1),
3361            delivered(usr1, libc::SI_USER, 3),
3362            &Groups,
3363        );
3364        writer.observe(
3365            9,
3366            &received(4, usr1),
3367            delivered(usr1, libc::SI_USER, 3),
3368            &Groups,
3369        );
3370        writer.finish(10).unwrap();
3371        assert_eq!(
3372            signal_edges(&bytes),
3373            vec![(at(3, 2), at(4, 8)), (at(3, 5), at(4, 9))]
3374        );
3375    }
3376
3377    /// Two threads of one process send: the first thread's kill may run
3378    /// after the second's (a `--happens-before` checkpoint between its
3379    /// prehook and its kill), so while its posthook is not before the
3380    /// second's prehook, the first delivery is used up without an edge.
3381    /// The next takes the second send.
3382    #[test]
3383    fn overlapping_sends_from_two_threads_order_nothing() {
3384        let usr1 = libc::SIGUSR1;
3385        let kill = [4, usr1 as u64, 0, 0, 0, 0];
3386        let (mut writer, bytes) = in_memory_writer();
3387        thread_4_runs(&mut writer, 1);
3388        writer.observe(
3389            2,
3390            &prehook(3, Sysno::kill),
3391            entry_with(Sysno::kill, 3, kill),
3392            &Groups,
3393        );
3394        writer.observe(
3395            3,
3396            &prehook(5, Sysno::kill),
3397            entry_with(Sysno::kill, 3, kill),
3398            &Groups,
3399        );
3400        writer.observe(4, &posthook(5, Sysno::kill), Some(exit(0, vec![])), &Groups);
3401        writer.observe(
3402            5,
3403            &received(4, usr1),
3404            delivered(usr1, libc::SI_USER, 3),
3405            &Groups,
3406        );
3407        writer.observe(6, &posthook(3, Sysno::kill), Some(exit(0, vec![])), &Groups);
3408        writer.observe(
3409            7,
3410            &received(4, usr1),
3411            delivered(usr1, libc::SI_USER, 3),
3412            &Groups,
3413        );
3414        writer.finish(8).unwrap();
3415        assert_eq!(signal_edges(&bytes), vec![(at(5, 3), at(4, 7))]);
3416    }
3417
3418    /// A send to the process and one to the thread queue separately (the
3419    /// shared and the private pending sets), so the thread gets two
3420    /// deliveries, one for each send.
3421    #[test]
3422    fn a_process_send_and_a_thread_send_each_order_one_delivery() {
3423        let usr1 = libc::SIGUSR1;
3424        let (mut writer, bytes) = in_memory_writer();
3425        thread_4_runs(&mut writer, 1);
3426        call(
3427            &mut writer,
3428            2,
3429            Sysno::kill,
3430            [4, usr1 as u64, 0, 0, 0, 0],
3431            0,
3432            vec![],
3433        );
3434        call(
3435            &mut writer,
3436            4,
3437            Sysno::tgkill,
3438            [4, 4, usr1 as u64, 0, 0, 0],
3439            0,
3440            vec![],
3441        );
3442        writer.observe(
3443            6,
3444            &received(4, usr1),
3445            delivered(usr1, libc::SI_TKILL, 3),
3446            &Groups,
3447        );
3448        writer.observe(
3449            7,
3450            &received(4, usr1),
3451            delivered(usr1, libc::SI_USER, 3),
3452            &Groups,
3453        );
3454        writer.finish(8).unwrap();
3455        // The delivery's code names the call: SI_TKILL the tgkill, SI_USER
3456        // the kill, whichever came first.
3457        assert_eq!(
3458            signal_edges(&bytes),
3459            vec![(at(3, 4), at(4, 6)), (at(3, 2), at(4, 7))]
3460        );
3461    }
3462
3463    /// Realtime signals queue: each delivery takes the earliest send left.
3464    #[test]
3465    fn realtime_signal_deliveries_take_their_sends_in_order() {
3466        let rt = 34; // SIGRTMIN + 2
3467        let send = [4, rt as u64, 0, 0, 0, 0];
3468        let (mut writer, bytes) = in_memory_writer();
3469        thread_4_runs(&mut writer, 1);
3470        call(&mut writer, 2, Sysno::kill, send, 0, vec![]);
3471        call(&mut writer, 4, Sysno::kill, send, 0, vec![]);
3472        writer.observe(
3473            6,
3474            &received(4, rt),
3475            delivered(rt, libc::SI_USER, 3),
3476            &Groups,
3477        );
3478        writer.observe(
3479            7,
3480            &received(4, rt),
3481            delivered(rt, libc::SI_USER, 3),
3482            &Groups,
3483        );
3484        writer.observe(
3485            8,
3486            &received(4, rt),
3487            delivered(rt, libc::SI_USER, 3),
3488            &Groups,
3489        );
3490        writer.finish(9).unwrap();
3491        assert_eq!(
3492            signal_edges(&bytes),
3493            vec![(at(3, 2), at(4, 6)), (at(3, 4), at(4, 7))]
3494        );
3495    }
3496
3497    /// A timer's expiry takes its oldest live arm. The oldest arm retires
3498    /// once an expiry recorded after a newer arm's posthook has matched it;
3499    /// an expiry between the re-arm's prehook and posthook keeps it. After
3500    /// timer_delete one more expiry, dequeued before the delete, can still
3501    /// match, and then nothing does.
3502    #[test]
3503    fn a_timer_expiry_takes_its_oldest_live_arm() {
3504        let usr2 = libc::SIGUSR2;
3505        let arm = [0, 0, 0x3000, 0, 0, 0];
3506        let expiry = || delivered(usr2, libc::SI_TIMER, 0);
3507        let (mut writer, bytes) = in_memory_writer();
3508        call(&mut writer, 2, Sysno::timer_settime, arm, 0, vec![]);
3509        writer.observe(
3510            4,
3511            &prehook(3, Sysno::timer_settime),
3512            entry_with(Sysno::timer_settime, 3, arm),
3513            &Groups,
3514        );
3515        writer.observe(5, &received(3, usr2), expiry(), &Groups);
3516        writer.observe(
3517            6,
3518            &posthook(3, Sysno::timer_settime),
3519            Some(exit(0, vec![])),
3520            &Groups,
3521        );
3522        writer.observe(7, &received(3, usr2), expiry(), &Groups);
3523        writer.observe(8, &received(3, usr2), expiry(), &Groups);
3524        call(&mut writer, 9, Sysno::timer_delete, [0; 6], 0, vec![]);
3525        writer.observe(11, &received(3, usr2), expiry(), &Groups);
3526        assert!(
3527            writer.edges.signals.is_empty(),
3528            "{:?}",
3529            writer.edges.signals
3530        );
3531        writer.observe(12, &received(3, usr2), expiry(), &Groups);
3532        writer.finish(13).unwrap();
3533        assert_eq!(
3534            signal_edges(&bytes),
3535            vec![
3536                (at(3, 2), at(3, 5)),
3537                (at(3, 2), at(3, 7)),
3538                (at(3, 4), at(3, 8)),
3539                (at(3, 4), at(3, 11)),
3540            ]
3541        );
3542    }
3543
3544    /// Past the cap a send goes before a live timer's arm, which can cause
3545    /// expiries for the rest of the run.
3546    #[test]
3547    fn the_cap_evicts_sends_before_a_live_timers_arm() {
3548        let (mut writer, _) = in_memory_writer();
3549        call(
3550            &mut writer,
3551            0,
3552            Sysno::timer_settime,
3553            [0, 0, 0x3000, 0, 0, 0],
3554            0,
3555            vec![],
3556        );
3557        for n in 1..=MAX_PENDING_SIGNALS as u64 {
3558            call(
3559                &mut writer,
3560                2 * n,
3561                Sysno::kill,
3562                [4, 34, 0, 0, 0, 0],
3563                0,
3564                vec![],
3565            );
3566        }
3567        let signals = &writer.edges.signals;
3568        assert_eq!(signals.len(), MAX_PENDING_SIGNALS);
3569        assert_eq!(signals[0].timer, Some(0), "the arm stayed");
3570        assert_eq!(signals[1].from, at(3, 4), "the oldest send went");
3571    }
3572
3573    /// A signal to the caller's own group reaches each member listed at its
3574    /// posthook once, and is used up after the last one.
3575    #[test]
3576    fn a_group_send_reaches_each_member_once() {
3577        let usr1 = libc::SIGUSR1;
3578        let (mut writer, bytes) = in_memory_writer();
3579        thread_4_runs(&mut writer, 1);
3580        let group = vec![Access::Lifetime {
3581            target: Task::ProcessGroup(OWN_PROCESS_GROUP),
3582            op: LifetimeOp::Signal { signo: usr1 },
3583        }];
3584        call(
3585            &mut writer,
3586            2,
3587            Sysno::kill,
3588            [0, usr1 as u64, 0, 0, 0, 0],
3589            0,
3590            group,
3591        );
3592        assert_eq!(
3593            writer.edges.signals[0].members,
3594            Some(vec![DetPid::from_raw(3), DetPid::from_raw(4)])
3595        );
3596        writer.observe(
3597            4,
3598            &received(4, usr1),
3599            delivered(usr1, libc::SI_USER, 3),
3600            &Groups,
3601        );
3602        writer.observe(
3603            5,
3604            &received(3, usr1),
3605            delivered(usr1, libc::SI_USER, 3),
3606            &Groups,
3607        );
3608        assert!(
3609            writer.edges.signals.is_empty(),
3610            "{:?}",
3611            writer.edges.signals
3612        );
3613        writer.observe(
3614            6,
3615            &received(4, usr1),
3616            delivered(usr1, libc::SI_USER, 3),
3617            &Groups,
3618        );
3619        writer.finish(7).unwrap();
3620        assert_eq!(
3621            signal_edges(&bytes),
3622            vec![(at(3, 2), at(4, 4)), (at(3, 2), at(3, 5))]
3623        );
3624    }
3625
3626    /// The cap evicts the oldest send. A later delivery of that signal to
3627    /// that target from that sender may be the evicted send's, so it gets no
3628    /// edge, where the next pending send would have claimed more order than
3629    /// the run had; a delivery no evicted send could have caused still gets
3630    /// its edge.
3631    #[test]
3632    fn a_delivery_an_evicted_send_could_have_caused_gets_no_edge() {
3633        let (usr1, usr2) = (libc::SIGUSR1, libc::SIGUSR2);
3634        let (mut writer, bytes) = in_memory_writer();
3635        thread_4_runs(&mut writer, 1);
3636        let sends = MAX_PENDING_SIGNALS as u64 + 1;
3637        for n in 1..=sends {
3638            call(
3639                &mut writer,
3640                2 * n,
3641                Sysno::kill,
3642                [4, usr1 as u64, 0, 0, 0, 0],
3643                0,
3644                vec![],
3645            );
3646        }
3647        let other = 2 * (sends + 1);
3648        call(
3649            &mut writer,
3650            other,
3651            Sysno::kill,
3652            [4, usr2 as u64, 0, 0, 0, 0],
3653            0,
3654            vec![],
3655        );
3656        assert_eq!(writer.edges.evicted.len(), 1, "one key, evicted twice");
3657        writer.observe(
3658            other + 2,
3659            &received(4, usr1),
3660            delivered(usr1, libc::SI_USER, 3),
3661            &Groups,
3662        );
3663        writer.observe(
3664            other + 3,
3665            &received(4, usr2),
3666            delivered(usr2, libc::SI_USER, 3),
3667            &Groups,
3668        );
3669        writer.finish(other + 4).unwrap();
3670        assert_eq!(signal_edges(&bytes), vec![(at(3, other), at(4, other + 3))]);
3671    }
3672
3673    /// A leader may queue a signal to its own process with a forged SI_USER
3674    /// code and si_pid, and Linux may deliver it to a sibling thread: while
3675    /// process 3's own sigqueue of SIGUSR1 is pending, its thread 7's SI_USER
3676    /// delivery naming process 5 gets no edge, though process 5's kill of
3677    /// process 3 is pending too.
3678    #[test]
3679    fn a_delivery_a_sibling_could_have_queued_gets_no_edge() {
3680        let usr1 = libc::SIGUSR1;
3681        let (mut writer, bytes) = in_memory_writer();
3682        writer.observe(
3683            1,
3684            &prehook(7, Sysno::getpid),
3685            entry_with(Sysno::getpid, 3, [0; 6]),
3686            &Groups,
3687        );
3688        call(
3689            &mut writer,
3690            2,
3691            Sysno::rt_sigqueueinfo,
3692            [3, usr1 as u64, 0, 0, 0, 0],
3693            0,
3694            vec![],
3695        );
3696        let kill = [3, usr1 as u64, 0, 0, 0, 0];
3697        writer.observe(
3698            4,
3699            &prehook(5, Sysno::kill),
3700            entry_with(Sysno::kill, 5, kill),
3701            &Groups,
3702        );
3703        writer.observe(5, &posthook(5, Sysno::kill), Some(exit(0, vec![])), &Groups);
3704        writer.observe(
3705            6,
3706            &received(7, usr1),
3707            delivered(usr1, libc::SI_USER, 5),
3708            &Groups,
3709        );
3710        writer.finish(7).unwrap();
3711        assert_eq!(signal_edges(&bytes), vec![]);
3712    }
3713
3714    /// A sigqueue caller chooses its delivery's si_code and si_pid. Process
3715    /// 5 queues SIGUSR1 to thread 4 alone, naming process 3, while process
3716    /// 3's kill of process 4 is pending: Linux takes the thread's own
3717    /// signal first, and its SI_QUEUE delivery gets no edge rather than
3718    /// using up process 3's kill, which the SI_USER delivery then takes.
3719    /// Once thread 4 queues SIGUSR1 to itself, which may carry SI_TKILL and
3720    /// any si_pid, a delivery that looks like process 3's tgkill gets no
3721    /// edge either.
3722    #[test]
3723    fn a_delivery_whose_sender_the_caller_chose_gets_no_edge() {
3724        let usr1 = libc::SIGUSR1;
3725        let (mut writer, bytes) = in_memory_writer();
3726        thread_4_runs(&mut writer, 1);
3727        call(
3728            &mut writer,
3729            2,
3730            Sysno::kill,
3731            [4, usr1 as u64, 0, 0, 0, 0],
3732            0,
3733            vec![],
3734        );
3735        let queue = [4, 4, usr1 as u64, 0, 0, 0];
3736        writer.observe(
3737            4,
3738            &prehook(5, Sysno::rt_tgsigqueueinfo),
3739            entry_with(Sysno::rt_tgsigqueueinfo, 5, queue),
3740            &Groups,
3741        );
3742        writer.observe(
3743            5,
3744            &posthook(5, Sysno::rt_tgsigqueueinfo),
3745            Some(exit(0, vec![])),
3746            &Groups,
3747        );
3748        writer.observe(
3749            6,
3750            &received(4, usr1),
3751            delivered(usr1, libc::SI_QUEUE, 3),
3752            &Groups,
3753        );
3754        writer.observe(
3755            7,
3756            &received(4, usr1),
3757            delivered(usr1, libc::SI_USER, 3),
3758            &Groups,
3759        );
3760        writer.observe(
3761            8,
3762            &prehook(4, Sysno::rt_tgsigqueueinfo),
3763            entry_with(Sysno::rt_tgsigqueueinfo, 4, queue),
3764            &Groups,
3765        );
3766        writer.observe(
3767            9,
3768            &posthook(4, Sysno::rt_tgsigqueueinfo),
3769            Some(exit(0, vec![])),
3770            &Groups,
3771        );
3772        call(&mut writer, 10, Sysno::tgkill, queue, 0, vec![]);
3773        writer.observe(
3774            12,
3775            &received(4, usr1),
3776            delivered(usr1, libc::SI_TKILL, 3),
3777            &Groups,
3778        );
3779        writer.finish(13).unwrap();
3780        assert_eq!(signal_edges(&bytes), vec![(at(3, 2), at(4, 7))]);
3781    }
3782
3783    /// A thread that a futex wake released but that is gone before its wait
3784    /// returned (killed, say) leaves no edge waiting: a later task given its
3785    /// id ends nothing at its own first posthook. Its open wait is still
3786    /// written, without a posthook, at the end of the trace.
3787    #[test]
3788    fn a_gone_threads_waiting_edge_does_not_end_at_its_ids_next_task() {
3789        let (mut writer, bytes) = in_memory_writer();
3790        let futex = FutexID::private(mm(), 0x5000);
3791        writer.observe(
3792            2,
3793            &prehook(5, Sysno::futex),
3794            entry_with(Sysno::futex, 5, [0; 6]),
3795            &Groups,
3796        );
3797        writer.observe(
3798            3,
3799            &prehook(3, Sysno::futex),
3800            entry_with(Sysno::futex, 3, [0; 6]),
3801            &Groups,
3802        );
3803        writer.note_wake(
3804            EdgeKind::FutexWake,
3805            tid(3),
3806            tid(5),
3807            SyncObject::Futex(futex),
3808        );
3809        writer.note_thread_gone(tid(5));
3810        writer.observe(
3811            4,
3812            &prehook(5, Sysno::getpid),
3813            entry_with(Sysno::getpid, 9, [0; 6]),
3814            &Groups,
3815        );
3816        writer.observe(
3817            5,
3818            &posthook(5, Sysno::getpid),
3819            Some(exit(9, vec![])),
3820            &Groups,
3821        );
3822        writer.finish(6).unwrap();
3823        assert_eq!(edges_of(&bytes), vec![]);
3824        let waits: Vec<(EventIndex, Option<EventIndex>)> =
3825            read_access_trace(&bytes.lock().unwrap()[..])
3826                .unwrap()
3827                .records
3828                .iter()
3829                .filter_map(|record| match record {
3830                    TraceRecord::Syscall(call) if call.sysno == Sysno::futex => {
3831                        Some((call.pre, call.post))
3832                    }
3833                    _ => None,
3834                })
3835                .collect();
3836        assert_eq!(waits, vec![(3, None), (2, None)]);
3837    }
3838
3839    /// Reaping a member takes it out of a group send still waiting for it,
3840    /// and drops the sends to it.
3841    #[test]
3842    fn reaping_forgets_the_sends_waiting_for_the_child() {
3843        let usr1 = libc::SIGUSR1;
3844        let (mut writer, _) = in_memory_writer();
3845        thread_4_runs(&mut writer, 1);
3846        let group = vec![Access::Lifetime {
3847            target: Task::ProcessGroup(OWN_PROCESS_GROUP),
3848            op: LifetimeOp::Signal { signo: usr1 },
3849        }];
3850        call(
3851            &mut writer,
3852            2,
3853            Sysno::kill,
3854            [0, usr1 as u64, 0, 0, 0, 0],
3855            0,
3856            group,
3857        );
3858        call(
3859            &mut writer,
3860            4,
3861            Sysno::kill,
3862            [4, usr1 as u64, 0, 0, 0, 0],
3863            0,
3864            vec![],
3865        );
3866        writer.observe(
3867            6,
3868            &received(3, usr1),
3869            delivered(usr1, libc::SI_USER, 3),
3870            &Groups,
3871        );
3872        assert_eq!(writer.edges.signals.len(), 2, "a's send waits for 4");
3873        let reaped = vec![Access::Lifetime {
3874            target: Task::Process(DetPid::from_raw(4)),
3875            op: LifetimeOp::Wait,
3876        }];
3877        call(&mut writer, 7, Sysno::wait4, [4, 0, 0, 0, 0, 0], 4, reaped);
3878        assert!(
3879            writer.edges.signals.is_empty(),
3880            "{:?}",
3881            writer.edges.signals
3882        );
3883    }
3884}