use core::marker::PhantomData; use managed::ManagedSlice; use crate::common::*; use crate::connection::Connection; use crate::protocol::{commands::Command, Packet, ResponseWriter, SpecificIdKind}; use crate::target::Target; use crate::util::managed_vec::ManagedVec; use crate::SINGLE_THREAD_TID; mod builder; mod error; mod ext; mod target_result_ext; pub use builder::{GdbStubBuilder, GdbStubBuilderError}; pub use error::GdbStubError; use GdbStubError as Error; /// Describes why the GDB session ended. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum DisconnectReason { /// Target exited with given status code TargetExited(u8), /// Target terminated with given signal TargetTerminated(u8), /// GDB issued a disconnect command Disconnect, /// GDB issued a kill command Kill, } /// 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>, state: 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. /// /// For fine-grained control over various `GdbStub` options, use the /// [`builder()`](GdbStub::builder) method instead. /// /// _Note:_ `new` is only available when the `alloc` feature is enabled. #[cfg(feature = "alloc")] pub fn new(conn: C) -> GdbStub<'a, T, C> { GdbStubBuilder::new(conn).build().unwrap() } /// Starts a GDB remote debugging session. /// /// Returns once the GDB client closes the debugging session, or if the /// target halts. pub fn run(&mut self, target: &mut T) -> Result> { self.state .run(target, &mut self.conn, &mut self.packet_buffer) } } struct GdbStubImpl { _target: PhantomData, _connection: PhantomData, current_mem_tid: Tid, current_resume_tid: SpecificIdKind, no_ack_mode: bool, } enum HandlerStatus { Handled, NeedsOk, Disconnect(DisconnectReason), } impl GdbStubImpl { fn new() -> GdbStubImpl { GdbStubImpl { _target: PhantomData, _connection: PhantomData, // NOTE: `current_mem_tid` and `current_resume_tid` are never queried prior to being set // by the GDB client (via the 'H' packet), so it's fine to use dummy values here. // // The alternative would be to use `Option`, and while this would be more "correct", it // would introduce a _lot_ of noisy and heavy error handling logic all over the place. // // Plus, even if the GDB client is acting strangely and doesn't overwrite these values, // the target will simply return a non-fatal error, which is totally fine. current_mem_tid: SINGLE_THREAD_TID, current_resume_tid: SpecificIdKind::WithId(SINGLE_THREAD_TID), no_ack_mode: false, } } fn run( &mut self, target: &mut T, conn: &mut C, packet_buffer: &mut ManagedSlice, ) -> Result> { conn.on_session_start().map_err(Error::ConnectionRead)?; loop { match Self::recv_packet(conn, target, packet_buffer)? { Packet::Ack => {} Packet::Nack => return Err(Error::ClientSentNack), Packet::Interrupt => { debug!("<-- interrupt packet"); let mut res = ResponseWriter::new(conn); res.write_str("S05")?; res.flush()?; } Packet::Command(command) => { // Acknowledge the command if !self.no_ack_mode { conn.write(b'+').map_err(Error::ConnectionRead)?; } let mut res = ResponseWriter::new(conn); let disconnect = match self.handle_command(&mut res, target, command) { Ok(HandlerStatus::Handled) => None, Ok(HandlerStatus::NeedsOk) => { res.write_str("OK")?; None } Ok(HandlerStatus::Disconnect(reason)) => Some(reason), // HACK: handling this "dummy" error is required as part of the // `TargetResultExt::handle_error()` machinery. Err(Error::NonFatalError(code)) => { res.write_str("E")?; res.write_num(code)?; None } Err(Error::TargetError(e)) => { // unlike all other errors which are "unrecoverable" in the sense that // the GDB session cannot continue, there's still a chance that a target // might want to keep the debugging session alive to do a "post-mortem" // analysis. As such, we simply report a standard TRAP stop reason. let mut res = ResponseWriter::new(conn); res.write_str("S05")?; res.flush()?; return Err(Error::TargetError(e)); } Err(e) => return Err(e), }; // HACK: this could be more elegant... if disconnect != Some(DisconnectReason::Kill) { res.flush()?; } if let Some(disconnect_reason) = disconnect { return Ok(disconnect_reason); } } }; } } fn recv_packet<'a>( conn: &mut C, target: &mut T, pkt_buf: &'a mut ManagedSlice, ) -> Result, Error> { let header_byte = conn.read().map_err(Error::ConnectionRead)?; // Wrap the buf in a `ManagedVec` to keep the code readable. let mut buf = ManagedVec::new(pkt_buf); buf.clear(); buf.push(header_byte)?; if header_byte == b'$' { // read the packet body loop { let c = conn.read().map_err(Error::ConnectionRead)?; buf.push(c)?; if c == b'#' { break; } } // read the checksum as well buf.push(conn.read().map_err(Error::ConnectionRead)?)?; buf.push(conn.read().map_err(Error::ConnectionRead)?)?; } trace!( "<-- {}", core::str::from_utf8(buf.as_slice()).unwrap_or("") ); drop(buf); Packet::from_buf(target, pkt_buf.as_mut()).map_err(Error::PacketParse) } fn handle_command( &mut self, res: &mut ResponseWriter, target: &mut T, cmd: Command<'_>, ) -> Result> { match cmd { Command::Unknown(cmd) => { // cmd must be ASCII, as the slice originated from a PacketBuf, which checks for // ASCII as part of the initial validation. info!("Unknown command: {}", core::str::from_utf8(cmd).unwrap()); Ok(HandlerStatus::Handled) } // `handle_X` methods are defined in the `ext` module Command::Base(cmd) => self.handle_base(res, target, cmd), Command::SingleRegisterAccess(cmd) => { self.handle_single_register_access(res, target, cmd) } Command::Breakpoints(cmd) => self.handle_breakpoints(res, target, cmd), Command::ExtendedMode(cmd) => self.handle_extended_mode(res, target, cmd), Command::MonitorCmd(cmd) => self.handle_monitor_cmd(res, target, cmd), Command::SectionOffsets(cmd) => self.handle_section_offsets(res, target, cmd), Command::ReverseCont(cmd) => self.handle_reverse_cont(res, target, cmd), Command::ReverseStep(cmd) => self.handle_reverse_step(res, target, cmd), } } }