1 //! Low-level state-machine interface that underpins [`GdbStub`].
2 //
3 // TODO: write some proper documentation + examples of how to interface with
4 // this API.
5 //!
6 //! # Hey, what gives? Where are all the docs!?
7 //!
8 //! Yep, sorry about that!
9 //!
10 //! `gdbstub` 0.6 turned out ot be a pretty massive release, and documenting
11 //! everything has proven to be a somewhat gargantuan task that's kept delaying
12 //! the release data further and further back...
13 //!
14 //! To avoid blocking the release any further, I've decided to leave this bit of
15 //! the API sparsely documented.
16 //!
17 //! If you're interested in using this API directly (e.g: to integrate `gdbstub`
18 //! into a `no_std` project, or to use `gdbstub` in a non-blocking manner
19 //! alongside `async/await` / a project specific event loop), your best bet
20 //! would be to review the following bits of code to get a feel for the API:
21 //!
22 //! - The implementation of [`GdbStub::run_blocking`]
23 //! - Implementations of [`BlockingEventLoop`] used alongside
24 //!   `GdbStub::run_blocking` (e.g: the in-tree `armv4t` / `armv4t_multicore`
25 //!   examples)
26 //! - Real-world projects using the API (see the repo's README.md)
27 //!
28 //! If you have any questions, feel free to open a discussion thread over at the
29 //! [`gdbstub` GitHub repo](https://github.com/daniel5151/gdbstub/).
30 //!
31 //! [`BlockingEventLoop`]: super::run_blocking::BlockingEventLoop
32 //! [`GdbStub::run_blocking`]: super::GdbStub::run_blocking
33 
34 use super::core_impl::FinishExecStatus;
35 use super::core_impl::GdbStubImpl;
36 use super::core_impl::State;
37 use super::DisconnectReason;
38 use super::GdbStub;
39 use crate::arch::Arch;
40 use crate::conn::Connection;
41 use crate::protocol::recv_packet::RecvPacketStateMachine;
42 use crate::protocol::Packet;
43 use crate::protocol::ResponseWriter;
44 use crate::stub::error::GdbStubError;
45 use crate::stub::error::InternalError;
46 use crate::stub::stop_reason::IntoStopReason;
47 use crate::target::Target;
48 use managed::ManagedSlice;
49 
50 /// State-machine interface to `GdbStub`.
51 ///
52 /// See the [module level documentation](self) for more details.
53 pub enum GdbStubStateMachine<'a, T, C>
54 where
55     T: Target,
56     C: Connection,
57 {
58     /// The target is completely stopped, and the GDB stub is waiting for
59     /// additional input.
60     Idle(GdbStubStateMachineInner<'a, state::Idle<T>, T, C>),
61     /// The target is currently running, and the GDB client is waiting for
62     /// the target to report a stop reason.
63     ///
64     /// Note that the client may still send packets to the target
65     /// (e.g: to trigger a Ctrl-C interrupt).
66     Running(GdbStubStateMachineInner<'a, state::Running, T, C>),
67     /// The GDB client has sent a Ctrl-C interrupt to the target.
68     CtrlCInterrupt(GdbStubStateMachineInner<'a, state::CtrlCInterrupt, T, C>),
69     /// The GDB client has disconnected.
70     Disconnected(GdbStubStateMachineInner<'a, state::Disconnected, T, C>),
71 }
72 
73 /// State machine typestates.
74 ///
75 /// The types in this module are used to parameterize instances of
76 /// [`GdbStubStateMachineInner`], thereby enforcing that certain API methods
77 /// can only be called while the stub is in a certain state.
78 // As an internal implementation detail, they _also_ carry state-specific
79 // payloads, which are used when transitioning between states.
80 pub mod state {
81     use super::*;
82     use crate::stub::stop_reason::MultiThreadStopReason;
83 
84     // used internally when logging state transitions
85     pub(crate) const MODULE_PATH: &str = concat!(module_path!(), "::");
86 
87     /// Typestate corresponding to the "Idle" state.
88     #[non_exhaustive]
89     pub struct Idle<T: Target> {
90         pub(crate) deferred_ctrlc_stop_reason:
91             Option<MultiThreadStopReason<<<T as Target>::Arch as Arch>::Usize>>,
92     }
93 
94     /// Typestate corresponding to the "Running" state.
95     #[non_exhaustive]
96     pub struct Running {}
97 
98     /// Typestate corresponding to the "CtrlCInterrupt" state.
99     #[non_exhaustive]
100     pub struct CtrlCInterrupt {
101         pub(crate) from_idle: bool,
102     }
103 
104     /// Typestate corresponding to the "Disconnected" state.
105     #[non_exhaustive]
106     pub struct Disconnected {
107         pub(crate) reason: DisconnectReason,
108     }
109 }
110 
111 /// Internal helper macro to convert between a particular inner state into
112 /// its corresponding `GdbStubStateMachine` variant.
113 macro_rules! impl_from_inner {
114         ($state:ident $($tt:tt)*) => {
115             impl<'a, T, C> From<GdbStubStateMachineInner<'a, state::$state $($tt)*, T, C>>
116                 for GdbStubStateMachine<'a, T, C>
117             where
118                 T: Target,
119                 C: Connection,
120             {
121                 fn from(inner: GdbStubStateMachineInner<'a, state::$state $($tt)*, T, C>) -> Self {
122                     GdbStubStateMachine::$state(inner)
123                 }
124             }
125         };
126     }
127 
128 impl_from_inner!(Idle<T>);
129 impl_from_inner!(Running);
130 impl_from_inner!(CtrlCInterrupt);
131 impl_from_inner!(Disconnected);
132 
133 /// Internal helper trait to cut down on boilerplate required to transition
134 /// between states.
135 trait Transition<'a, T, C>
136 where
137     T: Target,
138     C: Connection,
139 {
140     /// Transition between different state machine states
transition<S2>(self, state: S2) -> GdbStubStateMachineInner<'a, S2, T, C>141     fn transition<S2>(self, state: S2) -> GdbStubStateMachineInner<'a, S2, T, C>;
142 }
143 
144 impl<'a, S1, T, C> Transition<'a, T, C> for GdbStubStateMachineInner<'a, S1, T, C>
145 where
146     T: Target,
147     C: Connection,
148 {
149     #[inline(always)]
transition<S2>(self, state: S2) -> GdbStubStateMachineInner<'a, S2, T, C>150     fn transition<S2>(self, state: S2) -> GdbStubStateMachineInner<'a, S2, T, C> {
151         if log::log_enabled!(log::Level::Trace) {
152             let s1 = core::any::type_name::<S1>();
153             let s2 = core::any::type_name::<S2>();
154             log::trace!(
155                 "transition: {:?} --> {:?}",
156                 s1.strip_prefix(state::MODULE_PATH).unwrap_or(s1),
157                 s2.strip_prefix(state::MODULE_PATH).unwrap_or(s2)
158             );
159         }
160         GdbStubStateMachineInner { i: self.i, state }
161     }
162 }
163 
164 // split off `GdbStubStateMachineInner`'s non state-dependant data into separate
165 // struct for code bloat optimization (i.e: `transition` will generate better
166 // code when the struct is cleaved this way).
167 struct GdbStubStateMachineReallyInner<'a, T: Target, C: Connection> {
168     conn: C,
169     packet_buffer: ManagedSlice<'a, u8>,
170     recv_packet: RecvPacketStateMachine,
171     inner: GdbStubImpl<T, C>,
172 }
173 
174 /// Core state machine implementation that is parameterized by various
175 /// [states](state). Can be converted back into the appropriate
176 /// [`GdbStubStateMachine`] variant via [`Into::into`].
177 pub struct GdbStubStateMachineInner<'a, S, T: Target, C: Connection> {
178     i: GdbStubStateMachineReallyInner<'a, T, C>,
179     state: S,
180 }
181 
182 /// Methods which can be called regardless of the current state.
183 impl<'a, S, T: Target, C: Connection> GdbStubStateMachineInner<'a, S, T, C> {
184     /// Return a mutable reference to the underlying connection.
borrow_conn(&mut self) -> &mut C185     pub fn borrow_conn(&mut self) -> &mut C {
186         &mut self.i.conn
187     }
188 }
189 
190 /// Methods which can only be called from the [`GdbStubStateMachine::Idle`]
191 /// state.
192 impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::Idle<T>, T, C> {
193     /// Internal entrypoint into the state machine.
from_plain_gdbstub( stub: GdbStub<'a, T, C>, ) -> GdbStubStateMachineInner<'a, state::Idle<T>, T, C>194     pub(crate) fn from_plain_gdbstub(
195         stub: GdbStub<'a, T, C>,
196     ) -> GdbStubStateMachineInner<'a, state::Idle<T>, T, C> {
197         GdbStubStateMachineInner {
198             i: GdbStubStateMachineReallyInner {
199                 conn: stub.conn,
200                 packet_buffer: stub.packet_buffer,
201                 recv_packet: RecvPacketStateMachine::new(),
202                 inner: stub.inner,
203             },
204             state: state::Idle {
205                 deferred_ctrlc_stop_reason: None,
206             },
207         }
208     }
209 
210     /// Pass a byte to the GDB stub.
incoming_data( mut self, target: &mut T, byte: u8, ) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>>211     pub fn incoming_data(
212         mut self,
213         target: &mut T,
214         byte: u8,
215     ) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
216         let packet_buffer = match self.i.recv_packet.pump(&mut self.i.packet_buffer, byte)? {
217             Some(buf) => buf,
218             None => return Ok(self.into()),
219         };
220 
221         let packet = Packet::from_buf(target, packet_buffer).map_err(InternalError::PacketParse)?;
222         let state = self
223             .i
224             .inner
225             .handle_packet(target, &mut self.i.conn, packet)?;
226         Ok(match state {
227             State::Pump => self.into(),
228             State::Disconnect(reason) => self.transition(state::Disconnected { reason }).into(),
229             State::DeferredStopReason => {
230                 match self.state.deferred_ctrlc_stop_reason {
231                     // if we were interrupted while idle, immediately report the deferred stop
232                     // reason after transitioning into the running state
233                     Some(reason) => {
234                         return self
235                             .transition(state::Running {})
236                             .report_stop(target, reason)
237                     }
238                     // otherwise, just transition into the running state as usual
239                     None => self.transition(state::Running {}).into(),
240                 }
241             }
242             State::CtrlCInterrupt => self
243                 .transition(state::CtrlCInterrupt { from_idle: true })
244                 .into(),
245         })
246     }
247 }
248 
249 /// Methods which can only be called from the
250 /// [`GdbStubStateMachine::Running`] state.
251 impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::Running, T, C> {
252     /// Report a target stop reason back to GDB.
report_stop( mut self, target: &mut T, reason: impl IntoStopReason<T>, ) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>>253     pub fn report_stop(
254         mut self,
255         target: &mut T,
256         reason: impl IntoStopReason<T>,
257     ) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
258         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())?;
260         res.flush().map_err(InternalError::from)?;
261 
262         Ok(match event {
263             FinishExecStatus::Handled => self
264                 .transition(state::Idle {
265                     deferred_ctrlc_stop_reason: None,
266                 })
267                 .into(),
268             FinishExecStatus::Disconnect(reason) => {
269                 self.transition(state::Disconnected { reason }).into()
270             }
271         })
272     }
273 
274     /// Pass a byte to the GDB stub.
incoming_data( mut self, target: &mut T, byte: u8, ) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>>275     pub fn incoming_data(
276         mut self,
277         target: &mut T,
278         byte: u8,
279     ) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
280         let packet_buffer = match self.i.recv_packet.pump(&mut self.i.packet_buffer, byte)? {
281             Some(buf) => buf,
282             None => return Ok(self.into()),
283         };
284 
285         let packet = Packet::from_buf(target, packet_buffer).map_err(InternalError::PacketParse)?;
286         let state = self
287             .i
288             .inner
289             .handle_packet(target, &mut self.i.conn, packet)?;
290         Ok(match state {
291             State::Pump => self.transition(state::Running {}).into(),
292             State::Disconnect(reason) => self.transition(state::Disconnected { reason }).into(),
293             State::DeferredStopReason => self.transition(state::Running {}).into(),
294             State::CtrlCInterrupt => self
295                 .transition(state::CtrlCInterrupt { from_idle: false })
296                 .into(),
297         })
298     }
299 }
300 
301 /// Methods which can only be called from the
302 /// [`GdbStubStateMachine::CtrlCInterrupt`] state.
303 impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::CtrlCInterrupt, T, C> {
304     /// Acknowledge the Ctrl-C interrupt.
305     ///
306     /// Passing `None` as a stop reason will return the state machine to
307     /// whatever state it was in pre-interruption, without immediately returning
308     /// a stop reason.
309     ///
310     /// Depending on how the target is implemented, it may or may not make sense
311     /// to immediately return a stop reason as part of handling the Ctrl-C
312     /// interrupt. e.g: in some cases, it may be better to send the target a
313     /// signal upon receiving a Ctrl-C interrupt _without_ immediately sending a
314     /// stop reason, and instead deferring the stop reason to some later point
315     /// in the target's execution.
316     ///
317     /// Some notes on handling Ctrl-C interrupts:
318     ///
319     /// - Stubs are not required to recognize these interrupt mechanisms, and
320     ///   the precise meaning associated with receipt of the interrupt is
321     ///   implementation defined.
322     /// - If the target supports debugging of multiple threads and/or processes,
323     ///   it should attempt to interrupt all currently-executing threads and
324     ///   processes.
325     /// - If the stub is successful at interrupting the running program, it
326     ///   should send one of the stop reply packets (see Stop Reply Packets) to
327     ///   GDB as a result of successfully stopping the program
interrupt_handled( self, target: &mut T, stop_reason: Option<impl IntoStopReason<T>>, ) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>>328     pub fn interrupt_handled(
329         self,
330         target: &mut T,
331         stop_reason: Option<impl IntoStopReason<T>>,
332     ) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
333         if self.state.from_idle {
334             // target is stopped - we cannot report the stop reason yet
335             Ok(self
336                 .transition(state::Idle {
337                     deferred_ctrlc_stop_reason: stop_reason.map(Into::into),
338                 })
339                 .into())
340         } else {
341             // target is running - we can immediately report the stop reason
342             let gdb = self.transition(state::Running {});
343             match stop_reason {
344                 Some(reason) => gdb.report_stop(target, reason),
345                 None => Ok(gdb.into()),
346             }
347         }
348     }
349 }
350 
351 /// Methods which can only be called from the
352 /// [`GdbStubStateMachine::Disconnected`] state.
353 impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::Disconnected, T, C> {
354     /// Inspect why the GDB client disconnected.
get_reason(&self) -> DisconnectReason355     pub fn get_reason(&self) -> DisconnectReason {
356         self.state.reason
357     }
358 
359     /// Reuse the existing state machine instance, reentering the idle loop.
return_to_idle(self) -> GdbStubStateMachine<'a, T, C>360     pub fn return_to_idle(self) -> GdbStubStateMachine<'a, T, C> {
361         self.transition(state::Idle {
362             deferred_ctrlc_stop_reason: None,
363         })
364         .into()
365     }
366 }
367