summary refs log tree commit diff
diff options
context:
space:
mode:
authoredef <edef@unfathomable.blue>2022-07-30 19:29:15 +0000
committeredef <edef@unfathomable.blue>2022-07-30 19:29:15 +0000
commit82652914c933f50931338e4bbc924013c358fe71 (patch)
treea77f0d48e59d497720de3f51a7ef4f928d361b15
parentdd94473c5724f8215790a9195df96cfa7bd6a04b (diff)
ripple/minitrace/syscall_abi: init
Factor out the rather sprawling syscall ABI definitions from the main
program. The macros, argument parsing, and file descriptor code get
some space to breathe too.

Change-Id: I0aa01b6b94e4d4b770bb9ef59926e2236c50b258
-rw-r--r--ripple/minitrace/src/main.rs404
-rw-r--r--ripple/minitrace/src/syscall_abi/arg.rs84
-rw-r--r--ripple/minitrace/src/syscall_abi/fd.rs62
-rw-r--r--ripple/minitrace/src/syscall_abi/macros.rs161
-rw-r--r--ripple/minitrace/src/syscall_abi/mod.rs127
5 files changed, 437 insertions, 401 deletions
diff --git a/ripple/minitrace/src/main.rs b/ripple/minitrace/src/main.rs
index c7fb369..74bfee2 100644
--- a/ripple/minitrace/src/main.rs
+++ b/ripple/minitrace/src/main.rs
@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: OSL-3.0
 
 use {
+	crate::syscall_abi::{AtFlags, DirFd, MapFlags, SyscallEntry},
 	anyhow::{bail, Context, Result},
 	bitflags::bitflags,
 	nix::{
@@ -17,10 +18,9 @@ use {
 		unistd::Pid,
 	},
 	std::{
-		convert::TryInto,
 		env,
 		ffi::CString,
-		fmt::{self, Debug},
+		fmt::Debug,
 		fs::{self, File},
 		io::{self, BufRead, Seek, SeekFrom},
 		os::unix::process::CommandExt,
@@ -29,6 +29,7 @@ use {
 };
 
 mod maps_file;
+mod syscall_abi;
 
 // TODO(edef): consider implementing this in terms of TID?
 // tgids are a strict subset of tids
@@ -178,272 +179,6 @@ impl Process {
 	}
 }
 
-macro_rules! define_syscalls {
-	(enum $SyscallEntry:ident {
-		$(fn $syscall:ident ( $($arg:ident : $Arg:ty),* ) -> $Ret:ty = $nr:literal ;)*
-	}) => {
-		#[derive(Debug, Clone)]
-		#[allow(non_camel_case_types)]
-		// TODO(edef): re-enable dead_code lint when we start fully interpreting syscall args
-		#[allow(dead_code)]
-		enum $SyscallEntry {
-			$($syscall {
-				$($arg : $Arg),*
-			}),*
-		}
-
-		impl $SyscallEntry {
-			fn from_regs(process: &Process, regs: libc::user_regs_struct) -> Result<$SyscallEntry> {
-				Ok(match (regs.orig_rax, [regs.rdi, regs.rsi, regs.rdx, regs.r10, regs.r8, regs.r9]) {
-					$(
-						($nr, [$($arg,)* ..]) => $SyscallEntry::$syscall {
-							$($arg: match ProcessSyscallArg::try_from_process_reg(process, $arg) {
-								Some(x) => x,
-								None => bail!("couldn't parse {}(2) {}: {:#08x}", stringify!($syscall), stringify!($arg), $arg)
-							}),*
-						},
-					)*
-					(n, _) => bail!("unknown syscall number {n}")
-				})
-			}
-		}
-	}
-}
-
-#[cfg(test)]
-fn libc_check<T: Debug + Eq + fmt::LowerHex>(
-	item: &'static str,
-	(our_name, our_value): (&'static str, T),
-	(libc_name, libc_value): (&'static str, T),
-) {
-	match () {
-		_ if libc_name.ends_with(our_name) => {}
-		_ if libc_name.starts_with(&format!("{our_name}_")) => {}
-		() => panic!("{libc_name} doesn't match {our_name}"),
-	}
-
-	assert!(
-		our_value == libc_value,
-		"{item}::{our_name} ({our_value:#x}) != libc::{libc_name} ({libc_value:#x})",
-	);
-}
-
-macro_rules! syscall_bitflags {
-	(
-		$(
-			struct $BitFlags:ident: $T:ty {
-				$(
-					const $FLAG:ident = $value:expr => $LIBC_FLAG:ident;
-				)*
-			}
-		)*
-	) => {
-		#[test]
-		fn verify_syscall_bitflags() {
-			$(
-				$BitFlags::verify();
-			)*
-		}
-
-		$(
-			bitflags! {
-				struct $BitFlags: $T {
-					$(
-						const $FLAG = $value;
-					)*
-				}
-			}
-
-			impl $BitFlags {
-				#[cfg(test)]
-				fn verify() {
-					$(
-						libc_check(
-							stringify!($BitFlags),
-							(stringify!($FLAG), Self::$FLAG.bits()),
-							(stringify!($LIBC_FLAG), libc::$LIBC_FLAG)
-						);
-					)*
-				}
-			}
-
-			impl SyscallArg for $BitFlags {
-				fn try_from_reg(reg: u64) -> Option<Self> {
-					SyscallArg::try_from_reg(reg).and_then(Self::from_bits)
-				}
-			}
-		)*
-	};
-}
-
-macro_rules! syscall_enums {
-	(
-		$(
-			enum $Enum:ident: $T:ty {
-				$(
-					$VARIANT:ident = $value:literal $(=> $LIBC_VALUE:ident)?,
-				)*
-			}
-		)*
-	) => {
-		#[test]
-		fn verify_syscall_enums() {
-			$(
-				$Enum::verify();
-			)*
-		}
-
-		$(
-			#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-			#[allow(non_camel_case_types)]
-			enum $Enum {
-				$($VARIANT = $value),*
-			}
-
-			impl SyscallArg for $Enum {
-				fn try_from_reg(reg: u64) -> Option<Self> {
-					let reg = <$T as SyscallArg>::try_from_reg(reg)?;
-					Some(match reg {
-						$(
-							$value => $Enum::$VARIANT,
-						)*
-						_ => return None
-					})
-				}
-			}
-
-			impl $Enum {
-				#[cfg(test)]
-				fn verify() {
-					$(
-						$(
-							libc_check(
-								stringify!($Enum),
-								(stringify!($VARIANT), Self::$VARIANT as $T),
-								(stringify!($LIBC_VALUE), libc::$LIBC_VALUE)
-							);
-						)?
-					)*
-				}
-			}
-		)*
-	};
-}
-
-trait ProcessSyscallArg: Sized {
-	fn try_from_process_reg(process: &Process, reg: u64) -> Option<Self>;
-}
-
-impl ProcessSyscallArg for CString {
-	fn try_from_process_reg(process: &Process, reg: u64) -> Option<Self> {
-		process.read_mem_cstr(reg).ok()
-	}
-}
-
-impl<T: SyscallArg> ProcessSyscallArg for T {
-	fn try_from_process_reg(_process: &Process, reg: u64) -> Option<Self> {
-		SyscallArg::try_from_reg(reg)
-	}
-}
-
-trait SyscallArg: Sized {
-	fn try_from_reg(reg: u64) -> Option<Self>;
-}
-
-impl SyscallArg for u16 {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		reg.try_into().ok()
-	}
-}
-
-impl SyscallArg for u32 {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		reg.try_into().ok()
-	}
-}
-
-impl SyscallArg for u64 {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		Some(reg)
-	}
-}
-
-impl SyscallArg for i32 {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		Some(u32::try_from(reg).ok()? as i32)
-	}
-}
-
-impl SyscallArg for *mut i32 {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		Some(usize::try_from_reg(reg)? as *mut i32)
-	}
-}
-
-impl SyscallArg for usize {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		reg.try_into().ok()
-	}
-}
-
-impl SyscallArg for *const u8 {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		Some(usize::try_from_reg(reg)? as *const u8)
-	}
-}
-
-impl SyscallArg for *mut u8 {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		Some(usize::try_from_reg(reg)? as *mut u8)
-	}
-}
-
-type SigAction = ();
-type SysInfo = ();
-type Tms = ();
-type Stat = ();
-type RobustListHead = ();
-type RLimit64 = ();
-
-impl SyscallArg for *mut () {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		Some(usize::try_from_reg(reg)? as *mut ())
-	}
-}
-
-impl SyscallArg for *const () {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		Some(usize::try_from_reg(reg)? as *const ())
-	}
-}
-
-define_syscalls! {
-	enum SyscallEntry {
-		fn read(fd: FileDesc, buf: *mut u8, count: usize) -> i64 = 0;
-		fn write(fd: FileDesc, buf: *const u8, count: usize) -> i64 = 1;
-		fn close(fd: FileDesc) -> i64 = 3;
-		fn mmap(addr: u64, len: u64, prot: ProtFlags, flags: MapFlags, fd: Option<FileDesc>, off: u64) -> i64 = 9;
-		fn mprotect(addr: u64, len: usize, prot: ProtFlags) -> i64 = 10;
-		fn brk(brk: u64) -> i64 = 12;
-		fn rt_sigaction(sig: Signal, act: *const SigAction, oact: *mut SigAction, sigsetsize: usize) -> i64 = 13;
-		fn ioctl(fd: FileDesc, cmd: Ioctl, arg: u64) -> i64 = 16;
-		fn pread64(fd: FileDesc, buf: *mut u8, count: usize, pos: u64) -> i64 = 17;
-		fn access(filename: CString, mode: AccessMode) -> i64 = 21;
-		fn getcwd(buf: *mut u8, size: u64) -> i64 = 79;
-		fn readlink(path: CString, buf: *mut u8, bufsiz: i32) -> i64 = 89;
-		fn sysinfo(info: *mut SysInfo) -> i64 = 99;
-		fn times(tbuf: *mut Tms) -> i64 = 100;
-		fn arch_prctl(option: ArchOption, arg2: u64) -> i64 = 158;
-		fn set_tid_address(tidptr: *mut i32) -> i64 = 218;
-		fn exit_group(error_code: i32) -> i64 = 231;
-		fn openat(dfd: DirFd, filename: CString, flags: OpenFlags, mode: FileMode) -> i64 = 257;
-		fn newfstatat(dfd: DirFd, filename: CString, statbuf: *mut Stat, flags: AtFlags) -> i64 = 262;
-		fn set_robust_list(head: *mut RobustListHead, len: usize) -> i64 = 273;
-		fn prlimit64(pid: i32, resource: ResourceLimit, new_rlim: *const RLimit64, old_rlim: *mut RLimit64) -> i64 = 302;
-		fn getrandom(ubuf: *mut u8, len: usize, flags: GrndFlags) -> i64 = 318;
-	}
-}
-
 #[derive(Debug, Clone)]
 enum EntryExit {
 	/// Process is about to enter a syscall
@@ -535,8 +270,6 @@ fn main() -> Result<()> {
 	Ok(())
 }
 
-const AT_FDCWD: i32 = -100;
-
 fn check_syscall(entry: &SyscallEntry) -> bool {
 	match *entry {
 		SyscallEntry::mmap {
@@ -648,134 +381,3 @@ fn check_syscall(entry: &SyscallEntry) -> bool {
 	}
 	true
 }
-
-syscall_bitflags! {
-	struct OpenFlags: i32 {
-		const WRONLY  = 1 <<  0 => O_WRONLY;
-		const CREAT   = 1 <<  6 => O_CREAT;
-		const NOCTTY  = 1 <<  8 => O_NOCTTY;
-		const TRUNC   = 1 <<  9 => O_TRUNC;
-		const CLOEXEC = 1 << 19 => O_CLOEXEC;
-	}
-
-	struct GrndFlags: u32 {
-		const NONBLOCK = 1 << 0 => GRND_NONBLOCK;
-		const RANDOM   = 1 << 1 => GRND_RANDOM;
-	}
-
-	struct MapFlags: i32 {
-		const PRIVATE   = 1 <<  1 => MAP_PRIVATE;
-		const FIXED     = 1 <<  4 => MAP_FIXED;
-		const ANONYMOUS = 1 <<  5 => MAP_ANONYMOUS;
-		const DENYWRITE = 1 << 11 => MAP_DENYWRITE;
-	}
-
-	struct ProtFlags: i32 {
-		const READ  = 1 << 0 => PROT_READ;
-		const WRITE = 1 << 1 => PROT_WRITE;
-		const EXEC  = 1 << 2 => PROT_EXEC;
-	}
-
-	struct AtFlags: i32 {
-		const EMPTY_PATH = 1 << 12 => AT_EMPTY_PATH;
-	}
-
-	struct AccessMode: i32 {
-		const F = 0 => F_OK;
-		const X = 1 << 0 => X_OK;
-		const W = 1 << 1 => W_OK;
-		const R = 1 << 2 => R_OK;
-	}
-
-	struct FileMode: u32 {
-		const IRUSR = 0o400 => S_IRUSR;
-		const IWUSR = 0o200 => S_IWUSR;
-		const IXUSR = 0o100 => S_IXUSR;
-
-		const IRGRP = 0o040 => S_IRGRP;
-		const IWGRP = 0o020 => S_IWGRP;
-		const IXGRP = 0o010 => S_IXGRP;
-
-		const IROTH = 0o004 => S_IROTH;
-		const IWOTH = 0o002 => S_IWOTH;
-		const IXOTH = 0o001 => S_IXOTH;
-	}
-}
-
-syscall_enums! {
-	enum ResourceLimit: u32 {
-		STACK = 0x3 => RLIMIT_STACK,
-		RSS   = 0x5 => RLIMIT_RSS,
-		AS    = 0x9 => RLIMIT_AS,
-	}
-
-	enum ArchOption: i32 {
-		SET_FS = 0x1002,
-	}
-
-	enum Ioctl: u64 {
-		TCGETS     = 0x5401 => TCGETS,
-		TIOCGWINSZ = 0x5413 => TIOCGWINSZ,
-	}
-
-	enum Signal: i32 {
-		ILL  =  4 => SIGILL,
-		ABRT =  6 => SIGABRT,
-		BUS  =  7 => SIGBUS,
-		FPE  =  8 => SIGFPE,
-		SEGV = 11 => SIGSEGV,
-	}
-}
-
-#[derive(Clone, Copy, Eq, PartialEq)]
-struct FileDesc(i32);
-
-impl Debug for FileDesc {
-	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-		write!(f, "{}", self.0)
-	}
-}
-
-impl SyscallArg for FileDesc {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		Some(match i32::try_from_reg(reg)? {
-			fd @ 0..=i32::MAX => FileDesc(fd),
-			_ => return None,
-		})
-	}
-}
-
-impl SyscallArg for Option<FileDesc> {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		Some(match i32::try_from_reg(reg)? {
-			-1 => None,
-			fd @ 0..=i32::MAX => Some(FileDesc(fd)),
-			_ => return None,
-		})
-	}
-}
-
-#[derive(Clone, Copy, Eq, PartialEq)]
-enum DirFd {
-	Cwd,
-	Fd(FileDesc),
-}
-
-impl Debug for DirFd {
-	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-		match *self {
-			DirFd::Cwd => write!(f, "AT_FDCWD"),
-			DirFd::Fd(FileDesc(fd)) => write!(f, "{fd}"),
-		}
-	}
-}
-
-impl SyscallArg for DirFd {
-	fn try_from_reg(reg: u64) -> Option<Self> {
-		Some(match i32::try_from_reg(reg)? {
-			AT_FDCWD => Self::Cwd,
-			fd @ 0..=i32::MAX => DirFd::Fd(FileDesc(fd)),
-			_ => return None,
-		})
-	}
-}
diff --git a/ripple/minitrace/src/syscall_abi/arg.rs b/ripple/minitrace/src/syscall_abi/arg.rs
new file mode 100644
index 0000000..b25757c
--- /dev/null
+++ b/ripple/minitrace/src/syscall_abi/arg.rs
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+// SPDX-License-Identifier: OSL-3.0
+
+use {crate::Process, std::ffi::CString};
+
+pub(crate) trait ProcessSyscallArg: Sized {
+	fn try_from_process_reg(process: &Process, reg: u64) -> Option<Self>;
+}
+
+impl ProcessSyscallArg for CString {
+	fn try_from_process_reg(process: &Process, reg: u64) -> Option<Self> {
+		process.read_mem_cstr(reg).ok()
+	}
+}
+
+impl<T: SyscallArg> ProcessSyscallArg for T {
+	fn try_from_process_reg(_process: &Process, reg: u64) -> Option<Self> {
+		SyscallArg::try_from_reg(reg)
+	}
+}
+
+pub(crate) trait SyscallArg: Sized {
+	fn try_from_reg(reg: u64) -> Option<Self>;
+}
+
+impl SyscallArg for u16 {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		reg.try_into().ok()
+	}
+}
+
+impl SyscallArg for u32 {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		reg.try_into().ok()
+	}
+}
+
+impl SyscallArg for u64 {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		Some(reg)
+	}
+}
+
+impl SyscallArg for i32 {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		Some(u32::try_from(reg).ok()? as i32)
+	}
+}
+
+impl SyscallArg for *mut i32 {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		Some(usize::try_from_reg(reg)? as *mut i32)
+	}
+}
+
+impl SyscallArg for usize {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		reg.try_into().ok()
+	}
+}
+
+impl SyscallArg for *const u8 {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		Some(usize::try_from_reg(reg)? as *const u8)
+	}
+}
+
+impl SyscallArg for *mut u8 {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		Some(usize::try_from_reg(reg)? as *mut u8)
+	}
+}
+
+impl SyscallArg for *mut () {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		Some(usize::try_from_reg(reg)? as *mut ())
+	}
+}
+
+impl SyscallArg for *const () {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		Some(usize::try_from_reg(reg)? as *const ())
+	}
+}
diff --git a/ripple/minitrace/src/syscall_abi/fd.rs b/ripple/minitrace/src/syscall_abi/fd.rs
new file mode 100644
index 0000000..cd0c008
--- /dev/null
+++ b/ripple/minitrace/src/syscall_abi/fd.rs
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+// SPDX-License-Identifier: OSL-3.0
+
+use {
+	super::SyscallArg,
+	std::fmt::{self, Debug},
+};
+
+#[derive(Clone, Copy, Eq, PartialEq)]
+pub(crate) struct FileDesc(i32);
+
+impl Debug for FileDesc {
+	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+		write!(f, "{}", self.0)
+	}
+}
+
+impl SyscallArg for FileDesc {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		Some(match i32::try_from_reg(reg)? {
+			fd @ 0..=i32::MAX => FileDesc(fd),
+			_ => return None,
+		})
+	}
+}
+
+impl SyscallArg for Option<FileDesc> {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		Some(match i32::try_from_reg(reg)? {
+			-1 => None,
+			fd @ 0..=i32::MAX => Some(FileDesc(fd)),
+			_ => return None,
+		})
+	}
+}
+
+#[derive(Clone, Copy, Eq, PartialEq)]
+pub(crate) enum DirFd {
+	Cwd,
+	Fd(FileDesc),
+}
+
+const AT_FDCWD: i32 = -100;
+
+impl Debug for DirFd {
+	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+		match *self {
+			DirFd::Cwd => write!(f, "AT_FDCWD"),
+			DirFd::Fd(FileDesc(fd)) => write!(f, "{fd}"),
+		}
+	}
+}
+
+impl SyscallArg for DirFd {
+	fn try_from_reg(reg: u64) -> Option<Self> {
+		Some(match i32::try_from_reg(reg)? {
+			AT_FDCWD => Self::Cwd,
+			fd @ 0..=i32::MAX => DirFd::Fd(FileDesc(fd)),
+			_ => return None,
+		})
+	}
+}
diff --git a/ripple/minitrace/src/syscall_abi/macros.rs b/ripple/minitrace/src/syscall_abi/macros.rs
new file mode 100644
index 0000000..07e6099
--- /dev/null
+++ b/ripple/minitrace/src/syscall_abi/macros.rs
@@ -0,0 +1,161 @@
+// SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+// SPDX-License-Identifier: OSL-3.0
+
+#[cfg(test)]
+use std::fmt::{self, Debug};
+
+#[cfg(test)]
+pub(crate) fn libc_check<T: Debug + Eq + fmt::LowerHex>(
+	item: &'static str,
+	(our_name, our_value): (&'static str, T),
+	(libc_name, libc_value): (&'static str, T),
+) {
+	match () {
+		_ if libc_name.ends_with(our_name) => {}
+		_ if libc_name.starts_with(&format!("{our_name}_")) => {}
+		() => panic!("{libc_name} doesn't match {our_name}"),
+	}
+
+	assert!(
+		our_value == libc_value,
+		"{item}::{our_name} ({our_value:#x}) != libc::{libc_name} ({libc_value:#x})",
+	);
+}
+
+#[macro_export]
+macro_rules! syscall_bitflags {
+	(
+		$(
+			struct $BitFlags:ident: $T:ty {
+				$(
+					const $FLAG:ident = $value:expr => $LIBC_FLAG:ident;
+				)*
+			}
+		)*
+	) => {
+		#[test]
+		fn verify_syscall_bitflags() {
+			$(
+				$BitFlags::verify();
+			)*
+		}
+
+		$(
+			$crate::bitflags! {
+				pub(crate) struct $BitFlags: $T {
+					$(
+						const $FLAG = $value;
+					)*
+				}
+			}
+
+			impl $BitFlags {
+				#[cfg(test)]
+				fn verify() {
+					$(
+						$crate::syscall_abi::macros::libc_check(
+							stringify!($BitFlags),
+							(stringify!($FLAG), Self::$FLAG.bits()),
+							(stringify!($LIBC_FLAG), $crate::libc::$LIBC_FLAG)
+						);
+					)*
+				}
+			}
+
+			impl $crate::syscall_abi::SyscallArg for $BitFlags {
+				fn try_from_reg(reg: u64) -> Option<Self> {
+					$crate::syscall_abi::SyscallArg::try_from_reg(reg).and_then(Self::from_bits)
+				}
+			}
+		)*
+	};
+}
+
+#[macro_export]
+macro_rules! syscall_enums {
+	(
+		$(
+			enum $Enum:ident: $T:ty {
+				$(
+					$VARIANT:ident = $value:literal $(=> $LIBC_VALUE:ident)?,
+				)*
+			}
+		)*
+	) => {
+		#[test]
+		fn verify_syscall_enums() {
+			$(
+				$Enum::verify();
+			)*
+		}
+
+		$(
+			#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+			#[allow(non_camel_case_types)]
+			pub(crate) enum $Enum {
+				$($VARIANT = $value),*
+			}
+
+			impl $crate::syscall_abi::SyscallArg for $Enum {
+				fn try_from_reg(reg: u64) -> Option<Self> {
+					let reg = <$T as $crate::syscall_abi::SyscallArg>::try_from_reg(reg)?;
+					Some(match reg {
+						$(
+							$value => $Enum::$VARIANT,
+						)*
+						_ => return None
+					})
+				}
+			}
+
+			impl $Enum {
+				#[cfg(test)]
+				fn verify() {
+					$(
+						$(
+							$crate::syscall_abi::macros::libc_check(
+								stringify!($Enum),
+								(stringify!($VARIANT), Self::$VARIANT as $T),
+								(stringify!($LIBC_VALUE), $crate::libc::$LIBC_VALUE)
+							);
+						)?
+					)*
+				}
+			}
+		)*
+	};
+}
+
+#[macro_export]
+macro_rules! define_syscalls {
+	(enum $SyscallEntry:ident {
+		$(fn $syscall:ident ( $($arg:ident : $Arg:ty),* ) -> $Ret:ty = $nr:literal ;)*
+	}) => {
+		#[derive(Debug, Clone)]
+		#[allow(non_camel_case_types)]
+		// TODO(edef): re-enable dead_code lint when we start fully interpreting syscall args
+		#[allow(dead_code)]
+		pub(crate) enum $SyscallEntry {
+			$($syscall {
+				$($arg : $Arg),*
+			}),*
+		}
+
+		impl $SyscallEntry {
+			pub(crate) fn from_regs(process: &Process, regs: $crate::libc::user_regs_struct) -> anyhow::Result<$SyscallEntry> {
+				use anyhow::bail;
+				Ok(match (regs.orig_rax, [regs.rdi, regs.rsi, regs.rdx, regs.r10, regs.r8, regs.r9]) {
+					$(
+						($nr, [$($arg,)* ..]) => $SyscallEntry::$syscall {
+							$($arg: match ProcessSyscallArg::try_from_process_reg(process, $arg) {
+								Some(x) => x,
+								None => bail!("couldn't parse {}(2) {}: {:#08x}", stringify!($syscall), stringify!($arg), $arg)
+							}),*
+						},
+					)*
+					(n, _) => bail!("unknown syscall number {n}")
+				})
+			}
+		}
+	}
+}
diff --git a/ripple/minitrace/src/syscall_abi/mod.rs b/ripple/minitrace/src/syscall_abi/mod.rs
new file mode 100644
index 0000000..e0b35a9
--- /dev/null
+++ b/ripple/minitrace/src/syscall_abi/mod.rs
@@ -0,0 +1,127 @@
+// SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+// SPDX-FileCopyrightText: V <v@unfathomable.blue>
+// SPDX-License-Identifier: OSL-3.0
+
+use {
+	crate::{define_syscalls, syscall_bitflags, syscall_enums, Process},
+	std::ffi::CString,
+};
+
+pub(crate) use arg::{ProcessSyscallArg, SyscallArg};
+pub(crate) use fd::{DirFd, FileDesc};
+
+mod arg;
+mod fd;
+pub mod macros;
+
+type SigAction = ();
+type SysInfo = ();
+type Tms = ();
+type Stat = ();
+type RobustListHead = ();
+type RLimit64 = ();
+
+define_syscalls! {
+	enum SyscallEntry {
+		fn read(fd: FileDesc, buf: *mut u8, count: usize) -> i64 = 0;
+		fn write(fd: FileDesc, buf: *const u8, count: usize) -> i64 = 1;
+		fn close(fd: FileDesc) -> i64 = 3;
+		fn mmap(addr: u64, len: u64, prot: ProtFlags, flags: MapFlags, fd: Option<FileDesc>, off: u64) -> i64 = 9;
+		fn mprotect(addr: u64, len: usize, prot: ProtFlags) -> i64 = 10;
+		fn brk(brk: u64) -> i64 = 12;
+		fn rt_sigaction(sig: Signal, act: *const SigAction, oact: *mut SigAction, sigsetsize: usize) -> i64 = 13;
+		fn ioctl(fd: FileDesc, cmd: Ioctl, arg: u64) -> i64 = 16;
+		fn pread64(fd: FileDesc, buf: *mut u8, count: usize, pos: u64) -> i64 = 17;
+		fn access(filename: CString, mode: AccessMode) -> i64 = 21;
+		fn getcwd(buf: *mut u8, size: u64) -> i64 = 79;
+		fn readlink(path: CString, buf: *mut u8, bufsiz: i32) -> i64 = 89;
+		fn sysinfo(info: *mut SysInfo) -> i64 = 99;
+		fn times(tbuf: *mut Tms) -> i64 = 100;
+		fn arch_prctl(option: ArchOption, arg2: u64) -> i64 = 158;
+		fn set_tid_address(tidptr: *mut i32) -> i64 = 218;
+		fn exit_group(error_code: i32) -> i64 = 231;
+		fn openat(dfd: DirFd, filename: CString, flags: OpenFlags, mode: FileMode) -> i64 = 257;
+		fn newfstatat(dfd: DirFd, filename: CString, statbuf: *mut Stat, flags: AtFlags) -> i64 = 262;
+		fn set_robust_list(head: *mut RobustListHead, len: usize) -> i64 = 273;
+		fn prlimit64(pid: i32, resource: ResourceLimit, new_rlim: *const RLimit64, old_rlim: *mut RLimit64) -> i64 = 302;
+		fn getrandom(ubuf: *mut u8, len: usize, flags: GrndFlags) -> i64 = 318;
+	}
+}
+
+syscall_bitflags! {
+	struct OpenFlags: i32 {
+		const WRONLY  = 1 <<  0 => O_WRONLY;
+		const CREAT   = 1 <<  6 => O_CREAT;
+		const NOCTTY  = 1 <<  8 => O_NOCTTY;
+		const TRUNC   = 1 <<  9 => O_TRUNC;
+		const CLOEXEC = 1 << 19 => O_CLOEXEC;
+	}
+
+	struct GrndFlags: u32 {
+		const NONBLOCK = 1 << 0 => GRND_NONBLOCK;
+		const RANDOM   = 1 << 1 => GRND_RANDOM;
+	}
+
+	struct MapFlags: i32 {
+		const PRIVATE   = 1 <<  1 => MAP_PRIVATE;
+		const FIXED     = 1 <<  4 => MAP_FIXED;
+		const ANONYMOUS = 1 <<  5 => MAP_ANONYMOUS;
+		const DENYWRITE = 1 << 11 => MAP_DENYWRITE;
+	}
+
+	struct ProtFlags: i32 {
+		const READ  = 1 << 0 => PROT_READ;
+		const WRITE = 1 << 1 => PROT_WRITE;
+		const EXEC  = 1 << 2 => PROT_EXEC;
+	}
+
+	struct AtFlags: i32 {
+		const EMPTY_PATH = 1 << 12 => AT_EMPTY_PATH;
+	}
+
+	struct AccessMode: i32 {
+		const F = 0 => F_OK;
+		const X = 1 << 0 => X_OK;
+		const W = 1 << 1 => W_OK;
+		const R = 1 << 2 => R_OK;
+	}
+
+	struct FileMode: u32 {
+		const IRUSR = 0o400 => S_IRUSR;
+		const IWUSR = 0o200 => S_IWUSR;
+		const IXUSR = 0o100 => S_IXUSR;
+
+		const IRGRP = 0o040 => S_IRGRP;
+		const IWGRP = 0o020 => S_IWGRP;
+		const IXGRP = 0o010 => S_IXGRP;
+
+		const IROTH = 0o004 => S_IROTH;
+		const IWOTH = 0o002 => S_IWOTH;
+		const IXOTH = 0o001 => S_IXOTH;
+	}
+}
+
+syscall_enums! {
+	enum ResourceLimit: u32 {
+		STACK = 0x3 => RLIMIT_STACK,
+		RSS   = 0x5 => RLIMIT_RSS,
+		AS    = 0x9 => RLIMIT_AS,
+	}
+
+	enum ArchOption: i32 {
+		SET_FS = 0x1002,
+	}
+
+	enum Ioctl: u64 {
+		TCGETS     = 0x5401 => TCGETS,
+		TIOCGWINSZ = 0x5413 => TIOCGWINSZ,
+	}
+
+	enum Signal: i32 {
+		ILL  =  4 => SIGILL,
+		ABRT =  6 => SIGABRT,
+		BUS  =  7 => SIGBUS,
+		FPE  =  8 => SIGFPE,
+		SEGV = 11 => SIGSEGV,
+	}
+}