aboutsummaryrefslogtreecommitdiff
path: root/src/target/ext/extended_mode.rs
blob: 817bec0837a8f9e3f91dd2149c3c0fed00274cc3 (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
//! Enables [Extended Mode](https://sourceware.org/gdb/current/onlinedocs/gdb/Connecting.html)
//! functionality when connecting using `target extended-remote`, such as
//! spawning new processes and/or attaching to existing processes.
//!
//! # Disclaimer
//!
//! While this API has been end-to-end tested and confirmed working with a "toy"
//! target implementation (see the included `armv4t` example), it has _not_ been
//! "battle-tested" with a fully-featured extended-mode capable target.
//!
//! If you end up using this API to implement an extended-mode capable target,
//! _please_ file an issue on the repo detailing any bugs / usability issues you
//! may encountered while implementing this API! If everything happens to Just
//! Work as expected, nonetheless file an issue so that this disclaimer can be
//! removed in future releases!

use crate::common::*;
use crate::target::{Target, TargetResult};

/// Returned from `ExtendedMode::kill`
///
/// Retuning `ShouldTerminate::Yes` will cause the `GdbStub` to immediately
/// shut down and return a `DisconnectReason::Kill`. Returning
/// `ShouldTerminate::No` will keep the `GdbStub` running and listening for
/// further run/attach requests.
pub enum ShouldTerminate {
    /// Terminate GdbStub
    Yes,
    /// Don't Terminate GdbStub
    No,
}

impl ShouldTerminate {
    /// Convert `ShouldTerminate::Yes` into `true`, and `ShouldTerminate::No`
    /// into `false`
    pub fn into_bool(self) -> bool {
        match self {
            ShouldTerminate::Yes => true,
            ShouldTerminate::No => false,
        }
    }
}

/// Describes how the target attached to a process.
pub enum AttachKind {
    /// It attached to an existing process.
    Attach,
    /// It spawned a new process.
    Run,
}

impl AttachKind {
    pub(crate) fn was_attached(self) -> bool {
        match self {
            AttachKind::Attach => true,
            AttachKind::Run => false,
        }
    }
}

/// Target Extension - Support
/// [Extended Mode](https://sourceware.org/gdb/current/onlinedocs/gdb/Connecting.html) functionality.
///
/// # Extended Mode for Single/Multi Threaded Targets
///
/// While extended-mode is primarily intended to be implemented by targets which
/// support debugging multiple processes, there's no reason why a basic
/// single/multi-threaded target can't implement these extensions as well.
///
/// For example, instead of "spawning" a process, the `run` command could be
/// used to reset the execution state instead (e.g: resetting an emulator).
pub trait ExtendedMode: Target {
    /// Spawn and attach to the program `filename`, passing it the provided
    /// `args` on its command line.
    ///
    /// The program is created in the stopped state.
    ///
    /// If no filename is provided, the stub may use a default program (e.g. the
    /// last program run), or a non fatal error should be returned.
    ///
    /// `filename` and `args` are not guaranteed to be valid UTF-8, and are
    /// passed as raw byte arrays. If the filenames/arguments could not be
    /// converted into an appropriate representation, a non fatal error should
    /// be returned.
    ///
    /// _Note:_ This method's implementation should handle any additional
    /// configuration options set via the various `ConfigureXXX` extensions to
    /// `ExtendedMode`. e.g: if the [`ConfigureEnv`](trait.ConfigureEnv.html)
    /// extension is implemented and enabled, this method should set the spawned
    /// processes' environment variables accordingly.
    fn run(&mut self, filename: Option<&[u8]>, args: Args<'_, '_>) -> TargetResult<Pid, Self>;

    /// Attach to a new process with the specified PID.
    ///
    /// In all-stop mode, all threads in the attached process are stopped; in
    /// non-stop mode, it may be attached without being stopped (if that is
    /// supported by the target).
    fn attach(&mut self, pid: Pid) -> TargetResult<(), Self>;

    /// Query if specified PID was spawned by the target (via `run`), or if the
    /// target attached to an existing process (via `attach`).
    ///
    /// If the PID doesn't correspond to a process the target has run or
    /// attached to, a non fatal error should be returned.
    fn query_if_attached(&mut self, pid: Pid) -> TargetResult<AttachKind, Self>;

    /// Called when the GDB client sends a Kill request.
    ///
    /// If the PID doesn't correspond to a process the target has run or
    /// attached to, a non fatal error should be returned.
    ///
    /// GDB may or may not specify a specific PID to kill. When no PID is
    /// specified, the target is free to decide what to do (e.g: kill the
    /// last-used pid, terminate the connection, etc...).
    ///
    /// If `ShouldTerminate::Yes` is returned, `GdbStub` will immediately stop
    /// and return a `DisconnectReason::Kill`. Otherwise, the connection will
    /// remain open, and `GdbStub` will continue listening for run/attach
    /// requests.
    fn kill(&mut self, pid: Option<Pid>) -> TargetResult<ShouldTerminate, Self>;

    /// Restart the program being debugged.
    ///
    /// The GDB docs don't do a good job describing what a "restart" operation
    /// entails. For reference, the official `gdbserver` seems to kill all
    /// inferior processes, and then re-run whatever program was provided on the
    /// command line (if one was provided).
    ///
    /// _Author's Note:_ Based on my current (as of Sept 2020) understanding of
    /// the GDB client;s source code, it seems that the "R" packet is _never_
    /// sent so-long as the target implements the "vRun" packet (which
    /// corresponds to this trait's `run` method). As such, while `gdbstub`
    /// exposes this functionality, and "requires" an implementation, unless
    /// you're running a fairly old version of GDB, it should be fine to
    /// simply stub it out -- e.g: using the `unimplemented!()` macro /
    /// returning a fatal error.
    fn restart(&mut self) -> Result<(), Self::Error>;

    /// (optional) Invoked when GDB client switches to extended mode.
    ///
    /// The default implementation is a no-op.
    ///
    /// Target implementations can override this implementation if they need to
    /// perform any operations once extended mode is activated.
    fn on_start(&mut self) -> Result<(), Self::Error> {
        Ok(())
    }

    /// Support for enabling / disabling ASLR for spawned processes.
    #[inline(always)]
    fn support_configure_aslr(&mut self) -> Option<ConfigureAslrOps<'_, Self>> {
        None
    }

    /// Support for setting / removing / resetting environment variables for
    /// spawned processes.
    #[inline(always)]
    fn support_configure_env(&mut self) -> Option<ConfigureEnvOps<'_, Self>> {
        None
    }

    /// Support for configuring if spawned processes should be spawned using a
    /// shell.
    #[inline(always)]
    fn support_configure_startup_shell(&mut self) -> Option<ConfigureStartupShellOps<'_, Self>> {
        None
    }

    /// Support for configuring the working directory for spawned processes.
    #[inline(always)]
    fn support_configure_working_dir(&mut self) -> Option<ConfigureWorkingDirOps<'_, Self>> {
        None
    }
}

define_ext!(ExtendedModeOps, ExtendedMode);

/// Iterator of `args` passed to a spawned process (used in
/// `ExtendedMode::run`)
pub struct Args<'a, 'args> {
    inner: &'a mut dyn Iterator<Item = &'args [u8]>,
}

impl core::fmt::Debug for Args<'_, '_> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "Args {{ .. }}")
    }
}

impl<'a, 'b> Args<'a, 'b> {
    pub(crate) fn new(inner: &'a mut dyn Iterator<Item = &'b [u8]>) -> Args<'a, 'b> {
        Args { inner }
    }
}

impl<'args> Iterator for Args<'_, 'args> {
    type Item = &'args [u8];

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next()
    }
}

/// Nested Target Extension - Enable/Disable ASLR for spawned processes (for a
/// more consistent debugging experience).
///
/// Corresponds to GDB's [`set disable-randomization`](https://sourceware.org/gdb/onlinedocs/gdb/Starting.html) command.
pub trait ConfigureAslr: ExtendedMode {
    /// Enable/Disable ASLR for spawned processes.
    fn cfg_aslr(&mut self, enabled: bool) -> TargetResult<(), Self>;
}

define_ext!(ConfigureAslrOps, ConfigureAslr);

/// Nested Target Extension - Set/Remove/Reset the Environment variables for
/// spawned processes.
///
/// Corresponds to GDB's [`set environment`](https://sourceware.org/gdb/onlinedocs/gdb/Environment.html#set-environment) cmd.
///
/// _Note:_ Environment variables are not guaranteed to be UTF-8, and are passed
/// as raw byte arrays. If the provided keys/values could not be converted into
/// an appropriate representation, a non fatal error should be returned.
pub trait ConfigureEnv: ExtendedMode {
    /// Set an environment variable.
    fn set_env(&mut self, key: &[u8], val: Option<&[u8]>) -> TargetResult<(), Self>;

    /// Remove an environment variable.
    fn remove_env(&mut self, key: &[u8]) -> TargetResult<(), Self>;

    /// Reset all environment variables to their initial state (i.e: undo all
    /// previous `set/remove_env` calls).
    fn reset_env(&mut self) -> TargetResult<(), Self>;
}

define_ext!(ConfigureEnvOps, ConfigureEnv);

/// Nested Target Extension - Configure if spawned processes should be spawned
/// using a shell.
///
/// Corresponds to GDB's [`set startup-with-shell`](https://sourceware.org/gdb/onlinedocs/gdb/Starting.html) command.
pub trait ConfigureStartupShell: ExtendedMode {
    /// Configure if spawned processes should be spawned using a shell.
    ///
    /// On UNIX-like targets, it is possible to start the inferior using a shell
    /// program. This is the default behavior on both `GDB` and `gdbserver`.
    fn cfg_startup_with_shell(&mut self, enabled: bool) -> TargetResult<(), Self>;
}

define_ext!(ConfigureStartupShellOps, ConfigureStartupShell);

/// Nested Target Extension - Configure the working directory for spawned
/// processes.
///
/// Corresponds to GDB's [`set cwd` and `cd`](https://sourceware.org/gdb/onlinedocs/gdb/Working-Directory.html) commands.
pub trait ConfigureWorkingDir: ExtendedMode {
    /// Set the working directory for spawned processes.
    ///
    /// If no directory is provided, the stub should reset the value to it's
    /// original value.
    ///
    /// The path is not guaranteed to be valid UTF-8, and is passed as a raw
    /// byte array. If the path could not be converted into an appropriate
    /// representation, a non fatal error should be returned.
    fn cfg_working_dir(&mut self, dir: Option<&[u8]>) -> TargetResult<(), Self>;
}

define_ext!(ConfigureWorkingDirOps, ConfigureWorkingDir);