Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/arch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<NonZeroUsize>)>;

/// 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<usize> {
None
}
}

/// Stub implementation -- Returns `None` for all raw IDs.
Expand Down
2 changes: 2 additions & 0 deletions src/stub/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub(crate) enum InternalError<T, C> {
TracepointFeatureUnimplemented(u8),
TracepointUnsupportedSourceEnumeration,
MissingMultiThreadSchedulerLocking,
MissingToRawId,

// Internal - A non-fatal error occurred (with errno-style error code)
//
Expand Down Expand Up @@ -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!"),
}
Expand Down
55 changes: 54 additions & 1 deletion src/stub/state_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ 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;
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;

Expand Down Expand Up @@ -251,12 +253,63 @@ impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::Idle<T>,
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<T>,
) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
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<T>,
regs: &mut dyn Iterator<Item = (<<T as Target>::Arch as Arch>::RegId, &[u8])>,
) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
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<T>,
regs: Option<&mut dyn Iterator<Item = (<<T as Target>::Arch as Arch>::RegId, &[u8])>>,
) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
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 {
Expand Down
19 changes: 19 additions & 0 deletions src/stub/stop_reason.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,25 @@ pub enum BaseStopReason<Tid, U> {
VForkDone(Tid),
}

impl<Tid, U> BaseStopReason<Tid, U> {
/// 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
Expand Down
Loading