• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use core::marker::PhantomData;
2 
3 use crate::common::{Signal, Tid};
4 use crate::conn::Connection;
5 use crate::protocol::commands::Command;
6 use crate::protocol::{Packet, ResponseWriter, SpecificIdKind};
7 use crate::stub::GdbStubError as Error;
8 use crate::target::Target;
9 use crate::SINGLE_THREAD_TID;
10 
11 /// Common imports used by >50% of all extensions.
12 ///
13 /// Do not clutter this prelude with types only used by a few extensions.
14 mod prelude {
15     pub(super) use crate::conn::Connection;
16     pub(super) use crate::internal::BeBytes;
17     pub(super) use crate::protocol::ResponseWriter;
18     pub(super) use crate::stub::core_impl::target_result_ext::TargetResultExt;
19     pub(super) use crate::stub::core_impl::{GdbStubImpl, HandlerStatus};
20     pub(super) use crate::stub::error::GdbStubError as Error;
21     pub(super) use crate::target::Target;
22 }
23 
24 mod auxv;
25 mod base;
26 mod breakpoints;
27 mod catch_syscalls;
28 mod exec_file;
29 mod extended_mode;
30 mod host_io;
31 mod lldb_register_info;
32 mod memory_map;
33 mod monitor_cmd;
34 mod resume;
35 mod reverse_exec;
36 mod section_offsets;
37 mod single_register_access;
38 mod target_xml;
39 mod thread_extra_info;
40 mod x_upcase_packet;
41 
42 pub(crate) use resume::FinishExecStatus;
43 
44 pub(crate) mod target_result_ext {
45     use crate::stub::GdbStubError;
46     use crate::target::TargetError;
47 
48     /// Extension trait to ease working with `TargetResult` in the GdbStub
49     /// implementation.
50     pub(super) trait TargetResultExt<V, T, C> {
51         /// Encapsulates the boilerplate associated with handling
52         /// `TargetError`s, such as bailing-out on Fatal errors, or
53         /// returning response codes.
handle_error(self) -> Result<V, GdbStubError<T, C>>54         fn handle_error(self) -> Result<V, GdbStubError<T, C>>;
55     }
56 
57     impl<V, T, C> TargetResultExt<V, T, C> for Result<V, TargetError<T>> {
handle_error(self) -> Result<V, GdbStubError<T, C>>58         fn handle_error(self) -> Result<V, GdbStubError<T, C>> {
59             let code = match self {
60                 Ok(v) => return Ok(v),
61                 Err(TargetError::Fatal(e)) => return Err(GdbStubError::TargetError(e)),
62                 // Recoverable errors:
63                 // Error code 121 corresponds to `EREMOTEIO` lol
64                 Err(TargetError::NonFatal) => 121,
65                 Err(TargetError::Errno(code)) => code,
66                 #[cfg(feature = "std")]
67                 Err(TargetError::Io(e)) => e.raw_os_error().unwrap_or(121) as u8,
68             };
69 
70             Err(GdbStubError::NonFatalError(code))
71         }
72     }
73 }
74 
75 /// Describes why the GDB session ended.
76 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
77 pub enum DisconnectReason {
78     /// Target exited with given status code
79     TargetExited(u8),
80     /// Target terminated with given signal
81     TargetTerminated(Signal),
82     /// GDB issued a disconnect command
83     Disconnect,
84     /// GDB issued a kill command
85     Kill,
86 }
87 
88 pub enum State {
89     Pump,
90     DeferredStopReason,
91     CtrlCInterrupt,
92     Disconnect(DisconnectReason),
93 }
94 
95 pub struct GdbStubImpl<T: Target, C: Connection> {
96     _target: PhantomData<T>,
97     _connection: PhantomData<C>,
98 
99     current_mem_tid: Tid,
100     current_resume_tid: SpecificIdKind,
101     features: ProtocolFeatures,
102 }
103 
104 pub enum HandlerStatus {
105     Handled,
106     NeedsOk,
107     DeferredStopReason,
108     Disconnect(DisconnectReason),
109 }
110 
111 impl<T: Target, C: Connection> GdbStubImpl<T, C> {
new() -> GdbStubImpl<T, C>112     pub fn new() -> GdbStubImpl<T, C> {
113         GdbStubImpl {
114             _target: PhantomData,
115             _connection: PhantomData,
116 
117             // NOTE: `current_mem_tid` and `current_resume_tid` are never queried prior to being set
118             // by the GDB client (via the 'H' packet), so it's fine to use dummy values here.
119             //
120             // The alternative would be to use `Option`, and while this would be more "correct", it
121             // would introduce a _lot_ of noisy and heavy error handling logic all over the place.
122             //
123             // Plus, even if the GDB client is acting strangely and doesn't overwrite these values,
124             // the target will simply return a non-fatal error, which is totally fine.
125             current_mem_tid: SINGLE_THREAD_TID,
126             current_resume_tid: SpecificIdKind::WithId(SINGLE_THREAD_TID),
127             features: ProtocolFeatures::empty(),
128         }
129     }
130 
handle_packet( &mut self, target: &mut T, conn: &mut C, packet: Packet<'_>, ) -> Result<State, Error<T::Error, C::Error>>131     pub fn handle_packet(
132         &mut self,
133         target: &mut T,
134         conn: &mut C,
135         packet: Packet<'_>,
136     ) -> Result<State, Error<T::Error, C::Error>> {
137         match packet {
138             Packet::Ack => Ok(State::Pump),
139             Packet::Nack => Err(Error::ClientSentNack),
140             Packet::Interrupt => {
141                 debug!("<-- interrupt packet");
142                 Ok(State::CtrlCInterrupt)
143             }
144             Packet::Command(command) => {
145                 // Acknowledge the command
146                 if !self.features.no_ack_mode() {
147                     conn.write(b'+').map_err(Error::ConnectionWrite)?;
148                 }
149 
150                 let mut res = ResponseWriter::new(conn, target.use_rle());
151                 let disconnect_reason = match self.handle_command(&mut res, target, command) {
152                     Ok(HandlerStatus::Handled) => None,
153                     Ok(HandlerStatus::NeedsOk) => {
154                         res.write_str("OK")?;
155                         None
156                     }
157                     Ok(HandlerStatus::DeferredStopReason) => return Ok(State::DeferredStopReason),
158                     Ok(HandlerStatus::Disconnect(reason)) => Some(reason),
159                     // HACK: handling this "dummy" error is required as part of the
160                     // `TargetResultExt::handle_error()` machinery.
161                     Err(Error::NonFatalError(code)) => {
162                         res.write_str("E")?;
163                         res.write_num(code)?;
164                         None
165                     }
166                     Err(e) => return Err(e),
167                 };
168 
169                 // every response needs to be flushed, _except_ for the response to a kill
170                 // packet, but ONLY when extended mode is NOT implemented.
171                 let is_kill = matches!(disconnect_reason, Some(DisconnectReason::Kill));
172                 if !(target.support_extended_mode().is_none() && is_kill) {
173                     res.flush()?;
174                 }
175 
176                 let state = match disconnect_reason {
177                     Some(reason) => State::Disconnect(reason),
178                     None => State::Pump,
179                 };
180 
181                 Ok(state)
182             }
183         }
184     }
185 
handle_command( &mut self, res: &mut ResponseWriter<'_, C>, target: &mut T, cmd: Command<'_>, ) -> Result<HandlerStatus, Error<T::Error, C::Error>>186     fn handle_command(
187         &mut self,
188         res: &mut ResponseWriter<'_, C>,
189         target: &mut T,
190         cmd: Command<'_>,
191     ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
192         match cmd {
193             // `handle_X` methods are defined in the `ext` module
194             Command::Base(cmd) => self.handle_base(res, target, cmd),
195             Command::TargetXml(cmd) => self.handle_target_xml(res, target, cmd),
196             Command::Resume(cmd) => self.handle_stop_resume(res, target, cmd),
197             Command::XUpcasePacket(cmd) => self.handle_x_upcase_packet(res, target, cmd),
198             Command::SingleRegisterAccess(cmd) => {
199                 self.handle_single_register_access(res, target, cmd)
200             }
201             Command::Breakpoints(cmd) => self.handle_breakpoints(res, target, cmd),
202             Command::CatchSyscalls(cmd) => self.handle_catch_syscalls(res, target, cmd),
203             Command::ExtendedMode(cmd) => self.handle_extended_mode(res, target, cmd),
204             Command::MonitorCmd(cmd) => self.handle_monitor_cmd(res, target, cmd),
205             Command::SectionOffsets(cmd) => self.handle_section_offsets(res, target, cmd),
206             Command::ReverseCont(cmd) => self.handle_reverse_cont(res, target, cmd),
207             Command::ReverseStep(cmd) => self.handle_reverse_step(res, target, cmd),
208             Command::MemoryMap(cmd) => self.handle_memory_map(res, target, cmd),
209             Command::HostIo(cmd) => self.handle_host_io(res, target, cmd),
210             Command::ExecFile(cmd) => self.handle_exec_file(res, target, cmd),
211             Command::Auxv(cmd) => self.handle_auxv(res, target, cmd),
212             Command::ThreadExtraInfo(cmd) => self.handle_thread_extra_info(res, target, cmd),
213             Command::LldbRegisterInfo(cmd) => self.handle_lldb_register_info(res, target, cmd),
214             // in the worst case, the command could not be parsed...
215             Command::Unknown(cmd) => {
216                 // HACK: if the user accidentally sends a resume command to a
217                 // target without resume support, inform them of their mistake +
218                 // return a dummy stop reason.
219                 if target.base_ops().resume_ops().is_none() && target.use_resume_stub() {
220                     let is_resume_pkt = cmd
221                         .first()
222                         .map(|c| matches!(c, b'c' | b'C' | b's' | b'S'))
223                         .unwrap_or(false);
224 
225                     if is_resume_pkt {
226                         warn!("attempted to resume target without resume support!");
227 
228                         // TODO: omit this message if non-stop mode is active
229                         {
230                             let mut res = ResponseWriter::new(res.as_conn(), target.use_rle());
231                             res.write_str("O")?;
232                             res.write_hex_buf(b"target has not implemented `support_resume()`\n")?;
233                             res.flush()?;
234                         }
235 
236                         res.write_str("S05")?;
237                     }
238                 }
239 
240                 info!("Unknown command: {:?}", core::str::from_utf8(cmd));
241                 Ok(HandlerStatus::Handled)
242             }
243         }
244     }
245 }
246 
247 // This bitflag is not part of the protocol - it is an internal implementation
248 // detail. The alternative would be to use multiple `bool` fields, which wastes
249 // space in minimal `gdbstub` configurations.
250 bitflags::bitflags! {
251     struct ProtocolFeatures: u8 {
252         const NO_ACK_MODE = 1 << 0;
253         const MULTIPROCESS = 1 << 1;
254     }
255 }
256 
257 impl ProtocolFeatures {
258     #[inline(always)]
no_ack_mode(&self) -> bool259     fn no_ack_mode(&self) -> bool {
260         self.contains(ProtocolFeatures::NO_ACK_MODE)
261     }
262 
263     #[inline(always)]
set_no_ack_mode(&mut self, val: bool)264     fn set_no_ack_mode(&mut self, val: bool) {
265         self.set(ProtocolFeatures::NO_ACK_MODE, val)
266     }
267 
268     #[inline(always)]
multiprocess(&self) -> bool269     fn multiprocess(&self) -> bool {
270         self.contains(ProtocolFeatures::MULTIPROCESS)
271     }
272 
273     #[inline(always)]
set_multiprocess(&mut self, val: bool)274     fn set_multiprocess(&mut self, val: bool) {
275         self.set(ProtocolFeatures::MULTIPROCESS, val)
276     }
277 }
278