diff options
Diffstat (limited to 'ripple/minitrace')
-rw-r--r-- | ripple/minitrace/.gitignore | 4 | ||||
-rw-r--r-- | ripple/minitrace/Cargo.toml | 12 | ||||
-rw-r--r-- | ripple/minitrace/src/main.rs | 147 |
3 files changed, 163 insertions, 0 deletions
diff --git a/ripple/minitrace/.gitignore b/ripple/minitrace/.gitignore new file mode 100644 index 0000000..be75022 --- /dev/null +++ b/ripple/minitrace/.gitignore @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: edef <edef@unfathomable.blue> +# SPDX-License-Identifier: OSL-3.0 + +/target diff --git a/ripple/minitrace/Cargo.toml b/ripple/minitrace/Cargo.toml new file mode 100644 index 0000000..4a64930 --- /dev/null +++ b/ripple/minitrace/Cargo.toml @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: edef <edef@unfathomable.blue> +# SPDX-License-Identifier: OSL-3.0 + +[package] +name = "minitrace" +version = "0.0.0" +edition = "2018" + +[dependencies] +nix = "0.19.1" +spawn-ptrace = "0.1.2" +anyhow = "1.0.43" diff --git a/ripple/minitrace/src/main.rs b/ripple/minitrace/src/main.rs new file mode 100644 index 0000000..362be9c --- /dev/null +++ b/ripple/minitrace/src/main.rs @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: edef <edef@unfathomable.blue> +// SPDX-License-Identifier: OSL-3.0 + +use { + nix::{ + libc, + sys::{ + ptrace, + wait::{waitpid, WaitPidFlag, WaitStatus}, + }, + unistd::Pid, + }, + spawn_ptrace::CommandPtraceSpawn, + std::{env, io, process::Command}, +}; + +// TODO(edef): consider implementing this in terms of TID? +// tgids are a strict subset of tids +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct Tgid(pub libc::pid_t); + +impl Tgid { + fn as_pid(&self) -> Pid { + Pid::from_raw(self.0) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct Tid(pub libc::pid_t); + +impl Tid { + fn as_pid(&self) -> Pid { + Pid::from_raw(self.0) + } +} + +#[derive(Debug)] +struct Process { + tgid: Tgid, +} + +impl Process { + fn spawn(cmd: &mut Command) -> io::Result<Process> { + let child = cmd.spawn_ptrace()?; + + // the thread group leader's TID is equal to the TGID + let tgid = Tgid(child.id() as _); + + Ok(Process { tgid }) + } +} + +#[derive(Debug, Copy, Clone)] +struct SyscallEntry { + number: u64, + // rdi, rsi, rdx, rcx, r8, r9 + args: [u64; 6], +} + +impl SyscallEntry { + fn from_regs(regs: libc::user_regs_struct) -> SyscallEntry { + SyscallEntry { + number: regs.orig_rax, + args: [regs.rdi, regs.rsi, regs.rdx, regs.rcx, regs.r8, regs.r9], + } + } +} + +#[derive(Debug, Copy, Clone)] +enum EntryExit { + /// Process is about to enter a syscall + Entry(SyscallEntry), + /// Process is about to exit a syscall + Exit(SyscallEntry, i64), +} + +fn main() -> anyhow::Result<()> { + let process = Process::spawn(&mut { + let mut args = env::args(); + + // drop argv[0] + args.next(); + + let mut cmd = Command::new(args.next().unwrap()); + for arg in args { + cmd.arg(arg); + } + + cmd + })?; + + let options = ptrace::Options::PTRACE_O_TRACESYSGOOD | ptrace::Options::PTRACE_O_TRACECLONE; + ptrace::setoptions(process.tgid.as_pid(), options)?; + + // this is always equal to tgid for now, + // but I'm keeping this separate so it's obvious what has to be tgid + let tid = Tid(process.tgid.0); + + let mut syscall_state: Option<EntryExit> = None; + + loop { + ptrace::syscall(tid.as_pid(), None)?; + if let Some(EntryExit::Exit(..)) = syscall_state { + // syscall has completed now + syscall_state = None; + } + + let status = waitpid(tid.as_pid(), Some(WaitPidFlag::__WALL))?; + println!("{:?}", status); + + match (syscall_state, status) { + (None, WaitStatus::PtraceSyscall(event_tid)) => { + let event_tid = Tid(event_tid.as_raw()); + assert_eq!(tid, event_tid); + + let regs = ptrace::getregs(event_tid.as_pid())?; + let entry = SyscallEntry::from_regs(regs); + + syscall_state = Some(EntryExit::Entry(entry)); + println!("entry: {:?}", regs); + } + (Some(EntryExit::Entry(entry)), WaitStatus::PtraceSyscall(event_tid)) => { + let event_tid = Tid(event_tid.as_raw()); + assert_eq!(tid, event_tid); + + let regs = ptrace::getregs(event_tid.as_pid())?; + let ret = regs.rax as i64; + syscall_state = Some(EntryExit::Exit(entry, ret)); + + println!("syscall returned {:?} with {:?}", ret, regs); + } + (_, WaitStatus::Exited(event_tid, _)) => { + let event_tid = Tid(event_tid.as_raw()); + assert_eq!(tid, event_tid); + + // TODO(edef): this only works for main thread + break; + } + _ => panic!( + "unknown status {:?} with syscall_state = {:?}", + status, syscall_state + ), + } + } + + Ok(()) +} |