diff options
Diffstat (limited to 'src/target/ext/base/multithread.rs')
-rw-r--r-- | src/target/ext/base/multithread.rs | 407 |
1 files changed, 181 insertions, 226 deletions
diff --git a/src/target/ext/base/multithread.rs b/src/target/ext/base/multithread.rs index eba9d79..693030e 100644 --- a/src/target/ext/base/multithread.rs +++ b/src/target/ext/base/multithread.rs @@ -1,127 +1,12 @@ //! Base debugging operations for multi threaded targets. use crate::arch::Arch; -use crate::common::*; -use crate::target::ext::breakpoints::WatchKind; +use crate::common::Signal; +use crate::common::Tid; use crate::target::{Target, TargetResult}; -use super::{ReplayLogPosition, SingleRegisterAccessOps}; - -// Convenient re-exports -pub use super::{GdbInterrupt, ResumeAction}; - -/// Base debugging operations for multi threaded targets. -#[allow(clippy::type_complexity)] -pub trait MultiThreadOps: Target { - /// Resume execution on the target. - /// - /// Prior to calling `resume`, `gdbstub` will call `clear_resume_actions`, - /// followed by zero or more calls to `set_resume_action`, specifying any - /// thread-specific resume actions. - /// - /// The `default_action` parameter specifies the "fallback" resume action - /// for any threads that did not have a specific resume action set via - /// `set_resume_action`. The GDB client typically sets this to - /// `ResumeAction::Continue`, though this is not guaranteed. - /// - /// The `check_gdb_interrupt` callback can be invoked to check if GDB sent - /// an Interrupt packet (i.e: the user pressed Ctrl-C). It's recommended to - /// invoke this callback every-so-often while the system is running (e.g: - /// every X cycles/milliseconds). Periodically checking for incoming - /// interrupt packets is _not_ required, but it is _recommended_. - /// - /// # Implementation requirements - /// - /// These requirements cannot be satisfied by `gdbstub` internally, and must - /// be handled on a per-target basis. - /// - /// ### Adjusting PC after a breakpoint is hit - /// - /// The [GDB remote serial protocol documentation](https://sourceware.org/gdb/current/onlinedocs/gdb/Stop-Reply-Packets.html#swbreak-stop-reason) - /// notes the following: - /// - /// > On some architectures, such as x86, at the architecture level, when a - /// > breakpoint instruction executes the program counter points at the - /// > breakpoint address plus an offset. On such targets, the stub is - /// > responsible for adjusting the PC to point back at the breakpoint - /// > address. - /// - /// Omitting PC adjustment may result in unexpected execution flow and/or - /// breakpoints not working correctly. - /// - /// # Additional Considerations - /// - /// ### Bare-Metal Targets - /// - /// On bare-metal targets (such as microcontrollers or emulators), it's - /// common to treat individual _CPU cores_ as a separate "threads". e.g: - /// in a dual-core system, [CPU0, CPU1] might be mapped to [TID1, TID2] - /// (note that TIDs cannot be zero). - /// - /// In this case, the `Tid` argument of `read/write_addrs` becomes quite - /// relevant, as different cores may have different memory maps. - /// - /// ### Running in "Non-stop" mode - /// - /// At the moment, `gdbstub` only supports GDB's - /// ["All-Stop" mode](https://sourceware.org/gdb/current/onlinedocs/gdb/All_002dStop-Mode.html), - /// whereby _all_ threads must be stopped when returning from `resume` - /// (not just the thread associated with the `ThreadStopReason`). - fn resume( - &mut self, - default_resume_action: ResumeAction, - gdb_interrupt: GdbInterrupt<'_>, - ) -> Result<ThreadStopReason<<Self::Arch as Arch>::Usize>, Self::Error>; - - /// Clear all previously set resume actions. - fn clear_resume_actions(&mut self) -> Result<(), Self::Error>; - - /// Specify what action each thread should take when - /// [`resume`](Self::resume) is called. - /// - /// A simple implementation of this method would simply update an internal - /// `HashMap<Tid, ResumeAction>`. - /// - /// Aside from the four "base" resume actions handled by this method (i.e: - /// `Step`, `Continue`, `StepWithSignal`, and `ContinueWithSignal`), - /// there are also two additional resume actions which are only set if the - /// target implements their corresponding protocol extension: - /// - /// Action | Protocol Extension - /// ---------------------------|--------------------------- - /// Optimized [Range Stepping] | See [`support_range_step()`] - /// "Stop" | Used in "Non-Stop" mode \* - /// - /// \* "Non-Stop" mode is currently unimplemented - /// - /// [Range Stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping - /// [`support_range_step()`]: Self::support_range_step - fn set_resume_action(&mut self, tid: Tid, action: ResumeAction) -> Result<(), Self::Error>; - - /// Support for the optimized [range stepping] resume action. - /// - /// [range stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping - #[inline(always)] - fn support_range_step(&mut self) -> Option<MultiThreadRangeSteppingOps<Self>> { - None - } - - /// Support for [reverse stepping] a target. - /// - /// [reverse stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html - #[inline(always)] - fn support_reverse_step(&mut self) -> Option<MultiThreadReverseStepOps<Self>> { - None - } - - /// Support for [reverse continuing] a target. - /// - /// [reverse continuing]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html - #[inline(always)] - fn support_reverse_cont(&mut self) -> Option<MultiThreadReverseContOps<Self>> { - None - } - +/// Base required debugging operations for multi threaded targets. +pub trait MultiThreadBase: Target { /// Read the target's registers. /// /// If the registers could not be accessed, an appropriate non-fatal error @@ -143,14 +28,18 @@ pub trait MultiThreadOps: Target { ) -> TargetResult<(), Self>; /// Support for single-register access. - /// See [`SingleRegisterAccess`](super::SingleRegisterAccess) for more - /// details. + /// See [`SingleRegisterAccess`] for more details. /// /// While this is an optional feature, it is **highly recommended** to /// implement it when possible, as it can significantly improve performance /// on certain architectures. + /// + /// [`SingleRegisterAccess`]: + /// super::single_register_access::SingleRegisterAccess #[inline(always)] - fn single_register_access(&mut self) -> Option<SingleRegisterAccessOps<Tid, Self>> { + fn support_single_register_access( + &mut self, + ) -> Option<super::single_register_access::SingleRegisterAccessOps<'_, Tid, Self>> { None } @@ -182,6 +71,10 @@ pub trait MultiThreadOps: Target { /// /// See [the section above](#bare-metal-targets) on implementing /// thread-related methods on bare-metal (threadless) targets. + /// + /// _Note_: Implementors should mark this method as `#[inline(always)]`, as + /// this will result in better codegen (namely, by sidestepping any of the + /// `dyn FnMut` closure machinery). fn list_active_threads( &mut self, thread_is_active: &mut dyn FnMut(Tid), @@ -193,6 +86,7 @@ pub trait MultiThreadOps: Target { /// uses `list_active_threads` to do a linear-search through all active /// threads. On thread-heavy systems, it may be more efficient /// to override this method with a more direct query. + #[allow(clippy::wrong_self_convention)] // requires breaking change to fix fn is_thread_alive(&mut self, tid: Tid) -> Result<bool, Self::Error> { let mut found = false; self.list_active_threads(&mut |active_tid| { @@ -202,59 +96,182 @@ pub trait MultiThreadOps: Target { })?; Ok(found) } + + /// Support for resuming the target (e.g: via `continue` or `step`) + #[inline(always)] + fn support_resume(&mut self) -> Option<MultiThreadResumeOps<'_, Self>> { + None + } + + /// Support for providing thread extra information. + #[inline(always)] + fn support_thread_extra_info( + &mut self, + ) -> Option<crate::target::ext::thread_extra_info::ThreadExtraInfoOps<'_, Self>> { + None + } } -/// Target Extension - [Reverse continue] for multi threaded targets. -/// -/// Reverse continue allows the target to run backwards until it reaches the end -/// of the replay log. -/// -/// [Reverse continue]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html -pub trait MultiThreadReverseCont: Target + MultiThreadOps { - /// Reverse-continue the target. - fn reverse_cont( +/// Target extension - support for resuming multi threaded targets. +pub trait MultiThreadResume: Target { + /// Resume execution on the target. + /// + /// Prior to calling `resume`, `gdbstub` will call `clear_resume_actions`, + /// followed by zero or more calls to the `set_resume_action_XXX` methods, + /// specifying any thread-specific resume actions. + /// + /// Upon returning from the `resume` method, the target being debugged + /// should be configured to run according to whatever resume actions the + /// GDB client had specified using any of the `set_resume_action_XXX` + /// methods. + /// + /// Any thread that wasn't explicitly resumed by a `set_resume_action_XXX` + /// method should be resumed as though it was resumed with + /// `set_resume_action_continue`. + /// + /// A basic target implementation only needs to implement support for + /// `set_resume_action_continue`, with all other resume actions requiring + /// their corresponding protocol extension to be implemented: + /// + /// Action | Protocol Extension + /// ----------------------------|------------------------------ + /// Optimized [Single Stepping] | See [`support_single_step()`] + /// Optimized [Range Stepping] | See [`support_range_step()`] + /// "Stop" | Used in "Non-Stop" mode \* + /// + /// \* "Non-Stop" mode is currently unimplemented in `gdbstub` + /// + /// [Single stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#index-stepi + /// [Range Stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping + /// [`support_single_step()`]: Self::support_single_step + /// [`support_range_step()`]: Self::support_range_step + /// + /// # Additional Considerations + /// + /// ### Adjusting PC after a breakpoint is hit + /// + /// The [GDB remote serial protocol documentation](https://sourceware.org/gdb/current/onlinedocs/gdb/Stop-Reply-Packets.html#swbreak-stop-reason) + /// notes the following: + /// + /// > On some architectures, such as x86, at the architecture level, when a + /// > breakpoint instruction executes the program counter points at the + /// > breakpoint address plus an offset. On such targets, the stub is + /// > responsible for adjusting the PC to point back at the breakpoint + /// > address. + /// + /// Omitting PC adjustment may result in unexpected execution flow and/or + /// breakpoints not appearing to work correctly. + /// + /// ### Bare-Metal Targets + /// + /// On bare-metal targets (such as microcontrollers or emulators), it's + /// common to treat individual _CPU cores_ as a separate "threads". e.g: + /// in a dual-core system, [CPU0, CPU1] might be mapped to [TID1, TID2] + /// (note that TIDs cannot be zero). + /// + /// In this case, the `Tid` argument of `read/write_addrs` becomes quite + /// relevant, as different cores may have different memory maps. + fn resume(&mut self) -> Result<(), Self::Error>; + + /// Clear all previously set resume actions. + fn clear_resume_actions(&mut self) -> Result<(), Self::Error>; + + /// Continue the specified thread. + /// + /// See the [`resume`](Self::resume) docs for information on when this is + /// called. + /// + /// The GDB client may also include a `signal` which should be passed to the + /// target. + fn set_resume_action_continue( + &mut self, + tid: Tid, + signal: Option<Signal>, + ) -> Result<(), Self::Error>; + + /// Support for optimized [single stepping]. + /// + /// [single stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#index-stepi + #[inline(always)] + fn support_single_step(&mut self) -> Option<MultiThreadSingleStepOps<'_, Self>> { + None + } + + /// Support for optimized [range stepping]. + /// + /// [range stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping + #[inline(always)] + fn support_range_step(&mut self) -> Option<MultiThreadRangeSteppingOps<'_, Self>> { + None + } + + /// Support for [reverse stepping] a target. + /// + /// [reverse stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html + #[inline(always)] + fn support_reverse_step( &mut self, - gdb_interrupt: GdbInterrupt<'_>, - ) -> Result<ThreadStopReason<<Self::Arch as Arch>::Usize>, Self::Error>; + ) -> Option<super::reverse_exec::ReverseStepOps<'_, Tid, Self>> { + None + } + + /// Support for [reverse continuing] a target. + /// + /// [reverse continuing]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html + #[inline(always)] + fn support_reverse_cont( + &mut self, + ) -> Option<super::reverse_exec::ReverseContOps<'_, Tid, Self>> { + None + } } -define_ext!(MultiThreadReverseContOps, MultiThreadReverseCont); +define_ext!(MultiThreadResumeOps, MultiThreadResume); -/// Target Extension - [Reverse stepping] for multi threaded targets. -/// -/// Reverse stepping allows the target to run backwards by one step. -/// -/// [Reverse stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html -pub trait MultiThreadReverseStep: Target + MultiThreadOps { - /// Reverse-step the specified [`Tid`]. - fn reverse_step( +/// Target Extension - Optimized single stepping for multi threaded targets. +/// See [`MultiThreadResume::support_single_step`]. +pub trait MultiThreadSingleStep: Target + MultiThreadResume { + /// [Single step] the specified target thread. + /// + /// Single stepping will step the target a single "step" - typically a + /// single instruction. + /// + /// The GDB client may also include a `signal` which should be passed to the + /// target. + /// + /// If your target does not support signals (e.g: the target is a bare-metal + /// microcontroller / emulator), the recommended behavior is to return a + /// target-specific fatal error + /// + /// [Single step]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#index-stepi + fn set_resume_action_step( &mut self, tid: Tid, - gdb_interrupt: GdbInterrupt<'_>, - ) -> Result<ThreadStopReason<<Self::Arch as Arch>::Usize>, Self::Error>; + signal: Option<Signal>, + ) -> Result<(), Self::Error>; } -define_ext!(MultiThreadReverseStepOps, MultiThreadReverseStep); +define_ext!(MultiThreadSingleStepOps, MultiThreadSingleStep); -/// Target Extension - Optimized [range stepping] for multi threaded targets. -/// See [`MultiThreadOps::support_range_step`]. -/// -/// Range Stepping will step the target once, and keep stepping the target as -/// long as execution remains between the specified start (inclusive) and end -/// (exclusive) addresses, or another stop condition is met (e.g: a breakpoint -/// it hit). -/// -/// If the range is empty (`start` == `end`), then the action becomes -/// equivalent to the āsā action. In other words, single-step once, and -/// report the stop (even if the stepped instruction jumps to start). -/// -/// _Note:_ A stop reply may be sent at any point even if the PC is still -/// within the stepping range; for example, it is valid to implement range -/// stepping in a degenerate way as a single instruction step operation. -/// -/// [range stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping -pub trait MultiThreadRangeStepping: Target + MultiThreadOps { - /// See [`MultiThreadOps::set_resume_action`]. +/// Target Extension - Optimized range stepping for multi threaded targets. +/// See [`MultiThreadResume::support_range_step`]. +pub trait MultiThreadRangeStepping: Target + MultiThreadResume { + /// [Range step] the specified target thread. + /// + /// Range Stepping will step the target once, and keep stepping the target + /// as long as execution remains between the specified start (inclusive) + /// and end (exclusive) addresses, or another stop condition is met + /// (e.g: a breakpoint it hit). + /// + /// If the range is empty (`start` == `end`), then the action becomes + /// equivalent to the āsā action. In other words, single-step once, and + /// report the stop (even if the stepped instruction jumps to start). + /// + /// _Note:_ A stop reply may be sent at any point even if the PC is still + /// within the stepping range; for example, it is valid to implement range + /// stepping in a degenerate way as a single instruction step operation. + /// + /// [Range step]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping fn set_resume_action_range_step( &mut self, tid: Tid, @@ -264,65 +281,3 @@ pub trait MultiThreadRangeStepping: Target + MultiThreadOps { } define_ext!(MultiThreadRangeSteppingOps, MultiThreadRangeStepping); - -/// Describes why a thread stopped. -/// -/// Targets MUST only respond with stop reasons that correspond to IDETs that -/// target has implemented. -/// -/// e.g: A target which has not implemented the [`HwBreakpoint`] IDET must not -/// return a `HwBreak` stop reason. While this is not enforced at compile time, -/// doing so will result in a runtime `UnsupportedStopReason` error. -/// -/// [`HwBreakpoint`]: crate::target::ext::breakpoints::HwBreakpoint -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum ThreadStopReason<U> { - /// Completed the single-step request. - DoneStep, - /// `check_gdb_interrupt` returned `true`. - GdbInterrupt, - /// The process exited with the specified exit status. - Exited(u8), - /// The process terminated with the specified signal number. - Terminated(u8), - /// The program received a signal. - Signal(u8), - /// A thread hit a software breakpoint (e.g. due to a trap instruction). - /// - /// Requires: [`SwBreakpoint`]. - /// - /// NOTE: This does not necessarily have to be a breakpoint configured by - /// the client/user of the current GDB session. - /// - /// [`SwBreakpoint`]: crate::target::ext::breakpoints::SwBreakpoint - SwBreak(Tid), - /// A thread hit a hardware breakpoint. - /// - /// Requires: [`HwBreakpoint`]. - /// - /// [`HwBreakpoint`]: crate::target::ext::breakpoints::HwBreakpoint - HwBreak(Tid), - /// A thread hit a watchpoint. - /// - /// Requires: [`HwWatchpoint`]. - /// - /// [`HwWatchpoint`]: crate::target::ext::breakpoints::HwWatchpoint - Watch { - /// Which thread hit the watchpoint - tid: Tid, - /// Kind of watchpoint that was hit - kind: WatchKind, - /// Address of watched memory - addr: U, - }, - /// The program has reached the end of the logged replay events. - /// - /// Requires: [`MultiThreadReverseCont`] or [`MultiThreadReverseStep`]. - /// - /// This is used for GDB's reverse execution. When playing back a recording, - /// you may hit the end of the buffer of recorded events, and as such no - /// further execution can be done. This stop reason tells GDB that this has - /// occurred. - ReplayLog(ReplayLogPosition), -} |