| 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, |
| } |
| |
| #[derive(Debug)] |
| pub(crate) enum InternalError<T, C> { |
| /// Connection Error |
| Connection(C, ConnectionErrorKind), |
| /// Target encountered a fatal error. |
| TargetError(T), |
| |
| ClientSentNack, |
| PacketBufferOverflow, |
| PacketParse(PacketParseError), |
| PacketUnexpected, |
| TargetMismatch, |
| UnsupportedStopReason, |
| UnexpectedStepPacket, |
| ImplicitSwBreakpoints, |
| // 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) |
| // |
| // This "dummy" error is required as part of the internal |
| // `TargetResultExt::handle_error()` machinery, and will never be |
| // propagated up to the end user. |
| #[doc(hidden)] |
| NonFatalError(u8), |
| } |
| |
| 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<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: Display, |
| T: Display, |
| { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 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, "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!"), |
| } |
| } |
| } |
| |
| #[cfg(feature = "std")] |
| impl<T, C> std::error::Error for GdbStubError<T, C> |
| where |
| 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() |
| } |
| } |