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