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 managed::ManagedSlice; 37 38 use crate::arch::Arch; 39 use crate::conn::Connection; 40 use crate::protocol::recv_packet::RecvPacketStateMachine; 41 use crate::protocol::{Packet, ResponseWriter}; 42 use crate::stub::error::GdbStubError as Error; 43 use crate::stub::stop_reason::IntoStopReason; 44 use crate::target::Target; 45 46 use super::core_impl::{FinishExecStatus, GdbStubImpl, State}; 47 use super::{DisconnectReason, GdbStub}; 48 49 /// State-machine interface to `GdbStub`. 50 /// 51 /// See the [module level documentation](self) for more details. 52 pub enum GdbStubStateMachine<'a, T, C> 53 where 54 T: Target, 55 C: Connection, 56 { 57 /// The target is completely stopped, and the GDB stub is waiting for 58 /// additional input. 59 Idle(GdbStubStateMachineInner<'a, state::Idle<T>, T, C>), 60 /// The target is currently running, and the GDB client is waiting for 61 /// the target to report a stop reason. 62 /// 63 /// Note that the client may still send packets to the target 64 /// (e.g: to trigger a Ctrl-C interrupt). 65 Running(GdbStubStateMachineInner<'a, state::Running, T, C>), 66 /// The GDB client has sent a Ctrl-C interrupt to the target. 67 CtrlCInterrupt(GdbStubStateMachineInner<'a, state::CtrlCInterrupt, T, C>), 68 /// The GDB client has disconnected. 69 Disconnected(GdbStubStateMachineInner<'a, state::Disconnected, T, C>), 70 } 71 72 /// State machine typestates. 73 /// 74 /// The types in this module are used to parameterize instances of 75 /// [`GdbStubStateMachineInner`], thereby enforcing that certain API methods 76 /// can only be called while the stub is in a certain state. 77 // As an internal implementation detail, they _also_ carry state-specific 78 // payloads, which are used when transitioning between states. 79 pub mod state { 80 use super::*; 81 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>, Error<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>, Error<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(Error::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>, Error<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>, Error<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()?; 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. 275 /// 276 /// NOTE: unlike the `incoming_data` method in the `state::Idle` state, 277 /// this method does not perform any state transitions, and will 278 /// return a `GdbStubStateMachineInner` in the `state::Running` state. incoming_data( mut self, target: &mut T, byte: u8, ) -> Result<GdbStubStateMachine<'a, T, C>, Error<T::Error, C::Error>>279 pub fn incoming_data( 280 mut self, 281 target: &mut T, 282 byte: u8, 283 ) -> Result<GdbStubStateMachine<'a, T, C>, Error<T::Error, C::Error>> { 284 let packet_buffer = match self.i.recv_packet.pump(&mut self.i.packet_buffer, byte)? { 285 Some(buf) => buf, 286 None => return Ok(self.into()), 287 }; 288 289 let packet = Packet::from_buf(target, packet_buffer).map_err(Error::PacketParse)?; 290 let state = self 291 .i 292 .inner 293 .handle_packet(target, &mut self.i.conn, packet)?; 294 Ok(match state { 295 State::Pump => self.transition(state::Running {}).into(), 296 State::Disconnect(reason) => self.transition(state::Disconnected { reason }).into(), 297 State::DeferredStopReason => self.transition(state::Running {}).into(), 298 State::CtrlCInterrupt => self 299 .transition(state::CtrlCInterrupt { from_idle: false }) 300 .into(), 301 }) 302 } 303 } 304 305 /// Methods which can only be called from the 306 /// [`GdbStubStateMachine::CtrlCInterrupt`] state. 307 impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::CtrlCInterrupt, T, C> { 308 /// Acknowledge the Ctrl-C interrupt. 309 /// 310 /// Passing `None` as a stop reason will return the state machine to 311 /// whatever state it was in pre-interruption, without immediately returning 312 /// a stop reason. 313 /// 314 /// Depending on how the target is implemented, it may or may not make sense 315 /// to immediately return a stop reason as part of handling the Ctrl-C 316 /// interrupt. e.g: in some cases, it may be better to send the target a 317 /// signal upon receiving a Ctrl-C interrupt _without_ immediately sending a 318 /// stop reason, and instead deferring the stop reason to some later point 319 /// in the target's execution. 320 /// 321 /// Some notes on handling Ctrl-C interrupts: 322 /// 323 /// - Stubs are not required to recognize these interrupt mechanisms, and 324 /// the precise meaning associated with receipt of the interrupt is 325 /// implementation defined. 326 /// - If the target supports debugging of multiple threads and/or processes, 327 /// it should attempt to interrupt all currently-executing threads and 328 /// processes. 329 /// - If the stub is successful at interrupting the running program, it 330 /// should send one of the stop reply packets (see Stop Reply Packets) to 331 /// 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>, Error<T::Error, C::Error>>332 pub fn interrupt_handled( 333 self, 334 target: &mut T, 335 stop_reason: Option<impl IntoStopReason<T>>, 336 ) -> Result<GdbStubStateMachine<'a, T, C>, Error<T::Error, C::Error>> { 337 if self.state.from_idle { 338 // target is stopped - we cannot report the stop reason yet 339 Ok(self 340 .transition(state::Idle { 341 deferred_ctrlc_stop_reason: stop_reason.map(Into::into), 342 }) 343 .into()) 344 } else { 345 // target is running - we can immediately report the stop reason 346 let gdb = self.transition(state::Running {}); 347 match stop_reason { 348 Some(reason) => gdb.report_stop(target, reason), 349 None => Ok(gdb.into()), 350 } 351 } 352 } 353 } 354 355 /// Methods which can only be called from the 356 /// [`GdbStubStateMachine::Disconnected`] state. 357 impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::Disconnected, T, C> { 358 /// Inspect why the GDB client disconnected. get_reason(&self) -> DisconnectReason359 pub fn get_reason(&self) -> DisconnectReason { 360 self.state.reason 361 } 362 363 /// Reuse the existing state machine instance, reentering the idle loop. return_to_idle(self) -> GdbStubStateMachine<'a, T, C>364 pub fn return_to_idle(self) -> GdbStubStateMachine<'a, T, C> { 365 self.transition(state::Idle { 366 deferred_ctrlc_stop_reason: None, 367 }) 368 .into() 369 } 370 } 371