• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use core::marker::PhantomData;
2 
3 use managed::ManagedSlice;
4 
5 use crate::common::*;
6 use crate::connection::Connection;
7 use crate::protocol::{commands::Command, Packet, ResponseWriter, SpecificIdKind};
8 use crate::target::Target;
9 use crate::util::managed_vec::ManagedVec;
10 use crate::SINGLE_THREAD_TID;
11 
12 mod builder;
13 mod error;
14 mod ext;
15 mod target_result_ext;
16 
17 pub use builder::{GdbStubBuilder, GdbStubBuilderError};
18 pub use error::GdbStubError;
19 
20 use GdbStubError as Error;
21 
22 /// Describes why the GDB session ended.
23 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
24 pub enum DisconnectReason {
25     /// Target exited with given status code
26     TargetExited(u8),
27     /// Target terminated with given signal
28     TargetTerminated(u8),
29     /// GDB issued a disconnect command
30     Disconnect,
31     /// GDB issued a kill command
32     Kill,
33 }
34 
35 /// Debug a [`Target`] using the GDB Remote Serial Protocol over a given
36 /// [`Connection`].
37 pub struct GdbStub<'a, T: Target, C: Connection> {
38     conn: C,
39     packet_buffer: ManagedSlice<'a, u8>,
40     state: GdbStubImpl<T, C>,
41 }
42 
43 impl<'a, T: Target, C: Connection> GdbStub<'a, T, C> {
44     /// Create a [`GdbStubBuilder`] using the provided Connection.
builder(conn: C) -> GdbStubBuilder<'a, T, C>45     pub fn builder(conn: C) -> GdbStubBuilder<'a, T, C> {
46         GdbStubBuilder::new(conn)
47     }
48 
49     /// Create a new `GdbStub` using the provided connection.
50     ///
51     /// For fine-grained control over various `GdbStub` options, use the
52     /// [`builder()`](GdbStub::builder) method instead.
53     ///
54     /// _Note:_ `new` is only available when the `alloc` feature is enabled.
55     #[cfg(feature = "alloc")]
new(conn: C) -> GdbStub<'a, T, C>56     pub fn new(conn: C) -> GdbStub<'a, T, C> {
57         GdbStubBuilder::new(conn).build().unwrap()
58     }
59 
60     /// Starts a GDB remote debugging session.
61     ///
62     /// Returns once the GDB client closes the debugging session, or if the
63     /// target halts.
run(&mut self, target: &mut T) -> Result<DisconnectReason, Error<T::Error, C::Error>>64     pub fn run(&mut self, target: &mut T) -> Result<DisconnectReason, Error<T::Error, C::Error>> {
65         self.state
66             .run(target, &mut self.conn, &mut self.packet_buffer)
67     }
68 }
69 
70 struct GdbStubImpl<T: Target, C: Connection> {
71     _target: PhantomData<T>,
72     _connection: PhantomData<C>,
73 
74     current_mem_tid: Tid,
75     current_resume_tid: SpecificIdKind,
76     no_ack_mode: bool,
77 }
78 
79 enum HandlerStatus {
80     Handled,
81     NeedsOk,
82     Disconnect(DisconnectReason),
83 }
84 
85 impl<T: Target, C: Connection> GdbStubImpl<T, C> {
new() -> GdbStubImpl<T, C>86     fn new() -> GdbStubImpl<T, C> {
87         GdbStubImpl {
88             _target: PhantomData,
89             _connection: PhantomData,
90 
91             // NOTE: `current_mem_tid` and `current_resume_tid` are never queried prior to being set
92             // by the GDB client (via the 'H' packet), so it's fine to use dummy values here.
93             //
94             // The alternative would be to use `Option`, and while this would be more "correct", it
95             // would introduce a _lot_ of noisy and heavy error handling logic all over the place.
96             //
97             // Plus, even if the GDB client is acting strangely and doesn't overwrite these values,
98             // the target will simply return a non-fatal error, which is totally fine.
99             current_mem_tid: SINGLE_THREAD_TID,
100             current_resume_tid: SpecificIdKind::WithId(SINGLE_THREAD_TID),
101             no_ack_mode: false,
102         }
103     }
104 
run( &mut self, target: &mut T, conn: &mut C, packet_buffer: &mut ManagedSlice<u8>, ) -> Result<DisconnectReason, Error<T::Error, C::Error>>105     fn run(
106         &mut self,
107         target: &mut T,
108         conn: &mut C,
109         packet_buffer: &mut ManagedSlice<u8>,
110     ) -> Result<DisconnectReason, Error<T::Error, C::Error>> {
111         conn.on_session_start().map_err(Error::ConnectionRead)?;
112 
113         loop {
114             match Self::recv_packet(conn, target, packet_buffer)? {
115                 Packet::Ack => {}
116                 Packet::Nack => return Err(Error::ClientSentNack),
117                 Packet::Interrupt => {
118                     debug!("<-- interrupt packet");
119                     let mut res = ResponseWriter::new(conn);
120                     res.write_str("S05")?;
121                     res.flush()?;
122                 }
123                 Packet::Command(command) => {
124                     // Acknowledge the command
125                     if !self.no_ack_mode {
126                         conn.write(b'+').map_err(Error::ConnectionRead)?;
127                     }
128 
129                     let mut res = ResponseWriter::new(conn);
130                     let disconnect = match self.handle_command(&mut res, target, command) {
131                         Ok(HandlerStatus::Handled) => None,
132                         Ok(HandlerStatus::NeedsOk) => {
133                             res.write_str("OK")?;
134                             None
135                         }
136                         Ok(HandlerStatus::Disconnect(reason)) => Some(reason),
137                         // HACK: handling this "dummy" error is required as part of the
138                         // `TargetResultExt::handle_error()` machinery.
139                         Err(Error::NonFatalError(code)) => {
140                             res.write_str("E")?;
141                             res.write_num(code)?;
142                             None
143                         }
144                         Err(Error::TargetError(e)) => {
145                             // unlike all other errors which are "unrecoverable" in the sense that
146                             // the GDB session cannot continue, there's still a chance that a target
147                             // might want to keep the debugging session alive to do a "post-mortem"
148                             // analysis. As such, we simply report a standard TRAP stop reason.
149                             let mut res = ResponseWriter::new(conn);
150                             res.write_str("S05")?;
151                             res.flush()?;
152                             return Err(Error::TargetError(e));
153                         }
154                         Err(e) => return Err(e),
155                     };
156 
157                     // HACK: this could be more elegant...
158                     if disconnect != Some(DisconnectReason::Kill) {
159                         res.flush()?;
160                     }
161 
162                     if let Some(disconnect_reason) = disconnect {
163                         return Ok(disconnect_reason);
164                     }
165                 }
166             };
167         }
168     }
169 
recv_packet<'a>( conn: &mut C, target: &mut T, pkt_buf: &'a mut ManagedSlice<u8>, ) -> Result<Packet<'a>, Error<T::Error, C::Error>>170     fn recv_packet<'a>(
171         conn: &mut C,
172         target: &mut T,
173         pkt_buf: &'a mut ManagedSlice<u8>,
174     ) -> Result<Packet<'a>, Error<T::Error, C::Error>> {
175         let header_byte = conn.read().map_err(Error::ConnectionRead)?;
176 
177         // Wrap the buf in a `ManagedVec` to keep the code readable.
178         let mut buf = ManagedVec::new(pkt_buf);
179 
180         buf.clear();
181         buf.push(header_byte)?;
182         if header_byte == b'$' {
183             // read the packet body
184             loop {
185                 let c = conn.read().map_err(Error::ConnectionRead)?;
186                 buf.push(c)?;
187                 if c == b'#' {
188                     break;
189                 }
190             }
191             // read the checksum as well
192             buf.push(conn.read().map_err(Error::ConnectionRead)?)?;
193             buf.push(conn.read().map_err(Error::ConnectionRead)?)?;
194         }
195 
196         trace!(
197             "<-- {}",
198             core::str::from_utf8(buf.as_slice()).unwrap_or("<invalid packet>")
199         );
200 
201         drop(buf);
202 
203         Packet::from_buf(target, pkt_buf.as_mut()).map_err(Error::PacketParse)
204     }
205 
handle_command( &mut self, res: &mut ResponseWriter<C>, target: &mut T, cmd: Command<'_>, ) -> Result<HandlerStatus, Error<T::Error, C::Error>>206     fn handle_command(
207         &mut self,
208         res: &mut ResponseWriter<C>,
209         target: &mut T,
210         cmd: Command<'_>,
211     ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
212         match cmd {
213             Command::Unknown(cmd) => {
214                 // cmd must be ASCII, as the slice originated from a PacketBuf, which checks for
215                 // ASCII as part of the initial validation.
216                 info!("Unknown command: {}", core::str::from_utf8(cmd).unwrap());
217                 Ok(HandlerStatus::Handled)
218             }
219             // `handle_X` methods are defined in the `ext` module
220             Command::Base(cmd) => self.handle_base(res, target, cmd),
221             Command::SingleRegisterAccess(cmd) => {
222                 self.handle_single_register_access(res, target, cmd)
223             }
224             Command::Breakpoints(cmd) => self.handle_breakpoints(res, target, cmd),
225             Command::ExtendedMode(cmd) => self.handle_extended_mode(res, target, cmd),
226             Command::MonitorCmd(cmd) => self.handle_monitor_cmd(res, target, cmd),
227             Command::SectionOffsets(cmd) => self.handle_section_offsets(res, target, cmd),
228             Command::ReverseCont(cmd) => self.handle_reverse_cont(res, target, cmd),
229             Command::ReverseStep(cmd) => self.handle_reverse_step(res, target, cmd),
230         }
231     }
232 }
233