diff --git a/src/arch.rs b/src/arch.rs index 5c242c7..e84d738 100644 --- a/src/arch.rs +++ b/src/arch.rs @@ -41,6 +41,17 @@ pub trait RegId: Sized + Debug { /// /// Returns `None` if the register is not available. fn from_raw_id(id: usize) -> Option<(Self, Option)>; + + /// Map a `RegId` back to a raw GDB register number. + /// + /// Returns `None` if this mapping direction is not implemented. + /// + /// This method currently only needs to return `Some` for a + /// register if that register is sent with + /// [`crate::stub::state_machine::GdbStubStateMachineInner::report_stop_with_regs`]. + fn to_raw_id(&self) -> Option { + None + } } /// Stub implementation -- Returns `None` for all raw IDs. diff --git a/src/stub/error.rs b/src/stub/error.rs index ec1ef93..b331c50 100644 --- a/src/stub/error.rs +++ b/src/stub/error.rs @@ -44,6 +44,7 @@ pub(crate) enum InternalError { TracepointFeatureUnimplemented(u8), TracepointUnsupportedSourceEnumeration, MissingMultiThreadSchedulerLocking, + MissingToRawId, // Internal - A non-fatal error occurred (with errno-style error code) // @@ -149,6 +150,7 @@ where TracepointFeatureUnimplemented(feat) => write!(f, "GDB client sent us a tracepoint packet using feature {}, but `gdbstub` doesn't implement it. If this is something you require, please file an issue at https://github.com/daniel5151/gdbstub/issues", *feat as char), TracepointUnsupportedSourceEnumeration => write!(f, "The target doesn't support the gdbstub TracepointSource extension, but attempted to transition to enumerating tracepoint sources"), MissingMultiThreadSchedulerLocking => write!(f, "GDB requested Scheduler Locking, but the Target does not implement the `MultiThreadSchedulerLocking` IDET"), + MissingToRawId => write!(f, "A RegId was used with an API that requires raw register IDs to be available (e.g. `report_stop_with_regs`) but returned `None` from `to_raw_id()`"), NonFatalError(_) => write!(f, "Internal non-fatal error. You should never see this! Please file an issue if you do!"), } diff --git a/src/stub/state_machine.rs b/src/stub/state_machine.rs index 4a41aaf..963c9b9 100644 --- a/src/stub/state_machine.rs +++ b/src/stub/state_machine.rs @@ -37,6 +37,7 @@ use super::core_impl::State; use super::DisconnectReason; use super::GdbStub; use crate::arch::Arch; +use crate::arch::RegId; use crate::conn::Connection; use crate::protocol::recv_packet::RecvPacketStateMachine; use crate::protocol::Packet; @@ -44,6 +45,7 @@ use crate::protocol::ResponseWriter; use crate::stub::error::GdbStubError; use crate::stub::error::InternalError; use crate::stub::stop_reason::IntoStopReason; +use crate::stub::BaseStopReason; use crate::target::Target; use managed::ManagedSlice; @@ -251,12 +253,63 @@ impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::Idle, impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::Running, T, C> { /// Report a target stop reason back to GDB. pub fn report_stop( + self, + target: &mut T, + reason: impl IntoStopReason, + ) -> Result, GdbStubError> { + self.report_stop_impl(target, reason, None) + } + + /// Report a target stop reason back to GDB, including expedited + /// register values in the stop reply T-packet. + /// + /// The iterator yields `(register_number, value_bytes)` pairs that + /// are written as expedition registers in the T-packet. Values + /// should be in target byte order (typically little-endian). + /// + /// This may be useful to use, rather than [`Self::report_stop`], when + /// we want to provide register values immediately to, for + /// example, avoid a round-trip, or work around a quirk/bug in a + /// debugger that does not otherwise request new register values. + /// + /// Note that if you use this method, you'll need to provide + /// [`crate::arch::RegId::to_raw_id`] so that the raw register IDs + /// can be sent. + pub fn report_stop_with_regs( + self, + target: &mut T, + reason: impl IntoStopReason, + regs: &mut dyn Iterator::Arch as Arch>::RegId, &[u8])>, + ) -> Result, GdbStubError> { + self.report_stop_impl(target, reason, Some(regs)) + } + + /// Shared implementation for the + /// `report_stop`/`report_stop_with_regs` API. Takes an `Option` + /// around the `&mut dyn Iterator` to avoid making a dynamic + /// vtable dispatch in the common `report_stop` case. + fn report_stop_impl( mut self, target: &mut T, reason: impl IntoStopReason, + regs: Option<&mut dyn Iterator::Arch as Arch>::RegId, &[u8])>>, ) -> Result, GdbStubError> { + let reason: BaseStopReason<_, _> = reason.into(); let mut res = ResponseWriter::new(&mut self.i.conn, target.use_rle()); - let event = self.i.inner.finish_exec(&mut res, target, reason.into())?; + let event = self.i.inner.finish_exec(&mut res, target, reason)?; + + if let Some(regs) = regs { + if reason.is_t_packet() { + for (reg_id, value) in regs { + let reg = reg_id.to_raw_id().ok_or(InternalError::MissingToRawId)?; + res.write_num(reg).map_err(InternalError::from)?; + res.write_str(":").map_err(InternalError::from)?; + res.write_hex_buf(value).map_err(InternalError::from)?; + res.write_str(";").map_err(InternalError::from)?; + } + } + } + res.flush().map_err(InternalError::from)?; Ok(match event { diff --git a/src/stub/stop_reason.rs b/src/stub/stop_reason.rs index 84b6a36..c378190 100644 --- a/src/stub/stop_reason.rs +++ b/src/stub/stop_reason.rs @@ -135,6 +135,25 @@ pub enum BaseStopReason { VForkDone(Tid), } +impl BaseStopReason { + /// Does this stop reason respond with a `T` packet? + pub(crate) fn is_t_packet(&self) -> bool { + match self { + Self::SignalWithThread { .. } + | Self::SwBreak(_) + | Self::HwBreak(_) + | Self::Watch { .. } + | Self::ReplayLog { .. } + | Self::CatchSyscall { .. } + | Self::Library(_) + | Self::Fork { .. } + | Self::VFork { .. } + | Self::VForkDone(_) => true, + Self::DoneStep | Self::Signal(_) | Self::Exited(_) | Self::Terminated(_) => false, + } + } +} + /// A stop reason for a single threaded target. /// /// Threads are identified using the unit type `()` (as there is only a single