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 {}
|
||||
|
||||
impl ThreadDebugger {
|
||||
@ -10,16 +8,16 @@ impl ThreadDebugger {
|
||||
// Self { channel }
|
||||
// }
|
||||
|
||||
pub fn send(&self, frame: &DebugFrame) -> Result<(), Error> {
|
||||
let _ = frame;
|
||||
todo!()
|
||||
// let bytes = serde_json::to_vec(frame).unwrap();
|
||||
// self.channel
|
||||
// .send_message(
|
||||
// MessagePayload::Data(bytes.into_boxed_slice()),
|
||||
// MessageDestination::AllExceptSelf,
|
||||
// )
|
||||
// .unwrap();
|
||||
// Ok(())
|
||||
}
|
||||
// pub fn send(&self, frame: &DebugFrame) -> Result<(), Error> {
|
||||
// let _ = frame;
|
||||
// todo!()
|
||||
// // let bytes = serde_json::to_vec(frame).unwrap();
|
||||
// // self.channel
|
||||
// // .send_message(
|
||||
// // MessagePayload::Data(bytes.into_boxed_slice()),
|
||||
// // MessageDestination::AllExceptSelf,
|
||||
// // )
|
||||
// // .unwrap();
|
||||
// // Ok(())
|
||||
// }
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ use yggdrasil_abi::{
|
||||
error::Error,
|
||||
option::OptionValue,
|
||||
process::{
|
||||
options::ProcessOptionVariant, ExitCode, ProcessGroupId, ProcessId, Signal,
|
||||
options::ProcessOptionVariant, ExitCode, ProcessGroupId, ProcessId, Signal, ThreadEvent,
|
||||
ThreadSpawnOptions, WaitFlags,
|
||||
},
|
||||
};
|
||||
@ -73,8 +73,6 @@ pub struct ProcessInner {
|
||||
mutexes: BTreeMap<usize, Arc<UserspaceMutex>>,
|
||||
space: Option<Arc<ProcessAddressSpace>>,
|
||||
image: Option<ProcessImage>,
|
||||
|
||||
thread_exits: BTreeMap<ThreadId, Arc<BoolEvent>>,
|
||||
}
|
||||
|
||||
/// Describes a process within the system
|
||||
@ -304,6 +302,12 @@ impl Process {
|
||||
ProcessOptionVariant::SignalEntry => {
|
||||
options::SignalEntry::store(&self.signal_entry(), buffer)
|
||||
}
|
||||
ProcessOptionVariant::MainThread => {
|
||||
let id = self.inner.read().threads[0].id;
|
||||
let abi =
|
||||
unsafe { yggdrasil_abi::process::ThreadId::from_raw(id.as_user() as u32) };
|
||||
options::MainThread::store(&abi, buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,6 +326,7 @@ impl Process {
|
||||
self.set_signal_entry(value);
|
||||
Ok(())
|
||||
}
|
||||
ProcessOptionVariant::MainThread => Err(Error::ReadOnly),
|
||||
}
|
||||
}
|
||||
|
||||
@ -583,18 +588,18 @@ impl Process {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wait_for_thread(&self, thread: ThreadId) -> Result<(), Error> {
|
||||
let exit = {
|
||||
let inner = self.inner.read();
|
||||
inner
|
||||
.thread_exits
|
||||
.get(&thread)
|
||||
.cloned()
|
||||
.ok_or(Error::DoesNotExist)?
|
||||
};
|
||||
pub async fn wait_for_thread(&self, thread: ThreadId) -> Result<ThreadEvent, Error> {
|
||||
let thread = Thread::get(thread).ok_or(Error::ProcessNotFound)?;
|
||||
|
||||
exit.wait().await;
|
||||
Ok(())
|
||||
// Check that the process is the thread's tracer or parent
|
||||
if !thread.is_child_of(self.id) && !thread.is_tracee_of(self.id) {
|
||||
// Cannot wait for events from unrelated threads
|
||||
log::warn!("{:?} is not tracer nor parent of {:?}", self.id, thread.id);
|
||||
return Err(Error::PermissionDenied);
|
||||
}
|
||||
let with_trace = thread.is_tracee_of(self.id);
|
||||
|
||||
Ok(thread.events.wait(with_trace).await)
|
||||
}
|
||||
|
||||
/// Terminates all threads with the exclusion of `except`. This may be useful when a thread
|
||||
@ -610,7 +615,7 @@ impl Process {
|
||||
|
||||
log::debug!("Terminate thread {}", thread.id);
|
||||
thread.terminate();
|
||||
thread.exit.wait().await;
|
||||
thread.events.exit.wait().await;
|
||||
log::debug!("{} died", thread.id);
|
||||
}
|
||||
|
||||
@ -647,13 +652,10 @@ impl ProcessInner {
|
||||
mutexes: BTreeMap::new(),
|
||||
image,
|
||||
space: space.clone(),
|
||||
|
||||
thread_exits: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_thread(&mut self, thread: Arc<Thread>) {
|
||||
self.thread_exits.insert(thread.id, thread.exit.clone());
|
||||
self.threads.push(thread);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,14 @@
|
||||
use core::{cell::Cell, mem::size_of, ops::Deref};
|
||||
use core::{
|
||||
cell::Cell,
|
||||
future::poll_fn,
|
||||
mem::size_of,
|
||||
ops::Deref,
|
||||
sync::atomic::{AtomicU32, AtomicU64, Ordering},
|
||||
task::Poll,
|
||||
};
|
||||
|
||||
use alloc::{
|
||||
collections::{btree_map, BTreeMap},
|
||||
collections::BTreeMap,
|
||||
string::String,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
@ -9,35 +16,39 @@ use crossbeam_queue::SegQueue;
|
||||
use futures_util::task::ArcWake;
|
||||
use kernel_arch::{
|
||||
task::{Scheduler, TaskContext, TaskFrame},
|
||||
Architecture, ArchitectureImpl, CpuImpl,
|
||||
CpuImpl,
|
||||
};
|
||||
use libk_mm::{process::ProcessAddressSpace, PageFaultKind};
|
||||
use libk_util::{
|
||||
event::BoolEvent,
|
||||
sync::{spin_rwlock::IrqSafeRwLock, IrqGuard, IrqSafeSpinlock},
|
||||
ring::LossyRingQueue,
|
||||
sync::{spin_rwlock::IrqSafeRwLock, IrqGuard, IrqSafeSpinlock, IrqSafeSpinlockGuard},
|
||||
};
|
||||
use yggdrasil_abi::{
|
||||
arch::SavedFrame,
|
||||
debug::DebugFrame,
|
||||
debug::{self, DebugControl, TraceEvent, TraceEventPayload, TraceFlags},
|
||||
error::Error,
|
||||
option::OptionValue,
|
||||
option::{OptionValue, RequestValue},
|
||||
process::{
|
||||
thread::{ThreadOptionVariant, ThreadSignalStack},
|
||||
ExitCode, ProcessId, Signal, SignalEntryData,
|
||||
ExitCode, ProcessId, Signal, SignalEntryData, ThreadEvent,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::task::{
|
||||
mem::ForeignPointer,
|
||||
sched::CpuQueue,
|
||||
types::{ThreadAffinity, ThreadId, ThreadState},
|
||||
TaskContextImpl,
|
||||
use crate::{
|
||||
task::{
|
||||
mem::ForeignPointer,
|
||||
sched::CpuQueue,
|
||||
types::{ThreadAffinity, ThreadId, ThreadState},
|
||||
TaskContextImpl,
|
||||
},
|
||||
time::monotonic_time,
|
||||
};
|
||||
|
||||
use super::{debug::ThreadDebugger, process::Process};
|
||||
use super::process::Process;
|
||||
|
||||
type BreakpointType = <ArchitectureImpl as Architecture>::BreakpointType;
|
||||
const BREAKPOINT_VALUE: BreakpointType = ArchitectureImpl::BREAKPOINT_VALUE;
|
||||
// type BreakpointType = <ArchitectureImpl as Architecture>::BreakpointType;
|
||||
// const BREAKPOINT_VALUE: BreakpointType = ArchitectureImpl::BREAKPOINT_VALUE;
|
||||
|
||||
/// Provides details about how the thread is scheduled onto CPUs
|
||||
pub struct ThreadSchedulingInfo {
|
||||
@ -50,18 +61,21 @@ pub struct ThreadSchedulingInfo {
|
||||
pub queue: Option<&'static CpuQueue>,
|
||||
}
|
||||
|
||||
pub struct ThreadDebuggingInfo {
|
||||
pub struct ThreadTracingInfo {
|
||||
pub tracer: Option<ProcessId>,
|
||||
pub single_step: bool,
|
||||
pub restore_breakpoint: Option<usize>,
|
||||
pub debugger: Option<ThreadDebugger>,
|
||||
pub saved_frame: Option<SavedFrame>,
|
||||
pub breakpoints: BTreeMap<usize, BreakpointType>,
|
||||
pub state: SavedFrame,
|
||||
}
|
||||
|
||||
pub struct ThreadInfo {
|
||||
pub signal_stack: ThreadSignalStack,
|
||||
}
|
||||
|
||||
pub struct ThreadEvents {
|
||||
pub exit: BoolEvent,
|
||||
pub trace: LossyRingQueue<TraceEvent>,
|
||||
}
|
||||
|
||||
/// Describes a single thread within the system
|
||||
pub struct Thread {
|
||||
/// Unique thread ID
|
||||
@ -74,19 +88,25 @@ pub struct Thread {
|
||||
pub context: Cell<TaskContextImpl>,
|
||||
process: Option<ProcessId>,
|
||||
space: Option<Arc<ProcessAddressSpace>>,
|
||||
debug: IrqSafeSpinlock<ThreadDebuggingInfo>,
|
||||
debug: IrqSafeSpinlock<ThreadTracingInfo>,
|
||||
trace_flags: AtomicU32,
|
||||
syscall_trace_seq: AtomicU64,
|
||||
|
||||
// inner: IrqSafeSpinlock<ThreadInner>,
|
||||
info: IrqSafeRwLock<ThreadInfo>,
|
||||
signal_queue: SegQueue<Signal>,
|
||||
|
||||
pub exit: Arc<BoolEvent>,
|
||||
pub events: ThreadEvents,
|
||||
pub kill: BoolEvent,
|
||||
|
||||
/// CPU scheduling affinity mask
|
||||
pub affinity: ThreadAffinity,
|
||||
}
|
||||
|
||||
// Prevents thread from getting scheduled during some critical section
|
||||
#[allow(unused)]
|
||||
pub struct SuspendGuard<'a>(IrqSafeSpinlockGuard<'a, ThreadSchedulingInfo>);
|
||||
|
||||
/// Wrapper which guarantees the thread referred to is the current one on the current CPU
|
||||
#[repr(C)]
|
||||
pub struct CurrentThread(Arc<Thread>, IrqGuard);
|
||||
@ -121,23 +141,18 @@ impl Thread {
|
||||
in_queue: false,
|
||||
queue: None,
|
||||
}),
|
||||
// TODO lazy initialization for debugging info
|
||||
debug: IrqSafeSpinlock::new(ThreadDebuggingInfo {
|
||||
single_step: false,
|
||||
restore_breakpoint: None,
|
||||
debugger: None,
|
||||
saved_frame: None,
|
||||
breakpoints: BTreeMap::new(),
|
||||
}),
|
||||
debug: IrqSafeSpinlock::new(ThreadTracingInfo::default()),
|
||||
context: Cell::new(context),
|
||||
process,
|
||||
space,
|
||||
trace_flags: AtomicU32::new(0),
|
||||
syscall_trace_seq: AtomicU64::new(1),
|
||||
|
||||
info: IrqSafeRwLock::new(ThreadInfo {
|
||||
signal_stack: ThreadSignalStack::default(),
|
||||
}),
|
||||
signal_queue: SegQueue::new(),
|
||||
exit: Arc::new(BoolEvent::new()),
|
||||
events: ThreadEvents::new(),
|
||||
kill: BoolEvent::new(),
|
||||
|
||||
affinity: ThreadAffinity::any_cpu(),
|
||||
@ -294,6 +309,11 @@ impl Thread {
|
||||
self.process.unwrap()
|
||||
}
|
||||
|
||||
/// Returns `true` if the thread is a child of given process
|
||||
pub fn is_child_of(&self, pid: ProcessId) -> bool {
|
||||
self.process.map_or(false, |p| p == pid)
|
||||
}
|
||||
|
||||
/// Returns the thread's parent process reference
|
||||
pub fn try_get_process(&self) -> Option<Arc<Process>> {
|
||||
self.process.and_then(Process::get)
|
||||
@ -315,78 +335,39 @@ impl Thread {
|
||||
/// Updates the thread's terminated status and wakes up any other threads waiting for it to
|
||||
/// exit
|
||||
pub fn set_terminated(&self) {
|
||||
self.exit.signal_saturating();
|
||||
self.events.exit.signal_saturating();
|
||||
}
|
||||
|
||||
pub fn trace_event<F: TaskFrame>(&self, suspend: bool, frame: &F, payload: TraceEventPayload) {
|
||||
let mut debug = self.debug.lock();
|
||||
if debug.tracer.is_some() {
|
||||
let timestamp = monotonic_time();
|
||||
debug.store_state(frame);
|
||||
self.events.trace.write(TraceEvent {
|
||||
suspend,
|
||||
timestamp,
|
||||
payload,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Signals
|
||||
|
||||
/// Pushes a signal to the thread's signal queue
|
||||
pub fn raise_signal(&self, signal: Signal) {
|
||||
log::debug!("{}: raise signal {signal:?}", self.id);
|
||||
self.signal_queue.push(signal);
|
||||
self.enqueue();
|
||||
}
|
||||
pub fn raise_signal(&self, signal: Signal) -> bool {
|
||||
let is_traced = self.debug.lock().tracer.is_some();
|
||||
|
||||
// Debugging
|
||||
pub fn attach_debugger(&self, debugger: ThreadDebugger) {
|
||||
// TODO kick out the previous debugger
|
||||
let mut debug = self.debug.lock();
|
||||
|
||||
debug.saved_frame = None;
|
||||
debug.single_step = true;
|
||||
debug.debugger = Some(debugger);
|
||||
|
||||
self.signal_queue.push(Signal::Debug);
|
||||
|
||||
let frame = self
|
||||
.process()
|
||||
.map_image(|img| DebugFrame::Startup {
|
||||
image_base: img.load_base,
|
||||
ip_offset: img.ip_offset,
|
||||
ip: img.entry,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
debug.debugger.as_mut().unwrap().send(&frame).ok();
|
||||
}
|
||||
|
||||
pub fn set_breakpoint(&self, address: usize) -> Result<(), Error> {
|
||||
log::debug!(
|
||||
"Set breakpoint in {} ({:?}) @ {:#x}",
|
||||
self.id,
|
||||
*self.name.read(),
|
||||
address
|
||||
);
|
||||
let mut debug = self.debug.lock();
|
||||
debug.set_breakpoint_inner(self.address_space(), address)
|
||||
}
|
||||
|
||||
pub fn read_memory(&self, address: usize, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
log::debug!(
|
||||
"Read memory in {} ({:?}) @ {:#x}",
|
||||
self.id,
|
||||
*self.name.read(),
|
||||
address
|
||||
);
|
||||
|
||||
let space = self.address_space();
|
||||
|
||||
// TODO optimize this later
|
||||
for (i, item) in buffer.iter_mut().enumerate() {
|
||||
*item = unsafe { ((address + i) as *const u8).try_read_foreign_volatile(space) }?;
|
||||
if is_traced && signal != Signal::Debug && signal != Signal::Killed {
|
||||
log::info!("{}: caught traced signal {signal:?}", self.id);
|
||||
// TODO
|
||||
// self.trace_event(true, );
|
||||
true
|
||||
} else {
|
||||
log::info!("{}: raise signal {signal:?}", self.id);
|
||||
self.signal_queue.push(signal);
|
||||
self.enqueue();
|
||||
false
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resume(&self, single_step: bool) {
|
||||
{
|
||||
let mut debug = self.debug.lock();
|
||||
|
||||
debug.single_step = single_step;
|
||||
}
|
||||
|
||||
self.enqueue();
|
||||
}
|
||||
|
||||
// Scheduling
|
||||
@ -508,6 +489,108 @@ impl Thread {
|
||||
let strong = weak.upgrade()?;
|
||||
Some(CurrentThread(strong, guard))
|
||||
}
|
||||
|
||||
// Tracing/debugging
|
||||
|
||||
/// Returns `true` if the thread is a tracee of given process
|
||||
pub fn is_tracee_of(&self, pid: ProcessId) -> bool {
|
||||
self.debug.lock().tracer.map_or(false, |p| p == pid)
|
||||
}
|
||||
|
||||
pub fn attach_trace(&self, tracer: ProcessId) -> Result<(), Error> {
|
||||
let mut debug = self.debug.lock();
|
||||
if debug.tracer.is_some() {
|
||||
log::warn!("{:?}: tracer already attached", self.id);
|
||||
return Err(Error::AlreadyExists);
|
||||
}
|
||||
debug.tracer = Some(tracer);
|
||||
debug.single_step = true;
|
||||
log::info!("{:?}: tracer ({:?}) attached", self.id, tracer);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_trace_flags(&self, flags: TraceFlags) {
|
||||
let debug = self.debug.lock();
|
||||
let value = if debug.tracer.is_some() {
|
||||
flags.bits()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.trace_flags.store(value, Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn trace_flags(&self) -> TraceFlags {
|
||||
TraceFlags::new(self.trace_flags.load(Ordering::Acquire))
|
||||
}
|
||||
|
||||
pub fn resume(&self, single_step: bool) {
|
||||
{
|
||||
let mut debug = self.debug.lock();
|
||||
|
||||
debug.single_step = single_step;
|
||||
}
|
||||
|
||||
self.enqueue();
|
||||
}
|
||||
|
||||
pub fn hold_suspended(&self) -> Result<SuspendGuard, Error> {
|
||||
let sched = self.sched.lock();
|
||||
if sched.state == ThreadState::Running {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
Ok(SuspendGuard(sched))
|
||||
}
|
||||
|
||||
pub fn debug_control(
|
||||
&self,
|
||||
option: u32,
|
||||
buffer: &mut [u8],
|
||||
len: usize,
|
||||
) -> Result<usize, Error> {
|
||||
// Deserialize request
|
||||
let input = &buffer[..len];
|
||||
let option = DebugControl::try_from(option)?;
|
||||
|
||||
match option {
|
||||
// TODO runtime attach
|
||||
DebugControl::Attach => todo!(),
|
||||
// TODO
|
||||
DebugControl::Detach => todo!(),
|
||||
DebugControl::Resume => {
|
||||
let single_step = debug::Resume::load_request(input)?;
|
||||
self.resume(single_step);
|
||||
debug::Resume::store_response(&(), buffer)
|
||||
}
|
||||
DebugControl::SetTraceFlags => {
|
||||
let flags = debug::SetTraceFlags::load_request(buffer)?;
|
||||
self.set_trace_flags(flags);
|
||||
debug::SetTraceFlags::store_response(&(), buffer)
|
||||
}
|
||||
DebugControl::GetTraceFlags => {
|
||||
debug::GetTraceFlags::store_response(&self.trace_flags(), buffer)
|
||||
}
|
||||
DebugControl::GetRegisters => {
|
||||
let _guard = self.hold_suspended()?;
|
||||
let debug = self.debug.lock();
|
||||
debug::GetRegisters::store_response(&debug.state, buffer)
|
||||
}
|
||||
DebugControl::SetRegisters => {
|
||||
let _guard = self.hold_suspended()?;
|
||||
todo!()
|
||||
}
|
||||
DebugControl::ReadMemory => {
|
||||
let address = debug::ReadMemory::load_request(buffer)?;
|
||||
let pointer = core::ptr::with_exposed_provenance::<usize>(address);
|
||||
let _guard = self.hold_suspended()?;
|
||||
let space = self.address_space();
|
||||
let word = unsafe { pointer.try_read_foreign_volatile(space)? };
|
||||
debug::ReadMemory::store_response(&word, buffer)
|
||||
}
|
||||
DebugControl::WriteMemory => {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalThreadList {
|
||||
@ -552,12 +635,49 @@ impl CurrentThread {
|
||||
Arc::downgrade(&self.0)
|
||||
}
|
||||
|
||||
pub fn raise_signal(&self, signal: Signal) {
|
||||
if self.0.raise_signal(signal) {
|
||||
// Signal was intercepted and the thread will need to be suspended
|
||||
log::info!("{}: suspending: signal intercepted by tracer", self.id);
|
||||
self.suspend().ok();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO point data
|
||||
pub fn trace_point<F: TaskFrame, P: FnOnce() -> TraceEventPayload>(
|
||||
&self,
|
||||
filter: TraceFlags,
|
||||
frame: &F,
|
||||
producer: P,
|
||||
) {
|
||||
if self.trace_flags().contains(filter) {
|
||||
self.trace_event(true, frame, producer());
|
||||
self.suspend().ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trace_syscall_entry<F: TaskFrame>(&self, frame: &F) -> u64 {
|
||||
let seq = self.syscall_trace_seq.fetch_add(1, Ordering::Relaxed);
|
||||
self.trace_point(TraceFlags::SYSCALL_ENTRY, frame, || {
|
||||
TraceEventPayload::SyscallEntry(seq)
|
||||
});
|
||||
seq
|
||||
}
|
||||
|
||||
pub fn trace_syscall_exit<F: TaskFrame>(&self, frame: &F, seq: u64) {
|
||||
self.trace_point(TraceFlags::SYSCALL_EXIT, frame, || {
|
||||
TraceEventPayload::SyscallExit(seq)
|
||||
});
|
||||
}
|
||||
|
||||
/// Terminate the current thread
|
||||
pub fn exit(&self, code: ExitCode) -> ! {
|
||||
// Can detach debugger now
|
||||
let debug = self.debug.lock();
|
||||
if let Some(debugger) = debug.debugger.as_ref() {
|
||||
debugger.send(&DebugFrame::Exited).ok();
|
||||
{
|
||||
let mut debug = self.debug.lock();
|
||||
debug.tracer = None;
|
||||
debug.single_step = false;
|
||||
self.trace_flags.store(0, Ordering::Release);
|
||||
}
|
||||
|
||||
if let Some(process) = self.try_get_process() {
|
||||
@ -597,81 +717,59 @@ impl CurrentThread {
|
||||
|
||||
pub fn handle_single_step<F: TaskFrame>(&self, frame: &mut F) -> bool {
|
||||
{
|
||||
let mut debug = self.debug.lock();
|
||||
if debug.debugger.is_none() {
|
||||
return false;
|
||||
let debug = self.debug.lock();
|
||||
if !debug.single_step {
|
||||
frame.set_single_step(false);
|
||||
return true;
|
||||
}
|
||||
let space = self.address_space();
|
||||
|
||||
if let Some(original) = debug.restore_breakpoint.take() {
|
||||
let brk_range = original..original + size_of::<BreakpointType>();
|
||||
assert!(!brk_range.contains(&frame.user_ip()));
|
||||
|
||||
log::debug!(
|
||||
"Restore breakpoint, current_ip={:#x}, breakpoint_ip={:#x}",
|
||||
frame.user_ip(),
|
||||
original
|
||||
);
|
||||
debug.set_breakpoint_inner(space, original).unwrap();
|
||||
} else {
|
||||
// Single step cleared
|
||||
if !debug.single_step {
|
||||
log::debug!("Clear single step ({} {:?})", self.id, *self.name.read());
|
||||
frame.set_single_step(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
let frame = frame.store();
|
||||
debug.saved_frame = Some(frame.clone());
|
||||
// TODO handle cases of detached debugger
|
||||
let debugger = debug.debugger.as_ref().unwrap();
|
||||
debugger.send(&DebugFrame::Step { frame }).ok();
|
||||
}
|
||||
|
||||
self.trace_event(true, frame, TraceEventPayload::SingleStep);
|
||||
|
||||
match self.suspend() {
|
||||
Ok(_) | Err(Error::Interrupted) => true,
|
||||
Err(err) => panic!("TODO: handle error in debug suspend: {:?}", err),
|
||||
Err(err) => panic!("TODO: handle error in single-step suspend: {err:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_breakpoint<F: TaskFrame>(&self, frame: &mut F) -> bool {
|
||||
let mut debug = self.debug.lock();
|
||||
let ip = frame.user_ip();
|
||||
pub fn handle_breakpoint<F: TaskFrame>(&self, _frame: &mut F) -> bool {
|
||||
false
|
||||
// let mut debug = self.debug.lock();
|
||||
// let ip = frame.user_ip();
|
||||
|
||||
if let Some(value) = debug.breakpoints.remove(&ip) {
|
||||
let space = self.address_space();
|
||||
// if let Some(value) = debug.breakpoints.remove(&ip) {
|
||||
// let space = self.address_space();
|
||||
|
||||
// Restore original code
|
||||
let pointer = ip as *mut BreakpointType;
|
||||
unsafe { pointer.write_foreign_volatile(space, value) };
|
||||
// // Restore original code
|
||||
// let pointer = ip as *mut BreakpointType;
|
||||
// unsafe { pointer.write_foreign_volatile(space, value) };
|
||||
|
||||
log::debug!(
|
||||
"Thread {} ({:?}) hit a breakpoint @ {:#x}, step={}",
|
||||
self.id,
|
||||
*self.name.read(),
|
||||
ip,
|
||||
debug.single_step
|
||||
);
|
||||
// log::debug!(
|
||||
// "Thread {} ({:?}) hit a breakpoint @ {:#x}, step={}",
|
||||
// self.id,
|
||||
// *self.name.read(),
|
||||
// ip,
|
||||
// debug.single_step
|
||||
// );
|
||||
|
||||
// TODO handle cases when no debugger is attached (clear breakpoint and resume)
|
||||
frame.set_single_step(true);
|
||||
// // TODO handle cases when no debugger is attached (clear breakpoint and resume)
|
||||
// frame.set_single_step(true);
|
||||
|
||||
let frame = frame.store();
|
||||
debug.restore_breakpoint = Some(ip);
|
||||
debug.saved_frame = Some(frame.clone());
|
||||
// let frame = frame.store();
|
||||
// debug.restore_breakpoint = Some(ip);
|
||||
// debug.saved_frame = Some(frame.clone());
|
||||
|
||||
let debugger = debug.debugger.as_ref().unwrap();
|
||||
debugger.send(&DebugFrame::HitBreakpoint { frame }).ok();
|
||||
// let debugger = debug.debugger.as_ref().unwrap();
|
||||
// debugger.send(&DebugFrame::HitBreakpoint { frame }).ok();
|
||||
|
||||
drop(debug);
|
||||
// drop(debug);
|
||||
|
||||
self.suspend().unwrap();
|
||||
// self.suspend().unwrap();
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
// true
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn handle_page_fault(&self, address: usize, kind: PageFaultKind) -> Result<(), Error> {
|
||||
@ -697,19 +795,8 @@ impl CurrentThread {
|
||||
);
|
||||
|
||||
if signal == Signal::Debug {
|
||||
frame.set_single_step(true);
|
||||
|
||||
let frame = frame.store();
|
||||
let mut debug = self.debug.lock();
|
||||
|
||||
debug.single_step = true;
|
||||
debug.saved_frame = Some(frame.clone());
|
||||
|
||||
let debugger = debug.debugger.as_ref().unwrap();
|
||||
|
||||
debugger.send(&DebugFrame::Step { frame }).ok();
|
||||
|
||||
return;
|
||||
// Debugger attached in runtime
|
||||
todo!()
|
||||
}
|
||||
|
||||
let ip = self.process().signal_entry();
|
||||
@ -766,27 +853,70 @@ impl Deref for CurrentThread {
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadDebuggingInfo {
|
||||
fn set_breakpoint_inner(
|
||||
&mut self,
|
||||
space: &ProcessAddressSpace,
|
||||
address: usize,
|
||||
) -> Result<(), Error> {
|
||||
match self.breakpoints.entry(address) {
|
||||
btree_map::Entry::Vacant(vacant) => {
|
||||
let pointer = address as *mut BreakpointType;
|
||||
// impl ThreadTracingInfo {
|
||||
// fn set_breakpoint_inner(
|
||||
// &mut self,
|
||||
// space: &ProcessAddressSpace,
|
||||
// address: usize,
|
||||
// ) -> Result<(), Error> {
|
||||
// match self.breakpoints.entry(address) {
|
||||
// btree_map::Entry::Vacant(vacant) => {
|
||||
// let pointer = address as *mut BreakpointType;
|
||||
//
|
||||
// // Read old code from the address space at that location
|
||||
// let original =
|
||||
// unsafe { (pointer as *const BreakpointType).try_read_foreign_volatile(space) }?;
|
||||
//
|
||||
// unsafe { pointer.write_foreign_volatile(space, BREAKPOINT_VALUE) };
|
||||
//
|
||||
// vacant.insert(original);
|
||||
// Ok(())
|
||||
// }
|
||||
// // No need, breakpoint already present
|
||||
// btree_map::Entry::Occupied(_) => Err(Error::AlreadyExists),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Read old code from the address space at that location
|
||||
let original =
|
||||
unsafe { (pointer as *const BreakpointType).try_read_foreign_volatile(space) }?;
|
||||
impl ThreadTracingInfo {
|
||||
pub fn store_state<F: TaskFrame>(&mut self, frame: &F) {
|
||||
self.state = frame.store();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe { pointer.write_foreign_volatile(space, BREAKPOINT_VALUE) };
|
||||
|
||||
vacant.insert(original);
|
||||
Ok(())
|
||||
}
|
||||
// No need, breakpoint already present
|
||||
btree_map::Entry::Occupied(_) => Err(Error::AlreadyExists),
|
||||
impl Default for ThreadTracingInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
single_step: false,
|
||||
tracer: None,
|
||||
state: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadEvents {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
exit: BoolEvent::new(),
|
||||
trace: LossyRingQueue::with_capacity(8),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wait(&self, with_trace: bool) -> ThreadEvent {
|
||||
poll_fn(|cx| {
|
||||
if with_trace {
|
||||
if let Poll::Ready(mut lock) = self.trace.poll_lock(cx) {
|
||||
let event = unsafe { lock.read_single_unchecked() };
|
||||
return Poll::Ready(ThreadEvent::Trace(event));
|
||||
}
|
||||
}
|
||||
|
||||
if self.exit.poll(cx).is_ready() {
|
||||
Poll::Ready(ThreadEvent::Exited)
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
@ -227,6 +227,7 @@ fn el0_sync_inner(frame: &mut ExceptionFrame) {
|
||||
let dump = match ec {
|
||||
// SVC in AArch64
|
||||
0b010101 => {
|
||||
let seq = Thread::current().trace_syscall_entry(frame);
|
||||
let func = frame.r[8];
|
||||
if func == usize::from(SyscallFunction::ExitSignal) {
|
||||
unsafe {
|
||||
@ -238,6 +239,7 @@ fn el0_sync_inner(frame: &mut ExceptionFrame) {
|
||||
let args = &frame.r[0..6];
|
||||
let result = raw_syscall_handler(func, args) as _;
|
||||
frame.r[0] = result;
|
||||
Thread::current().trace_syscall_exit(frame, seq);
|
||||
false
|
||||
}
|
||||
// Software Step from lower Exception Level
|
||||
|
@ -95,6 +95,7 @@ unsafe fn umode_exception_handler(frame: &mut TrapFrame) {
|
||||
}
|
||||
}
|
||||
Some(Cause::EcallUmode) => {
|
||||
let seq = Thread::current().trace_syscall_entry(frame);
|
||||
let func = frame.an[0];
|
||||
if func == usize::from(SyscallFunction::ExitSignal) {
|
||||
unsafe {
|
||||
@ -108,6 +109,7 @@ unsafe fn umode_exception_handler(frame: &mut TrapFrame) {
|
||||
mem::tlb_flush_full();
|
||||
frame.an[0] = result;
|
||||
frame.sepc += 4;
|
||||
Thread::current().trace_syscall_exit(frame, seq);
|
||||
(false, false)
|
||||
}
|
||||
_ => {
|
||||
|
@ -11,6 +11,8 @@ use tock_registers::interfaces::{ReadWriteable, Writeable};
|
||||
use crate::syscall::{self, raw_syscall_handler};
|
||||
|
||||
fn syscall_inner(frame: &mut SyscallFrame) {
|
||||
let trace_seq = Thread::current().trace_syscall_entry(frame);
|
||||
|
||||
if frame.rax == usize::from(SyscallFunction::ExitSignal) {
|
||||
unsafe {
|
||||
syscall::handle_signal_exit(frame);
|
||||
@ -31,6 +33,9 @@ fn syscall_inner(frame: &mut SyscallFrame) {
|
||||
let result = raw_syscall_handler(frame.rax, &frame.args);
|
||||
|
||||
frame.rax = result as _;
|
||||
|
||||
Thread::current().trace_syscall_exit(frame, trace_seq);
|
||||
|
||||
if code == usize::from(SyscallFunction::CreateDirectory) {
|
||||
log::debug!("frame out = {frame:#x?}");
|
||||
}
|
||||
|
@ -1,11 +1,7 @@
|
||||
use abi::{
|
||||
debug::{DebugOperation, TraceLevel},
|
||||
error::Error,
|
||||
process::ProcessId,
|
||||
};
|
||||
use abi::{debug::TraceLevel, error::Error};
|
||||
use libk::{
|
||||
debug,
|
||||
task::{process::Process, thread::Thread},
|
||||
task::{thread::Thread, ThreadId},
|
||||
};
|
||||
|
||||
pub(crate) fn debug_trace(level: TraceLevel, message: &str) {
|
||||
@ -21,24 +17,21 @@ pub(crate) fn debug_trace(level: TraceLevel, message: &str) {
|
||||
debug::program_trace(&process, &thread, level, message);
|
||||
}
|
||||
|
||||
pub(crate) fn debug_control(pid: ProcessId, op: &mut DebugOperation) -> Result<(), Error> {
|
||||
let target = Process::get(pid).ok_or(Error::DoesNotExist)?;
|
||||
let target_thread = target.as_single_thread().unwrap();
|
||||
pub(crate) fn debug_control(
|
||||
tid: u32,
|
||||
option: u32,
|
||||
buffer: &mut [u8],
|
||||
len: usize,
|
||||
) -> Result<usize, Error> {
|
||||
let thread = Thread::current();
|
||||
let process = thread.process();
|
||||
let tid = ThreadId::User(tid as _);
|
||||
let tracee = Thread::get(tid).ok_or(Error::ProcessNotFound)?;
|
||||
|
||||
match op {
|
||||
&mut DebugOperation::Continue(single_step) => {
|
||||
// TODO check if it's paused currently
|
||||
target_thread.resume(single_step);
|
||||
Ok(())
|
||||
}
|
||||
&mut DebugOperation::SetBreakpoint(address) => target_thread.set_breakpoint(address),
|
||||
DebugOperation::ReadMemory { address, buffer } => {
|
||||
target_thread.read_memory(*address, buffer)
|
||||
}
|
||||
DebugOperation::WriteMemory { .. } => todo!(),
|
||||
_ => {
|
||||
log::warn!("Unsupported debug operation: {:?}", op);
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
if !tracee.is_tracee_of(process.id) {
|
||||
log::warn!("{}: {} is not our tracee", process.id, tracee.id);
|
||||
return Err(Error::PermissionDenied);
|
||||
}
|
||||
|
||||
tracee.debug_control(option, buffer, len)
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use abi::{
|
||||
process::{
|
||||
options::ProcessOptionVariant, thread::ThreadOptionVariant, ExitCode, MutexOperation,
|
||||
ProcessGroupId, ProcessId, ProcessWait, Signal, SpawnFlags, SpawnOption, SpawnOptions,
|
||||
ThreadSpawnOptions, WaitFlags,
|
||||
ThreadEvent, ThreadSpawnOptions, WaitFlags,
|
||||
},
|
||||
};
|
||||
use alloc::sync::Arc;
|
||||
@ -91,6 +91,10 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Err
|
||||
let process = thread.process();
|
||||
|
||||
let result = run_with_io(&process, |mut io| {
|
||||
let attach_trace = options
|
||||
.optional
|
||||
.iter()
|
||||
.any(|opt| matches!(opt, SpawnOption::AttachTrace));
|
||||
// let attach_debugger = options
|
||||
// .optional
|
||||
// .iter()
|
||||
@ -112,8 +116,7 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Err
|
||||
path: options.program,
|
||||
args: options.arguments,
|
||||
envs: options.arguments,
|
||||
single_step: false,
|
||||
// single_step: attach_debugger.is_some(),
|
||||
single_step: attach_trace,
|
||||
disable_aslr: options.flags.contains(SpawnFlags::DISABLE_ASLR),
|
||||
};
|
||||
let (child_process, child_main) = proc::load_binary(io.ioctx_mut(), &load_options)?;
|
||||
@ -171,6 +174,11 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Err
|
||||
drop(child_io);
|
||||
|
||||
child_main.enqueue();
|
||||
if attach_trace {
|
||||
child_main
|
||||
.attach_trace(process.id)
|
||||
.expect("BUG: Could not attach to thread trace");
|
||||
}
|
||||
// if let Some(debugger) = attach_debugger {
|
||||
// child_main.attach_debugger(ThreadDebugger::new(debugger));
|
||||
// } else {
|
||||
@ -315,12 +323,14 @@ pub(crate) fn exit_thread() -> ! {
|
||||
thread.exit(ExitCode::SUCCESS)
|
||||
}
|
||||
|
||||
pub(crate) fn wait_thread(id: u32) -> Result<(), Error> {
|
||||
pub(crate) fn wait_thread(id: u32, event: &mut MaybeUninit<ThreadEvent>) -> Result<(), Error> {
|
||||
let tid = ThreadId::User(id as u64);
|
||||
let this_thread = Thread::current();
|
||||
let process = this_thread.process();
|
||||
|
||||
block!(process.wait_for_thread(tid).await)?
|
||||
let ev = block!(process.wait_for_thread(tid).await)??;
|
||||
event.write(ev);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn get_thread_option(option: u32, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
@ -335,16 +345,36 @@ pub(crate) fn set_thread_option(option: u32, buffer: &[u8]) -> Result<(), Error>
|
||||
thread.set_option(option, buffer)
|
||||
}
|
||||
|
||||
pub(crate) fn get_process_option(option: u32, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
pub(crate) fn get_process_option(
|
||||
pid: Option<ProcessId>,
|
||||
option: u32,
|
||||
buffer: &mut [u8],
|
||||
) -> Result<usize, Error> {
|
||||
let option = ProcessOptionVariant::try_from(option)?;
|
||||
let thread = Thread::current();
|
||||
let process = thread.process();
|
||||
let process = if let Some(pid) = pid {
|
||||
// TODO check that the process is actually a child of the current one
|
||||
Process::get(pid).ok_or(Error::DoesNotExist)?
|
||||
} else {
|
||||
process
|
||||
};
|
||||
process.get_option(option, buffer)
|
||||
}
|
||||
|
||||
pub(crate) fn set_process_option(option: u32, buffer: &[u8]) -> Result<(), Error> {
|
||||
pub(crate) fn set_process_option(
|
||||
pid: Option<ProcessId>,
|
||||
option: u32,
|
||||
buffer: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
let option = ProcessOptionVariant::try_from(option)?;
|
||||
let thread = Thread::current();
|
||||
let process = thread.process();
|
||||
let process = if let Some(pid) = pid {
|
||||
// TODO check that the process is actually a child of the current one
|
||||
Process::get(pid).ok_or(Error::DoesNotExist)?
|
||||
} else {
|
||||
process
|
||||
};
|
||||
process.set_option(option, buffer)
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use crate::{
|
||||
des::{DeserializeError, Deserializer},
|
||||
ser::Serializer,
|
||||
@ -51,6 +53,25 @@ impl<'de> Deserialize<'de> for () {
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T: Serialize> Serialize for [T; N] {
|
||||
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
||||
for i in 0..N {
|
||||
self[i].serialize(serializer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, const N: usize, T: Deserialize<'de>> Deserialize<'de> for [T; N] {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
|
||||
let mut array = [const { MaybeUninit::uninit() }; N];
|
||||
for i in 0..N {
|
||||
array[i].write(Deserialize::deserialize(deserializer)?);
|
||||
}
|
||||
Ok(unsafe { MaybeUninit::array_assume_init(array) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for &[u8] {
|
||||
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
||||
serializer.write_bytes(self)
|
||||
@ -75,26 +96,26 @@ impl<'de> Deserialize<'de> for &'de str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Serialize for [u8; N]
|
||||
where
|
||||
[u8; N]: Sized,
|
||||
{
|
||||
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
||||
serializer.write_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, const N: usize> Deserialize<'de> for [u8; N]
|
||||
where
|
||||
[u8; N]: Sized,
|
||||
{
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
|
||||
deserializer
|
||||
.read_bytes()?
|
||||
.try_into()
|
||||
.map_err(|_| D::Error::INVALID_ARRAY_LEN)
|
||||
}
|
||||
}
|
||||
// impl<const N: usize> Serialize for [u8; N]
|
||||
// where
|
||||
// [u8; N]: Sized,
|
||||
// {
|
||||
// fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
||||
// serializer.write_bytes(self)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl<'de, const N: usize> Deserialize<'de> for [u8; N]
|
||||
// where
|
||||
// [u8; N]: Sized,
|
||||
// {
|
||||
// fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
|
||||
// deserializer
|
||||
// .read_bytes()?
|
||||
// .try_into()
|
||||
// .map_err(|_| D::Error::INVALID_ARRAY_LEN)
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<T: Serialize> Serialize for Option<T> {
|
||||
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
||||
|
@ -1,4 +1,4 @@
|
||||
#![feature(decl_macro, ip_from)]
|
||||
#![feature(decl_macro, ip_from, maybe_uninit_array_assume_init)]
|
||||
#![no_std]
|
||||
|
||||
#[cfg(not(feature = "rustc-dep-of-std"))]
|
||||
|
@ -22,6 +22,9 @@ abi-lib = { path = "../../lib/abi-lib" }
|
||||
abi-generator = { path = "../../tool/abi-generator" }
|
||||
prettyplease = "0.2.15"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(rust_analyzer)'] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
alloc = []
|
||||
|
@ -17,7 +17,7 @@ enum Signal(u32) {
|
||||
Terminated = 7,
|
||||
}
|
||||
|
||||
newtype ProcessId(u32);
|
||||
newtype ProcessId(u31);
|
||||
newtype ProcessGroupId(u32);
|
||||
newtype ThreadId(u32);
|
||||
|
||||
|
@ -15,6 +15,8 @@ extern {
|
||||
type MutexOperation = yggdrasil_abi::process::MutexOperation;
|
||||
#[thin]
|
||||
type ExitCode = yggdrasil_abi::process::ExitCode;
|
||||
#[thin]
|
||||
type ThreadEvent = yggdrasil_abi::process::ThreadEvent;
|
||||
type ProcessWait = yggdrasil_abi::process::ProcessWait;
|
||||
|
||||
type FileMetadataUpdate = yggdrasil_abi::io::FileMetadataUpdate;
|
||||
@ -102,11 +104,11 @@ syscall get_tid() -> u32;
|
||||
// TODO use ThreadId
|
||||
syscall spawn_thread(options: &ThreadSpawnOptions) -> Result<u32>;
|
||||
syscall exit_thread() -> !;
|
||||
syscall wait_thread(tid: u32) -> Result<()>;
|
||||
syscall wait_thread(tid: u32, event: &mut MaybeUninit<ThreadEvent>) -> Result<()>;
|
||||
syscall get_thread_option(option: u32, value: &mut [u8]) -> Result<usize>;
|
||||
syscall set_thread_option(option: u32, value: &[u8]) -> Result<()>;
|
||||
syscall get_process_option(option: u32, value: &mut [u8]) -> Result<usize>;
|
||||
syscall set_process_option(option: u32, value: &[u8]) -> Result<()>;
|
||||
syscall get_process_option(process: Option<ProcessId>, option: u32, value: &mut [u8]) -> Result<usize>;
|
||||
syscall set_process_option(process: Option<ProcessId>, option: u32, value: &[u8]) -> Result<()>;
|
||||
|
||||
syscall nanosleep(duration: &Duration, remaining: &mut MaybeUninit<Duration>) -> Result<()>;
|
||||
|
||||
@ -182,4 +184,4 @@ syscall execve(options: &ExecveOptions<'_>) -> Result<()>;
|
||||
|
||||
// Debugging
|
||||
syscall debug_trace(level: TraceLevel, message: &str);
|
||||
syscall debug_control(pid: ProcessId, op: &mut DebugOperation<'_>) -> Result<()>;
|
||||
syscall debug_control(tid: u32, option: u32, buffer: &mut [u8], len: usize) -> Result<usize>;
|
||||
|
@ -1,5 +1,7 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use abi_serde::impl_struct_serde;
|
||||
|
||||
use super::FrameOps;
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
@ -22,3 +24,11 @@ impl FrameOps for SavedFrame {
|
||||
self.elr_el1 as _
|
||||
}
|
||||
}
|
||||
|
||||
impl_struct_serde!(SavedFrame: [
|
||||
gp_regs,
|
||||
spsr_el1,
|
||||
elr_el1,
|
||||
sp_el0,
|
||||
mdscr_el1,
|
||||
]);
|
||||
|
@ -1,5 +1,7 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use abi_serde::impl_struct_serde;
|
||||
|
||||
use super::FrameOps;
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
@ -28,3 +30,17 @@ impl FrameOps for SavedFrame {
|
||||
self.user_ip as _
|
||||
}
|
||||
}
|
||||
|
||||
impl_struct_serde!(SavedFrame: [
|
||||
eax,
|
||||
ecx,
|
||||
edx,
|
||||
ebx,
|
||||
ebp,
|
||||
esi,
|
||||
edi,
|
||||
|
||||
user_ip,
|
||||
user_sp,
|
||||
eflags,
|
||||
]);
|
||||
|
@ -2,24 +2,24 @@
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
|
||||
pub(crate) mod aarch64;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
|
||||
use aarch64 as arch_impl;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
|
||||
pub(crate) mod x86_64;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
|
||||
use x86_64 as arch_impl;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
#[cfg(any(target_arch = "x86", rust_analyzer))]
|
||||
pub(crate) mod i686;
|
||||
#[cfg(target_arch = "x86")]
|
||||
#[cfg(any(target_arch = "x86", rust_analyzer))]
|
||||
use i686 as arch_impl;
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
#[cfg(any(target_arch = "riscv64", rust_analyzer))]
|
||||
pub(crate) mod riscv64;
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
#[cfg(any(target_arch = "riscv64", rust_analyzer))]
|
||||
use riscv64 as arch_impl;
|
||||
|
||||
pub use arch_impl::SavedFrame;
|
||||
|
@ -1,5 +1,7 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use abi_serde::impl_struct_serde;
|
||||
|
||||
use super::FrameOps;
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
@ -28,3 +30,14 @@ impl FrameOps for SavedFrame {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl_struct_serde!(SavedFrame: [
|
||||
ra,
|
||||
gp,
|
||||
tn,
|
||||
sn,
|
||||
an,
|
||||
sp,
|
||||
ip,
|
||||
tp,
|
||||
]);
|
||||
|
@ -1,5 +1,7 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use abi_serde::impl_struct_serde;
|
||||
|
||||
use super::FrameOps;
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
@ -37,3 +39,26 @@ impl FrameOps for SavedFrame {
|
||||
self.user_ip as _
|
||||
}
|
||||
}
|
||||
|
||||
impl_struct_serde!(SavedFrame: [
|
||||
rax,
|
||||
rcx,
|
||||
rdx,
|
||||
rbx,
|
||||
rsi,
|
||||
rdi,
|
||||
rbp,
|
||||
|
||||
r8,
|
||||
r9,
|
||||
r10,
|
||||
r11,
|
||||
r12,
|
||||
r13,
|
||||
r14,
|
||||
r15,
|
||||
|
||||
user_ip,
|
||||
user_sp,
|
||||
rflags
|
||||
]);
|
||||
|
@ -1,63 +1,81 @@
|
||||
//! Debugging functionality
|
||||
use crate::{arch::SavedFrame, io::RawFd};
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use abi_serde::{impl_newtype_serde, impl_struct_serde};
|
||||
|
||||
pub use crate::generated::TraceLevel;
|
||||
use crate::{arch::SavedFrame, bitflags, time::SystemTime};
|
||||
|
||||
/// Describes a debugger operation
|
||||
#[derive(Debug)]
|
||||
pub enum DebugOperation<'a> {
|
||||
/// Attach to a given process
|
||||
Attach(RawFd),
|
||||
/// Detach from the current process
|
||||
Detach,
|
||||
// TODO SetSignal to send signals from the tracer to the tracee
|
||||
request_group!(
|
||||
#[doc = "Debugging/tracing operation on a thread"]
|
||||
pub enum DebugControl<'de> {
|
||||
/// Attach to a tracee, stopping it
|
||||
0x1000: Attach((), ()),
|
||||
/// Detach the tracer from the thread, resuming it
|
||||
0x1001: Detach((), ()),
|
||||
/// Resume stopped tracee, optionally setting it into a single-step mode
|
||||
0x1002: Resume(bool, ()),
|
||||
/// Returns the current state of tracee's registers.
|
||||
///
|
||||
/// Requires stopped tracee.
|
||||
0x1003: GetRegisters((), SavedFrame),
|
||||
/// Updates the current state of tracee's registers.
|
||||
///
|
||||
/// Requires stopped tracee.
|
||||
0x1004: SetRegisters(SavedFrame, ()),
|
||||
/// Reads an `usize` from tracee's memory space.
|
||||
///
|
||||
/// Requires stopped tracee.
|
||||
0x1005: ReadMemory(usize, usize),
|
||||
/// Writes an `usize` to tracee's memory space.
|
||||
///
|
||||
/// Requires stopped tracee.
|
||||
0x1006: WriteMemory(WriteTraceeMemory, ()),
|
||||
/// Updates trace flags for the tracee.
|
||||
0x1007: SetTraceFlags(TraceFlags, ()),
|
||||
/// Returns current trace flags of the tracee.
|
||||
0x1008: GetTraceFlags((), TraceFlags),
|
||||
}
|
||||
);
|
||||
|
||||
/// Send an interrupt to the debugee
|
||||
Interrupt,
|
||||
/// Continue the debugee, `Continue(true)` represents a "single step"
|
||||
Continue(bool),
|
||||
/// Set a breakpoint at a given address
|
||||
SetBreakpoint(usize),
|
||||
/// A request to write into tracee's memory space
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct WriteTraceeMemory {
|
||||
/// Address to write to, must be `usize`-aligned
|
||||
pub address: usize,
|
||||
/// Value to write
|
||||
pub value: usize,
|
||||
}
|
||||
impl_struct_serde!(WriteTraceeMemory: [
|
||||
address,
|
||||
value
|
||||
]);
|
||||
|
||||
/// Read data from the debugee memory
|
||||
ReadMemory {
|
||||
/// Address to read the data from
|
||||
address: usize,
|
||||
/// Buffer to place the result into
|
||||
buffer: &'a mut [u8],
|
||||
},
|
||||
/// Write data to the debugee memory
|
||||
WriteMemory {
|
||||
/// Address to write the data to
|
||||
address: usize,
|
||||
/// Buffer to read the data from
|
||||
buffer: &'a [u8],
|
||||
},
|
||||
bitflags! {
|
||||
/// Defines which trace events to record
|
||||
pub struct TraceFlags: u32 {
|
||||
/// Trace system call entries
|
||||
const SYSCALL_ENTRY: bit 0;
|
||||
/// Trace system call exits
|
||||
const SYSCALL_EXIT: bit 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a debug event
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
pub enum DebugFrame {
|
||||
/// The debugee has started
|
||||
Startup {
|
||||
/// The address at which the process image is loaded
|
||||
image_base: usize,
|
||||
/// The address by which the instruction pointer is offset (when ASLR is in effect)
|
||||
ip_offset: usize,
|
||||
/// Current instruction pointer
|
||||
ip: usize,
|
||||
},
|
||||
/// The debugee has completed a single instruction step
|
||||
Step {
|
||||
/// Current process state
|
||||
frame: SavedFrame,
|
||||
},
|
||||
/// The debugee has hit a breakpoint
|
||||
HitBreakpoint {
|
||||
/// Current process state
|
||||
frame: SavedFrame,
|
||||
},
|
||||
// TODO exit status
|
||||
/// The debugee has exited
|
||||
Exited,
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub enum TraceEventPayload {
|
||||
SingleStep,
|
||||
SyscallEntry(u64),
|
||||
SyscallExit(u64),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct TraceEvent {
|
||||
pub suspend: bool,
|
||||
pub timestamp: SystemTime,
|
||||
pub payload: TraceEventPayload,
|
||||
}
|
||||
|
||||
impl_newtype_serde!(TraceFlags);
|
||||
|
@ -20,6 +20,7 @@ extern crate rustc_std_alloc as alloc;
|
||||
pub(crate) mod macros;
|
||||
pub mod util;
|
||||
|
||||
pub use abi_lib;
|
||||
pub use abi_serde;
|
||||
|
||||
mod generated {
|
||||
|
@ -1,6 +1,13 @@
|
||||
use core::num::NonZeroI32;
|
||||
|
||||
use abi_lib::SyscallRegister;
|
||||
use abi_serde::{
|
||||
des::{DeserializeError, Deserializer},
|
||||
ser::Serializer,
|
||||
Deserialize, Serialize,
|
||||
};
|
||||
|
||||
use crate::debug::TraceEvent;
|
||||
|
||||
use super::Signal;
|
||||
|
||||
@ -14,6 +21,16 @@ pub enum ExitCode {
|
||||
BySignal(Result<Signal, u32>),
|
||||
}
|
||||
|
||||
/// Event signalled by a thread
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub enum ThreadEvent {
|
||||
/// Thread has exited
|
||||
Exited,
|
||||
/// Thread was interrupted by a trace event
|
||||
Trace(TraceEvent),
|
||||
}
|
||||
|
||||
impl ExitCode {
|
||||
/// Returned when a process has exited successfully
|
||||
pub const SUCCESS: Self = Self::Exited(0);
|
||||
@ -70,3 +87,28 @@ impl SyscallRegister for ExitCode {
|
||||
Self::from(value as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ExitCode {
|
||||
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
||||
match self {
|
||||
&Self::Exited(status) => {
|
||||
serializer.write_u8(0)?;
|
||||
status.serialize(serializer)
|
||||
}
|
||||
&Self::BySignal(signal) => {
|
||||
serializer.write_u8(1)?;
|
||||
signal.serialize(serializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ExitCode {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
|
||||
match deserializer.read_u8()? {
|
||||
0 => Deserialize::deserialize(deserializer).map(Self::Exited),
|
||||
1 => Deserialize::deserialize(deserializer).map(Self::Exited),
|
||||
_ => Err(D::Error::INVALID_ENUM_VARIANT),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,12 @@ pub use crate::generated::{
|
||||
ExecveOptions, ProcessGroupId, ProcessId, Signal, SignalEntryData, SpawnFlags, SpawnOptions,
|
||||
ThreadId, ThreadSpawnOptions, WaitFlags,
|
||||
};
|
||||
pub use exit::ExitCode;
|
||||
use abi_serde::{
|
||||
des::{DeserializeError, Deserializer},
|
||||
ser::Serializer,
|
||||
Deserialize, Serialize,
|
||||
};
|
||||
pub use exit::{ExitCode, ThreadEvent};
|
||||
|
||||
// TODO this is ugly
|
||||
pub mod auxv {
|
||||
@ -64,9 +69,10 @@ pub enum SpawnOption {
|
||||
SetProcessGroup(ProcessGroupId),
|
||||
/// Gain terminal control for the given FD
|
||||
GainTerminal(RawFd),
|
||||
/// Attach debugging to a channel in parent's I/O context. The process will start in
|
||||
/// single-stepping mode
|
||||
AttachDebug(RawFd),
|
||||
/// Attach tracing to the spawned process. This is equivalent to issuing a `debug_control` with
|
||||
/// `DebugControl::Attach` to the newly spawned process.
|
||||
/// The newly spawned process will have its main thread paused.
|
||||
AttachTrace,
|
||||
}
|
||||
|
||||
/// Describes a single mutex operation
|
||||
@ -227,3 +233,27 @@ impl Signal {
|
||||
|
||||
abi_serde::impl_newtype_serde!(ProcessGroupId);
|
||||
abi_serde::impl_newtype_serde!(ProcessId);
|
||||
|
||||
impl Serialize for Signal {
|
||||
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
||||
serializer.write_u32(self.into_raw())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Signal {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
|
||||
Self::try_from(deserializer.read_u32()?).map_err(|_| D::Error::INVALID_ENUM_VARIANT)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ThreadId {
|
||||
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
|
||||
self.into_raw().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ThreadId {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
|
||||
Deserialize::deserialize(deserializer).map(|r| unsafe { Self::from_raw(r) })
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
//! Process option definitions
|
||||
|
||||
use super::ThreadId;
|
||||
|
||||
option_group!(
|
||||
#[doc = "Process options"]
|
||||
pub enum ProcessOptionVariant<'a> {
|
||||
@ -8,6 +10,8 @@ option_group!(
|
||||
#[doc = "Current working directory"]
|
||||
0x1001: Directory(&'a str),
|
||||
#[doc = "Process name"]
|
||||
0x1002: Name(&'a str)
|
||||
0x1002: Name(&'a str),
|
||||
#[doc = "Main thread ID"]
|
||||
0x1003: MainThread # 4 (ThreadId),
|
||||
}
|
||||
);
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
use core::fmt;
|
||||
|
||||
pub use abi::debug::{DebugFrame, DebugOperation, TraceLevel};
|
||||
pub use abi::debug::TraceLevel;
|
||||
use abi::{error::Error, option::RequestValue};
|
||||
|
||||
/// Prints a debug message using DebugTrace syscall
|
||||
#[macro_export]
|
||||
@ -54,3 +55,14 @@ pub fn _debug_trace(level: TraceLevel, a: core::fmt::Arguments<'_>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a debugging operation on a thread
|
||||
pub fn debug_control<'de, T: RequestValue<'de>>(
|
||||
tid: u32,
|
||||
buffer: &'de mut [u8],
|
||||
request: &T::Request,
|
||||
) -> Result<T::Response, Error> {
|
||||
let len = T::store_request(request, buffer)?;
|
||||
let len = unsafe { crate::sys::debug_control(tid, T::VARIANT.into(), buffer, len) }?;
|
||||
T::load_response(&buffer[..len])
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ pub fn home_directory<T, F: FnOnce(&str) -> T>(_mapper: F) -> Result<T, Error> {
|
||||
|
||||
pub fn current_directory<T, F: FnOnce(&str) -> T>(mapper: F) -> Result<T, Error> {
|
||||
let mut buffer = [0; 512];
|
||||
let path = process::get_process_option::<process::options::Directory>(&mut buffer)?;
|
||||
let path = process::get_process_option::<process::options::Directory>(None, &mut buffer)?;
|
||||
Ok(mapper(path))
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ pub fn current_directory_string() -> Result<String, Error> {
|
||||
|
||||
pub fn set_current_directory(path: &str) -> Result<(), Error> {
|
||||
let mut buffer = [0; 512];
|
||||
process::set_process_option_with::<process::options::Directory>(&mut buffer, &path)
|
||||
process::set_process_option_with::<process::options::Directory>(None, &mut buffer, &path)
|
||||
}
|
||||
|
||||
pub fn make_temp_directory(_template: &mut [u8]) -> Result<(), Error> {
|
||||
|
@ -5,7 +5,8 @@ use core::{mem::MaybeUninit, time::Duration};
|
||||
pub use abi::process::{
|
||||
auxv, AuxValue, AuxValueIter, ExecveOptions, ExitCode, MutexOperation, ProcessGroupId,
|
||||
ProcessId, ProcessInfoElement, ProcessWait, ProgramArgumentInner, Signal, SignalEntryData,
|
||||
SpawnFlags, SpawnOption, SpawnOptions, StringArgIter, ThreadId, ThreadSpawnOptions, WaitFlags,
|
||||
SpawnFlags, SpawnOption, SpawnOptions, StringArgIter, ThreadEvent, ThreadId,
|
||||
ThreadSpawnOptions, WaitFlags,
|
||||
};
|
||||
use abi::{
|
||||
error::Error,
|
||||
@ -46,16 +47,17 @@ pub fn uninterruptible_sleep(mut duration: Duration) {
|
||||
}
|
||||
|
||||
/// Helper macro for [get_process_option].
|
||||
pub macro get_process_option($variant_ty:ty) {{
|
||||
pub macro get_process_option($process:expr, $variant_ty:ty) {{
|
||||
let mut buffer = [0; <$variant_ty as $crate::io::OptionSizeHint>::SIZE_HINT];
|
||||
$crate::process::get_process_option::<$variant_ty>(&mut buffer)
|
||||
$crate::process::get_process_option::<$variant_ty>($process, &mut buffer)
|
||||
}}
|
||||
|
||||
/// Retrieves a process option value.
|
||||
pub fn get_process_option<'de, T: OptionValue<'de>>(
|
||||
process: Option<ProcessId>,
|
||||
buffer: &'de mut [u8],
|
||||
) -> Result<T::Value, Error> {
|
||||
let len = unsafe { crate::sys::get_process_option(T::VARIANT.into(), buffer) }?;
|
||||
let len = unsafe { crate::sys::get_process_option(process, T::VARIANT.into(), buffer) }?;
|
||||
T::load(&buffer[..len])
|
||||
}
|
||||
|
||||
@ -75,22 +77,24 @@ pub fn get_thread_option<'de, T: OptionValue<'de>>(
|
||||
|
||||
/// Update a process option value. Requires `T`: [OptionSizeHint].
|
||||
pub fn set_process_option<'de, T: OptionValue<'de> + OptionSizeHint>(
|
||||
process: Option<ProcessId>,
|
||||
value: &T::Value,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
[u8; T::SIZE_HINT]: Sized,
|
||||
{
|
||||
let mut buffer = [0; T::SIZE_HINT];
|
||||
set_process_option_with::<T>(&mut buffer, value)
|
||||
set_process_option_with::<T>(process, &mut buffer, value)
|
||||
}
|
||||
|
||||
/// Update a process option value, using provided buffer for serialization.
|
||||
pub fn set_process_option_with<'de, T: OptionValue<'de>>(
|
||||
process: Option<ProcessId>,
|
||||
buffer: &mut [u8],
|
||||
value: &T::Value,
|
||||
) -> Result<(), Error> {
|
||||
let len = T::store(value, buffer)?;
|
||||
unsafe { crate::sys::set_process_option(T::VARIANT.into(), &buffer[..len]) }
|
||||
unsafe { crate::sys::set_process_option(process, T::VARIANT.into(), &buffer[..len]) }
|
||||
}
|
||||
|
||||
/// Update a thread option value. Requires `T`: [OptionSizeHint].
|
||||
|
@ -102,7 +102,7 @@ pub fn get_signal_stack() -> (usize, usize) {
|
||||
/// 1. It must not return, it must call `exit_thread` instead.
|
||||
/// 2. It must conform to the kernel's signal entry ABI.
|
||||
pub unsafe fn set_signal_entry(entry: usize) {
|
||||
process::set_process_option::<process::options::SignalEntry>(&entry)
|
||||
process::set_process_option::<process::options::SignalEntry>(None, &entry)
|
||||
.expect("set_signal_entry() failed");
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! Runtime utilities for thread handling
|
||||
|
||||
use core::{
|
||||
mem::MaybeUninit,
|
||||
ptr,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
@ -8,7 +9,7 @@ use core::{
|
||||
use abi::{
|
||||
error::Error,
|
||||
mem::{MappingFlags, MappingSource},
|
||||
process::ThreadSpawnOptions,
|
||||
process::{ThreadEvent, ThreadSpawnOptions},
|
||||
};
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
|
||||
@ -219,7 +220,14 @@ impl<R> ThreadHandle<R> {
|
||||
/// Waits for the thread to finish and returns its result.
|
||||
pub fn join(self) -> Result<Option<R>, Error> {
|
||||
// TODO prevent threads from attempting to join themselves?
|
||||
unsafe { crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire)) }?;
|
||||
loop {
|
||||
let mut event = MaybeUninit::uninit();
|
||||
unsafe { crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire), &mut event) }?;
|
||||
let event = unsafe { event.assume_init() };
|
||||
if matches!(event, ThreadEvent::Exited) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(self.into_result())
|
||||
}
|
||||
|
||||
@ -230,8 +238,16 @@ impl<R> ThreadHandle<R> {
|
||||
/// Will panic if the kernel returns any error besides [Error::Interrupted].
|
||||
pub fn join_uninterruptible(self) -> Option<R> {
|
||||
loop {
|
||||
match unsafe { crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire)) } {
|
||||
Ok(_) => (),
|
||||
let mut event = MaybeUninit::uninit();
|
||||
match unsafe {
|
||||
crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire), &mut event)
|
||||
} {
|
||||
Ok(_) => {
|
||||
let event = unsafe { event.assume_init() };
|
||||
if !matches!(event, ThreadEvent::Exited) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(Error::Interrupted) => continue,
|
||||
Err(error) => panic!("wait_thread syscall returned error: {error:?}"),
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ pub fn get_thread_pointer() -> usize {
|
||||
/// `value` must hold an address to a structure, first element of which is a pointer to itself.
|
||||
/// Usual pointer safety requirements apply.
|
||||
pub unsafe fn set_thread_pointer(value: usize) -> Result<(), Error> {
|
||||
process::set_process_option::<thread::options::ThreadPointer>(&value)
|
||||
process::set_thread_option::<thread::options::ThreadPointer>(&value)
|
||||
}
|
||||
|
||||
// ___tls_get_addr, TLS_index structure address gets passed in the %eax register
|
||||
|
10
userspace/Cargo.lock
generated
10
userspace/Cargo.lock
generated
@ -2278,6 +2278,7 @@ dependencies = [
|
||||
name = "runtime"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"abi-lib",
|
||||
"abi-serde",
|
||||
"yggdrasil-abi",
|
||||
"yggdrasil-rt",
|
||||
@ -2589,6 +2590,14 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "strace"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strict-num"
|
||||
version = "0.1.1"
|
||||
@ -2656,6 +2665,7 @@ dependencies = [
|
||||
"logsink",
|
||||
"pci-ids",
|
||||
"rand 0.9.0-alpha.1",
|
||||
"runtime",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
|
@ -20,7 +20,8 @@ members = [
|
||||
"lib/runtime",
|
||||
"lib/uipc",
|
||||
"lib/logsink",
|
||||
"lib/libpsf"
|
||||
"lib/libpsf",
|
||||
"strace"
|
||||
]
|
||||
exclude = ["dynload-program", "test-kernel-module", "lib/ygglibc"]
|
||||
|
||||
@ -57,6 +58,7 @@ uipc.path = "lib/uipc"
|
||||
yggdrasil-rt.path = "../lib/runtime"
|
||||
yggdrasil-abi = { path = "../lib/abi", features = ["serde", "alloc", "bytemuck"] }
|
||||
abi-serde = { path = "../lib/abi-serde" }
|
||||
abi-lib = { path = "../lib/abi-lib" }
|
||||
logsink.path = "lib/logsink"
|
||||
libutil.path = "../lib/libutil"
|
||||
|
||||
|
@ -9,6 +9,7 @@ edition = "2021"
|
||||
yggdrasil-abi = { workspace = true, features = ["alloc", "bytemuck"] }
|
||||
yggdrasil-rt.workspace = true
|
||||
abi-serde.workspace = true
|
||||
abi-lib.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@ -6,7 +6,10 @@ pub use yggdrasil_rt as rt;
|
||||
pub use yggdrasil_abi as abi;
|
||||
#[cfg(rust_analyzer)]
|
||||
pub use abi_serde;
|
||||
#[cfg(rust_analyzer)]
|
||||
pub use abi_lib;
|
||||
|
||||
pub use std::os::yggdrasil::rt;
|
||||
pub use std::os::yggdrasil::rt::abi;
|
||||
pub use std::os::yggdrasil::rt::abi::abi_serde;
|
||||
pub use std::os::yggdrasil::rt::abi::abi_lib;
|
||||
|
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
|
||||
logsink.workspace = true
|
||||
libutil.workspace = true
|
||||
runtime.workspace = true
|
||||
|
||||
log.workspace = true
|
||||
rand.workspace = true
|
||||
|
@ -1,14 +1 @@
|
||||
#![feature(seek_stream_len)]
|
||||
|
||||
use std::{fs::File, io::{stdout, Seek, Write}};
|
||||
|
||||
use cross::mem::FileMapping;
|
||||
|
||||
fn main() {
|
||||
let mut file = File::open("/etc/test.txt").unwrap();
|
||||
let size = file.stream_len().unwrap();
|
||||
let mapping = FileMapping::map(file, size as usize).unwrap();
|
||||
let mut data = vec![0; size as usize];
|
||||
data.copy_from_slice(&mapping[..]);
|
||||
stdout().write_all(&data).unwrap();
|
||||
}
|
||||
fn main() {}
|
||||
|
@ -28,6 +28,7 @@ const PROGRAMS: &[(&str, &str)] = &[
|
||||
// sysutils
|
||||
("mount", "sbin/mount"),
|
||||
("login", "sbin/login"),
|
||||
("strace", "bin/strace"),
|
||||
("ls", "bin/ls"),
|
||||
("mv", "bin/mv"),
|
||||
("ln", "bin/ln"),
|
||||
|
Loading…
x
Reference in New Issue
Block a user