aboutsummaryrefslogtreecommitdiff
path: root/docs/transition_guide.md
blob: 3ae106cb6d8f3f473f5db798c4b7c6714477e379 (plain)
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
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# Transition Guide

This document provides a brief overview of breaking changes between major `gdbstub` releases, along with tips/tricks/suggestions on how to migrate between `gdbstub` releases.

This 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).

> _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.

## `0.6` -> `0.7`

`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.

The 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.

##### `stub::GdbStubError` Changes

`stub::GdbStubError` is now an opaque `struct` with a handful of methods to extract user-defined context.

**Please file an issue if your code required matching on concrete error variants aside from `TargetError` and `ConnectionError`!**.

In 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.

Assuming you stuck to the example error handling described in the `gdbstub` getting started guide, adapting to the new type should be quite straightforward.


```rust
// ==== 0.6.x ==== //

match gdb.run_blocking::<EmuGdbEventLoop>(&mut emu) {
    Ok(disconnect_reason) => { ... },
    Err(GdbStubError::TargetError(e)) => {
        println!("target encountered a fatal error: {}", e)
    }
    Err(e) => {
        println!("gdbstub encountered a fatal error: {}", e)
    }
}

// ==== 0.7.0 ==== //

match gdb.run_blocking::<EmuGdbEventLoop>(&mut emu) {
    Ok(disconnect_reason) => { ... },
    Err(e) => {
        if e.is_target_error() {
            println!(
                "target encountered a fatal error: {}",
                e.into_target_error().unwrap()
            )
        } else if e.is_connection_error() {
            let (e, kind) = e.into_connection_error().unwrap();
            println!("connection error: {:?} - {}", kind, e,)
        } else {
            println!("gdbstub encountered a fatal error: {}", e)
        }
    }
}
```


##### `{Single, Multi}ThreadBase::read_addrs` return value

`read_addrs` now returns a `usize` instead of a `()`, allowing implementations to report cases where only a subset of memory could be read.

In 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.

When upgrading, the Rust compiler will emit a clear error message pointing out the updated function signature. The fix should be trivial.

##### Removal of `Arch::single_step_behavior`

See [\#132](https://github.com/daniel5151/gdbstub/pull/132) for more discussion on why this API was removed.

This change only affects you if you're maintaining a custom `Arch` implementation (vs. using a community-maintained one via `gdbstub_arch`).

The fix here is to simply remove the `Arch::single_step_behavior` impl.

That's it! It's that easy.


## `0.5` -> `0.6`

`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.

The following guide is a **best-effort** attempt to document all the changes, but there are some parts that may be missing / incomplete.

##### General API change - _lots_ of renaming + exported type reorganization

Many types have been renamed, and many import paths have changed in `0.6`.

Exhaustively listing them would be nearly impossible, but suffice it to say, you will need to tweak your imports.

##### `Connection` API changes

> _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.

The 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.

For more context around this change, please refer to [Moving from `GdbStub::run` to `GdbStub::run_blocking`](#moving-from-gdbstubrun-to-gdbstubrun_blocking).

Porting a `0.5` `Connection` to `0.6` is incredibly straightforward - you simply split your existing implementation in two:

```rust
// ==== 0.5.x ==== //

impl Connection for MyConnection {
    type Error = MyError;

    fn write(&mut self, byte: u8) -> Result<(), Self::Error> { .. }
    fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { .. }
    fn read(&mut self) -> Result<u8, Self::Error> { .. }
    fn peek(&mut self) -> Result<Option<u8>, Self::Error> { .. }
    fn flush(&mut self) -> Result<(), Self::Error> { .. }
    fn on_session_start(&mut self) -> Result<(), Self::Error> { .. }
}

// ==== 0.6.0 ==== //

impl Connection for MyConnection {
    type Error = MyError;

    fn write(&mut self, byte: u8) -> Result<(), Self::Error> { .. }
    fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { .. }
    fn flush(&mut self) -> Result<(), Self::Error> { .. }
    fn on_session_start(&mut self) -> Result<(), Self::Error> { .. }
}

impl ConnectionExt for MyConnection {
    type Error = MyError;

    fn read(&mut self) -> Result<u8, Self::Error> { .. }
    fn peek(&mut self) -> Result<Option<u8>, Self::Error> { .. }
}

```

##### `Arch` API - `RegId::from_raw_id`

> _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.

The `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>`.

If 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.

If the register size is `None`, `gdbstub` will _omit_ this runtime check, and trust that the target's implementation of `read_register` is correct.

_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.

##### `Target` API - IDET methods are now prefixed with `supports_`

All 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.

As 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.

##### `Target` API - Introducing `enum Signal`

In 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.

`0.6` introduces a new `enum Signal` which encodes this information within `gdbstub` itself.

This 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.

_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.

##### `HwWatchpoint` API - Plumb watchpoint `length` parameter to public API

The watchpoint API has been updated to include a new `length` parameter, specifying what range of memory addresses the watchpoint should encompass.

##### `TargetXmlOverride` API - Return data via `&mut [u8]` buffer

In 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.

Porting a `0.5` `TargetDescriptionXmlOverride` to `0.6` is straightforward, though a bit boilerplate-y.

```rust
// ==== 0.5.x ==== //

impl target::ext::target_description_xml_override::TargetDescriptionXmlOverride for Emu {
    fn target_description_xml(&self) -> &str {
        r#"<target version="1.0"><!-- custom override string --><architecture>armv4t</architecture></target>"#
    }
}

// ==== 0.6.0 ==== //

pub fn copy_to_buf(data: &[u8], buf: &mut [u8]) -> usize {
    let len = data.len();
    let buf = &mut buf[..len];
    buf.copy_from_slice(data);
    len
}

pub fn copy_range_to_buf(data: &[u8], offset: u64, length: usize, buf: &mut [u8]) -> usize {
    let offset = match usize::try_from(offset) {
        Ok(v) => v,
        Err(_) => return 0,
    };
    let len = data.len();
    let data = &data[len.min(offset)..len.min(offset + length)];
    copy_to_buf(data, buf)
}

impl target::ext::target_description_xml_override::TargetDescriptionXmlOverride for Emu {
    fn target_description_xml(
        &self,
        offset: u64,
        length: usize,
        buf: &mut [u8],
    ) -> TargetResult<usize, Self> {
        let xml = r#"<target version="1.0"><!-- custom override string --><architecture>armv4t</architecture></target>"#
            .trim()
            .as_bytes();
        Ok(copy_range_to_buf(xml, offset, length, buf))
    }
}
```

##### Updates to `{Single,Multi}ThreadOps::resume` API

`0.6` includes three fairly major behavioral changes to the `resume` method:

###### Support for `resume` is now entirely optional

There 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.)

In `0.6`, all resume-related functionality has been extracted out of `{Single,Multi}ThreadBase`, and split into new `{Singe,Multi}ThreadResume` IDETs.

###### Removing `ResumeAction`, and making single-step support optional

The GDB protocol only requires that targets implement support for _continuing_ execution - support for instruction-level single-step execution is totally optional.

> 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...

To 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`.

In doing so, the `enum ResumeAction` type could be removed entirely, as single-step resume was to be handled in its own method.

###### Removing `gdb_interrupt: GdbInterrupt`, and making `resume` non-blocking

In 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.

`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".

In 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.

For more context around this change, please refer to [Moving from `GdbStub::run` to `GdbStub::run_blocking`](#moving-from-gdbstubrun-to-gdbstubrun_blocking).

###### Example: migrating `resume` from `0.5` to `0.6`

Much 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.

```rust
// ==== 0.5.x ==== //

impl SingleThreadOps for Emu {
    fn resume(
        &mut self,
        action: ResumeAction,
        gdb_interrupt: GdbInterrupt<'_>,
    ) -> Result<StopReason<u32>, Self::Error> {
        match action {
            ResumeAction::Step => self.do_single_step(),
            ResumeAction::Continue => self.block_until_stop_reason_or_interrupt(action, || gdb_interrupt.pending()),
            _ => self.handle_resume_with_signal(action),
        }
    }
}

// ==== 0.6.0 ==== //

impl SingleThreadBase for Emu {
    // resume has been split into a separate IDET
    #[inline(always)]
    fn support_resume(
        &mut self
    ) -> Option<SingleThreadResumeOps<Self>> {
        Some(self)
    }
}


impl SingleThreadResume for Emu {
    fn resume(
        &mut self,
        signal: Option<Signal>,
    ) -> Result<(), Self::Error> { // <-- no longer returns a stop reason!
        if let Some(signal) = signal {
            self.handle_signal(signal)?;
        }

        // upper layers of the `gdbstub` API will be responsible for "driving"
        // target execution - `resume` simply performs book keeping on _how_ the
        // target should be resumed.
        self.set_execution_mode(ExecMode::Continue)?;

        Ok(())
    }

    // single-step support has been split into a separate IDET
    #[inline(always)]
    fn support_single_step(
        &mut self
    ) -> Option<SingleThreadSingleStepOps<'_, Self>> {
        Some(self)
    }
}

impl SingleThreadSingleStep for Emu {
    fn step(&mut self, signal: Option<Signal>) -> Result<(), Self::Error> {
        if let Some(signal) = signal {
            self.handle_signal(signal)?;
        }

        self.set_execution_mode(ExecMode::Step)?;
        Ok(())
    }
}
```

##### Moving from `GdbStub::run` to `GdbStub::run_blocking`

With 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...

> TODO. In the meantime, I would suggest looking at rustdoc for details on how to use `GdbStub::run_blocking`...

## `0.4` -> `0.5`

While 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.

##### Consolidating the `{Hw,Sw}Breakpoint/Watchpoint` IDETs under the newly added `Breakpoints` IDETs.

The 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.

Porting from `0.4` to `0.5` should be as simple as:

```rust
// ==== 0.4.x ==== //

impl Target for Emu {
    fn sw_breakpoint(&mut self) -> Option<target::ext::breakpoints::SwBreakpointOps<Self>> {
        Some(self)
    }

    fn hw_watchpoint(&mut self) -> Option<target::ext::breakpoints::HwWatchpointOps<Self>> {
        Some(self)
    }
}

impl target::ext::breakpoints::SwBreakpoint for Emu {
    fn add_sw_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> { ... }
    fn remove_sw_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> { ... }
}

impl target::ext::breakpoints::HwWatchpoint for Emu {
    fn add_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
    fn remove_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
}

// ==== 0.5.0 ==== //

impl Target for Emu {
    // (New Method) //
    fn breakpoints(&mut self) -> Option<target::ext::breakpoints::BreakpointsOps<Self>> {
        Some(self)
    }
}

impl target::ext::breakpoints::Breakpoints for Emu {
    fn sw_breakpoint(&mut self) -> Option<target::ext::breakpoints::SwBreakpointOps<Self>> {
        Some(self)
    }

    fn hw_watchpoint(&mut self) -> Option<target::ext::breakpoints::HwWatchpointOps<Self>> {
        Some(self)
    }
}

// (Almost Unchanged) //
impl target::ext::breakpoints::SwBreakpoint for Emu {
    //                                            /-- New `kind` parameter
    //                                           \/
    fn add_sw_breakpoint(&mut self, addr: u32, _kind: arch::arm::ArmBreakpointKind) -> TargetResult<bool, Self> { ... }
    fn remove_sw_breakpoint(&mut self, addr: u32, _kind: arch::arm::ArmBreakpointKind) -> TargetResult<bool, Self> { ... }
}

// (Unchanged) //
impl target::ext::breakpoints::HwWatchpoint for Emu {
    fn add_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
    fn remove_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
}

```

##### Single-register access methods (`{read,write}_register`) are now a separate `SingleRegisterAccess` trait

Single 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.

Porting from `0.4` to `0.5` should be as simple as:

```rust
// ==== 0.4.x ==== //

impl SingleThreadOps for Emu {
    fn read_register(&mut self, reg_id: arch::arm::reg::id::ArmCoreRegId, dst: &mut [u8]) -> TargetResult<(), Self> { ... }
    fn write_register(&mut self, reg_id: arch::arm::reg::id::ArmCoreRegId, val: &[u8]) -> TargetResult<(), Self> { ... }
}

// ==== 0.5.0 ==== //

impl SingleThreadOps for Emu {
    // (New Method) //
    fn single_register_access(&mut self) -> Option<target::ext::base::SingleRegisterAccessOps<(), Self>> {
        Some(self)
    }
}

impl target::ext::base::SingleRegisterAccess<()> for Emu {
    //                           /-- New `tid` parameter (ignored on single-threaded systems)
    //                          \/
    fn read_register(&mut self, _tid: (), reg_id: arch::arm::reg::id::ArmCoreRegId, dst: &mut [u8]) -> TargetResult<(), Self> { ... }
    fn write_register(&mut self, _tid: (), reg_id: arch::arm::reg::id::ArmCoreRegId, val: &[u8]) -> TargetResult<(), Self> { ... }
}
```

##### New `MultiThreadOps::resume` API

In `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:

- 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`
- The iterator machinery was quite heavy, and did not optimize very effectively
- Handling malformed packets encountered during iteration was tricky, as the user-facing API exposed an infallible iterator, thereby complicating the internal error handling
- 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.

In `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:

1. `resume`
    - As before, when `resume` is called the target should resume execution.
    - But how does the target know how each thread should be resumed? That's where the next method comes in...
1. `set_resume_action`
    - This method is called prior to `resume`, and notifies the target how a particular `Tid` should be resumed.
1. (optionally) `set_resume_action_range_step`
    - If the target supports optimized range-stepping, it can opt to implement the newly added `MultiThreadRangeStepping` IDET which includes this method.
    - Targets that aren't interested in optimized range-stepping can skip this method!
1. `clear_resume_actions`
    - After the target returns a `ThreadStopReason` from `resume`, this method will be called to reset the previously set per-`tid` resume actions.

NOTE: 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>`.

Please refer to the in-tree `armv4t_multicore` example for an example of how this new `resume` flow works.