proc: implement process tracing

This commit is contained in:
Mark Poliakov 2025-02-27 18:49:20 +02:00
parent bbdcfd947a
commit 03242a0635
40 changed files with 1423 additions and 375 deletions

View File

@ -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(())
// }
}

View File

@ -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<usize, Arc<UserspaceMutex>>,
space: Option<Arc<ProcessAddressSpace>>,
image: Option<ProcessImage>,
thread_exits: BTreeMap<ThreadId, Arc<BoolEvent>>,
}
/// 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<ThreadEvent, Error> {
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<Thread>) {
self.thread_exits.insert(thread.id, thread.exit.clone());
self.threads.push(thread);
}

View File

@ -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 = <ArchitectureImpl as Architecture>::BreakpointType;
const BREAKPOINT_VALUE: BreakpointType = ArchitectureImpl::BREAKPOINT_VALUE;
// type BreakpointType = <ArchitectureImpl as Architecture>::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<ProcessId>,
pub single_step: bool,
pub restore_breakpoint: Option<usize>,
pub debugger: Option<ThreadDebugger>,
pub saved_frame: Option<SavedFrame>,
pub breakpoints: BTreeMap<usize, BreakpointType>,
pub state: SavedFrame,
}
pub struct ThreadInfo {
pub signal_stack: ThreadSignalStack,
}
pub struct ThreadEvents {
pub exit: BoolEvent,
pub trace: LossyRingQueue<TraceEvent>,
}
/// Describes a single thread within the system
pub struct Thread {
/// Unique thread ID
@ -74,19 +88,25 @@ pub struct Thread {
pub context: Cell<TaskContextImpl>,
process: Option<ProcessId>,
space: Option<Arc<ProcessAddressSpace>>,
debug: IrqSafeSpinlock<ThreadDebuggingInfo>,
debug: IrqSafeSpinlock<ThreadTracingInfo>,
trace_flags: AtomicU32,
syscall_trace_seq: AtomicU64,
// inner: IrqSafeSpinlock<ThreadInner>,
info: IrqSafeRwLock<ThreadInfo>,
signal_queue: SegQueue<Signal>,
pub exit: Arc<BoolEvent>,
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<Thread>, 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<Arc<Process>> {
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<F: TaskFrame>(&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<SuspendGuard, Error> {
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<usize, Error> {
// 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::<usize>(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<F: TaskFrame, P: FnOnce() -> 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<F: TaskFrame>(&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<F: TaskFrame>(&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<F: TaskFrame>(&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::<BreakpointType>();
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<F: TaskFrame>(&self, frame: &mut F) -> bool {
let mut debug = self.debug.lock();
let ip = frame.user_ip();
pub fn handle_breakpoint<F: TaskFrame>(&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<F: TaskFrame>(&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
}
}

View File

@ -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

View File

@ -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)
}
_ => {

View File

@ -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?}");
}

View File

@ -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<usize, Error> {
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)
}

View File

@ -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<ProcessId, Err
let process = thread.process();
let result = run_with_io(&process, |mut io| {
let attach_trace = options
.optional
.iter()
.any(|opt| matches!(opt, SpawnOption::AttachTrace));
// let attach_debugger = options
// .optional
// .iter()
@ -112,8 +116,7 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Err
path: options.program,
args: options.arguments,
envs: options.arguments,
single_step: false,
// single_step: attach_debugger.is_some(),
single_step: attach_trace,
disable_aslr: options.flags.contains(SpawnFlags::DISABLE_ASLR),
};
let (child_process, child_main) = proc::load_binary(io.ioctx_mut(), &load_options)?;
@ -171,6 +174,11 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Err
drop(child_io);
child_main.enqueue();
if attach_trace {
child_main
.attach_trace(process.id)
.expect("BUG: Could not attach to thread trace");
}
// if let Some(debugger) = attach_debugger {
// child_main.attach_debugger(ThreadDebugger::new(debugger));
// } else {
@ -315,12 +323,14 @@ pub(crate) fn exit_thread() -> ! {
thread.exit(ExitCode::SUCCESS)
}
pub(crate) fn wait_thread(id: u32) -> Result<(), Error> {
pub(crate) fn wait_thread(id: u32, event: &mut MaybeUninit<ThreadEvent>) -> 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<usize, Error> {
@ -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<usize, Error> {
pub(crate) fn get_process_option(
pid: Option<ProcessId>,
option: u32,
buffer: &mut [u8],
) -> Result<usize, 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.get_option(option, buffer)
}
pub(crate) fn set_process_option(option: u32, buffer: &[u8]) -> Result<(), Error> {
pub(crate) fn set_process_option(
pid: Option<ProcessId>,
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)
}

View File

@ -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<const N: usize, T: Serialize> Serialize for [T; N] {
fn serialize<S: Serializer>(&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<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
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<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
serializer.write_bytes(self)
@ -75,26 +96,26 @@ impl<'de> Deserialize<'de> for &'de str {
}
}
impl<const N: usize> Serialize for [u8; N]
where
[u8; N]: Sized,
{
fn serialize<S: Serializer>(&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<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
deserializer
.read_bytes()?
.try_into()
.map_err(|_| D::Error::INVALID_ARRAY_LEN)
}
}
// impl<const N: usize> Serialize for [u8; N]
// where
// [u8; N]: Sized,
// {
// fn serialize<S: Serializer>(&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<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
// deserializer
// .read_bytes()?
// .try_into()
// .map_err(|_| D::Error::INVALID_ARRAY_LEN)
// }
// }
impl<T: Serialize> Serialize for Option<T> {
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {

View File

@ -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"))]

View File

@ -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 = []

View File

@ -17,7 +17,7 @@ enum Signal(u32) {
Terminated = 7,
}
newtype ProcessId(u32);
newtype ProcessId(u31);
newtype ProcessGroupId(u32);
newtype ThreadId(u32);

View File

@ -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<u32>;
syscall exit_thread() -> !;
syscall wait_thread(tid: u32) -> Result<()>;
syscall wait_thread(tid: u32, event: &mut MaybeUninit<ThreadEvent>) -> Result<()>;
syscall get_thread_option(option: u32, value: &mut [u8]) -> Result<usize>;
syscall set_thread_option(option: u32, value: &[u8]) -> Result<()>;
syscall get_process_option(option: u32, value: &mut [u8]) -> Result<usize>;
syscall set_process_option(option: u32, value: &[u8]) -> Result<()>;
syscall get_process_option(process: Option<ProcessId>, option: u32, value: &mut [u8]) -> Result<usize>;
syscall set_process_option(process: Option<ProcessId>, option: u32, value: &[u8]) -> Result<()>;
syscall nanosleep(duration: &Duration, remaining: &mut MaybeUninit<Duration>) -> 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<usize>;

View File

@ -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,
]);

View File

@ -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,
]);

View File

@ -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;

View File

@ -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,
]);

View File

@ -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
]);

View File

@ -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);

View File

@ -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 {

View File

@ -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<Signal, u32>),
}
/// 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<S: Serializer>(&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<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
match deserializer.read_u8()? {
0 => Deserialize::deserialize(deserializer).map(Self::Exited),
1 => Deserialize::deserialize(deserializer).map(Self::Exited),
_ => Err(D::Error::INVALID_ENUM_VARIANT),
}
}
}

View File

@ -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<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
serializer.write_u32(self.into_raw())
}
}
impl<'de> Deserialize<'de> for Signal {
fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
Self::try_from(deserializer.read_u32()?).map_err(|_| D::Error::INVALID_ENUM_VARIANT)
}
}
impl Serialize for ThreadId {
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
self.into_raw().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ThreadId {
fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
Deserialize::deserialize(deserializer).map(|r| unsafe { Self::from_raw(r) })
}
}

View File

@ -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),
}
);

View File

@ -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<T::Response, Error> {
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])
}

View File

@ -13,7 +13,7 @@ pub fn home_directory<T, F: FnOnce(&str) -> T>(_mapper: F) -> Result<T, Error> {
pub fn current_directory<T, F: FnOnce(&str) -> T>(mapper: F) -> Result<T, Error> {
let mut buffer = [0; 512];
let path = process::get_process_option::<process::options::Directory>(&mut buffer)?;
let path = process::get_process_option::<process::options::Directory>(None, &mut buffer)?;
Ok(mapper(path))
}
@ -23,7 +23,7 @@ pub fn current_directory_string() -> Result<String, Error> {
pub fn set_current_directory(path: &str) -> Result<(), Error> {
let mut buffer = [0; 512];
process::set_process_option_with::<process::options::Directory>(&mut buffer, &path)
process::set_process_option_with::<process::options::Directory>(None, &mut buffer, &path)
}
pub fn make_temp_directory(_template: &mut [u8]) -> Result<(), Error> {

View File

@ -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<ProcessId>,
buffer: &'de mut [u8],
) -> Result<T::Value, Error> {
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<ProcessId>,
value: &T::Value,
) -> Result<(), Error>
where
[u8; T::SIZE_HINT]: Sized,
{
let mut buffer = [0; T::SIZE_HINT];
set_process_option_with::<T>(&mut buffer, value)
set_process_option_with::<T>(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<ProcessId>,
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].

View File

@ -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::<process::options::SignalEntry>(&entry)
process::set_process_option::<process::options::SignalEntry>(None, &entry)
.expect("set_signal_entry() failed");
}

View File

@ -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<R> ThreadHandle<R> {
/// Waits for the thread to finish and returns its result.
pub fn join(self) -> Result<Option<R>, 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<R> ThreadHandle<R> {
/// Will panic if the kernel returns any error besides [Error::Interrupted].
pub fn join_uninterruptible(self) -> Option<R> {
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:?}"),
}

View File

@ -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::<thread::options::ThreadPointer>(&value)
process::set_thread_option::<thread::options::ThreadPointer>(&value)
}
// ___tls_get_addr, TLS_index structure address gets passed in the %eax register

10
userspace/Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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

View File

@ -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;

View File

@ -0,0 +1,12 @@
[package]
name = "strace"
version = "0.1.0"
edition = "2021"
[dependencies]
runtime.workspace = true
clap.workspace = true
[lints]
workspace = true

View File

@ -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<Formatter>; 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 "<empty>".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() {
"<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 {
"<self>".into()
} else {
format!("#{}", args[0])
};
Some((result, 1))
}
Arg::OptFd => Some((
if args[0] == usize::MAX {
"<none>".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 {
"<null>".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<String> {
Some(match self {
Self::ResDec | Self::ResFd | Self::ResU32 | Self::ResPid => {
if result & (1 << 63) != 0 {
"<error>".into()
} else {
format!("{result}")
}
}
Self::ResHex => {
if result & (1 << 63) != 0 {
"<error>".into()
} else {
format!("{result:#x}")
}
}
Self::ResUnit => {
if result == 0 {
"<ok>"
} else {
"<error>"
}.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);
}
}

View File

@ -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<String>,
}
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
}
}
}

View File

@ -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<EnteredSyscall>,
}
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<Self> {
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::<debug::SetTraceFlags>(
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<ThreadEvent> {
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::<debug::Resume>(self.tid, &mut self.buffer, &false).map_err(io::Error::from)
}
fn get_regs(&mut self) -> io::Result<SavedFrame> {
debug_control::<debug::GetRegisters>(self.tid, &mut self.buffer, &())
.map_err(io::Error::from)
}
pub fn read_word(&mut self, address: usize) -> io::Result<usize> {
debug_control::<debug::ReadMemory>(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::<usize>() - 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::<usize>() - 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<F: Fn(&mut Self, &SyscallTrace)>(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, &regs, 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,
}
}
}

View File

@ -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

View File

@ -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() {}

View File

@ -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"),