diff options
Diffstat (limited to 'src/stub/error.rs')
-rw-r--r-- | src/stub/error.rs | 244 |
1 files changed, 155 insertions, 89 deletions
diff --git a/src/stub/error.rs b/src/stub/error.rs index 6d55997..8cdc18e 100644 --- a/src/stub/error.rs +++ b/src/stub/error.rs @@ -1,62 +1,42 @@ -use core::fmt::{self, Debug, Display}; - -use crate::arch::SingleStepGdbBehavior; -use crate::protocol::{PacketParseError, ResponseWriterError}; +use crate::protocol::PacketParseError; +use crate::protocol::ResponseWriterError; use crate::util::managed_vec::CapacityError; +use core::fmt::Debug; +use core::fmt::Display; +use core::fmt::{self}; + +/// An error that may occur while interacting with a +/// [`Connection`](crate::conn::Connection). +#[derive(Debug)] +pub enum ConnectionErrorKind { + /// Error initializing the session. + Init, + /// Error reading data. + Read, + /// Error writing data. + Write, +} -/// An error which may occur during a GDB debugging session. #[derive(Debug)] -#[non_exhaustive] -pub enum GdbStubError<T, C> { - /// Connection Error while initializing the session. - ConnectionInit(C), - /// Connection Error while reading request. - ConnectionRead(C), - /// Connection Error while writing response. - ConnectionWrite(C), - - /// Client nack'd the last packet, but `gdbstub` doesn't implement - /// re-transmission. +pub(crate) enum InternalError<T, C> { + /// Connection Error + Connection(C, ConnectionErrorKind), + /// Target encountered a fatal error. + TargetError(T), + ClientSentNack, - /// Packet cannot fit in the provided packet buffer. PacketBufferOverflow, - /// Could not parse the packet into a valid command. PacketParse(PacketParseError), - /// GDB client sent an unexpected packet. This should never happen! - /// Please re-run with `log` trace-level logging enabled and file an issue - /// at <https://github.com/daniel5151/gdbstub/issues> PacketUnexpected, - /// GDB client sent a packet with too much data for the given target. TargetMismatch, - /// Target encountered a fatal error. - TargetError(T), - /// Target responded with an unsupported stop reason. - /// - /// Certain stop reasons can only be used when their associated protocol - /// feature has been implemented. e.g: a Target cannot return a - /// `StopReason::HwBreak` if the hardware breakpoints IDET hasn't been - /// implemented. UnsupportedStopReason, - /// Target didn't report any active threads when there should have been at - /// least one running. - NoActiveThreads, - - /// The target has not opted into using implicit software breakpoints. - /// See [`Target::guard_rail_implicit_sw_breakpoints`] for more information. - /// - /// [`Target::guard_rail_implicit_sw_breakpoints`]: - /// crate::target::Target::guard_rail_implicit_sw_breakpoints + UnexpectedStepPacket, ImplicitSwBreakpoints, - /// The target has not indicated support for optional single stepping. See - /// [`Target::guard_rail_single_step_gdb_behavior`] for more information. - /// - /// If you encountered this error while using an `Arch` implementation - /// defined in `gdbstub_arch` and believe this is incorrect, please file an - /// issue at <https://github.com/daniel5151/gdbstub/issues>. - /// - /// [`Target::guard_rail_single_step_gdb_behavior`]: - /// crate::target::Target::guard_rail_single_step_gdb_behavior - SingleStepGdbBehavior(SingleStepGdbBehavior), + // DEVNOTE: this is a temporary workaround for something that can and should + // be caught at compile time via IDETs. That said, since i'm not sure when + // I'll find the time to cut a breaking release of gdbstub, I'd prefer to + // push out this feature as a non-breaking change now. + MissingCurrentActivePidImpl, // Internal - A non-fatal error occurred (with errno-style error code) // @@ -67,55 +47,100 @@ pub enum GdbStubError<T, C> { NonFatalError(u8), } -impl<T, C> From<ResponseWriterError<C>> for GdbStubError<T, C> { - fn from(e: ResponseWriterError<C>) -> Self { - GdbStubError::ConnectionWrite(e.0) +impl<T, C> InternalError<T, C> { + pub fn conn_read(e: C) -> Self { + InternalError::Connection(e, ConnectionErrorKind::Read) + } + + pub fn conn_write(e: C) -> Self { + InternalError::Connection(e, ConnectionErrorKind::Write) + } + + pub fn conn_init(e: C) -> Self { + InternalError::Connection(e, ConnectionErrorKind::Init) } } -impl<A, T, C> From<CapacityError<A>> for GdbStubError<T, C> { - fn from(_: CapacityError<A>) -> Self { - GdbStubError::PacketBufferOverflow +impl<T, C> From<ResponseWriterError<C>> for InternalError<T, C> { + fn from(e: ResponseWriterError<C>) -> Self { + InternalError::Connection(e.0, ConnectionErrorKind::Write) } } +// these macros are used to keep the docs and `Display` impl in-sync + +macro_rules! unsupported_stop_reason { + () => { + "User error: cannot report stop reason without also implementing its corresponding IDET" + }; +} + +macro_rules! unexpected_step_packet { + () => { + "Received an unexpected `step` request. This is most-likely due to this GDB client bug: <https://sourceware.org/bugzilla/show_bug.cgi?id=28440>" + }; +} + +/// An error which may occur during a GDB debugging session. +/// +/// ## Additional Notes +/// +/// `GdbStubError`'s inherent `Display` impl typically contains enough context +/// for users to understand why the error occurred. +/// +/// That said, there are a few instances where the error condition requires +/// additional context. +/// +/// * * * +#[doc = concat!("_", unsupported_stop_reason!(), "_")] +/// +/// This is a not a bug with `gdbstub`. Rather, this is indicative of a bug in +/// your `gdbstub` integration. +/// +/// Certain stop reasons can only be used when their associated protocol feature +/// has been implemented. e.g: a Target cannot return a `StopReason::HwBreak` if +/// the hardware breakpoints IDET hasn't been implemented. +/// +/// Please double-check that you've implemented all the necessary `supports_` +/// methods related to the stop reason you're trying to report. +/// +/// * * * +#[doc = concat!("_", unexpected_step_packet!(), "_")] +/// +/// Unfortunately, there's nothing `gdbstub` can do to work around this bug. +/// +/// Until the issue is fixed upstream, certain architectures are essentially +/// forced to manually implement single-step support. +#[derive(Debug)] +pub struct GdbStubError<T, C> { + kind: InternalError<T, C>, +} + impl<T, C> Display for GdbStubError<T, C> where - C: Debug, - T: Debug, + C: Display, + T: Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use self::GdbStubError::*; - match self { - ConnectionInit(e) => write!(f, "Connection Error while initializing the session: {:?}", e), - ConnectionRead(e) => write!(f, "Connection Error while reading request: {:?}", e), - ConnectionWrite(e) => write!(f, "Connection Error while writing response: {:?}", e), + use self::InternalError::*; + const CONTEXT: &str = "See the `GdbStubError` docs for more details"; + match &self.kind { + Connection(e, ConnectionErrorKind::Init) => write!(f, "Connection Error while initializing the session: {}", e), + Connection(e, ConnectionErrorKind::Read) => write!(f, "Connection Error while reading request: {}", e), + Connection(e, ConnectionErrorKind::Write) => write!(f, "Connection Error while writing response: {}", e), ClientSentNack => write!(f, "Client nack'd the last packet, but `gdbstub` doesn't implement re-transmission."), - PacketBufferOverflow => write!(f, "Packet too big for provided buffer!"), - PacketParse(e) => write!(f, "Could not parse the packet into a valid command: {:?}", e), - PacketUnexpected => write!(f, "Client sent an unexpected packet. Please re-run with `log` trace-level logging enabled and file an issue at https://github.com/daniel5151/gdbstub/issues"), - TargetMismatch => write!(f, "GDB client sent a packet with too much data for the given target."), - TargetError(e) => write!(f, "Target threw a fatal error: {:?}", e), - UnsupportedStopReason => write!(f, "Target responded with an unsupported stop reason."), - NoActiveThreads => write!(f, "Target didn't report any active threads when there should have been at least one running."), - - ImplicitSwBreakpoints => write!(f, "Warning: The target has not opted into using implicit software breakpoints. See `Target::guard_rail_implicit_sw_breakpoints` for more information."), - SingleStepGdbBehavior(behavior) => { - use crate::arch::SingleStepGdbBehavior; - write!( - f, - "Warning: Mismatch between the targets' single-step support and arch-level single-step behavior: {} ", - match behavior { - SingleStepGdbBehavior::Optional => "", // unreachable, since optional single step will not result in an error - SingleStepGdbBehavior::Required => "GDB requires single-step support on this arch.", - SingleStepGdbBehavior::Ignored => "GDB ignores single-step support on this arch, yet the target has implemented support for it.", - SingleStepGdbBehavior::Unknown => "This arch's single-step behavior hasn't been tested yet: please conduct a test + upstream your findings!", - } - )?; - write!(f, "See `Target::guard_rail_single_step_gdb_behavior` for more information.") - }, - - NonFatalError(_) => write!(f, "Internal non-fatal error. End users should never see this! Please file an issue if you do!"), + PacketBufferOverflow => write!(f, "Received an oversized packet (did not fit in provided packet buffer)"), + PacketParse(e) => write!(f, "Failed to parse packet into a valid command: {:?}", e), + PacketUnexpected => write!(f, "Client sent an unexpected packet. This should never happen! Please re-run with `log` trace-level logging enabled and file an issue at https://github.com/daniel5151/gdbstub/issues"), + TargetMismatch => write!(f, "Received a packet with too much data for the given target"), + TargetError(e) => write!(f, "Target threw a fatal error: {}", e), + UnsupportedStopReason => write!(f, "{} {}", unsupported_stop_reason!(), CONTEXT), + UnexpectedStepPacket => write!(f, "{} {}", unexpected_step_packet!(), CONTEXT), + + ImplicitSwBreakpoints => write!(f, "Warning: The target has not opted into using implicit software breakpoints. See `Target::guard_rail_implicit_sw_breakpoints` for more information"), + MissingCurrentActivePidImpl => write!(f, "GDB client attempted to attach to a new process, but the target has not implemented support for `ExtendedMode::support_current_active_pid`"), + + NonFatalError(_) => write!(f, "Internal non-fatal error. You should never see this! Please file an issue if you do!"), } } } @@ -123,7 +148,48 @@ where #[cfg(feature = "std")] impl<T, C> std::error::Error for GdbStubError<T, C> where - C: Debug, - T: Debug, + C: Debug + Display, + T: Debug + Display, { } + +impl<T, C> GdbStubError<T, C> { + /// Check if the error was due to a target error. + pub fn is_target_error(&self) -> bool { + matches!(self.kind, InternalError::TargetError(..)) + } + + /// If the error was due to a target error, return the concrete error type. + pub fn into_target_error(self) -> Option<T> { + match self.kind { + InternalError::TargetError(e) => Some(e), + _ => None, + } + } + + /// Check if the error was due to a connection error. + pub fn is_connection_error(&self) -> bool { + matches!(self.kind, InternalError::Connection(..)) + } + + /// If the error was due to a connection error, return the concrete error + /// type. + pub fn into_connection_error(self) -> Option<(C, ConnectionErrorKind)> { + match self.kind { + InternalError::Connection(e, kind) => Some((e, kind)), + _ => None, + } + } +} + +impl<T, C> From<InternalError<T, C>> for GdbStubError<T, C> { + fn from(kind: InternalError<T, C>) -> Self { + GdbStubError { kind } + } +} + +impl<A, T, C> From<CapacityError<A>> for GdbStubError<T, C> { + fn from(_: CapacityError<A>) -> Self { + InternalError::PacketBufferOverflow.into() + } +} |