proc: implement process tracing
This commit is contained in:
parent
bbdcfd947a
commit
03242a0635
@ -1,5 +1,3 @@
|
|||||||
use yggdrasil_abi::{debug::DebugFrame, error::Error};
|
|
||||||
|
|
||||||
pub struct ThreadDebugger {}
|
pub struct ThreadDebugger {}
|
||||||
|
|
||||||
impl ThreadDebugger {
|
impl ThreadDebugger {
|
||||||
@ -10,16 +8,16 @@ impl ThreadDebugger {
|
|||||||
// Self { channel }
|
// Self { channel }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub fn send(&self, frame: &DebugFrame) -> Result<(), Error> {
|
// pub fn send(&self, frame: &DebugFrame) -> Result<(), Error> {
|
||||||
let _ = frame;
|
// let _ = frame;
|
||||||
todo!()
|
// todo!()
|
||||||
// let bytes = serde_json::to_vec(frame).unwrap();
|
// // let bytes = serde_json::to_vec(frame).unwrap();
|
||||||
// self.channel
|
// // self.channel
|
||||||
// .send_message(
|
// // .send_message(
|
||||||
// MessagePayload::Data(bytes.into_boxed_slice()),
|
// // MessagePayload::Data(bytes.into_boxed_slice()),
|
||||||
// MessageDestination::AllExceptSelf,
|
// // MessageDestination::AllExceptSelf,
|
||||||
// )
|
// // )
|
||||||
// .unwrap();
|
// // .unwrap();
|
||||||
// Ok(())
|
// // Ok(())
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ use yggdrasil_abi::{
|
|||||||
error::Error,
|
error::Error,
|
||||||
option::OptionValue,
|
option::OptionValue,
|
||||||
process::{
|
process::{
|
||||||
options::ProcessOptionVariant, ExitCode, ProcessGroupId, ProcessId, Signal,
|
options::ProcessOptionVariant, ExitCode, ProcessGroupId, ProcessId, Signal, ThreadEvent,
|
||||||
ThreadSpawnOptions, WaitFlags,
|
ThreadSpawnOptions, WaitFlags,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -73,8 +73,6 @@ pub struct ProcessInner {
|
|||||||
mutexes: BTreeMap<usize, Arc<UserspaceMutex>>,
|
mutexes: BTreeMap<usize, Arc<UserspaceMutex>>,
|
||||||
space: Option<Arc<ProcessAddressSpace>>,
|
space: Option<Arc<ProcessAddressSpace>>,
|
||||||
image: Option<ProcessImage>,
|
image: Option<ProcessImage>,
|
||||||
|
|
||||||
thread_exits: BTreeMap<ThreadId, Arc<BoolEvent>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes a process within the system
|
/// Describes a process within the system
|
||||||
@ -304,6 +302,12 @@ impl Process {
|
|||||||
ProcessOptionVariant::SignalEntry => {
|
ProcessOptionVariant::SignalEntry => {
|
||||||
options::SignalEntry::store(&self.signal_entry(), buffer)
|
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);
|
self.set_signal_entry(value);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
ProcessOptionVariant::MainThread => Err(Error::ReadOnly),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,18 +588,18 @@ impl Process {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_for_thread(&self, thread: ThreadId) -> Result<(), Error> {
|
pub async fn wait_for_thread(&self, thread: ThreadId) -> Result<ThreadEvent, Error> {
|
||||||
let exit = {
|
let thread = Thread::get(thread).ok_or(Error::ProcessNotFound)?;
|
||||||
let inner = self.inner.read();
|
|
||||||
inner
|
|
||||||
.thread_exits
|
|
||||||
.get(&thread)
|
|
||||||
.cloned()
|
|
||||||
.ok_or(Error::DoesNotExist)?
|
|
||||||
};
|
|
||||||
|
|
||||||
exit.wait().await;
|
// Check that the process is the thread's tracer or parent
|
||||||
Ok(())
|
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
|
/// 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);
|
log::debug!("Terminate thread {}", thread.id);
|
||||||
thread.terminate();
|
thread.terminate();
|
||||||
thread.exit.wait().await;
|
thread.events.exit.wait().await;
|
||||||
log::debug!("{} died", thread.id);
|
log::debug!("{} died", thread.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -647,13 +652,10 @@ impl ProcessInner {
|
|||||||
mutexes: BTreeMap::new(),
|
mutexes: BTreeMap::new(),
|
||||||
image,
|
image,
|
||||||
space: space.clone(),
|
space: space.clone(),
|
||||||
|
|
||||||
thread_exits: BTreeMap::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_thread(&mut self, thread: Arc<Thread>) {
|
pub fn register_thread(&mut self, thread: Arc<Thread>) {
|
||||||
self.thread_exits.insert(thread.id, thread.exit.clone());
|
|
||||||
self.threads.push(thread);
|
self.threads.push(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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::{
|
use alloc::{
|
||||||
collections::{btree_map, BTreeMap},
|
collections::BTreeMap,
|
||||||
string::String,
|
string::String,
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
};
|
};
|
||||||
@ -9,35 +16,39 @@ use crossbeam_queue::SegQueue;
|
|||||||
use futures_util::task::ArcWake;
|
use futures_util::task::ArcWake;
|
||||||
use kernel_arch::{
|
use kernel_arch::{
|
||||||
task::{Scheduler, TaskContext, TaskFrame},
|
task::{Scheduler, TaskContext, TaskFrame},
|
||||||
Architecture, ArchitectureImpl, CpuImpl,
|
CpuImpl,
|
||||||
};
|
};
|
||||||
use libk_mm::{process::ProcessAddressSpace, PageFaultKind};
|
use libk_mm::{process::ProcessAddressSpace, PageFaultKind};
|
||||||
use libk_util::{
|
use libk_util::{
|
||||||
event::BoolEvent,
|
event::BoolEvent,
|
||||||
sync::{spin_rwlock::IrqSafeRwLock, IrqGuard, IrqSafeSpinlock},
|
ring::LossyRingQueue,
|
||||||
|
sync::{spin_rwlock::IrqSafeRwLock, IrqGuard, IrqSafeSpinlock, IrqSafeSpinlockGuard},
|
||||||
};
|
};
|
||||||
use yggdrasil_abi::{
|
use yggdrasil_abi::{
|
||||||
arch::SavedFrame,
|
arch::SavedFrame,
|
||||||
debug::DebugFrame,
|
debug::{self, DebugControl, TraceEvent, TraceEventPayload, TraceFlags},
|
||||||
error::Error,
|
error::Error,
|
||||||
option::OptionValue,
|
option::{OptionValue, RequestValue},
|
||||||
process::{
|
process::{
|
||||||
thread::{ThreadOptionVariant, ThreadSignalStack},
|
thread::{ThreadOptionVariant, ThreadSignalStack},
|
||||||
ExitCode, ProcessId, Signal, SignalEntryData,
|
ExitCode, ProcessId, Signal, SignalEntryData, ThreadEvent,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::task::{
|
use crate::{
|
||||||
|
task::{
|
||||||
mem::ForeignPointer,
|
mem::ForeignPointer,
|
||||||
sched::CpuQueue,
|
sched::CpuQueue,
|
||||||
types::{ThreadAffinity, ThreadId, ThreadState},
|
types::{ThreadAffinity, ThreadId, ThreadState},
|
||||||
TaskContextImpl,
|
TaskContextImpl,
|
||||||
|
},
|
||||||
|
time::monotonic_time,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{debug::ThreadDebugger, process::Process};
|
use super::process::Process;
|
||||||
|
|
||||||
type BreakpointType = <ArchitectureImpl as Architecture>::BreakpointType;
|
// type BreakpointType = <ArchitectureImpl as Architecture>::BreakpointType;
|
||||||
const BREAKPOINT_VALUE: BreakpointType = ArchitectureImpl::BREAKPOINT_VALUE;
|
// const BREAKPOINT_VALUE: BreakpointType = ArchitectureImpl::BREAKPOINT_VALUE;
|
||||||
|
|
||||||
/// Provides details about how the thread is scheduled onto CPUs
|
/// Provides details about how the thread is scheduled onto CPUs
|
||||||
pub struct ThreadSchedulingInfo {
|
pub struct ThreadSchedulingInfo {
|
||||||
@ -50,18 +61,21 @@ pub struct ThreadSchedulingInfo {
|
|||||||
pub queue: Option<&'static CpuQueue>,
|
pub queue: Option<&'static CpuQueue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ThreadDebuggingInfo {
|
pub struct ThreadTracingInfo {
|
||||||
|
pub tracer: Option<ProcessId>,
|
||||||
pub single_step: bool,
|
pub single_step: bool,
|
||||||
pub restore_breakpoint: Option<usize>,
|
pub state: SavedFrame,
|
||||||
pub debugger: Option<ThreadDebugger>,
|
|
||||||
pub saved_frame: Option<SavedFrame>,
|
|
||||||
pub breakpoints: BTreeMap<usize, BreakpointType>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ThreadInfo {
|
pub struct ThreadInfo {
|
||||||
pub signal_stack: ThreadSignalStack,
|
pub signal_stack: ThreadSignalStack,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ThreadEvents {
|
||||||
|
pub exit: BoolEvent,
|
||||||
|
pub trace: LossyRingQueue<TraceEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Describes a single thread within the system
|
/// Describes a single thread within the system
|
||||||
pub struct Thread {
|
pub struct Thread {
|
||||||
/// Unique thread ID
|
/// Unique thread ID
|
||||||
@ -74,19 +88,25 @@ pub struct Thread {
|
|||||||
pub context: Cell<TaskContextImpl>,
|
pub context: Cell<TaskContextImpl>,
|
||||||
process: Option<ProcessId>,
|
process: Option<ProcessId>,
|
||||||
space: Option<Arc<ProcessAddressSpace>>,
|
space: Option<Arc<ProcessAddressSpace>>,
|
||||||
debug: IrqSafeSpinlock<ThreadDebuggingInfo>,
|
debug: IrqSafeSpinlock<ThreadTracingInfo>,
|
||||||
|
trace_flags: AtomicU32,
|
||||||
|
syscall_trace_seq: AtomicU64,
|
||||||
|
|
||||||
// inner: IrqSafeSpinlock<ThreadInner>,
|
// inner: IrqSafeSpinlock<ThreadInner>,
|
||||||
info: IrqSafeRwLock<ThreadInfo>,
|
info: IrqSafeRwLock<ThreadInfo>,
|
||||||
signal_queue: SegQueue<Signal>,
|
signal_queue: SegQueue<Signal>,
|
||||||
|
|
||||||
pub exit: Arc<BoolEvent>,
|
pub events: ThreadEvents,
|
||||||
pub kill: BoolEvent,
|
pub kill: BoolEvent,
|
||||||
|
|
||||||
/// CPU scheduling affinity mask
|
/// CPU scheduling affinity mask
|
||||||
pub affinity: ThreadAffinity,
|
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
|
/// Wrapper which guarantees the thread referred to is the current one on the current CPU
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct CurrentThread(Arc<Thread>, IrqGuard);
|
pub struct CurrentThread(Arc<Thread>, IrqGuard);
|
||||||
@ -121,23 +141,18 @@ impl Thread {
|
|||||||
in_queue: false,
|
in_queue: false,
|
||||||
queue: None,
|
queue: None,
|
||||||
}),
|
}),
|
||||||
// TODO lazy initialization for debugging info
|
debug: IrqSafeSpinlock::new(ThreadTracingInfo::default()),
|
||||||
debug: IrqSafeSpinlock::new(ThreadDebuggingInfo {
|
|
||||||
single_step: false,
|
|
||||||
restore_breakpoint: None,
|
|
||||||
debugger: None,
|
|
||||||
saved_frame: None,
|
|
||||||
breakpoints: BTreeMap::new(),
|
|
||||||
}),
|
|
||||||
context: Cell::new(context),
|
context: Cell::new(context),
|
||||||
process,
|
process,
|
||||||
space,
|
space,
|
||||||
|
trace_flags: AtomicU32::new(0),
|
||||||
|
syscall_trace_seq: AtomicU64::new(1),
|
||||||
|
|
||||||
info: IrqSafeRwLock::new(ThreadInfo {
|
info: IrqSafeRwLock::new(ThreadInfo {
|
||||||
signal_stack: ThreadSignalStack::default(),
|
signal_stack: ThreadSignalStack::default(),
|
||||||
}),
|
}),
|
||||||
signal_queue: SegQueue::new(),
|
signal_queue: SegQueue::new(),
|
||||||
exit: Arc::new(BoolEvent::new()),
|
events: ThreadEvents::new(),
|
||||||
kill: BoolEvent::new(),
|
kill: BoolEvent::new(),
|
||||||
|
|
||||||
affinity: ThreadAffinity::any_cpu(),
|
affinity: ThreadAffinity::any_cpu(),
|
||||||
@ -294,6 +309,11 @@ impl Thread {
|
|||||||
self.process.unwrap()
|
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
|
/// Returns the thread's parent process reference
|
||||||
pub fn try_get_process(&self) -> Option<Arc<Process>> {
|
pub fn try_get_process(&self) -> Option<Arc<Process>> {
|
||||||
self.process.and_then(Process::get)
|
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
|
/// Updates the thread's terminated status and wakes up any other threads waiting for it to
|
||||||
/// exit
|
/// exit
|
||||||
pub fn set_terminated(&self) {
|
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
|
// Signals
|
||||||
|
|
||||||
/// Pushes a signal to the thread's signal queue
|
/// Pushes a signal to the thread's signal queue
|
||||||
pub fn raise_signal(&self, signal: Signal) {
|
pub fn raise_signal(&self, signal: Signal) -> bool {
|
||||||
log::debug!("{}: raise signal {signal:?}", self.id);
|
let is_traced = self.debug.lock().tracer.is_some();
|
||||||
|
|
||||||
|
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.signal_queue.push(signal);
|
||||||
self.enqueue();
|
self.enqueue();
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) }?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resume(&self, single_step: bool) {
|
|
||||||
{
|
|
||||||
let mut debug = self.debug.lock();
|
|
||||||
|
|
||||||
debug.single_step = single_step;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.enqueue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scheduling
|
// Scheduling
|
||||||
@ -508,6 +489,108 @@ impl Thread {
|
|||||||
let strong = weak.upgrade()?;
|
let strong = weak.upgrade()?;
|
||||||
Some(CurrentThread(strong, guard))
|
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 {
|
impl GlobalThreadList {
|
||||||
@ -552,12 +635,49 @@ impl CurrentThread {
|
|||||||
Arc::downgrade(&self.0)
|
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
|
/// Terminate the current thread
|
||||||
pub fn exit(&self, code: ExitCode) -> ! {
|
pub fn exit(&self, code: ExitCode) -> ! {
|
||||||
// Can detach debugger now
|
// Can detach debugger now
|
||||||
let debug = self.debug.lock();
|
{
|
||||||
if let Some(debugger) = debug.debugger.as_ref() {
|
let mut debug = self.debug.lock();
|
||||||
debugger.send(&DebugFrame::Exited).ok();
|
debug.tracer = None;
|
||||||
|
debug.single_step = false;
|
||||||
|
self.trace_flags.store(0, Ordering::Release);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(process) = self.try_get_process() {
|
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 {
|
pub fn handle_single_step<F: TaskFrame>(&self, frame: &mut F) -> bool {
|
||||||
{
|
{
|
||||||
let mut debug = self.debug.lock();
|
let debug = self.debug.lock();
|
||||||
if debug.debugger.is_none() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
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 {
|
if !debug.single_step {
|
||||||
log::debug!("Clear single step ({} {:?})", self.id, *self.name.read());
|
|
||||||
frame.set_single_step(false);
|
frame.set_single_step(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame = frame.store();
|
self.trace_event(true, frame, TraceEventPayload::SingleStep);
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.suspend() {
|
match self.suspend() {
|
||||||
Ok(_) | Err(Error::Interrupted) => true,
|
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 {
|
pub fn handle_breakpoint<F: TaskFrame>(&self, _frame: &mut F) -> bool {
|
||||||
let mut debug = self.debug.lock();
|
|
||||||
let ip = frame.user_ip();
|
|
||||||
|
|
||||||
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) };
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
drop(debug);
|
|
||||||
|
|
||||||
self.suspend().unwrap();
|
|
||||||
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
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();
|
||||||
|
|
||||||
|
// // 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
|
||||||
|
// );
|
||||||
|
|
||||||
|
// // 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 debugger = debug.debugger.as_ref().unwrap();
|
||||||
|
// debugger.send(&DebugFrame::HitBreakpoint { frame }).ok();
|
||||||
|
|
||||||
|
// drop(debug);
|
||||||
|
|
||||||
|
// self.suspend().unwrap();
|
||||||
|
|
||||||
|
// true
|
||||||
|
// } else {
|
||||||
|
// false
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_page_fault(&self, address: usize, kind: PageFaultKind) -> Result<(), Error> {
|
pub fn handle_page_fault(&self, address: usize, kind: PageFaultKind) -> Result<(), Error> {
|
||||||
@ -697,19 +795,8 @@ impl CurrentThread {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if signal == Signal::Debug {
|
if signal == Signal::Debug {
|
||||||
frame.set_single_step(true);
|
// Debugger attached in runtime
|
||||||
|
todo!()
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ip = self.process().signal_entry();
|
let ip = self.process().signal_entry();
|
||||||
@ -766,27 +853,70 @@ impl Deref for CurrentThread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThreadDebuggingInfo {
|
// impl ThreadTracingInfo {
|
||||||
fn set_breakpoint_inner(
|
// fn set_breakpoint_inner(
|
||||||
&mut self,
|
// &mut self,
|
||||||
space: &ProcessAddressSpace,
|
// space: &ProcessAddressSpace,
|
||||||
address: usize,
|
// address: usize,
|
||||||
) -> Result<(), Error> {
|
// ) -> Result<(), Error> {
|
||||||
match self.breakpoints.entry(address) {
|
// match self.breakpoints.entry(address) {
|
||||||
btree_map::Entry::Vacant(vacant) => {
|
// btree_map::Entry::Vacant(vacant) => {
|
||||||
let pointer = address as *mut BreakpointType;
|
// 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
|
impl ThreadTracingInfo {
|
||||||
let original =
|
pub fn store_state<F: TaskFrame>(&mut self, frame: &F) {
|
||||||
unsafe { (pointer as *const BreakpointType).try_read_foreign_volatile(space) }?;
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -227,6 +227,7 @@ fn el0_sync_inner(frame: &mut ExceptionFrame) {
|
|||||||
let dump = match ec {
|
let dump = match ec {
|
||||||
// SVC in AArch64
|
// SVC in AArch64
|
||||||
0b010101 => {
|
0b010101 => {
|
||||||
|
let seq = Thread::current().trace_syscall_entry(frame);
|
||||||
let func = frame.r[8];
|
let func = frame.r[8];
|
||||||
if func == usize::from(SyscallFunction::ExitSignal) {
|
if func == usize::from(SyscallFunction::ExitSignal) {
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -238,6 +239,7 @@ fn el0_sync_inner(frame: &mut ExceptionFrame) {
|
|||||||
let args = &frame.r[0..6];
|
let args = &frame.r[0..6];
|
||||||
let result = raw_syscall_handler(func, args) as _;
|
let result = raw_syscall_handler(func, args) as _;
|
||||||
frame.r[0] = result;
|
frame.r[0] = result;
|
||||||
|
Thread::current().trace_syscall_exit(frame, seq);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
// Software Step from lower Exception Level
|
// Software Step from lower Exception Level
|
||||||
|
@ -95,6 +95,7 @@ unsafe fn umode_exception_handler(frame: &mut TrapFrame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Cause::EcallUmode) => {
|
Some(Cause::EcallUmode) => {
|
||||||
|
let seq = Thread::current().trace_syscall_entry(frame);
|
||||||
let func = frame.an[0];
|
let func = frame.an[0];
|
||||||
if func == usize::from(SyscallFunction::ExitSignal) {
|
if func == usize::from(SyscallFunction::ExitSignal) {
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -108,6 +109,7 @@ unsafe fn umode_exception_handler(frame: &mut TrapFrame) {
|
|||||||
mem::tlb_flush_full();
|
mem::tlb_flush_full();
|
||||||
frame.an[0] = result;
|
frame.an[0] = result;
|
||||||
frame.sepc += 4;
|
frame.sepc += 4;
|
||||||
|
Thread::current().trace_syscall_exit(frame, seq);
|
||||||
(false, false)
|
(false, false)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -11,6 +11,8 @@ use tock_registers::interfaces::{ReadWriteable, Writeable};
|
|||||||
use crate::syscall::{self, raw_syscall_handler};
|
use crate::syscall::{self, raw_syscall_handler};
|
||||||
|
|
||||||
fn syscall_inner(frame: &mut SyscallFrame) {
|
fn syscall_inner(frame: &mut SyscallFrame) {
|
||||||
|
let trace_seq = Thread::current().trace_syscall_entry(frame);
|
||||||
|
|
||||||
if frame.rax == usize::from(SyscallFunction::ExitSignal) {
|
if frame.rax == usize::from(SyscallFunction::ExitSignal) {
|
||||||
unsafe {
|
unsafe {
|
||||||
syscall::handle_signal_exit(frame);
|
syscall::handle_signal_exit(frame);
|
||||||
@ -31,6 +33,9 @@ fn syscall_inner(frame: &mut SyscallFrame) {
|
|||||||
let result = raw_syscall_handler(frame.rax, &frame.args);
|
let result = raw_syscall_handler(frame.rax, &frame.args);
|
||||||
|
|
||||||
frame.rax = result as _;
|
frame.rax = result as _;
|
||||||
|
|
||||||
|
Thread::current().trace_syscall_exit(frame, trace_seq);
|
||||||
|
|
||||||
if code == usize::from(SyscallFunction::CreateDirectory) {
|
if code == usize::from(SyscallFunction::CreateDirectory) {
|
||||||
log::debug!("frame out = {frame:#x?}");
|
log::debug!("frame out = {frame:#x?}");
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
use abi::{
|
use abi::{debug::TraceLevel, error::Error};
|
||||||
debug::{DebugOperation, TraceLevel},
|
|
||||||
error::Error,
|
|
||||||
process::ProcessId,
|
|
||||||
};
|
|
||||||
use libk::{
|
use libk::{
|
||||||
debug,
|
debug,
|
||||||
task::{process::Process, thread::Thread},
|
task::{thread::Thread, ThreadId},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) fn debug_trace(level: TraceLevel, message: &str) {
|
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);
|
debug::program_trace(&process, &thread, level, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn debug_control(pid: ProcessId, op: &mut DebugOperation) -> Result<(), Error> {
|
pub(crate) fn debug_control(
|
||||||
let target = Process::get(pid).ok_or(Error::DoesNotExist)?;
|
tid: u32,
|
||||||
let target_thread = target.as_single_thread().unwrap();
|
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 {
|
if !tracee.is_tracee_of(process.id) {
|
||||||
&mut DebugOperation::Continue(single_step) => {
|
log::warn!("{}: {} is not our tracee", process.id, tracee.id);
|
||||||
// TODO check if it's paused currently
|
return Err(Error::PermissionDenied);
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tracee.debug_control(option, buffer, len)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use abi::{
|
|||||||
process::{
|
process::{
|
||||||
options::ProcessOptionVariant, thread::ThreadOptionVariant, ExitCode, MutexOperation,
|
options::ProcessOptionVariant, thread::ThreadOptionVariant, ExitCode, MutexOperation,
|
||||||
ProcessGroupId, ProcessId, ProcessWait, Signal, SpawnFlags, SpawnOption, SpawnOptions,
|
ProcessGroupId, ProcessId, ProcessWait, Signal, SpawnFlags, SpawnOption, SpawnOptions,
|
||||||
ThreadSpawnOptions, WaitFlags,
|
ThreadEvent, ThreadSpawnOptions, WaitFlags,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
@ -91,6 +91,10 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Err
|
|||||||
let process = thread.process();
|
let process = thread.process();
|
||||||
|
|
||||||
let result = run_with_io(&process, |mut io| {
|
let result = run_with_io(&process, |mut io| {
|
||||||
|
let attach_trace = options
|
||||||
|
.optional
|
||||||
|
.iter()
|
||||||
|
.any(|opt| matches!(opt, SpawnOption::AttachTrace));
|
||||||
// let attach_debugger = options
|
// let attach_debugger = options
|
||||||
// .optional
|
// .optional
|
||||||
// .iter()
|
// .iter()
|
||||||
@ -112,8 +116,7 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Err
|
|||||||
path: options.program,
|
path: options.program,
|
||||||
args: options.arguments,
|
args: options.arguments,
|
||||||
envs: options.arguments,
|
envs: options.arguments,
|
||||||
single_step: false,
|
single_step: attach_trace,
|
||||||
// single_step: attach_debugger.is_some(),
|
|
||||||
disable_aslr: options.flags.contains(SpawnFlags::DISABLE_ASLR),
|
disable_aslr: options.flags.contains(SpawnFlags::DISABLE_ASLR),
|
||||||
};
|
};
|
||||||
let (child_process, child_main) = proc::load_binary(io.ioctx_mut(), &load_options)?;
|
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);
|
drop(child_io);
|
||||||
|
|
||||||
child_main.enqueue();
|
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 {
|
// if let Some(debugger) = attach_debugger {
|
||||||
// child_main.attach_debugger(ThreadDebugger::new(debugger));
|
// child_main.attach_debugger(ThreadDebugger::new(debugger));
|
||||||
// } else {
|
// } else {
|
||||||
@ -315,12 +323,14 @@ pub(crate) fn exit_thread() -> ! {
|
|||||||
thread.exit(ExitCode::SUCCESS)
|
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 tid = ThreadId::User(id as u64);
|
||||||
let this_thread = Thread::current();
|
let this_thread = Thread::current();
|
||||||
let process = this_thread.process();
|
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> {
|
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)
|
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 option = ProcessOptionVariant::try_from(option)?;
|
||||||
let thread = Thread::current();
|
let thread = Thread::current();
|
||||||
let process = thread.process();
|
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)
|
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 option = ProcessOptionVariant::try_from(option)?;
|
||||||
let thread = Thread::current();
|
let thread = Thread::current();
|
||||||
let process = thread.process();
|
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)
|
process.set_option(option, buffer)
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use core::mem::MaybeUninit;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
des::{DeserializeError, Deserializer},
|
des::{DeserializeError, Deserializer},
|
||||||
ser::Serializer,
|
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] {
|
impl Serialize for &[u8] {
|
||||||
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
||||||
serializer.write_bytes(self)
|
serializer.write_bytes(self)
|
||||||
@ -75,26 +96,26 @@ impl<'de> Deserialize<'de> for &'de str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Serialize for [u8; N]
|
// impl<const N: usize> Serialize for [u8; N]
|
||||||
where
|
// where
|
||||||
[u8; N]: Sized,
|
// [u8; N]: Sized,
|
||||||
{
|
// {
|
||||||
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
// fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
||||||
serializer.write_bytes(self)
|
// serializer.write_bytes(self)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
impl<'de, const N: usize> Deserialize<'de> for [u8; N]
|
// impl<'de, const N: usize> Deserialize<'de> for [u8; N]
|
||||||
where
|
// where
|
||||||
[u8; N]: Sized,
|
// [u8; N]: Sized,
|
||||||
{
|
// {
|
||||||
fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
|
// fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
|
||||||
deserializer
|
// deserializer
|
||||||
.read_bytes()?
|
// .read_bytes()?
|
||||||
.try_into()
|
// .try_into()
|
||||||
.map_err(|_| D::Error::INVALID_ARRAY_LEN)
|
// .map_err(|_| D::Error::INVALID_ARRAY_LEN)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl<T: Serialize> Serialize for Option<T> {
|
impl<T: Serialize> Serialize for Option<T> {
|
||||||
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#![feature(decl_macro, ip_from)]
|
#![feature(decl_macro, ip_from, maybe_uninit_array_assume_init)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
#[cfg(not(feature = "rustc-dep-of-std"))]
|
#[cfg(not(feature = "rustc-dep-of-std"))]
|
||||||
|
@ -22,6 +22,9 @@ abi-lib = { path = "../../lib/abi-lib" }
|
|||||||
abi-generator = { path = "../../tool/abi-generator" }
|
abi-generator = { path = "../../tool/abi-generator" }
|
||||||
prettyplease = "0.2.15"
|
prettyplease = "0.2.15"
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(rust_analyzer)'] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
alloc = []
|
alloc = []
|
||||||
|
@ -17,7 +17,7 @@ enum Signal(u32) {
|
|||||||
Terminated = 7,
|
Terminated = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
newtype ProcessId(u32);
|
newtype ProcessId(u31);
|
||||||
newtype ProcessGroupId(u32);
|
newtype ProcessGroupId(u32);
|
||||||
newtype ThreadId(u32);
|
newtype ThreadId(u32);
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ extern {
|
|||||||
type MutexOperation = yggdrasil_abi::process::MutexOperation;
|
type MutexOperation = yggdrasil_abi::process::MutexOperation;
|
||||||
#[thin]
|
#[thin]
|
||||||
type ExitCode = yggdrasil_abi::process::ExitCode;
|
type ExitCode = yggdrasil_abi::process::ExitCode;
|
||||||
|
#[thin]
|
||||||
|
type ThreadEvent = yggdrasil_abi::process::ThreadEvent;
|
||||||
type ProcessWait = yggdrasil_abi::process::ProcessWait;
|
type ProcessWait = yggdrasil_abi::process::ProcessWait;
|
||||||
|
|
||||||
type FileMetadataUpdate = yggdrasil_abi::io::FileMetadataUpdate;
|
type FileMetadataUpdate = yggdrasil_abi::io::FileMetadataUpdate;
|
||||||
@ -102,11 +104,11 @@ syscall get_tid() -> u32;
|
|||||||
// TODO use ThreadId
|
// TODO use ThreadId
|
||||||
syscall spawn_thread(options: &ThreadSpawnOptions) -> Result<u32>;
|
syscall spawn_thread(options: &ThreadSpawnOptions) -> Result<u32>;
|
||||||
syscall exit_thread() -> !;
|
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 get_thread_option(option: u32, value: &mut [u8]) -> Result<usize>;
|
||||||
syscall set_thread_option(option: u32, value: &[u8]) -> Result<()>;
|
syscall set_thread_option(option: u32, value: &[u8]) -> Result<()>;
|
||||||
syscall get_process_option(option: u32, value: &mut [u8]) -> Result<usize>;
|
syscall get_process_option(process: Option<ProcessId>, option: u32, value: &mut [u8]) -> Result<usize>;
|
||||||
syscall set_process_option(option: u32, value: &[u8]) -> Result<()>;
|
syscall set_process_option(process: Option<ProcessId>, option: u32, value: &[u8]) -> Result<()>;
|
||||||
|
|
||||||
syscall nanosleep(duration: &Duration, remaining: &mut MaybeUninit<Duration>) -> Result<()>;
|
syscall nanosleep(duration: &Duration, remaining: &mut MaybeUninit<Duration>) -> Result<()>;
|
||||||
|
|
||||||
@ -182,4 +184,4 @@ syscall execve(options: &ExecveOptions<'_>) -> Result<()>;
|
|||||||
|
|
||||||
// Debugging
|
// Debugging
|
||||||
syscall debug_trace(level: TraceLevel, message: &str);
|
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>;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
use abi_serde::impl_struct_serde;
|
||||||
|
|
||||||
use super::FrameOps;
|
use super::FrameOps;
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
@ -22,3 +24,11 @@ impl FrameOps for SavedFrame {
|
|||||||
self.elr_el1 as _
|
self.elr_el1 as _
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_struct_serde!(SavedFrame: [
|
||||||
|
gp_regs,
|
||||||
|
spsr_el1,
|
||||||
|
elr_el1,
|
||||||
|
sp_el0,
|
||||||
|
mdscr_el1,
|
||||||
|
]);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
use abi_serde::impl_struct_serde;
|
||||||
|
|
||||||
use super::FrameOps;
|
use super::FrameOps;
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
@ -28,3 +30,17 @@ impl FrameOps for SavedFrame {
|
|||||||
self.user_ip as _
|
self.user_ip as _
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_struct_serde!(SavedFrame: [
|
||||||
|
eax,
|
||||||
|
ecx,
|
||||||
|
edx,
|
||||||
|
ebx,
|
||||||
|
ebp,
|
||||||
|
esi,
|
||||||
|
edi,
|
||||||
|
|
||||||
|
user_ip,
|
||||||
|
user_sp,
|
||||||
|
eflags,
|
||||||
|
]);
|
||||||
|
@ -2,24 +2,24 @@
|
|||||||
|
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
|
||||||
pub(crate) mod aarch64;
|
pub(crate) mod aarch64;
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
|
||||||
use aarch64 as arch_impl;
|
use aarch64 as arch_impl;
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
|
||||||
pub(crate) mod x86_64;
|
pub(crate) mod x86_64;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
|
||||||
use x86_64 as arch_impl;
|
use x86_64 as arch_impl;
|
||||||
|
|
||||||
#[cfg(target_arch = "x86")]
|
#[cfg(any(target_arch = "x86", rust_analyzer))]
|
||||||
pub(crate) mod i686;
|
pub(crate) mod i686;
|
||||||
#[cfg(target_arch = "x86")]
|
#[cfg(any(target_arch = "x86", rust_analyzer))]
|
||||||
use i686 as arch_impl;
|
use i686 as arch_impl;
|
||||||
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
#[cfg(any(target_arch = "riscv64", rust_analyzer))]
|
||||||
pub(crate) mod riscv64;
|
pub(crate) mod riscv64;
|
||||||
#[cfg(target_arch = "riscv64")]
|
#[cfg(any(target_arch = "riscv64", rust_analyzer))]
|
||||||
use riscv64 as arch_impl;
|
use riscv64 as arch_impl;
|
||||||
|
|
||||||
pub use arch_impl::SavedFrame;
|
pub use arch_impl::SavedFrame;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
use abi_serde::impl_struct_serde;
|
||||||
|
|
||||||
use super::FrameOps;
|
use super::FrameOps;
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
@ -28,3 +30,14 @@ impl FrameOps for SavedFrame {
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_struct_serde!(SavedFrame: [
|
||||||
|
ra,
|
||||||
|
gp,
|
||||||
|
tn,
|
||||||
|
sn,
|
||||||
|
an,
|
||||||
|
sp,
|
||||||
|
ip,
|
||||||
|
tp,
|
||||||
|
]);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
use abi_serde::impl_struct_serde;
|
||||||
|
|
||||||
use super::FrameOps;
|
use super::FrameOps;
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
@ -37,3 +39,26 @@ impl FrameOps for SavedFrame {
|
|||||||
self.user_ip as _
|
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
|
||||||
|
]);
|
||||||
|
@ -1,63 +1,81 @@
|
|||||||
//! Debugging functionality
|
//! 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;
|
pub use crate::generated::TraceLevel;
|
||||||
|
use crate::{arch::SavedFrame, bitflags, time::SystemTime};
|
||||||
|
|
||||||
/// Describes a debugger operation
|
// TODO SetSignal to send signals from the tracer to the tracee
|
||||||
#[derive(Debug)]
|
request_group!(
|
||||||
pub enum DebugOperation<'a> {
|
#[doc = "Debugging/tracing operation on a thread"]
|
||||||
/// Attach to a given process
|
pub enum DebugControl<'de> {
|
||||||
Attach(RawFd),
|
/// Attach to a tracee, stopping it
|
||||||
/// Detach from the current process
|
0x1000: Attach((), ()),
|
||||||
Detach,
|
/// 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
|
/// A request to write into tracee's memory space
|
||||||
Interrupt,
|
#[derive(Debug, Clone, Copy)]
|
||||||
/// Continue the debugee, `Continue(true)` represents a "single step"
|
pub struct WriteTraceeMemory {
|
||||||
Continue(bool),
|
/// Address to write to, must be `usize`-aligned
|
||||||
/// Set a breakpoint at a given address
|
pub address: usize,
|
||||||
SetBreakpoint(usize),
|
/// Value to write
|
||||||
|
pub value: usize,
|
||||||
|
}
|
||||||
|
impl_struct_serde!(WriteTraceeMemory: [
|
||||||
|
address,
|
||||||
|
value
|
||||||
|
]);
|
||||||
|
|
||||||
/// Read data from the debugee memory
|
bitflags! {
|
||||||
ReadMemory {
|
/// Defines which trace events to record
|
||||||
/// Address to read the data from
|
pub struct TraceFlags: u32 {
|
||||||
address: usize,
|
/// Trace system call entries
|
||||||
/// Buffer to place the result into
|
const SYSCALL_ENTRY: bit 0;
|
||||||
buffer: &'a mut [u8],
|
/// Trace system call exits
|
||||||
},
|
const SYSCALL_EXIT: bit 1;
|
||||||
/// Write data to the debugee memory
|
}
|
||||||
WriteMemory {
|
|
||||||
/// Address to write the data to
|
|
||||||
address: usize,
|
|
||||||
/// Buffer to read the data from
|
|
||||||
buffer: &'a [u8],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a debug event
|
#[derive(Debug, Clone, Copy)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[repr(C)]
|
||||||
#[derive(Debug)]
|
pub enum TraceEventPayload {
|
||||||
pub enum DebugFrame {
|
SingleStep,
|
||||||
/// The debugee has started
|
SyscallEntry(u64),
|
||||||
Startup {
|
SyscallExit(u64),
|
||||||
/// 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 struct TraceEvent {
|
||||||
|
pub suspend: bool,
|
||||||
|
pub timestamp: SystemTime,
|
||||||
|
pub payload: TraceEventPayload,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_newtype_serde!(TraceFlags);
|
||||||
|
@ -20,6 +20,7 @@ extern crate rustc_std_alloc as alloc;
|
|||||||
pub(crate) mod macros;
|
pub(crate) mod macros;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
|
pub use abi_lib;
|
||||||
pub use abi_serde;
|
pub use abi_serde;
|
||||||
|
|
||||||
mod generated {
|
mod generated {
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
use core::num::NonZeroI32;
|
use core::num::NonZeroI32;
|
||||||
|
|
||||||
use abi_lib::SyscallRegister;
|
use abi_lib::SyscallRegister;
|
||||||
|
use abi_serde::{
|
||||||
|
des::{DeserializeError, Deserializer},
|
||||||
|
ser::Serializer,
|
||||||
|
Deserialize, Serialize,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::debug::TraceEvent;
|
||||||
|
|
||||||
use super::Signal;
|
use super::Signal;
|
||||||
|
|
||||||
@ -14,6 +21,16 @@ pub enum ExitCode {
|
|||||||
BySignal(Result<Signal, u32>),
|
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 {
|
impl ExitCode {
|
||||||
/// Returned when a process has exited successfully
|
/// Returned when a process has exited successfully
|
||||||
pub const SUCCESS: Self = Self::Exited(0);
|
pub const SUCCESS: Self = Self::Exited(0);
|
||||||
@ -70,3 +87,28 @@ impl SyscallRegister for ExitCode {
|
|||||||
Self::from(value as i32)
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,7 +12,12 @@ pub use crate::generated::{
|
|||||||
ExecveOptions, ProcessGroupId, ProcessId, Signal, SignalEntryData, SpawnFlags, SpawnOptions,
|
ExecveOptions, ProcessGroupId, ProcessId, Signal, SignalEntryData, SpawnFlags, SpawnOptions,
|
||||||
ThreadId, ThreadSpawnOptions, WaitFlags,
|
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
|
// TODO this is ugly
|
||||||
pub mod auxv {
|
pub mod auxv {
|
||||||
@ -64,9 +69,10 @@ pub enum SpawnOption {
|
|||||||
SetProcessGroup(ProcessGroupId),
|
SetProcessGroup(ProcessGroupId),
|
||||||
/// Gain terminal control for the given FD
|
/// Gain terminal control for the given FD
|
||||||
GainTerminal(RawFd),
|
GainTerminal(RawFd),
|
||||||
/// Attach debugging to a channel in parent's I/O context. The process will start in
|
/// Attach tracing to the spawned process. This is equivalent to issuing a `debug_control` with
|
||||||
/// single-stepping mode
|
/// `DebugControl::Attach` to the newly spawned process.
|
||||||
AttachDebug(RawFd),
|
/// The newly spawned process will have its main thread paused.
|
||||||
|
AttachTrace,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes a single mutex operation
|
/// Describes a single mutex operation
|
||||||
@ -227,3 +233,27 @@ impl Signal {
|
|||||||
|
|
||||||
abi_serde::impl_newtype_serde!(ProcessGroupId);
|
abi_serde::impl_newtype_serde!(ProcessGroupId);
|
||||||
abi_serde::impl_newtype_serde!(ProcessId);
|
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) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
//! Process option definitions
|
//! Process option definitions
|
||||||
|
|
||||||
|
use super::ThreadId;
|
||||||
|
|
||||||
option_group!(
|
option_group!(
|
||||||
#[doc = "Process options"]
|
#[doc = "Process options"]
|
||||||
pub enum ProcessOptionVariant<'a> {
|
pub enum ProcessOptionVariant<'a> {
|
||||||
@ -8,6 +10,8 @@ option_group!(
|
|||||||
#[doc = "Current working directory"]
|
#[doc = "Current working directory"]
|
||||||
0x1001: Directory(&'a str),
|
0x1001: Directory(&'a str),
|
||||||
#[doc = "Process name"]
|
#[doc = "Process name"]
|
||||||
0x1002: Name(&'a str)
|
0x1002: Name(&'a str),
|
||||||
|
#[doc = "Main thread ID"]
|
||||||
|
0x1003: MainThread # 4 (ThreadId),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
use core::fmt;
|
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
|
/// Prints a debug message using DebugTrace syscall
|
||||||
#[macro_export]
|
#[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])
|
||||||
|
}
|
||||||
|
@ -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> {
|
pub fn current_directory<T, F: FnOnce(&str) -> T>(mapper: F) -> Result<T, Error> {
|
||||||
let mut buffer = [0; 512];
|
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))
|
Ok(mapper(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ pub fn current_directory_string() -> Result<String, Error> {
|
|||||||
|
|
||||||
pub fn set_current_directory(path: &str) -> Result<(), Error> {
|
pub fn set_current_directory(path: &str) -> Result<(), Error> {
|
||||||
let mut buffer = [0; 512];
|
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> {
|
pub fn make_temp_directory(_template: &mut [u8]) -> Result<(), Error> {
|
||||||
|
@ -5,7 +5,8 @@ use core::{mem::MaybeUninit, time::Duration};
|
|||||||
pub use abi::process::{
|
pub use abi::process::{
|
||||||
auxv, AuxValue, AuxValueIter, ExecveOptions, ExitCode, MutexOperation, ProcessGroupId,
|
auxv, AuxValue, AuxValueIter, ExecveOptions, ExitCode, MutexOperation, ProcessGroupId,
|
||||||
ProcessId, ProcessInfoElement, ProcessWait, ProgramArgumentInner, Signal, SignalEntryData,
|
ProcessId, ProcessInfoElement, ProcessWait, ProgramArgumentInner, Signal, SignalEntryData,
|
||||||
SpawnFlags, SpawnOption, SpawnOptions, StringArgIter, ThreadId, ThreadSpawnOptions, WaitFlags,
|
SpawnFlags, SpawnOption, SpawnOptions, StringArgIter, ThreadEvent, ThreadId,
|
||||||
|
ThreadSpawnOptions, WaitFlags,
|
||||||
};
|
};
|
||||||
use abi::{
|
use abi::{
|
||||||
error::Error,
|
error::Error,
|
||||||
@ -46,16 +47,17 @@ pub fn uninterruptible_sleep(mut duration: Duration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper macro for [get_process_option].
|
/// 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];
|
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.
|
/// Retrieves a process option value.
|
||||||
pub fn get_process_option<'de, T: OptionValue<'de>>(
|
pub fn get_process_option<'de, T: OptionValue<'de>>(
|
||||||
|
process: Option<ProcessId>,
|
||||||
buffer: &'de mut [u8],
|
buffer: &'de mut [u8],
|
||||||
) -> Result<T::Value, Error> {
|
) -> 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])
|
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].
|
/// Update a process option value. Requires `T`: [OptionSizeHint].
|
||||||
pub fn set_process_option<'de, T: OptionValue<'de> + OptionSizeHint>(
|
pub fn set_process_option<'de, T: OptionValue<'de> + OptionSizeHint>(
|
||||||
|
process: Option<ProcessId>,
|
||||||
value: &T::Value,
|
value: &T::Value,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
[u8; T::SIZE_HINT]: Sized,
|
[u8; T::SIZE_HINT]: Sized,
|
||||||
{
|
{
|
||||||
let mut buffer = [0; T::SIZE_HINT];
|
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.
|
/// Update a process option value, using provided buffer for serialization.
|
||||||
pub fn set_process_option_with<'de, T: OptionValue<'de>>(
|
pub fn set_process_option_with<'de, T: OptionValue<'de>>(
|
||||||
|
process: Option<ProcessId>,
|
||||||
buffer: &mut [u8],
|
buffer: &mut [u8],
|
||||||
value: &T::Value,
|
value: &T::Value,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let len = T::store(value, buffer)?;
|
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].
|
/// Update a thread option value. Requires `T`: [OptionSizeHint].
|
||||||
|
@ -102,7 +102,7 @@ pub fn get_signal_stack() -> (usize, usize) {
|
|||||||
/// 1. It must not return, it must call `exit_thread` instead.
|
/// 1. It must not return, it must call `exit_thread` instead.
|
||||||
/// 2. It must conform to the kernel's signal entry ABI.
|
/// 2. It must conform to the kernel's signal entry ABI.
|
||||||
pub unsafe fn set_signal_entry(entry: usize) {
|
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");
|
.expect("set_signal_entry() failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Runtime utilities for thread handling
|
//! Runtime utilities for thread handling
|
||||||
|
|
||||||
use core::{
|
use core::{
|
||||||
|
mem::MaybeUninit,
|
||||||
ptr,
|
ptr,
|
||||||
sync::atomic::{AtomicU32, Ordering},
|
sync::atomic::{AtomicU32, Ordering},
|
||||||
};
|
};
|
||||||
@ -8,7 +9,7 @@ use core::{
|
|||||||
use abi::{
|
use abi::{
|
||||||
error::Error,
|
error::Error,
|
||||||
mem::{MappingFlags, MappingSource},
|
mem::{MappingFlags, MappingSource},
|
||||||
process::ThreadSpawnOptions,
|
process::{ThreadEvent, ThreadSpawnOptions},
|
||||||
};
|
};
|
||||||
use alloc::{boxed::Box, sync::Arc};
|
use alloc::{boxed::Box, sync::Arc};
|
||||||
|
|
||||||
@ -219,7 +220,14 @@ impl<R> ThreadHandle<R> {
|
|||||||
/// Waits for the thread to finish and returns its result.
|
/// Waits for the thread to finish and returns its result.
|
||||||
pub fn join(self) -> Result<Option<R>, Error> {
|
pub fn join(self) -> Result<Option<R>, Error> {
|
||||||
// TODO prevent threads from attempting to join themselves?
|
// 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())
|
Ok(self.into_result())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,8 +238,16 @@ impl<R> ThreadHandle<R> {
|
|||||||
/// Will panic if the kernel returns any error besides [Error::Interrupted].
|
/// Will panic if the kernel returns any error besides [Error::Interrupted].
|
||||||
pub fn join_uninterruptible(self) -> Option<R> {
|
pub fn join_uninterruptible(self) -> Option<R> {
|
||||||
loop {
|
loop {
|
||||||
match unsafe { crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire)) } {
|
let mut event = MaybeUninit::uninit();
|
||||||
Ok(_) => (),
|
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::Interrupted) => continue,
|
||||||
Err(error) => panic!("wait_thread syscall returned error: {error:?}"),
|
Err(error) => panic!("wait_thread syscall returned error: {error:?}"),
|
||||||
}
|
}
|
||||||
|
@ -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.
|
/// `value` must hold an address to a structure, first element of which is a pointer to itself.
|
||||||
/// Usual pointer safety requirements apply.
|
/// Usual pointer safety requirements apply.
|
||||||
pub unsafe fn set_thread_pointer(value: usize) -> Result<(), Error> {
|
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
|
// ___tls_get_addr, TLS_index structure address gets passed in the %eax register
|
||||||
|
10
userspace/Cargo.lock
generated
10
userspace/Cargo.lock
generated
@ -2278,6 +2278,7 @@ dependencies = [
|
|||||||
name = "runtime"
|
name = "runtime"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"abi-lib",
|
||||||
"abi-serde",
|
"abi-serde",
|
||||||
"yggdrasil-abi",
|
"yggdrasil-abi",
|
||||||
"yggdrasil-rt",
|
"yggdrasil-rt",
|
||||||
@ -2589,6 +2590,14 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strace"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"runtime",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strict-num"
|
name = "strict-num"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -2656,6 +2665,7 @@ dependencies = [
|
|||||||
"logsink",
|
"logsink",
|
||||||
"pci-ids",
|
"pci-ids",
|
||||||
"rand 0.9.0-alpha.1",
|
"rand 0.9.0-alpha.1",
|
||||||
|
"runtime",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
@ -20,7 +20,8 @@ members = [
|
|||||||
"lib/runtime",
|
"lib/runtime",
|
||||||
"lib/uipc",
|
"lib/uipc",
|
||||||
"lib/logsink",
|
"lib/logsink",
|
||||||
"lib/libpsf"
|
"lib/libpsf",
|
||||||
|
"strace"
|
||||||
]
|
]
|
||||||
exclude = ["dynload-program", "test-kernel-module", "lib/ygglibc"]
|
exclude = ["dynload-program", "test-kernel-module", "lib/ygglibc"]
|
||||||
|
|
||||||
@ -57,6 +58,7 @@ uipc.path = "lib/uipc"
|
|||||||
yggdrasil-rt.path = "../lib/runtime"
|
yggdrasil-rt.path = "../lib/runtime"
|
||||||
yggdrasil-abi = { path = "../lib/abi", features = ["serde", "alloc", "bytemuck"] }
|
yggdrasil-abi = { path = "../lib/abi", features = ["serde", "alloc", "bytemuck"] }
|
||||||
abi-serde = { path = "../lib/abi-serde" }
|
abi-serde = { path = "../lib/abi-serde" }
|
||||||
|
abi-lib = { path = "../lib/abi-lib" }
|
||||||
logsink.path = "lib/logsink"
|
logsink.path = "lib/logsink"
|
||||||
libutil.path = "../lib/libutil"
|
libutil.path = "../lib/libutil"
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ edition = "2021"
|
|||||||
yggdrasil-abi = { workspace = true, features = ["alloc", "bytemuck"] }
|
yggdrasil-abi = { workspace = true, features = ["alloc", "bytemuck"] }
|
||||||
yggdrasil-rt.workspace = true
|
yggdrasil-rt.workspace = true
|
||||||
abi-serde.workspace = true
|
abi-serde.workspace = true
|
||||||
|
abi-lib.workspace = true
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
@ -6,7 +6,10 @@ pub use yggdrasil_rt as rt;
|
|||||||
pub use yggdrasil_abi as abi;
|
pub use yggdrasil_abi as abi;
|
||||||
#[cfg(rust_analyzer)]
|
#[cfg(rust_analyzer)]
|
||||||
pub use abi_serde;
|
pub use abi_serde;
|
||||||
|
#[cfg(rust_analyzer)]
|
||||||
|
pub use abi_lib;
|
||||||
|
|
||||||
pub use std::os::yggdrasil::rt;
|
pub use std::os::yggdrasil::rt;
|
||||||
pub use std::os::yggdrasil::rt::abi;
|
pub use std::os::yggdrasil::rt::abi;
|
||||||
pub use std::os::yggdrasil::rt::abi::abi_serde;
|
pub use std::os::yggdrasil::rt::abi::abi_serde;
|
||||||
|
pub use std::os::yggdrasil::rt::abi::abi_lib;
|
||||||
|
12
userspace/strace/Cargo.toml
Normal file
12
userspace/strace/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "strace"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
runtime.workspace = true
|
||||||
|
|
||||||
|
clap.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
383
userspace/strace/src/format.rs
Normal file
383
userspace/strace/src/format.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
36
userspace/strace/src/main.rs
Normal file
36
userspace/strace/src/main.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
233
userspace/strace/src/tracer.rs
Normal file
233
userspace/strace/src/tracer.rs
Normal 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, ®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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ yggdrasil-rt.workspace = true
|
|||||||
cross.workspace = true
|
cross.workspace = true
|
||||||
logsink.workspace = true
|
logsink.workspace = true
|
||||||
libutil.workspace = true
|
libutil.workspace = true
|
||||||
|
runtime.workspace = true
|
||||||
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
|
@ -1,14 +1 @@
|
|||||||
#![feature(seek_stream_len)]
|
fn main() {}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
@ -28,6 +28,7 @@ const PROGRAMS: &[(&str, &str)] = &[
|
|||||||
// sysutils
|
// sysutils
|
||||||
("mount", "sbin/mount"),
|
("mount", "sbin/mount"),
|
||||||
("login", "sbin/login"),
|
("login", "sbin/login"),
|
||||||
|
("strace", "bin/strace"),
|
||||||
("ls", "bin/ls"),
|
("ls", "bin/ls"),
|
||||||
("mv", "bin/mv"),
|
("mv", "bin/mv"),
|
||||||
("ln", "bin/ln"),
|
("ln", "bin/ln"),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user