-
-
Notifications
You must be signed in to change notification settings - Fork 45
/
multithread.rs
328 lines (305 loc) · 12.7 KB
/
multithread.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
//! Base debugging operations for multi threaded targets.
use crate::arch::Arch;
use crate::common::*;
use crate::target::ext::breakpoints::WatchKind;
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
}
/// Read the target's registers.
///
/// If the registers could not be accessed, an appropriate non-fatal error
/// should be returned.
fn read_registers(
&mut self,
regs: &mut <Self::Arch as Arch>::Registers,
tid: Tid,
) -> TargetResult<(), Self>;
/// Write the target's registers.
///
/// If the registers could not be accessed, an appropriate non-fatal error
/// should be returned.
fn write_registers(
&mut self,
regs: &<Self::Arch as Arch>::Registers,
tid: Tid,
) -> TargetResult<(), Self>;
/// Support for single-register access.
/// See [`SingleRegisterAccess`](super::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.
#[inline(always)]
fn single_register_access(&mut self) -> Option<SingleRegisterAccessOps<Tid, Self>> {
None
}
/// Read bytes from the specified address range.
///
/// If the requested address range could not be accessed (e.g: due to
/// MMU protection, unhanded page fault, etc...), an appropriate non-fatal
/// error should be returned.
fn read_addrs(
&mut self,
start_addr: <Self::Arch as Arch>::Usize,
data: &mut [u8],
tid: Tid,
) -> TargetResult<(), Self>;
/// Write bytes to the specified address range.
///
/// If the requested address range could not be accessed (e.g: due to
/// MMU protection, unhanded page fault, etc...), an appropriate non-fatal
/// error should be returned.
fn write_addrs(
&mut self,
start_addr: <Self::Arch as Arch>::Usize,
data: &[u8],
tid: Tid,
) -> TargetResult<(), Self>;
/// List all currently active threads.
///
/// See [the section above](#bare-metal-targets) on implementing
/// thread-related methods on bare-metal (threadless) targets.
fn list_active_threads(
&mut self,
thread_is_active: &mut dyn FnMut(Tid),
) -> Result<(), Self::Error>;
/// Check if the specified thread is alive.
///
/// As a convenience, this method provides a default implementation which
/// 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.
fn is_thread_alive(&mut self, tid: Tid) -> Result<bool, Self::Error> {
let mut found = false;
self.list_active_threads(&mut |active_tid| {
if tid == active_tid {
found = true;
}
})?;
Ok(found)
}
}
/// 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(
&mut self,
gdb_interrupt: GdbInterrupt<'_>,
) -> Result<ThreadStopReason<<Self::Arch as Arch>::Usize>, Self::Error>;
}
define_ext!(MultiThreadReverseContOps, MultiThreadReverseCont);
/// 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(
&mut self,
tid: Tid,
gdb_interrupt: GdbInterrupt<'_>,
) -> Result<ThreadStopReason<<Self::Arch as Arch>::Usize>, Self::Error>;
}
define_ext!(MultiThreadReverseStepOps, MultiThreadReverseStep);
/// 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`].
fn set_resume_action_range_step(
&mut self,
tid: Tid,
start: <Self::Arch as Arch>::Usize,
end: <Self::Arch as Arch>::Usize,
) -> Result<(), Self::Error>;
}
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),
}