aboutsummaryrefslogtreecommitdiff
path: root/src/stub/core_impl.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/stub/core_impl.rs')
-rw-r--r--src/stub/core_impl.rs277
1 files changed, 277 insertions, 0 deletions
diff --git a/src/stub/core_impl.rs b/src/stub/core_impl.rs
new file mode 100644
index 0000000..31ab884
--- /dev/null
+++ b/src/stub/core_impl.rs
@@ -0,0 +1,277 @@
+use core::marker::PhantomData;
+
+use crate::common::{Signal, Tid};
+use crate::conn::Connection;
+use crate::protocol::commands::Command;
+use crate::protocol::{Packet, ResponseWriter, SpecificIdKind};
+use crate::stub::GdbStubError as Error;
+use crate::target::Target;
+use crate::SINGLE_THREAD_TID;
+
+/// Common imports used by >50% of all extensions.
+///
+/// Do not clutter this prelude with types only used by a few extensions.
+mod prelude {
+ pub(super) use crate::conn::Connection;
+ pub(super) use crate::internal::BeBytes;
+ pub(super) use crate::protocol::ResponseWriter;
+ pub(super) use crate::stub::core_impl::target_result_ext::TargetResultExt;
+ pub(super) use crate::stub::core_impl::{GdbStubImpl, HandlerStatus};
+ pub(super) use crate::stub::error::GdbStubError as Error;
+ pub(super) use crate::target::Target;
+}
+
+mod auxv;
+mod base;
+mod breakpoints;
+mod catch_syscalls;
+mod exec_file;
+mod extended_mode;
+mod host_io;
+mod lldb_register_info;
+mod memory_map;
+mod monitor_cmd;
+mod resume;
+mod reverse_exec;
+mod section_offsets;
+mod single_register_access;
+mod target_xml;
+mod thread_extra_info;
+mod x_upcase_packet;
+
+pub(crate) use resume::FinishExecStatus;
+
+pub(crate) mod target_result_ext {
+ use crate::stub::GdbStubError;
+ use crate::target::TargetError;
+
+ /// Extension trait to ease working with `TargetResult` in the GdbStub
+ /// implementation.
+ pub(super) trait TargetResultExt<V, T, C> {
+ /// Encapsulates the boilerplate associated with handling
+ /// `TargetError`s, such as bailing-out on Fatal errors, or
+ /// returning response codes.
+ fn handle_error(self) -> Result<V, GdbStubError<T, C>>;
+ }
+
+ impl<V, T, C> TargetResultExt<V, T, C> for Result<V, TargetError<T>> {
+ fn handle_error(self) -> Result<V, GdbStubError<T, C>> {
+ let code = match self {
+ Ok(v) => return Ok(v),
+ Err(TargetError::Fatal(e)) => return Err(GdbStubError::TargetError(e)),
+ // Recoverable errors:
+ // Error code 121 corresponds to `EREMOTEIO` lol
+ Err(TargetError::NonFatal) => 121,
+ Err(TargetError::Errno(code)) => code,
+ #[cfg(feature = "std")]
+ Err(TargetError::Io(e)) => e.raw_os_error().unwrap_or(121) as u8,
+ };
+
+ Err(GdbStubError::NonFatalError(code))
+ }
+ }
+}
+
+/// 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(Signal),
+ /// GDB issued a disconnect command
+ Disconnect,
+ /// GDB issued a kill command
+ Kill,
+}
+
+pub enum State {
+ Pump,
+ DeferredStopReason,
+ CtrlCInterrupt,
+ Disconnect(DisconnectReason),
+}
+
+pub struct GdbStubImpl<T: Target, C: Connection> {
+ _target: PhantomData<T>,
+ _connection: PhantomData<C>,
+
+ current_mem_tid: Tid,
+ current_resume_tid: SpecificIdKind,
+ features: ProtocolFeatures,
+}
+
+pub enum HandlerStatus {
+ Handled,
+ NeedsOk,
+ DeferredStopReason,
+ Disconnect(DisconnectReason),
+}
+
+impl<T: Target, C: Connection> GdbStubImpl<T, C> {
+ pub fn new() -> GdbStubImpl<T, C> {
+ 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),
+ features: ProtocolFeatures::empty(),
+ }
+ }
+
+ pub fn handle_packet(
+ &mut self,
+ target: &mut T,
+ conn: &mut C,
+ packet: Packet<'_>,
+ ) -> Result<State, Error<T::Error, C::Error>> {
+ match packet {
+ Packet::Ack => Ok(State::Pump),
+ Packet::Nack => Err(Error::ClientSentNack),
+ Packet::Interrupt => {
+ debug!("<-- interrupt packet");
+ Ok(State::CtrlCInterrupt)
+ }
+ Packet::Command(command) => {
+ // Acknowledge the command
+ if !self.features.no_ack_mode() {
+ conn.write(b'+').map_err(Error::ConnectionWrite)?;
+ }
+
+ let mut res = ResponseWriter::new(conn, target.use_rle());
+ let disconnect_reason = match self.handle_command(&mut res, target, command) {
+ Ok(HandlerStatus::Handled) => None,
+ Ok(HandlerStatus::NeedsOk) => {
+ res.write_str("OK")?;
+ None
+ }
+ Ok(HandlerStatus::DeferredStopReason) => return Ok(State::DeferredStopReason),
+ 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(e) => return Err(e),
+ };
+
+ // every response needs to be flushed, _except_ for the response to a kill
+ // packet, but ONLY when extended mode is NOT implemented.
+ let is_kill = matches!(disconnect_reason, Some(DisconnectReason::Kill));
+ if !(target.support_extended_mode().is_none() && is_kill) {
+ res.flush()?;
+ }
+
+ let state = match disconnect_reason {
+ Some(reason) => State::Disconnect(reason),
+ None => State::Pump,
+ };
+
+ Ok(state)
+ }
+ }
+ }
+
+ fn handle_command(
+ &mut self,
+ res: &mut ResponseWriter<'_, C>,
+ target: &mut T,
+ cmd: Command<'_>,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ match cmd {
+ // `handle_X` methods are defined in the `ext` module
+ Command::Base(cmd) => self.handle_base(res, target, cmd),
+ Command::TargetXml(cmd) => self.handle_target_xml(res, target, cmd),
+ Command::Resume(cmd) => self.handle_stop_resume(res, target, cmd),
+ Command::XUpcasePacket(cmd) => self.handle_x_upcase_packet(res, target, cmd),
+ Command::SingleRegisterAccess(cmd) => {
+ self.handle_single_register_access(res, target, cmd)
+ }
+ Command::Breakpoints(cmd) => self.handle_breakpoints(res, target, cmd),
+ Command::CatchSyscalls(cmd) => self.handle_catch_syscalls(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),
+ Command::MemoryMap(cmd) => self.handle_memory_map(res, target, cmd),
+ Command::HostIo(cmd) => self.handle_host_io(res, target, cmd),
+ Command::ExecFile(cmd) => self.handle_exec_file(res, target, cmd),
+ Command::Auxv(cmd) => self.handle_auxv(res, target, cmd),
+ Command::ThreadExtraInfo(cmd) => self.handle_thread_extra_info(res, target, cmd),
+ Command::LldbRegisterInfo(cmd) => self.handle_lldb_register_info(res, target, cmd),
+ // in the worst case, the command could not be parsed...
+ Command::Unknown(cmd) => {
+ // HACK: if the user accidentally sends a resume command to a
+ // target without resume support, inform them of their mistake +
+ // return a dummy stop reason.
+ if target.base_ops().resume_ops().is_none() && target.use_resume_stub() {
+ let is_resume_pkt = cmd
+ .first()
+ .map(|c| matches!(c, b'c' | b'C' | b's' | b'S'))
+ .unwrap_or(false);
+
+ if is_resume_pkt {
+ warn!("attempted to resume target without resume support!");
+
+ // TODO: omit this message if non-stop mode is active
+ {
+ let mut res = ResponseWriter::new(res.as_conn(), target.use_rle());
+ res.write_str("O")?;
+ res.write_hex_buf(b"target has not implemented `support_resume()`\n")?;
+ res.flush()?;
+ }
+
+ res.write_str("S05")?;
+ }
+ }
+
+ info!("Unknown command: {:?}", core::str::from_utf8(cmd));
+ Ok(HandlerStatus::Handled)
+ }
+ }
+ }
+}
+
+// This bitflag is not part of the protocol - it is an internal implementation
+// detail. The alternative would be to use multiple `bool` fields, which wastes
+// space in minimal `gdbstub` configurations.
+bitflags::bitflags! {
+ struct ProtocolFeatures: u8 {
+ const NO_ACK_MODE = 1 << 0;
+ const MULTIPROCESS = 1 << 1;
+ }
+}
+
+impl ProtocolFeatures {
+ #[inline(always)]
+ fn no_ack_mode(&self) -> bool {
+ self.contains(ProtocolFeatures::NO_ACK_MODE)
+ }
+
+ #[inline(always)]
+ fn set_no_ack_mode(&mut self, val: bool) {
+ self.set(ProtocolFeatures::NO_ACK_MODE, val)
+ }
+
+ #[inline(always)]
+ fn multiprocess(&self) -> bool {
+ self.contains(ProtocolFeatures::MULTIPROCESS)
+ }
+
+ #[inline(always)]
+ fn set_multiprocess(&mut self, val: bool) {
+ self.set(ProtocolFeatures::MULTIPROCESS, val)
+ }
+}