diff --git a/kernel/libk/src/task/thread.rs b/kernel/libk/src/task/thread.rs index 024d6c24..7466b3c8 100644 --- a/kernel/libk/src/task/thread.rs +++ b/kernel/libk/src/task/thread.rs @@ -350,6 +350,7 @@ impl Thread { if debug.tracer.is_some() { let timestamp = monotonic_time(); debug.store_state(frame); + // log::info!("TRACE {payload:?}"); self.events.trace.write(TraceEvent { suspend, timestamp, @@ -513,7 +514,11 @@ impl Thread { /// Returns `true` if the thread is a tracee of given process pub fn is_tracee_of(&self, pid: ProcessId) -> bool { - self.debug.lock().tracer == Some(pid) + { + let tracer = self.debug.lock().tracer; + // log::info!("{:?}'s tracer: {:?}", self.id, tracer); + tracer == Some(pid) + } } pub fn attach_trace(&self, tracer: ProcessId) -> Result<(), Error> { @@ -577,11 +582,13 @@ impl Thread { DebugControl::Detach => todo!(), DebugControl::Resume => { let single_step = debug::Resume::load_request(input)?; + // log::info!("Resume single_step={single_step}"); self.resume(single_step); debug::Resume::store_response(&(), buffer) } DebugControl::SetTraceFlags => { let flags = debug::SetTraceFlags::load_request(buffer)?; + // log::info!("SetTraceFlags {flags:?}"); self.set_trace_flags(flags); debug::SetTraceFlags::store_response(&(), buffer) } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index c4c5e972..6cb33016 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -75,8 +75,10 @@ cfg_if::cfg_if! { if #[cfg(target_arch = "x86_64")] { extern crate ygg_driver_net_igbe; } else if #[cfg(target_arch = "aarch64")] { + extern crate ygg_driver_bsp_arm; extern crate ygg_driver_bsp_bcm283x; } else if #[cfg(target_arch = "riscv64")] { + extern crate ygg_driver_bsp_riscv; extern crate ygg_driver_bsp_jh7110; extern crate ygg_driver_net_stmmac; diff --git a/userspace/Cargo.lock b/userspace/Cargo.lock index 05b38b88..be156e1d 100644 --- a/userspace/Cargo.lock +++ b/userspace/Cargo.lock @@ -2970,6 +2970,8 @@ name = "strace" version = "0.1.0" dependencies = [ "clap", + "cross", + "libc", "runtime", ] diff --git a/userspace/lib/cross/src/debug.rs b/userspace/lib/cross/src/debug.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/userspace/lib/cross/src/debug.rs @@ -0,0 +1 @@ + diff --git a/userspace/lib/cross/src/lib.rs b/userspace/lib/cross/src/lib.rs index 89d46d45..c23721f2 100644 --- a/userspace/lib/cross/src/lib.rs +++ b/userspace/lib/cross/src/lib.rs @@ -4,6 +4,7 @@ pub(crate) mod sys; +pub mod debug; pub mod fs; pub mod io; pub mod mem; diff --git a/userspace/lib/cross/src/process.rs b/userspace/lib/cross/src/process.rs index 444cd2b0..283fb2d2 100644 --- a/userspace/lib/cross/src/process.rs +++ b/userspace/lib/cross/src/process.rs @@ -1,6 +1,64 @@ use std::io; +#[derive(Debug)] +pub enum TraceEvent { + SyscallEntry(u64, [u64; 6]), + SyscallExit(i64), + Exited, +} + +pub trait ChildTrace { + fn next_event(&mut self) -> io::Result; + fn resume(&mut self) -> io::Result<()>; + fn kill(&mut self) -> io::Result<()>; + + unsafe fn peek_u8(&mut self, address: usize) -> io::Result { + let aligned = address & !7; + let shift = (address & 7) << 3; + let word = self.peek_u64(aligned)?; + Ok((word >> shift) as u8) + } + unsafe fn peek_u32(&mut self, address: usize) -> io::Result { + eprintln!("PEEK U32 @ {address:#x}"); + assert_eq!(address & 3, 0); + let aligned = address & !7; + let shift = (address & 7) << 3; + let word = self.peek_u64(aligned)?; + Ok((word >> shift) as u32) + } + unsafe fn peek_u64(&mut self, address: usize) -> io::Result; + + unsafe fn peek_bytes(&mut self, address: usize, buffer: &mut [u8]) -> io::Result<()> { + for i in 0..buffer.len() { + buffer[i] = self.peek_u8(address + i)?; + } + Ok(()) + } + + unsafe fn peek_cstr(&mut self, address: usize, buffer: &mut [u8]) -> io::Result { + let mut len = 0; + while len < buffer.len() - 1 { + let byte = self.peek_u8(address + len)?; + buffer[len] = byte; + if byte == 0 { + break; + } + len += 1; + } + Ok(len) + } + + unsafe fn peek_usize(&mut self, address: usize) -> io::Result { + // FIXME I only support 64-bit archs + Ok(self.peek_u64(address)? as _) + } +} + pub trait CommandSpawnExt { + type ChildTrace: ChildTrace; + fn create_session(&mut self) -> io::Result<&mut Self>; fn create_process_group(&mut self) -> io::Result<&mut Self>; + // TODO options for tracing across subchildren, etc + fn spawn_with_trace(&mut self) -> io::Result; } diff --git a/userspace/lib/cross/src/sys/unix/mod.rs b/userspace/lib/cross/src/sys/unix/mod.rs index 59f5c33d..18ab8587 100644 --- a/userspace/lib/cross/src/sys/unix/mod.rs +++ b/userspace/lib/cross/src/sys/unix/mod.rs @@ -10,10 +10,12 @@ pub mod time; pub mod timer; use std::{ - ffi::c_int, + ffi::{c_int, c_void}, io, + mem::MaybeUninit, os::{fd::RawFd, unix::process::CommandExt}, process::Command, + ptr::null_mut, sync::Mutex, }; @@ -26,7 +28,126 @@ pub use socket::{BorrowedAddressImpl, LocalPacketSocketImpl, OwnedAddressImpl}; pub use term::RawStdinImpl; pub use timer::TimerFdImpl; -use crate::process::CommandSpawnExt; +use crate::process::{ChildTrace, CommandSpawnExt, TraceEvent}; + +pub struct ChildTraceImpl { + child: libc::pid_t, +} + +impl ChildTrace for ChildTraceImpl { + fn next_event(&mut self) -> io::Result { + let ev = loop { + unsafe { + let mut status = 0; + if libc::waitpid(self.child, &mut status, 0) < 0 { + return Err(io::Error::last_os_error()); + } + if libc::WIFSTOPPED(status) { + let stopsig = libc::WSTOPSIG(status); + if stopsig == libc::SIGTRAP | 0x80 { + let mut syscall_info = MaybeUninit::::zeroed(); + if libc::ptrace( + libc::PTRACE_GET_SYSCALL_INFO, + self.child, + size_of::(), + syscall_info.as_mut_ptr(), + ) < 0 + { + return Err(io::Error::last_os_error()); + } + + let syscall_info = syscall_info.assume_init(); + match syscall_info.op { + libc::PTRACE_SYSCALL_INFO_EXIT => { + break TraceEvent::SyscallExit(syscall_info.u.exit.sval); + } + libc::PTRACE_SYSCALL_INFO_ENTRY => { + break TraceEvent::SyscallEntry( + syscall_info.u.entry.nr, + syscall_info.u.entry.args, + ); + } + _ => self.resume()?, + } + } else if stopsig == libc::SIGTRAP { + let event = (status >> 16) & 0xffff; + eprintln!("event = {event}"); + match event { + libc::PTRACE_EVENT_EXIT => { + // Ignore + eprintln!("PTRACE_EVENT_EXIT"); + self.resume()?; + } + libc::PTRACE_EVENT_VFORK => { + eprintln!("PTRACE_EVENT_VFORK"); + self.resume()?; + } + libc::PTRACE_EVENT_EXEC => { + // Ignore + eprintln!("PTRACE_EVENT_EXEC"); + self.resume()?; + } + _ => todo!(), + } + } else { + todo!("signum == {stopsig}"); + } + } else if libc::WIFEXITED(status) { + break TraceEvent::Exited; + } else { + todo!(); + } + } + }; + + Ok(ev) + } + + fn kill(&mut self) -> io::Result<()> { + todo!() + } + + fn resume(&mut self) -> io::Result<()> { + if unsafe { + libc::ptrace( + libc::PTRACE_SYSCALL, + self.child, + null_mut::(), + null_mut::(), + ) + } == 0 + { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } + + unsafe fn peek_u64(&mut self, address: usize) -> io::Result { + libc::__errno_location().write_volatile(0); + let word = libc::ptrace( + libc::PTRACE_PEEKDATA, + self.child, + address, + null_mut::(), + ); + if libc::__errno_location().read_volatile() != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(word as _) + } + } +} + +impl Drop for ChildTraceImpl { + fn drop(&mut self) { + let mut ignore = 0; + unsafe { + libc::ptrace(libc::PTRACE_DETACH, self.child); + libc::waitpid(self.child, &mut ignore, 0); + } + } +} fn dummy_sigint() {} @@ -61,6 +182,8 @@ pub fn clone_fd(fd: RawFd) -> io::Result { } impl CommandSpawnExt for Command { + type ChildTrace = ChildTraceImpl; + fn create_process_group(&mut self) -> io::Result<&mut Self> { Ok(self.process_group(0)) } @@ -69,4 +192,54 @@ impl CommandSpawnExt for Command { // TODO Ok(self) } + + fn spawn_with_trace(&mut self) -> io::Result { + unsafe { + match libc::fork() { + 0 => { + // Child + // if libc::ptrace(libc::PTRACE_TRACEME, libc::getpid()) != 0 { + // panic!("PTRACE_TRACEME: {}", io::Error::last_os_error()); + // } + libc::raise(libc::SIGSTOP); + panic!("exec error: {}", self.exec()); + } + code if code < 0 => { + // Error + Err(io::Error::last_os_error()) + } + pid => { + let options = libc::PTRACE_O_TRACESYSGOOD | libc::PTRACE_O_TRACEEXEC; + let mut ignore = 0; + // Parent + if libc::ptrace( + libc::PTRACE_SEIZE, + pid, + null_mut::(), + null_mut::(), + ) != 0 + { + // TODO waitpid child? kill child? + eprintln!("PTRACE_SEIZE error"); + return Err(io::Error::last_os_error()); + } + if libc::waitpid(pid, &mut ignore, 0) < 0 { + return Err(io::Error::last_os_error()); + } + if libc::ptrace(libc::PTRACE_SETOPTIONS, pid, null_mut::(), options) + != 0 + { + return Err(io::Error::last_os_error()); + } + libc::ptrace( + libc::PTRACE_CONT, + pid, + null_mut::(), + null_mut::(), + ); + Ok(ChildTraceImpl { child: pid }) + } + } + } + } } diff --git a/userspace/lib/cross/src/sys/yggdrasil/mod.rs b/userspace/lib/cross/src/sys/yggdrasil/mod.rs index 273688ac..6d295f4b 100644 --- a/userspace/lib/cross/src/sys/yggdrasil/mod.rs +++ b/userspace/lib/cross/src/sys/yggdrasil/mod.rs @@ -11,8 +11,20 @@ pub mod timer; use std::{ io, - os::{fd::RawFd, yggdrasil::process::CommandExt}, - process::Command, + mem::MaybeUninit, + os::{ + fd::RawFd, + yggdrasil::process::{ChildExt, CommandExt}, + }, + process::{Child, Command}, +}; + +use runtime::{ + abi::{ + arch::SavedFrame, + debug::{self, TraceFlags}, + }, + rt::{debug::debug_control, process::ThreadEvent}, }; pub use mem::{FileMappingImpl, SharedMemoryImpl}; @@ -24,7 +36,131 @@ pub use socket::{BorrowedAddressImpl, LocalPacketSocketImpl, OwnedAddressImpl}; pub use term::RawStdinImpl; pub use timer::TimerFdImpl; -use crate::process::CommandSpawnExt; +use crate::process::{ChildTrace, CommandSpawnExt, TraceEvent}; + +pub struct ChildTraceImpl { + #[allow(unused)] + child: Child, + buffer: [u8; 512], + tid: u32, +} + +impl ChildTraceImpl { + fn attach(child: Child) -> io::Result { + let mut buffer = [0; 512]; + let tid = child.main_thread_id()?; + + debug_control::( + tid, + &mut buffer, + &(TraceFlags::SYSCALL_ENTRY | TraceFlags::SYSCALL_EXIT), + ) + .map_err(io::Error::from) + .unwrap(); + + Ok(Self { child, tid, buffer }) + } + + fn read_regs(&mut self) -> io::Result { + debug_control::(self.tid, &mut self.buffer, &()) + .map_err(io::Error::from) + } +} + +#[cfg(any(rust_analyzer, target_arch = "aarch64"))] +impl ChildTraceImpl { + fn read_syscall_entry(&mut self) -> io::Result<(u64, [u64; 6])> { + let frame = self.read_regs()?; + Ok(( + frame.gp_regs[8] as _, + [ + frame.gp_regs[0] as _, + frame.gp_regs[1] as _, + frame.gp_regs[2] as _, + frame.gp_regs[3] as _, + frame.gp_regs[4] as _, + frame.gp_regs[5] as _, + ], + )) + } + + fn read_syscall_exit(&mut self) -> io::Result { + let frame = self.read_regs()?; + Ok(frame.gp_regs[0] as _) + } +} + +#[cfg(any(rust_analyzer, target_arch = "x86_64"))] +impl ChildTraceImpl { + fn read_syscall_entry(&mut self) -> io::Result<(u64, [u64; 6])> { + let frame = self.read_regs()?; + Ok(( + frame.rax as _, + [ + frame.rdi as _, + frame.rsi as _, + frame.rdx as _, + frame.r10 as _, + frame.r8 as _, + frame.r9 as _, + ], + )) + } + + fn read_syscall_exit(&mut self) -> io::Result { + let frame = self.read_regs()?; + Ok(frame.rax as _) + } +} + +impl ChildTrace for ChildTraceImpl { + unsafe fn peek_u64(&mut self, address: usize) -> io::Result { + let word = debug_control::(self.tid, &mut self.buffer, &address)?; + Ok(word as _) + } + + fn kill(&mut self) -> io::Result<()> { + todo!() + } + + fn resume(&mut self) -> io::Result<()> { + debug_control::(self.tid, &mut self.buffer, &false).map_err(io::Error::from) + } + + fn next_event(&mut self) -> io::Result { + loop { + let mut event = MaybeUninit::uninit(); + unsafe { runtime::rt::sys::wait_thread(self.tid, &mut event) } + .map_err(io::Error::from)?; + let event = unsafe { event.assume_init() }; + + match event { + ThreadEvent::Trace(trace) => { + let event = match trace.payload { + debug::TraceEventPayload::SyscallEntry(_seq) => { + let (nr, args) = self.read_syscall_entry()?; + TraceEvent::SyscallEntry(nr, args) + } + debug::TraceEventPayload::SyscallExit(_seq) => { + let status = self.read_syscall_exit()?; + TraceEvent::SyscallExit(status as _) + } + debug::TraceEventPayload::SingleStep => { + self.resume()?; + continue; + } + }; + + break Ok(event); + } + ThreadEvent::Exited => { + // TODO status + break Ok(TraceEvent::Exited); + } + } + } + } +} pub fn set_sigint_handler(_handler: fn()) {} @@ -45,6 +181,13 @@ pub fn clone_fd(fd: RawFd) -> io::Result { } impl CommandSpawnExt for Command { + type ChildTrace = ChildTraceImpl; + + fn spawn_with_trace(&mut self) -> io::Result { + unsafe { self.attach_tracing() }; + self.spawn().and_then(ChildTraceImpl::attach) + } + fn create_process_group(&mut self) -> io::Result<&mut Self> { let group = unsafe { runtime::rt::sys::create_process_group() }; Ok(self.process_group(group)) diff --git a/userspace/tools/rdb/src/main.rs b/userspace/tools/rdb/src/main.rs index 6e14af7d..7b48e39d 100644 --- a/userspace/tools/rdb/src/main.rs +++ b/userspace/tools/rdb/src/main.rs @@ -1,68 +1,3 @@ #![feature(yggdrasil_os, if_let_guard)] fn main() {} -// use std::{fmt::{LowerHex, Display}, io, path::PathBuf, process::Command}; -// -// use clap::Parser; -// use debugger::{Debugger, SymbolResolver}; -// use imp::TargetImpl; -// use yggdrasil_abi::arch::SavedFrame; -// -// #[cfg(any(target_arch = "x86_64", target_arch = "x86", rust_analyzer))] -// #[path = "x86.rs"] -// pub mod imp; -// #[cfg(any(target_arch = "aarch64", rust_analyzer))] -// #[path = "aarch64.rs"] -// pub mod imp; -// -// pub mod comm; -// pub mod state; -// -// pub mod debugger; -// -// #[derive(thiserror::Error, Debug)] -// pub enum Error { -// #[error("I/O error: {0}")] -// IoError(#[from] io::Error), -// #[error("Terminal error: {0}")] -// TermError(#[from] libterm::Error), -// #[error("Debug control error: {0:?}")] -// DebugError(yggdrasil_rt::Error), -// #[error("Invalid address: {0:?}")] -// InvalidAddress(String) -// } -// -// pub trait Target { -// type Instruction; -// type InstructionFormatter: InstructionFormatter; -// type Register: LowerHex; -// -// fn disassemble( -// window: &[u8], -// ip: usize, -// limit: usize, -// ) -> Result, Error>; -// fn new_instruction_formatter(resolver: SymbolResolver) -> Self::InstructionFormatter; -// -// fn register_list(frame: &SavedFrame, out: &mut Vec<(String, Self::Register)>); -// fn flags_register_as_display(frame: &SavedFrame) -> impl Display; -// fn real_ip(frame: &SavedFrame) -> usize; -// } -// -// pub trait InstructionFormatter { -// fn format_instruction(&mut self, insn: &I, out: &mut String); -// } -// -// #[derive(Parser)] -// struct Args { -// program: PathBuf, -// } -// -// fn main() { -// let args = Args::parse(); -// -// let command = Command::new(&args.program); -// let debug: Debugger = Debugger::from_command(&args.program, command).unwrap(); -// -// debug.run().unwrap(); -// } diff --git a/userspace/tools/strace/Cargo.toml b/userspace/tools/strace/Cargo.toml index 431169d3..9754783b 100644 --- a/userspace/tools/strace/Cargo.toml +++ b/userspace/tools/strace/Cargo.toml @@ -4,9 +4,19 @@ version = "0.1.0" edition = "2021" [dependencies] -runtime.workspace = true +cross.workspace = true clap.workspace = true +[target.'cfg(target_os = "yggdrasil")'.dependencies] +runtime.workspace = true + +[target.'cfg(target_os = "linux")'.dependencies] +libc = "*" + +[dev-dependencies] +runtime.workspace = true +libc = "*" + [lints] workspace = true diff --git a/userspace/tools/strace/src/definition/linux/mod.rs b/userspace/tools/strace/src/definition/linux/mod.rs new file mode 100644 index 00000000..0fdb2916 --- /dev/null +++ b/userspace/tools/strace/src/definition/linux/mod.rs @@ -0,0 +1,175 @@ +#[cfg(any(rust_analyzer, target_arch = "x86_64"))] +#[path = "x86_64.rs"] +pub mod imp; + +use std::fmt; + +pub use imp::*; +use libc::{c_int, c_uint}; + +macro_rules! impl_ioctls { + ($f:expr, $v:expr, $ty:ty, [$($ioctl:ident),* $(,)?]) => {{ + #[allow(unreachable_patterns)] + let v = match $v as $ty { + $(libc::$ioctl => stringify!($ioctl),)* + _ => return write!($f, "{:#x}", $v), + }; + write!($f, "{v}") + }}; +} + +fn print_ioctl(f: &mut W, v: c_uint) -> fmt::Result { + impl_ioctls!( + f, + v, + u64, + [ + TCGETS, + TCSETS, + TCSETSW, + TCSETSF, + TCGETA, + TCSETA, + TCSETAW, + TCSETAF, + TCSBRK, + TCXONC, + TCFLSH, + TIOCEXCL, + TIOCNXCL, + TIOCSCTTY, + TIOCGPGRP, + TIOCSPGRP, + TIOCOUTQ, + TIOCSTI, + TIOCGWINSZ, + TIOCSWINSZ, + TIOCMGET, + TIOCMBIS, + TIOCMBIC, + TIOCMSET, + TIOCGSOFTCAR, + TIOCSSOFTCAR, + FIONREAD, + TIOCLINUX, + TIOCCONS, + TIOCGSERIAL, + TIOCSSERIAL, + TIOCPKT, + FIONBIO, + TIOCNOTTY, + TIOCSETD, + TIOCGETD, + TCSBRKP, + TIOCSBRK, + TIOCCBRK, + TIOCGSID, + TCGETS2, + TCSETS2, + TCSETSW2, + TCSETSF2, + TIOCGRS485, + TIOCSRS485, + TIOCGPTN, + TIOCSPTLCK, + TIOCGDEV, + TCGETX, + TCSETX, + TCSETXF, + TCSETXW, + TIOCSIG, + TIOCVHANGUP, + TIOCGPKT, + TIOCGPTLCK, + TIOCGEXCL, + TIOCGPTPEER, + FIONCLEX, + FIOCLEX, + FIOASYNC, + TIOCSERCONFIG, + TIOCSERGWILD, + TIOCSERSWILD, + TIOCGLCKTRMIOS, + TIOCSLCKTRMIOS, + TIOCSERGSTRUCT, + TIOCSERGETLSR, + TIOCSERGETMULTI, + TIOCSERSETMULTI, + TIOCMIWAIT, + TIOCGICOUNT, + ] + ) +} + +fn print_prctl(f: &mut W, c: c_int) -> fmt::Result { + impl_ioctls!( + f, + c, + c_int, + [ + PR_SET_PDEATHSIG, + PR_GET_PDEATHSIG, + PR_GET_DUMPABLE, + PR_SET_DUMPABLE, + PR_GET_UNALIGN, + PR_SET_UNALIGN, + PR_GET_KEEPCAPS, + PR_SET_KEEPCAPS, + PR_GET_FPEMU, + PR_SET_FPEMU, + PR_GET_FPEXC, + PR_SET_FPEXC, + PR_GET_TIMING, + PR_SET_TIMING, + PR_SET_NAME, + PR_GET_NAME, + PR_GET_ENDIAN, + PR_SET_ENDIAN, + PR_GET_SECCOMP, + PR_SET_SECCOMP, + PR_CAPBSET_READ, + PR_CAPBSET_DROP, + PR_GET_TSC, + PR_SET_TSC, + PR_GET_SECUREBITS, + PR_SET_SECUREBITS, + PR_SET_TIMERSLACK, + PR_GET_TIMERSLACK, + PR_TASK_PERF_EVENTS_DISABLE, + PR_TASK_PERF_EVENTS_ENABLE, + PR_MCE_KILL, + PR_SET_MM, + PR_SET_PTRACER, + PR_SET_CHILD_SUBREAPER, + PR_GET_CHILD_SUBREAPER, + PR_SET_NO_NEW_PRIVS, + PR_GET_NO_NEW_PRIVS, + PR_GET_TID_ADDRESS, + PR_SET_THP_DISABLE, + PR_GET_THP_DISABLE, + PR_MPX_ENABLE_MANAGEMENT, + PR_MPX_DISABLE_MANAGEMENT, + PR_SET_FP_MODE, + PR_GET_FP_MODE, + PR_CAP_AMBIENT, + PR_GET_SPECULATION_CTRL, + PR_SET_SPECULATION_CTRL, + PR_SCHED_CORE, + PR_SET_MDWE, + PR_GET_MDWE, + PR_SET_VMA, + ] + ) +} + +fn print_access_mode(f: &mut W, mode: c_int) -> fmt::Result { + let s = match mode { + libc::F_OK => "F_OK", + libc::W_OK => "W_OK", + libc::R_OK => "R_OK", + libc::X_OK => "X_OK", + _ => return write!(f, "{mode:#x}"), + }; + + write!(f, "{s}") +} diff --git a/userspace/tools/strace/src/definition/linux/x86_64.rs b/userspace/tools/strace/src/definition/linux/x86_64.rs new file mode 100644 index 00000000..22b6175c --- /dev/null +++ b/userspace/tools/strace/src/definition/linux/x86_64.rs @@ -0,0 +1,123 @@ +use std::{ffi::c_void, fmt}; + +use cross::process::ChildTrace; +use libc::{c_char, c_int, c_long, c_uint, c_ulong}; + +use crate::definition::{ + imp::{print_access_mode, print_ioctl, print_prctl}, + print_cstring, PrintSyscallArgument, Syscall, +}; + +pub enum SyscallArgument { + Fd(c_int), + MaybeFd(c_int), + Bytes(*const c_char), + BytesMut(*const c_char), + Size(usize), + Filename(*const c_char), + OpenOptions(c_int), + FileMode(c_int), + Ptr(*const c_void), + Long(c_long), + ULong(c_ulong), + Int(c_int), + UInt(c_uint), + Whence(c_int), + Off(libc::off_t), + LOff(libc::loff_t), + IoctlCmd(c_uint), + PrctlCmd(c_int), + AccessMode(c_int), + AtFd(c_int), +} + +impl PrintSyscallArgument for SyscallArgument { + fn print_syscall_argument(&self, f: &mut W, trace: &mut T) { + match self { + Self::Fd(libc::STDIN_FILENO) => write!(f, "STDIN_FILENO"), + Self::Fd(libc::STDOUT_FILENO) => write!(f, "STDOUT_FILENO"), + Self::Fd(libc::STDERR_FILENO) => write!(f, "STDERR_FILENO"), + Self::Fd(fd) => write!(f, "{fd}"), + Self::MaybeFd(-1) => write!(f, ""), + Self::MaybeFd(fd) => write!(f, "{fd}"), + Self::Bytes(bytes) => write!(f, "{bytes:p}"), + Self::BytesMut(bytes) => write!(f, "{bytes:p}"), + Self::Size(size) if *size <= 999 => write!(f, "{size}"), + Self::Size(size) => write!(f, "{size:#x}"), + Self::Filename(filename) => print_cstring(f, trace, filename.addr()), + Self::OpenOptions(opts) => write!(f, "{opts:#x}"), + Self::FileMode(mode) => write!(f, "0{mode:o}"), + Self::Ptr(ptr) => write!(f, "{ptr:p}"), + Self::Long(val) => write!(f, "{val}"), + Self::ULong(val) => write!(f, "{val}"), + Self::Int(val) => write!(f, "{val}"), + Self::UInt(val) => write!(f, "{val}"), + Self::Whence(libc::SEEK_SET) => write!(f, "SEEK_SET"), + Self::Whence(libc::SEEK_CUR) => write!(f, "SEEK_CUR"), + Self::Whence(libc::SEEK_END) => write!(f, "SEEK_END"), + Self::Whence(w) => write!(f, "{w}"), + Self::Off(off) => write!(f, "{off}"), + Self::LOff(off) => write!(f, "{off}"), + &Self::IoctlCmd(v) => print_ioctl(f, v), + &Self::PrctlCmd(v) => print_prctl(f, v), + &Self::AccessMode(m) => print_access_mode(f, m), + Self::AtFd(libc::AT_FDCWD) => write!(f, "AT_FDCWD"), + Self::AtFd(fd) => write!(f, "{fd}"), + } + .ok(); + } +} + +macro_rules! impl_syscall_args { + ($input:expr, [$($collect:expr),*], $i:expr, [$arg:ident, $($tail:ident),+]) => { + impl_syscall_args!($input, [$($collect,)* (SyscallArgument::$arg($input[$i] as _))], $i + 1, [$($tail),+]) + }; + ($input:expr, [$($collect:expr),*], $i:expr, [$arg:ident]) => { + vec![$($collect,)* (SyscallArgument::$arg($input[$i] as _))] + }; + ($input:expr, [], $i:expr, []) => { + vec![] + }; +} + +macro_rules! impl_syscall_parse { + ($( + $nr:literal => $name:ident ( $($arg:ident),* ) + ),+ $(,)?) => { + pub fn parse_syscall(nr: u64, args: &[u64]) -> Option { + Some(match nr { + $($nr => Syscall( + stringify!($name), + impl_syscall_args!(args, [], 0, [$($arg),*]) + ),)+ + _ => return None, + }) + } + }; +} + +impl_syscall_parse!( + 0 => read(Fd, BytesMut, Size), + 1 => write(Fd, Bytes, Size), + 2 => open(Filename, OpenOptions, FileMode), + 3 => close(Fd), + 4 => stat(Filename, BytesMut), + 5 => fstat(Fd, BytesMut), + 6 => lstat(Filename, BytesMut), + 7 => poll(Ptr, ULong, Long), + 8 => lseek(Fd, Off, Whence), + 9 => mmap(Ptr, ULong, ULong, ULong, MaybeFd, ULong), + 10 => mprotect(Ptr, Size, ULong), + 11 => munmap(Ptr, Size), + 12 => brk(Ptr), + // TODO rt_... + 16 => ioctl(Fd, IoctlCmd, ULong), + 17 => pread64(Fd, BytesMut, Size, LOff), + 21 => access(Filename, AccessMode), + 157 => prctl(PrctlCmd, ULong, ULong, ULong), + 218 => set_tid_address(Ptr), + 334 => rseq(Ptr, UInt, Int, UInt), + 257 => openat(AtFd, Filename, OpenOptions, FileMode), + 262 => newfstatat(AtFd, Filename, Ptr, Int), + 24 => sched_yield(), +); diff --git a/userspace/tools/strace/src/definition/mod.rs b/userspace/tools/strace/src/definition/mod.rs new file mode 100644 index 00000000..730f32fd --- /dev/null +++ b/userspace/tools/strace/src/definition/mod.rs @@ -0,0 +1,67 @@ +use std::fmt; + +use cross::process::ChildTrace; + +#[cfg(any(rust_analyzer, target_os = "linux"))] +#[path = "linux/mod.rs"] +pub mod imp; +#[cfg(any(rust_analyzer, target_os = "yggdrasil"))] +#[path = "yggdrasil.rs"] +pub mod imp; + +pub use imp::*; + +pub trait PrintSyscallArgument { + fn print_syscall_argument(&self, f: &mut W, trace: &mut T); +} + +pub struct Syscall(pub &'static str, pub Vec); + +impl Syscall { + pub fn print(&self, f: &mut W, trace: &mut T) { + write!(f, "{}(", self.0).ok(); + for (i, arg) in self.1.iter().enumerate() { + if i != 0 { + write!(f, ", ").ok(); + } + arg.print_syscall_argument(f, trace); + } + write!(f, ")").ok(); + } +} + +pub fn print_string( + f: &mut W, + trace: &mut T, + address: usize, + len: usize, +) -> fmt::Result { + let mut buffer = [0; 64]; + let len = len.min(buffer.len()); + if let Ok(()) = unsafe { trace.peek_bytes(address, &mut buffer[..len]) } { + if let Ok(s) = std::str::from_utf8(&buffer[..len]) { + write!(f, "{s:?}") + } else { + todo!() + } + } else { + write!(f, "") + } +} + +pub fn print_cstring( + f: &mut W, + trace: &mut T, + address: usize, +) -> fmt::Result { + let mut buffer = [0; 64]; + if let Ok(len) = unsafe { trace.peek_cstr(address, &mut buffer) } { + if let Ok(s) = std::str::from_utf8(&buffer[..len]) { + write!(f, "{s:?}") + } else { + todo!() + } + } else { + write!(f, "") + } +} diff --git a/userspace/tools/strace/src/definition/yggdrasil.rs b/userspace/tools/strace/src/definition/yggdrasil.rs new file mode 100644 index 00000000..4c363954 --- /dev/null +++ b/userspace/tools/strace/src/definition/yggdrasil.rs @@ -0,0 +1,335 @@ +use std::fmt; + +use cross::process::ChildTrace; +use runtime::{abi::SyscallFunction, abi_lib::SyscallRegister, rt::io::RawFd}; + +use crate::definition::{print_string, PrintSyscallArgument, Syscall}; + +pub enum SyscallArgument { + Bytes(u64, u64), + BytesMut(u64, u64), + MaybePtr(u64), + Fd(u64), + OptionFd(u64), + Ptr(u64), + Size(u64), + U64(u64), + I64(i64), + U32(u32), + I32(i32), + PtrSystemTime(u64), + ClockType(u64), + Filename(u64, u64), +} + +pub enum ArgType { + Bytes, + BytesMut, + MaybePtr, + Fd, + OptionFd, + Ptr, + Size, + U64, + I64, + U32, + I32, + PtrSystemTime, + ClockType, + Filename, +} + +struct SyscallDefinition(SyscallFunction, &'static str, &'static [ArgType]); + +impl PrintSyscallArgument for SyscallArgument { + fn print_syscall_argument(&self, f: &mut W, trace: &mut T) { + match self { + &Self::MaybePtr(0) => write!(f, "None"), + Self::Fd(fd) => { + let fd = unsafe { RawFd::from_raw(*fd as _) }; + match fd { + RawFd::STDIN => write!(f, "STDIN"), + RawFd::STDOUT => write!(f, "STDOUT"), + RawFd::STDERR => write!(f, "STDERR"), + _ => write!(f, "{fd:?}"), + } + } + Self::OptionFd(fd) => match Option::::from_syscall_register(*fd as _) { + Some(fd) => write!(f, "Some({fd:?})"), + None => write!(f, "None"), + }, + Self::MaybePtr(addr) => write!(f, "Some({addr:#x})"), + Self::Ptr(ptr) => write!(f, "{ptr:#x}"), + Self::U64(val) => write!(f, "{val}"), + Self::I64(val) => write!(f, "{val}"), + Self::U32(val) => write!(f, "{val}"), + Self::I32(val) => write!(f, "{val}"), + Self::Size(val) => write!(f, "{val}"), + Self::Bytes(addr, len) => write!(f, "&[{addr:#x}; {len}]"), + Self::BytesMut(addr, len) => write!(f, "&mut [{addr:#x}; {len}]"), + Self::PtrSystemTime(ptr) => write!(f, "{ptr:#x}"), + Self::ClockType(1) => write!(f, "RealTime"), + Self::ClockType(2) => write!(f, "Monotonic"), + Self::ClockType(v) => write!(f, "{v}"), + Self::Filename(ptr, len) => print_string(f, trace, *ptr as _, *len as _), + } + .ok(); + } +} + +const DEFS: &[SyscallDefinition] = &const { + use ArgType::*; + [ + SyscallDefinition(SyscallFunction::GetRandom, "get_random", &[BytesMut]), + SyscallDefinition( + SyscallFunction::GetClock, + "get_clock", + &[ClockType, PtrSystemTime], + ), + SyscallDefinition( + SyscallFunction::SetClock, + "set_clock", + &[ClockType, PtrSystemTime], + ), + SyscallDefinition(SyscallFunction::Mount, "mount", &[Ptr]), // TODO + SyscallDefinition(SyscallFunction::Unmount, "unmount", &[Ptr]), // TODO + SyscallDefinition(SyscallFunction::LoadModule, "load_module", &[Filename]), + SyscallDefinition( + SyscallFunction::FilesystemControl, + "filesystem_control", + &[OptionFd, U32, Size], + ), + SyscallDefinition( + SyscallFunction::GetSystemInfo, + "get_system_info", + &[U32, BytesMut], + ), // TODO + // Memory management + SyscallDefinition( + SyscallFunction::MapMemory, + "map_memory", + &[MaybePtr, Size, U32, Ptr], + ), + SyscallDefinition(SyscallFunction::UnmapMemory, "unmap_memory", &[Ptr, Size]), + // Process/thread management + SyscallDefinition( + SyscallFunction::CreateProcessGroup, + "create_process_group", + &[], + ), + SyscallDefinition( + SyscallFunction::GetProcessGroupId, + "get_process_group_id", + &[], + ), + SyscallDefinition(SyscallFunction::ExitProcess, "exit_process", &[U64]), // TODO + SyscallDefinition(SyscallFunction::SpawnProcess, "spawn_process", &[Ptr]), // TODO + SyscallDefinition( + SyscallFunction::WaitProcess, + "wait_process", + &[Ptr, Ptr, U32], + ), // TODO + SyscallDefinition(SyscallFunction::GetPid, "get_pid", &[]), + SyscallDefinition(SyscallFunction::GetTid, "get_tid", &[]), + SyscallDefinition(SyscallFunction::SpawnThread, "spawn_thread", &[Ptr]), // TODO + SyscallDefinition(SyscallFunction::ExitThread, "exit_thread", &[]), + SyscallDefinition(SyscallFunction::WaitThread, "wait_thread", &[U32, Ptr]), // TODO + SyscallDefinition( + SyscallFunction::GetThreadOption, + "get_thread_option", + &[U32, BytesMut], + ), // TODO + SyscallDefinition( + SyscallFunction::SetThreadOption, + "set_thread_option", + &[U32, Bytes], + ), // TODO + SyscallDefinition( + SyscallFunction::GetProcessOption, + "get_process_option", + &[U32, U32, BytesMut], + ), // TODO + SyscallDefinition( + SyscallFunction::SetProcessOption, + "set_process_option", + &[U32, U32, Bytes], + ), // TODO + SyscallDefinition(SyscallFunction::Nanosleep, "nanosleep", &[Ptr, Ptr]), // TODO + SyscallDefinition(SyscallFunction::ExitSignal, "exit_signal", &[Ptr]), // TODO + SyscallDefinition(SyscallFunction::SendSignal, "send_signal", &[U32, U32]), // TODO + SyscallDefinition(SyscallFunction::Mutex, "mutex", &[Ptr, Ptr]), // TODO + SyscallDefinition(SyscallFunction::StartSession, "start_session", &[]), + // I/O + SyscallDefinition( + SyscallFunction::Open, + "open", + &[OptionFd, Filename, U32, U32], + ), // TODO + SyscallDefinition( + SyscallFunction::CheckAccess, + "check_access", + &[OptionFd, Filename, U32], + ), // TODO + SyscallDefinition(SyscallFunction::Close, "close", &[Fd]), + SyscallDefinition(SyscallFunction::Write, "write", &[Fd, Bytes]), + SyscallDefinition(SyscallFunction::Read, "read", &[Fd, BytesMut]), + SyscallDefinition(SyscallFunction::Seek, "seek", &[Fd, U32, Ptr]), // TODO + SyscallDefinition(SyscallFunction::Truncate, "truncate", &[Fd, U64]), // TODO + SyscallDefinition(SyscallFunction::Fsync, "fsync", &[Fd, U32]), // TODO + SyscallDefinition(SyscallFunction::ReadAt, "read_at", &[Fd, U64, BytesMut]), + SyscallDefinition(SyscallFunction::WriteAt, "write_at", &[Fd, U64, Bytes]), + SyscallDefinition( + SyscallFunction::GetFileOption, + "get_file_option", + &[Fd, U32, BytesMut], + ), // TODO + SyscallDefinition( + SyscallFunction::SetFileOption, + "set_file_option", + &[Fd, U32, Bytes], + ), // TODO + SyscallDefinition( + SyscallFunction::OpenDirectory, + "open_directory", + &[OptionFd, Filename], + ), + SyscallDefinition( + SyscallFunction::ReadDirectoryEntries, + "read_directory_entries", + &[Fd, Ptr, Size], + ), // TODO + SyscallDefinition( + SyscallFunction::CreateDirectory, + "create_directory", + &[OptionFd, Filename, U32], + ), // TODO + SyscallDefinition( + SyscallFunction::ReadLink, + "read_link", + &[OptionFd, Filename, BytesMut], + ), + SyscallDefinition( + SyscallFunction::Remove, + "remove", + &[OptionFd, Filename, U32], + ), // TODO + SyscallDefinition(SyscallFunction::Rename, "rename", &[Ptr]), // TODO + SyscallDefinition(SyscallFunction::CloneFd, "clone_fd", &[Fd, OptionFd]), + SyscallDefinition( + SyscallFunction::UpdateMetadata, + "update_metadata", + &[OptionFd, Filename, Ptr], + ), // TODO + SyscallDefinition( + SyscallFunction::GetMetadata, + "get_metadata", + &[OptionFd, Filename, Ptr, U32], + ), // TODO + SyscallDefinition( + SyscallFunction::DeviceRequest, + "device_request", + &[Fd, U32, BytesMut, Size], + ), // TODO + SyscallDefinition(SyscallFunction::CreateTimer, "create_timer", &[U32]), // TODO + SyscallDefinition(SyscallFunction::CreatePid, "create_pid", &[Ptr, U32]), // TODO + SyscallDefinition(SyscallFunction::CreatePty, "create_pty", &[Ptr, Ptr, Ptr]), // TODO + SyscallDefinition( + SyscallFunction::CreateSharedMemory, + "create_shared_memory", + &[Size], + ), + SyscallDefinition( + SyscallFunction::CreatePollChannel, + "create_poll_channel", + &[], + ), + SyscallDefinition(SyscallFunction::CreatePipe, "create_pipe", &[Ptr]), // TODO + SyscallDefinition( + SyscallFunction::PollChannelWait, + "poll_channel_wait", + &[Fd, Ptr, U32, Ptr], + ), // TODO + SyscallDefinition( + SyscallFunction::PollChannelControl, + "poll_channel_control", + &[Fd, U32, Fd], + ), // TODO + // Network + SyscallDefinition(SyscallFunction::CreateSocket, "create_socket", &[U32]), // TODO + SyscallDefinition(SyscallFunction::Bind, "bind", &[Fd, Bytes]), // TODO + SyscallDefinition(SyscallFunction::Listen, "listen", &[Fd]), + SyscallDefinition(SyscallFunction::Connect, "connect", &[Fd, Bytes]), // TODO + SyscallDefinition(SyscallFunction::Accept, "accept", &[Fd, MaybePtr]), // TODO + SyscallDefinition(SyscallFunction::Shutdown, "shutdown", &[Fd, U32]), // TODO + SyscallDefinition(SyscallFunction::SendTo, "send_to", &[Fd, Bytes, MaybePtr]), // TODO + SyscallDefinition( + SyscallFunction::ReceiveFrom, + "receive_from", + &[Fd, BytesMut, MaybePtr], + ), // TODO + SyscallDefinition( + SyscallFunction::GetSocketOption, + "get_socket_option", + &[Fd, U32, BytesMut], + ), // TODO + SyscallDefinition( + SyscallFunction::SetSocketOption, + "set_socket_option", + &[Fd, U32, Bytes], + ), // TODO + SyscallDefinition(SyscallFunction::SendMessage, "send_message", &[Fd, Ptr]), // TODO + SyscallDefinition( + SyscallFunction::ReceiveMessage, + "receive_message", + &[Fd, Ptr], + ), // TODO + // C compat + SyscallDefinition(SyscallFunction::Fork, "fork", &[]), + SyscallDefinition(SyscallFunction::Execve, "execve", &[Ptr]), // TODO + // Debugging + SyscallDefinition(SyscallFunction::DebugTrace, "debug_trace", &[U32, Filename]), // TODO + SyscallDefinition( + SyscallFunction::DebugControl, + "debug_control", + &[U32, U32, BytesMut, Size], + ), // TODO + ] +}; + +impl SyscallDefinition { + fn parse(&self, args: &[u64]) -> Syscall { + let mut res = vec![]; + let mut pos = 0; + for arg_def in self.2.iter() { + use SyscallArgument::*; + + let (a, l) = match arg_def { + ArgType::MaybePtr => (MaybePtr(args[pos]), 1), + ArgType::Bytes => (Bytes(args[pos], args[pos + 1]), 2), + ArgType::BytesMut => (BytesMut(args[pos], args[pos + 1]), 2), + ArgType::Fd => (Fd(args[pos]), 1), + ArgType::OptionFd => (OptionFd(args[pos]), 1), + ArgType::Ptr => (Ptr(args[pos]), 1), + ArgType::Size => (Size(args[pos]), 1), + ArgType::U64 => (U64(args[pos]), 1), + ArgType::I64 => (I64(args[pos] as _), 1), + ArgType::U32 => (U32(args[pos] as _), 1), + ArgType::I32 => (I32(args[pos] as _), 1), + ArgType::PtrSystemTime => (PtrSystemTime(args[pos]), 1), + ArgType::ClockType => (ClockType(args[pos]), 1), + ArgType::Filename => (Filename(args[pos], args[pos + 1]), 2), + }; + + pos += l; + res.push(a); + } + + Syscall(self.1, res) + } +} + +pub fn parse_syscall(nr: u64, args: &[u64]) -> Option { + let func = SyscallFunction::try_from(nr as usize).ok()?; + let def = DEFS.iter().find(|e| e.0 == func)?; + Some(def.parse(args)) +} diff --git a/userspace/tools/strace/src/main.rs b/userspace/tools/strace/src/main.rs index c87e1b35..bf1aa65b 100644 --- a/userspace/tools/strace/src/main.rs +++ b/userspace/tools/strace/src/main.rs @@ -1,36 +1,68 @@ -#![feature(rustc_private, yggdrasil_os, let_chains)] - +#![feature(rustc_private)] use std::{ - io, process::{Command, ExitCode}, + sync::atomic::{AtomicBool, Ordering}, }; use clap::Parser; -use tracer::CommandTracer; +use cross::{ + process::{ChildTrace, CommandSpawnExt, TraceEvent}, + signal, +}; -pub mod format; -pub mod tracer; +use crate::definition::parse_syscall; + +pub mod definition; #[derive(Debug, Parser)] struct Args { - command: String, + program: String, args: Vec, } -fn run(args: &Args) -> io::Result<()> { - let mut command = Command::new(&args.command); - command.args(&args.args); - let tracer = CommandTracer::spawn(command)?; - tracer.run(format::format_syscall) -} - fn main() -> ExitCode { let args = Args::parse(); - match run(&args) { - Ok(()) => ExitCode::SUCCESS, - Err(error) => { - eprintln!("{}: {error}", args.command); - ExitCode::FAILURE + + static ABORT: AtomicBool = AtomicBool::new(false); + + signal::set_sigint_handler(|| { + ABORT.store(true, Ordering::Release); + eprintln!("SIGINT"); + }); + let mut child = Command::new(args.program) + .args(args.args) + .spawn_with_trace() + .unwrap(); + + let mut last_syscall: Option<(u64, [u64; 6])> = None; + + while !ABORT.load(Ordering::Relaxed) { + let event = child.next_event().unwrap(); + match event { + TraceEvent::Exited => { + eprintln!("Exit"); + break; + } + TraceEvent::SyscallExit(ret) => match last_syscall { + Some((nr, args)) => { + let call = parse_syscall(nr, &args); + if let Some(call) = call { + let mut s = String::new(); + call.print(&mut s, &mut child); + eprintln!("{s} -> {ret}"); + } else { + eprintln!("syscall {nr} {args:?} -> {ret}"); + } + } + None => { + eprintln!("??? -> {ret}"); + } + }, + TraceEvent::SyscallEntry(nr, args) => { + last_syscall = Some((nr, args)); + } } + child.resume().ok(); } + ExitCode::SUCCESS }