aboutsummaryrefslogtreecommitdiff
path: root/src/target/ext/mod.rs
blob: b7e2d8fb9c883ba4eaa1ae7def58c00881d08d15 (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
//! Extensions to [`Target`](super::Target) which add support for various
//! subsets of the GDB Remote Serial Protocol.
//!
//! ### Note: Missing Protocol Extensions
//!
//! `gdbstub`'s development is guided by the needs of its contributors, with
//! new features being added on an "as-needed" basis.
//!
//! If there's a GDB protocol extensions you're interested in that hasn't been
//! implemented in `gdbstub` yet, (e.g: remote filesystem access, tracepoint
//! support, etc...), consider opening an issue / filing a PR on the
//! [`gdbstub` GitHub repo](https://github.com/daniel5151/gdbstub/).
//!
//! Check out the [GDB Remote Configuration Docs](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Configuration.html)
//! for a table of GDB commands + their corresponding Remote Serial Protocol
//! packets.
//!
//! ## How Protocol Extensions Work - Inlineable Dyn Extension Traits (IDETs)
//!
//! The GDB protocol is massive, and contains all sorts of optional
//! functionality. In the early versions of `gdbstub`, the `Target` trait
//! directly implemented a method for _every single protocol extension_. If this
//! trend continued, there would've been literally _hundreds_ of associated
//! methods - of which only a small subset were ever used at once!
//!
//! Aside from the cognitive complexity of having so many methods on a single
//! trait, this approach had numerous other drawbacks as well:
//!
//!  - Implementations that did not implement all available protocol extensions
//!    still had to "pay" for the unused packet parsing/handler code, resulting
//!    in substantial code bloat, even on `no_std` platforms.
//!  - `GdbStub`'s internal implementation needed to include a large number of
//!    _runtime_ checks to deal with incorrectly implemented `Target`s.
//!      - No way to enforce "mutually-dependent" trait methods at compile-time.
//!          - e.g: When implementing hardware breakpoint extensions, targets
//!            _must_ implement both the `add_breakpoint` and
//!            `remove_breakpoints` methods.
//!      - No way to enforce "mutually-exclusive" trait methods at compile-time.
//!          - e.g: The `resume` method for single-threaded targets has a much
//!            simpler API than for multi-threaded targets, but it would be
//!            incorrect for a target to implement both.
//!
//! At first blush, it seems the the solution to all these issues is obvious:
//! simply tie each protocol extension to a `cargo` feature! And yes, while
//! this would indeed work, there would be several serious ergonomic drawbacks:
//!
//! - There would be _hundreds_ of individual feature flags that would need to
//!   be toggled by end users.
//! - It would be functionally impossible to _test_ all permutations of
//!   enabled/disabled cargo features.
//! - A single binary would need to rely on some [non-trivial `cargo`-fu](https://github.com/rust-lang/cargo/issues/674)
//!   in order to have multiple `Target` implementations in a single binary.
//!
//! After much experimentation and iteration, `gdbstub` ended up taking a
//! radically different approach to implementing and enumerating available
//! features, using a technique called **Inlineable Dyn Extension Traits**.
//!
//! > _Author's note:_ As far as I can tell, this isn't a very well-known trick,
//! or at the very least, I've personally never encountered any library that
//! uses this sort of API. As such, I've decided to be a bit cheeky and give it
//! a name! At some point, I'm hoping to write a standalone blog post which
//! further explores this technique, comparing it to other/existing approaches,
//! and diving into details of the how the compiler optimizes this sort of code.
//! In fact, I've already got a [very rough github repo](https://github.com/daniel5151/optional-trait-methods) with some of my
//! findings.
//!
//! So, what are "Inlineable Dyn Extension Traits"? Well, let's break it down:
//!
//! - **Extension Traits** - A common [Rust convention](https://rust-lang.github.io/rfcs/0445-extension-trait-conventions.html#what-is-an-extension-trait)
//!   to extend the functionality of a Trait, _without_ modifying the original
//!   trait.
//! - **Dyn** - Alludes to the use of Dynamic Dispatch via [Trait Objects](https://doc.rust-lang.org/book/ch17-02-trait-objects.html).
//! - **Inlineable** - Alludes to the fact that this approach can be easily
//!   inlined, making it a truly zero-cost abstraction.
//!
//! In a nutshell, Inlineable Dyn Extension Traits (or IDETs) are an abuse of
//! the Rust trait system + modern compiler optimizations to emulate zero-cost,
//! runtime-enumerable optional trait methods!
//!
//! #### Technical overview
//!
//! The basic principles behind Inlineable Dyn Extension Traits are best
//! explained though example:
//!
//! Lets say we want to add an optional protocol extension described by an
//! `ProtocolExt` trait to a base `Protocol` trait. How would we do that using
//! IDETs?
//!
//! - (library) Define a `trait ProtocolExt: Protocol { ... }` which includes
//!   all the methods required by the protocol extension:
//!    - _Note:_ Making `ProtocolExt` a subtrait of `Protocol` is not strictly
//!      required, but it does enable transparently using `Protocol`'s
//!      associated types as part of `ProtocolExt`'s method definitions.
//!
//! ```rust,ignore
//! /// `foo` and `bar` are mutually-dependent methods.
//! trait ProtocolExt: Protocol {
//!     fn foo(&self);
//!     // can use associated types in method signature!
//!     fn bar(&mut self) -> Result<(), Self::Error>;
//! }
//! ```
//!
//! - (library) "Associate" the `ProtocolExt` extension trait to the original
//!   `Protocol` trait by adding a new `Protocol` method that "downcasts" `self`
//!   into a `&mut dyn ProtocolExt`.
//!
//! ```rust,ignore
//! trait Protocol {
//!     // ... other methods ...
//!
//!     // Optional extension
//!     #[inline(always)]
//!     fn support_protocol_ext(&mut self) -> Option<ProtocolExtOps<Self>> {
//!         // disabled by default
//!         None
//!     }
//!
//!     // Mutually-exclusive extensions
//!     fn get_ext_a_or_b(&mut self) -> EitherOrExt<Self::Arch, Self::Error>;
//! }
//!
//! // Using a typedef for readability
//! type ProtocolExtOps<T> =
//!     &'a mut dyn ProtocolExt<Arch = <T as Protocol>::Arch, Error = <T as Protocol>::Error>;
//!
//! enum EitherOrExt<A, E> {
//!     ProtocolExtA(&'a mut dyn ProtocolExtA<Arch = A, Error = E>),
//!     ProtocolExtB(&'a mut dyn ProtocolExtB<Arch = A, Error = E>),
//! }
//! ```
//!
//! - (user) Implements the `ProtocolExt` extension for their target (just like
//!   a normal trait).
//!
//! ```rust,ignore
//! impl ProtocolExt for MyTarget {
//!     fn foo(&self) { ... }
//!     fn bar(&mut self) -> Result<(), Self::Error> { ... }
//! }
//! ```
//!
//! - (user) Implements the base `Protocol` trait, overriding the
//!   `support_protocol_ext` method to return `Some(self)`, which will
//!   effectively "enable" the extension.
//!
//! ```rust,ignore
//! impl Protocol for MyTarget {
//!     // Optional extension
//!     #[inline(always)]
//!     fn support_protocol_ext(&mut self) -> Option<ProtocolExtOps<Self>> {
//!         Some(self) // will not compile unless `MyTarget` also implements `ProtocolExt`
//!     }
//!
//!     // Mutually-exclusive extensions
//!     #[inline(always)]
//!     fn get_ext_a_or_b(&mut self) -> EitherOrExt<Self::Arch, Self::Error> {
//!         EitherOrExt::ProtocolExtA(self)
//!     }
//! }
//! ```
//!
//! > Please note the use of `#[inline(always)]` when enabling IDET methods.
//! While LLVM is usually smart enough to inline single-level IDETs (such as in
//! the example above), nested IDETs will often require a bit of "help" from the
//! `inline` directive to be correctly optimized.
//!
//! Now, here's where IDETs really shine: If the user didn't implement
//! `ProtocolExt`, but _did_ try to enable the feature by overriding
//! `support_protocol_ext` to return `Some(self)`, they'll get a compile-time
//! error that looks something like this:
//!
//! ```text
//! error[E0277]: the trait bound `MyTarget: ProtocolExt` is not satisfied
//!   --> path/to/implementation.rs:44:14
//!    |
//! 44 |         Some(self)
//!    |              ^^^^ the trait `ProtocolExt` is not implemented for `MyTarget`
//!    |
//!    = note: required for the cast to the object type `dyn ProtocolExt<Arch = ..., Error = ...>`
//! ```
//!
//! The Rust compiler is preventing you from enabling a feature you haven't
//! implemented _at compile time!_
//!
//! - (library) Is able to _query_ whether or not an extension is available,
//!   _without_ having to actually invoke any method on the target!
//!
//! ```rust,ignore
//! fn execute_protocol(mut target: impl Target) {
//!     match target.support_protocol_ext() {
//!         Some(ops) => ops.foo(),
//!         None => { /* fallback when not enabled */ }
//!     }
//! }
//! ```
//!
//! This is already pretty cool, but what's _even cooler_ is that if you take a
//! look at the generated assembly of a monomorphized `execute_protocol` method
//! (e.g: using godbolt.org), you'll find that the compiler is able to
//! efficiently inline and devirtualize _all_ the calls to
//! `support_protocol_ext` method, which in-turn allows the dead-code-eliminator
//! to work its magic, and remove the unused branches from the generated code!
//! i.e: If a target implemention didn't implement the `ProtocolExt` extension,
//! then that `match` statement in `execute_protocol` would simply turn into a
//! noop!
//!
//! If IDETs are something you're interested in, consider checking out
//! [daniel5151/optional-trait-methods](https://github.com/daniel5151/optional-trait-methods)
//! for some sample code that shows off the power of IDETs. It's not
//! particularly polished, but it does includes code snippets which can be
//! pasted into godbolt.org directly to confirm the optimizations described
//! above, and a brief writeup which compares / contrasts alternatives to IDETs.
//!
//! Long story short: Optimizing compilers really are magic!
//!
//! #### Summary: The Benefits of IDETs
//!
//! IDETs solve the numerous issues and shortcomings that arise from the
//! traditional single trait + "optional" methods approach:
//!
//! - **Compile-time enforcement of mutually-dependent methods**
//!    - By grouping mutually-dependent methods behind a single extension trait
//!      and marking them all as required methods, the Rust compiler is able to
//!      catch missing mutually-dependent methods at compile time, with no need
//!      for any runtime checks!
//! - **Compile-time enforcement of mutually-exclusive methods**
//!    - By grouping mutually-exclusive methods behind two extension traits, and
//!      wrapping those in an `enum`, the API is able to document
//!      mutually-exclusive functions _at the type-level_, in-turn enabling the
//!      library to omit any runtime checks!
//!    - _Note:_ Strictly speaking, this isn't really compile time
//!      "enforcement", as there's nothing stopping an "adversarial"
//!      implementation from implementing both sets of methods, and then
//!      "flipping" between the two at runtime. Nonetheless, it serves as a good
//!      guardrail.
//! - **Enforce dead-code-elimination _without_ `cargo` feature flags**
//!     - This is a really awesome trick: by wrapping code in an `if
//!       target.support_protocol_ext().is_some()` block, it's possible to
//!       specify _arbitrary_ blocks of code to be feature-dependent!
//!     - This is used to great effect in `gdbstub` to optimize-out any packet
//!       parsing / handler code for unimplemented protocol extensions.

macro_rules! doc_comment {
    ($x:expr, $($tt:tt)*) => {
        #[doc = $x]
        $($tt)*
    };
}

macro_rules! define_ext {
    ($extname:ident, $exttrait:ident) => {
        doc_comment! {
            concat!("See [`", stringify!($exttrait), "`](trait.", stringify!($exttrait), ".html)."),
            pub type $extname<'a, T> =
                &'a mut dyn $exttrait<Arch = <T as Target>::Arch, Error = <T as Target>::Error>;
        }
    };
}

pub mod auxv;
pub mod base;
pub mod breakpoints;
pub mod catch_syscalls;
pub mod exec_file;
pub mod extended_mode;
pub mod host_io;
pub mod lldb_register_info_override;
pub mod memory_map;
pub mod monitor_cmd;
pub mod section_offsets;
pub mod target_description_xml_override;
pub mod thread_extra_info;