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