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