gdbstub/stub/
core_impl.rs

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