//! The core [`GdbStub`] type, used to drive a GDB debugging session for a //! particular [`Target`] over a given [`Connection`]. pub use builder::GdbStubBuilder; pub use builder::GdbStubBuilderError; pub use core_impl::DisconnectReason; pub use error::GdbStubError; pub use stop_reason::BaseStopReason; pub use stop_reason::IntoStopReason; pub use stop_reason::MultiThreadStopReason; pub use stop_reason::SingleThreadStopReason; mod builder; mod core_impl; mod error; mod stop_reason; pub mod state_machine; use self::error::InternalError; use crate::conn::Connection; use crate::conn::ConnectionExt; use crate::target::Target; use managed::ManagedSlice; /// Types and traits related to the [`GdbStub::run_blocking`] interface. pub mod run_blocking { use super::*; use crate::conn::ConnectionExt; /// A set of user-provided methods required to run a GDB debugging session /// using the [`GdbStub::run_blocking`] method. /// /// Reminder: to use `gdbstub` in a non-blocking manner (e.g: via /// async/await, unix polling, from an interrupt handler, etc...) you will /// need to interface with the /// [`GdbStubStateMachine`](state_machine::GdbStubStateMachine) API /// directly. pub trait BlockingEventLoop { /// The Target being driven. type Target: Target; /// Connection being used to drive the target. type Connection: ConnectionExt; /// Which variant of the `StopReason` type should be used. Single /// threaded targets should use [`SingleThreadStopReason`], whereas /// multi threaded targets should use [`MultiThreadStopReason`]. /// /// [`SingleThreadStopReason`]: crate::stub::SingleThreadStopReason /// [`MultiThreadStopReason`]: crate::stub::MultiThreadStopReason type StopReason: IntoStopReason; /// Invoked immediately after the target's `resume` method has been /// called. The implementation should block until either the target /// reports a stop reason, or if new data was sent over the connection. /// /// The specific mechanism to "select" between these two events is /// implementation specific. Some examples might include: `epoll`, /// `select!` across multiple event channels, periodic polling, etc... fn wait_for_stop_reason( target: &mut Self::Target, conn: &mut Self::Connection, ) -> Result< Event, WaitForStopReasonError< ::Error, ::Error, >, >; /// Invoked when the GDB client sends a Ctrl-C interrupt. /// /// Depending on how the target is implemented, it may or may not make /// sense to immediately return a stop reason as part of handling the /// Ctrl-C interrupt. e.g: in some cases, it may be better to send the /// target a signal upon receiving a Ctrl-C interrupt _without_ /// immediately sending a stop reason, and instead deferring the stop /// reason to some later point in the target's execution. /// /// _Suggestion_: If you're unsure which stop reason to report, /// [`BaseStopReason::Signal(Signal::SIGINT)`] is a sensible default. /// /// [`BaseStopReason::Signal(Signal::SIGINT)`]: /// crate::stub::BaseStopReason::Signal fn on_interrupt( target: &mut Self::Target, ) -> Result, ::Error>; } /// Returned by the `wait_for_stop_reason` closure in /// [`GdbStub::run_blocking`] pub enum Event { /// GDB Client sent data while the target was running. IncomingData(u8), /// The target has stopped. TargetStopped(StopReason), } /// Error value returned by the `wait_for_stop_reason` closure in /// [`GdbStub::run_blocking`] pub enum WaitForStopReasonError { /// A fatal target error has occurred. Target(T), /// A fatal connection error has occurred. Connection(C), } } /// Debug a [`Target`] using the GDB Remote Serial Protocol over a given /// [`Connection`]. pub struct GdbStub<'a, T: Target, C: Connection> { conn: C, packet_buffer: ManagedSlice<'a, u8>, inner: core_impl::GdbStubImpl, } impl<'a, T: Target, C: Connection> GdbStub<'a, T, C> { /// Create a [`GdbStubBuilder`] using the provided Connection. pub fn builder(conn: C) -> GdbStubBuilder<'a, T, C> { GdbStubBuilder::new(conn) } /// Create a new `GdbStub` using the provided connection. /// /// _Note:_ `new` is only available when the `alloc` feature is enabled, as /// it will use a dynamically allocated `Vec` as a packet buffer. /// /// For fine-grained control over various `GdbStub` options, including the /// ability to specify a fixed-size buffer, use the [`GdbStub::builder`] /// method instead. #[cfg(feature = "alloc")] pub fn new(conn: C) -> GdbStub<'a, T, C> { GdbStubBuilder::new(conn).build().unwrap() } /// (Quickstart) Start a GDB remote debugging session using a blocking event /// loop. /// /// This method provides a quick and easy way to get up and running with /// `gdbstub` without directly having to immediately interface with the /// lower-level [state-machine](state_machine::GdbStubStateMachine) /// based interface. /// /// Instead, an implementation simply needs to provide a implementation of /// [`run_blocking::BlockingEventLoop`], which is a simplified set /// of methods describing how to drive the target. /// /// `GdbStub::run_blocking` returns once the GDB client closes the debugging /// session, or if the target triggers a disconnect. /// /// Note that this implementation is **blocking**, which many not be /// preferred (or suitable) in all cases. To use `gdbstub` in a non-blocking /// manner (e.g: via async/await, unix polling, from an interrupt handler, /// etc...) you will need to interface with the underlying /// [`GdbStubStateMachine`](state_machine::GdbStubStateMachine) API /// directly. pub fn run_blocking( self, target: &mut T, ) -> Result> where C: ConnectionExt, E: run_blocking::BlockingEventLoop, { let mut gdb = self.run_state_machine(target)?; loop { gdb = match gdb { state_machine::GdbStubStateMachine::Idle(mut gdb) => { // needs more data, so perform a blocking read on the connection let byte = gdb.borrow_conn().read().map_err(InternalError::conn_read)?; gdb.incoming_data(target, byte)? } state_machine::GdbStubStateMachine::Disconnected(gdb) => { // run_blocking keeps things simple, and doesn't expose a way to re-use the // state machine break Ok(gdb.get_reason()); } state_machine::GdbStubStateMachine::CtrlCInterrupt(gdb) => { // defer to the implementation on how it wants to handle the interrupt let stop_reason = E::on_interrupt(target).map_err(InternalError::TargetError)?; gdb.interrupt_handled(target, stop_reason)? } state_machine::GdbStubStateMachine::Running(mut gdb) => { use run_blocking::Event as BlockingEventLoopEvent; use run_blocking::WaitForStopReasonError; // block waiting for the target to return a stop reason let event = E::wait_for_stop_reason(target, gdb.borrow_conn()); match event { Ok(BlockingEventLoopEvent::TargetStopped(stop_reason)) => { gdb.report_stop(target, stop_reason)? } Ok(BlockingEventLoopEvent::IncomingData(byte)) => { gdb.incoming_data(target, byte)? } Err(WaitForStopReasonError::Target(e)) => { break Err(InternalError::TargetError(e).into()); } Err(WaitForStopReasonError::Connection(e)) => { break Err(InternalError::conn_read(e).into()); } } } } } } /// Starts a GDB remote debugging session, converting this instance of /// `GdbStub` into a /// [`GdbStubStateMachine`](state_machine::GdbStubStateMachine) that is /// ready to receive data. pub fn run_state_machine( mut self, target: &mut T, ) -> Result, GdbStubError> { // Check if the target hasn't explicitly opted into implicit sw breakpoints { let support_software_breakpoints = target .support_breakpoints() .map(|ops| ops.support_sw_breakpoint().is_some()) .unwrap_or(false); if !support_software_breakpoints && !target.guard_rail_implicit_sw_breakpoints() { return Err(InternalError::ImplicitSwBreakpoints.into()); } } // Perform any connection initialization { self.conn .on_session_start() .map_err(InternalError::conn_init)?; } Ok(state_machine::GdbStubStateMachineInner::from_plain_gdbstub(self).into()) } }