blob: 8cf08d9168e47cefe5562dc93575761336fe19d1 [file] [log] [blame]
//! The core [`GdbStub`] type, used to drive a GDB debugging session for a
//! particular [`Target`] over a given [`Connection`].
use managed::ManagedSlice;
use crate::conn::{Connection, ConnectionExt};
use crate::target::Target;
mod builder;
mod core_impl;
mod error;
mod stop_reason;
pub mod state_machine;
pub use builder::{GdbStubBuilder, GdbStubBuilderError};
pub use core_impl::DisconnectReason;
pub use error::GdbStubError;
pub use stop_reason::{
BaseStopReason, IntoStopReason, MultiThreadStopReason, SingleThreadStopReason,
};
use GdbStubError as Error;
/// 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<Self::Target>;
/// 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<Self::StopReason>,
WaitForStopReasonError<
<Self::Target as Target>::Error,
<Self::Connection as Connection>::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<Option<Self::StopReason>, <Self::Target as Target>::Error>;
}
/// Returned by the `wait_for_stop_reason` closure in
/// [`GdbStub::run_blocking`]
pub enum Event<StopReason> {
/// 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<T, C> {
/// 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<T, C>,
}
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<E>(
self,
target: &mut T,
) -> Result<DisconnectReason, Error<T::Error, C::Error>>
where
C: ConnectionExt,
E: run_blocking::BlockingEventLoop<Target = T, Connection = C>,
{
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(Error::ConnectionRead)?;
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(Error::TargetError)?;
gdb.interrupt_handled(target, stop_reason)?
}
state_machine::GdbStubStateMachine::Running(mut gdb) => {
use run_blocking::{Event as BlockingEventLoopEvent, 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(Error::TargetError(e));
}
Err(WaitForStopReasonError::Connection(e)) => {
break Err(Error::ConnectionRead(e));
}
}
}
}
}
}
/// 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<state_machine::GdbStubStateMachine<'a, T, C>, Error<T::Error, C::Error>> {
// 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(Error::ImplicitSwBreakpoints);
}
}
// Check how the target's arch handles single stepping
{
use crate::arch::SingleStepGdbBehavior;
use crate::target::ext::base::ResumeOps;
if let Some(ops) = target.base_ops().resume_ops() {
let support_single_step = match ops {
ResumeOps::SingleThread(ops) => ops.support_single_step().is_some(),
ResumeOps::MultiThread(ops) => ops.support_single_step().is_some(),
};
let behavior = target.guard_rail_single_step_gdb_behavior();
let return_error = match behavior {
SingleStepGdbBehavior::Optional => false,
SingleStepGdbBehavior::Required => !support_single_step,
SingleStepGdbBehavior::Ignored => support_single_step,
SingleStepGdbBehavior::Unknown => true,
};
if return_error {
return Err(Error::SingleStepGdbBehavior(behavior));
}
}
}
// Perform any connection initialization
{
self.conn
.on_session_start()
.map_err(Error::ConnectionInit)?;
}
Ok(state_machine::GdbStubStateMachineInner::from_plain_gdbstub(self).into())
}
}