From 03242a063531801b41d81d32c959f592da2b0264 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Thu, 27 Feb 2025 18:49:20 +0200 Subject: [PATCH] proc: implement process tracing --- kernel/libk/src/task/debug.rs | 26 +- kernel/libk/src/task/process.rs | 38 +- kernel/libk/src/task/thread.rs | 508 ++++++++++++------- kernel/src/arch/aarch64/exception.rs | 2 + kernel/src/arch/riscv64/exception.rs | 2 + kernel/src/arch/x86_64/syscall.rs | 5 + kernel/src/syscall/imp/sys_debug.rs | 41 +- kernel/src/syscall/imp/sys_process.rs | 44 +- lib/abi-serde/src/impls/base.rs | 61 ++- lib/abi-serde/src/lib.rs | 2 +- lib/abi/Cargo.toml | 3 + lib/abi/def/process.abi | 2 +- lib/abi/def/yggdrasil.abi | 10 +- lib/abi/src/arch/aarch64.rs | 10 + lib/abi/src/arch/i686.rs | 16 + lib/abi/src/arch/mod.rs | 16 +- lib/abi/src/arch/riscv64.rs | 13 + lib/abi/src/arch/x86_64.rs | 25 + lib/abi/src/debug.rs | 126 +++-- lib/abi/src/lib.rs | 1 + lib/abi/src/process/exit.rs | 42 ++ lib/abi/src/process/mod.rs | 38 +- lib/abi/src/process/options.rs | 6 +- lib/runtime/src/debug.rs | 14 +- lib/runtime/src/io/paths.rs | 4 +- lib/runtime/src/process/mod.rs | 16 +- lib/runtime/src/process/signal.rs | 2 +- lib/runtime/src/process/thread.rs | 24 +- lib/runtime/src/process/thread_local/i686.rs | 2 +- userspace/Cargo.lock | 10 + userspace/Cargo.toml | 4 +- userspace/lib/runtime/Cargo.toml | 1 + userspace/lib/runtime/src/lib.rs | 3 + userspace/strace/Cargo.toml | 12 + userspace/strace/src/format.rs | 383 ++++++++++++++ userspace/strace/src/main.rs | 36 ++ userspace/strace/src/tracer.rs | 233 +++++++++ userspace/sysutils/Cargo.toml | 1 + userspace/sysutils/src/tst.rs | 15 +- xtask/src/build/userspace.rs | 1 + 40 files changed, 1423 insertions(+), 375 deletions(-) create mode 100644 userspace/strace/Cargo.toml create mode 100644 userspace/strace/src/format.rs create mode 100644 userspace/strace/src/main.rs create mode 100644 userspace/strace/src/tracer.rs diff --git a/kernel/libk/src/task/debug.rs b/kernel/libk/src/task/debug.rs index a63779aa..3b5c31d9 100644 --- a/kernel/libk/src/task/debug.rs +++ b/kernel/libk/src/task/debug.rs @@ -1,5 +1,3 @@ -use yggdrasil_abi::{debug::DebugFrame, error::Error}; - pub struct ThreadDebugger {} impl ThreadDebugger { @@ -10,16 +8,16 @@ impl ThreadDebugger { // Self { channel } // } - pub fn send(&self, frame: &DebugFrame) -> Result<(), Error> { - let _ = frame; - todo!() - // let bytes = serde_json::to_vec(frame).unwrap(); - // self.channel - // .send_message( - // MessagePayload::Data(bytes.into_boxed_slice()), - // MessageDestination::AllExceptSelf, - // ) - // .unwrap(); - // Ok(()) - } + // pub fn send(&self, frame: &DebugFrame) -> Result<(), Error> { + // let _ = frame; + // todo!() + // // let bytes = serde_json::to_vec(frame).unwrap(); + // // self.channel + // // .send_message( + // // MessagePayload::Data(bytes.into_boxed_slice()), + // // MessageDestination::AllExceptSelf, + // // ) + // // .unwrap(); + // // Ok(()) + // } } diff --git a/kernel/libk/src/task/process.rs b/kernel/libk/src/task/process.rs index f187e0e0..f187855a 100644 --- a/kernel/libk/src/task/process.rs +++ b/kernel/libk/src/task/process.rs @@ -26,7 +26,7 @@ use yggdrasil_abi::{ error::Error, option::OptionValue, process::{ - options::ProcessOptionVariant, ExitCode, ProcessGroupId, ProcessId, Signal, + options::ProcessOptionVariant, ExitCode, ProcessGroupId, ProcessId, Signal, ThreadEvent, ThreadSpawnOptions, WaitFlags, }, }; @@ -73,8 +73,6 @@ pub struct ProcessInner { mutexes: BTreeMap>, space: Option>, image: Option, - - thread_exits: BTreeMap>, } /// Describes a process within the system @@ -304,6 +302,12 @@ impl Process { ProcessOptionVariant::SignalEntry => { options::SignalEntry::store(&self.signal_entry(), buffer) } + ProcessOptionVariant::MainThread => { + let id = self.inner.read().threads[0].id; + let abi = + unsafe { yggdrasil_abi::process::ThreadId::from_raw(id.as_user() as u32) }; + options::MainThread::store(&abi, buffer) + } } } @@ -322,6 +326,7 @@ impl Process { self.set_signal_entry(value); Ok(()) } + ProcessOptionVariant::MainThread => Err(Error::ReadOnly), } } @@ -583,18 +588,18 @@ impl Process { } } - pub async fn wait_for_thread(&self, thread: ThreadId) -> Result<(), Error> { - let exit = { - let inner = self.inner.read(); - inner - .thread_exits - .get(&thread) - .cloned() - .ok_or(Error::DoesNotExist)? - }; + pub async fn wait_for_thread(&self, thread: ThreadId) -> Result { + let thread = Thread::get(thread).ok_or(Error::ProcessNotFound)?; - exit.wait().await; - Ok(()) + // Check that the process is the thread's tracer or parent + if !thread.is_child_of(self.id) && !thread.is_tracee_of(self.id) { + // Cannot wait for events from unrelated threads + log::warn!("{:?} is not tracer nor parent of {:?}", self.id, thread.id); + return Err(Error::PermissionDenied); + } + let with_trace = thread.is_tracee_of(self.id); + + Ok(thread.events.wait(with_trace).await) } /// Terminates all threads with the exclusion of `except`. This may be useful when a thread @@ -610,7 +615,7 @@ impl Process { log::debug!("Terminate thread {}", thread.id); thread.terminate(); - thread.exit.wait().await; + thread.events.exit.wait().await; log::debug!("{} died", thread.id); } @@ -647,13 +652,10 @@ impl ProcessInner { mutexes: BTreeMap::new(), image, space: space.clone(), - - thread_exits: BTreeMap::new(), } } pub fn register_thread(&mut self, thread: Arc) { - self.thread_exits.insert(thread.id, thread.exit.clone()); self.threads.push(thread); } diff --git a/kernel/libk/src/task/thread.rs b/kernel/libk/src/task/thread.rs index a2a4c379..8baf1abe 100644 --- a/kernel/libk/src/task/thread.rs +++ b/kernel/libk/src/task/thread.rs @@ -1,7 +1,14 @@ -use core::{cell::Cell, mem::size_of, ops::Deref}; +use core::{ + cell::Cell, + future::poll_fn, + mem::size_of, + ops::Deref, + sync::atomic::{AtomicU32, AtomicU64, Ordering}, + task::Poll, +}; use alloc::{ - collections::{btree_map, BTreeMap}, + collections::BTreeMap, string::String, sync::{Arc, Weak}, }; @@ -9,35 +16,39 @@ use crossbeam_queue::SegQueue; use futures_util::task::ArcWake; use kernel_arch::{ task::{Scheduler, TaskContext, TaskFrame}, - Architecture, ArchitectureImpl, CpuImpl, + CpuImpl, }; use libk_mm::{process::ProcessAddressSpace, PageFaultKind}; use libk_util::{ event::BoolEvent, - sync::{spin_rwlock::IrqSafeRwLock, IrqGuard, IrqSafeSpinlock}, + ring::LossyRingQueue, + sync::{spin_rwlock::IrqSafeRwLock, IrqGuard, IrqSafeSpinlock, IrqSafeSpinlockGuard}, }; use yggdrasil_abi::{ arch::SavedFrame, - debug::DebugFrame, + debug::{self, DebugControl, TraceEvent, TraceEventPayload, TraceFlags}, error::Error, - option::OptionValue, + option::{OptionValue, RequestValue}, process::{ thread::{ThreadOptionVariant, ThreadSignalStack}, - ExitCode, ProcessId, Signal, SignalEntryData, + ExitCode, ProcessId, Signal, SignalEntryData, ThreadEvent, }, }; -use crate::task::{ - mem::ForeignPointer, - sched::CpuQueue, - types::{ThreadAffinity, ThreadId, ThreadState}, - TaskContextImpl, +use crate::{ + task::{ + mem::ForeignPointer, + sched::CpuQueue, + types::{ThreadAffinity, ThreadId, ThreadState}, + TaskContextImpl, + }, + time::monotonic_time, }; -use super::{debug::ThreadDebugger, process::Process}; +use super::process::Process; -type BreakpointType = ::BreakpointType; -const BREAKPOINT_VALUE: BreakpointType = ArchitectureImpl::BREAKPOINT_VALUE; +// type BreakpointType = ::BreakpointType; +// const BREAKPOINT_VALUE: BreakpointType = ArchitectureImpl::BREAKPOINT_VALUE; /// Provides details about how the thread is scheduled onto CPUs pub struct ThreadSchedulingInfo { @@ -50,18 +61,21 @@ pub struct ThreadSchedulingInfo { pub queue: Option<&'static CpuQueue>, } -pub struct ThreadDebuggingInfo { +pub struct ThreadTracingInfo { + pub tracer: Option, pub single_step: bool, - pub restore_breakpoint: Option, - pub debugger: Option, - pub saved_frame: Option, - pub breakpoints: BTreeMap, + pub state: SavedFrame, } pub struct ThreadInfo { pub signal_stack: ThreadSignalStack, } +pub struct ThreadEvents { + pub exit: BoolEvent, + pub trace: LossyRingQueue, +} + /// Describes a single thread within the system pub struct Thread { /// Unique thread ID @@ -74,19 +88,25 @@ pub struct Thread { pub context: Cell, process: Option, space: Option>, - debug: IrqSafeSpinlock, + debug: IrqSafeSpinlock, + trace_flags: AtomicU32, + syscall_trace_seq: AtomicU64, // inner: IrqSafeSpinlock, info: IrqSafeRwLock, signal_queue: SegQueue, - pub exit: Arc, + pub events: ThreadEvents, pub kill: BoolEvent, /// CPU scheduling affinity mask pub affinity: ThreadAffinity, } +// Prevents thread from getting scheduled during some critical section +#[allow(unused)] +pub struct SuspendGuard<'a>(IrqSafeSpinlockGuard<'a, ThreadSchedulingInfo>); + /// Wrapper which guarantees the thread referred to is the current one on the current CPU #[repr(C)] pub struct CurrentThread(Arc, IrqGuard); @@ -121,23 +141,18 @@ impl Thread { in_queue: false, queue: None, }), - // TODO lazy initialization for debugging info - debug: IrqSafeSpinlock::new(ThreadDebuggingInfo { - single_step: false, - restore_breakpoint: None, - debugger: None, - saved_frame: None, - breakpoints: BTreeMap::new(), - }), + debug: IrqSafeSpinlock::new(ThreadTracingInfo::default()), context: Cell::new(context), process, space, + trace_flags: AtomicU32::new(0), + syscall_trace_seq: AtomicU64::new(1), info: IrqSafeRwLock::new(ThreadInfo { signal_stack: ThreadSignalStack::default(), }), signal_queue: SegQueue::new(), - exit: Arc::new(BoolEvent::new()), + events: ThreadEvents::new(), kill: BoolEvent::new(), affinity: ThreadAffinity::any_cpu(), @@ -294,6 +309,11 @@ impl Thread { self.process.unwrap() } + /// Returns `true` if the thread is a child of given process + pub fn is_child_of(&self, pid: ProcessId) -> bool { + self.process.map_or(false, |p| p == pid) + } + /// Returns the thread's parent process reference pub fn try_get_process(&self) -> Option> { self.process.and_then(Process::get) @@ -315,78 +335,39 @@ impl Thread { /// Updates the thread's terminated status and wakes up any other threads waiting for it to /// exit pub fn set_terminated(&self) { - self.exit.signal_saturating(); + self.events.exit.signal_saturating(); + } + + pub fn trace_event(&self, suspend: bool, frame: &F, payload: TraceEventPayload) { + let mut debug = self.debug.lock(); + if debug.tracer.is_some() { + let timestamp = monotonic_time(); + debug.store_state(frame); + self.events.trace.write(TraceEvent { + suspend, + timestamp, + payload, + }); + } } // Signals /// Pushes a signal to the thread's signal queue - pub fn raise_signal(&self, signal: Signal) { - log::debug!("{}: raise signal {signal:?}", self.id); - self.signal_queue.push(signal); - self.enqueue(); - } + pub fn raise_signal(&self, signal: Signal) -> bool { + let is_traced = self.debug.lock().tracer.is_some(); - // Debugging - pub fn attach_debugger(&self, debugger: ThreadDebugger) { - // TODO kick out the previous debugger - let mut debug = self.debug.lock(); - - debug.saved_frame = None; - debug.single_step = true; - debug.debugger = Some(debugger); - - self.signal_queue.push(Signal::Debug); - - let frame = self - .process() - .map_image(|img| DebugFrame::Startup { - image_base: img.load_base, - ip_offset: img.ip_offset, - ip: img.entry, - }) - .unwrap(); - - debug.debugger.as_mut().unwrap().send(&frame).ok(); - } - - pub fn set_breakpoint(&self, address: usize) -> Result<(), Error> { - log::debug!( - "Set breakpoint in {} ({:?}) @ {:#x}", - self.id, - *self.name.read(), - address - ); - let mut debug = self.debug.lock(); - debug.set_breakpoint_inner(self.address_space(), address) - } - - pub fn read_memory(&self, address: usize, buffer: &mut [u8]) -> Result<(), Error> { - log::debug!( - "Read memory in {} ({:?}) @ {:#x}", - self.id, - *self.name.read(), - address - ); - - let space = self.address_space(); - - // TODO optimize this later - for (i, item) in buffer.iter_mut().enumerate() { - *item = unsafe { ((address + i) as *const u8).try_read_foreign_volatile(space) }?; + if is_traced && signal != Signal::Debug && signal != Signal::Killed { + log::info!("{}: caught traced signal {signal:?}", self.id); + // TODO + // self.trace_event(true, ); + true + } else { + log::info!("{}: raise signal {signal:?}", self.id); + self.signal_queue.push(signal); + self.enqueue(); + false } - - Ok(()) - } - - pub fn resume(&self, single_step: bool) { - { - let mut debug = self.debug.lock(); - - debug.single_step = single_step; - } - - self.enqueue(); } // Scheduling @@ -508,6 +489,108 @@ impl Thread { let strong = weak.upgrade()?; Some(CurrentThread(strong, guard)) } + + // Tracing/debugging + + /// Returns `true` if the thread is a tracee of given process + pub fn is_tracee_of(&self, pid: ProcessId) -> bool { + self.debug.lock().tracer.map_or(false, |p| p == pid) + } + + pub fn attach_trace(&self, tracer: ProcessId) -> Result<(), Error> { + let mut debug = self.debug.lock(); + if debug.tracer.is_some() { + log::warn!("{:?}: tracer already attached", self.id); + return Err(Error::AlreadyExists); + } + debug.tracer = Some(tracer); + debug.single_step = true; + log::info!("{:?}: tracer ({:?}) attached", self.id, tracer); + Ok(()) + } + + pub fn set_trace_flags(&self, flags: TraceFlags) { + let debug = self.debug.lock(); + let value = if debug.tracer.is_some() { + flags.bits() + } else { + 0 + }; + self.trace_flags.store(value, Ordering::Release); + } + + pub fn trace_flags(&self) -> TraceFlags { + TraceFlags::new(self.trace_flags.load(Ordering::Acquire)) + } + + pub fn resume(&self, single_step: bool) { + { + let mut debug = self.debug.lock(); + + debug.single_step = single_step; + } + + self.enqueue(); + } + + pub fn hold_suspended(&self) -> Result { + let sched = self.sched.lock(); + if sched.state == ThreadState::Running { + return Err(Error::InvalidOperation); + } + Ok(SuspendGuard(sched)) + } + + pub fn debug_control( + &self, + option: u32, + buffer: &mut [u8], + len: usize, + ) -> Result { + // Deserialize request + let input = &buffer[..len]; + let option = DebugControl::try_from(option)?; + + match option { + // TODO runtime attach + DebugControl::Attach => todo!(), + // TODO + DebugControl::Detach => todo!(), + DebugControl::Resume => { + let single_step = debug::Resume::load_request(input)?; + self.resume(single_step); + debug::Resume::store_response(&(), buffer) + } + DebugControl::SetTraceFlags => { + let flags = debug::SetTraceFlags::load_request(buffer)?; + self.set_trace_flags(flags); + debug::SetTraceFlags::store_response(&(), buffer) + } + DebugControl::GetTraceFlags => { + debug::GetTraceFlags::store_response(&self.trace_flags(), buffer) + } + DebugControl::GetRegisters => { + let _guard = self.hold_suspended()?; + let debug = self.debug.lock(); + debug::GetRegisters::store_response(&debug.state, buffer) + } + DebugControl::SetRegisters => { + let _guard = self.hold_suspended()?; + todo!() + } + DebugControl::ReadMemory => { + let address = debug::ReadMemory::load_request(buffer)?; + let pointer = core::ptr::with_exposed_provenance::(address); + let _guard = self.hold_suspended()?; + let space = self.address_space(); + let word = unsafe { pointer.try_read_foreign_volatile(space)? }; + debug::ReadMemory::store_response(&word, buffer) + } + DebugControl::WriteMemory => { + todo!(); + } + } + } } impl GlobalThreadList { @@ -552,12 +635,49 @@ impl CurrentThread { Arc::downgrade(&self.0) } + pub fn raise_signal(&self, signal: Signal) { + if self.0.raise_signal(signal) { + // Signal was intercepted and the thread will need to be suspended + log::info!("{}: suspending: signal intercepted by tracer", self.id); + self.suspend().ok(); + } + } + + // TODO point data + pub fn trace_point TraceEventPayload>( + &self, + filter: TraceFlags, + frame: &F, + producer: P, + ) { + if self.trace_flags().contains(filter) { + self.trace_event(true, frame, producer()); + self.suspend().ok(); + } + } + + pub fn trace_syscall_entry(&self, frame: &F) -> u64 { + let seq = self.syscall_trace_seq.fetch_add(1, Ordering::Relaxed); + self.trace_point(TraceFlags::SYSCALL_ENTRY, frame, || { + TraceEventPayload::SyscallEntry(seq) + }); + seq + } + + pub fn trace_syscall_exit(&self, frame: &F, seq: u64) { + self.trace_point(TraceFlags::SYSCALL_EXIT, frame, || { + TraceEventPayload::SyscallExit(seq) + }); + } + /// Terminate the current thread pub fn exit(&self, code: ExitCode) -> ! { // Can detach debugger now - let debug = self.debug.lock(); - if let Some(debugger) = debug.debugger.as_ref() { - debugger.send(&DebugFrame::Exited).ok(); + { + let mut debug = self.debug.lock(); + debug.tracer = None; + debug.single_step = false; + self.trace_flags.store(0, Ordering::Release); } if let Some(process) = self.try_get_process() { @@ -597,81 +717,59 @@ impl CurrentThread { pub fn handle_single_step(&self, frame: &mut F) -> bool { { - let mut debug = self.debug.lock(); - if debug.debugger.is_none() { - return false; + let debug = self.debug.lock(); + if !debug.single_step { + frame.set_single_step(false); + return true; } - let space = self.address_space(); - - if let Some(original) = debug.restore_breakpoint.take() { - let brk_range = original..original + size_of::(); - assert!(!brk_range.contains(&frame.user_ip())); - - log::debug!( - "Restore breakpoint, current_ip={:#x}, breakpoint_ip={:#x}", - frame.user_ip(), - original - ); - debug.set_breakpoint_inner(space, original).unwrap(); - } else { - // Single step cleared - if !debug.single_step { - log::debug!("Clear single step ({} {:?})", self.id, *self.name.read()); - frame.set_single_step(false); - return true; - } - } - - let frame = frame.store(); - debug.saved_frame = Some(frame.clone()); - // TODO handle cases of detached debugger - let debugger = debug.debugger.as_ref().unwrap(); - debugger.send(&DebugFrame::Step { frame }).ok(); } + self.trace_event(true, frame, TraceEventPayload::SingleStep); + match self.suspend() { Ok(_) | Err(Error::Interrupted) => true, - Err(err) => panic!("TODO: handle error in debug suspend: {:?}", err), + Err(err) => panic!("TODO: handle error in single-step suspend: {err:?}"), } } - pub fn handle_breakpoint(&self, frame: &mut F) -> bool { - let mut debug = self.debug.lock(); - let ip = frame.user_ip(); + pub fn handle_breakpoint(&self, _frame: &mut F) -> bool { + false + // let mut debug = self.debug.lock(); + // let ip = frame.user_ip(); - if let Some(value) = debug.breakpoints.remove(&ip) { - let space = self.address_space(); + // if let Some(value) = debug.breakpoints.remove(&ip) { + // let space = self.address_space(); - // Restore original code - let pointer = ip as *mut BreakpointType; - unsafe { pointer.write_foreign_volatile(space, value) }; + // // Restore original code + // let pointer = ip as *mut BreakpointType; + // unsafe { pointer.write_foreign_volatile(space, value) }; - log::debug!( - "Thread {} ({:?}) hit a breakpoint @ {:#x}, step={}", - self.id, - *self.name.read(), - ip, - debug.single_step - ); + // log::debug!( + // "Thread {} ({:?}) hit a breakpoint @ {:#x}, step={}", + // self.id, + // *self.name.read(), + // ip, + // debug.single_step + // ); - // TODO handle cases when no debugger is attached (clear breakpoint and resume) - frame.set_single_step(true); + // // TODO handle cases when no debugger is attached (clear breakpoint and resume) + // frame.set_single_step(true); - let frame = frame.store(); - debug.restore_breakpoint = Some(ip); - debug.saved_frame = Some(frame.clone()); + // let frame = frame.store(); + // debug.restore_breakpoint = Some(ip); + // debug.saved_frame = Some(frame.clone()); - let debugger = debug.debugger.as_ref().unwrap(); - debugger.send(&DebugFrame::HitBreakpoint { frame }).ok(); + // let debugger = debug.debugger.as_ref().unwrap(); + // debugger.send(&DebugFrame::HitBreakpoint { frame }).ok(); - drop(debug); + // drop(debug); - self.suspend().unwrap(); + // self.suspend().unwrap(); - true - } else { - false - } + // true + // } else { + // false + // } } pub fn handle_page_fault(&self, address: usize, kind: PageFaultKind) -> Result<(), Error> { @@ -697,19 +795,8 @@ impl CurrentThread { ); if signal == Signal::Debug { - frame.set_single_step(true); - - let frame = frame.store(); - let mut debug = self.debug.lock(); - - debug.single_step = true; - debug.saved_frame = Some(frame.clone()); - - let debugger = debug.debugger.as_ref().unwrap(); - - debugger.send(&DebugFrame::Step { frame }).ok(); - - return; + // Debugger attached in runtime + todo!() } let ip = self.process().signal_entry(); @@ -766,27 +853,70 @@ impl Deref for CurrentThread { } } -impl ThreadDebuggingInfo { - fn set_breakpoint_inner( - &mut self, - space: &ProcessAddressSpace, - address: usize, - ) -> Result<(), Error> { - match self.breakpoints.entry(address) { - btree_map::Entry::Vacant(vacant) => { - let pointer = address as *mut BreakpointType; +// impl ThreadTracingInfo { +// fn set_breakpoint_inner( +// &mut self, +// space: &ProcessAddressSpace, +// address: usize, +// ) -> Result<(), Error> { +// match self.breakpoints.entry(address) { +// btree_map::Entry::Vacant(vacant) => { +// let pointer = address as *mut BreakpointType; +// +// // Read old code from the address space at that location +// let original = +// unsafe { (pointer as *const BreakpointType).try_read_foreign_volatile(space) }?; +// +// unsafe { pointer.write_foreign_volatile(space, BREAKPOINT_VALUE) }; +// +// vacant.insert(original); +// Ok(()) +// } +// // No need, breakpoint already present +// btree_map::Entry::Occupied(_) => Err(Error::AlreadyExists), +// } +// } +// } - // Read old code from the address space at that location - let original = - unsafe { (pointer as *const BreakpointType).try_read_foreign_volatile(space) }?; +impl ThreadTracingInfo { + pub fn store_state(&mut self, frame: &F) { + self.state = frame.store(); + } +} - unsafe { pointer.write_foreign_volatile(space, BREAKPOINT_VALUE) }; - - vacant.insert(original); - Ok(()) - } - // No need, breakpoint already present - btree_map::Entry::Occupied(_) => Err(Error::AlreadyExists), +impl Default for ThreadTracingInfo { + fn default() -> Self { + Self { + single_step: false, + tracer: None, + state: Default::default(), } } } + +impl ThreadEvents { + pub fn new() -> Self { + Self { + exit: BoolEvent::new(), + trace: LossyRingQueue::with_capacity(8), + } + } + + pub async fn wait(&self, with_trace: bool) -> ThreadEvent { + poll_fn(|cx| { + if with_trace { + if let Poll::Ready(mut lock) = self.trace.poll_lock(cx) { + let event = unsafe { lock.read_single_unchecked() }; + return Poll::Ready(ThreadEvent::Trace(event)); + } + } + + if self.exit.poll(cx).is_ready() { + Poll::Ready(ThreadEvent::Exited) + } else { + Poll::Pending + } + }) + .await + } +} diff --git a/kernel/src/arch/aarch64/exception.rs b/kernel/src/arch/aarch64/exception.rs index f18e0bc2..f641cc82 100644 --- a/kernel/src/arch/aarch64/exception.rs +++ b/kernel/src/arch/aarch64/exception.rs @@ -227,6 +227,7 @@ fn el0_sync_inner(frame: &mut ExceptionFrame) { let dump = match ec { // SVC in AArch64 0b010101 => { + let seq = Thread::current().trace_syscall_entry(frame); let func = frame.r[8]; if func == usize::from(SyscallFunction::ExitSignal) { unsafe { @@ -238,6 +239,7 @@ fn el0_sync_inner(frame: &mut ExceptionFrame) { let args = &frame.r[0..6]; let result = raw_syscall_handler(func, args) as _; frame.r[0] = result; + Thread::current().trace_syscall_exit(frame, seq); false } // Software Step from lower Exception Level diff --git a/kernel/src/arch/riscv64/exception.rs b/kernel/src/arch/riscv64/exception.rs index 829e64f6..b6324e83 100644 --- a/kernel/src/arch/riscv64/exception.rs +++ b/kernel/src/arch/riscv64/exception.rs @@ -95,6 +95,7 @@ unsafe fn umode_exception_handler(frame: &mut TrapFrame) { } } Some(Cause::EcallUmode) => { + let seq = Thread::current().trace_syscall_entry(frame); let func = frame.an[0]; if func == usize::from(SyscallFunction::ExitSignal) { unsafe { @@ -108,6 +109,7 @@ unsafe fn umode_exception_handler(frame: &mut TrapFrame) { mem::tlb_flush_full(); frame.an[0] = result; frame.sepc += 4; + Thread::current().trace_syscall_exit(frame, seq); (false, false) } _ => { diff --git a/kernel/src/arch/x86_64/syscall.rs b/kernel/src/arch/x86_64/syscall.rs index bd798987..26f34569 100644 --- a/kernel/src/arch/x86_64/syscall.rs +++ b/kernel/src/arch/x86_64/syscall.rs @@ -11,6 +11,8 @@ use tock_registers::interfaces::{ReadWriteable, Writeable}; use crate::syscall::{self, raw_syscall_handler}; fn syscall_inner(frame: &mut SyscallFrame) { + let trace_seq = Thread::current().trace_syscall_entry(frame); + if frame.rax == usize::from(SyscallFunction::ExitSignal) { unsafe { syscall::handle_signal_exit(frame); @@ -31,6 +33,9 @@ fn syscall_inner(frame: &mut SyscallFrame) { let result = raw_syscall_handler(frame.rax, &frame.args); frame.rax = result as _; + + Thread::current().trace_syscall_exit(frame, trace_seq); + if code == usize::from(SyscallFunction::CreateDirectory) { log::debug!("frame out = {frame:#x?}"); } diff --git a/kernel/src/syscall/imp/sys_debug.rs b/kernel/src/syscall/imp/sys_debug.rs index 00a48830..161f5635 100644 --- a/kernel/src/syscall/imp/sys_debug.rs +++ b/kernel/src/syscall/imp/sys_debug.rs @@ -1,11 +1,7 @@ -use abi::{ - debug::{DebugOperation, TraceLevel}, - error::Error, - process::ProcessId, -}; +use abi::{debug::TraceLevel, error::Error}; use libk::{ debug, - task::{process::Process, thread::Thread}, + task::{thread::Thread, ThreadId}, }; pub(crate) fn debug_trace(level: TraceLevel, message: &str) { @@ -21,24 +17,21 @@ pub(crate) fn debug_trace(level: TraceLevel, message: &str) { debug::program_trace(&process, &thread, level, message); } -pub(crate) fn debug_control(pid: ProcessId, op: &mut DebugOperation) -> Result<(), Error> { - let target = Process::get(pid).ok_or(Error::DoesNotExist)?; - let target_thread = target.as_single_thread().unwrap(); +pub(crate) fn debug_control( + tid: u32, + option: u32, + buffer: &mut [u8], + len: usize, +) -> Result { + let thread = Thread::current(); + let process = thread.process(); + let tid = ThreadId::User(tid as _); + let tracee = Thread::get(tid).ok_or(Error::ProcessNotFound)?; - match op { - &mut DebugOperation::Continue(single_step) => { - // TODO check if it's paused currently - target_thread.resume(single_step); - Ok(()) - } - &mut DebugOperation::SetBreakpoint(address) => target_thread.set_breakpoint(address), - DebugOperation::ReadMemory { address, buffer } => { - target_thread.read_memory(*address, buffer) - } - DebugOperation::WriteMemory { .. } => todo!(), - _ => { - log::warn!("Unsupported debug operation: {:?}", op); - Err(Error::InvalidOperation) - } + if !tracee.is_tracee_of(process.id) { + log::warn!("{}: {} is not our tracee", process.id, tracee.id); + return Err(Error::PermissionDenied); } + + tracee.debug_control(option, buffer, len) } diff --git a/kernel/src/syscall/imp/sys_process.rs b/kernel/src/syscall/imp/sys_process.rs index de11c80a..8ea98fb0 100644 --- a/kernel/src/syscall/imp/sys_process.rs +++ b/kernel/src/syscall/imp/sys_process.rs @@ -6,7 +6,7 @@ use abi::{ process::{ options::ProcessOptionVariant, thread::ThreadOptionVariant, ExitCode, MutexOperation, ProcessGroupId, ProcessId, ProcessWait, Signal, SpawnFlags, SpawnOption, SpawnOptions, - ThreadSpawnOptions, WaitFlags, + ThreadEvent, ThreadSpawnOptions, WaitFlags, }, }; use alloc::sync::Arc; @@ -91,6 +91,10 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result) -> Result) -> Result ! { thread.exit(ExitCode::SUCCESS) } -pub(crate) fn wait_thread(id: u32) -> Result<(), Error> { +pub(crate) fn wait_thread(id: u32, event: &mut MaybeUninit) -> Result<(), Error> { let tid = ThreadId::User(id as u64); let this_thread = Thread::current(); let process = this_thread.process(); - block!(process.wait_for_thread(tid).await)? + let ev = block!(process.wait_for_thread(tid).await)??; + event.write(ev); + Ok(()) } pub(crate) fn get_thread_option(option: u32, buffer: &mut [u8]) -> Result { @@ -335,16 +345,36 @@ pub(crate) fn set_thread_option(option: u32, buffer: &[u8]) -> Result<(), Error> thread.set_option(option, buffer) } -pub(crate) fn get_process_option(option: u32, buffer: &mut [u8]) -> Result { +pub(crate) fn get_process_option( + pid: Option, + option: u32, + buffer: &mut [u8], +) -> Result { let option = ProcessOptionVariant::try_from(option)?; let thread = Thread::current(); let process = thread.process(); + let process = if let Some(pid) = pid { + // TODO check that the process is actually a child of the current one + Process::get(pid).ok_or(Error::DoesNotExist)? + } else { + process + }; process.get_option(option, buffer) } -pub(crate) fn set_process_option(option: u32, buffer: &[u8]) -> Result<(), Error> { +pub(crate) fn set_process_option( + pid: Option, + option: u32, + buffer: &[u8], +) -> Result<(), Error> { let option = ProcessOptionVariant::try_from(option)?; let thread = Thread::current(); let process = thread.process(); + let process = if let Some(pid) = pid { + // TODO check that the process is actually a child of the current one + Process::get(pid).ok_or(Error::DoesNotExist)? + } else { + process + }; process.set_option(option, buffer) } diff --git a/lib/abi-serde/src/impls/base.rs b/lib/abi-serde/src/impls/base.rs index b5bc4507..0fa559f5 100644 --- a/lib/abi-serde/src/impls/base.rs +++ b/lib/abi-serde/src/impls/base.rs @@ -1,3 +1,5 @@ +use core::mem::MaybeUninit; + use crate::{ des::{DeserializeError, Deserializer}, ser::Serializer, @@ -51,6 +53,25 @@ impl<'de> Deserialize<'de> for () { } } +impl Serialize for [T; N] { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> { + for i in 0..N { + self[i].serialize(serializer)?; + } + Ok(()) + } +} + +impl<'de, const N: usize, T: Deserialize<'de>> Deserialize<'de> for [T; N] { + fn deserialize>(deserializer: &mut D) -> Result { + let mut array = [const { MaybeUninit::uninit() }; N]; + for i in 0..N { + array[i].write(Deserialize::deserialize(deserializer)?); + } + Ok(unsafe { MaybeUninit::array_assume_init(array) }) + } +} + impl Serialize for &[u8] { fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> { serializer.write_bytes(self) @@ -75,26 +96,26 @@ impl<'de> Deserialize<'de> for &'de str { } } -impl Serialize for [u8; N] -where - [u8; N]: Sized, -{ - fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> { - serializer.write_bytes(self) - } -} - -impl<'de, const N: usize> Deserialize<'de> for [u8; N] -where - [u8; N]: Sized, -{ - fn deserialize>(deserializer: &mut D) -> Result { - deserializer - .read_bytes()? - .try_into() - .map_err(|_| D::Error::INVALID_ARRAY_LEN) - } -} +// impl Serialize for [u8; N] +// where +// [u8; N]: Sized, +// { +// fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> { +// serializer.write_bytes(self) +// } +// } +// +// impl<'de, const N: usize> Deserialize<'de> for [u8; N] +// where +// [u8; N]: Sized, +// { +// fn deserialize>(deserializer: &mut D) -> Result { +// deserializer +// .read_bytes()? +// .try_into() +// .map_err(|_| D::Error::INVALID_ARRAY_LEN) +// } +// } impl Serialize for Option { fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> { diff --git a/lib/abi-serde/src/lib.rs b/lib/abi-serde/src/lib.rs index b8b65f77..f7e29490 100644 --- a/lib/abi-serde/src/lib.rs +++ b/lib/abi-serde/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(decl_macro, ip_from)] +#![feature(decl_macro, ip_from, maybe_uninit_array_assume_init)] #![no_std] #[cfg(not(feature = "rustc-dep-of-std"))] diff --git a/lib/abi/Cargo.toml b/lib/abi/Cargo.toml index 54250a40..2fa3131a 100644 --- a/lib/abi/Cargo.toml +++ b/lib/abi/Cargo.toml @@ -22,6 +22,9 @@ abi-lib = { path = "../../lib/abi-lib" } abi-generator = { path = "../../tool/abi-generator" } prettyplease = "0.2.15" +[lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = ['cfg(rust_analyzer)'] } + [features] default = [] alloc = [] diff --git a/lib/abi/def/process.abi b/lib/abi/def/process.abi index 7b9aa9ff..1d06a54c 100644 --- a/lib/abi/def/process.abi +++ b/lib/abi/def/process.abi @@ -17,7 +17,7 @@ enum Signal(u32) { Terminated = 7, } -newtype ProcessId(u32); +newtype ProcessId(u31); newtype ProcessGroupId(u32); newtype ThreadId(u32); diff --git a/lib/abi/def/yggdrasil.abi b/lib/abi/def/yggdrasil.abi index a1b475e8..1a33d707 100644 --- a/lib/abi/def/yggdrasil.abi +++ b/lib/abi/def/yggdrasil.abi @@ -15,6 +15,8 @@ extern { type MutexOperation = yggdrasil_abi::process::MutexOperation; #[thin] type ExitCode = yggdrasil_abi::process::ExitCode; + #[thin] + type ThreadEvent = yggdrasil_abi::process::ThreadEvent; type ProcessWait = yggdrasil_abi::process::ProcessWait; type FileMetadataUpdate = yggdrasil_abi::io::FileMetadataUpdate; @@ -102,11 +104,11 @@ syscall get_tid() -> u32; // TODO use ThreadId syscall spawn_thread(options: &ThreadSpawnOptions) -> Result; syscall exit_thread() -> !; -syscall wait_thread(tid: u32) -> Result<()>; +syscall wait_thread(tid: u32, event: &mut MaybeUninit) -> Result<()>; syscall get_thread_option(option: u32, value: &mut [u8]) -> Result; syscall set_thread_option(option: u32, value: &[u8]) -> Result<()>; -syscall get_process_option(option: u32, value: &mut [u8]) -> Result; -syscall set_process_option(option: u32, value: &[u8]) -> Result<()>; +syscall get_process_option(process: Option, option: u32, value: &mut [u8]) -> Result; +syscall set_process_option(process: Option, option: u32, value: &[u8]) -> Result<()>; syscall nanosleep(duration: &Duration, remaining: &mut MaybeUninit) -> Result<()>; @@ -182,4 +184,4 @@ syscall execve(options: &ExecveOptions<'_>) -> Result<()>; // Debugging syscall debug_trace(level: TraceLevel, message: &str); -syscall debug_control(pid: ProcessId, op: &mut DebugOperation<'_>) -> Result<()>; +syscall debug_control(tid: u32, option: u32, buffer: &mut [u8], len: usize) -> Result; diff --git a/lib/abi/src/arch/aarch64.rs b/lib/abi/src/arch/aarch64.rs index 134e75d2..8b096d9b 100644 --- a/lib/abi/src/arch/aarch64.rs +++ b/lib/abi/src/arch/aarch64.rs @@ -1,5 +1,7 @@ #![allow(missing_docs)] +use abi_serde::impl_struct_serde; + use super::FrameOps; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -22,3 +24,11 @@ impl FrameOps for SavedFrame { self.elr_el1 as _ } } + +impl_struct_serde!(SavedFrame: [ + gp_regs, + spsr_el1, + elr_el1, + sp_el0, + mdscr_el1, +]); diff --git a/lib/abi/src/arch/i686.rs b/lib/abi/src/arch/i686.rs index 1857a20e..b6208994 100644 --- a/lib/abi/src/arch/i686.rs +++ b/lib/abi/src/arch/i686.rs @@ -1,5 +1,7 @@ #![allow(missing_docs)] +use abi_serde::impl_struct_serde; + use super::FrameOps; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -28,3 +30,17 @@ impl FrameOps for SavedFrame { self.user_ip as _ } } + +impl_struct_serde!(SavedFrame: [ + eax, + ecx, + edx, + ebx, + ebp, + esi, + edi, + + user_ip, + user_sp, + eflags, +]); diff --git a/lib/abi/src/arch/mod.rs b/lib/abi/src/arch/mod.rs index bd82f953..ae094814 100644 --- a/lib/abi/src/arch/mod.rs +++ b/lib/abi/src/arch/mod.rs @@ -2,24 +2,24 @@ #![allow(missing_docs)] -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", rust_analyzer))] pub(crate) mod aarch64; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", rust_analyzer))] use aarch64 as arch_impl; -#[cfg(target_arch = "x86_64")] +#[cfg(any(target_arch = "x86_64", rust_analyzer))] pub(crate) mod x86_64; -#[cfg(target_arch = "x86_64")] +#[cfg(any(target_arch = "x86_64", rust_analyzer))] use x86_64 as arch_impl; -#[cfg(target_arch = "x86")] +#[cfg(any(target_arch = "x86", rust_analyzer))] pub(crate) mod i686; -#[cfg(target_arch = "x86")] +#[cfg(any(target_arch = "x86", rust_analyzer))] use i686 as arch_impl; -#[cfg(target_arch = "riscv64")] +#[cfg(any(target_arch = "riscv64", rust_analyzer))] pub(crate) mod riscv64; -#[cfg(target_arch = "riscv64")] +#[cfg(any(target_arch = "riscv64", rust_analyzer))] use riscv64 as arch_impl; pub use arch_impl::SavedFrame; diff --git a/lib/abi/src/arch/riscv64.rs b/lib/abi/src/arch/riscv64.rs index bcce7a67..1c41d1ed 100644 --- a/lib/abi/src/arch/riscv64.rs +++ b/lib/abi/src/arch/riscv64.rs @@ -1,5 +1,7 @@ #![allow(missing_docs)] +use abi_serde::impl_struct_serde; + use super::FrameOps; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -28,3 +30,14 @@ impl FrameOps for SavedFrame { todo!() } } + +impl_struct_serde!(SavedFrame: [ + ra, + gp, + tn, + sn, + an, + sp, + ip, + tp, +]); diff --git a/lib/abi/src/arch/x86_64.rs b/lib/abi/src/arch/x86_64.rs index 0297f738..faac82b4 100644 --- a/lib/abi/src/arch/x86_64.rs +++ b/lib/abi/src/arch/x86_64.rs @@ -1,5 +1,7 @@ #![allow(missing_docs)] +use abi_serde::impl_struct_serde; + use super::FrameOps; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -37,3 +39,26 @@ impl FrameOps for SavedFrame { self.user_ip as _ } } + +impl_struct_serde!(SavedFrame: [ + rax, + rcx, + rdx, + rbx, + rsi, + rdi, + rbp, + + r8, + r9, + r10, + r11, + r12, + r13, + r14, + r15, + + user_ip, + user_sp, + rflags +]); diff --git a/lib/abi/src/debug.rs b/lib/abi/src/debug.rs index e76fa1bb..0f8f3819 100644 --- a/lib/abi/src/debug.rs +++ b/lib/abi/src/debug.rs @@ -1,63 +1,81 @@ //! Debugging functionality -use crate::{arch::SavedFrame, io::RawFd}; +#![allow(missing_docs)] + +use abi_serde::{impl_newtype_serde, impl_struct_serde}; pub use crate::generated::TraceLevel; +use crate::{arch::SavedFrame, bitflags, time::SystemTime}; -/// Describes a debugger operation -#[derive(Debug)] -pub enum DebugOperation<'a> { - /// Attach to a given process - Attach(RawFd), - /// Detach from the current process - Detach, +// TODO SetSignal to send signals from the tracer to the tracee +request_group!( + #[doc = "Debugging/tracing operation on a thread"] + pub enum DebugControl<'de> { + /// Attach to a tracee, stopping it + 0x1000: Attach((), ()), + /// Detach the tracer from the thread, resuming it + 0x1001: Detach((), ()), + /// Resume stopped tracee, optionally setting it into a single-step mode + 0x1002: Resume(bool, ()), + /// Returns the current state of tracee's registers. + /// + /// Requires stopped tracee. + 0x1003: GetRegisters((), SavedFrame), + /// Updates the current state of tracee's registers. + /// + /// Requires stopped tracee. + 0x1004: SetRegisters(SavedFrame, ()), + /// Reads an `usize` from tracee's memory space. + /// + /// Requires stopped tracee. + 0x1005: ReadMemory(usize, usize), + /// Writes an `usize` to tracee's memory space. + /// + /// Requires stopped tracee. + 0x1006: WriteMemory(WriteTraceeMemory, ()), + /// Updates trace flags for the tracee. + 0x1007: SetTraceFlags(TraceFlags, ()), + /// Returns current trace flags of the tracee. + 0x1008: GetTraceFlags((), TraceFlags), + } +); - /// Send an interrupt to the debugee - Interrupt, - /// Continue the debugee, `Continue(true)` represents a "single step" - Continue(bool), - /// Set a breakpoint at a given address - SetBreakpoint(usize), +/// A request to write into tracee's memory space +#[derive(Debug, Clone, Copy)] +pub struct WriteTraceeMemory { + /// Address to write to, must be `usize`-aligned + pub address: usize, + /// Value to write + pub value: usize, +} +impl_struct_serde!(WriteTraceeMemory: [ + address, + value +]); - /// Read data from the debugee memory - ReadMemory { - /// Address to read the data from - address: usize, - /// Buffer to place the result into - buffer: &'a mut [u8], - }, - /// Write data to the debugee memory - WriteMemory { - /// Address to write the data to - address: usize, - /// Buffer to read the data from - buffer: &'a [u8], - }, +bitflags! { + /// Defines which trace events to record + pub struct TraceFlags: u32 { + /// Trace system call entries + const SYSCALL_ENTRY: bit 0; + /// Trace system call exits + const SYSCALL_EXIT: bit 1; + } } -/// Represents a debug event -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug)] -pub enum DebugFrame { - /// The debugee has started - Startup { - /// The address at which the process image is loaded - image_base: usize, - /// The address by which the instruction pointer is offset (when ASLR is in effect) - ip_offset: usize, - /// Current instruction pointer - ip: usize, - }, - /// The debugee has completed a single instruction step - Step { - /// Current process state - frame: SavedFrame, - }, - /// The debugee has hit a breakpoint - HitBreakpoint { - /// Current process state - frame: SavedFrame, - }, - // TODO exit status - /// The debugee has exited - Exited, +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub enum TraceEventPayload { + SingleStep, + SyscallEntry(u64), + SyscallExit(u64), } + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct TraceEvent { + pub suspend: bool, + pub timestamp: SystemTime, + pub payload: TraceEventPayload, +} + +impl_newtype_serde!(TraceFlags); diff --git a/lib/abi/src/lib.rs b/lib/abi/src/lib.rs index 345defbd..310aaada 100644 --- a/lib/abi/src/lib.rs +++ b/lib/abi/src/lib.rs @@ -20,6 +20,7 @@ extern crate rustc_std_alloc as alloc; pub(crate) mod macros; pub mod util; +pub use abi_lib; pub use abi_serde; mod generated { diff --git a/lib/abi/src/process/exit.rs b/lib/abi/src/process/exit.rs index d3f15c37..7ada815d 100644 --- a/lib/abi/src/process/exit.rs +++ b/lib/abi/src/process/exit.rs @@ -1,6 +1,13 @@ use core::num::NonZeroI32; use abi_lib::SyscallRegister; +use abi_serde::{ + des::{DeserializeError, Deserializer}, + ser::Serializer, + Deserialize, Serialize, +}; + +use crate::debug::TraceEvent; use super::Signal; @@ -14,6 +21,16 @@ pub enum ExitCode { BySignal(Result), } +/// Event signalled by a thread +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub enum ThreadEvent { + /// Thread has exited + Exited, + /// Thread was interrupted by a trace event + Trace(TraceEvent), +} + impl ExitCode { /// Returned when a process has exited successfully pub const SUCCESS: Self = Self::Exited(0); @@ -70,3 +87,28 @@ impl SyscallRegister for ExitCode { Self::from(value as i32) } } + +impl Serialize for ExitCode { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> { + match self { + &Self::Exited(status) => { + serializer.write_u8(0)?; + status.serialize(serializer) + } + &Self::BySignal(signal) => { + serializer.write_u8(1)?; + signal.serialize(serializer) + } + } + } +} + +impl<'de> Deserialize<'de> for ExitCode { + fn deserialize>(deserializer: &mut D) -> Result { + match deserializer.read_u8()? { + 0 => Deserialize::deserialize(deserializer).map(Self::Exited), + 1 => Deserialize::deserialize(deserializer).map(Self::Exited), + _ => Err(D::Error::INVALID_ENUM_VARIANT), + } + } +} diff --git a/lib/abi/src/process/mod.rs b/lib/abi/src/process/mod.rs index 67332094..f04c1de8 100644 --- a/lib/abi/src/process/mod.rs +++ b/lib/abi/src/process/mod.rs @@ -12,7 +12,12 @@ pub use crate::generated::{ ExecveOptions, ProcessGroupId, ProcessId, Signal, SignalEntryData, SpawnFlags, SpawnOptions, ThreadId, ThreadSpawnOptions, WaitFlags, }; -pub use exit::ExitCode; +use abi_serde::{ + des::{DeserializeError, Deserializer}, + ser::Serializer, + Deserialize, Serialize, +}; +pub use exit::{ExitCode, ThreadEvent}; // TODO this is ugly pub mod auxv { @@ -64,9 +69,10 @@ pub enum SpawnOption { SetProcessGroup(ProcessGroupId), /// Gain terminal control for the given FD GainTerminal(RawFd), - /// Attach debugging to a channel in parent's I/O context. The process will start in - /// single-stepping mode - AttachDebug(RawFd), + /// Attach tracing to the spawned process. This is equivalent to issuing a `debug_control` with + /// `DebugControl::Attach` to the newly spawned process. + /// The newly spawned process will have its main thread paused. + AttachTrace, } /// Describes a single mutex operation @@ -227,3 +233,27 @@ impl Signal { abi_serde::impl_newtype_serde!(ProcessGroupId); abi_serde::impl_newtype_serde!(ProcessId); + +impl Serialize for Signal { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> { + serializer.write_u32(self.into_raw()) + } +} + +impl<'de> Deserialize<'de> for Signal { + fn deserialize>(deserializer: &mut D) -> Result { + Self::try_from(deserializer.read_u32()?).map_err(|_| D::Error::INVALID_ENUM_VARIANT) + } +} + +impl Serialize for ThreadId { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> { + self.into_raw().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for ThreadId { + fn deserialize>(deserializer: &mut D) -> Result { + Deserialize::deserialize(deserializer).map(|r| unsafe { Self::from_raw(r) }) + } +} diff --git a/lib/abi/src/process/options.rs b/lib/abi/src/process/options.rs index b336d91a..6a30c763 100644 --- a/lib/abi/src/process/options.rs +++ b/lib/abi/src/process/options.rs @@ -1,5 +1,7 @@ //! Process option definitions +use super::ThreadId; + option_group!( #[doc = "Process options"] pub enum ProcessOptionVariant<'a> { @@ -8,6 +10,8 @@ option_group!( #[doc = "Current working directory"] 0x1001: Directory(&'a str), #[doc = "Process name"] - 0x1002: Name(&'a str) + 0x1002: Name(&'a str), + #[doc = "Main thread ID"] + 0x1003: MainThread # 4 (ThreadId), } ); diff --git a/lib/runtime/src/debug.rs b/lib/runtime/src/debug.rs index 62abb585..7256e378 100644 --- a/lib/runtime/src/debug.rs +++ b/lib/runtime/src/debug.rs @@ -2,7 +2,8 @@ use core::fmt; -pub use abi::debug::{DebugFrame, DebugOperation, TraceLevel}; +pub use abi::debug::TraceLevel; +use abi::{error::Error, option::RequestValue}; /// Prints a debug message using DebugTrace syscall #[macro_export] @@ -54,3 +55,14 @@ pub fn _debug_trace(level: TraceLevel, a: core::fmt::Arguments<'_>) { } } } + +/// Perform a debugging operation on a thread +pub fn debug_control<'de, T: RequestValue<'de>>( + tid: u32, + buffer: &'de mut [u8], + request: &T::Request, +) -> Result { + let len = T::store_request(request, buffer)?; + let len = unsafe { crate::sys::debug_control(tid, T::VARIANT.into(), buffer, len) }?; + T::load_response(&buffer[..len]) +} diff --git a/lib/runtime/src/io/paths.rs b/lib/runtime/src/io/paths.rs index ff693642..d16c8591 100644 --- a/lib/runtime/src/io/paths.rs +++ b/lib/runtime/src/io/paths.rs @@ -13,7 +13,7 @@ pub fn home_directory T>(_mapper: F) -> Result { pub fn current_directory T>(mapper: F) -> Result { let mut buffer = [0; 512]; - let path = process::get_process_option::(&mut buffer)?; + let path = process::get_process_option::(None, &mut buffer)?; Ok(mapper(path)) } @@ -23,7 +23,7 @@ pub fn current_directory_string() -> Result { pub fn set_current_directory(path: &str) -> Result<(), Error> { let mut buffer = [0; 512]; - process::set_process_option_with::(&mut buffer, &path) + process::set_process_option_with::(None, &mut buffer, &path) } pub fn make_temp_directory(_template: &mut [u8]) -> Result<(), Error> { diff --git a/lib/runtime/src/process/mod.rs b/lib/runtime/src/process/mod.rs index b33e4659..5ed78bcf 100644 --- a/lib/runtime/src/process/mod.rs +++ b/lib/runtime/src/process/mod.rs @@ -5,7 +5,8 @@ use core::{mem::MaybeUninit, time::Duration}; pub use abi::process::{ auxv, AuxValue, AuxValueIter, ExecveOptions, ExitCode, MutexOperation, ProcessGroupId, ProcessId, ProcessInfoElement, ProcessWait, ProgramArgumentInner, Signal, SignalEntryData, - SpawnFlags, SpawnOption, SpawnOptions, StringArgIter, ThreadId, ThreadSpawnOptions, WaitFlags, + SpawnFlags, SpawnOption, SpawnOptions, StringArgIter, ThreadEvent, ThreadId, + ThreadSpawnOptions, WaitFlags, }; use abi::{ error::Error, @@ -46,16 +47,17 @@ pub fn uninterruptible_sleep(mut duration: Duration) { } /// Helper macro for [get_process_option]. -pub macro get_process_option($variant_ty:ty) {{ +pub macro get_process_option($process:expr, $variant_ty:ty) {{ let mut buffer = [0; <$variant_ty as $crate::io::OptionSizeHint>::SIZE_HINT]; - $crate::process::get_process_option::<$variant_ty>(&mut buffer) + $crate::process::get_process_option::<$variant_ty>($process, &mut buffer) }} /// Retrieves a process option value. pub fn get_process_option<'de, T: OptionValue<'de>>( + process: Option, buffer: &'de mut [u8], ) -> Result { - let len = unsafe { crate::sys::get_process_option(T::VARIANT.into(), buffer) }?; + let len = unsafe { crate::sys::get_process_option(process, T::VARIANT.into(), buffer) }?; T::load(&buffer[..len]) } @@ -75,22 +77,24 @@ pub fn get_thread_option<'de, T: OptionValue<'de>>( /// Update a process option value. Requires `T`: [OptionSizeHint]. pub fn set_process_option<'de, T: OptionValue<'de> + OptionSizeHint>( + process: Option, value: &T::Value, ) -> Result<(), Error> where [u8; T::SIZE_HINT]: Sized, { let mut buffer = [0; T::SIZE_HINT]; - set_process_option_with::(&mut buffer, value) + set_process_option_with::(process, &mut buffer, value) } /// Update a process option value, using provided buffer for serialization. pub fn set_process_option_with<'de, T: OptionValue<'de>>( + process: Option, buffer: &mut [u8], value: &T::Value, ) -> Result<(), Error> { let len = T::store(value, buffer)?; - unsafe { crate::sys::set_process_option(T::VARIANT.into(), &buffer[..len]) } + unsafe { crate::sys::set_process_option(process, T::VARIANT.into(), &buffer[..len]) } } /// Update a thread option value. Requires `T`: [OptionSizeHint]. diff --git a/lib/runtime/src/process/signal.rs b/lib/runtime/src/process/signal.rs index 2df3afed..07f3ce4d 100644 --- a/lib/runtime/src/process/signal.rs +++ b/lib/runtime/src/process/signal.rs @@ -102,7 +102,7 @@ pub fn get_signal_stack() -> (usize, usize) { /// 1. It must not return, it must call `exit_thread` instead. /// 2. It must conform to the kernel's signal entry ABI. pub unsafe fn set_signal_entry(entry: usize) { - process::set_process_option::(&entry) + process::set_process_option::(None, &entry) .expect("set_signal_entry() failed"); } diff --git a/lib/runtime/src/process/thread.rs b/lib/runtime/src/process/thread.rs index 0028ec8f..8f4d49d8 100644 --- a/lib/runtime/src/process/thread.rs +++ b/lib/runtime/src/process/thread.rs @@ -1,6 +1,7 @@ //! Runtime utilities for thread handling use core::{ + mem::MaybeUninit, ptr, sync::atomic::{AtomicU32, Ordering}, }; @@ -8,7 +9,7 @@ use core::{ use abi::{ error::Error, mem::{MappingFlags, MappingSource}, - process::ThreadSpawnOptions, + process::{ThreadEvent, ThreadSpawnOptions}, }; use alloc::{boxed::Box, sync::Arc}; @@ -219,7 +220,14 @@ impl ThreadHandle { /// Waits for the thread to finish and returns its result. pub fn join(self) -> Result, Error> { // TODO prevent threads from attempting to join themselves? - unsafe { crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire)) }?; + loop { + let mut event = MaybeUninit::uninit(); + unsafe { crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire), &mut event) }?; + let event = unsafe { event.assume_init() }; + if matches!(event, ThreadEvent::Exited) { + break; + } + } Ok(self.into_result()) } @@ -230,8 +238,16 @@ impl ThreadHandle { /// Will panic if the kernel returns any error besides [Error::Interrupted]. pub fn join_uninterruptible(self) -> Option { loop { - match unsafe { crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire)) } { - Ok(_) => (), + let mut event = MaybeUninit::uninit(); + match unsafe { + crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire), &mut event) + } { + Ok(_) => { + let event = unsafe { event.assume_init() }; + if !matches!(event, ThreadEvent::Exited) { + continue; + } + } Err(Error::Interrupted) => continue, Err(error) => panic!("wait_thread syscall returned error: {error:?}"), } diff --git a/lib/runtime/src/process/thread_local/i686.rs b/lib/runtime/src/process/thread_local/i686.rs index 6d1f3b0b..efd1c47b 100644 --- a/lib/runtime/src/process/thread_local/i686.rs +++ b/lib/runtime/src/process/thread_local/i686.rs @@ -19,7 +19,7 @@ pub fn get_thread_pointer() -> usize { /// `value` must hold an address to a structure, first element of which is a pointer to itself. /// Usual pointer safety requirements apply. pub unsafe fn set_thread_pointer(value: usize) -> Result<(), Error> { - process::set_process_option::(&value) + process::set_thread_option::(&value) } // ___tls_get_addr, TLS_index structure address gets passed in the %eax register diff --git a/userspace/Cargo.lock b/userspace/Cargo.lock index 81f77898..5e799b84 100644 --- a/userspace/Cargo.lock +++ b/userspace/Cargo.lock @@ -2278,6 +2278,7 @@ dependencies = [ name = "runtime" version = "0.1.0" dependencies = [ + "abi-lib", "abi-serde", "yggdrasil-abi", "yggdrasil-rt", @@ -2589,6 +2590,14 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "strace" +version = "0.1.0" +dependencies = [ + "clap", + "runtime", +] + [[package]] name = "strict-num" version = "0.1.1" @@ -2656,6 +2665,7 @@ dependencies = [ "logsink", "pci-ids", "rand 0.9.0-alpha.1", + "runtime", "serde", "serde_json", "sha2", diff --git a/userspace/Cargo.toml b/userspace/Cargo.toml index 416f5a2b..24dec191 100644 --- a/userspace/Cargo.toml +++ b/userspace/Cargo.toml @@ -20,7 +20,8 @@ members = [ "lib/runtime", "lib/uipc", "lib/logsink", - "lib/libpsf" + "lib/libpsf", + "strace" ] exclude = ["dynload-program", "test-kernel-module", "lib/ygglibc"] @@ -57,6 +58,7 @@ uipc.path = "lib/uipc" yggdrasil-rt.path = "../lib/runtime" yggdrasil-abi = { path = "../lib/abi", features = ["serde", "alloc", "bytemuck"] } abi-serde = { path = "../lib/abi-serde" } +abi-lib = { path = "../lib/abi-lib" } logsink.path = "lib/logsink" libutil.path = "../lib/libutil" diff --git a/userspace/lib/runtime/Cargo.toml b/userspace/lib/runtime/Cargo.toml index 59665a7a..b2afb957 100644 --- a/userspace/lib/runtime/Cargo.toml +++ b/userspace/lib/runtime/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" yggdrasil-abi = { workspace = true, features = ["alloc", "bytemuck"] } yggdrasil-rt.workspace = true abi-serde.workspace = true +abi-lib.workspace = true [lints] workspace = true diff --git a/userspace/lib/runtime/src/lib.rs b/userspace/lib/runtime/src/lib.rs index 67f212b4..3cf68c98 100644 --- a/userspace/lib/runtime/src/lib.rs +++ b/userspace/lib/runtime/src/lib.rs @@ -6,7 +6,10 @@ pub use yggdrasil_rt as rt; pub use yggdrasil_abi as abi; #[cfg(rust_analyzer)] pub use abi_serde; +#[cfg(rust_analyzer)] +pub use abi_lib; pub use std::os::yggdrasil::rt; pub use std::os::yggdrasil::rt::abi; pub use std::os::yggdrasil::rt::abi::abi_serde; +pub use std::os::yggdrasil::rt::abi::abi_lib; diff --git a/userspace/strace/Cargo.toml b/userspace/strace/Cargo.toml new file mode 100644 index 00000000..431169d3 --- /dev/null +++ b/userspace/strace/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "strace" +version = "0.1.0" +edition = "2021" + +[dependencies] +runtime.workspace = true + +clap.workspace = true + +[lints] +workspace = true diff --git a/userspace/strace/src/format.rs b/userspace/strace/src/format.rs new file mode 100644 index 00000000..2fd43e64 --- /dev/null +++ b/userspace/strace/src/format.rs @@ -0,0 +1,383 @@ +use std::fmt::Write; + +use runtime::{abi::SyscallFunction, abi_lib::SyscallRegister, rt::io::{OpenOptions, SeekFrom}}; + +use crate::tracer::{CommandTracer, SyscallTrace}; + +enum Arg { + Fd, + OptFd, + OptPid, + InByteSlice, + OutByteSlice, + OpenOptions, + Hex, + Dec, + Oct, + Bool, + ThinPointer, + U64Dec, + Seek, + Todo, +} + +#[derive(Clone, Copy)] +enum Result { + ResUnit, + ResFd, + ResPid, + ResDec, + ResHex, + ResU32, + None, +} + +#[derive(Clone, Copy)] +struct Formatter { + fun: &'static str, + args: &'static [Arg], + res: Result +} + +impl Formatter { + const EMPTY: Self = Self { + fun: "", + args: &[], + res: Result::None + }; +} + +macro_rules! formatter_list { + ( + [ + $($syscall:ident => $name:ident ($($arg:ident),* $(,)?) $(-> $result:ident)?),+ $(,)? + ] + ) => { + const FORMATTERS: [Option; SYSCALL_MAX] = const { + let mut array = [None; SYSCALL_MAX]; + + $( + array[SyscallFunction::$syscall as usize] = Some(Formatter { + fun: stringify!($name), + args: &[$(Arg::$arg),*], + $(res: Result::$result,)? + ..Formatter::EMPTY + }); + )+ + + array + }; + }; +} + +// TODO somehow sync this up with abi definition +const SYSCALL_MAX: usize = 256; +formatter_list!([ + // System + GetRandom => get_random(Todo), + GetClock => get_clock(Todo) -> ResUnit, + Mount => mount(Todo) -> ResUnit, + Unmount => unmount(Todo) -> ResUnit, + LoadModule => load_module(OutByteSlice) -> ResUnit, + FilesystemControl => filesystem_control(OptFd, Hex, Todo) -> ResDec, + GetSystemInfo => get_system_info(Hex, Todo) -> ResDec, + + // Memory management + MapMemory => map_memory(ThinPointer, Hex, Todo) -> ResHex, + UnmapMemory => unmap_memory(ThinPointer, Hex) -> ResUnit, + + // Process/thread + SpawnThread => spawn_thread(Todo) -> ResU32, + WaitThread => wait_thread(Dec, Todo) -> ResUnit, + GetThreadOption => get_thread_option(Hex, Todo) -> ResDec, + SetThreadOption => set_thread_option(Hex, Todo) -> ResUnit, + GetProcessOption => get_process_option(OptPid, Hex, Todo) -> ResDec, + SetProcessOption => set_process_option(OptPid, Hex, Todo) -> ResUnit, + + Nanosleep => nanosleep(Todo) -> ResUnit, + ExitSignal => exit_signal(Todo), + SendSignal => send_signal(Dec, Todo) -> ResUnit, + + Mutex => mutex(ThinPointer, Todo) -> ResUnit, + StartSession => start_session() -> ResUnit, + + // I/O + Open => open(OptFd, OutByteSlice, OpenOptions, Oct) -> ResFd, + Close => close(Fd) -> ResUnit, + Read => read(Fd, InByteSlice) -> ResDec, + Write => write(Fd, OutByteSlice) -> ResDec, + Seek => seek(Fd, Seek, Todo) -> ResUnit, + Truncate => truncate(Fd, U64Dec) -> ResUnit, + Fsync => fsync(Fd, Todo) -> ResUnit, + ReadAt => read_at(Fd, U64Dec, InByteSlice) -> ResDec, + WriteAt => write_at(Fd, U64Dec, OutByteSlice) -> ResDec, + GetFileOption => get_file_option(Fd, Hex, Todo) -> ResDec, + SetFileOption => set_file_option(Fd, Hex, Todo) -> ResUnit, + OpenDirectory => open_directory(OptFd, OutByteSlice) -> ResFd, + ReadDirectoryEntries => read_directory_entries(Fd, Todo) -> ResDec, + CreateDirectory => create_directory(OptFd, OutByteSlice, Oct) -> ResUnit, + CreateSymlink => create_symlink(OptFd, OutByteSlice, OutByteSlice) -> ResUnit, + ReadLink => read_link(OptFd, OutByteSlice, InByteSlice) -> ResDec, + Remove => remove(OptFd, OutByteSlice, Todo) -> ResUnit, + Rename => rename(Todo) -> ResUnit, + CloneFd => clone_fd(Fd, OptFd) -> ResFd, + UpdateMetadata => update_metadata(OptFd, OutByteSlice, Todo) -> ResUnit, + GetMetadata => get_metadata(OptFd, OutByteSlice, Todo, Bool) -> ResUnit, + DeviceRequest => device_request(Fd, Hex, Todo) -> ResDec, + + // Misc I/O + CreateTimer => create_timer(Todo) -> ResFd, + CreatePid => create_pid(Todo) -> ResFd, + CreatePty => create_pty(Todo) -> ResFd, + CreateSharedMemory => create_shared_memory(Hex) -> ResFd, + CreatePipe => create_pipe(Todo) -> ResFd, + PollChannelWait => poll_channel_wait(Fd, Todo) -> ResUnit, + PollChannelControl => poll_channel_control(Fd, Todo) -> ResUnit, + + // Network + CreateSocket => create_socket(Todo) -> ResFd, + Bind => bind(Fd, Todo) -> ResUnit, + Listen => listen(Fd) -> ResUnit, + Connect => connect(Fd, Todo) -> ResUnit, + Accept => accept(Fd, Todo) -> ResFd, + Shutdown => shutdown(Fd, Todo) -> ResUnit, + SendTo => send_to(Fd, OutByteSlice, Todo) -> ResDec, + ReceiveFrom => receive_from(Fd, InByteSlice, Todo) -> ResDec, + GetSocketOption => get_socket_option(Fd, Hex, Todo) -> ResDec, + SetSocketOption => set_socket_option(Fd, Hex, Todo) -> ResUnit, + SendMessage => send_message(Fd, Todo) -> ResDec, + ReceiveMessage => receive_message(Fd, Todo) -> ResDec, + + // C compat + Fork => fork() -> ResPid, + Execve => execve(Todo) -> ResUnit, + + // Debugging + DebugTrace => debug_trace(Dec, OutByteSlice), + DebugControl => debug_control(Dec, Hex, Todo) -> ResDec, +]); + +impl Formatter { + fn print(&self, tracer: &mut CommandTracer, args: &[usize], result: usize) { + print!("{}(", self.fun); + let mut arg_index = 0; + for (i, arg) in self.args.iter().enumerate() { + if i != 0 { + print!(", "); + } + + let Some((arg, len)) = arg.format(tracer, &args[arg_index..], result) else { + print!(""); + break; + }; + arg_index += len; + + print!("{arg}") + } + print!(")"); + if let Some(result) = self.res.format(result) { + print!(" -> {result}"); + } + println!(); + } +} + +const MAX_BYTES: usize = 32; + +fn format_byte_slice(bytes: &[u8], real_len: usize, allow_ascii: bool) -> String { + let mut result = String::new(); + if bytes.is_empty() { + return "".into(); + } + if bytes[0].is_ascii_graphic() && allow_ascii { + // Format as ASCII string + write!(result, "\"").ok(); + for &byte in bytes { + let ch = byte.escape_ascii(); + write!(result, "{ch}").ok(); + } + if bytes.len() < real_len { + write!(result, "...\" +{}", real_len - bytes.len()).ok(); + } else { + write!(result, "\"").ok(); + } + } else { + write!(result, "[").ok(); + for (i, &byte) in bytes.iter().enumerate() { + if i != 0 { + write!(result, ", ").ok(); + } + write!(result, "{byte:#04X}").ok(); + } + if bytes.len() < real_len { + write!(result, "... +{}", real_len - bytes.len()).ok(); + } + write!(result, "]").ok(); + } + result +} + +fn format_open_options(opts: OpenOptions) -> String { + const OPTS: &[(&str, OpenOptions)] = &[ + ("READ", OpenOptions::READ), + ("WRITE", OpenOptions::WRITE), + ("APPEND", OpenOptions::APPEND), + ("CREATE", OpenOptions::CREATE), + ("CREATE_EXCL", OpenOptions::CREATE_EXCL), + ("TRUNCATE", OpenOptions::TRUNCATE), + ]; + let mut result = String::new(); + + let mut i = 0; + for &(name, opt) in OPTS { + if opts.contains(opt) { + if i != 0 { + write!(result, "|").ok(); + } + write!(result, "{name}").ok(); + i += 1; + } + } + + if result.is_empty() { + "".into() + } else { + result + } +} + +impl Arg { + fn format( + &self, + tracer: &mut CommandTracer, + args: &[usize], + result: usize, + ) -> Option<(String, usize)> { + match self { + Arg::Dec => Some((format!("{}", args[0]), 1)), + Arg::Hex => Some((format!("{:#x}", args[0]), 1)), + Arg::Oct => Some((format!("{:#o}", args[0]), 1)), + #[cfg(any(rust_analyzer, target_pointer_width = "64"))] + Arg::U64Dec => { + Some((format!("{}", args[0]), 1)) + } + Arg::Bool => { + let result = if args[0] == 0 { + "false".into() + } else { + "true".into() + }; + Some((result, 1)) + } + Arg::Fd => Some((format!("{}", args[0]), 1)), + Arg::OptPid => { + let result = if args[0] == usize::MAX { + "".into() + } else { + format!("#{}", args[0]) + }; + Some((result, 1)) + } + Arg::OptFd => Some(( + if args[0] == usize::MAX { + "".into() + } else { + format!("{}", args[0]) + }, + 1, + )), + Arg::OutByteSlice => { + let mut len = args[1]; + if len > MAX_BYTES { + len = MAX_BYTES; + } + let mut buffer = vec![0; len]; + if tracer.read_memory(args[0], &mut buffer).is_ok() { + Some((format_byte_slice(&buffer, args[1], true), 2)) + } else { + Some((format!("<{} bytes @ {:#x}>", args[1], args[0]), 2)) + } + } + Arg::InByteSlice => { + let mut len = result; + if len > MAX_BYTES { + len = MAX_BYTES; + } + let mut buffer = vec![0; len]; + if tracer.read_memory(args[0], &mut buffer).is_ok() { + Some((format_byte_slice(&buffer, result, true), 2)) + } else { + Some(( + format!("<{}/{} bytes @ {:#x}>", result, args[1], args[0]), + 2, + )) + } + } + Arg::OpenOptions => { + let opts = unsafe { OpenOptions::from_raw(args[0] as u32) }; + Some((format_open_options(opts), 1)) + } + Arg::ThinPointer => { + let result = if args[0] == 0 { + "".into() + } else { + format!("{:#x}", args[0]) + }; + Some((result, 1)) + } + Arg::Seek => { + // TODO this will get encoded as two words in i686 + let seek = SeekFrom::from_syscall_register(args[0]); + let result = match seek { + SeekFrom::Start(pos) => format!("={pos}"), + SeekFrom::End(pos) => format!("=END{:+}", -pos), + SeekFrom::Current(pos) => format!("{pos:+}"), + }; + Some((result, 1)) + } + Arg::Todo => { + Some(("...".into(), 1)) + } + } + } +} + +impl Result { + fn format(&self, result: usize) -> Option { + Some(match self { + Self::ResDec | Self::ResFd | Self::ResU32 | Self::ResPid => { + if result & (1 << 63) != 0 { + "".into() + } else { + format!("{result}") + } + } + Self::ResHex => { + if result & (1 << 63) != 0 { + "".into() + } else { + format!("{result:#x}") + } + } + Self::ResUnit => { + if result == 0 { + "" + } else { + "" + }.into() + } + _ => return None + }) + } +} + +pub fn format_syscall(tracer: &mut CommandTracer, syscall: &SyscallTrace) { + // Ignore mutex calls for now, they're too noisy + if syscall.function == SyscallFunction::Mutex as usize { + return; + } + + if let Some(Some(formatter)) = FORMATTERS.get(syscall.function) { + formatter.print(tracer, &syscall.args, syscall.result); + } +} diff --git a/userspace/strace/src/main.rs b/userspace/strace/src/main.rs new file mode 100644 index 00000000..c87e1b35 --- /dev/null +++ b/userspace/strace/src/main.rs @@ -0,0 +1,36 @@ +#![feature(rustc_private, yggdrasil_os, let_chains)] + +use std::{ + io, + process::{Command, ExitCode}, +}; + +use clap::Parser; +use tracer::CommandTracer; + +pub mod format; +pub mod tracer; + +#[derive(Debug, Parser)] +struct Args { + command: 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 + } + } +} diff --git a/userspace/strace/src/tracer.rs b/userspace/strace/src/tracer.rs new file mode 100644 index 00000000..87b12925 --- /dev/null +++ b/userspace/strace/src/tracer.rs @@ -0,0 +1,233 @@ +use std::{ + io, + mem::MaybeUninit, + os::yggdrasil::process::{ChildExt, CommandExt}, + process::{Child, Command}, +}; + +use runtime::{ + abi::{ + arch::SavedFrame, + debug::{self, TraceEventPayload, TraceFlags}, + }, + rt::{ + debug::debug_control, + process::ThreadEvent, + time::{get_monotonic_time, SystemTime}, + }, +}; + +pub struct CommandTracer { + #[allow(unused)] + child: Child, + tid: u32, + buffer: [u8; 512], + start: SystemTime, + + last_syscall: Option, +} + +struct EnteredSyscall { + seq: u64, + timestamp: SystemTime, + regs: SavedFrame, +} + +#[allow(unused)] +#[derive(Debug)] +pub struct SyscallTrace { + pub entry: SystemTime, + pub exit: SystemTime, + pub function: usize, + pub args: [usize; 6], + pub result: usize, +} + +impl CommandTracer { + pub fn spawn(mut command: Command) -> io::Result { + let mut buffer = [0; 512]; + unsafe { command.attach_tracing() }; + let start = get_monotonic_time().unwrap(); + let child = command.spawn()?; + let tid = child.main_thread_id()?; + + debug_control::( + tid, + &mut buffer, + &(TraceFlags::SYSCALL_ENTRY | TraceFlags::SYSCALL_EXIT), + ) + .map_err(io::Error::from)?; + + Ok(Self { + child, + tid, + buffer, + start, + + last_syscall: None, + }) + } + + pub fn start_timestamp(&self) -> SystemTime { + self.start + } + + fn next_event(&self) -> io::Result { + let mut event = MaybeUninit::uninit(); + unsafe { runtime::rt::sys::wait_thread(self.tid, &mut event) }.map_err(io::Error::from)?; + Ok(unsafe { event.assume_init() }) + } + + fn resume(&mut self) -> io::Result<()> { + debug_control::(self.tid, &mut self.buffer, &false).map_err(io::Error::from) + } + + fn get_regs(&mut self) -> io::Result { + debug_control::(self.tid, &mut self.buffer, &()) + .map_err(io::Error::from) + } + + pub fn read_word(&mut self, address: usize) -> io::Result { + debug_control::(self.tid, &mut self.buffer, &address).map_err(io::Error::from) + } + + pub fn read_memory(&mut self, mut address: usize, buffer: &mut [u8]) -> io::Result<()> { + const ALIGN: usize = size_of::() - 1; + + let mut offset = 0; + let mut remaining = buffer.len(); + while remaining != 0 { + let aligned_address = address & !ALIGN; + let offset_in_word = address & ALIGN; + let amount = (size_of::() - offset_in_word).min(remaining); + let word = self.read_word(aligned_address)?.to_ne_bytes(); + + buffer[offset..offset + amount] + .copy_from_slice(&word[offset_in_word..offset_in_word + amount]); + + offset += amount; + address += amount; + remaining -= amount; + } + + Ok(()) + } + + pub fn run(mut self, formatter: F) -> io::Result<()> { + loop { + // Wait for event + let event = self.next_event()?; + + match event { + ThreadEvent::Exited => { + break; + } + ThreadEvent::Trace(trace) => { + match trace.payload { + TraceEventPayload::SyscallEntry(seq) => { + let regs = self.get_regs()?; + self.last_syscall = Some(EnteredSyscall { + seq, + timestamp: trace.timestamp, + regs, + }); + } + TraceEventPayload::SyscallExit(seq) => { + let regs = self.get_regs()?; + + if let Some(last) = self.last_syscall.take() + && last.seq == seq + { + let event = SyscallTrace::from_parts(&last, ®s, trace.timestamp); + formatter(&mut self, &event); + } + } + _ => (), + } + + if trace.suspend { + // Resume + self.resume()?; + } + } + } + } + + Ok(()) + } +} + +#[cfg(any(target_arch = "x86_64", rust_analyzer))] +impl SyscallTrace { + fn from_parts(entry: &EnteredSyscall, exit_regs: &SavedFrame, exit: SystemTime) -> Self { + let function = entry.regs.rax as usize; + let args = [ + entry.regs.rdi as usize, + entry.regs.rsi as usize, + entry.regs.rdx as usize, + entry.regs.r10 as usize, + entry.regs.r8 as usize, + entry.regs.r9 as usize, + ]; + let result = exit_regs.rax as usize; + let entry = entry.timestamp; + + Self { + entry, + exit, + function, + args, + result, + } + } +} + +#[cfg(any(target_arch = "riscv64", rust_analyzer))] +impl SyscallTrace { + fn from_parts(entry: &EnteredSyscall, exit_regs: &SavedFrame, exit: SystemTime) -> Self { + let function = entry.regs.an[0] as usize; + let args = [ + entry.regs.an[1] as usize, + entry.regs.an[2] as usize, + entry.regs.an[3] as usize, + entry.regs.an[4] as usize, + entry.regs.an[5] as usize, + entry.regs.an[6] as usize, + ]; + let result = exit_regs.an[0] as usize; + let entry = entry.timestamp; + + Self { + entry, + exit, + function, + args, + result + } + } +} + +#[cfg(any(target_arch = "aarch64", rust_analyzer))] +impl SyscallTrace { + fn from_parts(entry: &EnteredSyscall, exit_regs: &SavedFrame, exit: SystemTime) -> Self { + let function = entry.regs.gp_regs[8]; + let args = [ + entry.regs.gp_regs[0] as usize, + entry.regs.gp_regs[1] as usize, + entry.regs.gp_regs[2] as usize, + entry.regs.gp_regs[3] as usize, + entry.regs.gp_regs[4] as usize, + entry.regs.gp_regs[5] as usize, + ]; + let result = exit_regs.gp_regs[0] as usize; + let entry = entry.timestamp; + + Self { + entry, + exit, + function, + args, + result, + } + } +} diff --git a/userspace/sysutils/Cargo.toml b/userspace/sysutils/Cargo.toml index 732296f8..e5680c71 100644 --- a/userspace/sysutils/Cargo.toml +++ b/userspace/sysutils/Cargo.toml @@ -13,6 +13,7 @@ yggdrasil-rt.workspace = true cross.workspace = true logsink.workspace = true libutil.workspace = true +runtime.workspace = true log.workspace = true rand.workspace = true diff --git a/userspace/sysutils/src/tst.rs b/userspace/sysutils/src/tst.rs index d2651f34..f328e4d9 100644 --- a/userspace/sysutils/src/tst.rs +++ b/userspace/sysutils/src/tst.rs @@ -1,14 +1 @@ -#![feature(seek_stream_len)] - -use std::{fs::File, io::{stdout, Seek, Write}}; - -use cross::mem::FileMapping; - -fn main() { - let mut file = File::open("/etc/test.txt").unwrap(); - let size = file.stream_len().unwrap(); - let mapping = FileMapping::map(file, size as usize).unwrap(); - let mut data = vec![0; size as usize]; - data.copy_from_slice(&mapping[..]); - stdout().write_all(&data).unwrap(); -} +fn main() {} diff --git a/xtask/src/build/userspace.rs b/xtask/src/build/userspace.rs index 1a7fcf62..b3376c8b 100644 --- a/xtask/src/build/userspace.rs +++ b/xtask/src/build/userspace.rs @@ -28,6 +28,7 @@ const PROGRAMS: &[(&str, &str)] = &[ // sysutils ("mount", "sbin/mount"), ("login", "sbin/login"), + ("strace", "bin/strace"), ("ls", "bin/ls"), ("mv", "bin/mv"), ("ln", "bin/ln"),