Skip to content

Commit dbe9349

Browse files
authored
Add support for "expedited registers" in T-stop packets. (#189)
* Add support for "expedited registers" in T-stop packets. This is required to work around an issue encountered with LLDB with a Wasm target over gdbstub: the Wasm-target client incorrectly caches (does not re-query) the PC, and needs to be updated after a stop either via a response with the `QListThreadsInStopReply` capability or by sending explicit updated register values directly in the T-stop packets. I opted for the latter here as it's a little simpler.
1 parent 8811bba commit dbe9349

4 files changed

Lines changed: 86 additions & 1 deletion

File tree

src/arch.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ pub trait RegId: Sized + Debug {
4141
///
4242
/// Returns `None` if the register is not available.
4343
fn from_raw_id(id: usize) -> Option<(Self, Option<NonZeroUsize>)>;
44+
45+
/// Map a `RegId` back to a raw GDB register number.
46+
///
47+
/// Returns `None` if this mapping direction is not implemented.
48+
///
49+
/// This method currently only needs to return `Some` for a
50+
/// register if that register is sent with
51+
/// [`crate::stub::state_machine::GdbStubStateMachineInner::report_stop_with_regs`].
52+
fn to_raw_id(&self) -> Option<usize> {
53+
None
54+
}
4455
}
4556

4657
/// Stub implementation -- Returns `None` for all raw IDs.

src/stub/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub(crate) enum InternalError<T, C> {
4444
TracepointFeatureUnimplemented(u8),
4545
TracepointUnsupportedSourceEnumeration,
4646
MissingMultiThreadSchedulerLocking,
47+
MissingToRawId,
4748

4849
// Internal - A non-fatal error occurred (with errno-style error code)
4950
//
@@ -149,6 +150,7 @@ where
149150
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),
150151
TracepointUnsupportedSourceEnumeration => write!(f, "The target doesn't support the gdbstub TracepointSource extension, but attempted to transition to enumerating tracepoint sources"),
151152
MissingMultiThreadSchedulerLocking => write!(f, "GDB requested Scheduler Locking, but the Target does not implement the `MultiThreadSchedulerLocking` IDET"),
153+
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()`"),
152154

153155
NonFatalError(_) => write!(f, "Internal non-fatal error. You should never see this! Please file an issue if you do!"),
154156
}

src/stub/state_machine.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ use super::core_impl::State;
3737
use super::DisconnectReason;
3838
use super::GdbStub;
3939
use crate::arch::Arch;
40+
use crate::arch::RegId;
4041
use crate::conn::Connection;
4142
use crate::protocol::recv_packet::RecvPacketStateMachine;
4243
use crate::protocol::Packet;
4344
use crate::protocol::ResponseWriter;
4445
use crate::stub::error::GdbStubError;
4546
use crate::stub::error::InternalError;
4647
use crate::stub::stop_reason::IntoStopReason;
48+
use crate::stub::BaseStopReason;
4749
use crate::target::Target;
4850
use managed::ManagedSlice;
4951

@@ -251,12 +253,63 @@ impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::Idle<T>,
251253
impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::Running, T, C> {
252254
/// Report a target stop reason back to GDB.
253255
pub fn report_stop(
256+
self,
257+
target: &mut T,
258+
reason: impl IntoStopReason<T>,
259+
) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
260+
self.report_stop_impl(target, reason, None)
261+
}
262+
263+
/// Report a target stop reason back to GDB, including expedited
264+
/// register values in the stop reply T-packet.
265+
///
266+
/// The iterator yields `(register_number, value_bytes)` pairs that
267+
/// are written as expedition registers in the T-packet. Values
268+
/// should be in target byte order (typically little-endian).
269+
///
270+
/// This may be useful to use, rather than [`Self::report_stop`], when
271+
/// we want to provide register values immediately to, for
272+
/// example, avoid a round-trip, or work around a quirk/bug in a
273+
/// debugger that does not otherwise request new register values.
274+
///
275+
/// Note that if you use this method, you'll need to provide
276+
/// [`crate::arch::RegId::to_raw_id`] so that the raw register IDs
277+
/// can be sent.
278+
pub fn report_stop_with_regs(
279+
self,
280+
target: &mut T,
281+
reason: impl IntoStopReason<T>,
282+
regs: &mut dyn Iterator<Item = (<<T as Target>::Arch as Arch>::RegId, &[u8])>,
283+
) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
284+
self.report_stop_impl(target, reason, Some(regs))
285+
}
286+
287+
/// Shared implementation for the
288+
/// `report_stop`/`report_stop_with_regs` API. Takes an `Option`
289+
/// around the `&mut dyn Iterator` to avoid making a dynamic
290+
/// vtable dispatch in the common `report_stop` case.
291+
fn report_stop_impl(
254292
mut self,
255293
target: &mut T,
256294
reason: impl IntoStopReason<T>,
295+
regs: Option<&mut dyn Iterator<Item = (<<T as Target>::Arch as Arch>::RegId, &[u8])>>,
257296
) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
297+
let reason: BaseStopReason<_, _> = reason.into();
258298
let mut res = ResponseWriter::new(&mut self.i.conn, target.use_rle());
259-
let event = self.i.inner.finish_exec(&mut res, target, reason.into())?;
299+
let event = self.i.inner.finish_exec(&mut res, target, reason)?;
300+
301+
if let Some(regs) = regs {
302+
if reason.is_t_packet() {
303+
for (reg_id, value) in regs {
304+
let reg = reg_id.to_raw_id().ok_or(InternalError::MissingToRawId)?;
305+
res.write_num(reg).map_err(InternalError::from)?;
306+
res.write_str(":").map_err(InternalError::from)?;
307+
res.write_hex_buf(value).map_err(InternalError::from)?;
308+
res.write_str(";").map_err(InternalError::from)?;
309+
}
310+
}
311+
}
312+
260313
res.flush().map_err(InternalError::from)?;
261314

262315
Ok(match event {

src/stub/stop_reason.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,25 @@ pub enum BaseStopReason<Tid, U> {
135135
VForkDone(Tid),
136136
}
137137

138+
impl<Tid, U> BaseStopReason<Tid, U> {
139+
/// Does this stop reason respond with a `T` packet?
140+
pub(crate) fn is_t_packet(&self) -> bool {
141+
match self {
142+
Self::SignalWithThread { .. }
143+
| Self::SwBreak(_)
144+
| Self::HwBreak(_)
145+
| Self::Watch { .. }
146+
| Self::ReplayLog { .. }
147+
| Self::CatchSyscall { .. }
148+
| Self::Library(_)
149+
| Self::Fork { .. }
150+
| Self::VFork { .. }
151+
| Self::VForkDone(_) => true,
152+
Self::DoneStep | Self::Signal(_) | Self::Exited(_) | Self::Terminated(_) => false,
153+
}
154+
}
155+
}
156+
138157
/// A stop reason for a single threaded target.
139158
///
140159
/// Threads are identified using the unit type `()` (as there is only a single

0 commit comments

Comments
 (0)