1use 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94pub enum AccessPayload {
95 SyscallEntry(SyscallEntry),
97 SyscallExit(Box<SyscallOutcome>),
99 Signal(SignalNote),
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
105pub struct SyscallEntry {
106 pub pid: DetPid,
108 pub nth: u64,
110 pub sysno: Sysno,
112 pub args: [u64; 6],
114 #[serde(with = "accesses_as_json")]
118 pub accesses: Vec<Access>,
119}
120
121pub(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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136pub struct SyscallOutcome {
137 pub result: i64,
139 #[serde(with = "accesses_as_json")]
143 pub accesses: Vec<Access>,
144 pub returned: Option<ReturnedBytes>,
146 pub out_bytes: Option<Vec<u8>>,
148}
149
150mod 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
170pub struct SignalNote {
171 pub signo: i32,
173 pub code: i32,
175 pub addr: Option<u64>,
177 pub sender: Option<SignalSender>,
179 pub timer: Option<i32>,
181 pub observed_disposition: ObservedSignalDisposition,
183}
184
185pub(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 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 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#[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#[derive(Debug, Clone, PartialEq, Eq)]
289pub(crate) struct FdNote {
290 pub(crate) slot: FdSlot,
291 pub(crate) open_file: Option<OpenFileId>,
294 pub(crate) op: FdOp,
295 pub(crate) released: Option<FdInfo>,
299}
300
301#[derive(Debug, Default)]
303pub(crate) struct CallState {
304 pub(crate) fds: BTreeMap<i32, FdInfo>,
306 pub(crate) notes: Vec<FdNote>,
308}
309
310#[derive(Debug, Default)]
315pub(crate) struct TracedCall(Mutex<Option<CallState>>);
316
317impl Clone for TracedCall {
318 fn clone(&self) -> Self {
321 Self::default()
322 }
323}
324
325impl TracedCall {
326 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 pub(crate) fn is_active(&self) -> bool {
336 self.0.lock().expect("traced call mutex poisoned").is_some()
337 }
338
339 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 pub(crate) fn take(&self) -> Option<CallState> {
348 self.0.lock().expect("traced call mutex poisoned").take()
349 }
350}
351
352#[derive(Debug, Default, Clone, PartialEq, Eq)]
354pub(crate) struct Written {
355 pub(crate) timer_id: Option<i32>,
357 pub(crate) fd_pair: Option<[i32; 2]>,
359 pub(crate) waited_pid: Option<i32>,
361 pub(crate) clone3_flags: Option<u64>,
363}
364
365pub(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 #[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 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
389pub(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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418pub(crate) enum WrittenField {
419 TimerId,
420 FdPair,
421 WaitedPid,
422 Clone3Flags,
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
427enum Role {
428 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
441fn 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 #[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
483pub(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
504pub(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 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 #[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
560pub(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 pub(crate) fds: &'a BTreeMap<i32, FdInfo>,
571 pub(crate) notes: &'a [FdNote],
573 pub(crate) buffers: &'a [(MemoryOp, ByteRange)],
575 pub(crate) written: &'a Written,
576 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
589pub(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 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) = ¬e.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
662fn 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 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
749const 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 #[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 #[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 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
902pub(crate) const OWN_PROCESS_GROUP: DetPid = DetPid::from_raw(0);
905
906pub(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
941fn 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
954fn 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
965pub struct AccessTraceWriter {
971 out: Option<Box<dyn Write + Send>>,
974 from: EventIndex,
975 records: u64,
976 max_records: Option<u64>,
978 bytes: u64,
980 max_bytes: u64,
983 default_max_bytes: bool,
986 capped: bool,
989 name: String,
991 open: BTreeMap<DetTid, SyscallRecord>,
993 pairs: BTreeMap<OpenFileId, ChannelId>,
995 edges: EdgeState,
997}
998
999#[derive(Debug, Default)]
1002struct EdgeState {
1003 first_event: BTreeMap<DetTid, EventIndex>,
1005 last_event: BTreeMap<DetTid, EventIndex>,
1006 process_of: BTreeMap<DetTid, DetPid>,
1008 first_unclaimed: BTreeSet<DetTid>,
1011 gone_first: BTreeMap<DetTid, EventIndex>,
1014 process_last: BTreeMap<DetPid, EventRef>,
1016 at_posthook: BTreeMap<DetTid, Vec<PendingEdge>>,
1019 at_first_event: BTreeMap<DetTid, PendingEdge>,
1021 signals: Vec<PendingSignal>,
1023 evicted: Vec<PendingSignal>,
1029 evictions_overflowed: bool,
1032 orphaned: Vec<SyscallRecord>,
1035}
1036
1037#[derive(Debug, Clone)]
1039struct PendingEdge {
1040 kind: EdgeKind,
1041 from: EventRef,
1042 object: SyncObject,
1043}
1044
1045#[derive(Debug, Clone, PartialEq)]
1049struct PendingSignal {
1050 from: EventRef,
1051 sender: DetPid,
1052 kind: SendKind,
1055 signo: i32,
1056 target: Task,
1057 timer: Option<i32>,
1059 post: Option<EventIndex>,
1061 deleted: bool,
1063 members: Option<Vec<DetPid>>,
1066 reached: Vec<DetPid>,
1068}
1069
1070const MAX_PENDING_SIGNALS: usize = 4096;
1075
1076#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1078enum SendKind {
1079 Kill,
1081 Tkill,
1083 Queue,
1101 Timer,
1103}
1104
1105impl PendingSignal {
1106 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 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 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 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
1144pub trait ProcessGroups {
1146 fn group_of(&self, pid: DetPid) -> Option<DetPid>;
1148 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 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 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 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 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(¬e, 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 fn write_edge(&mut self, edge: PendingEdge, to: EventRef) {
1721 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 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 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 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#[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#[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
1885fn 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 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 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 #[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 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 ¬es,
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 #[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, ®ular), 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 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 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 const SEGV_MAPERR: i32 = 1;
2671
2672 #[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 #[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 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 #[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 #[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 *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 #[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 #[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 #[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 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 let one = read_access_trace(&run(Some(through(3) - 1))[..]).unwrap();
3002 assert_eq!((one.records.len(), one.end), (1, None));
3003 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 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 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 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 #[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 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 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 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 #[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 #[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 #[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 #[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 assert_eq!(
3458 signal_edges(&bytes),
3459 vec![(at(3, 4), at(4, 6)), (at(3, 2), at(4, 7))]
3460 );
3461 }
3462
3463 #[test]
3465 fn realtime_signal_deliveries_take_their_sends_in_order() {
3466 let rt = 34; 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}