use std::cell::{Ref, RefCell};
use std::fs::read_to_string;
use std::marker::PhantomData;
use std::mem::transmute;
use std::rc::Rc;
use nix::libc::{siginfo_t, CLD_DUMPED, CLD_EXITED, CLD_KILLED, CLD_STOPPED, CLD_TRAPPED};
use nix::sys::ptrace::{cont, detach, getevent, getregs, getsiginfo, Event};
use nix::sys::wait::waitpid;
use nix::Error as NixError;
use crate::breakpoint::BreakpointId;
use crate::error::{
CouldNotDetermineState, CouldNotGetSignalInfo, CouldNotReadRegisters, CouldNotResume,
CouldNotSetOptions, CouldNotWait,
};
use crate::process::{set_controller_breakpoint_id, Pid, TargetController, TargetProcess};
use crate::run::{
Executing, PtraceEventStrategy, PtraceOption, PtraceOptionMap, Reason, RunningState, Signal,
};
pub(crate) struct ThreadHandle {
pub(crate) tid: Pid,
option_map: RefCell<PtraceOptionMap>,
}
pub struct Thread<'process> {
handle: Rc<ThreadHandle>,
process: &'process TargetProcess,
_unsend: PhantomData<*mut ()>,
}
impl<'process> Thread<'process> {
#[must_use]
pub fn tid(&self) -> Pid {
self.handle.tid
}
pub(crate) const fn encapsulate(
handle: Rc<ThreadHandle>,
process: &'process TargetProcess,
) -> Thread<'process> {
Thread {
handle,
process,
_unsend: PhantomData,
}
}
#[must_use]
pub fn ptrace_option_map(&self) -> Ref<PtraceOptionMap> {
self.handle.option_map.borrow()
}
}
impl<'process> AsRef<Thread<'process>> for Thread<'process> {
fn as_ref(&self) -> &Thread<'process> {
self
}
}
impl<'process> Executing for Thread<'process> {
type StoppedRepresentation = Self;
type WaitError = CouldNotWait;
fn process(&self) -> &TargetProcess {
self.process
}
fn pid(&self) -> Pid {
self.tid()
}
fn set_ptrace_option(
ctrl: &mut TargetController<Self>,
option: PtraceOption,
strategy: PtraceEventStrategy,
) -> Result<PtraceEventStrategy, CouldNotSetOptions> {
let old = ctrl
.context
.handle
.option_map
.borrow_mut()
.set(option, strategy);
let options = ctrl.context.ptrace_option_map().as_ptrace_options();
ctrl.write_ptrace_options(options)?;
Ok(old)
}
fn wait(self) -> Result<RunningState<Self>, CouldNotWait> {
let status = waitpid(Some(self.pid().into()), None).map_err(|err| match err {
NixError::ECHILD => CouldNotWait::ProcessNotFound {
pid: self.handle.tid,
source: err.into(),
},
_ => todo!("Handle all errors from waitpid"),
})?;
let mut ctrl = TargetController::<Self>::new(self, Reason::Trapped, None);
let reason = Reason::from_wait_status(&mut ctrl, status);
ctrl.update_reason(reason);
if matches!(
ctrl.reason(),
Reason::Exited { .. } | Reason::Signaled { .. }
) {
Ok(RunningState::Exited {
tid: ctrl.context.tid(),
reason: *ctrl.reason(),
})
} else {
if let Reason::ThreadCreated { tid } = *ctrl.reason() {
let trace_clone_strategy = ctrl
.context
.handle
.option_map
.borrow()
.get(PtraceOption::TraceClone);
if trace_clone_strategy == PtraceEventStrategy::Trace {
let handle = ThreadHandle::new(tid);
ctrl.context().process().add_thread(handle);
} else {
detach(tid.into(), None).unwrap();
}
}
set_controller_breakpoint_id(&mut ctrl);
Ok(RunningState::Alive(ctrl))
}
}
fn resume(mut ctrl: TargetController<Self>) -> Result<Self, CouldNotResume> {
let pid = ctrl.context().pid();
ctrl.pass_over_breakpoint()?;
match cont(pid.into(), None) {
Ok(()) => Ok(ctrl.context),
Err(source) => Err(CouldNotResume::CouldNotRestart {
pid,
source: source.into(),
}),
}
}
}
impl ThreadHandle {
pub fn new(tid: Pid) -> Self {
Self {
tid,
option_map: RefCell::default(),
}
}
}
pub enum CurrentState<'process> {
Running(Thread<'process>),
Stopped(TargetController<Thread<'process>>),
}
fn reason_from_ptrace_event(
event: Event,
signo: i32,
brk_id: Option<BreakpointId>,
msg: i64,
) -> Reason {
match event {
Event::PTRACE_EVENT_CLONE => Reason::ThreadCreated {
tid: Pid::from_raw(msg as i32),
},
Event::PTRACE_EVENT_STOP => {
let signal = unsafe { transmute(signo) };
match signal {
Signal::SIGTRAP => brk_id.map_or_else(|| Reason::Trapped, Reason::Breakpoint),
_ => Reason::Stopped { signal },
}
}
unknown_event => {
let cld = unknown_event as i32;
unimplemented!("ptrace event: {:?} ({})", unknown_event, cld);
}
}
}
fn reason_from_siginfo(siginfo: siginfo_t, brk_id: Option<BreakpointId>, msg: i64) -> Reason {
let status = unsafe { siginfo.si_status() };
match siginfo.si_code {
CLD_EXITED => Reason::Exited { exit_code: status },
CLD_KILLED => Reason::Signaled {
signal: unsafe { transmute(status) },
dumped: false,
},
CLD_DUMPED => Reason::Signaled {
signal: unsafe { transmute(status) },
dumped: true,
},
CLD_STOPPED => Reason::Stopped {
signal: unsafe { transmute(status) },
},
CLD_TRAPPED => brk_id.map_or_else(|| Reason::Trapped, Reason::Breakpoint),
cld => {
if cld == 0x80 || cld <= 0
{
let signal = unsafe { transmute(siginfo.si_signo) };
if signal == Signal::SIGTRAP {
brk_id.map_or_else(|| Reason::Trapped, Reason::Breakpoint)
} else {
Reason::Stopped { signal }
}
} else if siginfo.si_signo == Signal::SIGTRAP as i32 {
let event_code = cld >> 8;
let event = unsafe { transmute(event_code) };
reason_from_ptrace_event(event, status, brk_id, msg)
} else {
unreachable!("si_code == {}", cld)
}
}
}
}
#[allow(clippy::missing_panics_doc)]
pub(crate) fn determine_state(thread: Thread) -> Result<CurrentState, CouldNotDetermineState> {
let stat_file = format!("/proc/{}/task/{}/stat", thread.pgid(), thread.tid());
let stat = read_to_string(stat_file)?;
let state = stat
.chars()
.skip_while(|c| *c != ')')
.nth(2) .unwrap();
Ok(match state {
'R' | 'S' | 'Z' => CurrentState::Running(thread),
'T' | 't' => {
let regs = getregs(thread.pid().into()).map_err(|err| CouldNotReadRegisters {
pid: thread.pid(),
source: err.into(),
})?;
let brk_id = thread
.process()
.breakpoints()
.find(|brk| brk.address() as u64 == regs.rip - 1)
.map(|brk| brk.id());
let eventmsg = getevent(thread.pid().into()).unwrap_or_default();
let siginfo = getsiginfo(thread.pid().into()).map_err(|err| CouldNotGetSignalInfo {
pid: thread.pid(),
source: err.into(),
})?;
let reason = reason_from_siginfo(siginfo, brk_id, eventmsg);
CurrentState::Stopped(TargetController::new(thread, reason, brk_id))
}
unknown => unreachable!("Process state '{}' is not supposed to exist.", unknown),
})
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use anyhow::Error as AnyError;
use crate::error::CouldNotExecute;
use crate::process::spawn_process;
use super::*;
fn spawn_create_thread_join_and_die() -> Result<TargetProcess, CouldNotExecute> {
let mut path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path_buf.push("resources/test/create_thread_join_and_die");
spawn_process::<_, _, &str>(path_buf, vec![])
}
#[test]
fn set_ptrace_option_clone() -> Result<(), AnyError> {
let mut process = spawn_create_thread_join_and_die()?;
let mut threads = process.threads();
let main_thread = threads
.pop()
.expect("Expected the process to have a main thread.");
assert_eq!(main_thread.tid(), main_thread.pgid());
let running_state = main_thread.wait()?;
assert!(running_state.is_alive());
let mut ctrl_start = running_state.assume_alive()?;
Thread::set_ptrace_option(
&mut ctrl_start,
PtraceOption::TraceClone,
PtraceEventStrategy::Trace,
)?;
let main_thread2 = ctrl_start.resume()?;
let running_state2 = main_thread2.wait()?;
assert!(running_state2.is_alive());
let ctrl_create_thread = running_state2.assume_alive()?;
assert!(matches!(
*ctrl_create_thread.reason(),
Reason::ThreadCreated { .. }
));
Ok(())
}
#[test]
fn whole_multithread_application() -> Result<(), AnyError> {
let mut process = spawn_create_thread_join_and_die()?;
let mut threads = process.threads();
let main_thread = threads
.pop()
.expect("Expected the process to have a main thread.");
assert_eq!(main_thread.tid(), main_thread.pgid());
let running_state = main_thread.wait()?;
assert!(running_state.is_alive());
let mut ctrl_start = running_state.assume_alive()?;
Thread::set_ptrace_option(
&mut ctrl_start,
PtraceOption::TraceClone,
PtraceEventStrategy::Trace,
)?;
let main_thread2 = ctrl_start.resume()?;
let running_state2 = main_thread2.wait()?;
assert!(running_state2.is_alive());
let ctrl_create_thread = running_state2.assume_alive()?;
assert!(matches!(
*ctrl_create_thread.reason(),
Reason::ThreadCreated { .. }
));
match *ctrl_create_thread.reason() {
Reason::ThreadCreated { tid } => {
let ctrl_after_thread_resumed = process.wait()?.assume_alive()?;
let mut process_all_restarted = ctrl_after_thread_resumed.resume()?;
let mut threads = process_all_restarted.threads();
assert_eq!(threads.len(), 2);
let child_thread = threads.pop().unwrap();
let main_thread = threads.pop().unwrap();
assert_eq!(main_thread.tid(), main_thread.pgid());
assert_eq!(child_thread.tid(), tid);
let state_child = child_thread.wait()?;
assert!(state_child.has_exited());
let state_main = main_thread.wait()?;
assert!(state_main.has_exited());
}
_ => unreachable!(),
}
Ok(())
}
}