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