use std::collections::HashMap;
use std::error::Error;
use std::fmt::Result as FmtResult;
use std::fmt::{Display, Formatter};
use std::mem::transmute;
use nix::sys::ptrace::Options as NixPtraceOptions;
use nix::sys::ptrace::{getevent, Event};
use nix::sys::signal::Signal as NixSignal;
use nix::sys::wait::WaitStatus;
use crate::breakpoint::BreakpointId;
use crate::error::{CouldNotResume, CouldNotSetOptions, UnexpectedExit};
use crate::process::{Pid, TargetController, TargetProcess};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
#[non_exhaustive]
pub enum Signal {
SIGHUP = 1,
SIGINT,
SIGQUIT,
SIGILL,
SIGTRAP,
SIGABRT,
SIGIOT,
SIGBUS,
SIGFPE,
SIGKILL,
SIGUSR1,
SIGSEGV,
SIGUSR2,
SIGPIPE,
SIGALRM,
SIGTERM,
SIGSTKFLT,
SIGCHLD,
SIGCLD,
SIGCONT,
SIGSTOP,
SIGTSTP,
SIGTTIN,
SIGTTOU,
SIGURG,
SIGXCPU,
SIGXFSZ,
SIGVTALRM,
SIGPROF,
SIGWINCH,
SIGPOLL,
SIGIO,
SIGPWR,
SIGSYS,
}
impl From<NixSignal> for Signal {
fn from(value: NixSignal) -> Self {
unsafe { transmute(value) }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Reason {
Breakpoint(BreakpointId),
Exited { exit_code: i32 },
Stopped { signal: Signal },
Signaled {
signal: Signal,
dumped: bool,
},
Trapped,
ThreadCreated { tid: Pid },
}
impl Display for Reason {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Self::Breakpoint(id) => write!(f, "stopped at breakpoint {id:?}"),
Self::Exited { exit_code } => write!(f, "exited with code: {exit_code}"),
Self::Stopped { signal } => write!(f, "stopped with signal {signal:?}"),
Self::Signaled { signal, .. } => write!(f, "killed by signal {signal:?}"),
Self::Trapped => write!(f, "encountered a trap instruction"),
Self::ThreadCreated { tid } => write!(f, "created a new thread (TID={tid})"),
}
}
}
impl Reason {
pub(super) fn from_wait_status<E>(ctrl: &mut TargetController<E>, status: WaitStatus) -> Self
where
E: Executing,
{
match status {
WaitStatus::Exited(_, exit_code) => Self::Exited { exit_code },
WaitStatus::Signaled(_, signal, dumped) => Self::Signaled {
signal: signal.into(),
dumped,
},
WaitStatus::Stopped(_, NixSignal::SIGTRAP) => {
let rip = ctrl.get_registers().unwrap().rip as usize;
ctrl.process()
.breakpoints()
.find(|brk| brk.address() == rip - 1)
.map_or(Self::Trapped, |brk| Self::Breakpoint(brk.id()))
}
WaitStatus::Stopped(_, signal) => Self::Stopped {
signal: signal.into(),
},
WaitStatus::PtraceEvent(_, _, event) => match unsafe { transmute(event) } {
Event::PTRACE_EVENT_CLONE => {
let tid = getevent(ctrl.process().pid().into()).unwrap();
Self::ThreadCreated {
tid: Pid::from_raw(tid as i32),
}
}
_ => todo!(),
},
WaitStatus::PtraceSyscall(_) => todo!(),
WaitStatus::Continued(_) => {
unreachable!("The remote process should not be 'continued' if it was stopped.")
}
WaitStatus::StillAlive => unreachable!("Steroid's wait is not WNOHANG"),
}
}
}
#[must_use]
pub enum RunningState<E>
where
E: Executing,
{
Alive(TargetController<E>),
Exited { tid: Pid, reason: Reason },
}
impl<E> RunningState<E>
where
E: Executing,
{
#[allow(clippy::missing_const_for_fn)]
pub fn assume_alive(self) -> Result<TargetController<E>, UnexpectedExit> {
match self {
Self::Alive(ctrl) => Ok(ctrl),
Self::Exited { tid: _, reason } => Err(UnexpectedExit { reason }),
}
}
#[must_use]
pub const fn has_exited(&self) -> bool {
matches!(self, Self::Exited { .. })
}
#[must_use]
pub const fn is_alive(&self) -> bool {
matches!(self, Self::Alive(_))
}
#[must_use]
pub const fn reason(&self) -> &Reason {
match self {
Self::Alive(ctrl) => ctrl.reason(),
Self::Exited { reason, .. } => reason,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PtraceEventStrategy {
Unset,
NotifyOnly,
Trace,
}
impl Default for PtraceEventStrategy {
fn default() -> Self {
Self::Unset
}
}
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub enum PtraceOption {
TraceSysGood,
TraceFork,
TraceVFork,
TraceClone,
TraceExec,
TraceVForkDone,
TraceExit,
TraceSeccomp,
ExitKill,
}
impl From<PtraceOption> for NixPtraceOptions {
fn from(value: PtraceOption) -> Self {
match value {
PtraceOption::TraceSysGood => Self::PTRACE_O_TRACESYSGOOD,
PtraceOption::TraceFork => Self::PTRACE_O_TRACEFORK,
PtraceOption::TraceVFork => Self::PTRACE_O_TRACEVFORK,
PtraceOption::TraceClone => Self::PTRACE_O_TRACECLONE,
PtraceOption::TraceExec => Self::PTRACE_O_TRACEEXEC,
PtraceOption::TraceVForkDone => Self::PTRACE_O_TRACEVFORKDONE,
PtraceOption::TraceExit => Self::PTRACE_O_TRACEEXIT,
PtraceOption::TraceSeccomp => Self::PTRACE_O_TRACESECCOMP,
PtraceOption::ExitKill => Self::PTRACE_O_EXITKILL,
}
}
}
#[derive(Default)]
pub struct PtraceOptionMap(HashMap<PtraceOption, PtraceEventStrategy>);
impl PtraceOptionMap {
#[must_use]
pub fn get(&self, option: PtraceOption) -> PtraceEventStrategy {
self.0.get(&option).copied().unwrap_or_default()
}
pub fn set(
&mut self,
option: PtraceOption,
strategy: PtraceEventStrategy,
) -> PtraceEventStrategy {
self.0.insert(option, strategy).unwrap_or_default()
}
#[must_use]
pub(crate) fn all_set_options(options: NixPtraceOptions) -> Vec<PtraceOption> {
let matches = |constant: NixPtraceOptions| -> bool { !(options & constant).is_empty() };
let constants = [
(
NixPtraceOptions::PTRACE_O_TRACESYSGOOD,
PtraceOption::TraceSysGood,
),
(
NixPtraceOptions::PTRACE_O_TRACEFORK,
PtraceOption::TraceFork,
),
(
NixPtraceOptions::PTRACE_O_TRACEVFORK,
PtraceOption::TraceVFork,
),
(
NixPtraceOptions::PTRACE_O_TRACECLONE,
PtraceOption::TraceClone,
),
(
NixPtraceOptions::PTRACE_O_TRACEEXEC,
PtraceOption::TraceExec,
),
(
NixPtraceOptions::PTRACE_O_TRACEVFORKDONE,
PtraceOption::TraceVForkDone,
),
(
NixPtraceOptions::PTRACE_O_TRACEEXIT,
PtraceOption::TraceExit,
),
(
NixPtraceOptions::PTRACE_O_TRACESECCOMP,
PtraceOption::TraceSeccomp,
),
(NixPtraceOptions::PTRACE_O_EXITKILL, PtraceOption::ExitKill),
];
constants
.iter()
.filter_map(|(nix, opt)| if matches(*nix) { Some(*opt) } else { None })
.collect()
}
#[must_use]
pub(crate) fn as_ptrace_options(&self) -> NixPtraceOptions {
self.0
.iter()
.fold(NixPtraceOptions::empty(), |options, (k, v)| {
options | v.to_ptrace_flag(*k)
})
}
}
impl PtraceEventStrategy {
#[must_use]
pub(crate) fn to_ptrace_flag(self, option: PtraceOption) -> NixPtraceOptions {
match self {
Self::Unset => NixPtraceOptions::empty(),
_ => option.into(),
}
}
}
pub trait Executing: Sized {
type StoppedRepresentation: AsRef<Self>;
type WaitError: Error;
fn process(&self) -> &TargetProcess;
#[must_use]
fn pgid(&self) -> Pid {
self.process().pid()
}
fn pid(&self) -> Pid;
fn set_ptrace_option(
ctrl: &mut TargetController<Self>,
option: PtraceOption,
strategy: PtraceEventStrategy,
) -> Result<PtraceEventStrategy, CouldNotSetOptions>;
fn wait(self) -> Result<RunningState<Self>, Self::WaitError>;
fn resume(ctrl: TargetController<Self>) -> Result<Self, CouldNotResume>;
}