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.6` -> `0.7`
10
11`0.7` is a fairly minimal "cleanup" release, landing a collection of small breaking changes that collectively improve various ergonomic issues in `gdbstub`'s API.
12
13The breaking changes introduced in `0.7` are generally trivial to fix, and porting from `0.6` to `0.7` shouldn't take more than ~10 minutes, at most.
14
15##### `stub::GdbStubError` Changes
16
17`stub::GdbStubError` is now an opaque `struct` with a handful of methods to extract user-defined context.
18
19**Please file an issue if your code required matching on concrete error variants aside from `TargetError` and `ConnectionError`!**.
20
21In contrast with the old version - which was an `enum` that directly exposed all error internals to the user - this new type will enable future versions of `gdbstub` to fearlessly improve error infrastructure without requiring semver breaking changes. See [\#112](https://github.com/daniel5151/gdbstub/pull/132) for more.
22
23Assuming you stuck to the example error handling described in the `gdbstub` getting started guide, adapting to the new type should be quite straightforward.
24
25
26```rust
27// ==== 0.6.x ==== //
28
29match gdb.run_blocking::<EmuGdbEventLoop>(&mut emu) {
30    Ok(disconnect_reason) => { ... },
31    Err(GdbStubError::TargetError(e)) => {
32        println!("target encountered a fatal error: {}", e)
33    }
34    Err(e) => {
35        println!("gdbstub encountered a fatal error: {}", e)
36    }
37}
38
39// ==== 0.7.0 ==== //
40
41match gdb.run_blocking::<EmuGdbEventLoop>(&mut emu) {
42    Ok(disconnect_reason) => { ... },
43    Err(e) => {
44        if e.is_target_error() {
45            println!(
46                "target encountered a fatal error: {}",
47                e.into_target_error().unwrap()
48            )
49        } else if e.is_connection_error() {
50            let (e, kind) = e.into_connection_error().unwrap();
51            println!("connection error: {:?} - {}", kind, e,)
52        } else {
53            println!("gdbstub encountered a fatal error: {}", e)
54        }
55    }
56}
57```
58
59
60##### `{Single, Multi}ThreadBase::read_addrs` return value
61
62`read_addrs` now returns a `usize` instead of a `()`, allowing implementations to report cases where only a subset of memory could be read.
63
64In the past, the only way to handle these cases was by returning a `TargetError`. This provides an alternative mechanism, which may or may not be more appropriate for your particular use-case.
65
66When upgrading, the Rust compiler will emit a clear error message pointing out the updated function signature. The fix should be trivial.
67
68##### Removal of `Arch::single_step_behavior`
69
70See [\#132](https://github.com/daniel5151/gdbstub/pull/132) for more discussion on why this API was removed.
71
72This change only affects you if you're maintaining a custom `Arch` implementation (vs. using a community-maintained one via `gdbstub_arch`).
73
74The fix here is to simply remove the `Arch::single_step_behavior` impl.
75
76That's it! It's that easy.
77
78
79## `0.5` -> `0.6`
80
81`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.
82
83The following guide is a **best-effort** attempt to document all the changes, but there are some parts that may be missing / incomplete.
84
85##### General API change - _lots_ of renaming + exported type reorganization
86
87Many types have been renamed, and many import paths have changed in `0.6`.
88
89Exhaustively listing them would be nearly impossible, but suffice it to say, you will need to tweak your imports.
90
91##### `Connection` API changes
92
93> _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.
94
95The 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.
96
97For more context around this change, please refer to [Moving from `GdbStub::run` to `GdbStub::run_blocking`](#moving-from-gdbstubrun-to-gdbstubrun_blocking).
98
99Porting a `0.5` `Connection` to `0.6` is incredibly straightforward - you simply split your existing implementation in two:
100
101```rust
102// ==== 0.5.x ==== //
103
104impl Connection for MyConnection {
105    type Error = MyError;
106
107    fn write(&mut self, byte: u8) -> Result<(), Self::Error> { .. }
108    fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { .. }
109    fn read(&mut self) -> Result<u8, Self::Error> { .. }
110    fn peek(&mut self) -> Result<Option<u8>, Self::Error> { .. }
111    fn flush(&mut self) -> Result<(), Self::Error> { .. }
112    fn on_session_start(&mut self) -> Result<(), Self::Error> { .. }
113}
114
115// ==== 0.6.0 ==== //
116
117impl Connection for MyConnection {
118    type Error = MyError;
119
120    fn write(&mut self, byte: u8) -> Result<(), Self::Error> { .. }
121    fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { .. }
122    fn flush(&mut self) -> Result<(), Self::Error> { .. }
123    fn on_session_start(&mut self) -> Result<(), Self::Error> { .. }
124}
125
126impl ConnectionExt for MyConnection {
127    type Error = MyError;
128
129    fn read(&mut self) -> Result<u8, Self::Error> { .. }
130    fn peek(&mut self) -> Result<Option<u8>, Self::Error> { .. }
131}
132
133```
134
135##### `Arch` API - `RegId::from_raw_id`
136
137> _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.
138
139The `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>`.
140
141If 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.
142
143If the register size is `None`, `gdbstub` will _omit_ this runtime check, and trust that the target's implementation of `read_register` is correct.
144
145_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.
146
147##### `Target` API - IDET methods are now prefixed with `supports_`
148
149All 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.
150
151As 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.
152
153##### `Target` API - Introducing `enum Signal`
154
155In 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.
156
157`0.6` introduces a new `enum Signal` which encodes this information within `gdbstub` itself.
158
159This 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.
160
161_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.
162
163##### `HwWatchpoint` API - Plumb watchpoint `length` parameter to public API
164
165The watchpoint API has been updated to include a new `length` parameter, specifying what range of memory addresses the watchpoint should encompass.
166
167##### `TargetXmlOverride` API - Return data via `&mut [u8]` buffer
168
169In 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.
170
171Porting a `0.5` `TargetDescriptionXmlOverride` to `0.6` is straightforward, though a bit boilerplate-y.
172
173```rust
174// ==== 0.5.x ==== //
175
176impl target::ext::target_description_xml_override::TargetDescriptionXmlOverride for Emu {
177    fn target_description_xml(&self) -> &str {
178        r#"<target version="1.0"><!-- custom override string --><architecture>armv4t</architecture></target>"#
179    }
180}
181
182// ==== 0.6.0 ==== //
183
184pub fn copy_to_buf(data: &[u8], buf: &mut [u8]) -> usize {
185    let len = data.len();
186    let buf = &mut buf[..len];
187    buf.copy_from_slice(data);
188    len
189}
190
191pub fn copy_range_to_buf(data: &[u8], offset: u64, length: usize, buf: &mut [u8]) -> usize {
192    let offset = match usize::try_from(offset) {
193        Ok(v) => v,
194        Err(_) => return 0,
195    };
196    let len = data.len();
197    let data = &data[len.min(offset)..len.min(offset + length)];
198    copy_to_buf(data, buf)
199}
200
201impl target::ext::target_description_xml_override::TargetDescriptionXmlOverride for Emu {
202    fn target_description_xml(
203        &self,
204        offset: u64,
205        length: usize,
206        buf: &mut [u8],
207    ) -> TargetResult<usize, Self> {
208        let xml = r#"<target version="1.0"><!-- custom override string --><architecture>armv4t</architecture></target>"#
209            .trim()
210            .as_bytes();
211        Ok(copy_range_to_buf(xml, offset, length, buf))
212    }
213}
214```
215
216##### Updates to `{Single,Multi}ThreadOps::resume` API
217
218`0.6` includes three fairly major behavioral changes to the `resume` method:
219
220###### Support for `resume` is now entirely optional
221
222There 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.)
223
224In `0.6`, all resume-related functionality has been extracted out of `{Single,Multi}ThreadBase`, and split into new `{Singe,Multi}ThreadResume` IDETs.
225
226###### Removing `ResumeAction`, and making single-step support optional
227
228The GDB protocol only requires that targets implement support for _continuing_ execution - support for instruction-level single-step execution is totally optional.
229
230> 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...
231
232To 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`.
233
234In doing so, the `enum ResumeAction` type could be removed entirely, as single-step resume was to be handled in its own method.
235
236###### Removing `gdb_interrupt: GdbInterrupt`, and making `resume` non-blocking
237
238In 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.
239
240`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".
241
242In 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.
243
244For more context around this change, please refer to [Moving from `GdbStub::run` to `GdbStub::run_blocking`](#moving-from-gdbstubrun-to-gdbstubrun_blocking).
245
246###### Example: migrating `resume` from `0.5` to `0.6`
247
248Much 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.
249
250```rust
251// ==== 0.5.x ==== //
252
253impl SingleThreadOps for Emu {
254    fn resume(
255        &mut self,
256        action: ResumeAction,
257        gdb_interrupt: GdbInterrupt<'_>,
258    ) -> Result<StopReason<u32>, Self::Error> {
259        match action {
260            ResumeAction::Step => self.do_single_step(),
261            ResumeAction::Continue => self.block_until_stop_reason_or_interrupt(action, || gdb_interrupt.pending()),
262            _ => self.handle_resume_with_signal(action),
263        }
264    }
265}
266
267// ==== 0.6.0 ==== //
268
269impl SingleThreadBase for Emu {
270    // resume has been split into a separate IDET
271    #[inline(always)]
272    fn support_resume(
273        &mut self
274    ) -> Option<SingleThreadResumeOps<Self>> {
275        Some(self)
276    }
277}
278
279
280impl SingleThreadResume for Emu {
281    fn resume(
282        &mut self,
283        signal: Option<Signal>,
284    ) -> Result<(), Self::Error> { // <-- no longer returns a stop reason!
285        if let Some(signal) = signal {
286            self.handle_signal(signal)?;
287        }
288
289        // upper layers of the `gdbstub` API will be responsible for "driving"
290        // target execution - `resume` simply performs book keeping on _how_ the
291        // target should be resumed.
292        self.set_execution_mode(ExecMode::Continue)?;
293
294        Ok(())
295    }
296
297    // single-step support has been split into a separate IDET
298    #[inline(always)]
299    fn support_single_step(
300        &mut self
301    ) -> Option<SingleThreadSingleStepOps<'_, Self>> {
302        Some(self)
303    }
304}
305
306impl SingleThreadSingleStep for Emu {
307    fn step(&mut self, signal: Option<Signal>) -> Result<(), Self::Error> {
308        if let Some(signal) = signal {
309            self.handle_signal(signal)?;
310        }
311
312        self.set_execution_mode(ExecMode::Step)?;
313        Ok(())
314    }
315}
316```
317
318##### Moving from `GdbStub::run` to `GdbStub::run_blocking`
319
320With 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...
321
322> TODO. In the meantime, I would suggest looking at rustdoc for details on how to use `GdbStub::run_blocking`...
323
324## `0.4` -> `0.5`
325
326While 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.
327
328##### Consolidating the `{Hw,Sw}Breakpoint/Watchpoint` IDETs under the newly added `Breakpoints` IDETs.
329
330The 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.
331
332Porting from `0.4` to `0.5` should be as simple as:
333
334```rust
335// ==== 0.4.x ==== //
336
337impl Target for Emu {
338    fn sw_breakpoint(&mut self) -> Option<target::ext::breakpoints::SwBreakpointOps<Self>> {
339        Some(self)
340    }
341
342    fn hw_watchpoint(&mut self) -> Option<target::ext::breakpoints::HwWatchpointOps<Self>> {
343        Some(self)
344    }
345}
346
347impl target::ext::breakpoints::SwBreakpoint for Emu {
348    fn add_sw_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> { ... }
349    fn remove_sw_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> { ... }
350}
351
352impl target::ext::breakpoints::HwWatchpoint for Emu {
353    fn add_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
354    fn remove_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
355}
356
357// ==== 0.5.0 ==== //
358
359impl Target for Emu {
360    // (New Method) //
361    fn breakpoints(&mut self) -> Option<target::ext::breakpoints::BreakpointsOps<Self>> {
362        Some(self)
363    }
364}
365
366impl target::ext::breakpoints::Breakpoints for Emu {
367    fn sw_breakpoint(&mut self) -> Option<target::ext::breakpoints::SwBreakpointOps<Self>> {
368        Some(self)
369    }
370
371    fn hw_watchpoint(&mut self) -> Option<target::ext::breakpoints::HwWatchpointOps<Self>> {
372        Some(self)
373    }
374}
375
376// (Almost Unchanged) //
377impl target::ext::breakpoints::SwBreakpoint for Emu {
378    //                                            /-- New `kind` parameter
379    //                                           \/
380    fn add_sw_breakpoint(&mut self, addr: u32, _kind: arch::arm::ArmBreakpointKind) -> TargetResult<bool, Self> { ... }
381    fn remove_sw_breakpoint(&mut self, addr: u32, _kind: arch::arm::ArmBreakpointKind) -> TargetResult<bool, Self> { ... }
382}
383
384// (Unchanged) //
385impl target::ext::breakpoints::HwWatchpoint for Emu {
386    fn add_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
387    fn remove_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
388}
389
390```
391
392##### Single-register access methods (`{read,write}_register`) are now a separate `SingleRegisterAccess` trait
393
394Single 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.
395
396Porting from `0.4` to `0.5` should be as simple as:
397
398```rust
399// ==== 0.4.x ==== //
400
401impl SingleThreadOps for Emu {
402    fn read_register(&mut self, reg_id: arch::arm::reg::id::ArmCoreRegId, dst: &mut [u8]) -> TargetResult<(), Self> { ... }
403    fn write_register(&mut self, reg_id: arch::arm::reg::id::ArmCoreRegId, val: &[u8]) -> TargetResult<(), Self> { ... }
404}
405
406// ==== 0.5.0 ==== //
407
408impl SingleThreadOps for Emu {
409    // (New Method) //
410    fn single_register_access(&mut self) -> Option<target::ext::base::SingleRegisterAccessOps<(), Self>> {
411        Some(self)
412    }
413}
414
415impl target::ext::base::SingleRegisterAccess<()> for Emu {
416    //                           /-- New `tid` parameter (ignored on single-threaded systems)
417    //                          \/
418    fn read_register(&mut self, _tid: (), reg_id: arch::arm::reg::id::ArmCoreRegId, dst: &mut [u8]) -> TargetResult<(), Self> { ... }
419    fn write_register(&mut self, _tid: (), reg_id: arch::arm::reg::id::ArmCoreRegId, val: &[u8]) -> TargetResult<(), Self> { ... }
420}
421```
422
423##### New `MultiThreadOps::resume` API
424
425In `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:
426
427- 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`
428- The iterator machinery was quite heavy, and did not optimize very effectively
429- Handling malformed packets encountered during iteration was tricky, as the user-facing API exposed an infallible iterator, thereby complicating the internal error handling
430- 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.
431
432In `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:
433
4341. `resume`
435    - As before, when `resume` is called the target should resume execution.
436    - But how does the target know how each thread should be resumed? That's where the next method comes in...
4371. `set_resume_action`
438    - This method is called prior to `resume`, and notifies the target how a particular `Tid` should be resumed.
4391. (optionally) `set_resume_action_range_step`
440    - If the target supports optimized range-stepping, it can opt to implement the newly added `MultiThreadRangeStepping` IDET which includes this method.
441    - Targets that aren't interested in optimized range-stepping can skip this method!
4421. `clear_resume_actions`
443    - After the target returns a `ThreadStopReason` from `resume`, this method will be called to reset the previously set per-`tid` resume actions.
444
445NOTE: 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>`.
446
447Please refer to the in-tree `armv4t_multicore` example for an example of how this new `resume` flow works.
448