• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Transition Guide
2
3This document provides a brief overview of breaking changes between major `gdbstub` releases, along with tips/tricks/suggestions on how to migrate between `gdbstub` releases.
4
5This document does _not_ discuss any new features that might have been added between releases. For a comprehensive overview of what's been _added_ to `gdbstub` (as opposed to what's _changed_), check out the [`CHANGELOG.md`](../CHANGELOG.md).
6
7> _Note:_ after reading through this doc, you may also find it helpful to refer to the in-tree `armv4t` and `armv4t_multicore` examples when transitioning between versions.
8
9## `0.5` -> `0.6`
10
11`0.6` introduces a large number of breaking changes to the public APIs, and will require quite a bit more more "hands on" porting than previous `gdbstub` upgrades.
12
13The following guide is a **best-effort** attempt to document all the changes, but there are some parts that may be missing / incomplete.
14
15##### General API change - _lots_ of renaming + exported type reorganization
16
17Many types have been renamed, and many import paths have changed in `0.6`.
18
19Exhaustively listing them would be nearly impossible, but suffice it to say, you will need to tweak your imports.
20
21##### `Connection` API changes
22
23> _Note:_ If you haven't implemented `Connection` yourself (i.e: you are using one of the built-in `Connection` impls on `TcpStream`/`UnixStream`), you can skip this section.
24
25The blocking `read` method and non-blocking `peek` methods have been removed from the base `Connection` API, and have been moved to a new `ConnectionExt` type.
26
27For more context around this change, please refer to [Moving from `GdbStub::run` to `GdbStub::run_blocking`](#moving-from-gdbstubrun-to-gdbstubrun_blocking).
28
29Porting a `0.5` `Connection` to `0.6` is incredibly straightforward - you simply split your existing implementation in two:
30
31```rust
32// ==== 0.5.x ==== //
33
34impl Connection for MyConnection {
35    type Error = MyError;
36
37    fn write(&mut self, byte: u8) -> Result<(), Self::Error> { .. }
38    fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { .. }
39    fn read(&mut self) -> Result<u8, Self::Error> { .. }
40    fn peek(&mut self) -> Result<Option<u8>, Self::Error> { .. }
41    fn flush(&mut self) -> Result<(), Self::Error> { .. }
42    fn on_session_start(&mut self) -> Result<(), Self::Error> { .. }
43}
44
45// ==== 0.6.0 ==== //
46
47impl Connection for MyConnection {
48    type Error = MyError;
49
50    fn write(&mut self, byte: u8) -> Result<(), Self::Error> { .. }
51    fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { .. }
52    fn flush(&mut self) -> Result<(), Self::Error> { .. }
53    fn on_session_start(&mut self) -> Result<(), Self::Error> { .. }
54}
55
56impl ConnectionExt for MyConnection {
57    type Error = MyError;
58
59    fn read(&mut self) -> Result<u8, Self::Error> { .. }
60    fn peek(&mut self) -> Result<Option<u8>, Self::Error> { .. }
61}
62
63```
64
65##### `Arch` API - `RegId::from_raw_id`
66
67> _Note:_ If you haven't implemented `Arch` yourself (i.e: you are any of the `Arch` impls from `gdbstub_arch`), you can skip this section.
68
69The `Arch` API has had one breaking changes: The `RegId::from_raw_id` method's "register size" return value has been changed from `usize` to `Option<NonZeroUsize>`.
70
71If the register size is `Some`, `gdbstub` will include a runtime check to ensures that the target implementation does not send back more bytes than the register allows when responding to single-register read requests.
72
73If the register size is `None`, `gdbstub` will _omit_ this runtime check, and trust that the target's implementation of `read_register` is correct.
74
75_Porting advice:_ If your `Arch` implementation targets a specific architecture, it is _highly recommended_ that you simply wrap your existing size value with `Some`. This API change was made to support dynamic `Arch` implementations, whereby the behavior of the `Arch` varies on the runtime state of the program (e.g: in multi-system emulators), and there is not "fixed" register size per id.
76
77##### `Target` API - IDET methods are now prefixed with `supports_`
78
79All IDET methods have been prefixed with `supports_`, to make it easier to tell at-a-glance which methods are actual handler methods, and which are simply IDET plumbing.
80
81As such, when porting target code from `0.5` to `0.6`, before you dive into any functional changes, you should take a moment to find and rename any methods that have had their name changed.
82
83##### `Target` API - Introducing `enum Signal`
84
85In prior versions of `gdbstub`, signals were encoded as raw `u8` values. This wasn't very user-friendly, as it meant users had to manually locate the signal-to-integer mapping table themselves when working with signals in code.
86
87`0.6` introduces a new `enum Signal` which encodes this information within `gdbstub` itself.
88
89This new `Signal` type has replaced `u8` in any places that a `u8` was used to represent a signal, such as in `StopReason::Signal`, or as part of the various `resume` APIs.
90
91_Porting advice:_ The Rust compiler should catch any type errors due to this change, making it easy to swap out any instances of `u8` with the new `Signal` type.
92
93##### `HwWatchpoint` API - Plumb watchpoint `length` parameter to public API
94
95The watchpoint API has been updated to include a new `length` parameter, specifying what range of memory addresses the watchpoint should encompass.
96
97##### `TargetXmlOverride` API - Return data via `&mut [u8]` buffer
98
99In an effort to unify the implementations of various new `qXfer`-backed protocol extensions, the existing `TargetXmlOverride` has been changed from returning a `&str` value to using a `std::io::Read`-style "write the data into a `&mut [u8]` buffer" API.
100
101Porting a `0.5` `TargetDescriptionXmlOverride` to `0.6` is straightforward, though a bit boilerplate-y.
102
103```rust
104// ==== 0.5.x ==== //
105
106impl target::ext::target_description_xml_override::TargetDescriptionXmlOverride for Emu {
107    fn target_description_xml(&self) -> &str {
108        r#"<target version="1.0"><!-- custom override string --><architecture>armv4t</architecture></target>"#
109    }
110}
111
112// ==== 0.6.0 ==== //
113
114pub fn copy_to_buf(data: &[u8], buf: &mut [u8]) -> usize {
115    let len = data.len();
116    let buf = &mut buf[..len];
117    buf.copy_from_slice(data);
118    len
119}
120
121pub fn copy_range_to_buf(data: &[u8], offset: u64, length: usize, buf: &mut [u8]) -> usize {
122    let offset = match usize::try_from(offset) {
123        Ok(v) => v,
124        Err(_) => return 0,
125    };
126    let len = data.len();
127    let data = &data[len.min(offset)..len.min(offset + length)];
128    copy_to_buf(data, buf)
129}
130
131impl target::ext::target_description_xml_override::TargetDescriptionXmlOverride for Emu {
132    fn target_description_xml(
133        &self,
134        offset: u64,
135        length: usize,
136        buf: &mut [u8],
137    ) -> TargetResult<usize, Self> {
138        let xml = r#"<target version="1.0"><!-- custom override string --><architecture>armv4t</architecture></target>"#
139            .trim()
140            .as_bytes();
141        Ok(copy_range_to_buf(xml, offset, length, buf))
142    }
143}
144```
145
146##### Updates to `{Single,Multi}ThreadOps::resume` API
147
148`0.6` includes three fairly major behavioral changes to the `resume` method:
149
150###### Support for `resume` is now entirely optional
151
152There are quite a few use cases where it might make sense to debug a target that does _not_ support resumption, e.g: a post-mortem debugging session, or when debugging crash dumps. In these cases, past version of `gdbstub` would force the user to nonetheless implement "stub" methods for resuming these targets, along with forcing users to pay the "cost" of including all the handler code related to resumption (of which there is quite a bit.)
153
154In `0.6`, all resume-related functionality has been extracted out of `{Single,Multi}ThreadBase`, and split into new `{Singe,Multi}ThreadResume` IDETs.
155
156###### Removing `ResumeAction`, and making single-step support optional
157
158The GDB protocol only requires that targets implement support for _continuing_ execution - support for instruction-level single-step execution is totally optional.
159
160> Note: this isn't actually true in practice, thanks to a bug in the mainline GDB client... See the docs for `Target::use_optional_single_step` for details...
161
162To model this behavior, `0.6` has split single-step support into its own IDET, in a manner similar to how optimized range step support was handled in `0.5`.
163
164In doing so, the `enum ResumeAction` type could be removed entirely, as single-step resume was to be handled in its own method.
165
166###### Removing `gdb_interrupt: GdbInterrupt`, and making `resume` non-blocking
167
168In past versions of `gdbstub`, the `resume` API would _block_ the thread waiting for the target to hit some kind of stop condition. In this model, checking for pending GDB interrupts was quite unergonomic, requiring that the thread periodically wake up and check whether an interrupt has arrived via the `GdbInterrupt` type.
169
170`gdbstub` `0.6` introduces a new paradigm of driving target execution, predicated on the idea that the target's `resume` method _does not block_, instead yielding execution immediately, and deferring the responsibility of "selecting" between incoming stop events and GDB interrupts to higher levels of the `gdbstub` "stack".
171
172In practice, this means that much of the logic that used to live in the `resume` implementation will now move into upper-levels of the `gdbstub` API, with the `resume` API serving more of a "bookkeeping" purpose, recording what kind of resumption mode the GDB client has requested from the target, while not actually resuming the target itself.
173
174For more context around this change, please refer to [Moving from `GdbStub::run` to `GdbStub::run_blocking`](#moving-from-gdbstubrun-to-gdbstubrun_blocking).
175
176###### Example: migrating `resume` from `0.5` to `0.6`
177
178Much of the code contained within methods such as `block_until_stop_reason_or_interrupt` will be lifted into upper layers of the `gdbstub` API, leaving behind just a small bit of code in the target's `resume` method to perform "bookkeeping" regarding how the GDB client requested the target to be resumed.
179
180```rust
181// ==== 0.5.x ==== //
182
183impl SingleThreadOps for Emu {
184    fn resume(
185        &mut self,
186        action: ResumeAction,
187        gdb_interrupt: GdbInterrupt<'_>,
188    ) -> Result<StopReason<u32>, Self::Error> {
189        match action {
190            ResumeAction::Step => self.do_single_step(),
191            ResumeAction::Continue => self.block_until_stop_reason_or_interrupt(action, || gdb_interrupt.pending()),
192            _ => self.handle_resume_with_signal(action),
193        }
194    }
195}
196
197// ==== 0.6.0 ==== //
198
199impl SingleThreadBase for Emu {
200    // resume has been split into a separate IDET
201    #[inline(always)]
202    fn support_resume(
203        &mut self
204    ) -> Option<SingleThreadResumeOps<Self>> {
205        Some(self)
206    }
207}
208
209
210impl SingleThreadResume for Emu {
211    fn resume(
212        &mut self,
213        signal: Option<Signal>,
214    ) -> Result<(), Self::Error> { // <-- no longer returns a stop reason!
215        if let Some(signal) = signal {
216            self.handle_signal(signal)?;
217        }
218
219        // upper layers of the `gdbstub` API will be responsible for "driving"
220        // target execution - `resume` simply performs book keeping on _how_ the
221        // target should be resumed.
222        self.set_execution_mode(ExecMode::Continue)?;
223
224        Ok(())
225    }
226
227    // single-step support has been split into a separate IDET
228    #[inline(always)]
229    fn support_single_step(
230        &mut self
231    ) -> Option<SingleThreadSingleStepOps<'_, Self>> {
232        Some(self)
233    }
234}
235
236impl SingleThreadSingleStep for Emu {
237    fn step(&mut self, signal: Option<Signal>) -> Result<(), Self::Error> {
238        if let Some(signal) = signal {
239            self.handle_signal(signal)?;
240        }
241
242        self.set_execution_mode(ExecMode::Step)?;
243        Ok(())
244    }
245}
246```
247
248##### Moving from `GdbStub::run` to `GdbStub::run_blocking`
249
250With the introduction of the new state-machine API, the responsibility of reading incoming has been lifted out of `gdbstub` itself, and is now something implementations are responsible for . The alternative approach would've been to have `Connection` include multiple different `read`-like methods for various kinds of paradigms - such as `async`/`await`, `epoll`, etc...
251
252> TODO. In the meantime, I would suggest looking at rustdoc for details on how to use `GdbStub::run_blocking`...
253
254## `0.4` -> `0.5`
255
256While the overall structure of the API has remained the same, `0.5.0` does introduce a few breaking API changes that require some attention. That being said, it should not be a difficult migration, and updating to `0.5.0` from `0.4` shouldn't take more than 10 mins of refactoring.
257
258##### Consolidating the `{Hw,Sw}Breakpoint/Watchpoint` IDETs under the newly added `Breakpoints` IDETs.
259
260The various breakpoint IDETs that were previously directly implemented on the top-level `Target` trait have now been consolidated under a single `Breakpoints` IDET. This is purely an organizational change, and will not require rewriting any existing `{add, remove}_{sw_break,hw_break,watch}point` implementations.
261
262Porting from `0.4` to `0.5` should be as simple as:
263
264```rust
265// ==== 0.4.x ==== //
266
267impl Target for Emu {
268    fn sw_breakpoint(&mut self) -> Option<target::ext::breakpoints::SwBreakpointOps<Self>> {
269        Some(self)
270    }
271
272    fn hw_watchpoint(&mut self) -> Option<target::ext::breakpoints::HwWatchpointOps<Self>> {
273        Some(self)
274    }
275}
276
277impl target::ext::breakpoints::SwBreakpoint for Emu {
278    fn add_sw_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> { ... }
279    fn remove_sw_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> { ... }
280}
281
282impl target::ext::breakpoints::HwWatchpoint for Emu {
283    fn add_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
284    fn remove_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
285}
286
287// ==== 0.5.0 ==== //
288
289impl Target for Emu {
290    // (New Method) //
291    fn breakpoints(&mut self) -> Option<target::ext::breakpoints::BreakpointsOps<Self>> {
292        Some(self)
293    }
294}
295
296impl target::ext::breakpoints::Breakpoints for Emu {
297    fn sw_breakpoint(&mut self) -> Option<target::ext::breakpoints::SwBreakpointOps<Self>> {
298        Some(self)
299    }
300
301    fn hw_watchpoint(&mut self) -> Option<target::ext::breakpoints::HwWatchpointOps<Self>> {
302        Some(self)
303    }
304}
305
306// (Almost Unchanged) //
307impl target::ext::breakpoints::SwBreakpoint for Emu {
308    //                                            /-- New `kind` parameter
309    //                                           \/
310    fn add_sw_breakpoint(&mut self, addr: u32, _kind: arch::arm::ArmBreakpointKind) -> TargetResult<bool, Self> { ... }
311    fn remove_sw_breakpoint(&mut self, addr: u32, _kind: arch::arm::ArmBreakpointKind) -> TargetResult<bool, Self> { ... }
312}
313
314// (Unchanged) //
315impl target::ext::breakpoints::HwWatchpoint for Emu {
316    fn add_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
317    fn remove_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
318}
319
320```
321
322##### Single-register access methods (`{read,write}_register`) are now a separate `SingleRegisterAccess` trait
323
324Single register access is not a required part of the GDB protocol, and as such, has been moved out into its own IDET. This is a purely organizational change, and will not require rewriting any existing `{read,write}_register` implementations.
325
326Porting from `0.4` to `0.5` should be as simple as:
327
328```rust
329// ==== 0.4.x ==== //
330
331impl SingleThreadOps for Emu {
332    fn read_register(&mut self, reg_id: arch::arm::reg::id::ArmCoreRegId, dst: &mut [u8]) -> TargetResult<(), Self> { ... }
333    fn write_register(&mut self, reg_id: arch::arm::reg::id::ArmCoreRegId, val: &[u8]) -> TargetResult<(), Self> { ... }
334}
335
336// ==== 0.5.0 ==== //
337
338impl SingleThreadOps for Emu {
339    // (New Method) //
340    fn single_register_access(&mut self) -> Option<target::ext::base::SingleRegisterAccessOps<(), Self>> {
341        Some(self)
342    }
343}
344
345impl target::ext::base::SingleRegisterAccess<()> for Emu {
346    //                           /-- New `tid` parameter (ignored on single-threaded systems)
347    //                          \/
348    fn read_register(&mut self, _tid: (), reg_id: arch::arm::reg::id::ArmCoreRegId, dst: &mut [u8]) -> TargetResult<(), Self> { ... }
349    fn write_register(&mut self, _tid: (), reg_id: arch::arm::reg::id::ArmCoreRegId, val: &[u8]) -> TargetResult<(), Self> { ... }
350}
351```
352
353##### New `MultiThreadOps::resume` API
354
355In `0.4`, resuming a multithreaded target was done using an `Actions` iterator passed to a single `resume` method. In hindsight, this approach had a couple issues:
356
357- It was impossible to statically enforce the property that the `Actions` iterator was guaranteed to return at least one element, often forcing users to manually `unwrap`
358- The iterator machinery was quite heavy, and did not optimize very effectively
359- Handling malformed packets encountered during iteration was tricky, as the user-facing API exposed an infallible iterator, thereby complicating the internal error handling
360- Adding new kinds of `ResumeAction` (e.g: range stepping) required a breaking change, and forced users to change their `resume` method implementation regardless whether or not their target ended up using said action.
361
362In `0.5`, the API has been refactored to address some of these issues, and the single `resume` method has now been split into multiple "lifecycle" methods:
363
3641. `resume`
365    - As before, when `resume` is called the target should resume execution.
366    - But how does the target know how each thread should be resumed? That's where the next method comes in...
3671. `set_resume_action`
368    - This method is called prior to `resume`, and notifies the target how a particular `Tid` should be resumed.
3691. (optionally) `set_resume_action_range_step`
370    - If the target supports optimized range-stepping, it can opt to implement the newly added `MultiThreadRangeStepping` IDET which includes this method.
371    - Targets that aren't interested in optimized range-stepping can skip this method!
3721. `clear_resume_actions`
373    - After the target returns a `ThreadStopReason` from `resume`, this method will be called to reset the previously set per-`tid` resume actions.
374
375NOTE: This change does mean that targets are now responsible for maintaining some internal state that maps `Tid`s to `ResumeAction`s. Thankfully, this isn't difficult at all, and can as simple as maintaining a `HashMap<Tid, ResumeAction>`.
376
377Please refer to the in-tree `armv4t_multicore` example for an example of how this new `resume` flow works.
378