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
329
330
331
332
333
334
335
336
337
338
//! Base debugging operations for multi threaded targets.
use crate::arch::Arch;
use crate::common::Signal;
use crate::common::Tid;
use crate::target::Target;
use crate::target::TargetResult;
/// 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
/// 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`] 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 support_single_register_access(
&mut self,
) -> Option<super::single_register_access::SingleRegisterAccessOps<'_, Tid, Self>> {
None
}
/// Read bytes from the specified address range and return the number of
/// bytes that were read.
///
/// Implementations may return a number `n` that is less than `data.len()`
/// to indicate that memory starting at `start_addr + n` cannot be
/// accessed.
///
/// Implemenations may also return an appropriate non-fatal error if the
/// requested address range could not be accessed (e.g: due to MMU
/// protection, unhanded page fault, etc...).
///
/// Implementations must guarantee that the returned number is less than or
/// equal `data.len()`.
fn read_addrs(
&mut self,
start_addr: <Self::Arch as Arch>::Usize,
data: &mut [u8],
tid: Tid,
) -> TargetResult<usize, 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.
///
/// _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),
) -> 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.
#[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| {
if tid == active_tid {
found = true;
}
})?;
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 - 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.
///
/// # Default Resume Behavior
///
/// By default, 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`.
///
/// **However**, if [`support_scheduler_locking`] is implemented and
/// [`set_resume_action_scheduler_lock`] has been called for the current
/// resume cycle, this default changes: **unmentioned threads must remain
/// stopped.**
///
/// [`support_scheduler_locking`]: Self::support_scheduler_locking
/// [`set_resume_action_scheduler_lock`]: MultiThreadSchedulerLocking::set_resume_action_scheduler_lock
///
/// # Protocol Extensions
///
/// 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()`]
/// [Scheduler Locking] | See [`support_scheduler_locking()`]
/// "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
/// [Scheduler Locking]: https://sourceware.org/gdb/current/onlinedocs/gdb#index-scheduler-locking-mode
/// [`support_single_step()`]: Self::support_single_step
/// [`support_range_step()`]: Self::support_range_step
/// [`support_scheduler_locking()`]: Self::support_scheduler_locking
///
/// # 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,
) -> 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
}
/// Support for [scheduler locking].
///
/// [scheduler locking]: https://sourceware.org/gdb/current/onlinedocs/gdb#index-scheduler-locking-mode
#[inline(always)]
fn support_scheduler_locking(&mut self) -> Option<MultiThreadSchedulerLockingOps<'_, Self>> {
None
}
}
define_ext!(MultiThreadResumeOps, MultiThreadResume);
/// 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,
signal: Option<Signal>,
) -> Result<(), Self::Error>;
}
define_ext!(MultiThreadSingleStepOps, MultiThreadSingleStep);
/// 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,
start: <Self::Arch as Arch>::Usize,
end: <Self::Arch as Arch>::Usize,
) -> Result<(), Self::Error>;
}
define_ext!(MultiThreadRangeSteppingOps, MultiThreadRangeStepping);
/// Target Extension - support for GDB's "Scheduler Locking" mode.
/// See [`MultiThreadResume::support_scheduler_locking`].
pub trait MultiThreadSchedulerLocking: Target + MultiThreadResume {
/// Configure the target to enable "Scheduler Locking" for the upcoming
/// resume.
///
/// This method is invoked when the GDB client expects only a specific set
/// of threads to run, while all other threads remain frozen. This behavior
/// is typically toggled in GDB using the `set scheduler-locking on`
/// command.
///
/// When this method is called, the implementation must ensure that any
/// threads not explicitly resumed via previous `set_resume_action_...`
/// calls **remain stopped**.
///
/// This prevents any "implicit continue" behavior for unmentioned threads,
/// satisfying GDB's expectation that only the designated threads will
/// advance during the next resume.
fn set_resume_action_scheduler_lock(&mut self) -> Result<(), Self::Error>;
}
define_ext!(MultiThreadSchedulerLockingOps, MultiThreadSchedulerLocking);