aboutsummaryrefslogtreecommitdiff
path: root/src/stub/core_impl/resume.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/stub/core_impl/resume.rs')
-rw-r--r--src/stub/core_impl/resume.rs432
1 files changed, 432 insertions, 0 deletions
diff --git a/src/stub/core_impl/resume.rs b/src/stub/core_impl/resume.rs
new file mode 100644
index 0000000..e63381a
--- /dev/null
+++ b/src/stub/core_impl/resume.rs
@@ -0,0 +1,432 @@
+use super::prelude::*;
+use crate::protocol::commands::ext::Resume;
+
+use crate::arch::Arch;
+use crate::common::{Signal, Tid};
+use crate::protocol::commands::_vCont::Actions;
+use crate::protocol::{SpecificIdKind, SpecificThreadId};
+use crate::stub::MultiThreadStopReason;
+use crate::target::ext::base::reverse_exec::ReplayLogPosition;
+use crate::target::ext::base::ResumeOps;
+use crate::target::ext::catch_syscalls::CatchSyscallPosition;
+use crate::FAKE_PID;
+
+use super::DisconnectReason;
+
+impl<T: Target, C: Connection> GdbStubImpl<T, C> {
+ pub(crate) fn handle_stop_resume(
+ &mut self,
+ res: &mut ResponseWriter<'_, C>,
+ target: &mut T,
+ command: Resume<'_>,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ let mut ops = match target.base_ops().resume_ops() {
+ Some(ops) => ops,
+ None => return Ok(HandlerStatus::Handled),
+ };
+
+ let actions = match command {
+ Resume::vCont(cmd) => {
+ use crate::protocol::commands::_vCont::vCont;
+ match cmd {
+ vCont::Query => {
+ // Continue is part of the base protocol
+ res.write_str("vCont;c;C")?;
+
+ // Single stepping is optional
+ if match &mut ops {
+ ResumeOps::SingleThread(ops) => ops.support_single_step().is_some(),
+ ResumeOps::MultiThread(ops) => ops.support_single_step().is_some(),
+ } {
+ res.write_str(";s;S")?;
+ }
+
+ // Range stepping is optional
+ if match &mut ops {
+ ResumeOps::SingleThread(ops) => ops.support_range_step().is_some(),
+ ResumeOps::MultiThread(ops) => ops.support_range_step().is_some(),
+ } {
+ res.write_str(";r")?;
+ }
+
+ // doesn't actually invoke vCont
+ return Ok(HandlerStatus::Handled);
+ }
+ vCont::Actions(actions) => actions,
+ }
+ }
+ // TODO?: support custom resume addr in 'c' and 's'
+ //
+ // vCont doesn't have a notion of "resume addr", and since the implementation of these
+ // packets reuse vCont infrastructure, supporting this obscure feature will be a bit
+ // annoying...
+ //
+ // TODO: add `support_legacy_s_c_packets` flag (similar to `use_X_packet`)
+ Resume::c(_) => Actions::new_continue(SpecificThreadId {
+ pid: None,
+ tid: self.current_resume_tid,
+ }),
+ Resume::s(_) => Actions::new_step(SpecificThreadId {
+ pid: None,
+ tid: self.current_resume_tid,
+ }),
+ };
+
+ self.do_vcont(ops, actions)
+ }
+
+ fn do_vcont_single_thread(
+ ops: &mut dyn crate::target::ext::base::singlethread::SingleThreadResume<
+ Arch = T::Arch,
+ Error = T::Error,
+ >,
+ actions: &Actions<'_>,
+ ) -> Result<(), Error<T::Error, C::Error>> {
+ use crate::protocol::commands::_vCont::VContKind;
+
+ let mut actions = actions.iter();
+ let first_action = actions
+ .next()
+ .ok_or(Error::PacketParse(
+ crate::protocol::PacketParseError::MalformedCommand,
+ ))?
+ .ok_or(Error::PacketParse(
+ crate::protocol::PacketParseError::MalformedCommand,
+ ))?;
+
+ let invalid_second_action = match actions.next() {
+ None => false,
+ Some(act) => match act {
+ None => {
+ return Err(Error::PacketParse(
+ crate::protocol::PacketParseError::MalformedCommand,
+ ))
+ }
+ Some(act) => !matches!(act.kind, VContKind::Continue),
+ },
+ };
+
+ if invalid_second_action || actions.next().is_some() {
+ return Err(Error::PacketUnexpected);
+ }
+
+ match first_action.kind {
+ VContKind::Continue | VContKind::ContinueWithSig(_) => {
+ let signal = match first_action.kind {
+ VContKind::ContinueWithSig(sig) => Some(sig),
+ _ => None,
+ };
+
+ ops.resume(signal).map_err(Error::TargetError)?;
+ Ok(())
+ }
+ VContKind::Step | VContKind::StepWithSig(_) if ops.support_single_step().is_some() => {
+ let ops = ops.support_single_step().unwrap();
+
+ let signal = match first_action.kind {
+ VContKind::StepWithSig(sig) => Some(sig),
+ _ => None,
+ };
+
+ ops.step(signal).map_err(Error::TargetError)?;
+ Ok(())
+ }
+ VContKind::RangeStep(start, end) if ops.support_range_step().is_some() => {
+ let ops = ops.support_range_step().unwrap();
+
+ let start = start.decode().map_err(|_| Error::TargetMismatch)?;
+ let end = end.decode().map_err(|_| Error::TargetMismatch)?;
+
+ ops.resume_range_step(start, end)
+ .map_err(Error::TargetError)?;
+ Ok(())
+ }
+ // TODO: update this case when non-stop mode is implemented
+ VContKind::Stop => Err(Error::PacketUnexpected),
+
+ // Instead of using `_ =>`, explicitly list out any remaining unguarded cases.
+ VContKind::RangeStep(..) | VContKind::Step | VContKind::StepWithSig(..) => {
+ error!("GDB client sent resume action not reported by `vCont?`");
+ Err(Error::PacketUnexpected)
+ }
+ }
+ }
+
+ fn do_vcont_multi_thread(
+ ops: &mut dyn crate::target::ext::base::multithread::MultiThreadResume<
+ Arch = T::Arch,
+ Error = T::Error,
+ >,
+ actions: &Actions<'_>,
+ ) -> Result<(), Error<T::Error, C::Error>> {
+ ops.clear_resume_actions().map_err(Error::TargetError)?;
+
+ for action in actions.iter() {
+ use crate::protocol::commands::_vCont::VContKind;
+
+ let action = action.ok_or(Error::PacketParse(
+ crate::protocol::PacketParseError::MalformedCommand,
+ ))?;
+
+ match action.kind {
+ VContKind::Continue | VContKind::ContinueWithSig(_) => {
+ let signal = match action.kind {
+ VContKind::ContinueWithSig(sig) => Some(sig),
+ _ => None,
+ };
+
+ match action.thread.map(|thread| thread.tid) {
+ // An action with no thread-id matches all threads
+ None | Some(SpecificIdKind::All) => {
+ // Target API contract specifies that the default
+ // resume action for all threads is continue.
+ }
+ Some(SpecificIdKind::WithId(tid)) => ops
+ .set_resume_action_continue(tid, signal)
+ .map_err(Error::TargetError)?,
+ }
+ }
+ VContKind::Step | VContKind::StepWithSig(_)
+ if ops.support_single_step().is_some() =>
+ {
+ let ops = ops.support_single_step().unwrap();
+
+ let signal = match action.kind {
+ VContKind::StepWithSig(sig) => Some(sig),
+ _ => None,
+ };
+
+ match action.thread.map(|thread| thread.tid) {
+ // An action with no thread-id matches all threads
+ None | Some(SpecificIdKind::All) => {
+ error!("GDB client sent 'step' as default resume action");
+ return Err(Error::PacketUnexpected);
+ }
+ Some(SpecificIdKind::WithId(tid)) => {
+ ops.set_resume_action_step(tid, signal)
+ .map_err(Error::TargetError)?;
+ }
+ };
+ }
+
+ VContKind::RangeStep(start, end) if ops.support_range_step().is_some() => {
+ let ops = ops.support_range_step().unwrap();
+
+ match action.thread.map(|thread| thread.tid) {
+ // An action with no thread-id matches all threads
+ None | Some(SpecificIdKind::All) => {
+ error!("GDB client sent 'range step' as default resume action");
+ return Err(Error::PacketUnexpected);
+ }
+ Some(SpecificIdKind::WithId(tid)) => {
+ let start = start.decode().map_err(|_| Error::TargetMismatch)?;
+ let end = end.decode().map_err(|_| Error::TargetMismatch)?;
+
+ ops.set_resume_action_range_step(tid, start, end)
+ .map_err(Error::TargetError)?;
+ }
+ };
+ }
+ // TODO: update this case when non-stop mode is implemented
+ VContKind::Stop => return Err(Error::PacketUnexpected),
+
+ // Instead of using `_ =>`, explicitly list out any remaining unguarded cases.
+ VContKind::RangeStep(..) | VContKind::Step | VContKind::StepWithSig(..) => {
+ error!("GDB client sent resume action not reported by `vCont?`");
+ return Err(Error::PacketUnexpected);
+ }
+ }
+ }
+
+ ops.resume().map_err(Error::TargetError)
+ }
+
+ fn do_vcont(
+ &mut self,
+ ops: ResumeOps<'_, T::Arch, T::Error>,
+ actions: Actions<'_>,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ match ops {
+ ResumeOps::SingleThread(ops) => Self::do_vcont_single_thread(ops, &actions)?,
+ ResumeOps::MultiThread(ops) => Self::do_vcont_multi_thread(ops, &actions)?,
+ };
+
+ Ok(HandlerStatus::DeferredStopReason)
+ }
+
+ fn write_stop_common(
+ &mut self,
+ res: &mut ResponseWriter<'_, C>,
+ tid: Option<Tid>,
+ signal: Signal,
+ ) -> Result<(), Error<T::Error, C::Error>> {
+ res.write_str("T")?;
+ res.write_num(signal as u8)?;
+
+ if let Some(tid) = tid {
+ self.current_mem_tid = tid;
+ self.current_resume_tid = SpecificIdKind::WithId(tid);
+
+ res.write_str("thread:")?;
+ res.write_specific_thread_id(SpecificThreadId {
+ pid: self
+ .features
+ .multiprocess()
+ .then_some(SpecificIdKind::WithId(FAKE_PID)),
+ tid: SpecificIdKind::WithId(tid),
+ })?;
+ res.write_str(";")?;
+ }
+
+ Ok(())
+ }
+
+ pub(crate) fn finish_exec(
+ &mut self,
+ res: &mut ResponseWriter<'_, C>,
+ target: &mut T,
+ stop_reason: MultiThreadStopReason<<T::Arch as Arch>::Usize>,
+ ) -> Result<FinishExecStatus, Error<T::Error, C::Error>> {
+ macro_rules! guard_reverse_exec {
+ () => {{
+ if let Some(resume_ops) = target.base_ops().resume_ops() {
+ let (reverse_cont, reverse_step) = match resume_ops {
+ ResumeOps::MultiThread(ops) => (
+ ops.support_reverse_cont().is_some(),
+ ops.support_reverse_step().is_some(),
+ ),
+ ResumeOps::SingleThread(ops) => (
+ ops.support_reverse_cont().is_some(),
+ ops.support_reverse_step().is_some(),
+ ),
+ };
+
+ reverse_cont || reverse_step
+ } else {
+ false
+ }
+ }};
+ }
+
+ macro_rules! guard_break {
+ ($op:ident) => {
+ target
+ .support_breakpoints()
+ .and_then(|ops| ops.$op())
+ .is_some()
+ };
+ }
+
+ macro_rules! guard_catch_syscall {
+ () => {
+ target.support_catch_syscalls().is_some()
+ };
+ }
+
+ let status = match stop_reason {
+ MultiThreadStopReason::DoneStep => {
+ res.write_str("S")?;
+ res.write_num(Signal::SIGTRAP as u8)?;
+ FinishExecStatus::Handled
+ }
+ MultiThreadStopReason::Signal(sig) => {
+ res.write_str("S")?;
+ res.write_num(sig as u8)?;
+ FinishExecStatus::Handled
+ }
+ MultiThreadStopReason::Exited(code) => {
+ res.write_str("W")?;
+ res.write_num(code)?;
+ FinishExecStatus::Disconnect(DisconnectReason::TargetExited(code))
+ }
+ MultiThreadStopReason::Terminated(sig) => {
+ res.write_str("X")?;
+ res.write_num(sig as u8)?;
+ FinishExecStatus::Disconnect(DisconnectReason::TargetTerminated(sig))
+ }
+ MultiThreadStopReason::SignalWithThread { tid, signal } => {
+ self.write_stop_common(res, Some(tid), signal)?;
+ FinishExecStatus::Handled
+ }
+ MultiThreadStopReason::SwBreak(tid) if guard_break!(support_sw_breakpoint) => {
+ crate::__dead_code_marker!("sw_breakpoint", "stop_reason");
+
+ self.write_stop_common(res, Some(tid), Signal::SIGTRAP)?;
+ res.write_str("swbreak:;")?;
+ FinishExecStatus::Handled
+ }
+ MultiThreadStopReason::HwBreak(tid) if guard_break!(support_hw_breakpoint) => {
+ crate::__dead_code_marker!("hw_breakpoint", "stop_reason");
+
+ self.write_stop_common(res, Some(tid), Signal::SIGTRAP)?;
+ res.write_str("hwbreak:;")?;
+ FinishExecStatus::Handled
+ }
+ MultiThreadStopReason::Watch { tid, kind, addr }
+ if guard_break!(support_hw_watchpoint) =>
+ {
+ crate::__dead_code_marker!("hw_watchpoint", "stop_reason");
+
+ self.write_stop_common(res, Some(tid), Signal::SIGTRAP)?;
+
+ use crate::target::ext::breakpoints::WatchKind;
+ match kind {
+ WatchKind::Write => res.write_str("watch:")?,
+ WatchKind::Read => res.write_str("rwatch:")?,
+ WatchKind::ReadWrite => res.write_str("awatch:")?,
+ }
+ res.write_num(addr)?;
+ res.write_str(";")?;
+ FinishExecStatus::Handled
+ }
+ MultiThreadStopReason::ReplayLog { tid, pos } if guard_reverse_exec!() => {
+ crate::__dead_code_marker!("reverse_exec", "stop_reason");
+
+ self.write_stop_common(res, tid, Signal::SIGTRAP)?;
+
+ res.write_str("replaylog:")?;
+ res.write_str(match pos {
+ ReplayLogPosition::Begin => "begin",
+ ReplayLogPosition::End => "end",
+ })?;
+ res.write_str(";")?;
+
+ FinishExecStatus::Handled
+ }
+ MultiThreadStopReason::CatchSyscall {
+ tid,
+ number,
+ position,
+ } if guard_catch_syscall!() => {
+ crate::__dead_code_marker!("catch_syscall", "stop_reason");
+
+ self.write_stop_common(res, tid, Signal::SIGTRAP)?;
+
+ res.write_str(match position {
+ CatchSyscallPosition::Entry => "syscall_entry:",
+ CatchSyscallPosition::Return => "syscall_return:",
+ })?;
+ res.write_num(number)?;
+ res.write_str(";")?;
+
+ FinishExecStatus::Handled
+ }
+ // Explicitly avoid using `_ =>` to handle the "unguarded" variants, as doing so would
+ // squelch the useful compiler error that crops up whenever stop reasons are added.
+ MultiThreadStopReason::SwBreak(_)
+ | MultiThreadStopReason::HwBreak(_)
+ | MultiThreadStopReason::Watch { .. }
+ | MultiThreadStopReason::ReplayLog { .. }
+ | MultiThreadStopReason::CatchSyscall { .. } => {
+ return Err(Error::UnsupportedStopReason);
+ }
+ };
+
+ Ok(status)
+ }
+}
+
+pub(crate) enum FinishExecStatus {
+ Handled,
+ Disconnect(DisconnectReason),
+}