aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-10 07:02:59 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-10 07:02:59 +0000
commit2d46476ac0f76f028a156d2135f378ba0bd5c036 (patch)
tree42f3e36df36464bdd7d7fcba2e800e7997ef11e6
parentbfd03d4d5761e85233613ef9095ffdd827ec20fe (diff)
parenta4a1a2293e7a4aacf1d3157aeba89c703cb7da02 (diff)
downloadgdbstub-android13-mainline-wifi-release.tar.gz
Change-Id: Iddca7d7ffb6352ca0f2fda5f5df6d77577de37d9
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--.github/pull_request_template.md149
-rw-r--r--.gitignore4
-rw-r--r--Android.bp13
-rw-r--r--CHANGELOG.md161
-rw-r--r--Cargo.toml2
-rw-r--r--Cargo.toml.orig8
-rw-r--r--METADATA8
-rw-r--r--README.md108
-rw-r--r--TEST_MAPPING8
-rw-r--r--cargo2android.json5
-rw-r--r--docs/transition_guide.md128
-rw-r--r--examples/armv4t/emu.rs1
-rw-r--r--examples/armv4t/gdb/breakpoints.rs68
-rw-r--r--examples/armv4t/gdb/extended_mode.rs18
-rw-r--r--examples/armv4t/gdb/mod.rs194
-rw-r--r--examples/armv4t/main.rs5
-rw-r--r--examples/armv4t_multicore/emu.rs7
-rw-r--r--examples/armv4t_multicore/gdb.rs93
-rw-r--r--examples/armv4t_multicore/main.rs5
-rw-r--r--src/arch.rs153
-rw-r--r--src/arch/arm/mod.rs18
-rw-r--r--src/arch/arm/reg/arm_core.rs76
-rw-r--r--src/arch/arm/reg/id.rs36
-rw-r--r--src/arch/arm/reg/mod.rs8
-rw-r--r--src/arch/mips/mod.rs74
-rw-r--r--src/arch/mips/reg/id.rs129
-rw-r--r--src/arch/mips/reg/mips.rs259
-rw-r--r--src/arch/mips/reg/mod.rs11
-rw-r--r--src/arch/mod.rs60
-rw-r--r--src/arch/msp430/mod.rs25
-rw-r--r--src/arch/msp430/reg/id.rs71
-rw-r--r--src/arch/msp430/reg/mod.rs8
-rw-r--r--src/arch/msp430/reg/msp430.rs65
-rw-r--r--src/arch/ppc/mod.rs27
-rw-r--r--src/arch/ppc/reg/common.rs159
-rw-r--r--src/arch/ppc/reg/id.rs2
-rw-r--r--src/arch/ppc/reg/mod.rs9
-rw-r--r--src/arch/riscv/mod.rs33
-rw-r--r--src/arch/riscv/reg/id.rs31
-rw-r--r--src/arch/riscv/reg/mod.rs8
-rw-r--r--src/arch/riscv/reg/riscv.rs71
-rw-r--r--src/arch/traits.rs84
-rw-r--r--src/arch/x86/mod.rs50
-rw-r--r--src/arch/x86/reg/core32.rs134
-rw-r--r--src/arch/x86/reg/core64.rs121
-rw-r--r--src/arch/x86/reg/id.rs198
-rw-r--r--src/arch/x86/reg/mod.rs82
-rw-r--r--src/gdbstub_impl/builder.rs16
-rw-r--r--src/gdbstub_impl/error.rs41
-rw-r--r--src/gdbstub_impl/ext/base.rs743
-rw-r--r--src/gdbstub_impl/ext/breakpoints.rs97
-rw-r--r--src/gdbstub_impl/ext/extended_mode.rs83
-rw-r--r--src/gdbstub_impl/ext/mod.rs20
-rw-r--r--src/gdbstub_impl/ext/monitor_cmd.rs48
-rw-r--r--src/gdbstub_impl/ext/reverse_exec.rs139
-rw-r--r--src/gdbstub_impl/ext/section_offsets.rs57
-rw-r--r--src/gdbstub_impl/ext/single_register_access.rs60
-rw-r--r--src/gdbstub_impl/mod.rs895
-rw-r--r--src/lib.rs121
-rw-r--r--src/protocol/commands.rs151
-rw-r--r--src/protocol/commands/_QAgent.rs18
-rw-r--r--src/protocol/commands/_bc.rs13
-rw-r--r--src/protocol/commands/_bs.rs13
-rw-r--r--src/protocol/commands/_qSupported.rs4
-rw-r--r--src/protocol/commands/_vCont.rs91
-rw-r--r--src/protocol/commands/_vRun.rs7
-rw-r--r--src/protocol/commands/_z.rs20
-rw-r--r--src/protocol/commands/_z_upcase.rs24
-rw-r--r--src/protocol/commands/breakpoint.rs117
-rw-r--r--src/protocol/common/hex.rs14
-rw-r--r--src/protocol/common/mod.rs7
-rw-r--r--src/protocol/common/thread_id.rs52
-rw-r--r--src/protocol/console_output.rs6
-rw-r--r--src/protocol/mod.rs2
-rw-r--r--src/protocol/packet.rs71
-rw-r--r--src/protocol/response_writer.rs158
-rw-r--r--src/target/ext/base/mod.rs94
-rw-r--r--src/target/ext/base/multithread.rs282
-rw-r--r--src/target/ext/base/single_register_access.rs67
-rw-r--r--src/target/ext/base/singlethread.rs189
-rw-r--r--src/target/ext/breakpoints.rs77
-rw-r--r--src/target/ext/extended_mode.rs42
-rw-r--r--src/target/ext/mod.rs199
-rw-r--r--src/target/mod.rs207
-rw-r--r--src/util/managed_vec.rs37
86 files changed, 3709 insertions, 3562 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index 72ca90a..5653d83 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,5 @@
{
"git": {
- "sha1": "7574b03e021e79b7023c76b21a340da197fd16c2"
+ "sha1": "90aad2e136d15d486324f1985a398c02da982cdb"
}
}
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..876020e
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,149 @@
+### Description
+
+<!-- Please include a brief description of what is being added/changed -->
+
+e.g: This PR implements the `foobar` extension, based off the GDB documentation [here](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html).
+
+Closes #(issue number) <!-- if appropriate -->
+
+### API Stability
+
+- [ ] This PR does not require a breaking API change
+
+<!-- If it does require making a breaking API change, please elaborate why -->
+
+### Checklist
+
+- Implementation
+ - [ ] `cargo build` compiles without `errors` or `warnings`
+ - [ ] `cargo clippy` runs without `errors` or `warnings`
+ - [ ] `cargo fmt` was run
+ - [ ] All tests pass
+- Documentation
+ - [ ] rustdoc + approprate inline code comments
+ - [ ] Updated CHANGELOG.md
+ - [ ] (if appropriate) Added feature to "Debugging Features" in README.md
+- _If implementing a new protocol extension IDET_
+ - [ ] Included a basic sample implementation in `examples/armv4t`
+ - [ ] Included output of running `examples/armv4t` with `RUST_LOG=trace` + any relevant GDB output under the "Validation" section below
+ - [ ] Confirmed that IDET can be optimized away (using `./scripts/test_dead_code_elim.sh` and/or `./example_no_std/check_size.sh`)
+ - [ ] **OR** Implementation requires adding non-optional binary bloat (please elaborate under "Description")
+- _If upstreaming an `Arch` implementation_
+ - [ ] I have tested this code in my project, and to the best of my knowledge, it is working as intended.
+
+<!-- If you are implementing `gdbstub` in an open-source project, consider updating the README.md's "Real World Examples" section to link back to your project! -->
+
+### Validation
+
+<!-- example output, from https://github.com/daniel5151/gdbstub/pull/54 -->
+
+<details>
+<summary>GDB output</summary>
+
+```
+!!!!! EXAMPLE OUTPUT !!!!!
+
+(gdb) info mem
+Using memory regions provided by the target.
+Num Enb Low Addr High Addr Attrs
+0 y 0x00000000 0x100000000 rw nocache
+```
+
+</details>
+
+<details>
+<summary>armv4t output</summary>
+
+```
+!!!!! EXAMPLE OUTPUT !!!!!
+
+ Finished dev [unoptimized + debuginfo] target(s) in 0.01s
+ Running `target/debug/examples/armv4t`
+loading section ".text" into memory from [0x55550000..0x55550078]
+Setting PC to 0x55550000
+Waiting for a GDB connection on "127.0.0.1:9001"...
+Debugger connected from 127.0.0.1:37142
+ TRACE gdbstub::gdbstub_impl > <-- +
+ TRACE gdbstub::gdbstub_impl > <-- $qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a
+ TRACE gdbstub::protocol::response_writer > --> $PacketSize=1000;vContSupported+;multiprocess+;QStartNoAckMode+;ReverseContinue+;ReverseStep+;QDisableRandomization+;QEnvironmentHexEncoded+;QEnvironmentUnset+;QEnvironmentReset+;QStartupWithShell+;QSetWorkingDir+;swbreak+;hwbreak+;qXfer:features:read+;qXfer:memory-map:read+#e4
+ TRACE gdbstub::gdbstub_impl > <-- +
+ TRACE gdbstub::gdbstub_impl > <-- $vMustReplyEmpty#3a
+ INFO gdbstub::gdbstub_impl > Unknown command: vMustReplyEmpty
+ TRACE gdbstub::protocol::response_writer > --> $#00
+ TRACE gdbstub::gdbstub_impl > <-- +
+ TRACE gdbstub::gdbstub_impl > <-- $QStartNoAckMode#b0
+ TRACE gdbstub::protocol::response_writer > --> $OK#9a
+ TRACE gdbstub::gdbstub_impl > <-- +
+ TRACE gdbstub::gdbstub_impl > <-- $Hgp0.0#ad
+ TRACE gdbstub::protocol::response_writer > --> $OK#9a
+ TRACE gdbstub::gdbstub_impl > <-- $qXfer:features:read:target.xml:0,ffb#79
+ TRACE gdbstub::protocol::response_writer > --> $l<target version="1.0"><!-- custom override string --><architecture>armv4t</architecture></target>#bb
+ TRACE gdbstub::gdbstub_impl > <-- $qTStatus#49
+ INFO gdbstub::gdbstub_impl > Unknown command: qTStatus
+ TRACE gdbstub::protocol::response_writer > --> $#00
+ TRACE gdbstub::gdbstub_impl > <-- $?#3f
+ TRACE gdbstub::protocol::response_writer > --> $S05#b8
+ TRACE gdbstub::gdbstub_impl > <-- $qfThreadInfo#bb
+ TRACE gdbstub::protocol::response_writer > --> $mp01.01#cd
+ TRACE gdbstub::gdbstub_impl > <-- $qsThreadInfo#c8
+ TRACE gdbstub::protocol::response_writer > --> $l#6c
+ TRACE gdbstub::gdbstub_impl > <-- $qAttached:1#fa
+GDB queried if it was attached to a process with PID 1
+ TRACE gdbstub::protocol::response_writer > --> $1#31
+ TRACE gdbstub::gdbstub_impl > <-- $Hc-1#09
+ TRACE gdbstub::protocol::response_writer > --> $OK#9a
+ TRACE gdbstub::gdbstub_impl > <-- $qC#b4
+ INFO gdbstub::gdbstub_impl > Unknown command: qC
+ TRACE gdbstub::protocol::response_writer > --> $#00
+ TRACE gdbstub::gdbstub_impl > <-- $g#67
+ TRACE gdbstub::protocol::response_writer > --> $00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000107856341200005555xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx10000000#66
+ TRACE gdbstub::gdbstub_impl > <-- $qfThreadInfo#bb
+ TRACE gdbstub::protocol::response_writer > --> $mp01.01#cd
+ TRACE gdbstub::gdbstub_impl > <-- $qsThreadInfo#c8
+ TRACE gdbstub::protocol::response_writer > --> $l#6c
+ TRACE gdbstub::gdbstub_impl > <-- $qXfer:memory-map:read::0,ffb#18
+ TRACE gdbstub::protocol::response_writer > --> $l<?xml version="1.0"?>
+<!DOCTYPE memory-map
+ PUBLIC "+//IDN gnu.org//DTD GDB Memory Map V1.0//EN"
+ "http://sourceware.org/gdb/gdb-memory-map.dtd">
+<memory-map>
+ <memory type="ram" start="0x0" length="0x100000000"/>
+</memory-map>#75
+ TRACE gdbstub::gdbstub_impl > <-- $m55550000,4#61
+ TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
+ TRACE gdbstub::gdbstub_impl > <-- $m5554fffc,4#35
+ TRACE gdbstub::protocol::response_writer > --> $00000000#7e
+ TRACE gdbstub::gdbstub_impl > <-- $m55550000,4#61
+ TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
+ TRACE gdbstub::gdbstub_impl > <-- $m5554fffc,4#35
+ TRACE gdbstub::protocol::response_writer > --> $00000000#7e
+ TRACE gdbstub::gdbstub_impl > <-- $m55550000,2#5f
+ TRACE gdbstub::protocol::response_writer > --> $04b0#f6
+ TRACE gdbstub::gdbstub_impl > <-- $m5554fffe,2#35
+ TRACE gdbstub::protocol::response_writer > --> $0000#7a
+ TRACE gdbstub::gdbstub_impl > <-- $m5554fffc,2#33
+ TRACE gdbstub::protocol::response_writer > --> $0000#7a
+ TRACE gdbstub::gdbstub_impl > <-- $m55550000,2#5f
+ TRACE gdbstub::protocol::response_writer > --> $04b0#f6
+ TRACE gdbstub::gdbstub_impl > <-- $m5554fffe,2#35
+ TRACE gdbstub::protocol::response_writer > --> $0000#7a
+ TRACE gdbstub::gdbstub_impl > <-- $m5554fffc,2#33
+ TRACE gdbstub::protocol::response_writer > --> $0000#7a
+ TRACE gdbstub::gdbstub_impl > <-- $m55550000,4#61
+ TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
+ TRACE gdbstub::gdbstub_impl > <-- $m5554fffc,4#35
+ TRACE gdbstub::protocol::response_writer > --> $00000000#7e
+ TRACE gdbstub::gdbstub_impl > <-- $m55550000,4#61
+ TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
+ TRACE gdbstub::gdbstub_impl > <-- $m5554fffc,4#35
+ TRACE gdbstub::protocol::response_writer > --> $00000000#7e
+ TRACE gdbstub::gdbstub_impl > <-- $m55550000,4#61
+ TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
+ TRACE gdbstub::gdbstub_impl > <-- $m55550000,4#61
+ TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
+ TRACE gdbstub::gdbstub_impl > <-- $m55550000,4#61
+ TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
+ TRACE gdbstub::gdbstub_impl > <-- $m0,4#fd
+ TRACE gdbstub::protocol::response_writer > --> $00000000#7e
+```
+</details>
diff --git a/.gitignore b/.gitignore
index a97cf42..9da96d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,7 @@
Cargo.lock
**/.gdb_history
-# GDB will core dump if the target is implemented incorrectly (which it often is during debugging)
+# The GDB client may core dump if the target is implemented incorrectly
**/core
+
+.vscode
diff --git a/Android.bp b/Android.bp
index b9af58b..6155ca7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --dependencies --no-subdir.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -22,6 +22,8 @@ rust_library {
name: "libgdbstub",
host_supported: true,
crate_name: "gdbstub",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.5.0",
srcs: ["src/lib.rs"],
edition: "2018",
features: [
@@ -37,12 +39,3 @@ rust_library {
],
proc_macros: ["libpaste"],
}
-
-// dependent_library ["feature_list"]
-// autocfg-1.0.1
-// cfg-if-0.1.10
-// cfg-if-1.0.0
-// log-0.4.14 "std"
-// managed-0.8.0 "alloc"
-// num-traits-0.2.14
-// paste-1.0.5
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2021cba..c5c2ed0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,74 @@
-# Changelog
-
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+# 0.5.0
+
+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.
+
+Check out [`transition_guide.md`](./docs/transition_guide.md) for guidance on upgrading from `0.4.x` to `0.5`.
+
+#### New Features
+
+- Implement Run-Length-Encoding (RLE) on outgoing packets
+ - _This significantly cuts down on the data being transferred over the wire when reading from registers/memory_
+- Add target-specific `kind: Arch::BreakpointKind` parameters to the Breakpoint API
+ - _While emulated systems typically implement breakpoints by pausing execution once the PC hits a certain value, "real" systems typically need to patch the instruction stream with a breakpoint instruction. On systems with variable-sized instructions, this `kind` parameter specifies the size of the instruction that should be injected._
+- Implement `ResumeAction::{Step,Continue}WithSignal`
+- Added the `Exited(u8)`, `Terminated(u8)`, and `ReplayLog("begin"|"end")` stop reasons.
+- Added `DisconnectReason::Exited(u8)` and `DisconnectReason::Terminated(u8)`.
+- Reworked the `MultiThreadOps::resume` API to be significantly more ergonomic and efficient
+ - See the [transition guide](https://github.com/daniel5151/gdbstub/blob/dev/0.5/docs/transition_guide.md#new-multithreadopsresume-api) for more details.
+
+#### New Protocol Extensions
+
+- `{Single,Multi}ThreadReverse{Step,Continue}` - Support for reverse-step and reverse-continue. [\#48](https://github.com/daniel5151/gdbstub/pull/48 ) ([DrChat](https://github.com/DrChat))
+- `{Single,Multi}ThreadRangeStepping` - Optional optimized [range stepping](https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping) support.
+
+#### Breaking Arch Changes
+
+- **`gdbstub::arch` has been moved into a separate `gdbstub_arch` crate**
+ - _See [\#45](https://github.com/daniel5151/gdbstub/issues/45) for details on why this was done._
+- (x86) Break GPRs & SRs into individual fields/variants [\#34](https://github.com/daniel5151/gdbstub/issues/34)
+
+#### Breaking API Changes
+
+- Base Protocol Refactors
+ - Reworked the `MultiThreadOps::resume` API
+ - Added a wrapper around the raw `check_gdb_interrupt` callback, hiding the underlying implementation details
+ - Extracted base protocol single-register access methods (`{read,write}_register`) into separate `SingleRegisterAccess` trait
+ - _These are optional GDB protocol methods, and as such, should be modeled as IDETs_
+- Protocol Extension Refactors
+ - Consolidated the `{Hw,Sw}Breakpoints/Watchpoints` IDETs under a single `Breakpoints` IDET + sub-IDETs
+ - Added new arch-specific `kind: Arch::BreakpointKind` parameter to `add_{hw,sw}_breakpoint` methods
+ - Renamed `target::ext::extended_mod::ConfigureASLR{Ops}` to `ConfigureAslr{Ops}` (clippy::upper_case_acronyms)
+- Added `{Step,Continue}WithSignal` variants to `target::ext::base::ResumeAction`
+- Trait Changes
+ - `arch::Arch`: Added `type BreakpointKind`. Required to support arch-specific breakpoint kinds
+ - `arch::Arch`: (very minor) Added [`num_traits::FromPrimitive`](https://docs.rs/num/0.4.0/num/traits/trait.FromPrimitive.html) bound to `Arch::Usize`
+ - `arch::Registers`: Added `type ProgramCounter` and associated `fn pc(&self) -> Self::ProgramCounter` method. Added preemptively in anticipation of future GDB Agent support
+- Removed the `Halted` stop reason (more accurate to simply return `{Exited|Terminated}(SIGSTOP)` instead).
+- Removed the `Halted` disconnect reason (replaced with the `Exited` and `Terminated` stop reasons instead).
+- Removed the implicit `ExtendedMode` attached PID tracking when `alloc` was available. See [`23b56038`](https://github.com/daniel5151/gdbstub/commit/23b56038) rationale behind this change.
+
+
+#### Internal Improvements
+
+- Split monolithic `GdbStubImpl` implementation into separate files (by protocol extension)
+- Finally rewrite + optimize `GdbStubImpl::do_vcont`, along with streamlining its interactions with the legacy `s` and `c` packets
+- Sprinkle more IDET-based dead code elimination hints (notably wrt. stop reasons)
+- Remove the default `self.current_mem_tid` hack, replacing it with a much more elegant solution
+- Packet Parser improvements
+ - Remove last remaining bit of UTF-8 related code
+ - Eliminate as much panicking bounds-checking code as possible
+ - support efficient parsing of packets that are parsed differently depending on active protocol extension (namely, the breakpoint packets)
+ - (currently unused) Zero-cost support for parsing `Z` and `z` packets with embedded agent bytecode expressions
+- Use intra-doc links whenever possible
+
+#### Bugfixes
+
+- Fix `RiscvRegId` for `arch::riscv::Riscv64` [\#46](https://github.com/daniel5151/gdbstub/issues/46) ([fzyz999](https://github.com/fzyz999))
+
# 0.4.5
#### New Protocol Extensions
@@ -14,36 +79,36 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
#### Bugfixes
-- use `write!` instead of `writeln!` in `output!` macro [\#41](https://github.com/daniel5151/gdbstub/issues/41)
+- use `write!` instead of `writeln!` in `output!` macro [\#41](https://github.com/daniel5151/gdbstub/issues/41)
# 0.4.3
#### New Arch Implementations
-- Implement `RegId` for Mips/Mips64 [\#38](https://github.com/daniel5151/gdbstub/pull/38) ([starfleetcadet75](https://github.com/starfleetcadet75))
-- Implement `RegId` for MSP430 [\#38](https://github.com/daniel5151/gdbstub/pull/38) ([starfleetcadet75](https://github.com/starfleetcadet75))
+- Implement `RegId` for Mips/Mips64 [\#38](https://github.com/daniel5151/gdbstub/pull/38) ([starfleetcadet75](https://github.com/starfleetcadet75))
+- Implement `RegId` for MSP430 [\#38](https://github.com/daniel5151/gdbstub/pull/38) ([starfleetcadet75](https://github.com/starfleetcadet75))
# 0.4.2
#### Packaging
-- Exclude test object files from package [\#37](https://github.com/daniel5151/gdbstub/pull/37) ([keiichiw](https://github.com/keiichiw))
+- Exclude test object files from package [\#37](https://github.com/daniel5151/gdbstub/pull/37) ([keiichiw](https://github.com/keiichiw))
# 0.4.1
#### New Arch Implementations
-- Implement `RegId` for x86/x86_64 [\#34](https://github.com/daniel5151/gdbstub/pull/34) ([keiichiw](https://github.com/keiichiw))
+- Implement `RegId` for x86/x86_64 [\#34](https://github.com/daniel5151/gdbstub/pull/34) ([keiichiw](https://github.com/keiichiw))
#### Bugfixes
-- Switch fatal error signal from `T06` to `S05`,
-- specify cfg-if 0.1.10 or later [\#33](https://github.com/daniel5151/gdbstub/pull/33) ([keiichiw](https://github.com/keiichiw))
- - `cargo build` fails if cfg-if is 0.1.9 or older
+- Switch fatal error signal from `T06` to `S05`,
+- specify cfg-if 0.1.10 or later [\#33](https://github.com/daniel5151/gdbstub/pull/33) ([keiichiw](https://github.com/keiichiw))
+ - `cargo build` fails if cfg-if is 0.1.9 or older
-#### Misc
+#### Internal Improvements
-- Don't hard-code u64 when parsing packets (use big-endian byte arrays + late conversion to `Target::Arch::Usize`).
+- Don't hard-code u64 when parsing packets (use big-endian byte arrays + late conversion to `Target::Arch::Usize`).
# 0.4.0
@@ -51,37 +116,37 @@ This version includes a _major_ API overhaul, alongside a slew of new features a
Fun fact: Even after adding a _bunch_ of new features and bug-fixes, the in-tree `example_no_std` has remained just as small! The example on the `semver-fix-0.2.2` branch is `20251` bytes, while the example on `0.4.0` is `20246` bytes.
-#### API Changes
+#### Breaking API Changes
-- Rewrite the `Target` API in terms of "Inlineable Dyn Extension Traits" (IDETs)
- - _By breaking up `Target` into smaller pieces which can be mixed-and-matched, it not only makes it easier to get up-and-running with `gdbstub`, but it also unlocks a lot of awesome internal optimizations:_
- - Substantially reduces binary-size footprint by guaranteeing dead-code-elimination of parsing/handling unimplemented GDB protocol features.
- - Compile-time enforcement that certain groups of methods are implemented in-tandem (e.g: `add_sw_breakpoint` and `remove_sw_breakpoint`).
-- Update the `Target` API with support for non-fatal error handling.
- - _The old approach of only allowing \*fatal\* errors was woefully inadequate when dealing with potentially fallible operations such as reading from unauthorized memory (which GDB likes to do a bunch), or handling non-fatal `std::io::Error` that occur as a result of `ExtendedMode` operations. The new `TargetResult`/`TargetError` result is much more robust, and opens to door to supporting additional error handling extensions (such as LLDB's ASCII Errors)._
-- Update the `Connection` trait with new methods (`flush` - required, `write_all`, `on_session_start`)
-- Lift `Registers::RegId` to `Arch::RegId`, and introduce new temporary `RegIdImpl` solution for avoiding breaking API changes due to new `RegId` implementations (see [\#29](https://github.com/daniel5151/gdbstub/pull/29))
-- Mark various `RegId` enums as `#[non_exhaustive]`, allowing more registers to be added if need be.
-- Error types are now marked as `#[non_exhaustive]`.
+- Rewrite the `Target` API in terms of "Inlineable Dyn Extension Traits" (IDETs)
+ - _By breaking up `Target` into smaller pieces which can be mixed-and-matched, it not only makes it easier to get up-and-running with `gdbstub`, but it also unlocks a lot of awesome internal optimizations:_
+ - Substantially reduces binary-size footprint by guaranteeing dead-code-elimination of parsing/handling unimplemented GDB protocol features.
+ - Compile-time enforcement that certain groups of methods are implemented in-tandem (e.g: `add_sw_breakpoint` and `remove_sw_breakpoint`).
+- Update the `Target` API with support for non-fatal error handling.
+ - _The old approach of only allowing \*fatal\* errors was woefully inadequate when dealing with potentially fallible operations such as reading from unauthorized memory (which GDB likes to do a bunch), or handling non-fatal `std::io::Error` that occur as a result of `ExtendedMode` operations. The new `TargetResult`/`TargetError` result is much more robust, and opens to door to supporting additional error handling extensions (such as LLDB's ASCII Errors)._
+- Update the `Connection` trait with new methods (`flush` - required, `write_all`, `on_session_start`)
+- Lift `Registers::RegId` to `Arch::RegId`, and introduce new temporary `RegIdImpl` solution for avoiding breaking API changes due to new `RegId` implementations (see [\#29](https://github.com/daniel5151/gdbstub/pull/29))
+- Mark various `RegId` enums as `#[non_exhaustive]`, allowing more registers to be added if need be.
+- Error types are now marked as `#[non_exhaustive]`.
#### New Protocol Extensions
-- `ExtendedMode` - Allow targets to run new processes / attach to existing processes / restart execution.
- - Includes support for `set disable-randomization`, `set environment`, `set startup-with-shell`, and `set cwd` and `cd`.
-- `SectionOffsets` - Get section/segment relocation offsets from the target. [\#30](https://github.com/daniel5151/gdbstub/pull/30) ([mchesser](https://github.com/mchesser))
- - Uses the `qOffsets` packet under-the-hood.
+- `ExtendedMode` - Allow targets to run new processes / attach to existing processes / restart execution.
+ - Includes support for `set disable-randomization`, `set environment`, `set startup-with-shell`, and `set cwd` and `cd`.
+- `SectionOffsets` - Get section/segment relocation offsets from the target. [\#30](https://github.com/daniel5151/gdbstub/pull/30) ([mchesser](https://github.com/mchesser))
+ - Uses the `qOffsets` packet under-the-hood.
#### Bugfixes
-- Fix issues related to selecting the incorrect thread after hitting a breakpoint in multi-threaded targets.
-- Ensure that `set_nodelay` is set when using a `TcpStream` as a `Connection` (via the new `Connection::on_session_start` API)
- - _This should result in a noticeable performance improvement when debugging over TCP._
+- Fix issues related to selecting the incorrect thread after hitting a breakpoint in multi-threaded targets.
+- Ensure that `set_nodelay` is set when using a `TcpStream` as a `Connection` (via the new `Connection::on_session_start` API)
+ - _This should result in a noticeable performance improvement when debugging over TCP._
-#### Misc
+#### Internal Improvements
-- Removed `btou` dependency.
-- Removed all `UTF-8` aware `str` handling code.
- - _GDB uses a pure ASCII protocol, so including code to deal with UTF-8 resulted in unnecessary binary bloat._
+- Removed `btou` dependency.
+- Removed all `UTF-8` aware `str` handling code.
+ - _GDB uses a pure ASCII protocol, so including code to deal with UTF-8 resulted in unnecessary binary bloat._
# 0.3.0 (formerly 0.2.2)
@@ -91,34 +156,34 @@ Version `0.3.0` is identical to the yanked version `0.2.2`, except that it adher
Thanks to [h33p](https://github.com/h33p) for reporting this issue ([\#27](https://github.com/daniel5151/gdbstub/issues/27))
-#### API Changes
+#### Breaking API Changes
-- Update `Target::resume` API to replace raw `&mut dyn Iterator` with a functionally identical concrete `Actions` iterator.
-- Mark the `StopReason` enum as `#[non_exhaustive]`, allowing further types to be added without being considered as an API breaking change.
+- Update `Target::resume` API to replace raw `&mut dyn Iterator` with a functionally identical concrete `Actions` iterator.
+- Mark the `StopReason` enum as `#[non_exhaustive]`, allowing further types to be added without being considered as an API breaking change.
#### New Protocol Extensions
-- Add `Target::read/write_register` support (to support single register accesses) [\#22](https://github.com/daniel5151/gdbstub/pull/22) ([thomashk0](https://github.com/thomashk0))
-- Add `StopReason::Signal(u8)` variant, to send arbitrary signal codes [\#19](https://github.com/daniel5151/gdbstub/pull/19) ([mchesser](https://github.com/mchesser))
+- Add `Target::read/write_register` support (to support single register accesses) [\#22](https://github.com/daniel5151/gdbstub/pull/22) ([thomashk0](https://github.com/thomashk0))
+- Add `StopReason::Signal(u8)` variant, to send arbitrary signal codes [\#19](https://github.com/daniel5151/gdbstub/pull/19) ([mchesser](https://github.com/mchesser))
#### New Arch Implementations
-- Add partial RISC-V support (only integer ISA at the moment) [\#21](https://github.com/daniel5151/gdbstub/pull/21) ([thomashk0](https://github.com/thomashk0))
-- Add i386 (x86) support [\#23](https://github.com/daniel5151/gdbstub/pull/23) ([jamcleod](https://github.com/jamcleod))
-- Add 32-bit PowerPC support [\#25](https://github.com/daniel5151/gdbstub/pull/25) ([jamcleod](https://github.com/jamcleod))
+- Add partial RISC-V support (only integer ISA at the moment) [\#21](https://github.com/daniel5151/gdbstub/pull/21) ([thomashk0](https://github.com/thomashk0))
+- Add i386 (x86) support [\#23](https://github.com/daniel5151/gdbstub/pull/23) ([jamcleod](https://github.com/jamcleod))
+- Add 32-bit PowerPC support [\#25](https://github.com/daniel5151/gdbstub/pull/25) ([jamcleod](https://github.com/jamcleod))
# 0.2.1
#### New Arch Implementations
-- Add x86_86 support [\#11](https://github.com/daniel5151/gdbstub/pull/11) ([jamcleod](https://github.com/jamcleod))
-- Add Mips and Mips64 support [\#13](https://github.com/daniel5151/gdbstub/pull/13) ([starfleetcadet75](https://github.com/starfleetcadet75))
+- Add x86_64 support [\#11](https://github.com/daniel5151/gdbstub/pull/11) ([jamcleod](https://github.com/jamcleod))
+- Add Mips and Mips64 support [\#13](https://github.com/daniel5151/gdbstub/pull/13) ([starfleetcadet75](https://github.com/starfleetcadet75))
-#### Misc
+#### Internal Improvements
-- Documentation improvements
- - Document PC adjustment requirements in `Target::resume`
- - Add docs on handling non-fatal invalid memory reads/writes in `Target::read/write_addrs`.
+- Documentation improvements
+ - Document PC adjustment requirements in `Target::resume`
+ - Add docs on handling non-fatal invalid memory reads/writes in `Target::read/write_addrs`.
# 0.2.0
diff --git a/Cargo.toml b/Cargo.toml
index f84b7c1..0706d77 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@
[package]
edition = "2018"
name = "gdbstub"
-version = "0.4.5"
+version = "0.5.0"
authors = ["Daniel Prilik <danielprilik@gmail.com>"]
exclude = ["examples/**/*.elf", "examples/**/*.o"]
description = "An implementation of the GDB Remote Serial Protocol in Rust"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 7f2e7d5..0c176ea 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -2,7 +2,7 @@
name = "gdbstub"
description = "An implementation of the GDB Remote Serial Protocol in Rust"
authors = ["Daniel Prilik <danielprilik@gmail.com>"]
-version = "0.4.5"
+version = "0.5.0"
license = "MIT"
edition = "2018"
readme = "README.md"
@@ -21,6 +21,8 @@ num-traits = { version = "0.2", default-features = false }
paste = "1.0"
[dev-dependencies]
+gdbstub_arch = { path = "./gdbstub_arch/" }
+
armv4t_emu = "0.1"
pretty_env_logger = "0.4"
goblin = "0.2"
@@ -41,3 +43,7 @@ required-features = ["std"]
[[example]]
name = "armv4t_multicore"
required-features = ["std"]
+
+[workspace]
+members = ["gdbstub_arch"]
+exclude = ["example_no_std"]
diff --git a/METADATA b/METADATA
index 8f37b6f..0d4065e 100644
--- a/METADATA
+++ b/METADATA
@@ -7,13 +7,13 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://static.crates.io/crates/gdbstub/gdbstub-0.4.5.crate"
+ value: "https://static.crates.io/crates/gdbstub/gdbstub-0.5.0.crate"
}
- version: "0.4.5"
+ version: "0.5.0"
license_type: NOTICE
last_upgrade_date {
year: 2021
- month: 4
- day: 1
+ month: 6
+ day: 21
}
}
diff --git a/README.md b/README.md
index 81cee45..d073267 100644
--- a/README.md
+++ b/README.md
@@ -5,35 +5,42 @@
An ergonomic and easy-to-integrate implementation of the [GDB Remote Serial Protocol](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html#Remote-Protocol) in Rust, with full `#![no_std]` support.
-Why `gdbstub`?
+ `gdbstub` makes it easy to integrate powerful guest debugging support to your emulator/hypervisor/debugger/embedded project. By implementing just a few basic methods of the [`gdbstub::Target`](https://docs.rs/gdbstub/latest/gdbstub/target/ext/base/singlethread/trait.SingleThreadOps.html) trait, you can have a rich GDB debugging session up and running in no time!
+
+**If you're looking for a quick snippet of example code to see what a typical `gdbstub` integration might look like, check out [examples/armv4t/gdb/mod.rs](https://github.com/daniel5151/gdbstub/blob/dev/0.5/examples/armv4t/gdb/mod.rs)**
+
+- [Documentation (gdbstub)](https://docs.rs/gdbstub)
+- [Documentation (gdbstub_arch)](https://docs.rs/gdbstub_arch)
+- [Changelog](CHANGELOG.md)
+- [0.4 to 0.5 Transition Guide](docs/transition_guide.md)
+
+Why use `gdbstub`?
- **Excellent Ergonomics**
- - Unlike other GDB stub libraries, which simply expose the underlying GDB protocol "warts and all", `gdbstub` tries to abstract as much of the raw GDB protocol details from the user.
- - For example, instead of having to dig through some [obscure XML files deep the GDB codebase](https://github.com/bminor/binutils-gdb/tree/master/gdb/features) just to read/write from CPU registers, `gdbstub` comes with [built-in register definitions](https://docs.rs/gdbstub/*/gdbstub/arch/index.html) for most common architectures!
+ - Instead of simply exposing the underlying GDB protocol "warts and all", `gdbstub` tries to abstract as much of the raw GDB protocol details from the user. For example:
+ - Instead of having to dig through [obscure XML files deep the GDB codebase](https://github.com/bminor/binutils-gdb/tree/master/gdb/features) just to read/write from CPU/architecture registers, `gdbstub` comes with a community-curated collection of [built-in architecture definitions](https://docs.rs/gdbstub_arch) for most popular platforms!
+ - Organizes GDB's countless optional protocol extensions into a coherent, understandable, and type-safe hierarchy of traits.
+ - Automatically handles client/server protocol feature negotiation, without needing to micro-manage the specific [`qSupported` packet](https://sourceware.org/gdb/onlinedocs/gdb/General-Query-Packets.html#qSupported) response.
- `gdbstub` makes _extensive_ use of Rust's powerful type system + generics to enforce protocol invariants at compile time, minimizing the number of tricky protocol details end users have to worry about.
+ - Using a novel technique called [**Inlineable Dyn Extension Traits**](#zero-overhead-protocol-extensions) (IDETs), `gdbstub` enables fine-grained control over active protocol extensions _without_ relying on clunky `cargo` features.
- **Easy to Integrate**
- `gdbstub`'s API is designed to be as unobtrusive as possible, and shouldn't require any large refactoring effort to integrate into an existing project. It doesn't require taking direct ownership of any key data structures, and aims to be a "drop in" solution when you need to add debugging to a project.
- **`#![no_std]` Ready & Size Optimized**
- - Can be configured to use fixed-size, pre-allocated buffers. **`gdbstub` does _not_ depend on `alloc`.**
+ - `gdbstub` is a **`no_std` first** library, whereby all protocol features are required to be `no_std` compatible.
+ - `gdbstub` does not require _any_ dynamic memory allocation, and can be configured to use fixed-size, pre-allocated buffers. This enables `gdbstub` to be used on even the most resource constrained, no-[`alloc`](https://doc.rust-lang.org/alloc/) platforms.
- `gdbstub` is transport-layer agnostic, and uses a basic [`Connection`](https://docs.rs/gdbstub/latest/gdbstub/trait.Connection.html) interface to communicate with the GDB server. As long as target has some method of performing in-order, serial, byte-wise I/O (e.g: putchar/getchar over UART), it's possible to run `gdbstub` on it.
- - "You don't pay for what you don't use": If you don't implement a particular protocol extension, the resulting binary won't include _any_ code related to parsing/handling that extension's packets! See the [Zero-overhead Protocol Extensions](#zero-overhead-protocol-extensions) section below for more details.
- - A lot of work has gone into reducing `gdbstub`'s binary and RAM footprints.
- - In release builds, using all the tricks outlined in [`min-sized-rust`](https://github.com/johnthagen/min-sized-rust), a baseline `gdbstub` implementation weighs in at roughly **_10kb of `.text` and negligible `.rodata`!_** \*
- - This is already pretty good, and I suspect that there are still lots of low-hanging optimizations which can reduce the size even further.
-
-\* Exact numbers vary by target platform, compiler version, and `gdbstub` revision. Data was collected using the included `example_no_std` project compiled on x86_64.
-
-`gdbstub` is particularly well suited for _emulation_, making it easy to add powerful, non-intrusive debugging support to an emulated system. Just provide an implementation of the [`Target`](https://docs.rs/gdbstub/latest/gdbstub/target/trait.Target.html) trait for your target platform, and you're ready to start debugging!
+ - "You don't pay for what you don't use": All code related to parsing/handling protocol extensions is guaranteed to be dead-code-eliminated from an optimized binary if left unimplmeneted! See the [Zero-overhead Protocol Extensions](#zero-overhead-protocol-extensions) section below for more details.
+ - `gdbstub` tries to keep the binary and RAM footprint of its minimal configuration to a bare minimum, enabling it to be used on even the most resource-constrained microcontrollers.
+ - When compiled in release mode, using all the tricks outlined in [`min-sized-rust`](https://github.com/johnthagen/min-sized-rust), a baseline `gdbstub` implementation weighs in at **_roughly 10kb of `.text` and negligible `.rodata`!_** \*
+ - \* Exact numbers vary by target platform, compiler version, and `gdbstub` revision. Data was collected using the included `example_no_std` project compiled on x86_64.
-- [Documentation](https://docs.rs/gdbstub)
-
-### Can I Use `gdsbtub` in Production?
+### Can I Use `gdbstub` in Production?
**Yes, as long as you don't mind some API churn until `1.0.0` is released.**
-`gdbstub` has been integrated into [many projects](#real-world-examples) since its initial `0.1.0` release, and thusfar, no _major_ bugs have been reported. Reported issues have typically been the result of faulty `Target` implementations (e.g: forgetting to adjust the PC after a breakpoint is hit), or were related to certain unimplemented GDB protocol features.
+Due to `gdbstub`'s heavy use of Rust's type system in enforcing GDB protocol invariants at compile time, it's often been the case that implementing new GDB protocol features has required making some breaking Trait/Type changes. While these changes are typically quite minor, they are nonetheless semver-breaking, and may require a code-change when moving between versions. Any particularly involved changes will typically be documented in a dedicated [transition guide](docs/transition_guide.md) document.
-That being said, due to `gdbstub`'s heavy use of Rust's type system in enforcing GDB protocol invariants at compile time, it's often been the case that implementing new GDB protocol features has required making some breaking Trait/Type changes (e.g: adding the `RegId` associated type to `Arch` to support addressing individual registers). While these changes are typically quite minor, they are nonetheless breaking, and may require a code-change when moving between versions.
+That being said, `gdbstub` has already been integrated into [many real-world projects](#real-world-examples) since its initial `0.1` release, and empirical evidence suggests that it seems to be doing its job quite well! Thusfar, there haven't been any reported issues related to core GDB debugging functionality, with most issues being caused by faulty `Target` and/or `Arch` implementations.
See the [Future Plans + Roadmap to `1.0.0`](#future-plans--roadmap-to-100) for more information on what features `gdbstub` still needs to implement before committing to API stability with version `1.0.0`.
@@ -45,15 +52,19 @@ The GDB Remote Serial Protocol is surprisingly complex, supporting advanced feat
- Step + Continue
- Read/Write memory
- Read/Write registers
- - (optional) Multithreading support
+ - Enumerating threads
+ - Only required in multithreaded targets
Of course, most use-cases will want to support additional debugging features as well. At the moment, `gdbstub` implements the following GDB protocol extensions:
-- Automatic architecture + feature detection (automatically implemented)
+- Automatic target architecture + feature reporting
- Breakpoints
- Software Breakpoints
- Hardware Breakpoints
- Read/Write/Access Watchpoints (i.e: value breakpoints)
+- Advanced step/continue
+ - Reverse execution (reverse-step, reverse-continue)
+ - Range-stepping
- Extended Mode
- Run/Attach/Kill Processes
- Pass environment variables / args to spawned processes
@@ -63,7 +74,9 @@ Of course, most use-cases will want to support additional debugging features as
- Custom `monitor` Commands
- Extend the GDB protocol with custom debug commands using GDB's `monitor` command
-_Note:_ Which GDB features are implemented are decided on an as-needed basis by `gdbstub`'s contributors. If there's a missing GDB feature that you'd like `gdbstub` to implement, please file an issue / open a PR! 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.
+_Note:_ GDB features are implemented on an as-needed basis by `gdbstub`'s contributors. If there's a missing GDB feature that you'd like `gdbstub` to implement, please file an issue and/or open a PR!
+
+For a full list of GDB remote features, 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.
### Zero-overhead Protocol Extensions
@@ -85,7 +98,6 @@ When using `gdbstub` in `#![no_std]` contexts, make sure to set `default-feature
- Provide built-in implementations for certain protocol features:
- Use a heap-allocated packet buffer in `GdbStub` (if none is provided via `GdbStubBuilder::with_packet_buffer`).
- (Monitor Command) Use a heap-allocated output buffer in `ConsoleOutput`.
- - (Extended Mode) Automatically track Attached/Spawned PIDs without implementing `ExtendedMode::query_if_attached`.
- `std` (implies `alloc`)
- Implement `Connection` for [`TcpStream`](https://doc.rust-lang.org/std/net/struct.TcpStream.html) and [`UnixStream`](https://doc.rust-lang.org/std/os/unix/net/struct.UnixStream.html).
- Implement [`std::error::Error`](https://doc.rust-lang.org/std/error/trait.Error.html) for `gdbstub::Error`.
@@ -96,19 +108,20 @@ When using `gdbstub` in `#![no_std]` contexts, make sure to set `default-feature
### Real-World Examples
- Virtual Machine Monitors (VMMs)
- - [crosvm](https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main#gdb-support) - The Chrome OS Virtual Machine Monitor
+ - [crosvm](https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main#gdb-support) - The Chrome OS Virtual Machine Monitor (x64)
- [Firecracker](https://firecracker-microvm.github.io/) - A lightweight VMM developed by AWS - feature is in [PR](https://github.com/firecracker-microvm/firecracker/pull/2168)
-- Emulators
+- Emulators (x64)
- [clicky](https://github.com/daniel5151/clicky/) - An emulator for classic clickwheel iPods (dual-core ARMv4T SoC)
- - [rustyboyadvance-ng](https://github.com/michelhe/rustboyadvance-ng/) - Nintendo GameBoy Advance emulator and debugger
+ - [rustyboyadvance-ng](https://github.com/michelhe/rustboyadvance-ng/) - Nintendo GameBoy Advance emulator and debugger (ARMv4T)
+ - [vaporstation](https://github.com/Colin-Suckow/vaporstation) - A Playstation One emulator (MIPS)
- [ts7200](https://github.com/daniel5151/ts7200/) - An emulator for the TS-7200, a somewhat bespoke embedded ARMv4t platform
- [microcorruption-emu](https://github.com/sapir/microcorruption-emu) - msp430 emulator for the microcorruption.com ctf
- Other
- - [memflow](https://github.com/memflow/memflow) - A physical memory introspection framework (part of `memflow-cli`)
+ - [memflow](https://github.com/memflow/memflow-cli) - A physical memory introspection framework (part of `memflow-cli`) (64)
While some of these projects may use older versions of `gdbstub`, they can nonetheless serve as useful examples of what a typical `gdbstub` integration might look like.
-If you end up using `gdbstub` in your project, consider opening a PR and add it to this list!
+If you end up using `gdbstub` in your project, consider opening a PR and adding it to this list!
### In-tree "Toy" Examples
@@ -116,14 +129,14 @@ These examples are built as part of the CI, and are guaranteed to be kept up to
- `armv4t` - `./examples/armv4t/`
- An incredibly simple ARMv4T-based system emulator with `gdbstub` support.
- - Unlike all other examples, `armv4t` **implements (almost) all available `target::ext` features.**
+ - **Implements (almost) all available `target::ext` features.** This makes it a great resource when first implementing a new protocol extension!
- `armv4t_multicore` - `./examples/armv4t_multicore/`
- A dual-core variation of the `armv4t` example.
- Implements the core of `gdbstub`'s multithread extensions API, but not much else.
- `example_no_std` - `./example_no_std`
- - An _extremely_ minimal example of how `gdbstub` can be used in a `#![no_std]` project.
- - Unlike the `armv4t/armv4t_multicore` examples, this project does _not_ include a working emulator, and stubs-out all `gdbstub` functions.
- - Tracks `gdbstub`'s approximate binary footprint (via the `check_size.sh` script)
+ - An _extremely_ minimal example which shows off how `gdbstub` can be used in a `#![no_std]` project.
+ - Unlike the `armv4t/armv4t_multicore` examples, this project does _not_ include a working emulator, and simply stubs all `gdbstub` functions.
+ - Doubles as a test-bed for tracking `gdbstub`'s approximate binary footprint (via the `check_size.sh` script), and validating certain dead-code-elimination optimizations.
## Using `gdbstub` on bare-metal hardware
@@ -133,22 +146,18 @@ If you happen to stumble across this crate and end up using it to debug some bar
## `unsafe` in `gdbstub`
-`gdbstub` "core" only has 2 instances of unsafe code:
-
-- A few trivially safe calls to `NonZeroUsize::new_unchecked()` when defining internal constants.
-- A call to `str::from_utf8_unchecked()` when working with incoming GDB packets (the underlying `&[u8]` buffer is checked with `is_ascii()` prior to the call).
+`gdbstub` limits its use of `unsafe` to a bare minimum, with all uses of `unsafe` required to have a corresponding `// SAFETY` comment as justification. The following list exhaustively documents all uses of `unsafe` in `gdbstub`.
-With the `std` feature enabled, there is one additional instance of `unsafe` code:
-
-- `gdbstub` includes an implementation of `UnixStream::peek` which uses `libc::recv`. This will be removed once [rust-lang/rust#73761](https://github.com/rust-lang/rust/pull/73761) is merged and stabilized.
+- When no cargo features are enabled:
+ - A few trivially safe calls to `NonZeroUsize::new_unchecked()` when defining internal constants.
+- When the `std` feature is enabled:
+ - An implementation of `UnixStream::peek` which uses `libc::recv`. This manual implementation will be removed once [rust-lang/rust#76923](https://github.com/rust-lang/rust/issues/76923) is stabilized.
## Future Plans + Roadmap to `1.0.0`
-Before `gdbstub` can comfortably commit to a stable `1.0.0` API, there are several outstanding features that should be implemented and questions that need to be addressed. Due to `gdbstub`'s heavy reliance on the Rust type system to enforce GDB protocol invariants, it's likely that a certain subset of yet-unimplemented protocol features may require breaking API changes.
-
-Notably, the vast majority of GDB protocol features (e.g: remote filesystem support, tracepoint packets, most query packets, etc...) should _not_ require breaking API changes, and could most likely be implemented using the standard backwards-compatible protocol extension approach.
+While the vast majority of GDB protocol features (e.g: remote filesystem support, tracepoint packets, most query packets, etc...) should _not_ require breaking API changes, there are still several key protocol features that'll need breaking API changes to be implemented.
-The following features are most likely to require breaking API changes, and should therefore be implemented prior to `1.0.0`.
+The following features are most likely to require breaking API changes, and should therefore be implemented prior to `1.0.0`. Not that this is _not_ an exhaustive list, and is subject to change.
- [ ] Stabilize the `Arch` trait
- [ ] Allow fine-grained control over target features ([\#12](https://github.com/daniel5151/gdbstub/issues/12))
@@ -156,19 +165,20 @@ The following features are most likely to require breaking API changes, and shou
- [ ] Implement GDB's various high-level operating modes:
- [x] Single/Multi Thread debugging
- [ ] Multiprocess Debugging
- - [ ] Add a third `base::multiprocess` API.
- - _Note:_ `gdbstub` already implements multiprocess extensions "under-the-hood", and just hard-codes a fake PID.
+ - [ ] Will require adding a third `target::ext::base::multiprocess` API.
+ - _Note:_ `gdbstub` already implements multiprocess extensions "under-the-hood", and just hard-codes a fake PID, so this is mostly a matter of "putting in the work".
- [x] [Extended Mode](https://sourceware.org/gdb/current/onlinedocs/gdb/Connecting.html) (`target extended-remote`)
- [ ] [Non-Stop Mode](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Non_002dStop.html#Remote-Non_002dStop)
- This may require some breaking API changes and/or some internals rework -- more research is needed.
- [ ] Have a working example of `gdbstub` running in a "bare-metal" `#![no_std]` environment (e.g: debugging a hobby OS via serial).
- - While there's no reason it _wouldn't_ work, it would be good to validate that the API + implementation supports this use-case.
+ - While there's no reason it _shouldn't_ work, it would be good to validate that the API + implementation supports this use-case.
Additionally, while not strict "blockers" to `1.0.0`, it would be good to explore these features as well:
-- [ ] Commit to a MSRV
-- [ ] Exposing an `async/await` interface
- - e.g: the current `check_gdb_interrupt` callback in `Target::resume()` could be modeled as a future.
- - Would require some tweaks to the Connection trait.
-- [ ] Adding [LLDB extension](https://raw.githubusercontent.com/llvm-mirror/lldb/master/docs/lldb-gdb-remote.txt) support
+- [ ] Should `gdbstub` commit to a MSRV?
+- [ ] Exposing `async/await` interfaces (particularly wrt. handling GDB client interrupts) ([\#36](https://github.com/daniel5151/gdbstub/issues/36))
+- [ ] Supporting various [LLDB extensions](https://raw.githubusercontent.com/llvm-mirror/lldb/master/docs/lldb-gdb-remote.txt) to the GDB RSP
- Skimming through the list, it doesn't seem like these extensions would require breaking API changes -- more research is needed.
+- [ ] Supporting multi-arch debugging via a single target
+ - e.g: debugging both x86 and x64 processes when running in extended mode
+- Proper handling of client "nack" packets for spotty connections.
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..c09ff4c
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,8 @@
+// Generated by update_crate_tests.py for tests that depend on this crate.
+{
+ "imports": [
+ {
+ "path": "external/rust/crates/gdbstub_arch"
+ }
+ ]
+}
diff --git a/cargo2android.json b/cargo2android.json
new file mode 100644
index 0000000..84da6ae
--- /dev/null
+++ b/cargo2android.json
@@ -0,0 +1,5 @@
+{
+ "device": true,
+ "no-subdir": true,
+ "run": true
+} \ No newline at end of file
diff --git a/docs/transition_guide.md b/docs/transition_guide.md
new file mode 100644
index 0000000..32eb98a
--- /dev/null
+++ b/docs/transition_guide.md
@@ -0,0 +1,128 @@
+You may also find it useful to refer to the in-tree `armv4t` and `armv4t_multicore` examples when transitioning between versions.
+
+# `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.
+
+Check out [`CHANGELOG.md`](../CHANGELOG.md) for a full list of changes.
+
+##### 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.
diff --git a/examples/armv4t/emu.rs b/examples/armv4t/emu.rs
index b7ef28f..3a1a4d3 100644
--- a/examples/armv4t/emu.rs
+++ b/examples/armv4t/emu.rs
@@ -63,6 +63,7 @@ impl Emu {
start_addr: elf_header.entry as u32,
cpu,
mem,
+
watchpoints: Vec::new(),
breakpoints: Vec::new(),
})
diff --git a/examples/armv4t/gdb/breakpoints.rs b/examples/armv4t/gdb/breakpoints.rs
new file mode 100644
index 0000000..1512943
--- /dev/null
+++ b/examples/armv4t/gdb/breakpoints.rs
@@ -0,0 +1,68 @@
+use gdbstub::target;
+use gdbstub::target::ext::breakpoints::WatchKind;
+use gdbstub::target::TargetResult;
+
+use crate::emu::Emu;
+
+impl target::ext::breakpoints::Breakpoints for Emu {
+ #[inline(always)]
+ fn sw_breakpoint(&mut self) -> Option<target::ext::breakpoints::SwBreakpointOps<Self>> {
+ Some(self)
+ }
+
+ #[inline(always)]
+ 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,
+ _kind: gdbstub_arch::arm::ArmBreakpointKind,
+ ) -> TargetResult<bool, Self> {
+ self.breakpoints.push(addr);
+ Ok(true)
+ }
+
+ fn remove_sw_breakpoint(
+ &mut self,
+ addr: u32,
+ _kind: gdbstub_arch::arm::ArmBreakpointKind,
+ ) -> TargetResult<bool, Self> {
+ match self.breakpoints.iter().position(|x| *x == addr) {
+ None => return Ok(false),
+ Some(pos) => self.breakpoints.remove(pos),
+ };
+
+ Ok(true)
+ }
+}
+
+impl target::ext::breakpoints::HwWatchpoint for Emu {
+ fn add_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> {
+ match kind {
+ WatchKind::Write => self.watchpoints.push(addr),
+ WatchKind::Read => self.watchpoints.push(addr),
+ WatchKind::ReadWrite => self.watchpoints.push(addr),
+ };
+
+ Ok(true)
+ }
+
+ fn remove_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> {
+ let pos = match self.watchpoints.iter().position(|x| *x == addr) {
+ None => return Ok(false),
+ Some(pos) => pos,
+ };
+
+ match kind {
+ WatchKind::Write => self.watchpoints.remove(pos),
+ WatchKind::Read => self.watchpoints.remove(pos),
+ WatchKind::ReadWrite => self.watchpoints.remove(pos),
+ };
+
+ Ok(true)
+ }
+}
diff --git a/examples/armv4t/gdb/extended_mode.rs b/examples/armv4t/gdb/extended_mode.rs
index d2b851a..6532507 100644
--- a/examples/armv4t/gdb/extended_mode.rs
+++ b/examples/armv4t/gdb/extended_mode.rs
@@ -1,6 +1,6 @@
use gdbstub::common::Pid;
use gdbstub::target;
-use gdbstub::target::ext::extended_mode::{Args, ShouldTerminate};
+use gdbstub::target::ext::extended_mode::{Args, AttachKind, ShouldTerminate};
use gdbstub::target::TargetResult;
use crate::emu::Emu;
@@ -61,20 +61,32 @@ impl target::ext::extended_mode::ExtendedMode for Emu {
Ok(Pid::new(1337).unwrap())
}
- fn configure_aslr(&mut self) -> Option<target::ext::extended_mode::ConfigureASLROps<Self>> {
+ fn query_if_attached(&mut self, pid: Pid) -> TargetResult<AttachKind, Self> {
+ eprintln!(
+ "GDB queried if it was attached to a process with PID {}",
+ pid
+ );
+ Ok(AttachKind::Attach)
+ }
+
+ #[inline(always)]
+ fn configure_aslr(&mut self) -> Option<target::ext::extended_mode::ConfigureAslrOps<Self>> {
Some(self)
}
+ #[inline(always)]
fn configure_env(&mut self) -> Option<target::ext::extended_mode::ConfigureEnvOps<Self>> {
Some(self)
}
+ #[inline(always)]
fn configure_startup_shell(
&mut self,
) -> Option<target::ext::extended_mode::ConfigureStartupShellOps<Self>> {
Some(self)
}
+ #[inline(always)]
fn configure_working_dir(
&mut self,
) -> Option<target::ext::extended_mode::ConfigureWorkingDirOps<Self>> {
@@ -82,7 +94,7 @@ impl target::ext::extended_mode::ExtendedMode for Emu {
}
}
-impl target::ext::extended_mode::ConfigureASLR for Emu {
+impl target::ext::extended_mode::ConfigureAslr for Emu {
fn cfg_aslr(&mut self, enabled: bool) -> TargetResult<(), Self> {
eprintln!("GDB {} ASLR", if enabled { "enabled" } else { "disabled" });
Ok(())
diff --git a/examples/armv4t/gdb/mod.rs b/examples/armv4t/gdb/mod.rs
index 7decab9..bb9a0e7 100644
--- a/examples/armv4t/gdb/mod.rs
+++ b/examples/armv4t/gdb/mod.rs
@@ -1,17 +1,20 @@
use core::convert::TryInto;
use armv4t_emu::{reg, Memory};
-use gdbstub::arch;
-use gdbstub::arch::arm::reg::id::ArmCoreRegId;
use gdbstub::target;
-use gdbstub::target::ext::base::singlethread::{ResumeAction, SingleThreadOps, StopReason};
+use gdbstub::target::ext::base::singlethread::{
+ GdbInterrupt, ResumeAction, SingleThreadOps, SingleThreadReverseContOps,
+ SingleThreadReverseStepOps, StopReason,
+};
use gdbstub::target::ext::breakpoints::WatchKind;
use gdbstub::target::{Target, TargetError, TargetResult};
+use gdbstub_arch::arm::reg::id::ArmCoreRegId;
use crate::emu::{Emu, Event};
// Additional GDB extensions
+mod breakpoints;
mod extended_mode;
mod monitor_cmd;
mod section_offsets;
@@ -30,33 +33,40 @@ fn cpu_reg_id(id: ArmCoreRegId) -> Option<u8> {
}
impl Target for Emu {
- type Arch = arch::arm::Armv4t;
+ type Arch = gdbstub_arch::arm::Armv4t;
type Error = &'static str;
+ // --------------- IMPORTANT NOTE ---------------
+ // Always remember to annotate IDET enable methods with `inline(always)`!
+ // Without this annotation, LLVM might fail to dead-code-eliminate nested IDET
+ // implementations, resulting in unnecessary binary bloat.
+
+ #[inline(always)]
fn base_ops(&mut self) -> target::ext::base::BaseOps<Self::Arch, Self::Error> {
target::ext::base::BaseOps::SingleThread(self)
}
- fn sw_breakpoint(&mut self) -> Option<target::ext::breakpoints::SwBreakpointOps<Self>> {
- Some(self)
- }
-
- fn hw_watchpoint(&mut self) -> Option<target::ext::breakpoints::HwWatchpointOps<Self>> {
+ #[inline(always)]
+ fn breakpoints(&mut self) -> Option<target::ext::breakpoints::BreakpointsOps<Self>> {
Some(self)
}
+ #[inline(always)]
fn extended_mode(&mut self) -> Option<target::ext::extended_mode::ExtendedModeOps<Self>> {
Some(self)
}
+ #[inline(always)]
fn monitor_cmd(&mut self) -> Option<target::ext::monitor_cmd::MonitorCmdOps<Self>> {
Some(self)
}
+ #[inline(always)]
fn section_offsets(&mut self) -> Option<target::ext::section_offsets::SectionOffsetsOps<Self>> {
Some(self)
}
+ #[inline(always)]
fn target_description_xml_override(
&mut self,
) -> Option<target::ext::target_description_xml_override::TargetDescriptionXmlOverrideOps<Self>>
@@ -65,12 +75,12 @@ impl Target for Emu {
}
}
-impl SingleThreadOps for Emu {
- fn resume(
+impl Emu {
+ fn inner_resume(
&mut self,
action: ResumeAction,
- check_gdb_interrupt: &mut dyn FnMut() -> bool,
- ) -> Result<StopReason<u32>, Self::Error> {
+ mut check_gdb_interrupt: impl FnMut() -> bool,
+ ) -> Result<StopReason<u32>, &'static str> {
let event = match action {
ResumeAction::Step => match self.step() {
Some(e) => e,
@@ -90,11 +100,12 @@ impl SingleThreadOps for Emu {
}
}
}
+ _ => return Err("cannot resume with signal"),
};
Ok(match event {
- Event::Halted => StopReason::Halted,
- Event::Break => StopReason::HwBreak,
+ Event::Halted => StopReason::Terminated(19), // SIGSTOP
+ Event::Break => StopReason::SwBreak,
Event::WatchWrite(addr) => StopReason::Watch {
kind: WatchKind::Write,
addr,
@@ -105,8 +116,22 @@ impl SingleThreadOps for Emu {
},
})
}
+}
- fn read_registers(&mut self, regs: &mut arch::arm::reg::ArmCoreRegs) -> TargetResult<(), Self> {
+impl SingleThreadOps for Emu {
+ fn resume(
+ &mut self,
+ action: ResumeAction,
+ gdb_interrupt: GdbInterrupt<'_>,
+ ) -> Result<StopReason<u32>, Self::Error> {
+ let mut gdb_interrupt = gdb_interrupt.no_async();
+ self.inner_resume(action, || gdb_interrupt.pending())
+ }
+
+ fn read_registers(
+ &mut self,
+ regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs,
+ ) -> TargetResult<(), Self> {
let mode = self.cpu.mode();
for i in 0..13 {
@@ -120,7 +145,10 @@ impl SingleThreadOps for Emu {
Ok(())
}
- fn write_registers(&mut self, regs: &arch::arm::reg::ArmCoreRegs) -> TargetResult<(), Self> {
+ fn write_registers(
+ &mut self,
+ regs: &gdbstub_arch::arm::reg::ArmCoreRegs,
+ ) -> TargetResult<(), Self> {
let mode = self.cpu.mode();
for i in 0..13 {
@@ -134,9 +162,50 @@ impl SingleThreadOps for Emu {
Ok(())
}
+ fn read_addrs(&mut self, start_addr: u32, data: &mut [u8]) -> TargetResult<(), Self> {
+ for (addr, val) in (start_addr..).zip(data.iter_mut()) {
+ *val = self.mem.r8(addr)
+ }
+ Ok(())
+ }
+
+ fn write_addrs(&mut self, start_addr: u32, data: &[u8]) -> TargetResult<(), Self> {
+ for (addr, val) in (start_addr..).zip(data.iter().copied()) {
+ self.mem.w8(addr, val)
+ }
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn single_register_access(
+ &mut self,
+ ) -> Option<target::ext::base::SingleRegisterAccessOps<(), Self>> {
+ Some(self)
+ }
+
+ #[inline(always)]
+ fn support_reverse_cont(&mut self) -> Option<SingleThreadReverseContOps<Self>> {
+ Some(self)
+ }
+
+ #[inline(always)]
+ fn support_reverse_step(&mut self) -> Option<SingleThreadReverseStepOps<Self>> {
+ Some(self)
+ }
+
+ #[inline(always)]
+ fn support_resume_range_step(
+ &mut self,
+ ) -> Option<target::ext::base::singlethread::SingleThreadRangeSteppingOps<Self>> {
+ Some(self)
+ }
+}
+
+impl target::ext::base::SingleRegisterAccess<()> for Emu {
fn read_register(
&mut self,
- reg_id: arch::arm::reg::id::ArmCoreRegId,
+ _tid: (),
+ reg_id: gdbstub_arch::arm::reg::id::ArmCoreRegId,
dst: &mut [u8],
) -> TargetResult<(), Self> {
if let Some(i) = cpu_reg_id(reg_id) {
@@ -150,7 +219,8 @@ impl SingleThreadOps for Emu {
fn write_register(
&mut self,
- reg_id: arch::arm::reg::id::ArmCoreRegId,
+ _tid: (),
+ reg_id: gdbstub_arch::arm::reg::id::ArmCoreRegId,
val: &[u8],
) -> TargetResult<(), Self> {
let w = u32::from_le_bytes(
@@ -164,61 +234,51 @@ impl SingleThreadOps for Emu {
Err(().into())
}
}
-
- fn read_addrs(&mut self, start_addr: u32, data: &mut [u8]) -> TargetResult<(), Self> {
- for (addr, val) in (start_addr..).zip(data.iter_mut()) {
- *val = self.mem.r8(addr)
- }
- Ok(())
- }
-
- fn write_addrs(&mut self, start_addr: u32, data: &[u8]) -> TargetResult<(), Self> {
- for (addr, val) in (start_addr..).zip(data.iter().copied()) {
- self.mem.w8(addr, val)
- }
- Ok(())
- }
}
-impl target::ext::breakpoints::SwBreakpoint for Emu {
- fn add_sw_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> {
- self.breakpoints.push(addr);
- Ok(true)
- }
-
- fn remove_sw_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> {
- match self.breakpoints.iter().position(|x| *x == addr) {
- None => return Ok(false),
- Some(pos) => self.breakpoints.remove(pos),
- };
-
- Ok(true)
+impl target::ext::base::singlethread::SingleThreadReverseCont for Emu {
+ fn reverse_cont(
+ &mut self,
+ gdb_interrupt: GdbInterrupt<'_>,
+ ) -> Result<StopReason<u32>, Self::Error> {
+ // FIXME: actually implement reverse step
+ eprintln!(
+ "FIXME: Not actually reverse-continuing. Performing forwards continue instead..."
+ );
+ self.resume(ResumeAction::Continue, gdb_interrupt)
}
}
-impl target::ext::breakpoints::HwWatchpoint for Emu {
- fn add_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> {
- match kind {
- WatchKind::Write => self.watchpoints.push(addr),
- WatchKind::Read => self.watchpoints.push(addr),
- WatchKind::ReadWrite => self.watchpoints.push(addr),
- };
-
- Ok(true)
+impl target::ext::base::singlethread::SingleThreadReverseStep for Emu {
+ fn reverse_step(
+ &mut self,
+ gdb_interrupt: GdbInterrupt<'_>,
+ ) -> Result<StopReason<u32>, Self::Error> {
+ // FIXME: actually implement reverse step
+ eprintln!(
+ "FIXME: Not actually reverse-stepping. Performing single forwards step instead..."
+ );
+ self.resume(ResumeAction::Step, gdb_interrupt)
}
+}
- fn remove_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> {
- let pos = match self.watchpoints.iter().position(|x| *x == addr) {
- None => return Ok(false),
- Some(pos) => pos,
- };
-
- match kind {
- WatchKind::Write => self.watchpoints.remove(pos),
- WatchKind::Read => self.watchpoints.remove(pos),
- WatchKind::ReadWrite => self.watchpoints.remove(pos),
- };
+impl target::ext::base::singlethread::SingleThreadRangeStepping for Emu {
+ fn resume_range_step(
+ &mut self,
+ start: u32,
+ end: u32,
+ gdb_interrupt: GdbInterrupt<'_>,
+ ) -> Result<StopReason<u32>, Self::Error> {
+ let mut gdb_interrupt = gdb_interrupt.no_async();
+ loop {
+ match self.inner_resume(ResumeAction::Step, || gdb_interrupt.pending())? {
+ StopReason::DoneStep => {}
+ stop_reason => return Ok(stop_reason),
+ }
- Ok(true)
+ if !(start..end).contains(&self.cpu.reg_get(self.cpu.mode(), reg::PC)) {
+ return Ok(StopReason::DoneStep);
+ }
+ }
}
}
diff --git a/examples/armv4t/main.rs b/examples/armv4t/main.rs
index 58d6f7b..4364819 100644
--- a/examples/armv4t/main.rs
+++ b/examples/armv4t/main.rs
@@ -71,7 +71,10 @@ fn main() -> DynResult<()> {
// run to completion
while emu.step() != Some(emu::Event::Halted) {}
}
- DisconnectReason::TargetHalted => println!("Target halted!"),
+ DisconnectReason::TargetExited(code) => println!("Target exited with code {}!", code),
+ DisconnectReason::TargetTerminated(sig) => {
+ println!("Target terminated with signal {}!", sig)
+ }
DisconnectReason::Kill => {
println!("GDB sent a kill command!");
return Ok(());
diff --git a/examples/armv4t_multicore/emu.rs b/examples/armv4t_multicore/emu.rs
index 95bd057..f0570fa 100644
--- a/examples/armv4t_multicore/emu.rs
+++ b/examples/armv4t_multicore/emu.rs
@@ -38,6 +38,9 @@ pub struct Emu {
pub(crate) cop: Cpu,
pub(crate) mem: ExampleMem,
+ // FIXME: properly handle multiple actions
+ pub(crate) resume_action_is_step: Option<bool>,
+
pub(crate) watchpoints: Vec<u32>,
/// (read, write)
pub(crate) watchpoint_kind: HashMap<u32, (bool, bool)>,
@@ -88,9 +91,13 @@ impl Emu {
cpu,
cop,
mem,
+
+ resume_action_is_step: None,
+
watchpoints: Vec::new(),
watchpoint_kind: HashMap::new(),
breakpoints: Vec::new(),
+
stall_cop_cycles: 24,
})
}
diff --git a/examples/armv4t_multicore/gdb.rs b/examples/armv4t_multicore/gdb.rs
index 47683e2..0c8f6dc 100644
--- a/examples/armv4t_multicore/gdb.rs
+++ b/examples/armv4t_multicore/gdb.rs
@@ -1,10 +1,9 @@
use armv4t_emu::{reg, Memory};
-use gdbstub::arch;
use gdbstub::common::Tid;
use gdbstub::target;
use gdbstub::target::ext::base::multithread::{
- Actions, MultiThreadOps, ResumeAction, ThreadStopReason,
+ GdbInterrupt, MultiThreadOps, ResumeAction, ThreadStopReason,
};
use gdbstub::target::ext::breakpoints::WatchKind;
use gdbstub::target::{Target, TargetError, TargetResult};
@@ -14,7 +13,7 @@ use crate::emu::{CpuId, Emu, Event};
fn event_to_stopreason(e: Event, id: CpuId) -> ThreadStopReason<u32> {
let tid = cpuid_to_tid(id);
match e {
- Event::Halted => ThreadStopReason::Halted,
+ Event::Halted => ThreadStopReason::Terminated(19), // SIGSTOP
Event::Break => ThreadStopReason::SwBreak(tid),
Event::WatchWrite(addr) => ThreadStopReason::Watch {
tid,
@@ -45,18 +44,16 @@ fn tid_to_cpuid(tid: Tid) -> Result<CpuId, &'static str> {
}
impl Target for Emu {
- type Arch = arch::arm::Armv4t;
+ type Arch = gdbstub_arch::arm::Armv4t;
type Error = &'static str;
+ #[inline(always)]
fn base_ops(&mut self) -> target::ext::base::BaseOps<Self::Arch, Self::Error> {
target::ext::base::BaseOps::MultiThread(self)
}
- fn sw_breakpoint(&mut self) -> Option<target::ext::breakpoints::SwBreakpointOps<Self>> {
- Some(self)
- }
-
- fn hw_watchpoint(&mut self) -> Option<target::ext::breakpoints::HwWatchpointOps<Self>> {
+ #[inline(always)]
+ fn breakpoints(&mut self) -> Option<target::ext::breakpoints::BreakpointsOps<Self>> {
Some(self)
}
}
@@ -64,32 +61,34 @@ impl Target for Emu {
impl MultiThreadOps for Emu {
fn resume(
&mut self,
- actions: Actions,
- check_gdb_interrupt: &mut dyn FnMut() -> bool,
+ default_resume_action: ResumeAction,
+ gdb_interrupt: GdbInterrupt<'_>,
) -> Result<ThreadStopReason<u32>, Self::Error> {
- // in this emulator, each core runs in lock-step, so we can ignore the
- // TidSelector associated with each action, and only care if GDB
- // requests execution to start / stop.
- //
// In general, the behavior of multi-threaded systems during debugging is
// determined by the system scheduler. On certain systems, this behavior can be
// configured using the GDB command `set scheduler-locking _mode_`, but at the
// moment, `gdbstub` doesn't plumb-through that configuration command.
- // FIXME: properly handle multiple actions...
- let actions = actions.collect::<Vec<_>>();
- let (_, action) = actions[0];
+ let default_resume_action_is_step = match default_resume_action {
+ ResumeAction::Step => true,
+ ResumeAction::Continue => false,
+ _ => return Err("no support for resuming with signal"),
+ };
- match action {
- ResumeAction::Step => match self.step() {
+ match self
+ .resume_action_is_step
+ .unwrap_or(default_resume_action_is_step)
+ {
+ true => match self.step() {
Some((event, id)) => Ok(event_to_stopreason(event, id)),
None => Ok(ThreadStopReason::DoneStep),
},
- ResumeAction::Continue => {
+ false => {
+ let mut gdb_interrupt = gdb_interrupt.no_async();
let mut cycles: usize = 0;
loop {
// check for GDB interrupt every 1024 instructions
- if cycles % 1024 == 0 && check_gdb_interrupt() {
+ if cycles % 1024 == 0 && gdb_interrupt.pending() {
return Ok(ThreadStopReason::GdbInterrupt);
}
cycles += 1;
@@ -102,9 +101,33 @@ impl MultiThreadOps for Emu {
}
}
+ // FIXME: properly handle multiple actions
+ fn clear_resume_actions(&mut self) -> Result<(), Self::Error> {
+ self.resume_action_is_step = None;
+ Ok(())
+ }
+
+ // FIXME: properly handle multiple actions
+ fn set_resume_action(&mut self, _tid: Tid, action: ResumeAction) -> Result<(), Self::Error> {
+ // in this emulator, each core runs in lock-step, so we don't actually care
+ // about the specific tid. In real integrations, you very much should!
+
+ if self.resume_action_is_step.is_some() {
+ return Ok(());
+ }
+
+ self.resume_action_is_step = match action {
+ ResumeAction::Step => Some(true),
+ ResumeAction::Continue => Some(false),
+ _ => return Err("no support for resuming with signal"),
+ };
+
+ Ok(())
+ }
+
fn read_registers(
&mut self,
- regs: &mut arch::arm::reg::ArmCoreRegs,
+ regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs,
tid: Tid,
) -> TargetResult<(), Self> {
let cpu = match tid_to_cpuid(tid).map_err(TargetError::Fatal)? {
@@ -127,7 +150,7 @@ impl MultiThreadOps for Emu {
fn write_registers(
&mut self,
- regs: &arch::arm::reg::ArmCoreRegs,
+ regs: &gdbstub_arch::arm::reg::ArmCoreRegs,
tid: Tid,
) -> TargetResult<(), Self> {
let cpu = match tid_to_cpuid(tid).map_err(TargetError::Fatal)? {
@@ -182,13 +205,31 @@ impl MultiThreadOps for Emu {
}
}
+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)
+ }
+}
+
impl target::ext::breakpoints::SwBreakpoint for Emu {
- fn add_sw_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> {
+ fn add_sw_breakpoint(
+ &mut self,
+ addr: u32,
+ _kind: gdbstub_arch::arm::ArmBreakpointKind,
+ ) -> TargetResult<bool, Self> {
self.breakpoints.push(addr);
Ok(true)
}
- fn remove_sw_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> {
+ fn remove_sw_breakpoint(
+ &mut self,
+ addr: u32,
+ _kind: gdbstub_arch::arm::ArmBreakpointKind,
+ ) -> TargetResult<bool, Self> {
match self.breakpoints.iter().position(|x| *x == addr) {
None => return Ok(false),
Some(pos) => self.breakpoints.remove(pos),
diff --git a/examples/armv4t_multicore/main.rs b/examples/armv4t_multicore/main.rs
index 3862513..f51c018 100644
--- a/examples/armv4t_multicore/main.rs
+++ b/examples/armv4t_multicore/main.rs
@@ -71,7 +71,10 @@ fn main() -> DynResult<()> {
// run to completion
while emu.step() != Some((emu::Event::Halted, emu::CpuId::Cpu)) {}
}
- DisconnectReason::TargetHalted => println!("Target halted!"),
+ DisconnectReason::TargetExited(code) => println!("Target exited with code {}!", code),
+ DisconnectReason::TargetTerminated(sig) => {
+ println!("Target terminated with signal {}!", sig)
+ }
DisconnectReason::Kill => {
println!("GDB sent a kill command!");
return Ok(());
diff --git a/src/arch.rs b/src/arch.rs
new file mode 100644
index 0000000..dbb21fa
--- /dev/null
+++ b/src/arch.rs
@@ -0,0 +1,153 @@
+//! Traits to encode architecture-specific target information.
+//!
+//! # Community created `Arch` Implementations
+//!
+//! Before getting your hands dirty and implementing a new `Arch` from scratch,
+//! make sure to check out [`gdbstub_arch`](https://docs.rs/gdbstub_arch), a
+//! companion crate to `gdbstub` which aggregates community-created `Arch`
+//! implementations for most common architectures!
+//!
+//! > _Note:_ Prior to `gdbstub 0.5`, `Arch` implementations were distributed as
+//! a part of the main `gdbstub` crate (under the `gdbstub::arch` module). This
+//! wasn't ideal, any `gdbstub::arch`-level breaking-changes forced the _entire_
+//! `gdbstub` crate to release a new (potentially breaking!) version.
+//!
+//! > Having community-created `Arch` implementations distributed in a separate
+//! crate helps minimize any unnecessary "version churn" in `gdbstub` core.
+
+use core::fmt::Debug;
+
+use num_traits::{FromPrimitive, PrimInt, Unsigned};
+
+use crate::internal::{BeBytes, LeBytes};
+
+/// Register identifier for target registers.
+///
+/// These identifiers are used by GDB to signal which register to read/wite when
+/// performing [single register accesses].
+///
+/// [single register accesses]: crate::target::ext::base::SingleRegisterAccess
+pub trait RegId: Sized + Debug {
+ /// Map raw GDB register number corresponding `RegId` and register size.
+ ///
+ /// Returns `None` if the register is not available.
+ fn from_raw_id(id: usize) -> Option<(Self, usize)>;
+}
+
+/// Stub implementation -- Returns `None` for all raw IDs.
+impl RegId for () {
+ fn from_raw_id(_id: usize) -> Option<(Self, usize)> {
+ None
+ }
+}
+
+/// Methods to read/write architecture-specific registers.
+///
+/// Registers must be de/serialized in the order specified by the architecture's
+/// `<target>.xml` in the GDB source tree.
+///
+/// e.g: for ARM:
+/// github.com/bminor/binutils-gdb/blob/master/gdb/features/arm/arm-core.xml
+// TODO: add way to de/serialize arbitrary "missing"/"uncollected" registers.
+pub trait Registers: Default + Debug + Clone + PartialEq {
+ /// The type of the architecture's program counter / instruction pointer.
+ /// Must match with the corresponding `Arch::Usize`.
+ type ProgramCounter: Copy;
+
+ /// Return the value of the program counter / instruction pointer.
+ fn pc(&self) -> Self::ProgramCounter;
+
+ /// Serialize `self` into a GDB register bytestream.
+ ///
+ /// Missing registers are serialized by passing `None` to write_byte.
+ fn gdb_serialize(&self, write_byte: impl FnMut(Option<u8>));
+
+ /// Deserialize a GDB register bytestream into `self`.
+ #[allow(clippy::result_unit_err)]
+ fn gdb_deserialize(&mut self, bytes: &[u8]) -> Result<(), ()>;
+}
+
+/// Breakpoint kind for specific architectures.
+///
+/// This trait corresponds to the _kind_ field of the "z" and "Z" breakpoint
+/// packets, as documented [here](https://sourceware.org/gdb/onlinedocs/gdb/Packets.html#insert-breakpoint-or-watchpoint-packet).
+///
+/// A breakpoint "kind" is architecture-specific and typically indicates the
+/// size of the breakpoint in bytes that should be inserted. As such, most
+/// architectures will set `BreakpointKind = usize`.
+///
+/// Some architectures, such as ARM and MIPS, have additional meanings for
+/// _kind_. See the [Architecture-Specific Protocol Details](https://sourceware.org/gdb/current/onlinedocs/gdb/Architecture_002dSpecific-Protocol-Details.html#Architecture_002dSpecific-Protocol-Details)
+/// section of the GBD documentation for more details.
+///
+/// If no architecture-specific value is being used, _kind_ should be set to
+/// '0', and the `BreakpointKind` associated type should be `()`.
+pub trait BreakpointKind: Sized + Debug {
+ /// Parse `Self` from a raw usize.
+ fn from_usize(kind: usize) -> Option<Self>;
+}
+
+impl BreakpointKind for () {
+ fn from_usize(kind: usize) -> Option<Self> {
+ if kind != 0 {
+ None
+ } else {
+ Some(())
+ }
+ }
+}
+
+impl BreakpointKind for usize {
+ #[allow(clippy::wrong_self_convention)]
+ fn from_usize(kind: usize) -> Option<Self> {
+ Some(kind)
+ }
+}
+
+/// Encodes architecture-specific information, such as pointer size, register
+/// layout, etc...
+///
+/// Types implementing `Arch` should be
+/// [Zero-variant Enums](https://doc.rust-lang.org/reference/items/enumerations.html#zero-variant-enums),
+/// as `Arch` impls are only ever used at the type level, and should never be
+/// explicitly instantiated.
+pub trait Arch {
+ /// The architecture's pointer size (e.g: `u32` on a 32-bit system).
+ type Usize: FromPrimitive + PrimInt + Unsigned + BeBytes + LeBytes;
+
+ /// The architecture's register file. See [`Registers`] for more details.
+ type Registers: Registers<ProgramCounter = Self::Usize>;
+
+ /// The architecture's breakpoint "kind", used to determine the "size"
+ /// of breakpoint to set. See [`BreakpointKind`] for more details.
+ type BreakpointKind: BreakpointKind;
+
+ /// Register identifier enum/struct.
+ ///
+ /// Used to access individual registers via `Target::read/write_register`.
+ ///
+ /// > NOTE: An arch's `RegId` type is not strictly required to have a 1:1
+ /// correspondence with the `Registers` type, and may include register
+ /// identifiers which are separate from the main `Registers` structure.
+ /// (e.g: the RISC-V Control and Status registers)
+ type RegId: RegId;
+
+ /// (optional) Return the arch's description XML file (`target.xml`).
+ ///
+ /// Implementing this method enables GDB to automatically detect the
+ /// target's architecture, saving the hassle of having to run `set
+ /// architecture <arch>` when starting a debugging session.
+ ///
+ /// These descriptions can be quite succinct. For example, the target
+ /// description for an `armv4t` target can be as simple as:
+ ///
+ /// ```
+ /// r#"<target version="1.0"><architecture>armv4t</architecture></target>"#;
+ /// ```
+ ///
+ /// See the [GDB docs](https://sourceware.org/gdb/current/onlinedocs/gdb/Target-Description-Format.html)
+ /// for details on the target description XML format.
+ fn target_description_xml() -> Option<&'static str> {
+ None
+ }
+}
diff --git a/src/arch/arm/mod.rs b/src/arch/arm/mod.rs
deleted file mode 100644
index eb26fbb..0000000
--- a/src/arch/arm/mod.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-//! Implementations for various ARM architectures.
-
-use crate::arch::Arch;
-
-pub mod reg;
-
-/// Implements `Arch` for ARMv4T
-pub enum Armv4t {}
-
-impl Arch for Armv4t {
- type Usize = u32;
- type Registers = reg::ArmCoreRegs;
- type RegId = reg::id::ArmCoreRegId;
-
- fn target_description_xml() -> Option<&'static str> {
- Some(r#"<target version="1.0"><architecture>armv4t</architecture></target>"#)
- }
-}
diff --git a/src/arch/arm/reg/arm_core.rs b/src/arch/arm/reg/arm_core.rs
deleted file mode 100644
index dba5f1d..0000000
--- a/src/arch/arm/reg/arm_core.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-use crate::arch::Registers;
-
-/// 32-bit ARM core registers.
-///
-/// Source: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/arm/arm-core.xml
-#[derive(Debug, Default, Clone, Eq, PartialEq)]
-pub struct ArmCoreRegs {
- /// General purpose registers (R0-R12)
- pub r: [u32; 13],
- /// Stack Pointer (R13)
- pub sp: u32,
- /// Link Register (R14)
- pub lr: u32,
- /// Program Counter (R15)
- pub pc: u32,
- /// Current Program Status Register (cpsr)
- pub cpsr: u32,
-}
-
-impl Registers for ArmCoreRegs {
- fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
- macro_rules! write_bytes {
- ($bytes:expr) => {
- for b in $bytes {
- write_byte(Some(*b))
- }
- };
- }
-
- for reg in self.r.iter() {
- write_bytes!(&reg.to_le_bytes());
- }
- write_bytes!(&self.sp.to_le_bytes());
- write_bytes!(&self.lr.to_le_bytes());
- write_bytes!(&self.pc.to_le_bytes());
-
- // Floating point registers (unused)
- for _ in 0..25 {
- (0..4).for_each(|_| write_byte(None))
- }
-
- write_bytes!(&self.cpsr.to_le_bytes());
- }
-
- fn gdb_deserialize(&mut self, bytes: &[u8]) -> Result<(), ()> {
- // ensure bytes.chunks_exact(4) won't panic
- if bytes.len() % 4 != 0 {
- return Err(());
- }
-
- use core::convert::TryInto;
- let mut regs = bytes
- .chunks_exact(4)
- .map(|c| u32::from_le_bytes(c.try_into().unwrap()));
-
- for reg in self.r.iter_mut() {
- *reg = regs.next().ok_or(())?
- }
- self.sp = regs.next().ok_or(())?;
- self.lr = regs.next().ok_or(())?;
- self.pc = regs.next().ok_or(())?;
-
- // Floating point registers (unused)
- for _ in 0..25 {
- regs.next().ok_or(())?;
- }
-
- self.cpsr = regs.next().ok_or(())?;
-
- if regs.next().is_some() {
- return Err(());
- }
-
- Ok(())
- }
-}
diff --git a/src/arch/arm/reg/id.rs b/src/arch/arm/reg/id.rs
deleted file mode 100644
index f22de27..0000000
--- a/src/arch/arm/reg/id.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-use crate::arch::RegId;
-
-/// 32-bit ARM core register identifier.
-#[derive(Debug, Clone, Copy)]
-#[non_exhaustive]
-pub enum ArmCoreRegId {
- /// General purpose registers (R0-R12)
- Gpr(u8),
- /// Stack Pointer (R13)
- Sp,
- /// Link Register (R14)
- Lr,
- /// Program Counter (R15)
- Pc,
- /// Floating point registers (F0-F7)
- Fpr(u8),
- /// Floating point status
- Fps,
- /// Current Program Status Register (cpsr)
- Cpsr,
-}
-
-impl RegId for ArmCoreRegId {
- fn from_raw_id(id: usize) -> Option<(Self, usize)> {
- let reg = match id {
- 0..=12 => Self::Gpr(id as u8),
- 13 => Self::Sp,
- 14 => Self::Lr,
- 15 => Self::Pc,
- 16..=23 => Self::Fpr((id as u8) - 16),
- 25 => Self::Cpsr,
- _ => return None,
- };
- Some((reg, 4))
- }
-}
diff --git a/src/arch/arm/reg/mod.rs b/src/arch/arm/reg/mod.rs
deleted file mode 100644
index 8f5a8be..0000000
--- a/src/arch/arm/reg/mod.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-//! `Register` structs for various ARM architectures.
-
-/// `RegId` definitions for ARM architectures.
-pub mod id;
-
-mod arm_core;
-
-pub use arm_core::ArmCoreRegs;
diff --git a/src/arch/mips/mod.rs b/src/arch/mips/mod.rs
deleted file mode 100644
index 2bd8362..0000000
--- a/src/arch/mips/mod.rs
+++ /dev/null
@@ -1,74 +0,0 @@
-//! Implementations for the MIPS architecture.
-
-use crate::arch::Arch;
-use crate::arch::RegId;
-
-pub mod reg;
-
-/// Implements `Arch` for 32-bit MIPS.
-///
-/// Check out the [module level docs](../index.html#whats-with-regidimpl) for
-/// more info about the `RegIdImpl` type parameter.
-pub enum Mips<RegIdImpl: RegId = reg::id::MipsRegId<u32>> {
- #[doc(hidden)]
- _Marker(core::marker::PhantomData<RegIdImpl>),
-}
-
-/// Implements `Arch` for 64-bit MIPS.
-///
-/// Check out the [module level docs](../index.html#whats-with-regidimpl) for
-/// more info about the `RegIdImpl` type parameter.
-pub enum Mips64<RegIdImpl: RegId = reg::id::MipsRegId<u64>> {
- #[doc(hidden)]
- _Marker(core::marker::PhantomData<RegIdImpl>),
-}
-
-/// Implements `Arch` for 32-bit MIPS with the DSP feature enabled.
-pub enum MipsWithDsp {}
-
-/// Implements `Arch` for 64-bit MIPS with the DSP feature enabled.
-pub enum Mips64WithDsp {}
-
-impl<RegIdImpl: RegId> Arch for Mips<RegIdImpl> {
- type Usize = u32;
- type Registers = reg::MipsCoreRegs<u32>;
- type RegId = RegIdImpl;
-
- fn target_description_xml() -> Option<&'static str> {
- Some(r#"<target version="1.0"><architecture>mips</architecture></target>"#)
- }
-}
-
-impl<RegIdImpl: RegId> Arch for Mips64<RegIdImpl> {
- type Usize = u64;
- type Registers = reg::MipsCoreRegs<u64>;
- type RegId = RegIdImpl;
-
- fn target_description_xml() -> Option<&'static str> {
- Some(r#"<target version="1.0"><architecture>mips64</architecture></target>"#)
- }
-}
-
-impl Arch for MipsWithDsp {
- type Usize = u32;
- type Registers = reg::MipsCoreRegsWithDsp<u32>;
- type RegId = reg::id::MipsRegId<u32>;
-
- fn target_description_xml() -> Option<&'static str> {
- Some(
- r#"<target version="1.0"><architecture>mips</architecture><feature name="org.gnu.gdb.mips.dsp"></feature></target>"#,
- )
- }
-}
-
-impl Arch for Mips64WithDsp {
- type Usize = u64;
- type Registers = reg::MipsCoreRegsWithDsp<u64>;
- type RegId = reg::id::MipsRegId<u64>;
-
- fn target_description_xml() -> Option<&'static str> {
- Some(
- r#"<target version="1.0"><architecture>mips64</architecture><feature name="org.gnu.gdb.mips.dsp"></feature></target>"#,
- )
- }
-}
diff --git a/src/arch/mips/reg/id.rs b/src/arch/mips/reg/id.rs
deleted file mode 100644
index 424cb29..0000000
--- a/src/arch/mips/reg/id.rs
+++ /dev/null
@@ -1,129 +0,0 @@
-use crate::arch::RegId;
-
-/// MIPS register identifier.
-#[derive(Debug, Clone, Copy)]
-#[non_exhaustive]
-pub enum MipsRegId<U> {
- /// General purpose registers (R0-R31)
- Gpr(u8),
- /// Status register
- Status,
- /// Low register
- Lo,
- /// High register
- Hi,
- /// Bad Virtual Address register
- Badvaddr,
- /// Exception Cause register
- Cause,
- /// Program Counter
- Pc,
- /// Floating point registers (F0-F31)
- Fpr(u8),
- /// Floating-point Control Status register
- Fcsr,
- /// Floating-point Implementation Register
- Fir,
- /// High 1 register
- Hi1,
- /// Low 1 register
- Lo1,
- /// High 2 register
- Hi2,
- /// Low 2 register
- Lo2,
- /// High 3 register
- Hi3,
- /// Low 3 register
- Lo3,
- /// DSP Control register
- Dspctl,
- /// Restart register
- Restart,
- #[doc(hidden)]
- _Size(U),
-}
-
-fn from_raw_id<U>(id: usize) -> Option<(MipsRegId<U>, usize)> {
- let reg = match id {
- 0..=31 => MipsRegId::Gpr(id as u8),
- 32 => MipsRegId::Status,
- 33 => MipsRegId::Lo,
- 34 => MipsRegId::Hi,
- 35 => MipsRegId::Badvaddr,
- 36 => MipsRegId::Cause,
- 37 => MipsRegId::Pc,
- 38..=69 => MipsRegId::Fpr((id as u8) - 38),
- 70 => MipsRegId::Fcsr,
- 71 => MipsRegId::Fir,
- 72 => MipsRegId::Hi1,
- 73 => MipsRegId::Lo1,
- 74 => MipsRegId::Hi2,
- 75 => MipsRegId::Lo2,
- 76 => MipsRegId::Hi3,
- 77 => MipsRegId::Lo3,
- // `MipsRegId::Dspctl` is the only register that will always be 4 bytes wide
- 78 => return Some((MipsRegId::Dspctl, 4)),
- 79 => MipsRegId::Restart,
- _ => return None,
- };
-
- let ptrsize = core::mem::size_of::<U>();
- Some((reg, ptrsize))
-}
-
-impl RegId for MipsRegId<u32> {
- fn from_raw_id(id: usize) -> Option<(Self, usize)> {
- from_raw_id::<u32>(id)
- }
-}
-
-impl RegId for MipsRegId<u64> {
- fn from_raw_id(id: usize) -> Option<(Self, usize)> {
- from_raw_id::<u64>(id)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::arch::traits::RegId;
- use crate::arch::traits::Registers;
-
- fn test<Rs: Registers, RId: RegId>() {
- // Obtain the data length written by `gdb_serialize` by passing a custom
- // closure.
- let mut serialized_data_len = 0;
- let counter = |b: Option<u8>| {
- if b.is_some() {
- serialized_data_len += 1;
- }
- };
- Rs::default().gdb_serialize(counter);
-
- // Accumulate register sizes returned by `from_raw_id`.
- let mut i = 0;
- let mut sum_reg_sizes = 0;
- while let Some((_, size)) = RId::from_raw_id(i) {
- sum_reg_sizes += size;
- i += 1;
- }
-
- assert_eq!(serialized_data_len, sum_reg_sizes);
- }
-
- #[test]
- fn test_mips32() {
- test::<
- crate::arch::mips::reg::MipsCoreRegsWithDsp<u32>,
- crate::arch::mips::reg::id::MipsRegId<u32>,
- >()
- }
-
- #[test]
- fn test_mips64() {
- test::<
- crate::arch::mips::reg::MipsCoreRegsWithDsp<u64>,
- crate::arch::mips::reg::id::MipsRegId<u64>,
- >()
- }
-}
diff --git a/src/arch/mips/reg/mips.rs b/src/arch/mips/reg/mips.rs
deleted file mode 100644
index 6d86b43..0000000
--- a/src/arch/mips/reg/mips.rs
+++ /dev/null
@@ -1,259 +0,0 @@
-use core::convert::TryInto;
-
-use num_traits::PrimInt;
-
-use crate::arch::Registers;
-use crate::internal::LeBytes;
-
-/// MIPS registers.
-///
-/// The register width is set to `u32` or `u64` based on the `<U>` type.
-///
-/// Source: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/mips-cpu.xml
-#[derive(Debug, Default, Clone, Eq, PartialEq)]
-pub struct MipsCoreRegs<U> {
- /// General purpose registers (R0-R31)
- pub r: [U; 32],
- /// Low register (regnum 33)
- pub lo: U,
- /// High register (regnum 34)
- pub hi: U,
- /// Program Counter (regnum 37)
- pub pc: U,
- /// CP0 registers
- pub cp0: MipsCp0Regs<U>,
- /// FPU registers
- pub fpu: MipsFpuRegs<U>,
-}
-
-/// MIPS CP0 (coprocessor 0) registers.
-///
-/// Source: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/mips-cp0.xml
-#[derive(Debug, Default, Clone, Eq, PartialEq)]
-pub struct MipsCp0Regs<U> {
- /// Status register (regnum 32)
- pub status: U,
- /// Bad Virtual Address register (regnum 35)
- pub badvaddr: U,
- /// Exception Cause register (regnum 36)
- pub cause: U,
-}
-
-/// MIPS FPU registers.
-///
-/// Source: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/mips-fpu.xml
-#[derive(Debug, Default, Clone, Eq, PartialEq)]
-pub struct MipsFpuRegs<U> {
- /// FP registers (F0-F31) starting at regnum 38
- pub r: [U; 32],
- /// Floating-point Control Status register
- pub fcsr: U,
- /// Floating-point Implementation Register
- pub fir: U,
-}
-
-/// MIPS DSP registers.
-///
-/// Source: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/mips-dsp.xml
-#[derive(Debug, Default, Clone, Eq, PartialEq)]
-pub struct MipsDspRegs<U> {
- /// High 1 register (regnum 72)
- pub hi1: U,
- /// Low 1 register (regnum 73)
- pub lo1: U,
- /// High 2 register (regnum 74)
- pub hi2: U,
- /// Low 2 register (regnum 75)
- pub lo2: U,
- /// High 3 register (regnum 76)
- pub hi3: U,
- /// Low 3 register (regnum 77)
- pub lo3: U,
- /// DSP Control register (regnum 78)
- /// Note: This register will always be 32-bit regardless of the target
- /// https://sourceware.org/gdb/current/onlinedocs/gdb/MIPS-Features.html#MIPS-Features
- pub dspctl: u32,
- /// Restart register (regnum 79)
- pub restart: U,
-}
-
-/// MIPS core and DSP registers.
-///
-/// Source: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/mips-dsp-linux.xml
-#[derive(Debug, Default, Clone, Eq, PartialEq)]
-pub struct MipsCoreRegsWithDsp<U> {
- /// Core registers
- pub core: MipsCoreRegs<U>,
- /// DSP registers
- pub dsp: MipsDspRegs<U>,
-}
-
-impl<U> Registers for MipsCoreRegs<U>
-where
- U: PrimInt + LeBytes + Default + core::fmt::Debug,
-{
- fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
- macro_rules! write_le_bytes {
- ($value:expr) => {
- let mut buf = [0; 16];
- // infallible (unless digit is a >128 bit number)
- let len = $value.to_le_bytes(&mut buf).unwrap();
- let buf = &buf[..len];
- for b in buf {
- write_byte(Some(*b));
- }
- };
- }
-
- // Write GPRs
- for reg in self.r.iter() {
- write_le_bytes!(reg);
- }
-
- // Status register is regnum 32
- write_le_bytes!(&self.cp0.status);
-
- // Low and high registers are regnums 33 and 34
- write_le_bytes!(&self.lo);
- write_le_bytes!(&self.hi);
-
- // Badvaddr and Cause registers are regnums 35 and 36
- write_le_bytes!(&self.cp0.badvaddr);
- write_le_bytes!(&self.cp0.cause);
-
- // Program Counter is regnum 37
- write_le_bytes!(&self.pc);
-
- // Write FPRs
- for reg in self.fpu.r.iter() {
- write_le_bytes!(&reg);
- }
-
- // Write FCSR and FIR registers
- write_le_bytes!(&self.fpu.fcsr);
- write_le_bytes!(&self.fpu.fir);
- }
-
- fn gdb_deserialize(&mut self, bytes: &[u8]) -> Result<(), ()> {
- let ptrsize = core::mem::size_of::<U>();
-
- // Ensure bytes contains enough data for all 72 registers
- if bytes.len() < ptrsize * 72 {
- return Err(());
- }
-
- // All core registers are the same size
- let mut regs = bytes
- .chunks_exact(ptrsize)
- .map(|c| U::from_le_bytes(c).unwrap());
-
- // Read GPRs
- for reg in self.r.iter_mut() {
- *reg = regs.next().ok_or(())?
- }
-
- // Read Status register
- self.cp0.status = regs.next().ok_or(())?;
-
- // Read Low and High registers
- self.lo = regs.next().ok_or(())?;
- self.hi = regs.next().ok_or(())?;
-
- // Read Badvaddr and Cause registers
- self.cp0.badvaddr = regs.next().ok_or(())?;
- self.cp0.cause = regs.next().ok_or(())?;
-
- // Read the Program Counter
- self.pc = regs.next().ok_or(())?;
-
- // Read FPRs
- for reg in self.fpu.r.iter_mut() {
- *reg = regs.next().ok_or(())?
- }
-
- // Read FCSR and FIR registers
- self.fpu.fcsr = regs.next().ok_or(())?;
- self.fpu.fir = regs.next().ok_or(())?;
-
- Ok(())
- }
-}
-
-impl<U> Registers for MipsCoreRegsWithDsp<U>
-where
- U: PrimInt + LeBytes + Default + core::fmt::Debug,
-{
- fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
- macro_rules! write_le_bytes {
- ($value:expr) => {
- let mut buf = [0; 16];
- // infallible (unless digit is a >128 bit number)
- let len = $value.to_le_bytes(&mut buf).unwrap();
- let buf = &buf[..len];
- for b in buf {
- write_byte(Some(*b));
- }
- };
- }
-
- // Serialize the core registers first
- self.core.gdb_serialize(&mut write_byte);
-
- // Write the DSP registers
- write_le_bytes!(&self.dsp.hi1);
- write_le_bytes!(&self.dsp.lo1);
- write_le_bytes!(&self.dsp.hi2);
- write_le_bytes!(&self.dsp.lo2);
- write_le_bytes!(&self.dsp.hi3);
- write_le_bytes!(&self.dsp.lo3);
-
- for b in &self.dsp.dspctl.to_le_bytes() {
- write_byte(Some(*b));
- }
-
- write_le_bytes!(&self.dsp.restart);
- }
-
- fn gdb_deserialize(&mut self, bytes: &[u8]) -> Result<(), ()> {
- // Deserialize the core registers first
- self.core.gdb_deserialize(bytes)?;
-
- // Ensure bytes contains enough data for all 79 registers of target-width
- // and the dspctl register which is always 4 bytes
- let ptrsize = core::mem::size_of::<U>();
- if bytes.len() < (ptrsize * 79) + 4 {
- return Err(());
- }
-
- // Calculate the offsets to the DSP registers based on the ptrsize
- let dspregs_start = ptrsize * 72;
- let dspctl_start = ptrsize * 78;
-
- // Read up until the dspctl register
- let mut regs = bytes[dspregs_start..dspctl_start]
- .chunks_exact(ptrsize)
- .map(|c| U::from_le_bytes(c).unwrap());
-
- self.dsp.hi1 = regs.next().ok_or(())?;
- self.dsp.lo1 = regs.next().ok_or(())?;
- self.dsp.hi2 = regs.next().ok_or(())?;
- self.dsp.lo2 = regs.next().ok_or(())?;
- self.dsp.hi3 = regs.next().ok_or(())?;
- self.dsp.lo3 = regs.next().ok_or(())?;
-
- // Dspctl will always be a u32
- self.dsp.dspctl =
- u32::from_le_bytes(bytes[dspctl_start..dspctl_start + 4].try_into().unwrap());
-
- // Only 4 or 8 bytes should remain to be read
- self.dsp.restart = U::from_le_bytes(
- bytes[dspctl_start + 4..]
- .chunks_exact(ptrsize)
- .next()
- .ok_or(())?,
- )
- .unwrap();
-
- Ok(())
- }
-}
diff --git a/src/arch/mips/reg/mod.rs b/src/arch/mips/reg/mod.rs
deleted file mode 100644
index 3cafbd1..0000000
--- a/src/arch/mips/reg/mod.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-//! `Register` structs for MIPS architectures.
-
-/// `RegId` definitions for MIPS architectures.
-pub mod id;
-
-mod mips;
-
-pub use mips::MipsCoreRegs;
-pub use mips::MipsCoreRegsWithDsp;
-pub use mips::MipsCp0Regs;
-pub use mips::MipsFpuRegs;
diff --git a/src/arch/mod.rs b/src/arch/mod.rs
deleted file mode 100644
index a37bcea..0000000
--- a/src/arch/mod.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-//! Built-in implementations of [`Arch`] for various architectures.
-//!
-//! _Note:_ If an architecture is missing from this module, that does _not_ mean
-//! that it can't be used with `gdbstub`! So-long as there's support for the
-//! target architecture in GDB, it should be fairly straightforward to implement
-//! `Arch` manually.
-//!
-//! Please consider upstreaming any missing `Arch` implementations you happen to
-//! implement yourself! Aside from the altruistic motive of improving `gdbstub`,
-//! upstreaming your `Arch` implementation will ensure that it's kept up-to-date
-//! with any future breaking API changes.
-//!
-//! **Disclaimer:** These implementations are all community contributions, and
-//! while they are tested (by the PR's author) and code-reviewed, it's not
-//! particularly feasible to write detailed tests for each architecture! If you
-//! spot a bug in any of the implementations, please file an issue / open a PR!
-//!
-//! # What's with `RegIdImpl`?
-//!
-//! Supporting the `Target::read/write_register` API required introducing a new
-//! [`RegId`] trait + [`Arch::RegId`] associated type. `RegId` is used by
-//! `gdbstub` to translate raw GDB register ids (a protocol level arch-dependent
-//! `usize`) into human-readable enum variants.
-//!
-//! Unfortunately, this API was added after several contributors had already
-//! upstreamed their `Arch` implementations, and as a result, there are several
-//! built-in arch implementations which are missing proper `RegId` enums
-//! (tracked under [issue #29](https://github.com/daniel5151/gdbstub/issues/29)).
-//!
-//! As a stop-gap measure, affected `Arch` implementations have been modified to
-//! accept a `RegIdImpl` type parameter, which requires users to manually
-//! specify a `RegId` implementation.
-//!
-//! If you're not interested in implementing the `Target::read/write_register`
-//! methods and just want to get up-and-running with `gdbstub`, it's fine to
-//! set `RegIdImpl` to `()` and use the built-in stubbed `impl RegId for ()`.
-//!
-//! A better approach would be to implement (and hopefully upstream!) a proper
-//! `RegId` enum. While this will require doing a bit of digging through the GDB
-//! docs + [architecture XML definitions](https://github.com/bminor/binutils-gdb/tree/master/gdb/features/),
-//! it's not too tricky to get a working implementation up and running, and
-//! makes it possible to safely and efficiently implement the
-//! `Target::read/write_register` API. As an example, check out
-//! [`ArmCoreRegId`](arm/reg/id/enum.ArmCoreRegId.html#impl-RegId).
-//!
-//! Whenever a `RegId` enum is upstreamed, the associated `Arch`'s `RegIdImpl`
-//! parameter will be defaulted to the newly added enum. This will simplify the
-//! API without requiring an explicit breaking API change. Once all `RegIdImpl`
-//! have a default implementation, only a single breaking API change will be
-//! required to remove `RegIdImpl` entirely (along with this documentation).
-
-pub mod arm;
-pub mod mips;
-pub mod msp430;
-pub mod ppc;
-pub mod riscv;
-pub mod x86;
-
-mod traits;
-pub use traits::*;
diff --git a/src/arch/msp430/mod.rs b/src/arch/msp430/mod.rs
deleted file mode 100644
index 9baf44d..0000000
--- a/src/arch/msp430/mod.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-//! Implementations for the TI-MSP430 family of MCUs.
-
-use crate::arch::Arch;
-use crate::arch::RegId;
-
-pub mod reg;
-
-/// Implements `Arch` for standard 16-bit TI-MSP430 MCUs.
-///
-/// Check out the [module level docs](../index.html#whats-with-regidimpl) for
-/// more info about the `RegIdImpl` type parameter.
-pub enum Msp430<RegIdImpl: RegId = reg::id::Msp430RegId> {
- #[doc(hidden)]
- _Marker(core::marker::PhantomData<RegIdImpl>),
-}
-
-impl<RegIdImpl: RegId> Arch for Msp430<RegIdImpl> {
- type Usize = u32;
- type Registers = reg::Msp430Regs;
- type RegId = RegIdImpl;
-
- fn target_description_xml() -> Option<&'static str> {
- Some(r#"<target version="1.0"><architecture>msp430</architecture></target>"#)
- }
-}
diff --git a/src/arch/msp430/reg/id.rs b/src/arch/msp430/reg/id.rs
deleted file mode 100644
index eec7062..0000000
--- a/src/arch/msp430/reg/id.rs
+++ /dev/null
@@ -1,71 +0,0 @@
-use crate::arch::RegId;
-
-/// TI-MSP430 register identifier.
-///
-/// GDB does not provide a XML file for the MSP430.
-/// The best file to reference is [msp430-tdep.c](https://github.com/bminor/binutils-gdb/blob/master/gdb/msp430-tdep.c).
-#[derive(Debug, Clone, Copy)]
-#[non_exhaustive]
-pub enum Msp430RegId {
- /// Program Counter (R0)
- Pc,
- /// Stack Pointer (R1)
- Sp,
- /// Status Register (R2)
- Sr,
- /// Constant Generator (R3)
- Cg,
- /// General Purpose Registers (R4-R15)
- Gpr(u8),
-}
-
-impl RegId for Msp430RegId {
- fn from_raw_id(id: usize) -> Option<(Self, usize)> {
- let reg = match id {
- 0 => Self::Pc,
- 1 => Self::Sp,
- 2 => Self::Sr,
- 3 => Self::Cg,
- 4..=15 => Self::Gpr((id as u8) - 4),
- _ => return None,
- };
- Some((reg, 2))
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::arch::traits::RegId;
- use crate::arch::traits::Registers;
-
- fn test<Rs: Registers, RId: RegId>() {
- // Obtain the data length written by `gdb_serialize` by passing a custom
- // closure.
- let mut serialized_data_len = 0;
- let counter = |b: Option<u8>| {
- if b.is_some() {
- serialized_data_len += 1;
- }
- };
- Rs::default().gdb_serialize(counter);
-
- // The `Msp430Regs` implementation does not increment the size for
- // the CG register since it will always be the constant zero.
- serialized_data_len += 4;
-
- // Accumulate register sizes returned by `from_raw_id`.
- let mut i = 0;
- let mut sum_reg_sizes = 0;
- while let Some((_, size)) = RId::from_raw_id(i) {
- sum_reg_sizes += size;
- i += 1;
- }
-
- assert_eq!(serialized_data_len, sum_reg_sizes);
- }
-
- #[test]
- fn test_msp430() {
- test::<crate::arch::msp430::reg::Msp430Regs, crate::arch::msp430::reg::id::Msp430RegId>()
- }
-}
diff --git a/src/arch/msp430/reg/mod.rs b/src/arch/msp430/reg/mod.rs
deleted file mode 100644
index 2b1285b..0000000
--- a/src/arch/msp430/reg/mod.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-//! `Register` structs for various TI-MSP430 CPUs.
-
-/// `RegId` definitions for various TI-MSP430 CPUs.
-pub mod id;
-
-mod msp430;
-
-pub use msp430::Msp430Regs;
diff --git a/src/arch/msp430/reg/msp430.rs b/src/arch/msp430/reg/msp430.rs
deleted file mode 100644
index 456fb96..0000000
--- a/src/arch/msp430/reg/msp430.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-use crate::arch::Registers;
-
-/// 16-bit TI-MSP430 registers.
-#[derive(Debug, Default, Clone, Eq, PartialEq)]
-pub struct Msp430Regs {
- /// Program Counter (R0)
- pub pc: u16,
- /// Stack Pointer (R1)
- pub sp: u16,
- /// Status Register (R2)
- pub sr: u16,
- /// General Purpose Registers (R4-R15)
- pub r: [u16; 11],
-}
-
-impl Registers for Msp430Regs {
- fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
- macro_rules! write_bytes {
- ($bytes:expr) => {
- for b in $bytes {
- write_byte(Some(*b))
- }
- };
- }
-
- write_bytes!(&self.pc.to_le_bytes());
- write_bytes!(&self.sp.to_le_bytes());
- write_bytes!(&self.sr.to_le_bytes());
- (0..4).for_each(|_| write_byte(None)); // Constant Generator (CG/R3)
- for reg in self.r.iter() {
- write_bytes!(&reg.to_le_bytes());
- }
- }
-
- fn gdb_deserialize(&mut self, bytes: &[u8]) -> Result<(), ()> {
- // ensure bytes.chunks_exact(2) won't panic
- if bytes.len() % 2 != 0 {
- return Err(());
- }
-
- use core::convert::TryInto;
- let mut regs = bytes
- .chunks_exact(2)
- .map(|c| u16::from_le_bytes(c.try_into().unwrap()));
-
- self.pc = regs.next().ok_or(())?;
- self.sp = regs.next().ok_or(())?;
- self.sr = regs.next().ok_or(())?;
-
- // Constant Generator (CG/R3) should always be 0
- if regs.next().ok_or(())? != 0 {
- return Err(());
- }
-
- for reg in self.r.iter_mut() {
- *reg = regs.next().ok_or(())?
- }
-
- if regs.next().is_some() {
- return Err(());
- }
-
- Ok(())
- }
-}
diff --git a/src/arch/ppc/mod.rs b/src/arch/ppc/mod.rs
deleted file mode 100644
index bdb9465..0000000
--- a/src/arch/ppc/mod.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-//! Implementations for various PowerPC architectures.
-
-use crate::arch::Arch;
-use crate::arch::RegId;
-
-pub mod reg;
-
-/// Implements `Arch` for 32-bit PowerPC + AltiVec SIMD.
-///
-/// Check out the [module level docs](../index.html#whats-with-regidimpl) for
-/// more info about the `RegIdImpl` type parameter.
-pub enum PowerPcAltivec32<RegIdImpl: RegId> {
- #[doc(hidden)]
- _Marker(core::marker::PhantomData<RegIdImpl>),
-}
-
-impl<RegIdImpl: RegId> Arch for PowerPcAltivec32<RegIdImpl> {
- type Usize = u32;
- type Registers = reg::PowerPcCommonRegs;
- type RegId = RegIdImpl;
-
- fn target_description_xml() -> Option<&'static str> {
- Some(
- r#"<target version="1.0"><architecture>powerpc:common</architecture><feature name="org.gnu.gdb.power.core"></feature><feature name="org.gnu.gdb.power.fpu"></feature><feature name="org.gnu.gdb.power.altivec"></feature></target>"#,
- )
- }
-}
diff --git a/src/arch/ppc/reg/common.rs b/src/arch/ppc/reg/common.rs
deleted file mode 100644
index 8936e3a..0000000
--- a/src/arch/ppc/reg/common.rs
+++ /dev/null
@@ -1,159 +0,0 @@
-use crate::arch::ppc::reg::PpcVector;
-use crate::arch::Registers;
-
-use core::convert::TryInto;
-
-/// 32-bit PowerPC core registers, FPU registers, and AltiVec SIMD registers.
-///
-/// Sources:
-/// * https://github.com/bminor/binutils-gdb/blob/master/gdb/features/rs6000/powerpc-altivec32.xml
-/// * https://github.com/bminor/binutils-gdb/blob/master/gdb/features/rs6000/power-core.xml
-/// * https://github.com/bminor/binutils-gdb/blob/master/gdb/features/rs6000/power-fpu.xml
-/// * https://github.com/bminor/binutils-gdb/blob/master/gdb/features/rs6000/power-altivec.xml
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct PowerPcCommonRegs {
- /// General purpose registers
- pub r: [u32; 32],
- /// Floating Point registers
- pub f: [f64; 32],
- /// Program counter
- pub pc: u32,
- /// Machine state
- pub msr: u32,
- /// Condition register
- pub cr: u32,
- /// Link register
- pub lr: u32,
- /// Count register
- pub ctr: u32,
- /// Integer exception register
- pub xer: u32,
- /// Floating-point status and control register
- pub fpscr: u32,
- /// Vector registers
- pub vr: [PpcVector; 32],
- /// Vector status and control register
- pub vscr: u32,
- /// Vector context save register
- pub vrsave: u32,
-}
-
-impl Registers for PowerPcCommonRegs {
- fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
- macro_rules! write_bytes {
- ($bytes:expr) => {
- for b in $bytes {
- write_byte(Some(*b))
- }
- };
- }
-
- macro_rules! write_regs {
- ($($reg:ident),*) => {
- $(
- write_bytes!(&self.$reg.to_be_bytes());
- )*
- }
- }
-
- for reg in &self.r {
- write_bytes!(&reg.to_be_bytes());
- }
-
- for reg in &self.f {
- write_bytes!(&reg.to_be_bytes());
- }
-
- write_regs!(pc, msr, cr, lr, ctr, xer, fpscr);
-
- for &reg in &self.vr {
- let reg: u128 = reg;
- write_bytes!(&reg.to_be_bytes());
- }
-
- write_regs!(vscr, vrsave);
- }
-
- fn gdb_deserialize(&mut self, bytes: &[u8]) -> Result<(), ()> {
- if bytes.len() < 0x3a4 {
- return Err(());
- }
-
- let mut regs = bytes[0..0x80]
- .chunks_exact(4)
- .map(|x| u32::from_be_bytes(x.try_into().unwrap()));
-
- for reg in &mut self.r {
- *reg = regs.next().ok_or(())?;
- }
-
- let mut regs = bytes[0x80..0x180]
- .chunks_exact(8)
- .map(|x| f64::from_be_bytes(x.try_into().unwrap()));
-
- for reg in &mut self.f {
- *reg = regs.next().ok_or(())?;
- }
-
- macro_rules! parse_regs {
- ($start:literal..$end:literal, $($reg:ident),*) => {
- let mut regs = bytes[$start..$end]
- .chunks_exact(4)
- .map(|x| u32::from_be_bytes(x.try_into().unwrap()));
- $(
- self.$reg = regs.next().ok_or(())?;
- )*
- }
- }
-
- parse_regs!(0x180..0x19c, pc, msr, cr, lr, ctr, xer, fpscr);
-
- let mut regs = bytes[0x19c..0x39c]
- .chunks_exact(0x10)
- .map(|x| u128::from_be_bytes(x.try_into().unwrap()));
-
- for reg in &mut self.vr {
- *reg = regs.next().ok_or(())?;
- }
-
- parse_regs!(0x39c..0x3a4, vscr, vrsave);
-
- Ok(())
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn ppc_core_round_trip() {
- let regs_before = PowerPcCommonRegs {
- r: [1; 32],
- pc: 2,
- msr: 3,
- cr: 4,
- lr: 5,
- ctr: 6,
- xer: 7,
- fpscr: 8,
- f: [9.0; 32],
- vr: [52; 32],
- vrsave: 10,
- vscr: 11,
- };
-
- let mut data = vec![];
-
- regs_before.gdb_serialize(|x| {
- data.push(x.unwrap_or(b'x'));
- });
-
- assert_eq!(data.len(), 0x3a4);
-
- let mut regs_after = PowerPcCommonRegs::default();
- regs_after.gdb_deserialize(&data).unwrap();
-
- assert_eq!(regs_before, regs_after);
- }
-}
diff --git a/src/arch/ppc/reg/id.rs b/src/arch/ppc/reg/id.rs
deleted file mode 100644
index 97ccfea..0000000
--- a/src/arch/ppc/reg/id.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-// TODO: Add proper `RegId` implementation. See [issue #29](https://github.com/daniel5151/gdbstub/issues/29)
-// pub enum PowerPc32RegId {}
diff --git a/src/arch/ppc/reg/mod.rs b/src/arch/ppc/reg/mod.rs
deleted file mode 100644
index fde8e55..0000000
--- a/src/arch/ppc/reg/mod.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-//! `Register` structs for PowerPC architectures
-
-/// `RegId` definitions for PowerPC architectures.
-pub mod id;
-
-mod common;
-
-pub use common::PowerPcCommonRegs;
-type PpcVector = u128;
diff --git a/src/arch/riscv/mod.rs b/src/arch/riscv/mod.rs
deleted file mode 100644
index 29dde28..0000000
--- a/src/arch/riscv/mod.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-//! Implementations for the [RISC-V](https://riscv.org/) architecture.
-//!
-//! *Note*: currently only supports integer versions of the ISA.
-
-use crate::arch::Arch;
-
-pub mod reg;
-
-/// Implements `Arch` for 32-bit RISC-V.
-pub enum Riscv32 {}
-
-/// Implements `Arch` for 64-bit RISC-V.
-pub enum Riscv64 {}
-
-impl Arch for Riscv32 {
- type Usize = u32;
- type Registers = reg::RiscvCoreRegs<u32>;
- type RegId = reg::id::RiscvRegId;
-
- fn target_description_xml() -> Option<&'static str> {
- Some(r#"<target version="1.0"><architecture>riscv</architecture></target>"#)
- }
-}
-
-impl Arch for Riscv64 {
- type Usize = u64;
- type Registers = reg::RiscvCoreRegs<u64>;
- type RegId = reg::id::RiscvRegId;
-
- fn target_description_xml() -> Option<&'static str> {
- Some(r#"<target version="1.0"><architecture>riscv64</architecture></target>"#)
- }
-}
diff --git a/src/arch/riscv/reg/id.rs b/src/arch/riscv/reg/id.rs
deleted file mode 100644
index 9430d2e..0000000
--- a/src/arch/riscv/reg/id.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use crate::arch::RegId;
-
-/// RISC-V Register identifier.
-#[derive(Debug, Clone, Copy)]
-#[non_exhaustive]
-pub enum RiscvRegId {
- /// General Purpose Register (x0-x31).
- Gpr(u8),
- /// Floating Point Register (f0-f31).
- Fpr(u8),
- /// Program Counter.
- Pc,
- /// Control and Status Register.
- Csr(u16),
- /// Privilege level.
- Priv,
-}
-
-impl RegId for RiscvRegId {
- fn from_raw_id(id: usize) -> Option<(Self, usize)> {
- let reg_size = match id {
- 0..=31 => (Self::Gpr(id as u8), 4),
- 32 => (Self::Pc, 4),
- 33..=64 => (Self::Fpr((id - 33) as u8), 4),
- 65..=4160 => (Self::Csr((id - 65) as u16), 4),
- 4161 => (Self::Priv, 1),
- _ => return None,
- };
- Some(reg_size)
- }
-}
diff --git a/src/arch/riscv/reg/mod.rs b/src/arch/riscv/reg/mod.rs
deleted file mode 100644
index e501c47..0000000
--- a/src/arch/riscv/reg/mod.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-//! `Register` structs for RISC-V architectures.
-
-/// `RegId` definitions for RISC-V architectures.
-pub mod id;
-
-mod riscv;
-
-pub use riscv::RiscvCoreRegs;
diff --git a/src/arch/riscv/reg/riscv.rs b/src/arch/riscv/reg/riscv.rs
deleted file mode 100644
index 1dfb465..0000000
--- a/src/arch/riscv/reg/riscv.rs
+++ /dev/null
@@ -1,71 +0,0 @@
-use num_traits::PrimInt;
-
-use crate::arch::Registers;
-use crate::internal::LeBytes;
-
-/// RISC-V Integer registers.
-///
-/// The register width is set to `u32` or `u64` based on the `<U>` type.
-///
-/// Useful links:
-/// * [GNU binutils-gdb XML descriptions](https://github.com/bminor/binutils-gdb/blob/master/gdb/features/riscv)
-/// * [riscv-tdep.h](https://github.com/bminor/binutils-gdb/blob/master/gdb/riscv-tdep.h)
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct RiscvCoreRegs<U> {
- /// General purpose registers (x0-x31)
- pub x: [U; 32],
- /// Program counter
- pub pc: U,
-}
-
-impl<U> Registers for RiscvCoreRegs<U>
-where
- U: PrimInt + LeBytes + Default + core::fmt::Debug,
-{
- fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
- macro_rules! write_le_bytes {
- ($value:expr) => {
- let mut buf = [0; 16];
- // infallible (unless digit is a >128 bit number)
- let len = $value.to_le_bytes(&mut buf).unwrap();
- let buf = &buf[..len];
- for b in buf {
- write_byte(Some(*b));
- }
- };
- }
-
- // Write GPRs
- for reg in self.x.iter() {
- write_le_bytes!(reg);
- }
-
- // Program Counter is regnum 33
- write_le_bytes!(&self.pc);
- }
-
- fn gdb_deserialize(&mut self, bytes: &[u8]) -> Result<(), ()> {
- let ptrsize = core::mem::size_of::<U>();
-
- // ensure bytes.chunks_exact(ptrsize) won't panic
- if bytes.len() % ptrsize != 0 {
- return Err(());
- }
-
- let mut regs = bytes
- .chunks_exact(ptrsize)
- .map(|c| U::from_le_bytes(c).unwrap());
-
- // Read GPRs
- for reg in self.x.iter_mut() {
- *reg = regs.next().ok_or(())?
- }
- self.pc = regs.next().ok_or(())?;
-
- if regs.next().is_some() {
- return Err(());
- }
-
- Ok(())
- }
-}
diff --git a/src/arch/traits.rs b/src/arch/traits.rs
deleted file mode 100644
index 4af97ec..0000000
--- a/src/arch/traits.rs
+++ /dev/null
@@ -1,84 +0,0 @@
-use core::fmt::Debug;
-
-use num_traits::{PrimInt, Unsigned};
-
-use crate::internal::{BeBytes, LeBytes};
-
-/// Register identifier for target registers.
-///
-/// These identifiers are used by GDB for single register operations.
-pub trait RegId: Sized + Debug {
- /// Map raw GDB register number corresponding `RegId` and register size.
- ///
- /// Returns `None` if the register is not available.
- fn from_raw_id(id: usize) -> Option<(Self, usize)>;
-}
-
-/// Stub implementation -- Returns `None` for all raw IDs.
-impl RegId for () {
- fn from_raw_id(_id: usize) -> Option<(Self, usize)> {
- None
- }
-}
-
-/// Methods to read/write architecture-specific registers.
-///
-/// Registers must be de/serialized in the order specified by the architecture's
-/// `<target>.xml` in the GDB source tree.
-///
-/// e.g: for ARM:
-/// github.com/bminor/binutils-gdb/blob/master/gdb/features/arm/arm-core.xml
-// TODO: add way to de/serialize arbitrary "missing"/"uncollected" registers.
-pub trait Registers: Default + Debug + Clone + PartialEq {
- /// Serialize `self` into a GDB register bytestream.
- ///
- /// Missing registers are serialized by passing `None` to write_byte.
- fn gdb_serialize(&self, write_byte: impl FnMut(Option<u8>));
-
- /// Deserialize a GDB register bytestream into `self`.
- #[allow(clippy::clippy::result_unit_err)]
- fn gdb_deserialize(&mut self, bytes: &[u8]) -> Result<(), ()>;
-}
-
-/// Encodes architecture-specific information, such as pointer size, register
-/// layout, etc...
-///
-/// Types implementing `Arch` should be
-/// [Zero-variant Enums](https://doc.rust-lang.org/reference/items/enumerations.html#zero-variant-enums),
-/// as they are only ever used at the type level, and should never be
-/// instantiated.
-pub trait Arch {
- /// The architecture's pointer size (e.g: `u32` on a 32-bit system).
- type Usize: PrimInt + Unsigned + BeBytes + LeBytes;
-
- /// The architecture's register file.
- type Registers: Registers;
-
- /// Register identifier enum/struct.
- ///
- /// Used to access individual registers via `Target::read/write_register`.
- ///
- /// NOTE: The `RegId` type is not required to have a 1:1 correspondence with
- /// the `Registers` type, and may include register identifiers which are
- /// separate from the main `Registers` structure.
- type RegId: RegId;
-
- /// (optional) Return the target's description XML file (`target.xml`).
- ///
- /// Implementing this method enables GDB to automatically detect the
- /// target's architecture, saving the hassle of having to run `set
- /// architecture <arch>` when starting a debugging session.
- ///
- /// These descriptions can be quite succinct. For example, the target
- /// description for an `armv4t` target can be as simple as:
- ///
- /// ```
- /// r#"<target version="1.0"><architecture>armv4t</architecture></target>"#;
- /// ```
- ///
- /// See the [GDB docs](https://sourceware.org/gdb/current/onlinedocs/gdb/Target-Description-Format.html)
- /// for details on the target description XML format.
- fn target_description_xml() -> Option<&'static str> {
- None
- }
-}
diff --git a/src/arch/x86/mod.rs b/src/arch/x86/mod.rs
deleted file mode 100644
index 28fbe88..0000000
--- a/src/arch/x86/mod.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-//! Implementations for various x86 architectures.
-
-use crate::arch::Arch;
-use crate::arch::RegId;
-
-pub mod reg;
-
-/// Implements `Arch` for 64-bit x86 + SSE Extensions.
-///
-/// Check out the [module level docs](../index.html#whats-with-regidimpl) for
-/// more info about the `RegIdImpl` type parameter.
-#[allow(non_camel_case_types)]
-pub enum X86_64_SSE<RegIdImpl: RegId = reg::id::X86_64CoreRegId> {
- #[doc(hidden)]
- _Marker(core::marker::PhantomData<RegIdImpl>),
-}
-
-impl<RegIdImpl: RegId> Arch for X86_64_SSE<RegIdImpl> {
- type Usize = u64;
- type Registers = reg::X86_64CoreRegs;
- type RegId = RegIdImpl;
-
- fn target_description_xml() -> Option<&'static str> {
- Some(
- r#"<target version="1.0"><architecture>i386:x86-64</architecture><feature name="org.gnu.gdb.i386.sse"></feature></target>"#,
- )
- }
-}
-
-/// Implements `Arch` for 32-bit x86 + SSE Extensions.
-///
-/// Check out the [module level docs](../index.html#whats-with-regidimpl) for
-/// more info about the `RegIdImpl` type parameter.
-#[allow(non_camel_case_types)]
-pub enum X86_SSE<RegIdImpl: RegId = reg::id::X86CoreRegId> {
- #[doc(hidden)]
- _Marker(core::marker::PhantomData<RegIdImpl>),
-}
-
-impl<RegIdImpl: RegId> Arch for X86_SSE<RegIdImpl> {
- type Usize = u32;
- type Registers = reg::X86CoreRegs;
- type RegId = RegIdImpl;
-
- fn target_description_xml() -> Option<&'static str> {
- Some(
- r#"<target version="1.0"><architecture>i386:intel</architecture><feature name="org.gnu.gdb.i386.sse"></feature></target>"#,
- )
- }
-}
diff --git a/src/arch/x86/reg/core32.rs b/src/arch/x86/reg/core32.rs
deleted file mode 100644
index a49ee9a..0000000
--- a/src/arch/x86/reg/core32.rs
+++ /dev/null
@@ -1,134 +0,0 @@
-use core::convert::TryInto;
-
-use crate::arch::x86::reg::{X87FpuInternalRegs, F80};
-use crate::arch::Registers;
-
-/// 32-bit x86 core registers (+ SSE extensions).
-///
-/// Source: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/i386/32bit-core.xml
-/// Additionally: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/i386/32bit-sse.xml
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct X86CoreRegs {
- /// Accumulator
- pub eax: u32,
- /// Count register
- pub ecx: u32,
- /// Data register
- pub edx: u32,
- /// Base register
- pub ebx: u32,
- /// Stack pointer
- pub esp: u32,
- /// Base pointer
- pub ebp: u32,
- /// Source index
- pub esi: u32,
- /// Destination index
- pub edi: u32,
- /// Instruction pointer
- pub eip: u32,
- /// Status register
- pub eflags: u32,
- /// Segment registers: CS, SS, DS, ES, FS, GS
- pub segments: [u32; 6],
- /// FPU registers: ST0 through ST7
- pub st: [F80; 8],
- /// FPU internal registers
- pub fpu: X87FpuInternalRegs,
- /// SIMD Registers: XMM0 through XMM7
- pub xmm: [u128; 8],
- /// SSE Status/Control Register
- pub mxcsr: u32,
-}
-
-impl Registers for X86CoreRegs {
- fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
- macro_rules! write_bytes {
- ($bytes:expr) => {
- for b in $bytes {
- write_byte(Some(*b))
- }
- };
- }
-
- macro_rules! write_regs {
- ($($reg:ident),*) => {
- $(
- write_bytes!(&self.$reg.to_le_bytes());
- )*
- }
- }
-
- write_regs!(eax, ecx, edx, ebx, esp, ebp, esi, edi, eip, eflags);
-
- // cs, ss, ds, es, fs, gs
- for seg in &self.segments {
- write_bytes!(&seg.to_le_bytes());
- }
-
- // st0 to st7
- for st_reg in &self.st {
- write_bytes!(st_reg);
- }
-
- self.fpu.gdb_serialize(&mut write_byte);
-
- // xmm0 to xmm15
- for xmm_reg in &self.xmm {
- write_bytes!(&xmm_reg.to_le_bytes());
- }
-
- // mxcsr
- write_bytes!(&self.mxcsr.to_le_bytes());
-
- // padding
- (0..4).for_each(|_| write_byte(None))
- }
-
- fn gdb_deserialize(&mut self, bytes: &[u8]) -> Result<(), ()> {
- if bytes.len() < 0x138 {
- return Err(());
- }
-
- macro_rules! parse_regs {
- ($($reg:ident),*) => {
- let mut regs = bytes[0..0x28]
- .chunks_exact(4)
- .map(|x| u32::from_le_bytes(x.try_into().unwrap()));
- $(
- self.$reg = regs.next().ok_or(())?;
- )*
- }
- }
-
- parse_regs!(eax, ecx, edx, ebx, esp, ebp, esi, edi, eip, eflags);
-
- let mut segments = bytes[0x28..0x40]
- .chunks_exact(4)
- .map(|x| u32::from_le_bytes(x.try_into().unwrap()));
-
- for seg in self.segments.iter_mut() {
- *seg = segments.next().ok_or(())?;
- }
-
- let mut regs = bytes[0x40..0x90].chunks_exact(10).map(TryInto::try_into);
-
- for reg in self.st.iter_mut() {
- *reg = regs.next().ok_or(())?.map_err(|_| ())?;
- }
-
- self.fpu.gdb_deserialize(&bytes[0x90..0xb0])?;
-
- let mut regs = bytes[0xb0..0x130]
- .chunks_exact(0x10)
- .map(|x| u128::from_le_bytes(x.try_into().unwrap()));
-
- for reg in self.xmm.iter_mut() {
- *reg = regs.next().ok_or(())?;
- }
-
- self.mxcsr = u32::from_le_bytes(bytes[0x130..0x134].try_into().unwrap());
-
- Ok(())
- }
-}
diff --git a/src/arch/x86/reg/core64.rs b/src/arch/x86/reg/core64.rs
deleted file mode 100644
index 6f54581..0000000
--- a/src/arch/x86/reg/core64.rs
+++ /dev/null
@@ -1,121 +0,0 @@
-use core::convert::TryInto;
-
-use crate::arch::x86::reg::{X87FpuInternalRegs, F80};
-use crate::arch::Registers;
-
-/// 64-bit x86 core registers (+ SSE extensions).
-///
-/// Source: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/i386/64bit-core.xml
-/// Additionally: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/i386/64bit-sse.xml
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct X86_64CoreRegs {
- /// RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, r8-r15
- pub regs: [u64; 16],
- /// Status register
- pub eflags: u32,
- /// Instruction pointer
- pub rip: u64,
- /// Segment registers: CS, SS, DS, ES, FS, GS
- pub segments: [u32; 6],
- /// FPU registers: ST0 through ST7
- pub st: [F80; 8],
- /// FPU internal registers
- pub fpu: X87FpuInternalRegs,
- /// SIMD Registers: XMM0 through XMM15
- pub xmm: [u128; 0x10],
- /// SSE Status/Control Register
- pub mxcsr: u32,
-}
-
-impl Registers for X86_64CoreRegs {
- fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
- macro_rules! write_bytes {
- ($bytes:expr) => {
- for b in $bytes {
- write_byte(Some(*b))
- }
- };
- }
-
- // rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8-r15
- for reg in &self.regs {
- write_bytes!(&reg.to_le_bytes());
- }
-
- // rip
- write_bytes!(&self.rip.to_le_bytes());
-
- // eflags
- write_bytes!(&self.eflags.to_le_bytes());
-
- // cs, ss, ds, es, fs, gs
- for seg in &self.segments {
- write_bytes!(&seg.to_le_bytes());
- }
-
- // st0 to st7
- for st_reg in &self.st {
- write_bytes!(st_reg);
- }
-
- self.fpu.gdb_serialize(&mut write_byte);
-
- // xmm0 to xmm15
- for xmm_reg in &self.xmm {
- write_bytes!(&xmm_reg.to_le_bytes());
- }
-
- // mxcsr
- write_bytes!(&self.mxcsr.to_le_bytes());
-
- // padding?
- // XXX: Couldn't figure out what these do and GDB doesn't actually display any
- // registers that use these values.
- (0..0x18).for_each(|_| write_byte(None))
- }
-
- fn gdb_deserialize(&mut self, bytes: &[u8]) -> Result<(), ()> {
- if bytes.len() < 0x218 {
- return Err(());
- }
-
- let mut regs = bytes[0..0x80]
- .chunks_exact(8)
- .map(|x| u64::from_le_bytes(x.try_into().unwrap()));
-
- for reg in self.regs.iter_mut() {
- *reg = regs.next().ok_or(())?;
- }
-
- self.rip = u64::from_le_bytes(bytes[0x80..0x88].try_into().unwrap());
- self.eflags = u32::from_le_bytes(bytes[0x88..0x8C].try_into().unwrap());
-
- let mut segments = bytes[0x8C..0xA4]
- .chunks_exact(4)
- .map(|x| u32::from_le_bytes(x.try_into().unwrap()));
-
- for seg in self.segments.iter_mut() {
- *seg = segments.next().ok_or(())?;
- }
-
- let mut regs = bytes[0xA4..0xF4].chunks_exact(10).map(TryInto::try_into);
-
- for reg in self.st.iter_mut() {
- *reg = regs.next().ok_or(())?.map_err(|_| ())?;
- }
-
- self.fpu.gdb_deserialize(&bytes[0xF4..0x114])?;
-
- let mut regs = bytes[0x114..0x214]
- .chunks_exact(0x10)
- .map(|x| u128::from_le_bytes(x.try_into().unwrap()));
-
- for reg in self.xmm.iter_mut() {
- *reg = regs.next().ok_or(())?;
- }
-
- self.mxcsr = u32::from_le_bytes(bytes[0x214..0x218].try_into().unwrap());
-
- Ok(())
- }
-}
diff --git a/src/arch/x86/reg/id.rs b/src/arch/x86/reg/id.rs
deleted file mode 100644
index 45d3de9..0000000
--- a/src/arch/x86/reg/id.rs
+++ /dev/null
@@ -1,198 +0,0 @@
-use crate::arch::RegId;
-
-/// FPU register identifier.
-#[derive(Debug, Clone, Copy)]
-pub enum X87FpuInternalRegId {
- /// Floating-point control register
- Fctrl,
- /// Floating-point status register
- Fstat,
- /// Tag word
- Ftag,
- /// FPU instruction pointer segment
- Fiseg,
- /// FPU intstruction pointer offset
- Fioff,
- /// FPU operand segment
- Foseg,
- /// FPU operand offset
- Fooff,
- /// Floating-point opcode
- Fop,
-}
-
-impl X87FpuInternalRegId {
- fn from_u8(val: u8) -> Option<Self> {
- use self::X87FpuInternalRegId::*;
-
- let r = match val {
- 0 => Fctrl,
- 1 => Fstat,
- 2 => Ftag,
- 3 => Fiseg,
- 4 => Fioff,
- 5 => Foseg,
- 6 => Fooff,
- 7 => Fop,
- _ => return None,
- };
- Some(r)
- }
-}
-
-/// 32-bit x86 core + SSE register identifier.
-///
-/// Source: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/i386/32bit-core.xml
-/// Additionally: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/i386/32bit-sse.xml
-#[derive(Debug, Clone, Copy)]
-#[non_exhaustive]
-pub enum X86CoreRegId {
- /// Accumulator
- Eax,
- /// Count register
- Ecx,
- /// Data register
- Edx,
- /// Base register
- Ebx,
- /// Stack pointer
- Esp,
- /// Base pointer
- Ebp,
- /// Source index
- Esi,
- /// Destination index
- Edi,
- /// Instruction pointer
- Eip,
- /// Status register
- Eflags,
- /// Segment registers: CS, SS, DS, ES, FS, GS
- Segment(u8),
- /// FPU registers: ST0 through ST7
- St(u8),
- /// FPU internal registers
- Fpu(X87FpuInternalRegId),
- /// SIMD Registers: XMM0 through XMM7
- Xmm(u8),
- /// SSE Status/Control Register
- Mxcsr,
-}
-
-impl RegId for X86CoreRegId {
- fn from_raw_id(id: usize) -> Option<(Self, usize)> {
- use self::X86CoreRegId::*;
-
- let r = match id {
- 0 => (Eax, 4),
- 1 => (Ecx, 4),
- 2 => (Edx, 4),
- 3 => (Ebx, 4),
- 4 => (Esp, 4),
- 5 => (Ebp, 4),
- 6 => (Esi, 4),
- 7 => (Edi, 4),
- 8 => (Eip, 4),
- 9 => (Eflags, 4),
- 10..=15 => (Segment(id as u8 - 10), 4),
- 16..=23 => (St(id as u8 - 16), 10),
- 24..=31 => match X87FpuInternalRegId::from_u8(id as u8 - 24) {
- Some(r) => (Fpu(r), 4),
- None => unreachable!(),
- },
- 32..=39 => (Xmm(id as u8 - 32), 16),
- 40 => (Mxcsr, 4),
- _ => return None,
- };
- Some(r)
- }
-}
-
-/// 64-bit x86 core + SSE register identifier.
-///
-/// Source: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/i386/64bit-core.xml
-/// Additionally: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/i386/64bit-sse.xml
-#[derive(Debug, Clone, Copy)]
-#[non_exhaustive]
-pub enum X86_64CoreRegId {
- /// General purpose registers:
- /// RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, r8-r15
- Gpr(u8),
- /// Instruction pointer
- Rip,
- /// Status register
- Eflags,
- /// Segment registers: CS, SS, DS, ES, FS, GS
- Segment(u8),
- /// FPU registers: ST0 through ST7
- St(u8),
- /// FPU internal registers
- Fpu(X87FpuInternalRegId),
- /// SIMD Registers: XMM0 through XMM15
- Xmm(u8),
- /// SSE Status/Control Register
- Mxcsr,
-}
-
-impl RegId for X86_64CoreRegId {
- fn from_raw_id(id: usize) -> Option<(Self, usize)> {
- use self::X86_64CoreRegId::*;
-
- let r = match id {
- 0..=15 => (Gpr(id as u8), 8),
- 16 => (Rip, 4),
- 17 => (Eflags, 8),
- 18..=23 => (Segment(id as u8 - 18), 4),
- 24..=31 => (St(id as u8 - 24), 10),
- 32..=39 => match X87FpuInternalRegId::from_u8(id as u8 - 32) {
- Some(r) => (Fpu(r), 4),
- None => unreachable!(),
- },
- 40..=55 => (Xmm(id as u8 - 40), 16),
- 56 => (Mxcsr, 4),
- _ => return None,
- };
- Some(r)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::arch::traits::RegId;
- use crate::arch::traits::Registers;
-
- /// Compare the following two values which are expected to be the same:
- /// * length of data written by `Registers::gdb_serialize()` in byte
- /// * sum of sizes of all registers obtained by `RegId::from_raw_id()`
- fn test<Rs: Registers, RId: RegId>() {
- // Obtain the data length written by `gdb_serialize` by passing a custom
- // closure.
- let mut serialized_data_len = 0;
- let counter = |b: Option<u8>| {
- if b.is_some() {
- serialized_data_len += 1;
- }
- };
- Rs::default().gdb_serialize(counter);
-
- // Accumulate register sizes returned by `from_raw_id`.
- let mut i = 0;
- let mut sum_reg_sizes = 0;
- while let Some((_, size)) = RId::from_raw_id(i) {
- sum_reg_sizes += size;
- i += 1;
- }
-
- assert_eq!(serialized_data_len, sum_reg_sizes);
- }
-
- #[test]
- fn test_x86() {
- test::<crate::arch::x86::reg::X86CoreRegs, crate::arch::x86::reg::id::X86CoreRegId>()
- }
-
- #[test]
- fn test_x86_64() {
- test::<crate::arch::x86::reg::X86_64CoreRegs, crate::arch::x86::reg::id::X86_64CoreRegId>()
- }
-}
diff --git a/src/arch/x86/reg/mod.rs b/src/arch/x86/reg/mod.rs
deleted file mode 100644
index 46c8508..0000000
--- a/src/arch/x86/reg/mod.rs
+++ /dev/null
@@ -1,82 +0,0 @@
-//! `Register` structs for x86 architectures.
-
-use core::convert::TryInto;
-
-use crate::arch::Registers;
-
-/// `RegId` definitions for x86 architectures.
-pub mod id;
-
-mod core32;
-mod core64;
-
-pub use core32::X86CoreRegs;
-pub use core64::X86_64CoreRegs;
-
-/// 80-bit floating point value
-pub type F80 = [u8; 10];
-
-/// FPU registers
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct X87FpuInternalRegs {
- /// Floating-point control register
- pub fctrl: u32,
- /// Floating-point status register
- pub fstat: u32,
- /// Tag word
- pub ftag: u32,
- /// FPU instruction pointer segment
- pub fiseg: u32,
- /// FPU intstruction pointer offset
- pub fioff: u32,
- /// FPU operand segment
- pub foseg: u32,
- /// FPU operand offset
- pub fooff: u32,
- /// Floating-point opcode
- pub fop: u32,
-}
-
-impl Registers for X87FpuInternalRegs {
- fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
- macro_rules! write_bytes {
- ($bytes:expr) => {
- for b in $bytes {
- write_byte(Some(*b))
- }
- };
- }
-
- // Note: GDB section names don't make sense unless you read x87 FPU section 8.1:
- // https://web.archive.org/web/20150123212110/http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-1-manual.pdf
- write_bytes!(&self.fctrl.to_le_bytes());
- write_bytes!(&self.fstat.to_le_bytes());
- write_bytes!(&self.ftag.to_le_bytes());
- write_bytes!(&self.fiseg.to_le_bytes());
- write_bytes!(&self.fioff.to_le_bytes());
- write_bytes!(&self.foseg.to_le_bytes());
- write_bytes!(&self.fooff.to_le_bytes());
- write_bytes!(&self.fop.to_le_bytes());
- }
-
- fn gdb_deserialize(&mut self, bytes: &[u8]) -> Result<(), ()> {
- if bytes.len() != 0x20 {
- return Err(());
- }
-
- let mut regs = bytes
- .chunks_exact(4)
- .map(|x| u32::from_le_bytes(x.try_into().unwrap()));
-
- self.fctrl = regs.next().ok_or(())?;
- self.fstat = regs.next().ok_or(())?;
- self.ftag = regs.next().ok_or(())?;
- self.fiseg = regs.next().ok_or(())?;
- self.fioff = regs.next().ok_or(())?;
- self.foseg = regs.next().ok_or(())?;
- self.fooff = regs.next().ok_or(())?;
- self.fop = regs.next().ok_or(())?;
-
- Ok(())
- }
-}
diff --git a/src/gdbstub_impl/builder.rs b/src/gdbstub_impl/builder.rs
index bf13e21..d5cb58a 100644
--- a/src/gdbstub_impl/builder.rs
+++ b/src/gdbstub_impl/builder.rs
@@ -73,28 +73,28 @@ impl<'a, T: Target, C: Connection> GdbStubBuilder<'a, T, C> {
/// Build the GdbStub, returning an error if something went wrong.
pub fn build(self) -> Result<GdbStub<'a, T, C>, GdbStubBuilderError> {
- let (packet_buffer, packet_buffer_len) = match self.packet_buffer {
+ let packet_buffer = match self.packet_buffer {
Some(buf) => {
- let len = match self.packet_buffer_size {
+ let buf = match self.packet_buffer_size {
Some(custom_len) => {
if custom_len > buf.len() {
return Err(GdbStubBuilderError::PacketBufSizeMismatch);
} else {
- custom_len
+ &mut buf[..custom_len]
}
}
- None => buf.len(),
+ None => buf,
};
- (ManagedSlice::Borrowed(buf), len)
+ ManagedSlice::Borrowed(buf)
}
None => {
cfg_if::cfg_if! {
if #[cfg(feature = "alloc")] {
- use alloc::vec::Vec;
+ use alloc::vec;
// need to pick some arbitrary value to report to GDB
// 4096 seems reasonable?
let len = self.packet_buffer_size.unwrap_or(4096);
- (ManagedSlice::Owned(Vec::with_capacity(len)), len)
+ ManagedSlice::Owned(vec![0; len])
} else {
return Err(GdbStubBuilderError::MissingPacketBuffer);
}
@@ -105,7 +105,7 @@ impl<'a, T: Target, C: Connection> GdbStubBuilder<'a, T, C> {
Ok(GdbStub {
conn: self.conn,
packet_buffer,
- state: GdbStubImpl::new(packet_buffer_len),
+ state: GdbStubImpl::new(),
})
}
}
diff --git a/src/gdbstub_impl/error.rs b/src/gdbstub_impl/error.rs
index 295c153..40d2317 100644
--- a/src/gdbstub_impl/error.rs
+++ b/src/gdbstub_impl/error.rs
@@ -10,41 +10,49 @@ pub enum GdbStubError<T, C> {
/// Connection Error while reading request.
ConnectionRead(C),
/// Connection Error while writing response.
- ConnectionWrite(ResponseWriterError<C>),
+ ConnectionWrite(C),
/// Client nack'd the last packet, but `gdbstub` doesn't implement
/// re-transmission.
ClientSentNack,
- /// GdbStub was not provided with a packet buffer in `no_std` mode
- /// (missing call to `with_packet_buffer`)
- MissingPacketBuffer,
/// Packet cannot fit in the provided packet buffer.
- PacketBufferOverlow,
+ PacketBufferOverflow,
/// Could not parse the packet into a valid command.
PacketParse(PacketParseError),
- /// GDB client sent an unexpected packet.
+ /// GDB client sent an unexpected packet. This should never happen!
+ /// Please file an issue at https://github.com/daniel5151/gdbstub/issues
PacketUnexpected,
/// GDB client sent a packet with too much data for the given target.
TargetMismatch,
- /// Target threw a fatal error.
+ /// Target encountered a fatal error.
TargetError(T),
- /// Target didn't report any active threads.
+ /// Target responded with an unsupported stop reason.
+ ///
+ /// Certain stop reasons can only be used when their associated protocol
+ /// feature has been implemented. e.g: a Target cannot return a
+ /// `StopReason::HwBreak` if the hardware breakpoints IDET hasn't been
+ /// implemented.
+ UnsupportedStopReason,
+ /// Target didn't report any active threads when there should have been at
+ /// least one running.
NoActiveThreads,
- /// Resuming with a signal is not implemented yet. Consider opening a PR?
- ResumeWithSignalUnimplemented,
/// Internal - A non-fatal error occurred (with errno-style error code)
+ ///
+ /// This "dummy" error is required as part of the internal
+ /// `TargetResultExt::handle_error()` machinery, and will never be
+ /// propagated up to the end user.
#[doc(hidden)]
NonFatalError(u8),
}
impl<T, C> From<ResponseWriterError<C>> for GdbStubError<T, C> {
fn from(e: ResponseWriterError<C>) -> Self {
- GdbStubError::ConnectionWrite(e)
+ GdbStubError::ConnectionWrite(e.0)
}
}
impl<A, T, C> From<CapacityError<A>> for GdbStubError<T, C> {
fn from(_: CapacityError<A>) -> Self {
- GdbStubError::PacketBufferOverlow
+ GdbStubError::PacketBufferOverflow
}
}
@@ -59,14 +67,13 @@ where
ConnectionRead(e) => write!(f, "Connection Error while reading request: {:?}", e),
ConnectionWrite(e) => write!(f, "Connection Error while writing response: {:?}", e),
ClientSentNack => write!(f, "Client nack'd the last packet, but `gdbstub` doesn't implement re-transmission."),
- MissingPacketBuffer => write!(f, "GdbStub was not provided with a packet buffer in `no_std` mode (missing call to `with_packet_buffer`)"),
- PacketBufferOverlow => write!(f, "Packet too big for provided buffer!"),
+ PacketBufferOverflow => write!(f, "Packet too big for provided buffer!"),
PacketParse(e) => write!(f, "Could not parse the packet into a valid command: {:?}", e),
- PacketUnexpected => write!(f, "Client sent an unexpected packet."),
+ PacketUnexpected => write!(f, "Client sent an unexpected packet. This should never happen! Please file an issue at https://github.com/daniel5151/gdbstub/issues"),
TargetMismatch => write!(f, "GDB client sent a packet with too much data for the given target."),
TargetError(e) => write!(f, "Target threw a fatal error: {:?}", e),
- NoActiveThreads => write!(f, "Target didn't report any active threads."),
- ResumeWithSignalUnimplemented => write!(f, "Resuming with a signal is not implemented yet. Consider opening a PR?"),
+ UnsupportedStopReason => write!(f, "Target responded with an unsupported stop reason."),
+ NoActiveThreads => write!(f, "Target didn't report any active threads when there should have been at least one running."),
NonFatalError(_) => write!(f, "Internal - A non-fatal error occurred (with errno-style error code)"),
}
}
diff --git a/src/gdbstub_impl/ext/base.rs b/src/gdbstub_impl/ext/base.rs
new file mode 100644
index 0000000..d130422
--- /dev/null
+++ b/src/gdbstub_impl/ext/base.rs
@@ -0,0 +1,743 @@
+use super::prelude::*;
+use crate::protocol::commands::ext::Base;
+
+use crate::arch::{Arch, Registers};
+use crate::protocol::{IdKind, SpecificIdKind, SpecificThreadId};
+use crate::target::ext::base::multithread::ThreadStopReason;
+use crate::target::ext::base::{BaseOps, GdbInterrupt, ReplayLogPosition, ResumeAction};
+use crate::{FAKE_PID, SINGLE_THREAD_TID};
+
+impl<T: Target, C: Connection> GdbStubImpl<T, C> {
+ #[inline(always)]
+ fn get_sane_any_tid(&mut self, target: &mut T) -> Result<Tid, Error<T::Error, C::Error>> {
+ let tid = match target.base_ops() {
+ BaseOps::SingleThread(_) => SINGLE_THREAD_TID,
+ BaseOps::MultiThread(ops) => {
+ let mut first_tid = None;
+ ops.list_active_threads(&mut |tid| {
+ if first_tid.is_none() {
+ first_tid = Some(tid);
+ }
+ })
+ .map_err(Error::TargetError)?;
+ // Note that `Error::NoActiveThreads` shouldn't ever occur, since this method is
+ // called from the `H` packet handler, which AFAIK is only sent after the GDB
+ // client has confirmed that a thread / process exists.
+ //
+ // If it does, that really sucks, and will require rethinking how to handle "any
+ // thread" messages.
+ first_tid.ok_or(Error::NoActiveThreads)?
+ }
+ };
+ Ok(tid)
+ }
+
+ pub(crate) fn handle_base<'a>(
+ &mut self,
+ res: &mut ResponseWriter<C>,
+ target: &mut T,
+ command: Base<'a>,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ let handler_status = match command {
+ // ------------------ Handshaking and Queries ------------------- //
+ Base::qSupported(cmd) => {
+ // XXX: actually read what the client supports, and enable/disable features
+ // appropriately
+ let _features = cmd.features.into_iter();
+
+ res.write_str("PacketSize=")?;
+ res.write_num(cmd.packet_buffer_len)?;
+
+ res.write_str(";vContSupported+")?;
+ res.write_str(";multiprocess+")?;
+ res.write_str(";QStartNoAckMode+")?;
+
+ let (reverse_cont, reverse_step) = match target.base_ops() {
+ BaseOps::MultiThread(ops) => (
+ ops.support_reverse_cont().is_some(),
+ ops.support_reverse_step().is_some(),
+ ),
+ BaseOps::SingleThread(ops) => (
+ ops.support_reverse_cont().is_some(),
+ ops.support_reverse_step().is_some(),
+ ),
+ };
+
+ if reverse_cont {
+ res.write_str(";ReverseContinue+")?;
+ }
+
+ if reverse_step {
+ res.write_str(";ReverseStep+")?;
+ }
+
+ if let Some(ops) = target.extended_mode() {
+ if ops.configure_aslr().is_some() {
+ res.write_str(";QDisableRandomization+")?;
+ }
+
+ if ops.configure_env().is_some() {
+ res.write_str(";QEnvironmentHexEncoded+")?;
+ res.write_str(";QEnvironmentUnset+")?;
+ res.write_str(";QEnvironmentReset+")?;
+ }
+
+ if ops.configure_startup_shell().is_some() {
+ res.write_str(";QStartupWithShell+")?;
+ }
+
+ if ops.configure_working_dir().is_some() {
+ res.write_str(";QSetWorkingDir+")?;
+ }
+ }
+
+ if let Some(ops) = target.breakpoints() {
+ if ops.sw_breakpoint().is_some() {
+ res.write_str(";swbreak+")?;
+ }
+
+ if ops.hw_breakpoint().is_some() || ops.hw_watchpoint().is_some() {
+ res.write_str(";hwbreak+")?;
+ }
+ }
+
+ if T::Arch::target_description_xml().is_some()
+ || target.target_description_xml_override().is_some()
+ {
+ res.write_str(";qXfer:features:read+")?;
+ }
+
+ HandlerStatus::Handled
+ }
+ Base::QStartNoAckMode(_) => {
+ self.no_ack_mode = true;
+ HandlerStatus::NeedsOk
+ }
+ Base::qXferFeaturesRead(cmd) => {
+ #[allow(clippy::redundant_closure)]
+ let xml = target
+ .target_description_xml_override()
+ .map(|ops| ops.target_description_xml())
+ .or_else(|| T::Arch::target_description_xml());
+
+ match xml {
+ Some(xml) => {
+ let xml = xml.trim();
+ if cmd.offset >= xml.len() {
+ // no more data
+ res.write_str("l")?;
+ } else if cmd.offset + cmd.len >= xml.len() {
+ // last little bit of data
+ res.write_str("l")?;
+ res.write_binary(&xml.as_bytes()[cmd.offset..])?
+ } else {
+ // still more data
+ res.write_str("m")?;
+ res.write_binary(&xml.as_bytes()[cmd.offset..(cmd.offset + cmd.len)])?
+ }
+ }
+ // If the target hasn't provided their own XML, then the initial response to
+ // "qSupported" wouldn't have included "qXfer:features:read", and gdb wouldn't
+ // send this packet unless it was explicitly marked as supported.
+ None => return Err(Error::PacketUnexpected),
+ }
+ HandlerStatus::Handled
+ }
+
+ // -------------------- "Core" Functionality -------------------- //
+ // TODO: Improve the '?' response based on last-sent stop reason.
+ // this will be particularly relevant when working on non-stop mode.
+ Base::QuestionMark(_) => {
+ res.write_str("S05")?;
+ HandlerStatus::Handled
+ }
+ Base::qAttached(cmd) => {
+ let is_attached = match target.extended_mode() {
+ // when _not_ running in extended mode, just report that we're attaching to an
+ // existing process.
+ None => true, // assume attached to an existing process
+ // When running in extended mode, we must defer to the target
+ Some(ops) => {
+ let pid: Pid = cmd.pid.ok_or(Error::PacketUnexpected)?;
+ ops.query_if_attached(pid).handle_error()?.was_attached()
+ }
+ };
+ res.write_str(if is_attached { "1" } else { "0" })?;
+ HandlerStatus::Handled
+ }
+ Base::g(_) => {
+ let mut regs: <T::Arch as Arch>::Registers = Default::default();
+ match target.base_ops() {
+ BaseOps::SingleThread(ops) => ops.read_registers(&mut regs),
+ BaseOps::MultiThread(ops) => {
+ ops.read_registers(&mut regs, self.current_mem_tid)
+ }
+ }
+ .handle_error()?;
+
+ let mut err = Ok(());
+ regs.gdb_serialize(|val| {
+ let res = match val {
+ Some(b) => res.write_hex_buf(&[b]),
+ None => res.write_str("xx"),
+ };
+ if let Err(e) = res {
+ err = Err(e);
+ }
+ });
+ err?;
+ HandlerStatus::Handled
+ }
+ Base::G(cmd) => {
+ let mut regs: <T::Arch as Arch>::Registers = Default::default();
+ regs.gdb_deserialize(cmd.vals)
+ .map_err(|_| Error::TargetMismatch)?;
+
+ match target.base_ops() {
+ BaseOps::SingleThread(ops) => ops.write_registers(&regs),
+ BaseOps::MultiThread(ops) => ops.write_registers(&regs, self.current_mem_tid),
+ }
+ .handle_error()?;
+
+ HandlerStatus::NeedsOk
+ }
+ Base::m(cmd) => {
+ let buf = cmd.buf;
+ let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
+ .ok_or(Error::TargetMismatch)?;
+
+ let mut i = 0;
+ let mut n = cmd.len;
+ while n != 0 {
+ let chunk_size = n.min(buf.len());
+
+ use num_traits::NumCast;
+
+ let addr = addr + NumCast::from(i).ok_or(Error::TargetMismatch)?;
+ let data = &mut buf[..chunk_size];
+ match target.base_ops() {
+ BaseOps::SingleThread(ops) => ops.read_addrs(addr, data),
+ BaseOps::MultiThread(ops) => {
+ ops.read_addrs(addr, data, self.current_mem_tid)
+ }
+ }
+ .handle_error()?;
+
+ n -= chunk_size;
+ i += chunk_size;
+
+ res.write_hex_buf(data)?;
+ }
+ HandlerStatus::Handled
+ }
+ Base::M(cmd) => {
+ let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
+ .ok_or(Error::TargetMismatch)?;
+
+ match target.base_ops() {
+ BaseOps::SingleThread(ops) => ops.write_addrs(addr, cmd.val),
+ BaseOps::MultiThread(ops) => {
+ ops.write_addrs(addr, cmd.val, self.current_mem_tid)
+ }
+ }
+ .handle_error()?;
+
+ HandlerStatus::NeedsOk
+ }
+ Base::k(_) | Base::vKill(_) => {
+ match target.extended_mode() {
+ // When not running in extended mode, stop the `GdbStub` and disconnect.
+ None => HandlerStatus::Disconnect(DisconnectReason::Kill),
+
+ // When running in extended mode, a kill command does not necessarily result in
+ // a disconnect...
+ Some(ops) => {
+ let pid = match command {
+ Base::vKill(cmd) => Some(cmd.pid),
+ _ => None,
+ };
+
+ let should_terminate = ops.kill(pid).handle_error()?;
+ if should_terminate.into_bool() {
+ // manually write OK, since we need to return a DisconnectReason
+ res.write_str("OK")?;
+ HandlerStatus::Disconnect(DisconnectReason::Kill)
+ } else {
+ HandlerStatus::NeedsOk
+ }
+ }
+ }
+ }
+ Base::D(_) => {
+ // TODO: plumb-through Pid when exposing full multiprocess + extended mode
+ res.write_str("OK")?; // manually write OK, since we need to return a DisconnectReason
+ HandlerStatus::Disconnect(DisconnectReason::Disconnect)
+ }
+ Base::vCont(cmd) => {
+ use crate::protocol::commands::_vCont::vCont;
+ match cmd {
+ vCont::Query => {
+ res.write_str("vCont;c;C;s;S")?;
+ if match target.base_ops() {
+ BaseOps::SingleThread(ops) => ops.support_resume_range_step().is_some(),
+ BaseOps::MultiThread(ops) => ops.support_range_step().is_some(),
+ } {
+ res.write_str(";r")?;
+ }
+ HandlerStatus::Handled
+ }
+ vCont::Actions(actions) => self.do_vcont(res, target, actions)?,
+ }
+ }
+ // TODO?: support custom resume addr in 'c' and 's'
+ //
+ // unfortunately, this wouldn't be a particularly easy thing to implement, since the
+ // vCont packet doesn't natively support custom resume addresses. This leaves a few
+ // options for the implementation:
+ //
+ // 1. Adding new ResumeActions (i.e: ContinueWithAddr(U) and StepWithAddr(U))
+ // 2. Automatically calling `read_registers`, updating the `pc`, and calling
+ // `write_registers` prior to resuming.
+ // - will require adding some sort of `get_pc_mut` method to the `Registers` trait.
+ //
+ // Option 1 is easier to implement, but puts more burden on the implementor. Option 2
+ // will require more effort to implement (and will be less performant), but it will hide
+ // this protocol wart from the end user.
+ //
+ // Oh, one more thought - there's a subtle pitfall to watch out for if implementing
+ // Option 1: if the target is using conditional breakpoints, `do_vcont` has to be
+ // modified to only pass the resume with address variants on the _first_ iteration
+ // through the loop.
+ Base::c(_) => {
+ use crate::protocol::commands::_vCont::Actions;
+
+ self.do_vcont(
+ res,
+ target,
+ Actions::new_continue(SpecificThreadId {
+ pid: None,
+ tid: self.current_resume_tid,
+ }),
+ )?
+ }
+ Base::s(_) => {
+ use crate::protocol::commands::_vCont::Actions;
+
+ self.do_vcont(
+ res,
+ target,
+ Actions::new_step(SpecificThreadId {
+ pid: None,
+ tid: self.current_resume_tid,
+ }),
+ )?
+ }
+
+ // ------------------- Multi-threading Support ------------------ //
+ Base::H(cmd) => {
+ use crate::protocol::commands::_h_upcase::Op;
+ match cmd.kind {
+ Op::Other => match cmd.thread.tid {
+ IdKind::Any => self.current_mem_tid = self.get_sane_any_tid(target)?,
+ // "All" threads doesn't make sense for memory accesses
+ IdKind::All => return Err(Error::PacketUnexpected),
+ IdKind::WithId(tid) => self.current_mem_tid = tid,
+ },
+ // technically, this variant is deprecated in favor of vCont...
+ Op::StepContinue => match cmd.thread.tid {
+ IdKind::Any => {
+ self.current_resume_tid =
+ SpecificIdKind::WithId(self.get_sane_any_tid(target)?)
+ }
+ IdKind::All => self.current_resume_tid = SpecificIdKind::All,
+ IdKind::WithId(tid) => {
+ self.current_resume_tid = SpecificIdKind::WithId(tid)
+ }
+ },
+ }
+ HandlerStatus::NeedsOk
+ }
+ Base::qfThreadInfo(_) => {
+ res.write_str("m")?;
+
+ match target.base_ops() {
+ BaseOps::SingleThread(_) => res.write_specific_thread_id(SpecificThreadId {
+ pid: Some(SpecificIdKind::WithId(FAKE_PID)),
+ tid: SpecificIdKind::WithId(SINGLE_THREAD_TID),
+ })?,
+ BaseOps::MultiThread(ops) => {
+ let mut err: Result<_, Error<T::Error, C::Error>> = Ok(());
+ let mut first = true;
+ ops.list_active_threads(&mut |tid| {
+ // TODO: replace this with a try block (once stabilized)
+ let e = (|| {
+ if !first {
+ res.write_str(",")?
+ }
+ first = false;
+ res.write_specific_thread_id(SpecificThreadId {
+ pid: Some(SpecificIdKind::WithId(FAKE_PID)),
+ tid: SpecificIdKind::WithId(tid),
+ })?;
+ Ok(())
+ })();
+
+ if let Err(e) = e {
+ err = Err(e)
+ }
+ })
+ .map_err(Error::TargetError)?;
+ err?;
+ }
+ }
+
+ HandlerStatus::Handled
+ }
+ Base::qsThreadInfo(_) => {
+ res.write_str("l")?;
+ HandlerStatus::Handled
+ }
+ Base::T(cmd) => {
+ let alive = match cmd.thread.tid {
+ IdKind::WithId(tid) => match target.base_ops() {
+ BaseOps::SingleThread(_) => tid == SINGLE_THREAD_TID,
+ BaseOps::MultiThread(ops) => {
+ ops.is_thread_alive(tid).map_err(Error::TargetError)?
+ }
+ },
+ // TODO: double-check if GDB ever sends other variants
+ // Even after ample testing, this arm has never been hit...
+ _ => return Err(Error::PacketUnexpected),
+ };
+ if alive {
+ HandlerStatus::NeedsOk
+ } else {
+ // any error code will do
+ return Err(Error::NonFatalError(1));
+ }
+ }
+ };
+ Ok(handler_status)
+ }
+
+ #[allow(clippy::type_complexity)]
+ fn do_vcont_single_thread(
+ ops: &mut dyn crate::target::ext::base::singlethread::SingleThreadOps<
+ Arch = T::Arch,
+ Error = T::Error,
+ >,
+ res: &mut ResponseWriter<C>,
+ actions: &crate::protocol::commands::_vCont::Actions,
+ ) -> Result<ThreadStopReason<<T::Arch as Arch>::Usize>, Error<T::Error, C::Error>> {
+ use crate::protocol::commands::_vCont::VContKind;
+
+ let mut err = Ok(());
+ let mut check_gdb_interrupt = || match res.as_conn().peek() {
+ Ok(Some(0x03)) => true, // 0x03 is the interrupt byte
+ Ok(Some(_)) => false, // it's nothing that can't wait...
+ Ok(None) => false,
+ Err(e) => {
+ err = Err(Error::ConnectionRead(e));
+ true // break ASAP if a connection error occurred
+ }
+ };
+
+ let mut actions = actions.iter();
+ let first_action = actions
+ .next()
+ .ok_or(Error::PacketParse(
+ crate::protocol::PacketParseError::MalformedCommand,
+ ))?
+ .ok_or(Error::PacketParse(
+ crate::protocol::PacketParseError::MalformedCommand,
+ ))?;
+
+ let invalid_second_action = match actions.next() {
+ None => false,
+ Some(act) => match act {
+ None => {
+ return Err(Error::PacketParse(
+ crate::protocol::PacketParseError::MalformedCommand,
+ ))
+ }
+ Some(act) => !matches!(act.kind, VContKind::Continue),
+ },
+ };
+
+ if invalid_second_action || actions.next().is_some() {
+ return Err(Error::PacketUnexpected);
+ }
+
+ let action = match first_action.kind {
+ VContKind::Step => ResumeAction::Step,
+ VContKind::Continue => ResumeAction::Continue,
+ VContKind::StepWithSig(sig) => ResumeAction::StepWithSignal(sig),
+ VContKind::ContinueWithSig(sig) => ResumeAction::ContinueWithSignal(sig),
+ VContKind::RangeStep(start, end) => {
+ if let Some(ops) = ops.support_resume_range_step() {
+ let start = start.decode().map_err(|_| Error::TargetMismatch)?;
+ let end = end.decode().map_err(|_| Error::TargetMismatch)?;
+
+ let ret = ops
+ .resume_range_step(start, end, GdbInterrupt::new(&mut check_gdb_interrupt))
+ .map_err(Error::TargetError)?
+ .into();
+ err?;
+ return Ok(ret);
+ } else {
+ return Err(Error::PacketUnexpected);
+ }
+ }
+ // TODO: update this case when non-stop mode is implemented
+ VContKind::Stop => return Err(Error::PacketUnexpected),
+ };
+
+ let ret = ops
+ .resume(action, GdbInterrupt::new(&mut check_gdb_interrupt))
+ .map_err(Error::TargetError)?
+ .into();
+ err?;
+ Ok(ret)
+ }
+
+ #[allow(clippy::type_complexity)]
+ fn do_vcont_multi_thread(
+ ops: &mut dyn crate::target::ext::base::multithread::MultiThreadOps<
+ Arch = T::Arch,
+ Error = T::Error,
+ >,
+ res: &mut ResponseWriter<C>,
+ actions: &crate::protocol::commands::_vCont::Actions,
+ ) -> Result<ThreadStopReason<<T::Arch as Arch>::Usize>, Error<T::Error, C::Error>> {
+ // this is a pretty arbitrary choice, but it seems reasonable for most cases.
+ let mut default_resume_action = ResumeAction::Continue;
+
+ ops.clear_resume_actions().map_err(Error::TargetError)?;
+
+ for action in actions.iter() {
+ use crate::protocol::commands::_vCont::VContKind;
+
+ let action = action.ok_or(Error::PacketParse(
+ crate::protocol::PacketParseError::MalformedCommand,
+ ))?;
+
+ let resume_action = match action.kind {
+ VContKind::Step => ResumeAction::Step,
+ VContKind::Continue => ResumeAction::Continue,
+ // there seems to be a GDB bug where it doesn't use `vCont` unless
+ // `vCont?` returns support for resuming with a signal.
+ VContKind::StepWithSig(sig) => ResumeAction::StepWithSignal(sig),
+ VContKind::ContinueWithSig(sig) => ResumeAction::ContinueWithSignal(sig),
+ VContKind::RangeStep(start, end) => {
+ if let Some(ops) = ops.support_range_step() {
+ match action.thread.map(|thread| thread.tid) {
+ // An action with no thread-id matches all threads
+ None | Some(SpecificIdKind::All) => {
+ return Err(Error::PacketUnexpected)
+ }
+ Some(SpecificIdKind::WithId(tid)) => {
+ let start = start.decode().map_err(|_| Error::TargetMismatch)?;
+ let end = end.decode().map_err(|_| Error::TargetMismatch)?;
+
+ ops.set_resume_action_range_step(tid, start, end)
+ .map_err(Error::TargetError)?;
+ continue;
+ }
+ };
+ } else {
+ return Err(Error::PacketUnexpected);
+ }
+ }
+ // TODO: update this case when non-stop mode is implemented
+ VContKind::Stop => return Err(Error::PacketUnexpected),
+ };
+
+ match action.thread.map(|thread| thread.tid) {
+ // An action with no thread-id matches all threads
+ None | Some(SpecificIdKind::All) => default_resume_action = resume_action,
+ Some(SpecificIdKind::WithId(tid)) => ops
+ .set_resume_action(tid, resume_action)
+ .map_err(Error::TargetError)?,
+ };
+ }
+
+ let mut err = Ok(());
+ let mut check_gdb_interrupt = || match res.as_conn().peek() {
+ Ok(Some(0x03)) => true, // 0x03 is the interrupt byte
+ Ok(Some(_)) => false, // it's nothing that can't wait...
+ Ok(None) => false,
+ Err(e) => {
+ err = Err(Error::ConnectionRead(e));
+ true // break ASAP if a connection error occurred
+ }
+ };
+
+ let ret = ops
+ .resume(
+ default_resume_action,
+ GdbInterrupt::new(&mut check_gdb_interrupt),
+ )
+ .map_err(Error::TargetError)?;
+
+ err?;
+
+ Ok(ret)
+ }
+
+ fn do_vcont(
+ &mut self,
+ res: &mut ResponseWriter<C>,
+ target: &mut T,
+ actions: crate::protocol::commands::_vCont::Actions,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ loop {
+ let stop_reason = match target.base_ops() {
+ BaseOps::SingleThread(ops) => Self::do_vcont_single_thread(ops, res, &actions)?,
+ BaseOps::MultiThread(ops) => Self::do_vcont_multi_thread(ops, res, &actions)?,
+ };
+
+ match self.finish_exec(res, target, stop_reason)? {
+ Some(status) => break Ok(status),
+ None => continue,
+ }
+ }
+ }
+
+ fn write_break_common(
+ &mut self,
+ res: &mut ResponseWriter<C>,
+ tid: Tid,
+ ) -> Result<(), Error<T::Error, C::Error>> {
+ self.current_mem_tid = tid;
+ self.current_resume_tid = SpecificIdKind::WithId(tid);
+
+ res.write_str("T05")?;
+
+ res.write_str("thread:")?;
+ res.write_specific_thread_id(SpecificThreadId {
+ pid: Some(SpecificIdKind::WithId(FAKE_PID)),
+ tid: SpecificIdKind::WithId(tid),
+ })?;
+ res.write_str(";")?;
+
+ Ok(())
+ }
+
+ pub(super) fn finish_exec(
+ &mut self,
+ res: &mut ResponseWriter<C>,
+ target: &mut T,
+ stop_reason: ThreadStopReason<<T::Arch as Arch>::Usize>,
+ ) -> Result<Option<HandlerStatus>, Error<T::Error, C::Error>> {
+ macro_rules! guard_reverse_exec {
+ () => {{
+ let (reverse_cont, reverse_step) = match target.base_ops() {
+ BaseOps::MultiThread(ops) => (
+ ops.support_reverse_cont().is_some(),
+ ops.support_reverse_step().is_some(),
+ ),
+ BaseOps::SingleThread(ops) => (
+ ops.support_reverse_cont().is_some(),
+ ops.support_reverse_step().is_some(),
+ ),
+ };
+ reverse_cont || reverse_step
+ }};
+ }
+
+ macro_rules! guard_break {
+ ($op:ident) => {
+ target.breakpoints().and_then(|ops| ops.$op()).is_some()
+ };
+ }
+
+ let status = match stop_reason {
+ ThreadStopReason::DoneStep | ThreadStopReason::GdbInterrupt => {
+ res.write_str("S05")?;
+ HandlerStatus::Handled
+ }
+ ThreadStopReason::Signal(sig) => {
+ res.write_str("S")?;
+ res.write_num(sig)?;
+ HandlerStatus::Handled
+ }
+ ThreadStopReason::Exited(code) => {
+ res.write_str("W")?;
+ res.write_num(code)?;
+ HandlerStatus::Disconnect(DisconnectReason::TargetExited(code))
+ }
+ ThreadStopReason::Terminated(sig) => {
+ res.write_str("X")?;
+ res.write_num(sig)?;
+ HandlerStatus::Disconnect(DisconnectReason::TargetTerminated(sig))
+ }
+ ThreadStopReason::SwBreak(tid) if guard_break!(sw_breakpoint) => {
+ crate::__dead_code_marker!("sw_breakpoint", "stop_reason");
+
+ self.write_break_common(res, tid)?;
+ res.write_str("swbreak:;")?;
+ HandlerStatus::Handled
+ }
+ ThreadStopReason::HwBreak(tid) if guard_break!(hw_breakpoint) => {
+ crate::__dead_code_marker!("hw_breakpoint", "stop_reason");
+
+ self.write_break_common(res, tid)?;
+ res.write_str("hwbreak:;")?;
+ HandlerStatus::Handled
+ }
+ ThreadStopReason::Watch { tid, kind, addr } if guard_break!(hw_watchpoint) => {
+ crate::__dead_code_marker!("hw_watchpoint", "stop_reason");
+
+ self.write_break_common(res, tid)?;
+
+ use crate::target::ext::breakpoints::WatchKind;
+ match kind {
+ WatchKind::Write => res.write_str("watch:")?,
+ WatchKind::Read => res.write_str("rwatch:")?,
+ WatchKind::ReadWrite => res.write_str("awatch:")?,
+ }
+ res.write_num(addr)?;
+ res.write_str(";")?;
+ HandlerStatus::Handled
+ }
+ ThreadStopReason::ReplayLog(pos) if guard_reverse_exec!() => {
+ crate::__dead_code_marker!("reverse_exec", "stop_reason");
+
+ res.write_str("T05")?;
+
+ res.write_str("replaylog:")?;
+ res.write_str(match pos {
+ ReplayLogPosition::Begin => "begin",
+ ReplayLogPosition::End => "end",
+ })?;
+ res.write_str(";")?;
+
+ HandlerStatus::Handled
+ }
+ _ => return Err(Error::UnsupportedStopReason),
+ };
+
+ Ok(Some(status))
+ }
+}
+
+use crate::target::ext::base::singlethread::StopReason;
+impl<U> From<StopReason<U>> for ThreadStopReason<U> {
+ fn from(st_stop_reason: StopReason<U>) -> ThreadStopReason<U> {
+ match st_stop_reason {
+ StopReason::DoneStep => ThreadStopReason::DoneStep,
+ StopReason::GdbInterrupt => ThreadStopReason::GdbInterrupt,
+ StopReason::Exited(code) => ThreadStopReason::Exited(code),
+ StopReason::Terminated(sig) => ThreadStopReason::Terminated(sig),
+ StopReason::SwBreak => ThreadStopReason::SwBreak(SINGLE_THREAD_TID),
+ StopReason::HwBreak => ThreadStopReason::HwBreak(SINGLE_THREAD_TID),
+ StopReason::Watch { kind, addr } => ThreadStopReason::Watch {
+ tid: SINGLE_THREAD_TID,
+ kind,
+ addr,
+ },
+ StopReason::Signal(sig) => ThreadStopReason::Signal(sig),
+ StopReason::ReplayLog(pos) => ThreadStopReason::ReplayLog(pos),
+ }
+ }
+}
diff --git a/src/gdbstub_impl/ext/breakpoints.rs b/src/gdbstub_impl/ext/breakpoints.rs
new file mode 100644
index 0000000..438ead0
--- /dev/null
+++ b/src/gdbstub_impl/ext/breakpoints.rs
@@ -0,0 +1,97 @@
+use super::prelude::*;
+use crate::protocol::commands::ext::Breakpoints;
+
+use crate::arch::{Arch, BreakpointKind};
+
+enum CmdKind {
+ Add,
+ Remove,
+}
+
+impl<T: Target, C: Connection> GdbStubImpl<T, C> {
+ #[inline(always)]
+ fn handle_breakpoint_common(
+ &mut self,
+ ops: crate::target::ext::breakpoints::BreakpointsOps<T>,
+ cmd: crate::protocol::commands::breakpoint::BasicBreakpoint<'_>,
+ cmd_kind: CmdKind,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ let addr =
+ <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr).ok_or(Error::TargetMismatch)?;
+ let kind =
+ <T::Arch as Arch>::BreakpointKind::from_usize(cmd.kind).ok_or(Error::TargetMismatch)?;
+
+ let handler_status = match cmd_kind {
+ CmdKind::Add => {
+ use crate::target::ext::breakpoints::WatchKind::*;
+ let supported = match cmd.type_ {
+ 0 => (ops.sw_breakpoint()).map(|op| op.add_sw_breakpoint(addr, kind)),
+ 1 => (ops.hw_breakpoint()).map(|op| op.add_hw_breakpoint(addr, kind)),
+ 2 => (ops.hw_watchpoint()).map(|op| op.add_hw_watchpoint(addr, Write)),
+ 3 => (ops.hw_watchpoint()).map(|op| op.add_hw_watchpoint(addr, Read)),
+ 4 => (ops.hw_watchpoint()).map(|op| op.add_hw_watchpoint(addr, ReadWrite)),
+ // only 5 types in the protocol
+ _ => None,
+ };
+
+ match supported {
+ None => HandlerStatus::Handled,
+ Some(Err(e)) => {
+ Err(e).handle_error()?;
+ HandlerStatus::Handled
+ }
+ Some(Ok(true)) => HandlerStatus::NeedsOk,
+ Some(Ok(false)) => return Err(Error::NonFatalError(22)),
+ }
+ }
+ CmdKind::Remove => {
+ use crate::target::ext::breakpoints::WatchKind::*;
+ let supported = match cmd.type_ {
+ 0 => (ops.sw_breakpoint()).map(|op| op.remove_sw_breakpoint(addr, kind)),
+ 1 => (ops.hw_breakpoint()).map(|op| op.remove_hw_breakpoint(addr, kind)),
+ 2 => (ops.hw_watchpoint()).map(|op| op.remove_hw_watchpoint(addr, Write)),
+ 3 => (ops.hw_watchpoint()).map(|op| op.remove_hw_watchpoint(addr, Read)),
+ 4 => (ops.hw_watchpoint()).map(|op| op.remove_hw_watchpoint(addr, ReadWrite)),
+ // only 5 types in the protocol
+ _ => None,
+ };
+
+ match supported {
+ None => HandlerStatus::Handled,
+ Some(Err(e)) => {
+ Err(e).handle_error()?;
+ HandlerStatus::Handled
+ }
+ Some(Ok(true)) => HandlerStatus::NeedsOk,
+ Some(Ok(false)) => return Err(Error::NonFatalError(22)),
+ }
+ }
+ };
+
+ Ok(handler_status)
+ }
+
+ pub(crate) fn handle_breakpoints<'a>(
+ &mut self,
+ _res: &mut ResponseWriter<C>,
+ target: &mut T,
+ command: Breakpoints<'a>,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ let ops = match target.breakpoints() {
+ Some(ops) => ops,
+ None => return Ok(HandlerStatus::Handled),
+ };
+
+ crate::__dead_code_marker!("breakpoints", "impl");
+
+ let handler_status = match command {
+ Breakpoints::z(cmd) => self.handle_breakpoint_common(ops, cmd, CmdKind::Remove)?,
+ Breakpoints::Z(cmd) => self.handle_breakpoint_common(ops, cmd, CmdKind::Add)?,
+ Breakpoints::ZWithBytecode(cmd) => {
+ warn!("Client sent breakpoint packet with bytecode even though target didn't support agent expressions");
+ self.handle_breakpoint_common(ops, cmd.base, CmdKind::Add)?
+ }
+ };
+ Ok(handler_status)
+ }
+}
diff --git a/src/gdbstub_impl/ext/extended_mode.rs b/src/gdbstub_impl/ext/extended_mode.rs
new file mode 100644
index 0000000..2ca5cd5
--- /dev/null
+++ b/src/gdbstub_impl/ext/extended_mode.rs
@@ -0,0 +1,83 @@
+use super::prelude::*;
+use crate::protocol::commands::ext::ExtendedMode;
+
+impl<T: Target, C: Connection> GdbStubImpl<T, C> {
+ pub(crate) fn handle_extended_mode<'a>(
+ &mut self,
+ res: &mut ResponseWriter<C>,
+ target: &mut T,
+ command: ExtendedMode<'a>,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ let ops = match target.extended_mode() {
+ Some(ops) => ops,
+ None => return Ok(HandlerStatus::Handled),
+ };
+
+ crate::__dead_code_marker!("extended_mode", "impl");
+
+ let handler_status = match command {
+ ExtendedMode::ExclamationMark(_cmd) => {
+ ops.on_start().map_err(Error::TargetError)?;
+ HandlerStatus::NeedsOk
+ }
+ ExtendedMode::R(_cmd) => {
+ ops.restart().map_err(Error::TargetError)?;
+ HandlerStatus::Handled
+ }
+ ExtendedMode::vAttach(cmd) => {
+ ops.attach(cmd.pid).handle_error()?;
+
+ // TODO: sends OK when running in Non-Stop mode
+ HandlerStatus::Handled
+ }
+ ExtendedMode::vRun(cmd) => {
+ use crate::target::ext::extended_mode::Args;
+
+ let _pid = ops
+ .run(cmd.filename, Args::new(&mut cmd.args.into_iter()))
+ .handle_error()?;
+
+ // TODO: send a more descriptive stop packet?
+ res.write_str("S05")?;
+ HandlerStatus::Handled
+ }
+ // --------- ASLR --------- //
+ ExtendedMode::QDisableRandomization(cmd) if ops.configure_aslr().is_some() => {
+ let ops = ops.configure_aslr().unwrap();
+ ops.cfg_aslr(cmd.value).handle_error()?;
+ HandlerStatus::NeedsOk
+ }
+ // --------- Environment --------- //
+ ExtendedMode::QEnvironmentHexEncoded(cmd) if ops.configure_env().is_some() => {
+ let ops = ops.configure_env().unwrap();
+ ops.set_env(cmd.key, cmd.value).handle_error()?;
+ HandlerStatus::NeedsOk
+ }
+ ExtendedMode::QEnvironmentUnset(cmd) if ops.configure_env().is_some() => {
+ let ops = ops.configure_env().unwrap();
+ ops.remove_env(cmd.key).handle_error()?;
+ HandlerStatus::NeedsOk
+ }
+ ExtendedMode::QEnvironmentReset(_cmd) if ops.configure_env().is_some() => {
+ let ops = ops.configure_env().unwrap();
+ ops.reset_env().handle_error()?;
+ HandlerStatus::NeedsOk
+ }
+ // --------- Working Dir --------- //
+ ExtendedMode::QSetWorkingDir(cmd) if ops.configure_working_dir().is_some() => {
+ let ops = ops.configure_working_dir().unwrap();
+ ops.cfg_working_dir(cmd.dir).handle_error()?;
+ HandlerStatus::NeedsOk
+ }
+ // --------- Startup Shell --------- //
+ ExtendedMode::QStartupWithShell(cmd) if ops.configure_startup_shell().is_some() => {
+ let ops = ops.configure_startup_shell().unwrap();
+ ops.cfg_startup_with_shell(cmd.value).handle_error()?;
+ HandlerStatus::NeedsOk
+ }
+ _ => HandlerStatus::Handled,
+ };
+
+ Ok(handler_status)
+ }
+}
diff --git a/src/gdbstub_impl/ext/mod.rs b/src/gdbstub_impl/ext/mod.rs
new file mode 100644
index 0000000..58a99df
--- /dev/null
+++ b/src/gdbstub_impl/ext/mod.rs
@@ -0,0 +1,20 @@
+mod prelude {
+ pub use crate::common::*;
+ pub use crate::connection::Connection;
+ pub use crate::internal::*;
+ pub use crate::target::Target;
+
+ pub(crate) use crate::protocol::ResponseWriter;
+
+ pub(super) use super::super::error::GdbStubError as Error;
+ pub(super) use super::super::target_result_ext::TargetResultExt;
+ pub(super) use super::super::{DisconnectReason, GdbStubImpl, HandlerStatus};
+}
+
+mod base;
+mod breakpoints;
+mod extended_mode;
+mod monitor_cmd;
+mod reverse_exec;
+mod section_offsets;
+mod single_register_access;
diff --git a/src/gdbstub_impl/ext/monitor_cmd.rs b/src/gdbstub_impl/ext/monitor_cmd.rs
new file mode 100644
index 0000000..cfc8821
--- /dev/null
+++ b/src/gdbstub_impl/ext/monitor_cmd.rs
@@ -0,0 +1,48 @@
+use super::prelude::*;
+use crate::protocol::commands::ext::MonitorCmd;
+
+use crate::protocol::ConsoleOutput;
+
+impl<T: Target, C: Connection> GdbStubImpl<T, C> {
+ pub(crate) fn handle_monitor_cmd<'a>(
+ &mut self,
+ res: &mut ResponseWriter<C>,
+ target: &mut T,
+ command: MonitorCmd<'a>,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ let ops = match target.monitor_cmd() {
+ Some(ops) => ops,
+ None => return Ok(HandlerStatus::Handled),
+ };
+
+ crate::__dead_code_marker!("monitor_cmd", "impl");
+
+ let handler_status = match command {
+ MonitorCmd::qRcmd(cmd) => {
+ let mut err: Result<_, Error<T::Error, C::Error>> = Ok(());
+ let mut callback = |msg: &[u8]| {
+ // TODO: replace this with a try block (once stabilized)
+ let e = (|| {
+ let mut res = ResponseWriter::new(res.as_conn());
+ res.write_str("O")?;
+ res.write_hex_buf(msg)?;
+ res.flush()?;
+ Ok(())
+ })();
+
+ if let Err(e) = e {
+ err = Err(e)
+ }
+ };
+
+ ops.handle_monitor_cmd(cmd.hex_cmd, ConsoleOutput::new(&mut callback))
+ .map_err(Error::TargetError)?;
+ err?;
+
+ HandlerStatus::NeedsOk
+ }
+ };
+
+ Ok(handler_status)
+ }
+}
diff --git a/src/gdbstub_impl/ext/reverse_exec.rs b/src/gdbstub_impl/ext/reverse_exec.rs
new file mode 100644
index 0000000..b4035c0
--- /dev/null
+++ b/src/gdbstub_impl/ext/reverse_exec.rs
@@ -0,0 +1,139 @@
+use super::prelude::*;
+use crate::protocol::commands::ext::{ReverseCont, ReverseStep};
+
+use crate::arch::Arch;
+use crate::protocol::SpecificIdKind;
+use crate::target::ext::base::multithread::{MultiThreadReverseCont, MultiThreadReverseStep};
+use crate::target::ext::base::singlethread::{SingleThreadReverseCont, SingleThreadReverseStep};
+use crate::target::ext::base::{BaseOps, GdbInterrupt};
+
+enum ReverseContOps<'a, A: Arch, E> {
+ SingleThread(&'a mut dyn SingleThreadReverseCont<Arch = A, Error = E>),
+ MultiThread(&'a mut dyn MultiThreadReverseCont<Arch = A, Error = E>),
+}
+
+enum ReverseStepOps<'a, A: Arch, E> {
+ SingleThread(&'a mut dyn SingleThreadReverseStep<Arch = A, Error = E>),
+ MultiThread(&'a mut dyn MultiThreadReverseStep<Arch = A, Error = E>),
+}
+
+impl<T: Target, C: Connection> GdbStubImpl<T, C> {
+ pub(crate) fn handle_reverse_cont(
+ &mut self,
+ res: &mut ResponseWriter<C>,
+ target: &mut T,
+ command: ReverseCont,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ // Resolve the reverse-continue operations. Error out if the target does not
+ // support it.
+ let ops = match target.base_ops() {
+ BaseOps::MultiThread(ops) => match ops.support_reverse_cont() {
+ Some(ops) => ReverseContOps::MultiThread(ops),
+ None => return Ok(HandlerStatus::Handled),
+ },
+ BaseOps::SingleThread(ops) => match ops.support_reverse_cont() {
+ Some(ops) => ReverseContOps::SingleThread(ops),
+ None => return Ok(HandlerStatus::Handled),
+ },
+ };
+
+ crate::__dead_code_marker!("reverse_cont", "impl");
+
+ let handler_status = match command {
+ ReverseCont::bc(_) => {
+ // FIXME: This block is duplicated from the vCont code.
+ let mut err = Ok(());
+ let mut check_gdb_interrupt = || match res.as_conn().peek() {
+ Ok(Some(0x03)) => true, // 0x03 is the interrupt byte
+ Ok(Some(_)) => false, // it's nothing that can't wait...
+ Ok(None) => false,
+ Err(e) => {
+ err = Err(Error::ConnectionRead(e));
+ true // break ASAP if a connection error occurred
+ }
+ };
+
+ let stop_reason = match ops {
+ ReverseContOps::MultiThread(ops) => ops
+ .reverse_cont(GdbInterrupt::new(&mut check_gdb_interrupt))
+ .map_err(Error::TargetError)?,
+ ReverseContOps::SingleThread(ops) => ops
+ .reverse_cont(GdbInterrupt::new(&mut check_gdb_interrupt))
+ .map_err(Error::TargetError)?
+ .into(),
+ };
+
+ err?;
+
+ // FIXME: properly handle None case
+ self.finish_exec(res, target, stop_reason)?
+ .ok_or(Error::PacketUnexpected)?
+ }
+ };
+
+ Ok(handler_status)
+ }
+
+ // FIXME: De-duplicate with above code?
+ pub(crate) fn handle_reverse_step(
+ &mut self,
+ res: &mut ResponseWriter<C>,
+ target: &mut T,
+ command: ReverseStep,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ // Resolve the reverse-step operations. Error out if the target does not
+ // support it.
+ let ops = match target.base_ops() {
+ BaseOps::MultiThread(ops) => match ops.support_reverse_step() {
+ Some(ops) => ReverseStepOps::MultiThread(ops),
+ None => return Ok(HandlerStatus::Handled),
+ },
+ BaseOps::SingleThread(ops) => match ops.support_reverse_step() {
+ Some(ops) => ReverseStepOps::SingleThread(ops),
+ None => return Ok(HandlerStatus::Handled),
+ },
+ };
+
+ crate::__dead_code_marker!("reverse_step", "impl");
+
+ let handler_status = match command {
+ ReverseStep::bs(_) => {
+ let tid = match self.current_resume_tid {
+ // NOTE: Can't single-step all cores.
+ SpecificIdKind::All => return Err(Error::PacketUnexpected),
+ SpecificIdKind::WithId(tid) => tid,
+ };
+
+ // FIXME: This block is duplicated from the vCont code.
+ let mut err = Ok(());
+ let mut check_gdb_interrupt = || match res.as_conn().peek() {
+ Ok(Some(0x03)) => true, // 0x03 is the interrupt byte
+ Ok(Some(_)) => false, // it's nothing that can't wait...
+ Ok(None) => false,
+ Err(e) => {
+ err = Err(Error::ConnectionRead(e));
+ true // break ASAP if a connection error occurred
+ }
+ };
+
+ let stop_reason = match ops {
+ ReverseStepOps::MultiThread(ops) => ops
+ .reverse_step(tid, GdbInterrupt::new(&mut check_gdb_interrupt))
+ .map_err(Error::TargetError)?,
+ ReverseStepOps::SingleThread(ops) => ops
+ .reverse_step(GdbInterrupt::new(&mut check_gdb_interrupt))
+ .map_err(Error::TargetError)?
+ .into(),
+ };
+
+ err?;
+
+ // FIXME: properly handle None case
+ self.finish_exec(res, target, stop_reason)?
+ .ok_or(Error::PacketUnexpected)?
+ }
+ };
+
+ Ok(handler_status)
+ }
+}
diff --git a/src/gdbstub_impl/ext/section_offsets.rs b/src/gdbstub_impl/ext/section_offsets.rs
new file mode 100644
index 0000000..3fa7e04
--- /dev/null
+++ b/src/gdbstub_impl/ext/section_offsets.rs
@@ -0,0 +1,57 @@
+use super::prelude::*;
+use crate::protocol::commands::ext::SectionOffsets;
+
+impl<T: Target, C: Connection> GdbStubImpl<T, C> {
+ pub(crate) fn handle_section_offsets(
+ &mut self,
+ res: &mut ResponseWriter<C>,
+ target: &mut T,
+ command: SectionOffsets,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ let ops = match target.section_offsets() {
+ Some(ops) => ops,
+ None => return Ok(HandlerStatus::Handled),
+ };
+
+ crate::__dead_code_marker!("section_offsets", "impl");
+
+ let handler_status = match command {
+ SectionOffsets::qOffsets(_cmd) => {
+ use crate::target::ext::section_offsets::Offsets;
+
+ match ops.get_section_offsets().map_err(Error::TargetError)? {
+ Offsets::Sections { text, data, bss } => {
+ res.write_str("Text=")?;
+ res.write_num(text)?;
+
+ res.write_str(";Data=")?;
+ res.write_num(data)?;
+
+ // "Note: while a Bss offset may be included in the response,
+ // GDB ignores this and instead applies the Data offset to the Bss section."
+ //
+ // While this would suggest that it's OK to omit `Bss=` entirely, recent
+ // versions of GDB seem to require that `Bss=` is present.
+ //
+ // See https://github.com/bminor/binutils-gdb/blob/master/gdb/remote.c#L4149-L4159
+ let bss = bss.unwrap_or(data);
+ res.write_str(";Bss=")?;
+ res.write_num(bss)?;
+ }
+ Offsets::Segments { text_seg, data_seg } => {
+ res.write_str("TextSeg=")?;
+ res.write_num(text_seg)?;
+
+ if let Some(data) = data_seg {
+ res.write_str(";DataSeg=")?;
+ res.write_num(data)?;
+ }
+ }
+ }
+ HandlerStatus::Handled
+ }
+ };
+
+ Ok(handler_status)
+ }
+}
diff --git a/src/gdbstub_impl/ext/single_register_access.rs b/src/gdbstub_impl/ext/single_register_access.rs
new file mode 100644
index 0000000..04ef9f2
--- /dev/null
+++ b/src/gdbstub_impl/ext/single_register_access.rs
@@ -0,0 +1,60 @@
+use super::prelude::*;
+use crate::protocol::commands::ext::SingleRegisterAccess;
+
+use crate::arch::{Arch, RegId};
+use crate::target::ext::base::BaseOps;
+
+impl<T: Target, C: Connection> GdbStubImpl<T, C> {
+ fn inner<Id>(
+ res: &mut ResponseWriter<C>,
+ ops: crate::target::ext::base::SingleRegisterAccessOps<Id, T>,
+ command: SingleRegisterAccess<'_>,
+ id: Id,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ let handler_status = match command {
+ SingleRegisterAccess::p(p) => {
+ let mut dst = [0u8; 32]; // enough for 256-bit registers
+ let reg = <T::Arch as Arch>::RegId::from_raw_id(p.reg_id);
+ let (reg_id, reg_size) = match reg {
+ // empty packet indicates unrecognized query
+ None => return Ok(HandlerStatus::Handled),
+ Some(v) => v,
+ };
+ let dst = &mut dst[0..reg_size];
+ ops.read_register(id, reg_id, dst).handle_error()?;
+
+ res.write_hex_buf(dst)?;
+ HandlerStatus::Handled
+ }
+ SingleRegisterAccess::P(p) => {
+ let reg = <T::Arch as Arch>::RegId::from_raw_id(p.reg_id);
+ match reg {
+ // empty packet indicates unrecognized query
+ None => return Ok(HandlerStatus::Handled),
+ Some((reg_id, _)) => ops.write_register(id, reg_id, p.val).handle_error()?,
+ }
+ HandlerStatus::NeedsOk
+ }
+ };
+
+ Ok(handler_status)
+ }
+
+ pub(crate) fn handle_single_register_access<'a>(
+ &mut self,
+ res: &mut ResponseWriter<C>,
+ target: &mut T,
+ command: SingleRegisterAccess<'a>,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ match target.base_ops() {
+ BaseOps::SingleThread(ops) => match ops.single_register_access() {
+ None => Ok(HandlerStatus::Handled),
+ Some(ops) => Self::inner(res, ops, command, ()),
+ },
+ BaseOps::MultiThread(ops) => match ops.single_register_access() {
+ None => Ok(HandlerStatus::Handled),
+ Some(ops) => Self::inner(res, ops, command, self.current_mem_tid),
+ },
+ }
+ }
+}
diff --git a/src/gdbstub_impl/mod.rs b/src/gdbstub_impl/mod.rs
index 72572fb..364da0e 100644
--- a/src/gdbstub_impl/mod.rs
+++ b/src/gdbstub_impl/mod.rs
@@ -1,42 +1,31 @@
use core::marker::PhantomData;
-#[cfg(feature = "alloc")]
-use alloc::collections::BTreeMap;
-
use managed::ManagedSlice;
use crate::common::*;
-use crate::{
- arch::{Arch, RegId, Registers},
- connection::Connection,
- internal::*,
- protocol::{
- commands::{ext, Command},
- ConsoleOutput, IdKind, Packet, ResponseWriter, ThreadId,
- },
- target::ext::base::multithread::{Actions, ResumeAction, ThreadStopReason, TidSelector},
- target::ext::base::BaseOps,
- target::Target,
- util::managed_vec::ManagedVec,
- FAKE_PID, SINGLE_THREAD_TID,
-};
+use crate::connection::Connection;
+use crate::protocol::{commands::Command, Packet, ResponseWriter, SpecificIdKind};
+use crate::target::Target;
+use crate::util::managed_vec::ManagedVec;
+use crate::SINGLE_THREAD_TID;
mod builder;
mod error;
+mod ext;
mod target_result_ext;
pub use builder::{GdbStubBuilder, GdbStubBuilderError};
pub use error::GdbStubError;
-use target_result_ext::TargetResultExt;
-
use GdbStubError as Error;
/// Describes why the GDB session ended.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DisconnectReason {
- /// Target Halted
- TargetHalted,
+ /// Target exited with given status code
+ TargetExited(u8),
+ /// Target terminated with given signal
+ TargetTerminated(u8),
/// GDB issued a disconnect command
Disconnect,
/// GDB issued a kill command
@@ -82,43 +71,34 @@ struct GdbStubImpl<T: Target, C: Connection> {
_target: PhantomData<T>,
_connection: PhantomData<C>,
- packet_buffer_len: usize,
current_mem_tid: Tid,
- current_resume_tid: TidSelector,
+ current_resume_tid: SpecificIdKind,
no_ack_mode: bool,
-
- // Used to track which Pids were attached to / spawned when running in extended mode.
- //
- // An empty `BTreeMap<Pid, bool>` is only 24 bytes (on 64-bit systems), and doesn't allocate
- // until the first element is inserted, so it should be fine to include it as part of the main
- // state structure whether or not extended mode is actually being used.
- #[cfg(feature = "alloc")]
- attached_pids: BTreeMap<Pid, bool>,
}
enum HandlerStatus {
Handled,
- NeedsOK,
+ NeedsOk,
Disconnect(DisconnectReason),
}
impl<T: Target, C: Connection> GdbStubImpl<T, C> {
- fn new(packet_buffer_len: usize) -> GdbStubImpl<T, C> {
+ fn new() -> GdbStubImpl<T, C> {
GdbStubImpl {
_target: PhantomData,
_connection: PhantomData,
- packet_buffer_len,
- // HACK: current_mem_tid is immediately updated with valid value once `run` is called.
- // While the more idiomatic way to handle this would be to use an Option, given that
- // it's only ever unset prior to the start of `run`, it's probably okay leaving it as-is
- // for code-clarity purposes.
+ // NOTE: `current_mem_tid` and `current_resume_tid` are never queried prior to being set
+ // by the GDB client (via the 'H' packet), so it's fine to use dummy values here.
+ //
+ // The alternative would be to use `Option`, and while this would be more "correct", it
+ // would introduce a _lot_ of noisy and heavy error handling logic all over the place.
+ //
+ // Plus, even if the GDB client is acting strangely and doesn't overwrite these values,
+ // the target will simply return a non-fatal error, which is totally fine.
current_mem_tid: SINGLE_THREAD_TID,
- current_resume_tid: TidSelector::All,
+ current_resume_tid: SpecificIdKind::WithId(SINGLE_THREAD_TID),
no_ack_mode: false,
-
- #[cfg(feature = "alloc")]
- attached_pids: BTreeMap::new(),
}
}
@@ -130,24 +110,6 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
) -> Result<DisconnectReason, Error<T::Error, C::Error>> {
conn.on_session_start().map_err(Error::ConnectionRead)?;
- // before even accepting packets, we query the target to get a sane value for
- // `self.current_mem_tid`.
- // NOTE: this will break if extended mode is ever implemented...
-
- self.current_mem_tid = match target.base_ops() {
- BaseOps::SingleThread(_) => SINGLE_THREAD_TID,
- BaseOps::MultiThread(ops) => {
- let mut first_tid = None;
- ops.list_active_threads(&mut |tid| {
- if first_tid.is_none() {
- first_tid = Some(tid);
- }
- })
- .map_err(Error::TargetError)?;
- first_tid.ok_or(Error::NoActiveThreads)?
- }
- };
-
loop {
match Self::recv_packet(conn, target, packet_buffer)? {
Packet::Ack => {}
@@ -167,7 +129,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
let mut res = ResponseWriter::new(conn);
let disconnect = match self.handle_command(&mut res, target, command) {
Ok(HandlerStatus::Handled) => None,
- Ok(HandlerStatus::NeedsOK) => {
+ Ok(HandlerStatus::NeedsOk) => {
res.write_str("OK")?;
None
}
@@ -231,10 +193,14 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
buf.push(conn.read().map_err(Error::ConnectionRead)?)?;
}
- match Packet::from_buf(target, pkt_buf.as_mut()) {
- Ok(packet) => Ok(packet),
- Err(e) => Err(Error::PacketParse(e)),
- }
+ trace!(
+ "<-- {}",
+ core::str::from_utf8(buf.as_slice()).unwrap_or("<invalid packet>")
+ );
+
+ drop(buf);
+
+ Packet::from_buf(target, pkt_buf.as_mut()).map_err(Error::PacketParse)
}
fn handle_command(
@@ -245,801 +211,22 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
match cmd {
Command::Unknown(cmd) => {
- info!("Unknown command: {}", cmd);
+ // cmd must be ASCII, as the slice originated from a PacketBuf, which checks for
+ // ASCII as part of the initial validation.
+ info!("Unknown command: {}", core::str::from_utf8(cmd).unwrap());
Ok(HandlerStatus::Handled)
}
+ // `handle_X` methods are defined in the `ext` module
Command::Base(cmd) => self.handle_base(res, target, cmd),
+ Command::SingleRegisterAccess(cmd) => {
+ self.handle_single_register_access(res, target, cmd)
+ }
+ Command::Breakpoints(cmd) => self.handle_breakpoints(res, target, cmd),
Command::ExtendedMode(cmd) => self.handle_extended_mode(res, target, cmd),
Command::MonitorCmd(cmd) => self.handle_monitor_cmd(res, target, cmd),
Command::SectionOffsets(cmd) => self.handle_section_offsets(res, target, cmd),
- }
- }
-
- fn handle_base<'a>(
- &mut self,
- res: &mut ResponseWriter<C>,
- target: &mut T,
- command: ext::Base<'a>,
- ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
- let handler_status = match command {
- // ------------------ Handshaking and Queries ------------------- //
- ext::Base::qSupported(cmd) => {
- // XXX: actually read what the client supports, and enable/disable features
- // appropriately
- let _features = cmd.features.into_iter();
-
- res.write_str("PacketSize=")?;
- res.write_num(self.packet_buffer_len)?;
-
- res.write_str(";vContSupported+")?;
- res.write_str(";multiprocess+")?;
- res.write_str(";QStartNoAckMode+")?;
-
- if let Some(ops) = target.extended_mode() {
- if ops.configure_aslr().is_some() {
- res.write_str(";QDisableRandomization+")?;
- }
-
- if ops.configure_env().is_some() {
- res.write_str(";QEnvironmentHexEncoded+")?;
- res.write_str(";QEnvironmentUnset+")?;
- res.write_str(";QEnvironmentReset+")?;
- }
-
- if ops.configure_startup_shell().is_some() {
- res.write_str(";QStartupWithShell+")?;
- }
-
- if ops.configure_working_dir().is_some() {
- res.write_str(";QSetWorkingDir+")?;
- }
- }
-
- res.write_str(";swbreak+")?;
- if target.hw_breakpoint().is_some() || target.hw_watchpoint().is_some() {
- res.write_str(";hwbreak+")?;
- }
-
- // TODO: implement conditional breakpoint support (since that's kool).
- // res.write_str("ConditionalBreakpoints+;")?;
-
- if T::Arch::target_description_xml().is_some()
- || target.target_description_xml_override().is_some()
- {
- res.write_str(";qXfer:features:read+")?;
- }
-
- HandlerStatus::Handled
- }
- ext::Base::QStartNoAckMode(_) => {
- self.no_ack_mode = true;
- HandlerStatus::NeedsOK
- }
- ext::Base::qXferFeaturesRead(cmd) => {
- #[allow(clippy::redundant_closure)]
- let xml = target
- .target_description_xml_override()
- .map(|ops| ops.target_description_xml())
- .or_else(|| T::Arch::target_description_xml());
-
- match xml {
- Some(xml) => {
- let xml = xml.trim();
- if cmd.offset >= xml.len() {
- // no more data
- res.write_str("l")?;
- } else if cmd.offset + cmd.len >= xml.len() {
- // last little bit of data
- res.write_str("l")?;
- res.write_binary(&xml.as_bytes()[cmd.offset..])?
- } else {
- // still more data
- res.write_str("m")?;
- res.write_binary(&xml.as_bytes()[cmd.offset..(cmd.offset + cmd.len)])?
- }
- }
- // If the target hasn't provided their own XML, then the initial response to
- // "qSupported" wouldn't have included "qXfer:features:read", and gdb wouldn't
- // send this packet unless it was explicitly marked as supported.
- None => return Err(Error::PacketUnexpected),
- }
- HandlerStatus::Handled
- }
-
- // -------------------- "Core" Functionality -------------------- //
- // TODO: Improve the '?' response based on last-sent stop reason.
- ext::Base::QuestionMark(_) => {
- res.write_str("S05")?;
- HandlerStatus::Handled
- }
- ext::Base::qAttached(cmd) => {
- let is_attached = match target.extended_mode() {
- // when _not_ running in extended mode, just report that we're attaching to an
- // existing process.
- None => true, // assume attached to an existing process
- // When running in extended mode, we must defer to the target
- Some(ops) => {
- let pid: Pid = cmd.pid.ok_or(Error::PacketUnexpected)?;
-
- #[cfg(feature = "alloc")]
- {
- let _ = ops; // doesn't actually query the target
- *self.attached_pids.get(&pid).unwrap_or(&true)
- }
-
- #[cfg(not(feature = "alloc"))]
- {
- ops.query_if_attached(pid).handle_error()?.was_attached()
- }
- }
- };
- res.write_str(if is_attached { "1" } else { "0" })?;
- HandlerStatus::Handled
- }
- ext::Base::g(_) => {
- let mut regs: <T::Arch as Arch>::Registers = Default::default();
- match target.base_ops() {
- BaseOps::SingleThread(ops) => ops.read_registers(&mut regs),
- BaseOps::MultiThread(ops) => {
- ops.read_registers(&mut regs, self.current_mem_tid)
- }
- }
- .handle_error()?;
-
- let mut err = Ok(());
- regs.gdb_serialize(|val| {
- let res = match val {
- Some(b) => res.write_hex_buf(&[b]),
- None => res.write_str("xx"),
- };
- if let Err(e) = res {
- err = Err(e);
- }
- });
- err?;
- HandlerStatus::Handled
- }
- ext::Base::G(cmd) => {
- let mut regs: <T::Arch as Arch>::Registers = Default::default();
- regs.gdb_deserialize(cmd.vals)
- .map_err(|_| Error::TargetMismatch)?;
-
- match target.base_ops() {
- BaseOps::SingleThread(ops) => ops.write_registers(&regs),
- BaseOps::MultiThread(ops) => ops.write_registers(&regs, self.current_mem_tid),
- }
- .handle_error()?;
-
- HandlerStatus::NeedsOK
- }
- ext::Base::m(cmd) => {
- let buf = cmd.buf;
- let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
- .ok_or(Error::TargetMismatch)?;
-
- let mut i = 0;
- let mut n = cmd.len;
- while n != 0 {
- let chunk_size = n.min(buf.len());
-
- use num_traits::NumCast;
-
- let addr = addr + NumCast::from(i).ok_or(Error::TargetMismatch)?;
- let data = &mut buf[..chunk_size];
- match target.base_ops() {
- BaseOps::SingleThread(ops) => ops.read_addrs(addr, data),
- BaseOps::MultiThread(ops) => {
- ops.read_addrs(addr, data, self.current_mem_tid)
- }
- }
- .handle_error()?;
-
- n -= chunk_size;
- i += chunk_size;
-
- res.write_hex_buf(data)?;
- }
- HandlerStatus::Handled
- }
- ext::Base::M(cmd) => {
- let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
- .ok_or(Error::TargetMismatch)?;
-
- match target.base_ops() {
- BaseOps::SingleThread(ops) => ops.write_addrs(addr, cmd.val),
- BaseOps::MultiThread(ops) => {
- ops.write_addrs(addr, cmd.val, self.current_mem_tid)
- }
- }
- .handle_error()?;
-
- HandlerStatus::NeedsOK
- }
- ext::Base::k(_) | ext::Base::vKill(_) => {
- match target.extended_mode() {
- // When not running in extended mode, stop the `GdbStub` and disconnect.
- None => HandlerStatus::Disconnect(DisconnectReason::Kill),
-
- // When running in extended mode, a kill command does not necessarily result in
- // a disconnect...
- Some(ops) => {
- let pid = match command {
- ext::Base::vKill(cmd) => Some(cmd.pid),
- _ => None,
- };
-
- let should_terminate = ops.kill(pid).handle_error()?;
- if should_terminate.into() {
- // manually write OK, since we need to return a DisconnectReason
- res.write_str("OK")?;
- HandlerStatus::Disconnect(DisconnectReason::Kill)
- } else {
- HandlerStatus::NeedsOK
- }
- }
- }
- }
- ext::Base::D(_) => {
- // TODO: plumb-through Pid when exposing full multiprocess + extended mode
- res.write_str("OK")?; // manually write OK, since we need to return a DisconnectReason
- HandlerStatus::Disconnect(DisconnectReason::Disconnect)
- }
- ext::Base::Z(cmd) => {
- let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
- .ok_or(Error::TargetMismatch)?;
-
- use crate::target::ext::breakpoints::WatchKind::*;
- let supported = match cmd.type_ {
- 0 => (target.sw_breakpoint()).map(|op| op.add_sw_breakpoint(addr)),
- 1 => (target.hw_breakpoint()).map(|op| op.add_hw_breakpoint(addr)),
- 2 => (target.hw_watchpoint()).map(|op| op.add_hw_watchpoint(addr, Write)),
- 3 => (target.hw_watchpoint()).map(|op| op.add_hw_watchpoint(addr, Read)),
- 4 => (target.hw_watchpoint()).map(|op| op.add_hw_watchpoint(addr, ReadWrite)),
- // only 5 types in the protocol
- _ => None,
- };
-
- match supported {
- None => HandlerStatus::Handled,
- Some(Err(e)) => {
- Err(e).handle_error()?;
- HandlerStatus::Handled
- }
- Some(Ok(true)) => HandlerStatus::NeedsOK,
- Some(Ok(false)) => return Err(Error::NonFatalError(22)),
- }
- }
- ext::Base::z(cmd) => {
- let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
- .ok_or(Error::TargetMismatch)?;
-
- use crate::target::ext::breakpoints::WatchKind::*;
- let supported = match cmd.type_ {
- 0 => (target.sw_breakpoint()).map(|op| op.remove_sw_breakpoint(addr)),
- 1 => (target.hw_breakpoint()).map(|op| op.remove_hw_breakpoint(addr)),
- 2 => (target.hw_watchpoint()).map(|op| op.remove_hw_watchpoint(addr, Write)),
- 3 => (target.hw_watchpoint()).map(|op| op.remove_hw_watchpoint(addr, Read)),
- 4 => {
- (target.hw_watchpoint()).map(|op| op.remove_hw_watchpoint(addr, ReadWrite))
- }
- // only 5 types in the protocol
- _ => None,
- };
-
- match supported {
- None => HandlerStatus::Handled,
- Some(Err(e)) => {
- Err(e).handle_error()?;
- HandlerStatus::Handled
- }
- Some(Ok(true)) => HandlerStatus::NeedsOK,
- Some(Ok(false)) => return Err(Error::NonFatalError(22)),
- }
- }
- ext::Base::p(p) => {
- let mut dst = [0u8; 32]; // enough for 256-bit registers
- let reg = <T::Arch as Arch>::RegId::from_raw_id(p.reg_id);
- let (reg_id, reg_size) = match reg {
- Some(v) => v,
- // empty packet indicates unrecognized query
- None => return Ok(HandlerStatus::Handled),
- };
- let dst = &mut dst[0..reg_size];
- match target.base_ops() {
- BaseOps::SingleThread(ops) => ops.read_register(reg_id, dst),
- BaseOps::MultiThread(ops) => {
- ops.read_register(reg_id, dst, self.current_mem_tid)
- }
- }
- .handle_error()?;
-
- res.write_hex_buf(dst)?;
- HandlerStatus::Handled
- }
- ext::Base::P(p) => {
- let reg = <T::Arch as Arch>::RegId::from_raw_id(p.reg_id);
- match reg {
- None => return Err(Error::NonFatalError(22)),
- Some((reg_id, _)) => match target.base_ops() {
- BaseOps::SingleThread(ops) => ops.write_register(reg_id, p.val),
- BaseOps::MultiThread(ops) => {
- ops.write_register(reg_id, p.val, self.current_mem_tid)
- }
- }
- .handle_error()?,
- }
- HandlerStatus::NeedsOK
- }
- ext::Base::vCont(cmd) => {
- use crate::protocol::commands::_vCont::{vCont, VContKind};
-
- let actions = match cmd {
- vCont::Query => {
- res.write_str("vCont;c;C;s;S")?;
- return Ok(HandlerStatus::Handled);
- }
- vCont::Actions(actions) => actions,
- };
-
- // map raw vCont action iterator to a format the `Target` expects
- let mut err = Ok(());
- let mut actions = actions.into_iter().filter_map(|action| {
- let action = match action {
- Some(action) => action,
- None => {
- err = Err(Error::PacketParse(
- crate::protocol::PacketParseError::MalformedCommand,
- ));
- return None;
- }
- };
-
- let resume_action = match action.kind {
- VContKind::Step => ResumeAction::Step,
- VContKind::Continue => ResumeAction::Continue,
- _ => {
- // there seems to be a GDB bug where it doesn't use `vCont` unless
- // `vCont?` returns support for resuming with a signal.
- //
- // This error case can be removed once "Resume with Signal" is
- // implemented
- err = Err(Error::ResumeWithSignalUnimplemented);
- return None;
- }
- };
-
- let tid = match action.thread {
- Some(thread) => match thread.tid {
- IdKind::Any => {
- err = Err(Error::PacketUnexpected);
- return None;
- }
- IdKind::All => TidSelector::All,
- IdKind::WithID(tid) => TidSelector::WithID(tid),
- },
- // An action with no thread-id matches all threads
- None => TidSelector::All,
- };
-
- Some((tid, resume_action))
- });
-
- let ret = match self.do_vcont(res, target, &mut actions) {
- Ok(None) => HandlerStatus::Handled,
- Ok(Some(dc)) => HandlerStatus::Disconnect(dc),
- Err(e) => return Err(e),
- };
- err?;
- ret
- }
- // TODO?: support custom resume addr in 'c' and 's'
- ext::Base::c(_) => {
- match self.do_vcont(
- res,
- target,
- &mut core::iter::once((self.current_resume_tid, ResumeAction::Continue)),
- ) {
- Ok(None) => HandlerStatus::Handled,
- Ok(Some(dc)) => HandlerStatus::Disconnect(dc),
- Err(e) => return Err(e),
- }
- }
- ext::Base::s(_) => {
- match self.do_vcont(
- res,
- target,
- &mut core::iter::once((self.current_resume_tid, ResumeAction::Step)),
- ) {
- Ok(None) => HandlerStatus::Handled,
- Ok(Some(dc)) => HandlerStatus::Disconnect(dc),
- Err(e) => return Err(e),
- }
- }
-
- // ------------------- Multi-threading Support ------------------ //
- ext::Base::H(cmd) => {
- use crate::protocol::commands::_h_upcase::Op;
- match cmd.kind {
- Op::Other => match cmd.thread.tid {
- IdKind::Any => {} // reuse old tid
- // "All" threads doesn't make sense for memory accesses
- IdKind::All => return Err(Error::PacketUnexpected),
- IdKind::WithID(tid) => self.current_mem_tid = tid,
- },
- // technically, this variant is deprecated in favor of vCont...
- Op::StepContinue => match cmd.thread.tid {
- IdKind::Any => {} // reuse old tid
- IdKind::All => self.current_resume_tid = TidSelector::All,
- IdKind::WithID(tid) => self.current_resume_tid = TidSelector::WithID(tid),
- },
- }
- HandlerStatus::NeedsOK
- }
- ext::Base::qfThreadInfo(_) => {
- res.write_str("m")?;
-
- match target.base_ops() {
- BaseOps::SingleThread(_) => res.write_thread_id(ThreadId {
- pid: Some(IdKind::WithID(FAKE_PID)),
- tid: IdKind::WithID(SINGLE_THREAD_TID),
- })?,
- BaseOps::MultiThread(ops) => {
- let mut err: Result<_, Error<T::Error, C::Error>> = Ok(());
- let mut first = true;
- ops.list_active_threads(&mut |tid| {
- // TODO: replace this with a try block (once stabilized)
- let e = (|| {
- if !first {
- res.write_str(",")?
- }
- first = false;
- res.write_thread_id(ThreadId {
- pid: Some(IdKind::WithID(FAKE_PID)),
- tid: IdKind::WithID(tid),
- })?;
- Ok(())
- })();
-
- if let Err(e) = e {
- err = Err(e)
- }
- })
- .map_err(Error::TargetError)?;
- err?;
- }
- }
-
- HandlerStatus::Handled
- }
- ext::Base::qsThreadInfo(_) => {
- res.write_str("l")?;
- HandlerStatus::Handled
- }
- ext::Base::T(cmd) => {
- let alive = match cmd.thread.tid {
- IdKind::WithID(tid) => match target.base_ops() {
- BaseOps::SingleThread(_) => tid == SINGLE_THREAD_TID,
- BaseOps::MultiThread(ops) => {
- ops.is_thread_alive(tid).map_err(Error::TargetError)?
- }
- },
- // TODO: double-check if GDB ever sends other variants
- // Even after ample testing, this arm has never been hit...
- _ => return Err(Error::PacketUnexpected),
- };
- if alive {
- HandlerStatus::NeedsOK
- } else {
- // any error code will do
- return Err(Error::NonFatalError(1));
- }
- }
- };
- Ok(handler_status)
- }
-
- fn handle_monitor_cmd<'a>(
- &mut self,
- res: &mut ResponseWriter<C>,
- target: &mut T,
- command: ext::MonitorCmd<'a>,
- ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
- let ops = match target.monitor_cmd() {
- Some(ops) => ops,
- None => return Ok(HandlerStatus::Handled),
- };
-
- let handler_status = match command {
- ext::MonitorCmd::qRcmd(cmd) => {
- crate::__dead_code_marker!("qRcmd", "impl");
-
- let mut err: Result<_, Error<T::Error, C::Error>> = Ok(());
- let mut callback = |msg: &[u8]| {
- // TODO: replace this with a try block (once stabilized)
- let e = (|| {
- let mut res = ResponseWriter::new(res.as_conn());
- res.write_str("O")?;
- res.write_hex_buf(msg)?;
- res.flush()?;
- Ok(())
- })();
-
- if let Err(e) = e {
- err = Err(e)
- }
- };
-
- ops.handle_monitor_cmd(cmd.hex_cmd, ConsoleOutput::new(&mut callback))
- .map_err(Error::TargetError)?;
- err?;
-
- HandlerStatus::NeedsOK
- }
- };
-
- Ok(handler_status)
- }
-
- fn handle_section_offsets(
- &mut self,
- res: &mut ResponseWriter<C>,
- target: &mut T,
- command: ext::SectionOffsets,
- ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
- let ops = match target.section_offsets() {
- Some(ops) => ops,
- None => return Ok(HandlerStatus::Handled),
- };
-
- let handler_status = match command {
- ext::SectionOffsets::qOffsets(_cmd) => {
- use crate::target::ext::section_offsets::Offsets;
-
- crate::__dead_code_marker!("qOffsets", "impl");
-
- match ops.get_section_offsets().map_err(Error::TargetError)? {
- Offsets::Sections { text, data, bss } => {
- res.write_str("Text=")?;
- res.write_num(text)?;
-
- res.write_str(";Data=")?;
- res.write_num(data)?;
-
- // "Note: while a Bss offset may be included in the response,
- // GDB ignores this and instead applies the Data offset to the Bss section."
- //
- // While this would suggest that it's OK to omit `Bss=` entirely, recent
- // versions of GDB seem to require that `Bss=` is present.
- //
- // See https://github.com/bminor/binutils-gdb/blob/master/gdb/remote.c#L4149-L4159
- let bss = bss.unwrap_or(data);
- res.write_str(";Bss=")?;
- res.write_num(bss)?;
- }
- Offsets::Segments { text_seg, data_seg } => {
- res.write_str("TextSeg=")?;
- res.write_num(text_seg)?;
-
- if let Some(data) = data_seg {
- res.write_str(";DataSeg=")?;
- res.write_num(data)?;
- }
- }
- }
- HandlerStatus::Handled
- }
- };
-
- Ok(handler_status)
- }
-
- fn handle_extended_mode<'a>(
- &mut self,
- res: &mut ResponseWriter<C>,
- target: &mut T,
- command: ext::ExtendedMode<'a>,
- ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
- let ops = match target.extended_mode() {
- Some(ops) => ops,
- None => return Ok(HandlerStatus::Handled),
- };
-
- let handler_status = match command {
- ext::ExtendedMode::ExclamationMark(_cmd) => {
- ops.on_start().map_err(Error::TargetError)?;
- HandlerStatus::NeedsOK
- }
- ext::ExtendedMode::R(_cmd) => {
- ops.restart().map_err(Error::TargetError)?;
- HandlerStatus::Handled
- }
- ext::ExtendedMode::vAttach(cmd) => {
- ops.attach(cmd.pid).handle_error()?;
-
- #[cfg(feature = "alloc")]
- self.attached_pids.insert(cmd.pid, true);
-
- // TODO: sends OK when running in Non-Stop mode
- HandlerStatus::Handled
- }
- ext::ExtendedMode::vRun(cmd) => {
- use crate::target::ext::extended_mode::Args;
-
- let mut pid = ops
- .run(cmd.filename, Args::new(&mut cmd.args.into_iter()))
- .handle_error()?;
-
- // on single-threaded systems, we'll ignore the provided PID and keep
- // using the FAKE_PID.
- if let BaseOps::SingleThread(_) = target.base_ops() {
- pid = FAKE_PID;
- }
-
- let _ = pid; // squelch warning on no_std targets
- #[cfg(feature = "alloc")]
- self.attached_pids.insert(pid, false);
-
- // TODO: send a more descriptive stop packet?
- res.write_str("S05")?;
- HandlerStatus::Handled
- }
- // --------- ASLR --------- //
- ext::ExtendedMode::QDisableRandomization(cmd) if ops.configure_aslr().is_some() => {
- let ops = ops.configure_aslr().unwrap();
- ops.cfg_aslr(cmd.value).handle_error()?;
- HandlerStatus::NeedsOK
- }
- // --------- Environment --------- //
- ext::ExtendedMode::QEnvironmentHexEncoded(cmd) if ops.configure_env().is_some() => {
- let ops = ops.configure_env().unwrap();
- ops.set_env(cmd.key, cmd.value).handle_error()?;
- HandlerStatus::NeedsOK
- }
- ext::ExtendedMode::QEnvironmentUnset(cmd) if ops.configure_env().is_some() => {
- let ops = ops.configure_env().unwrap();
- ops.remove_env(cmd.key).handle_error()?;
- HandlerStatus::NeedsOK
- }
- ext::ExtendedMode::QEnvironmentReset(_cmd) if ops.configure_env().is_some() => {
- let ops = ops.configure_env().unwrap();
- ops.reset_env().handle_error()?;
- HandlerStatus::NeedsOK
- }
- // --------- Working Dir --------- //
- ext::ExtendedMode::QSetWorkingDir(cmd) if ops.configure_working_dir().is_some() => {
- let ops = ops.configure_working_dir().unwrap();
- ops.cfg_working_dir(cmd.dir).handle_error()?;
- HandlerStatus::NeedsOK
- }
- // --------- Startup Shell --------- //
- ext::ExtendedMode::QStartupWithShell(cmd)
- if ops.configure_startup_shell().is_some() =>
- {
- let ops = ops.configure_startup_shell().unwrap();
- ops.cfg_startup_with_shell(cmd.value).handle_error()?;
- HandlerStatus::NeedsOK
- }
- _ => HandlerStatus::Handled,
- };
-
- Ok(handler_status)
- }
-
- fn do_vcont(
- &mut self,
- res: &mut ResponseWriter<C>,
- target: &mut T,
- actions: &mut dyn Iterator<Item = (TidSelector, ResumeAction)>,
- ) -> Result<Option<DisconnectReason>, Error<T::Error, C::Error>> {
- let mut err = Ok(());
-
- let mut check_gdb_interrupt = || match res.as_conn().peek() {
- Ok(Some(0x03)) => true, // 0x03 is the interrupt byte
- Ok(Some(_)) => false, // it's nothing that can't wait...
- Ok(None) => false,
- Err(e) => {
- err = Err(Error::ConnectionRead(e));
- true // break ASAP if a connection error occurred
- }
- };
-
- let stop_reason = match target.base_ops() {
- BaseOps::SingleThread(ops) => ops
- .resume(
- // TODO?: add a more descriptive error if vcont has multiple threads in
- // single-threaded mode?
- actions.next().ok_or(Error::PacketUnexpected)?.1,
- &mut check_gdb_interrupt,
- )
- .map_err(Error::TargetError)?
- .into(),
- BaseOps::MultiThread(ops) => ops
- .resume(Actions::new(actions), &mut check_gdb_interrupt)
- .map_err(Error::TargetError)?,
- };
-
- err?;
-
- self.finish_vcont(stop_reason, res)
- }
-
- // DEVNOTE: `do_vcont` and `finish_vcont` could be merged into a single
- // function, at the expense of slightly larger code. In the future, if the
- // `vCont` machinery is re-written, there's no reason why the two functions
- // couldn't be re-merged.
-
- fn finish_vcont(
- &mut self,
- stop_reason: ThreadStopReason<<T::Arch as Arch>::Usize>,
- res: &mut ResponseWriter<C>,
- ) -> Result<Option<DisconnectReason>, Error<T::Error, C::Error>> {
- match stop_reason {
- ThreadStopReason::DoneStep | ThreadStopReason::GdbInterrupt => {
- res.write_str("S05")?;
- Ok(None)
- }
- ThreadStopReason::Signal(code) => {
- res.write_str("S")?;
- res.write_num(code)?;
- Ok(None)
- }
- ThreadStopReason::Halted => {
- res.write_str("W19")?; // SIGSTOP
- Ok(Some(DisconnectReason::TargetHalted))
- }
- ThreadStopReason::SwBreak(tid)
- | ThreadStopReason::HwBreak(tid)
- | ThreadStopReason::Watch { tid, .. } => {
- self.current_mem_tid = tid;
- self.current_resume_tid = TidSelector::WithID(tid);
-
- res.write_str("T05")?;
-
- res.write_str("thread:")?;
- res.write_thread_id(ThreadId {
- pid: Some(IdKind::WithID(FAKE_PID)),
- tid: IdKind::WithID(tid),
- })?;
- res.write_str(";")?;
-
- match stop_reason {
- // don't include addr on sw/hw break
- ThreadStopReason::SwBreak(_) => res.write_str("swbreak:")?,
- ThreadStopReason::HwBreak(_) => res.write_str("hwbreak:")?,
- ThreadStopReason::Watch { kind, addr, .. } => {
- use crate::target::ext::breakpoints::WatchKind;
- match kind {
- WatchKind::Write => res.write_str("watch:")?,
- WatchKind::Read => res.write_str("rwatch:")?,
- WatchKind::ReadWrite => res.write_str("awatch:")?,
- }
- res.write_num(addr)?;
- }
- _ => unreachable!(),
- };
-
- res.write_str(";")?;
- Ok(None)
- }
- }
- }
-}
-
-use crate::target::ext::base::singlethread::StopReason;
-impl<U> From<StopReason<U>> for ThreadStopReason<U> {
- fn from(st_stop_reason: StopReason<U>) -> ThreadStopReason<U> {
- match st_stop_reason {
- StopReason::DoneStep => ThreadStopReason::DoneStep,
- StopReason::GdbInterrupt => ThreadStopReason::GdbInterrupt,
- StopReason::Halted => ThreadStopReason::Halted,
- StopReason::SwBreak => ThreadStopReason::SwBreak(SINGLE_THREAD_TID),
- StopReason::HwBreak => ThreadStopReason::HwBreak(SINGLE_THREAD_TID),
- StopReason::Watch { kind, addr } => ThreadStopReason::Watch {
- tid: SINGLE_THREAD_TID,
- kind,
- addr,
- },
- StopReason::Signal(sig) => ThreadStopReason::Signal(sig),
+ Command::ReverseCont(cmd) => self.handle_reverse_cont(res, target, cmd),
+ Command::ReverseStep(cmd) => self.handle_reverse_step(res, target, cmd),
}
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 451d380..fc9ce17 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,63 +2,50 @@
//! [GDB Remote Serial Protocol](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html#Remote-Protocol)
//! in Rust, with full `#![no_std]` support.
//!
-//! ## Feature flags
-//!
-//! By default, the `std` and `alloc` features are enabled.
-//!
-//! When using `gdbstub` in `#![no_std]` contexts, make sure to set
-//! `default-features = false`.
-//!
-//! - `alloc`
-//! - Implement `Connection` for `Box<dyn Connection>`.
-//! - Log outgoing packets via `log::trace!` (uses a heap-allocated output
-//! buffer).
-//! - Provide built-in implementations for certain protocol features:
-//! - Use a heap-allocated packet buffer in `GdbStub` (if none is
-//! provided via `GdbStubBuilder::with_packet_buffer`).
-//! - (Monitor Command) Use a heap-allocated output buffer in
-//! `ConsoleOutput`.
-//! - (Extended Mode) Automatically track Attached/Spawned PIDs without
-//! implementing `ExtendedMode::query_if_attached`.
-//! - `std` (implies `alloc`)
-//! - Implement `Connection` for [`TcpStream`](std::net::TcpStream) and
-//! [`UnixStream`](std::os::unix::net::UnixStream).
-//! - Implement [`std::error::Error`] for `gdbstub::Error`.
-//! - Add a `TargetError::Io` error variant to simplify I/O Error handling
-//! from `Target` methods.
-//!
//! ## Getting Started
//!
//! This section provides a brief overview of the key traits and types used in
//! `gdbstub`, and walks though the basic steps required to integrate `gdbstub`
//! into a project.
//!
-//! It is **highly recommended** to take a look at some of the
-//! [**examples**](https://github.com/daniel5151/gdbstub/blob/master/README.md#examples)
-//! listed in the project README. In particular, the included
-//! [**`armv4t`**](https://github.com/daniel5151/gdbstub/tree/master/examples/armv4t)
-//! example implements most of `gdbstub`'s protocol extensions, and can be a
-//! valuable resource when getting up-and-running with `gdbstub`.
+//! At a high level, there are only two things that are required to get up and
+//! running with `gdbstub`: a [`Connection`](#the-connection-trait), and a
+//! [`Target`](#the-target-trait)
+//!
+//! > _Note:_ I _highly recommended_ referencing some of the
+//! [examples](https://github.com/daniel5151/gdbstub/blob/master/README.md#examples)
+//! listed in the project README when integrating `gdbstub` into a project for
+//! the first time.
+//!
+//! > In particular, the in-tree
+//! [`armv4t`](https://github.com/daniel5151/gdbstub/tree/master/examples/armv4t)
+//! example contains basic implementations off almost all protocol extensions,
+//! making it an incredibly valuable reference when implementing protocol
+//! extensions.
//!
//! ### The `Connection` Trait
//!
-//! The [`Connection`] trait describes how `gdbstub` should communicate with the
-//! main GDB process.
+//! First things first: `gdbstub` needs some way to communicate with a GDB
+//! client. To facilitate this communication, `gdbstub` uses a custom
+//! [`Connection`] trait.
//!
//! `Connection` is automatically implemented for common `std` types such as
//! [`TcpStream`](std::net::TcpStream) and
-//! [`UnixStream`](std::os::unix::net::UnixStream). In `#![no_std]`
-//! environments, `Connection` must be manually implemented using whatever
-//! in-order, serial, byte-wise I/O the hardware has available (e.g:
-//! putchar/getchar over UART, an embedded TCP stack, etc.).
+//! [`UnixStream`](std::os::unix::net::UnixStream).
//!
-//! A common way to start a remote debugging session is to wait for a GDB client
-//! to connect via TCP:
+//! If you're using `gdbstub` in a `#![no_std]` environment, `Connection` will
+//! most likely need to be manually implemented on top of whatever in-order,
+//! serial, byte-wise I/O your particular platform has available (e.g:
+//! putchar/getchar over UART, using an embedded TCP stack, etc.).
+//!
+//! One common way to start a remote debugging session is to simply wait for a
+//! GDB client to connect via TCP:
//!
//! ```rust
+//! use std::io;
//! use std::net::{TcpListener, TcpStream};
//!
-//! fn wait_for_gdb_connection(port: u16) -> std::io::Result<TcpStream> {
+//! fn wait_for_gdb_connection(port: u16) -> io::Result<TcpStream> {
//! let sockaddr = format!("localhost:{}", port);
//! eprintln!("Waiting for a GDB connection on {:?}...", sockaddr);
//! let sock = TcpListener::bind(sockaddr)?;
@@ -68,7 +55,7 @@
//! // i.e: Running `target remote localhost:<port>` from the GDB prompt.
//!
//! eprintln!("Debugger connected from {}", addr);
-//! Ok(stream)
+//! Ok(stream) // `TcpStream` implements `gdbstub::Connection`
//! }
//! ```
//!
@@ -76,23 +63,26 @@
//!
//! The [`Target`](target::Target) trait describes how to control and modify
//! a system's execution state during a GDB debugging session, and serves as the
-//! primary bridge between `gdbstub`'s generic protocol implementation and a
-//! target's project/platform-specific code.
+//! primary bridge between `gdbstub`'s generic GDB protocol implementation and a
+//! specific target's project/platform-specific code.
//!
-//! For example: the `Target` trait includes a method called `read_registers()`,
-//! which the `GdbStub` calls whenever the GDB client queries the state of the
-//! target's registers.
+//! At a high level, the `Target` trait is a collection of user-defined handler
+//! methods that the GDB client can invoke via the GDB remote serial protocol.
+//! For example, the `Target` trait includes methods to read/write
+//! registers/memory, start/stop execution, etc...
//!
//! **`Target` is the most important trait in `gdbstub`, and must be implemented
-//! by anyone who uses the library!**
+//! by anyone integrating `gdbstub` into their project!**
//!
//! Please refer to the [`target` module documentation](target) for in-depth
-//! instructions on implementing `Target`.
+//! instructions on how to implement [`Target`](target::Target) for a particular
+//! platform.
//!
//! ### Starting the debugging session using `GdbStub`
//!
-//! Once a `Connection` has been established and `Target` has been all wired up,
-//! all that's left is to hand things off to [`GdbStub`] and let it do the rest!
+//! Once a [`Connection`](#the-connection-trait) has been established and
+//! [`Target`](#the-target-trait) has been all wired up, all that's left is to
+//! hand things off to [`gdbstub::GdbStub`](GdbStub) and let it do the rest!
//!
//! ```rust,ignore
//! // Set-up a valid `Target`
@@ -101,8 +91,8 @@
//! // Establish a `Connection`
//! let connection: TcpStream = wait_for_gdb_connection(9001);
//!
-//! // Create a new `GdbStub` using the established `Connection`.
-//! let mut debugger = GdbStub::new(connection);
+//! // Create a new `gdbstub::GdbStub` using the established `Connection`.
+//! let mut debugger = gdbstub::GdbStub::new(connection);
//!
//! // Instead of taking ownership of the system, `GdbStub` takes a &mut, yielding
//! // ownership back to the caller once the debugging session is closed.
@@ -115,13 +105,36 @@
//! // Handle any target-specific errors
//! Err(GdbStubError::TargetError(e)) => {
//! println!("Target raised a fatal error: {:?}", e);
-//! // e.g: re-enter the debugging session after "freezing" a system to
-//! // conduct some post-mortem debugging
+//! // `gdbstub` will not immediate close the debugging session if a
+//! // fatal error occurs, enabling "post mortem" debugging if required.
//! debugger.run(&mut target)?;
//! }
//! Err(e) => return Err(e.into())
//! }
//! ```
+//!
+//! ## Feature flags
+//!
+//! By default, both the `std` and `alloc` features are enabled.
+//!
+//! When using `gdbstub` in `#![no_std]` contexts, make sure to set
+//! `default-features = false`.
+//!
+//! - `alloc`
+//! - Implement `Connection` for `Box<dyn Connection>`.
+//! - Log outgoing packets via `log::trace!` (uses a heap-allocated output
+//! buffer).
+//! - Provide built-in implementations for certain protocol features:
+//! - Use a heap-allocated packet buffer in `GdbStub` (if none is
+//! provided via `GdbStubBuilder::with_packet_buffer`).
+//! - (Monitor Command) Use a heap-allocated output buffer in
+//! `ConsoleOutput`.
+//! - `std` (implies `alloc`)
+//! - Implement `Connection` for [`TcpStream`](std::net::TcpStream) and
+//! [`UnixStream`](std::os::unix::net::UnixStream).
+//! - Implement [`std::error::Error`] for `gdbstub::Error`.
+//! - Add a `TargetError::Io` error variant to simplify I/O Error handling
+//! from `Target` methods.
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
diff --git a/src/protocol/commands.rs b/src/protocol/commands.rs
index aa39ca8..1fd1810 100644
--- a/src/protocol/commands.rs
+++ b/src/protocol/commands.rs
@@ -6,7 +6,11 @@ use crate::target::Target;
pub(self) mod prelude {
pub use super::ParseCommand;
pub use crate::common::*;
- pub use crate::protocol::common::*;
+ pub use crate::protocol::common::hex::{decode_hex, decode_hex_buf, is_hex, HexString};
+ pub use crate::protocol::common::thread_id::{
+ IdKind, SpecificIdKind, SpecificThreadId, ThreadId,
+ };
+ pub use crate::protocol::common::Bstr;
pub use crate::protocol::packet::PacketBuf;
pub use core::convert::{TryFrom, TryInto};
}
@@ -16,6 +20,13 @@ pub trait ParseCommand<'a>: Sized {
fn from_packet(buf: PacketBuf<'a>) -> Option<Self>;
}
+// Breakpoint packets are special-cased, as the "Z" packet is parsed differently
+// depending on whether or not the target implements the `Agent` extension.
+//
+// While it's entirely possible to eagerly parse the "Z" packet for bytecode,
+// doing so would unnecessary bloat implementations that do not support
+// evaluating agent expressions.
+
macro_rules! commands {
(
$(
@@ -28,6 +39,7 @@ macro_rules! commands {
#[allow(non_snake_case, non_camel_case_types)]
pub mod $mod;
)*)*
+ pub mod breakpoint;
pub mod ext {
$(
@@ -36,6 +48,15 @@ macro_rules! commands {
$($command(super::$mod::$command<$($lifetime)?>),)*
}
)*
+
+ use super::breakpoint::{BasicBreakpoint, BytecodeBreakpoint};
+ #[allow(non_camel_case_types)]
+ pub enum Breakpoints<'a> {
+ z(BasicBreakpoint<'a>),
+ Z(BasicBreakpoint<'a>),
+ ZWithBytecode(BytecodeBreakpoint<'a>),
+ }
+
}
/// GDB commands
@@ -43,63 +64,102 @@ macro_rules! commands {
$(
[<$ext:camel>](ext::[<$ext:camel>]$(<$lt>)?),
)*
- Unknown(&'a str),
+ Breakpoints(ext::Breakpoints<'a>),
+ Unknown(&'a [u8]),
}
impl<'a> Command<'a> {
pub fn from_packet(
target: &mut impl Target,
- buf: PacketBuf<'a>
- ) -> Result<Command<'a>, CommandParseError<'a>> {
- if buf.as_body().is_empty() {
- return Err(CommandParseError::Empty);
+ mut buf: PacketBuf<'a>
+ ) -> Option<Command<'a>> {
+ // HACK: this locally-scoped trait enables using identifiers
+ // that aren't top-level `Target` IDETs to split-up the packet
+ // parsing code.
+ trait Hack {
+ fn base(&mut self) -> Option<()>;
+ fn single_register_access(&mut self) -> Option<()>;
+ fn reverse_step(&mut self) -> Option<()>;
+ fn reverse_cont(&mut self) -> Option<()>;
}
- let body = buf.as_body();
+ impl<T: Target> Hack for T {
+ fn base(&mut self) -> Option<()> {
+ Some(())
+ }
+
+ fn single_register_access(&mut self) -> Option<()> {
+ use crate::target::ext::base::BaseOps;
+ match self.base_ops() {
+ BaseOps::SingleThread(ops) => ops.single_register_access().map(drop),
+ BaseOps::MultiThread(ops) => ops.single_register_access().map(drop),
+ }
+ }
+
+ fn reverse_step(&mut self) -> Option<()> {
+ use crate::target::ext::base::BaseOps;
+ match self.base_ops() {
+ BaseOps::SingleThread(ops) => ops.support_reverse_step().map(drop),
+ BaseOps::MultiThread(ops) => ops.support_reverse_step().map(drop),
+ }
+ }
+
+ fn reverse_cont(&mut self) -> Option<()> {
+ use crate::target::ext::base::BaseOps;
+ match self.base_ops() {
+ BaseOps::SingleThread(ops) => ops.support_reverse_cont().map(drop),
+ BaseOps::MultiThread(ops) => ops.support_reverse_cont().map(drop),
+ }
+ }
+ }
- // This scoped extension trait enables using `base` as an
- // `$ext`, even through the `base` method on `Target` doesn't
- // return an Option.
- trait Hack { fn base(&mut self) -> Option<()> { Some(()) } }
- impl<T: Target> Hack for T {}
+ // TODO?: use tries for more efficient longest prefix matching
$(
+ #[allow(clippy::string_lit_as_bytes)]
if target.$ext().is_some() {
- // TODO?: use tries for more efficient longest prefix matching
- #[allow(clippy::string_lit_as_bytes)]
- match body {
- $(_ if body.starts_with($name.as_bytes()) => {
- crate::__dead_code_marker!($name, "prefix_match");
-
- let buf = buf.trim_start_body_bytes($name.len());
- let cmd = $mod::$command::from_packet(buf)
- .ok_or(CommandParseError::MalformedCommand($name))?;
-
- return Ok(
- Command::[<$ext:camel>](
- ext::[<$ext:camel>]::$command(cmd)
- )
+ $(
+ if buf.strip_prefix($name.as_bytes()) {
+ crate::__dead_code_marker!($name, "prefix_match");
+
+ let cmd = $mod::$command::from_packet(buf)?;
+
+ return Some(
+ Command::[<$ext:camel>](
+ ext::[<$ext:camel>]::$command(cmd)
)
- })*
- _ => {},
+ )
}
+ )*
}
)*
- Ok(Command::Unknown(buf.into_body_str()))
+ if let Some(_breakpoint_ops) = target.breakpoints() {
+ use breakpoint::{BasicBreakpoint, BytecodeBreakpoint};
+
+ if buf.strip_prefix(b"z") {
+ let cmd = BasicBreakpoint::from_slice(buf.into_body())?;
+ return Some(Command::Breakpoints(ext::Breakpoints::z(cmd)))
+ }
+
+ if buf.strip_prefix(b"Z") {
+ // TODO: agent bytecode currently unimplemented
+ if true {
+ let cmd = BasicBreakpoint::from_slice(buf.into_body())?;
+ return Some(Command::Breakpoints(ext::Breakpoints::Z(cmd)))
+ } else {
+ let cmd = BytecodeBreakpoint::from_slice(buf.into_body())?;
+ return Some(Command::Breakpoints(ext::Breakpoints::ZWithBytecode(cmd)))
+ }
+ }
+ }
+
+ Some(Command::Unknown(buf.into_body()))
}
}
}};
}
-/// Command parse error
-// TODO?: add more granular errors to command parsing code
-pub enum CommandParseError<'a> {
- Empty,
- /// catch-all
- MalformedCommand(&'a str),
-}
-
commands! {
base use 'a {
"?" => question_mark::QuestionMark,
@@ -111,8 +171,6 @@ commands! {
"k" => _k::k,
"m" => _m::m<'a>,
"M" => _m_upcase::M<'a>,
- "p" => _p::p,
- "P" => _p_upcase::P<'a>,
"qAttached" => _qAttached::qAttached,
"qfThreadInfo" => _qfThreadInfo::qfThreadInfo,
"QStartNoAckMode" => _QStartNoAckMode::QStartNoAckMode,
@@ -123,8 +181,11 @@ commands! {
"T" => _t_upcase::T,
"vCont" => _vCont::vCont<'a>,
"vKill" => _vKill::vKill,
- "z" => _z::z<'a>,
- "Z" => _z_upcase::Z<'a>,
+ }
+
+ single_register_access use 'a {
+ "p" => _p::p,
+ "P" => _p_upcase::P<'a>,
}
extended_mode use 'a {
@@ -147,4 +208,12 @@ commands! {
section_offsets {
"qOffsets" => _qOffsets::qOffsets,
}
+
+ reverse_cont {
+ "bc" => _bc::bc,
+ }
+
+ reverse_step {
+ "bs" => _bs::bs,
+ }
}
diff --git a/src/protocol/commands/_QAgent.rs b/src/protocol/commands/_QAgent.rs
new file mode 100644
index 0000000..a37669b
--- /dev/null
+++ b/src/protocol/commands/_QAgent.rs
@@ -0,0 +1,18 @@
+use super::prelude::*;
+
+#[derive(Debug)]
+pub struct QAgent {
+ pub value: bool,
+}
+
+impl<'a> ParseCommand<'a> for QAgent {
+ fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
+ let body = buf.into_body();
+ let value = match body as &[u8] {
+ b":0" => false,
+ b":1" => true,
+ _ => return None,
+ };
+ Some(QAgent { value })
+ }
+}
diff --git a/src/protocol/commands/_bc.rs b/src/protocol/commands/_bc.rs
new file mode 100644
index 0000000..d2d30d5
--- /dev/null
+++ b/src/protocol/commands/_bc.rs
@@ -0,0 +1,13 @@
+use super::prelude::*;
+
+#[derive(Debug)]
+pub struct bc;
+
+impl<'a> ParseCommand<'a> for bc {
+ fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
+ if !buf.into_body().is_empty() {
+ return None;
+ }
+ Some(bc)
+ }
+}
diff --git a/src/protocol/commands/_bs.rs b/src/protocol/commands/_bs.rs
new file mode 100644
index 0000000..30ef412
--- /dev/null
+++ b/src/protocol/commands/_bs.rs
@@ -0,0 +1,13 @@
+use super::prelude::*;
+
+#[derive(Debug)]
+pub struct bs;
+
+impl<'a> ParseCommand<'a> for bs {
+ fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
+ if !buf.into_body().is_empty() {
+ return None;
+ }
+ Some(bs)
+ }
+}
diff --git a/src/protocol/commands/_qSupported.rs b/src/protocol/commands/_qSupported.rs
index 1c644ff..97e8ff2 100644
--- a/src/protocol/commands/_qSupported.rs
+++ b/src/protocol/commands/_qSupported.rs
@@ -2,17 +2,21 @@ use super::prelude::*;
#[derive(Debug)]
pub struct qSupported<'a> {
+ pub packet_buffer_len: usize,
pub features: Features<'a>,
}
impl<'a> ParseCommand<'a> for qSupported<'a> {
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
+ let packet_buffer_len = buf.full_len();
+
let body = buf.into_body();
if body.is_empty() {
return None;
}
Some(qSupported {
+ packet_buffer_len,
features: Features(body),
})
}
diff --git a/src/protocol/commands/_vCont.rs b/src/protocol/commands/_vCont.rs
index 6a077d7..932c7db 100644
--- a/src/protocol/commands/_vCont.rs
+++ b/src/protocol/commands/_vCont.rs
@@ -1,9 +1,13 @@
use super::prelude::*;
-// TODO: instead of parsing lazily when invoked, parse the strings into a
+// TODO?: instead of parsing lazily when invoked, parse the strings into a
// compressed binary representations that can be stuffed back into the packet
// buffer, and return an iterator over the binary data that's _guaranteed_ to be
// valid. This would clean up some of the code in the vCont handler.
+//
+// The interesting part would be to see whether or not the simplified error
+// handing code will compensate for all the new code required to pre-validate
+// the data...
#[derive(Debug)]
pub enum vCont<'a> {
Query,
@@ -15,22 +19,56 @@ impl<'a> ParseCommand<'a> for vCont<'a> {
let body = buf.into_body();
match body as &[u8] {
b"?" => Some(vCont::Query),
- _ => Some(vCont::Actions(Actions(body))),
+ _ => Some(vCont::Actions(Actions::new_from_buf(body))),
}
}
}
-/// A lazily evaluated iterator over the actions specified in a vCont packet.
#[derive(Debug)]
-pub struct Actions<'a>(&'a mut [u8]);
+pub enum Actions<'a> {
+ Buf(ActionsBuf<'a>),
+ FixedStep(SpecificThreadId),
+ FixedCont(SpecificThreadId),
+}
impl<'a> Actions<'a> {
- pub fn into_iter(self) -> impl Iterator<Item = Option<VContAction<'a>>> + 'a {
- self.0.split_mut(|b| *b == b';').skip(1).map(|act| {
- let mut s = act.split_mut(|b| *b == b':');
+ fn new_from_buf(buf: &'a [u8]) -> Actions<'a> {
+ Actions::Buf(ActionsBuf(buf))
+ }
+
+ pub fn new_step(tid: SpecificThreadId) -> Actions<'a> {
+ Actions::FixedStep(tid)
+ }
+
+ pub fn new_continue(tid: SpecificThreadId) -> Actions<'a> {
+ Actions::FixedCont(tid)
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = Option<VContAction<'a>>> + '_ {
+ match self {
+ Actions::Buf(x) => EitherIter::A(x.iter()),
+ Actions::FixedStep(x) => EitherIter::B(core::iter::once(Some(VContAction {
+ kind: VContKind::Step,
+ thread: Some(*x),
+ }))),
+ Actions::FixedCont(x) => EitherIter::B(core::iter::once(Some(VContAction {
+ kind: VContKind::Continue,
+ thread: Some(*x),
+ }))),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct ActionsBuf<'a>(&'a [u8]);
+
+impl<'a> ActionsBuf<'a> {
+ fn iter(&self) -> impl Iterator<Item = Option<VContAction<'a>>> + '_ {
+ self.0.split(|b| *b == b';').skip(1).map(|act| {
+ let mut s = act.split(|b| *b == b':');
let kind = s.next()?;
let thread = match s.next() {
- Some(s) => Some(s.try_into().ok()?),
+ Some(s) => Some(SpecificThreadId::try_from(ThreadId::try_from(s).ok()?).ok()?),
None => None,
};
@@ -42,24 +80,24 @@ impl<'a> Actions<'a> {
}
}
-#[derive(Debug)]
+#[derive(Debug, Copy, Clone)]
pub struct VContAction<'a> {
pub kind: VContKind<'a>,
- pub thread: Option<ThreadId>,
+ pub thread: Option<SpecificThreadId>,
}
-#[derive(Debug)]
+#[derive(Debug, Copy, Clone)]
pub enum VContKind<'a> {
Continue,
ContinueWithSig(u8),
- RangeStep(&'a [u8], &'a [u8]),
+ RangeStep(HexString<'a>, HexString<'a>),
Step,
StepWithSig(u8),
Stop,
}
impl<'a> VContKind<'a> {
- fn from_bytes(s: &mut [u8]) -> Option<VContKind> {
+ fn from_bytes(s: &[u8]) -> Option<VContKind> {
use self::VContKind::*;
let res = match s {
@@ -69,10 +107,8 @@ impl<'a> VContKind<'a> {
[b'C', sig @ ..] => ContinueWithSig(decode_hex(sig).ok()?),
[b'S', sig @ ..] => StepWithSig(decode_hex(sig).ok()?),
[b'r', range @ ..] => {
- let mut range = range.split_mut(|b| *b == b',');
- let start = decode_hex_buf(range.next()?).ok()?;
- let end = decode_hex_buf(range.next()?).ok()?;
- RangeStep(start, end)
+ let mut range = range.split(|b| *b == b',');
+ RangeStep(HexString(range.next()?), HexString(range.next()?))
}
_ => return None,
};
@@ -80,3 +116,24 @@ impl<'a> VContKind<'a> {
Some(res)
}
}
+
+/// Helper type to unify iterators that output the same type. Returned as an
+/// opaque type from `Actions::iter()`.
+enum EitherIter<A, B> {
+ A(A),
+ B(B),
+}
+
+impl<A, B, T> Iterator for EitherIter<A, B>
+where
+ A: Iterator<Item = T>,
+ B: Iterator<Item = T>,
+{
+ type Item = T;
+ fn next(&mut self) -> Option<T> {
+ match self {
+ EitherIter::A(a) => a.next(),
+ EitherIter::B(b) => b.next(),
+ }
+ }
+}
diff --git a/src/protocol/commands/_vRun.rs b/src/protocol/commands/_vRun.rs
index 7d3e1eb..2f1db1b 100644
--- a/src/protocol/commands/_vRun.rs
+++ b/src/protocol/commands/_vRun.rs
@@ -54,8 +54,11 @@ mod tests {
macro_rules! test_buf {
($bufname:ident, $body:literal) => {
let mut test = $body.to_vec();
- let buf = PacketBuf::new_with_raw_body(&mut test).unwrap();
- let $bufname = buf.trim_start_body_bytes(b"vRun".len());
+ let mut buf = PacketBuf::new_with_raw_body(&mut test).unwrap();
+ if !buf.strip_prefix(b"vRun") {
+ panic!("invalid test");
+ }
+ let $bufname = buf;
};
}
diff --git a/src/protocol/commands/_z.rs b/src/protocol/commands/_z.rs
deleted file mode 100644
index 7b97dd2..0000000
--- a/src/protocol/commands/_z.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-use super::prelude::*;
-
-#[derive(Debug)]
-pub struct z<'a> {
- pub type_: u8,
- pub addr: &'a [u8],
- pub kind: u8,
-}
-
-impl<'a> ParseCommand<'a> for z<'a> {
- fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
- let body = buf.into_body();
- let mut body = body.split_mut(|&b| b == b',');
- let type_ = decode_hex(body.next()?).ok()?;
- let addr = decode_hex_buf(body.next()?).ok()?;
- let kind = decode_hex(body.next()?).ok()?;
-
- Some(z { type_, addr, kind })
- }
-}
diff --git a/src/protocol/commands/_z_upcase.rs b/src/protocol/commands/_z_upcase.rs
deleted file mode 100644
index d794be2..0000000
--- a/src/protocol/commands/_z_upcase.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use super::prelude::*;
-
-#[derive(Debug)]
-pub struct Z<'a> {
- pub type_: u8,
- pub addr: &'a [u8],
- /// architecture dependent
- pub kind: u8,
- // TODO: Add support for breakpoint 'conds', 'persist', and 'cmds' feature
-}
-
-impl<'a> ParseCommand<'a> for Z<'a> {
- fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
- let body = buf.into_body();
- let mut body = body.split_mut(|&b| b == b',');
- let type_ = decode_hex(body.next()?).ok()?;
- let addr = decode_hex_buf(body.next()?).ok()?;
- let kind = decode_hex(body.next()?).ok()?;
-
- // TODO: properly parse 'conds', 'persist', and 'cmds' fields in 'Z' packets
-
- Some(Z { type_, addr, kind })
- }
-}
diff --git a/src/protocol/commands/breakpoint.rs b/src/protocol/commands/breakpoint.rs
new file mode 100644
index 0000000..101d69e
--- /dev/null
+++ b/src/protocol/commands/breakpoint.rs
@@ -0,0 +1,117 @@
+pub use crate::common::*;
+pub use crate::protocol::common::hex::{decode_hex, decode_hex_buf};
+pub use crate::protocol::packet::PacketBuf;
+pub use core::convert::{TryFrom, TryInto};
+
+// Breakpoint packets are split up like this:
+//
+// Z0,addr,kind[;cond_list…][;cmds:persist,cmd_list…]
+// \_________/
+// |
+// BasicBreakpoint
+// \_______________________________________________/
+// |
+// BytecodeBreakpoint
+//
+// If the target does not implement the `Agent` extension, only the
+// `BasicBreakpoint` part is parsed, which helps cut down on binary bloat.
+
+#[derive(Debug)]
+pub struct BasicBreakpoint<'a> {
+ pub type_: u8,
+ pub addr: &'a [u8],
+ /// architecture dependent
+ pub kind: usize,
+}
+
+impl<'a> BasicBreakpoint<'a> {
+ pub fn from_slice(body: &'a mut [u8]) -> Option<BasicBreakpoint<'a>> {
+ let mut body = body.splitn_mut(4, |b| matches!(*b, b',' | b';'));
+ let type_ = decode_hex(body.next()?).ok()?;
+ let addr = decode_hex_buf(body.next()?).ok()?;
+ let kind = decode_hex(body.next()?).ok()?;
+
+ Some(BasicBreakpoint { type_, addr, kind })
+ }
+}
+
+#[derive(Debug)]
+pub struct BytecodeBreakpoint<'a> {
+ pub base: BasicBreakpoint<'a>,
+ pub conds: Option<BytecodeList<'a>>,
+ pub cmds_persist: Option<(BytecodeList<'a>, bool)>,
+}
+
+impl<'a> BytecodeBreakpoint<'a> {
+ pub fn from_slice(body: &'a mut [u8]) -> Option<BytecodeBreakpoint<'a>> {
+ let mut body = body.splitn_mut(2, |b| *b == b';');
+
+ let base = BasicBreakpoint::from_slice(body.next()?)?;
+
+ let mut conds = None;
+ let mut cmds_persist = None;
+
+ if let Some(rest) = body.next() {
+ let mut s = rest.split_mut(|b| *b == b':');
+ let (raw_conds, raw_cmds) = match (s.next(), s.next()) {
+ (Some(a), Some(b)) => (Some(strip_suffix_mut(a, b";cmds")?), Some(b)),
+ (Some(a), None) => {
+ if a.starts_with(b"cmds") {
+ (None, Some(a))
+ } else {
+ (Some(a), None)
+ }
+ }
+ _ => return None,
+ };
+
+ if let Some(raw_conds) = raw_conds {
+ conds = Some(BytecodeList(raw_conds));
+ }
+
+ if let Some(raw_cmds) = raw_cmds {
+ let mut raw_cmds = raw_cmds.split_mut(|b| *b == b',');
+ let raw_persist = decode_hex::<u8>(raw_cmds.next()?).ok()? != 0;
+ let raw_cmds = raw_cmds.next()?;
+
+ cmds_persist = Some((BytecodeList(raw_cmds), raw_persist));
+ }
+ }
+
+ Some(BytecodeBreakpoint {
+ base,
+ conds,
+ cmds_persist,
+ })
+ }
+}
+
+fn strip_suffix_mut<'a, T>(slice: &'a mut [T], suffix: &[T]) -> Option<&'a mut [T]>
+where
+ T: PartialEq,
+{
+ let (len, n) = (slice.len(), suffix.len());
+ if n <= len {
+ let (head, tail) = slice.split_at_mut(len - n);
+ if tail == suffix {
+ return Some(head);
+ }
+ }
+ None
+}
+
+/// A lazily evaluated iterator over a series of bytecode expressions.
+#[derive(Debug)]
+pub struct BytecodeList<'a>(&'a mut [u8]);
+
+impl<'a> BytecodeList<'a> {
+ #[allow(dead_code)]
+ pub fn into_iter(self) -> impl Iterator<Item = Option<&'a [u8]>> + 'a {
+ self.0.split_mut(|b| *b == b'X').skip(1).map(|s| {
+ let mut s = s.split_mut(|b| *b == b',');
+ let _len = s.next()?;
+ let code = decode_hex_buf(s.next()?).ok()?;
+ Some(code as &[u8])
+ })
+ }
+}
diff --git a/src/protocol/common/hex.rs b/src/protocol/common/hex.rs
index 7094826..4c3170b 100644
--- a/src/protocol/common/hex.rs
+++ b/src/protocol/common/hex.rs
@@ -35,6 +35,20 @@ where
Ok(result)
}
+/// Wrapper around a raw hex string. Enabled "late" calls to `decode` from
+/// outside the `crate::protocol` module.
+#[derive(Debug, Clone, Copy)]
+pub struct HexString<'a>(pub &'a [u8]);
+
+impl HexString<'_> {
+ pub fn decode<I>(&self) -> Result<I, DecodeHexError>
+ where
+ I: FromPrimitive + Zero + CheckedAdd + CheckedMul,
+ {
+ decode_hex(self.0)
+ }
+}
+
#[derive(Debug)]
pub enum DecodeHexBufError {
NotAscii,
diff --git a/src/protocol/common/mod.rs b/src/protocol/common/mod.rs
index b9acdf9..50895a2 100644
--- a/src/protocol/common/mod.rs
+++ b/src/protocol/common/mod.rs
@@ -1,8 +1,5 @@
-mod hex;
-mod thread_id;
-
-pub use hex::*;
-pub use thread_id::*;
+pub mod hex;
+pub mod thread_id;
/// Lightweight wrapper around `&[u8]` which denotes that the contained data is
/// a ASCII string.
diff --git a/src/protocol/common/thread_id.rs b/src/protocol/common/thread_id.rs
index e5e6eb4..2677132 100644
--- a/src/protocol/common/thread_id.rs
+++ b/src/protocol/common/thread_id.rs
@@ -1,7 +1,7 @@
use core::convert::{TryFrom, TryInto};
use core::num::NonZeroUsize;
-use super::decode_hex;
+use super::hex::decode_hex;
/// Tid/Pid Selector.
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
@@ -11,7 +11,7 @@ pub enum IdKind {
/// Any thread (0)
Any,
/// Thread with specific ID (id > 0)
- WithID(NonZeroUsize),
+ WithId(NonZeroUsize),
}
/// Unique Thread ID.
@@ -59,7 +59,7 @@ impl TryFrom<&[u8]> for IdKind {
Ok(match s {
b"-1" => IdKind::All,
b"0" => IdKind::Any,
- id => IdKind::WithID(NonZeroUsize::new(decode_hex(id).map_err(drop)?).ok_or(())?),
+ id => IdKind::WithId(NonZeroUsize::new(decode_hex(id).map_err(drop)?).ok_or(())?),
})
}
}
@@ -79,3 +79,49 @@ impl TryFrom<&mut [u8]> for IdKind {
Self::try_from(s as &[u8])
}
}
+
+/// Like [`IdKind`], without the `Any` variant. Typically used when working
+/// with vCont (i.e: where the `Any` variant wouldn't be valid).
+#[derive(PartialEq, Eq, Debug, Clone, Copy)]
+pub enum SpecificIdKind {
+ /// Thread with specific ID (id > 0)
+ WithId(core::num::NonZeroUsize),
+ /// All threads (-1)
+ All,
+}
+
+/// Like [`ThreadId`], without the `Any` variants. Typically used when working
+/// with vCont (i.e: where the `Any` variant wouldn't be valid).
+#[derive(Debug, Copy, Clone)]
+pub struct SpecificThreadId {
+ /// Process ID (may or may not be present).
+ pub pid: Option<SpecificIdKind>,
+ /// Thread ID.
+ pub tid: SpecificIdKind,
+}
+
+impl TryFrom<IdKind> for SpecificIdKind {
+ type Error = ();
+
+ fn try_from(id: IdKind) -> Result<SpecificIdKind, ()> {
+ Ok(match id {
+ IdKind::All => SpecificIdKind::All,
+ IdKind::WithId(id) => SpecificIdKind::WithId(id),
+ IdKind::Any => return Err(()),
+ })
+ }
+}
+
+impl TryFrom<ThreadId> for SpecificThreadId {
+ type Error = ();
+
+ fn try_from(thread: ThreadId) -> Result<SpecificThreadId, ()> {
+ Ok(SpecificThreadId {
+ pid: match thread.pid {
+ None => None,
+ Some(id_kind) => Some(id_kind.try_into()?),
+ },
+ tid: thread.tid.try_into()?,
+ })
+ }
+}
diff --git a/src/protocol/console_output.rs b/src/protocol/console_output.rs
index 211d8db..9918b26 100644
--- a/src/protocol/console_output.rs
+++ b/src/protocol/console_output.rs
@@ -6,7 +6,7 @@ use alloc::vec::Vec;
/// Helper struct to send console output to GDB.
///
/// The recommended way to interact with `ConsoleOutput` is through the provided
-/// [`output!`](macro.output.html) / [`outputln!`](macro.outputln.html) macros.
+/// [`output!`] and [`outputln!`] macros.
///
/// On resource constrained systems which might want to avoid using Rust's
/// [fairly "heavy" formatting machinery](https://jamesmunns.com/blog/fmt-unreasonably-expensive/),
@@ -70,7 +70,7 @@ impl Drop for ConsoleOutput<'_> {
/// Send formatted data to the GDB client console.
///
-/// The first argument must be a [`ConsoleWriter`](struct.ConsoleWriter.html).
+/// The first argument must be a [`ConsoleOutput`].
#[macro_export]
macro_rules! output {
($console_output:expr, $($args:tt)*) => {{
@@ -81,7 +81,7 @@ macro_rules! output {
/// Send formatted data to the GDB client console, with a newline appended.
///
-/// The first argument must be a [`ConsoleWriter`](struct.ConsoleWriter.html).
+/// The first argument must be a [`ConsoleOutput`].
#[macro_export]
macro_rules! outputln {
($console_output:expr) => {{
diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs
index fe09d57..e7116d4 100644
--- a/src/protocol/mod.rs
+++ b/src/protocol/mod.rs
@@ -5,7 +5,7 @@ mod response_writer;
pub(crate) mod commands;
-pub(crate) use common::{IdKind, ThreadId};
+pub(crate) use common::thread_id::{IdKind, SpecificIdKind, SpecificThreadId};
pub(crate) use packet::Packet;
pub(crate) use response_writer::{Error as ResponseWriterError, ResponseWriter};
diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs
index 8b1d4e4..9fbf977 100644
--- a/src/protocol/packet.rs
+++ b/src/protocol/packet.rs
@@ -1,4 +1,5 @@
-use crate::protocol::{commands::Command, common::decode_hex};
+use crate::protocol::commands::Command;
+use crate::protocol::common::hex::decode_hex;
use crate::target::Target;
/// Packet parse error.
@@ -9,7 +10,7 @@ pub enum PacketParseError {
MissingChecksum,
MalformedChecksum,
MalformedCommand,
- NotASCII,
+ NotAscii,
UnexpectedHeader(u8),
}
@@ -30,20 +31,24 @@ impl<'a> PacketBuf<'a> {
/// Validate the contents of the raw packet buffer, checking for checksum
/// consistency, structural correctness, and ASCII validation.
pub fn new(pkt_buf: &'a mut [u8]) -> Result<PacketBuf<'a>, PacketParseError> {
- // validate the packet is valid ASCII
- if !pkt_buf.is_ascii() {
- return Err(PacketParseError::NotASCII);
+ if pkt_buf.is_empty() {
+ return Err(PacketParseError::EmptyBuf);
}
- let end_of_body = pkt_buf
- .iter()
- .position(|b| *b == b'#')
- .ok_or(PacketParseError::MissingChecksum)?;
-
// split buffer into body and checksum components
- let (body, checksum) = pkt_buf.split_at_mut(end_of_body);
- let body = &mut body[1..]; // skip the '$'
- let checksum = &mut checksum[1..][..2]; // skip the '#'
+ let mut parts = pkt_buf[1..].split(|b| *b == b'#');
+
+ let body = parts.next().unwrap(); // spit iter always returns at least one elem
+ let checksum = parts
+ .next()
+ .ok_or(PacketParseError::MissingChecksum)?
+ .get(..2)
+ .ok_or(PacketParseError::MalformedChecksum)?;
+
+ // validate that the body is valid ASCII
+ if !body.is_ascii() {
+ return Err(PacketParseError::NotAscii);
+ }
// validate the checksum
let checksum = decode_hex(checksum).map_err(|_| PacketParseError::MalformedChecksum)?;
@@ -55,11 +60,7 @@ impl<'a> PacketBuf<'a> {
});
}
- if log_enabled!(log::Level::Trace) {
- // SAFETY: body confirmed to be `is_ascii()`
- let body = unsafe { core::str::from_utf8_unchecked(body) };
- trace!("<-- ${}#{:02x?}", body, checksum);
- }
+ let end_of_body = 1 + body.len();
Ok(PacketBuf {
buf: pkt_buf,
@@ -73,7 +74,7 @@ impl<'a> PacketBuf<'a> {
pub fn new_with_raw_body(body: &'a mut [u8]) -> Result<PacketBuf<'a>, PacketParseError> {
// validate the packet is valid ASCII
if !body.is_ascii() {
- return Err(PacketParseError::NotASCII);
+ return Err(PacketParseError::NotAscii);
}
let len = body.len();
@@ -83,35 +84,34 @@ impl<'a> PacketBuf<'a> {
})
}
- pub fn trim_start_body_bytes(self, n: usize) -> Self {
- PacketBuf {
- buf: self.buf,
- body_range: (self.body_range.start + n)..self.body_range.end,
+ pub fn strip_prefix(&mut self, prefix: &[u8]) -> bool {
+ if self.buf[self.body_range.clone()].starts_with(prefix) {
+ self.body_range = (self.body_range.start + prefix.len())..self.body_range.end;
+ true
+ } else {
+ false
}
}
- pub fn as_body(&'a self) -> &'a [u8] {
- &self.buf[self.body_range.clone()]
- }
-
/// Return a mut reference to slice of the packet buffer corresponding to
/// the current body.
pub fn into_body(self) -> &'a mut [u8] {
&mut self.buf[self.body_range]
}
- pub fn into_body_str(self) -> &'a str {
- // SAFETY: buffer confirmed to be `is_ascii()` in `new`, and no other PacketBuf
- // member allow arbitrary modification of `self.buf`.
- unsafe { core::str::from_utf8_unchecked(&self.buf[self.body_range]) }
- }
-
/// Return a mut reference to the _entire_ underlying packet buffer, and the
/// current body's range.
- #[allow(dead_code)]
pub fn into_raw_buf(self) -> (&'a mut [u8], core::ops::Range<usize>) {
(self.buf, self.body_range)
}
+
+ /// Returns the length of the _entire_ underlying packet buffer - not just
+ /// the length of the current range.
+ ///
+ /// This method is used when handing the `qSupported` packet.
+ pub fn full_len(&self) -> usize {
+ self.buf.len()
+ }
}
impl<'a> Packet<'a> {
@@ -127,8 +127,7 @@ impl<'a> Packet<'a> {
match buf[0] {
b'$' => Ok(Packet::Command(
Command::from_packet(target, PacketBuf::new(buf)?)
- // TODO?: preserve command parse error context
- .map_err(|_| PacketParseError::MalformedCommand)?,
+ .ok_or(PacketParseError::MalformedCommand)?,
)),
b'+' => Ok(Packet::Ack),
b'-' => Ok(Packet::Nack),
diff --git a/src/protocol/response_writer.rs b/src/protocol/response_writer.rs
index 3dc5544..484bd60 100644
--- a/src/protocol/response_writer.rs
+++ b/src/protocol/response_writer.rs
@@ -1,29 +1,31 @@
-#[cfg(feature = "alloc")]
-use alloc::string::String;
-
use num_traits::PrimInt;
use crate::internal::BeBytes;
-use crate::protocol::{IdKind, ThreadId};
+use crate::protocol::{SpecificIdKind, SpecificThreadId};
use crate::Connection;
/// Newtype around a Connection error. Having a newtype allows implementing a
/// `From<ResponseWriterError<C>> for crate::Error<T, C>`, which greatly
/// simplifies some of the error handling in the main gdbstub.
#[derive(Debug, Clone)]
-pub struct Error<C>(C);
+pub struct Error<C>(pub C);
/// A wrapper around [`Connection`] that computes the single-byte checksum of
/// incoming / outgoing data.
pub struct ResponseWriter<'a, C: Connection + 'a> {
+ // TODO: add `write_all` method to Connection, and allow user to optionally pass outgoing
+ // packet buffer? This could improve performance (instead of writing a single byte at a time)
inner: &'a mut C,
started: bool,
checksum: u8,
- // buffer outgoing message
- // TODO: add `write_all` method to Connection, and allow user to optionally pass outgoing
- // packet buffer? This could improve performance (instead of writing a single byte at a time)
- #[cfg(feature = "alloc")]
- msg: String,
+ // TODO?: Make using RLE configurable by the target?
+ // if implemented correctly, targets that disable RLE entirely could have all RLE code
+ // dead-code-eliminated.
+ rle_char: u8,
+ rle_repeat: u8,
+ // buffer to log outgoing packets. only allocates if logging is enabled.
+ #[cfg(feature = "std")]
+ msg: Vec<u8>,
}
impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
@@ -33,21 +35,34 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
inner,
started: false,
checksum: 0,
- #[cfg(feature = "alloc")]
- msg: String::new(),
+ rle_char: 0,
+ rle_repeat: 0,
+ #[cfg(feature = "std")]
+ msg: Vec::new(),
}
}
/// Consumes self, writing out the final '#' and checksum
pub fn flush(mut self) -> Result<(), Error<C::Error>> {
- // don't include '#' in checksum calculation
+ self.write(b'#')?;
+
+ // don't include the '#' in checksum calculation
+ // (note: even though `self.write` was called, the the '#' char hasn't been
+ // added to the checksum, and is just sitting in the RLE buffer)
let checksum = self.checksum;
- #[cfg(feature = "alloc")]
- trace!("--> ${}#{:02x?}", self.msg, checksum);
+ #[cfg(feature = "std")]
+ trace!(
+ "--> ${}#{:02x?}",
+ core::str::from_utf8(&self.msg).unwrap(), // buffers are always ascii
+ checksum
+ );
- self.write(b'#')?;
self.write_hex(checksum)?;
+ // HACK: "write" a dummy char to force an RLE flush
+ self.write(0)?;
+
+ self.inner.flush().map_err(Error)?;
Ok(())
}
@@ -57,10 +72,20 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
self.inner
}
- /// Write a single byte.
- pub fn write(&mut self, byte: u8) -> Result<(), Error<C::Error>> {
- #[cfg(feature = "alloc")]
- self.msg.push(byte as char);
+ fn inner_write(&mut self, byte: u8) -> Result<(), Error<C::Error>> {
+ #[cfg(feature = "std")]
+ if log_enabled!(log::Level::Trace) {
+ match self.msg.as_slice() {
+ [.., c, b'*'] => {
+ let c = *c;
+ self.msg.pop();
+ for _ in 0..(byte - 29) {
+ self.msg.push(c);
+ }
+ }
+ _ => self.msg.push(byte),
+ }
+ }
if !self.started {
self.started = true;
@@ -71,14 +96,53 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
self.inner.write(byte).map_err(Error)
}
- /// Write an entire buffer over the connection.
- pub fn write_all(&mut self, data: &[u8]) -> Result<(), Error<C::Error>> {
- data.iter().try_for_each(|b| self.write(*b))
+ fn write(&mut self, byte: u8) -> Result<(), Error<C::Error>> {
+ const ASCII_FIRST_PRINT: u8 = b' ';
+ const ASCII_LAST_PRINT: u8 = b'~';
+
+ // handle RLE
+ let rle_printable = (ASCII_FIRST_PRINT - 4 + (self.rle_repeat + 1)) <= ASCII_LAST_PRINT;
+ if byte == self.rle_char && rle_printable {
+ self.rle_repeat += 1;
+ Ok(())
+ } else {
+ loop {
+ match self.rle_repeat {
+ 0 => {} // happens once, after the first char is written
+ // RLE doesn't win, just output the byte
+ 1 | 2 | 3 => {
+ for _ in 0..self.rle_repeat {
+ self.inner_write(self.rle_char)?
+ }
+ }
+ // RLE would output an invalid char ('#' or '$')
+ 6 | 7 => {
+ self.inner_write(self.rle_char)?;
+ self.rle_repeat -= 1;
+ continue;
+ }
+ // RLE wins for repetitions >4
+ _ => {
+ self.inner_write(self.rle_char)?;
+ self.inner_write(b'*')?;
+ self.inner_write(ASCII_FIRST_PRINT - 4 + self.rle_repeat)?;
+ }
+ }
+
+ self.rle_char = byte;
+ self.rle_repeat = 1;
+
+ break Ok(());
+ }
+ }
}
/// Write an entire string over the connection.
- pub fn write_str(&mut self, s: &str) -> Result<(), Error<C::Error>> {
- self.write_all(&s.as_bytes())
+ pub fn write_str(&mut self, s: &'static str) -> Result<(), Error<C::Error>> {
+ for b in s.as_bytes().iter() {
+ self.write(*b)?;
+ }
+ Ok(())
}
/// Write a single byte as a hex string (two ascii chars)
@@ -96,18 +160,24 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
/// Write a byte-buffer as a hex string (i.e: two ascii chars / byte).
pub fn write_hex_buf(&mut self, data: &[u8]) -> Result<(), Error<C::Error>> {
- data.iter().try_for_each(|b| self.write_hex(*b))
+ for b in data.iter() {
+ self.write_hex(*b)?;
+ }
+ Ok(())
}
/// Write data using the binary protocol.
pub fn write_binary(&mut self, data: &[u8]) -> Result<(), Error<C::Error>> {
- data.iter().try_for_each(|b| match b {
- b'#' | b'$' | b'}' | b'*' => {
- self.write(b'}')?;
- self.write(*b ^ 0x20)
+ for &b in data.iter() {
+ match b {
+ b'#' | b'$' | b'}' | b'*' => {
+ self.write(b'}')?;
+ self.write(b ^ 0x20)?
+ }
+ _ => self.write(b)?,
}
- _ => self.write(*b),
- })
+ }
+ Ok(())
}
/// Write a number as a big-endian hex string using the most compact
@@ -121,28 +191,30 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
// infallible (unless digit is a >128 bit number)
let len = digit.to_be_bytes(&mut buf).unwrap();
let buf = &buf[..len];
- buf.iter()
- .copied()
- .skip_while(|&b| b == 0)
- .try_for_each(|b| self.write_hex(b))
+ for b in buf.iter().copied().skip_while(|&b| b == 0) {
+ self.write_hex(b)?
+ }
+ Ok(())
}
- pub fn write_id_kind(&mut self, tid: IdKind) -> Result<(), Error<C::Error>> {
+ fn write_specific_id_kind(&mut self, tid: SpecificIdKind) -> Result<(), Error<C::Error>> {
match tid {
- IdKind::All => self.write_str("-1")?,
- IdKind::Any => self.write_str("0")?,
- IdKind::WithID(id) => self.write_num(id.get())?,
+ SpecificIdKind::All => self.write_str("-1")?,
+ SpecificIdKind::WithId(id) => self.write_num(id.get())?,
};
Ok(())
}
- pub fn write_thread_id(&mut self, tid: ThreadId) -> Result<(), Error<C::Error>> {
+ pub fn write_specific_thread_id(
+ &mut self,
+ tid: SpecificThreadId,
+ ) -> Result<(), Error<C::Error>> {
if let Some(pid) = tid.pid {
self.write_str("p")?;
- self.write_id_kind(pid)?;
+ self.write_specific_id_kind(pid)?;
self.write_str(".")?;
}
- self.write_id_kind(tid.tid)?;
+ self.write_specific_id_kind(tid.tid)?;
Ok(())
}
}
diff --git a/src/target/ext/base/mod.rs b/src/target/ext/base/mod.rs
index 3c3c293..04bcbd9 100644
--- a/src/target/ext/base/mod.rs
+++ b/src/target/ext/base/mod.rs
@@ -1,12 +1,17 @@
//! Base operations required to debug any target (read/write memory/registers,
//! step/resume, etc...)
//!
-//! While not strictly required, it's recommended that single threaded targets
-//! implement the simplified `singlethread` API.
+//! It is recommended that single threaded targets implement the simplified
+//! `singlethread` API, as `gdbstub` includes optimized implementations of
+//! certain internal routines when operating in singlethreaded mode.
pub mod multithread;
pub mod singlethread;
+mod single_register_access;
+
+pub use single_register_access::{SingleRegisterAccess, SingleRegisterAccessOps};
+
/// Base operations for single/multi threaded targets.
pub enum BaseOps<'a, A, E> {
/// Single-threaded target
@@ -16,14 +21,87 @@ pub enum BaseOps<'a, A, E> {
}
/// Describes how the target should be resumed.
+///
+/// Due to a quirk / bug in the mainline GDB client, targets are required to
+/// handle the `WithSignal` variants of `Step` and `Continue` regardless of
+/// whether or not they have a concept of "signals".
+///
+/// If your target does not support signals (e.g: the target is a bare-metal
+/// microcontroller / emulator), the recommended behavior is to either return a
+/// target-specific fatal error, or to handle `{Step,Continue}WithSignal` the
+/// same way as their non-`WithSignal` variants.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResumeAction {
- /// Continue execution (until the next event occurs).
+ /// Continue execution, stopping once a
+ /// [`StopReason`](singlethread::StopReason) occurs.
Continue,
- /// Step forward a single instruction.
+ /// Step execution.
Step,
- /* ContinueWithSignal(u8),
- * StepWithSignal(u8),
- * Stop, // NOTE: won't be relevant until `gdbstub` supports non-stop mode
- * StepInRange(core::ops::Range<U>), */
+ /// Continue with signal.
+ ContinueWithSignal(u8),
+ /// Step with signal.
+ StepWithSignal(u8),
+}
+
+/// Describes the point reached in a replay log for the corresponding stop
+/// reason.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ReplayLogPosition {
+ /// Reached the beginning of the replay log.
+ Begin,
+ /// Reached the end of the replay log.
+ End,
+}
+
+/// A handle to check for incoming GDB interrupts.
+///
+/// At the moment, checking for incoming interrupts requires periodically
+/// polling for pending interrupts. e.g:
+///
+/// ```ignore
+/// let interrupts = gdb_interrupt.no_async();
+/// loop {
+/// if interrupts.pending() {
+/// return Ok(StopReason::GdbInterrupt)
+/// }
+///
+/// // execute some number of clock cycles
+/// for _ in 0..1024 {
+/// match self.system.step() { .. }
+/// }
+/// }
+/// ```
+///
+/// There is an outstanding issue to add a non-blocking interface to
+/// `GdbInterrupt` (see [daniel5151/gdbstub#36](https://github.com/daniel5151/gdbstub/issues/36)).
+/// Please comment on the issue if this is something you'd like to see
+/// implemented and/or would like to help out with!
+pub struct GdbInterrupt<'a> {
+ inner: &'a mut dyn FnMut() -> bool,
+}
+
+impl<'a> GdbInterrupt<'a> {
+ pub(crate) fn new(inner: &'a mut dyn FnMut() -> bool) -> GdbInterrupt<'a> {
+ GdbInterrupt { inner }
+ }
+
+ /// Returns a [`GdbInterruptNoAsync`] struct which can be polled using a
+ /// simple non-blocking [`pending(&mut self) ->
+ /// bool`](GdbInterruptNoAsync::pending) method.
+ pub fn no_async(self) -> GdbInterruptNoAsync<'a> {
+ GdbInterruptNoAsync { inner: self.inner }
+ }
+}
+
+/// A simplified interface to [`GdbInterrupt`] for projects without
+/// async/await infrastructure.
+pub struct GdbInterruptNoAsync<'a> {
+ inner: &'a mut dyn FnMut() -> bool,
+}
+
+impl<'a> GdbInterruptNoAsync<'a> {
+ /// Checks if there is a pending GDB interrupt.
+ pub fn pending(&mut self) -> bool {
+ (self.inner)()
+ }
}
diff --git a/src/target/ext/base/multithread.rs b/src/target/ext/base/multithread.rs
index 1da3c7c..eba9d79 100644
--- a/src/target/ext/base/multithread.rs
+++ b/src/target/ext/base/multithread.rs
@@ -5,32 +5,24 @@ use crate::common::*;
use crate::target::ext::breakpoints::WatchKind;
use crate::target::{Target, TargetResult};
-// Convenient re-exports
-pub use super::ResumeAction;
+use super::{ReplayLogPosition, SingleRegisterAccessOps};
-/// Selects a thread corresponding to a ResumeAction.
-// NOTE: this is a subset of the internal `IdKind` type, albeit without an `Any` variant. Selecting
-// `Any` thread is something that's handled by `gdbstub` internally, and shouldn't be exposed to the
-// end user.
-#[derive(PartialEq, Eq, Debug, Clone, Copy)]
-pub enum TidSelector {
- /// Thread with a specific ID.
- WithID(Tid),
- /// All (other) threads.
- All,
-}
+// Convenient re-exports
+pub use super::{GdbInterrupt, ResumeAction};
/// Base debugging operations for multi threaded targets.
#[allow(clippy::type_complexity)]
pub trait MultiThreadOps: Target {
/// Resume execution on the target.
///
- /// `actions` is an iterator over `(TidSelector, ResumeAction)` pairs which
- /// specify how various threads should be resumed (i.e: single-step vs.
- /// resume). It is _guaranteed_ to contain at least one action. It is not
- /// guaranteed to be exhaustive over all live threads, and any threads
- /// without a corresponding `TidSelector` should be left in the same state
- /// (if possible).
+ /// Prior to calling `resume`, `gdbstub` will call `clear_resume_actions`,
+ /// followed by zero or more calls to `set_resume_action`, specifying any
+ /// thread-specific resume actions.
+ ///
+ /// The `default_action` parameter specifies the "fallback" resume action
+ /// for any threads that did not have a specific resume action set via
+ /// `set_resume_action`. The GDB client typically sets this to
+ /// `ResumeAction::Continue`, though this is not guaranteed.
///
/// The `check_gdb_interrupt` callback can be invoked to check if GDB sent
/// an Interrupt packet (i.e: the user pressed Ctrl-C). It's recommended to
@@ -55,17 +47,10 @@ pub trait MultiThreadOps: Target {
/// > address.
///
/// Omitting PC adjustment may result in unexpected execution flow and/or
- /// breakpoints not appearing to work correctly.
+ /// breakpoints not working correctly.
///
/// # Additional Considerations
///
- /// ### "Non-stop" mode
- ///
- /// At the moment, `gdbstub` only supports GDB's
- /// ["All-Stop" mode](https://sourceware.org/gdb/current/onlinedocs/gdb/All_002dStop-Mode.html),
- /// whereby _all_ threads should be stopped when returning from `resume`
- /// (not just the thread associated with the `ThreadStopReason`).
- ///
/// ### Bare-Metal Targets
///
/// On bare-metal targets (such as microcontrollers or emulators), it's
@@ -75,12 +60,68 @@ pub trait MultiThreadOps: Target {
///
/// In this case, the `Tid` argument of `read/write_addrs` becomes quite
/// relevant, as different cores may have different memory maps.
+ ///
+ /// ### Running in "Non-stop" mode
+ ///
+ /// At the moment, `gdbstub` only supports GDB's
+ /// ["All-Stop" mode](https://sourceware.org/gdb/current/onlinedocs/gdb/All_002dStop-Mode.html),
+ /// whereby _all_ threads must be stopped when returning from `resume`
+ /// (not just the thread associated with the `ThreadStopReason`).
fn resume(
&mut self,
- actions: Actions<'_>,
- check_gdb_interrupt: &mut dyn FnMut() -> bool,
+ default_resume_action: ResumeAction,
+ gdb_interrupt: GdbInterrupt<'_>,
) -> Result<ThreadStopReason<<Self::Arch as Arch>::Usize>, Self::Error>;
+ /// Clear all previously set resume actions.
+ fn clear_resume_actions(&mut self) -> Result<(), Self::Error>;
+
+ /// Specify what action each thread should take when
+ /// [`resume`](Self::resume) is called.
+ ///
+ /// A simple implementation of this method would simply update an internal
+ /// `HashMap<Tid, ResumeAction>`.
+ ///
+ /// Aside from the four "base" resume actions handled by this method (i.e:
+ /// `Step`, `Continue`, `StepWithSignal`, and `ContinueWithSignal`),
+ /// there are also two additional resume actions which are only set if the
+ /// target implements their corresponding protocol extension:
+ ///
+ /// Action | Protocol Extension
+ /// ---------------------------|---------------------------
+ /// Optimized [Range Stepping] | See [`support_range_step()`]
+ /// "Stop" | Used in "Non-Stop" mode \*
+ ///
+ /// \* "Non-Stop" mode is currently unimplemented
+ ///
+ /// [Range Stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping
+ /// [`support_range_step()`]: Self::support_range_step
+ fn set_resume_action(&mut self, tid: Tid, action: ResumeAction) -> Result<(), Self::Error>;
+
+ /// Support for the optimized [range stepping] resume action.
+ ///
+ /// [range stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping
+ #[inline(always)]
+ fn support_range_step(&mut self) -> Option<MultiThreadRangeSteppingOps<Self>> {
+ None
+ }
+
+ /// Support for [reverse stepping] a target.
+ ///
+ /// [reverse stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html
+ #[inline(always)]
+ fn support_reverse_step(&mut self) -> Option<MultiThreadReverseStepOps<Self>> {
+ None
+ }
+
+ /// Support for [reverse continuing] a target.
+ ///
+ /// [reverse continuing]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html
+ #[inline(always)]
+ fn support_reverse_cont(&mut self) -> Option<MultiThreadReverseContOps<Self>> {
+ None
+ }
+
/// Read the target's registers.
///
/// If the registers could not be accessed, an appropriate non-fatal error
@@ -101,49 +142,16 @@ pub trait MultiThreadOps: Target {
tid: Tid,
) -> TargetResult<(), Self>;
- /// Read to a single register on the target.
- ///
- /// Implementations should write the value of the register using target's
- /// native byte order in the buffer `dst`.
- ///
- /// If the requested register could not be accessed, an appropriate
- /// non-fatal error should be returned.
+ /// Support for single-register access.
+ /// See [`SingleRegisterAccess`](super::SingleRegisterAccess) for more
+ /// details.
///
- /// _Note:_ This method includes a stubbed default implementation which
- /// simply returns `Ok(())`. This is due to the fact that several built-in
- /// `arch` implementations haven't been updated with proper `RegId`
- /// implementations.
- fn read_register(
- &mut self,
- reg_id: <Self::Arch as Arch>::RegId,
- dst: &mut [u8],
- tid: Tid,
- ) -> TargetResult<(), Self> {
- let _ = (reg_id, dst, tid);
- Ok(())
- }
-
- /// Write from a single register on the target.
- ///
- /// The `val` buffer contains the new value of the register in the target's
- /// native byte order. It is guaranteed to be the exact length as the target
- /// register.
- ///
- /// If the requested register could not be accessed, an appropriate
- /// non-fatal error should be returned.
- ///
- /// _Note:_ This method includes a stubbed default implementation which
- /// simply returns `Ok(())`. This is due to the fact that several built-in
- /// `arch` implementations haven't been updated with proper `RegId`
- /// implementations.
- fn write_register(
- &mut self,
- reg_id: <Self::Arch as Arch>::RegId,
- val: &[u8],
- tid: Tid,
- ) -> TargetResult<(), Self> {
- let _ = (reg_id, val, tid);
- Ok(())
+ /// While this is an optional feature, it is **highly recommended** to
+ /// implement it when possible, as it can significantly improve performance
+ /// on certain architectures.
+ #[inline(always)]
+ fn single_register_access(&mut self) -> Option<SingleRegisterAccessOps<Tid, Self>> {
+ None
}
/// Read bytes from the specified address range.
@@ -196,24 +204,110 @@ pub trait MultiThreadOps: Target {
}
}
+/// Target Extension - [Reverse continue] for multi threaded targets.
+///
+/// Reverse continue allows the target to run backwards until it reaches the end
+/// of the replay log.
+///
+/// [Reverse continue]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html
+pub trait MultiThreadReverseCont: Target + MultiThreadOps {
+ /// Reverse-continue the target.
+ fn reverse_cont(
+ &mut self,
+ gdb_interrupt: GdbInterrupt<'_>,
+ ) -> Result<ThreadStopReason<<Self::Arch as Arch>::Usize>, Self::Error>;
+}
+
+define_ext!(MultiThreadReverseContOps, MultiThreadReverseCont);
+
+/// Target Extension - [Reverse stepping] for multi threaded targets.
+///
+/// Reverse stepping allows the target to run backwards by one step.
+///
+/// [Reverse stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html
+pub trait MultiThreadReverseStep: Target + MultiThreadOps {
+ /// Reverse-step the specified [`Tid`].
+ fn reverse_step(
+ &mut self,
+ tid: Tid,
+ gdb_interrupt: GdbInterrupt<'_>,
+ ) -> Result<ThreadStopReason<<Self::Arch as Arch>::Usize>, Self::Error>;
+}
+
+define_ext!(MultiThreadReverseStepOps, MultiThreadReverseStep);
+
+/// Target Extension - Optimized [range stepping] for multi threaded targets.
+/// See [`MultiThreadOps::support_range_step`].
+///
+/// Range Stepping will step the target once, and keep stepping the target as
+/// long as execution remains between the specified start (inclusive) and end
+/// (exclusive) addresses, or another stop condition is met (e.g: a breakpoint
+/// it hit).
+///
+/// If the range is empty (`start` == `end`), then the action becomes
+/// equivalent to the ‘s’ action. In other words, single-step once, and
+/// report the stop (even if the stepped instruction jumps to start).
+///
+/// _Note:_ A stop reply may be sent at any point even if the PC is still
+/// within the stepping range; for example, it is valid to implement range
+/// stepping in a degenerate way as a single instruction step operation.
+///
+/// [range stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping
+pub trait MultiThreadRangeStepping: Target + MultiThreadOps {
+ /// See [`MultiThreadOps::set_resume_action`].
+ fn set_resume_action_range_step(
+ &mut self,
+ tid: Tid,
+ start: <Self::Arch as Arch>::Usize,
+ end: <Self::Arch as Arch>::Usize,
+ ) -> Result<(), Self::Error>;
+}
+
+define_ext!(MultiThreadRangeSteppingOps, MultiThreadRangeStepping);
+
/// Describes why a thread stopped.
+///
+/// Targets MUST only respond with stop reasons that correspond to IDETs that
+/// target has implemented.
+///
+/// e.g: A target which has not implemented the [`HwBreakpoint`] IDET must not
+/// return a `HwBreak` stop reason. While this is not enforced at compile time,
+/// doing so will result in a runtime `UnsupportedStopReason` error.
+///
+/// [`HwBreakpoint`]: crate::target::ext::breakpoints::HwBreakpoint
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ThreadStopReason<U> {
/// Completed the single-step request.
DoneStep,
- /// `check_gdb_interrupt` returned `true`
+ /// `check_gdb_interrupt` returned `true`.
GdbInterrupt,
- /// Halted
- Halted,
+ /// The process exited with the specified exit status.
+ Exited(u8),
+ /// The process terminated with the specified signal number.
+ Terminated(u8),
+ /// The program received a signal.
+ Signal(u8),
/// A thread hit a software breakpoint (e.g. due to a trap instruction).
///
+ /// Requires: [`SwBreakpoint`].
+ ///
/// NOTE: This does not necessarily have to be a breakpoint configured by
/// the client/user of the current GDB session.
+ ///
+ /// [`SwBreakpoint`]: crate::target::ext::breakpoints::SwBreakpoint
SwBreak(Tid),
/// A thread hit a hardware breakpoint.
+ ///
+ /// Requires: [`HwBreakpoint`].
+ ///
+ /// [`HwBreakpoint`]: crate::target::ext::breakpoints::HwBreakpoint
HwBreak(Tid),
/// A thread hit a watchpoint.
+ ///
+ /// Requires: [`HwWatchpoint`].
+ ///
+ /// [`HwWatchpoint`]: crate::target::ext::breakpoints::HwWatchpoint
Watch {
/// Which thread hit the watchpoint
tid: Tid,
@@ -222,35 +316,13 @@ pub enum ThreadStopReason<U> {
/// Address of watched memory
addr: U,
},
- /// The program received a signal
- Signal(u8),
-}
-
-/// An iterator of `(TidSelector, ResumeAction)` used to specify how threads
-/// should be resumed when running in multi threaded mode. It is _guaranteed_ to
-/// contain at least one action.
-///
-/// See the documentation for
-/// [`Target::resume`](trait.Target.html#tymethod.resume) for more details.
-pub struct Actions<'a> {
- inner: &'a mut dyn Iterator<Item = (TidSelector, ResumeAction)>,
-}
-
-impl core::fmt::Debug for Actions<'_> {
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- write!(f, "Actions {{ .. }}")
- }
-}
-
-impl Actions<'_> {
- pub(crate) fn new(iter: &mut dyn Iterator<Item = (TidSelector, ResumeAction)>) -> Actions<'_> {
- Actions { inner: iter }
- }
-}
-
-impl Iterator for Actions<'_> {
- type Item = (TidSelector, ResumeAction);
- fn next(&mut self) -> Option<Self::Item> {
- self.inner.next()
- }
+ /// The program has reached the end of the logged replay events.
+ ///
+ /// Requires: [`MultiThreadReverseCont`] or [`MultiThreadReverseStep`].
+ ///
+ /// This is used for GDB's reverse execution. When playing back a recording,
+ /// you may hit the end of the buffer of recorded events, and as such no
+ /// further execution can be done. This stop reason tells GDB that this has
+ /// occurred.
+ ReplayLog(ReplayLogPosition),
}
diff --git a/src/target/ext/base/single_register_access.rs b/src/target/ext/base/single_register_access.rs
new file mode 100644
index 0000000..bf94c80
--- /dev/null
+++ b/src/target/ext/base/single_register_access.rs
@@ -0,0 +1,67 @@
+use crate::arch::Arch;
+use crate::target::{Target, TargetResult};
+
+/// Target Extension - Support for single-register access.
+///
+/// While this is an optional feature, it is **highly recommended** to
+/// implement it when possible, as it can significantly improve performance
+/// on certain architectures.
+///
+/// If this extension is not implemented, the GDB client will fall-back to
+/// accessing _all_ registers, even in cases where it only requires knowing a
+/// single register's value.
+///
+/// Moreover, certain architectures have registers that are not accessible as
+/// part of the default default register file used by the `read/write_registers`
+/// methods, and can only be accessed via this extension (e.g: the RISC-V
+/// Control and Status registers).
+pub trait SingleRegisterAccess<Id>: Target {
+ /// Read to a single register on the target.
+ ///
+ /// The `tid` field identifies which thread the value should be read from.
+ /// On single threaded targets, `tid` is set to `()` and can be ignored.
+ ///
+ /// Implementations should write the value of the register using target's
+ /// native byte order in the buffer `dst`.
+ ///
+ /// If the requested register could not be accessed, an appropriate
+ /// non-fatal error should be returned.
+ ///
+ /// _Note:_ This method includes a stubbed default implementation which
+ /// simply returns `Ok(())`. This is due to the fact that several built-in
+ /// `arch` implementations haven't been updated with proper `RegId`
+ /// implementations.
+ fn read_register(
+ &mut self,
+ tid: Id,
+ reg_id: <Self::Arch as Arch>::RegId,
+ dst: &mut [u8],
+ ) -> TargetResult<(), Self>;
+
+ /// Write from a single register on the target.
+ ///
+ /// The `tid` field identifies which thread the value should be written to.
+ /// On single threaded targets, `tid` is set to `()` and can be ignored.
+ ///
+ /// The `val` buffer contains the new value of the register in the target's
+ /// native byte order. It is guaranteed to be the exact length as the target
+ /// register.
+ ///
+ /// If the requested register could not be accessed, an appropriate
+ /// non-fatal error should be returned.
+ ///
+ /// _Note:_ This method includes a stubbed default implementation which
+ /// simply returns `Ok(())`. This is due to the fact that several built-in
+ /// `arch` implementations haven't been updated with proper `RegId`
+ /// implementations.
+ fn write_register(
+ &mut self,
+ tid: Id,
+ reg_id: <Self::Arch as Arch>::RegId,
+ val: &[u8],
+ ) -> TargetResult<(), Self>;
+}
+
+/// See [`SingleRegisterAccess`]
+pub type SingleRegisterAccessOps<'a, Id, T> =
+ &'a mut dyn SingleRegisterAccess<Id, Arch = <T as Target>::Arch, Error = <T as Target>::Error>;
diff --git a/src/target/ext/base/singlethread.rs b/src/target/ext/base/singlethread.rs
index a92e1c4..f9ecaba 100644
--- a/src/target/ext/base/singlethread.rs
+++ b/src/target/ext/base/singlethread.rs
@@ -4,16 +4,18 @@ use crate::arch::Arch;
use crate::target::ext::breakpoints::WatchKind;
use crate::target::{Target, TargetResult};
-// Convenient re-export
-pub use super::ResumeAction;
+use super::{ReplayLogPosition, SingleRegisterAccessOps};
+
+// Convenient re-exports
+pub use super::{GdbInterrupt, ResumeAction};
/// Base debugging operations for single threaded targets.
#[allow(clippy::type_complexity)]
pub trait SingleThreadOps: Target {
/// Resume execution on the target.
///
- /// `action` specifies how the target should be resumed (i.e:
- /// single-step vs. full continue).
+ /// `action` specifies how the target should be resumed (i.e: step or
+ /// continue).
///
/// The `check_gdb_interrupt` callback can be invoked to check if GDB sent
/// an Interrupt packet (i.e: the user pressed Ctrl-C). It's recommended to
@@ -42,9 +44,33 @@ pub trait SingleThreadOps: Target {
fn resume(
&mut self,
action: ResumeAction,
- check_gdb_interrupt: &mut dyn FnMut() -> bool,
+ gdb_interrupt: GdbInterrupt<'_>,
) -> Result<StopReason<<Self::Arch as Arch>::Usize>, Self::Error>;
+ /// Support for the optimized [range stepping] resume action.
+ ///
+ /// [range stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping
+ #[inline(always)]
+ fn support_resume_range_step(&mut self) -> Option<SingleThreadRangeSteppingOps<Self>> {
+ None
+ }
+
+ /// Support for [reverse stepping] a target.
+ ///
+ /// [reverse stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html
+ #[inline(always)]
+ fn support_reverse_step(&mut self) -> Option<SingleThreadReverseStepOps<Self>> {
+ None
+ }
+
+ /// Support for [reverse continuing] a target.
+ ///
+ /// [reverse continuing]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html
+ #[inline(always)]
+ fn support_reverse_cont(&mut self) -> Option<SingleThreadReverseContOps<Self>> {
+ None
+ }
+
/// Read the target's registers.
fn read_registers(
&mut self,
@@ -55,47 +81,16 @@ pub trait SingleThreadOps: Target {
fn write_registers(&mut self, regs: &<Self::Arch as Arch>::Registers)
-> TargetResult<(), Self>;
- /// Read to a single register on the target.
- ///
- /// Implementations should write the value of the register using target's
- /// native byte order in the buffer `dst`.
- ///
- /// If the requested register could not be accessed, an appropriate
- /// non-fatal error should be returned.
- ///
- /// _Note:_ This method includes a stubbed default implementation which
- /// simply returns `Ok(())`. This is due to the fact that several built-in
- /// `arch` implementations haven't been updated with proper `RegId`
- /// implementations.
- fn read_register(
- &mut self,
- reg_id: <Self::Arch as Arch>::RegId,
- dst: &mut [u8],
- ) -> TargetResult<(), Self> {
- let _ = (reg_id, dst);
- Ok(())
- }
-
- /// Write from a single register on the target.
- ///
- /// The `val` buffer contains the new value of the register in the target's
- /// native byte order. It is guaranteed to be the exact length as the target
- /// register.
- ///
- /// If the requested register could not be accessed, an appropriate
- /// non-fatal error should be returned.
- ///
- /// _Note:_ This method includes a stubbed default implementation which
- /// simply returns `Ok(())`. This is due to the fact that several built-in
- /// `arch` implementations haven't been updated with proper `RegId`
- /// implementations.
- fn write_register(
- &mut self,
- reg_id: <Self::Arch as Arch>::RegId,
- val: &[u8],
- ) -> TargetResult<(), Self> {
- let _ = (reg_id, val);
- Ok(())
+ /// Support for single-register access.
+ /// See [`SingleRegisterAccess`](super::SingleRegisterAccess) for more
+ /// details.
+ ///
+ /// While this is an optional feature, it is **highly recommended** to
+ /// implement it when possible, as it can significantly improve performance
+ /// on certain architectures.
+ #[inline(always)]
+ fn single_register_access(&mut self) -> Option<SingleRegisterAccessOps<(), Self>> {
+ None
}
/// Read bytes from the specified address range.
@@ -121,7 +116,76 @@ pub trait SingleThreadOps: Target {
) -> TargetResult<(), Self>;
}
+/// Target Extension - [Reverse continue] for single threaded targets.
+///
+/// Reverse continue allows the target to run backwards until it reaches the end
+/// of the replay log.
+///
+/// [Reverse continue]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html
+pub trait SingleThreadReverseCont: Target + SingleThreadOps {
+ /// Reverse-continue the target.
+ fn reverse_cont(
+ &mut self,
+ gdb_interrupt: GdbInterrupt<'_>,
+ ) -> Result<StopReason<<Self::Arch as Arch>::Usize>, Self::Error>;
+}
+
+define_ext!(SingleThreadReverseContOps, SingleThreadReverseCont);
+
+/// Target Extension - [Reverse stepping] for single threaded targets.
+///
+/// Reverse stepping allows the target to run backwards by one step.
+///
+/// [Reverse stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html
+pub trait SingleThreadReverseStep: Target + SingleThreadOps {
+ /// Reverse-step the target.
+ fn reverse_step(
+ &mut self,
+ gdb_interrupt: GdbInterrupt<'_>,
+ ) -> Result<StopReason<<Self::Arch as Arch>::Usize>, Self::Error>;
+}
+
+define_ext!(SingleThreadReverseStepOps, SingleThreadReverseStep);
+
+/// Target Extension - Optimized [range stepping] for single threaded targets.
+/// See [`SingleThreadOps::support_resume_range_step`].
+///
+/// Range Stepping will step the target once, and keep stepping the target as
+/// long as execution remains between the specified start (inclusive) and end
+/// (exclusive) addresses, or another stop condition is met (e.g: a breakpoint
+/// it hit).
+///
+/// If the range is empty (`start` == `end`), then the action becomes
+/// equivalent to the ‘s’ action. In other words, single-step once, and
+/// report the stop (even if the stepped instruction jumps to start).
+///
+/// _Note:_ A stop reply may be sent at any point even if the PC is still
+/// within the stepping range; for example, it is valid to implement range
+/// stepping in a degenerate way as a single instruction step operation.
+///
+/// [range stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping
+pub trait SingleThreadRangeStepping: Target + SingleThreadOps {
+ /// See [`SingleThreadOps::resume`].
+ fn resume_range_step(
+ &mut self,
+ start: <Self::Arch as Arch>::Usize,
+ end: <Self::Arch as Arch>::Usize,
+ gdb_interrupt: GdbInterrupt<'_>,
+ ) -> Result<StopReason<<Self::Arch as Arch>::Usize>, Self::Error>;
+}
+
+define_ext!(SingleThreadRangeSteppingOps, SingleThreadRangeStepping);
+
/// Describes why the target stopped.
+///
+/// Targets MUST only respond with stop reasons that correspond to IDETs that
+/// target has implemented.
+///
+/// e.g: A target which has not implemented the [`HwBreakpoint`] IDET must not
+/// return a `HwBreak` stop reason. While this is not enforced at compile time,
+/// doing so will result in a runtime `UnsupportedStopReason` error.
+///
+/// [`HwBreakpoint`]: crate::target::ext::breakpoints::HwBreakpoint
// NOTE: This is a simplified version of `multithread::ThreadStopReason` that omits any references
// to Tid or threads. Internally, it is converted into multithread::ThreadStopReason.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -129,24 +193,47 @@ pub trait SingleThreadOps: Target {
pub enum StopReason<U> {
/// Completed the single-step request.
DoneStep,
- /// `check_gdb_interrupt` returned `true`
+ /// `check_gdb_interrupt` returned `true`.
GdbInterrupt,
- /// Halted
- Halted,
+ /// The process exited with the specified exit status.
+ Exited(u8),
+ /// The process terminated with the specified signal number.
+ Terminated(u8),
+ /// The program received a signal.
+ Signal(u8),
/// Hit a software breakpoint (e.g. due to a trap instruction).
///
+ /// Requires: [`SwBreakpoint`].
+ ///
/// NOTE: This does not necessarily have to be a breakpoint configured by
/// the client/user of the current GDB session.
+ ///
+ /// [`SwBreakpoint`]: crate::target::ext::breakpoints::SwBreakpoint
SwBreak,
/// Hit a hardware breakpoint.
+ ///
+ /// Requires: [`HwBreakpoint`].
+ ///
+ /// [`HwBreakpoint`]: crate::target::ext::breakpoints::HwBreakpoint
HwBreak,
/// Hit a watchpoint.
+ ///
+ /// Requires: [`HwWatchpoint`].
+ ///
+ /// [`HwWatchpoint`]: crate::target::ext::breakpoints::HwWatchpoint
Watch {
/// Kind of watchpoint that was hit
kind: WatchKind,
/// Address of watched memory
addr: U,
},
- /// The program received a signal
- Signal(u8),
+ /// The program has reached the end of the logged replay events.
+ ///
+ /// Requires: [`SingleThreadReverseCont`] or [`SingleThreadReverseStep`].
+ ///
+ /// This is used for GDB's reverse execution. When playing back a recording,
+ /// you may hit the end of the buffer of recorded events, and as such no
+ /// further execution can be done. This stop reason tells GDB that this has
+ /// occurred.
+ ReplayLog(ReplayLogPosition),
}
diff --git a/src/target/ext/breakpoints.rs b/src/target/ext/breakpoints.rs
index b39ed51..98fb5a0 100644
--- a/src/target/ext/breakpoints.rs
+++ b/src/target/ext/breakpoints.rs
@@ -3,18 +3,30 @@
use crate::arch::Arch;
use crate::target::{Target, TargetResult};
-/// The kind of watchpoint that should be set/removed.
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum WatchKind {
- /// Fire when the memory location is written to.
- Write,
- /// Fire when the memory location is read from.
- Read,
- /// Fire when the memory location is written to and/or read from.
- ReadWrite,
+/// Target Extension - Set/Remove Breakpoints.
+pub trait Breakpoints: Target {
+ /// Set/Remote software breakpoints.
+ #[inline(always)]
+ fn sw_breakpoint(&mut self) -> Option<SwBreakpointOps<Self>> {
+ None
+ }
+
+ /// Set/Remote hardware breakpoints.
+ #[inline(always)]
+ fn hw_breakpoint(&mut self) -> Option<HwBreakpointOps<Self>> {
+ None
+ }
+
+ /// Set/Remote hardware watchpoints.
+ #[inline(always)]
+ fn hw_watchpoint(&mut self) -> Option<HwWatchpointOps<Self>> {
+ None
+ }
}
-/// Target Extension - Set/remove Software Breakpoints.
+define_ext!(BreakpointsOps, Breakpoints);
+
+/// Nested Target Extension - Set/Remove Software Breakpoints.
///
/// See [this stackoverflow discussion](https://stackoverflow.com/questions/8878716/what-is-the-difference-between-hardware-and-software-breakpoints)
/// about the differences between hardware and software breakpoints.
@@ -22,23 +34,28 @@ pub enum WatchKind {
/// _Recommendation:_ If you're implementing `Target` for an emulator that's
/// using an _interpreted_ CPU (as opposed to a JIT), the simplest way to
/// implement "software" breakpoints would be to check the `PC` value after each
-/// CPU cycle.
-pub trait SwBreakpoint: Target {
+/// CPU cycle, ignoring the specified breakpoint `kind` entirely.
+pub trait SwBreakpoint: Target + Breakpoints {
/// Add a new software breakpoint.
/// Return `Ok(false)` if the operation could not be completed.
- fn add_sw_breakpoint(&mut self, addr: <Self::Arch as Arch>::Usize) -> TargetResult<bool, Self>;
+ fn add_sw_breakpoint(
+ &mut self,
+ addr: <Self::Arch as Arch>::Usize,
+ kind: <Self::Arch as Arch>::BreakpointKind,
+ ) -> TargetResult<bool, Self>;
/// Remove an existing software breakpoint.
/// Return `Ok(false)` if the operation could not be completed.
fn remove_sw_breakpoint(
&mut self,
addr: <Self::Arch as Arch>::Usize,
+ kind: <Self::Arch as Arch>::BreakpointKind,
) -> TargetResult<bool, Self>;
}
define_ext!(SwBreakpointOps, SwBreakpoint);
-/// Target Extension - Set/remove Hardware Breakpoints.
+/// Nested Target Extension - Set/Remove Hardware Breakpoints.
///
/// See [this stackoverflow discussion](https://stackoverflow.com/questions/8878716/what-is-the-difference-between-hardware-and-software-breakpoints)
/// about the differences between hardware and software breakpoints.
@@ -47,31 +64,47 @@ define_ext!(SwBreakpointOps, SwBreakpoint);
/// using an _interpreted_ CPU (as opposed to a JIT), there shouldn't be any
/// reason to implement this extension (as software breakpoints are likely to be
/// just-as-fast).
-pub trait HwBreakpoint: Target {
+pub trait HwBreakpoint: Target + Breakpoints {
/// Add a new hardware breakpoint.
/// Return `Ok(false)` if the operation could not be completed.
- fn add_hw_breakpoint(&mut self, addr: <Self::Arch as Arch>::Usize) -> TargetResult<bool, Self>;
+ fn add_hw_breakpoint(
+ &mut self,
+ addr: <Self::Arch as Arch>::Usize,
+ kind: <Self::Arch as Arch>::BreakpointKind,
+ ) -> TargetResult<bool, Self>;
/// Remove an existing hardware breakpoint.
/// Return `Ok(false)` if the operation could not be completed.
fn remove_hw_breakpoint(
&mut self,
addr: <Self::Arch as Arch>::Usize,
+ kind: <Self::Arch as Arch>::BreakpointKind,
) -> TargetResult<bool, Self>;
}
define_ext!(HwBreakpointOps, HwBreakpoint);
-/// Target Extension - Set/remove Hardware Watchpoints.
+/// The kind of watchpoint that should be set/removed.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum WatchKind {
+ /// Fire when the memory location is written to.
+ Write,
+ /// Fire when the memory location is read from.
+ Read,
+ /// Fire when the memory location is written to and/or read from.
+ ReadWrite,
+}
+
+/// Nested Target Extension - Set/Remove Hardware Watchpoints.
///
/// See the [GDB documentation](https://sourceware.org/gdb/current/onlinedocs/gdb/Set-Watchpoints.html)
/// regarding watchpoints for how they're supposed to work.
///
-/// _NOTE:_ If this extension isn't implemented, GDB will default to using
-/// _software watchpoints_, which tend to be excruciatingly slow (as
-/// they are implemented by single-stepping the system, and reading the
-/// watched memory location after each step).
-pub trait HwWatchpoint: Target {
+/// _Note:_ If this extension isn't implemented, GDB will default to using
+/// _software watchpoints_, which tend to be excruciatingly slow (as hey are
+/// implemented by single-stepping the system, and reading the watched memory
+/// location after each step).
+pub trait HwWatchpoint: Target + Breakpoints {
/// Add a new hardware watchpoint.
/// Return `Ok(false)` if the operation could not be completed.
fn add_hw_watchpoint(
diff --git a/src/target/ext/extended_mode.rs b/src/target/ext/extended_mode.rs
index 843343f..ce1e90e 100644
--- a/src/target/ext/extended_mode.rs
+++ b/src/target/ext/extended_mode.rs
@@ -30,9 +30,11 @@ pub enum ShouldTerminate {
No,
}
-impl From<ShouldTerminate> for bool {
- fn from(st: ShouldTerminate) -> bool {
- match st {
+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,
}
@@ -40,7 +42,6 @@ impl From<ShouldTerminate> for bool {
}
/// Describes how the target attached to a process.
-#[cfg(not(feature = "alloc"))]
pub enum AttachKind {
/// It attached to an existing process.
Attach,
@@ -48,7 +49,6 @@ pub enum AttachKind {
Run,
}
-#[cfg(not(feature = "alloc"))]
impl AttachKind {
pub(crate) fn was_attached(self) -> bool {
match self {
@@ -100,14 +100,15 @@ pub trait ExtendedMode: Target {
/// Query if specified PID was spawned by the target (via `run`), or if the
/// target attached to an existing process (via `attach`).
///
- /// This method is only required when the `alloc`/`std` features are
- /// disabled. If `alloc` is available, `gdbstub` will automatically track
- /// this property using a heap-allocated data structure.
- #[cfg(not(feature = "alloc"))]
+ /// 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...).
@@ -146,21 +147,25 @@ pub trait ExtendedMode: Target {
}
/// Enable/Disable ASLR for spawned processes.
- fn configure_aslr(&mut self) -> Option<ConfigureASLROps<Self>> {
+ #[inline(always)]
+ fn configure_aslr(&mut self) -> Option<ConfigureAslrOps<Self>> {
None
}
/// Set/Remove/Reset Environment variables for spawned processes.
+ #[inline(always)]
fn configure_env(&mut self) -> Option<ConfigureEnvOps<Self>> {
None
}
/// Configure if spawned processes should be spawned using a shell.
+ #[inline(always)]
fn configure_startup_shell(&mut self) -> Option<ConfigureStartupShellOps<Self>> {
None
}
/// Configure the working directory for spawned processes.
+ #[inline(always)]
fn configure_working_dir(&mut self) -> Option<ConfigureWorkingDirOps<Self>> {
None
}
@@ -194,18 +199,19 @@ impl<'args> Iterator for Args<'_, 'args> {
}
}
-/// Enable/Disable ASLR for spawned processes (for a more consistent debugging
-/// experience).
+/// 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 {
+pub trait ConfigureAslr: ExtendedMode {
/// Enable/Disable ASLR for spawned processes.
fn cfg_aslr(&mut self, enabled: bool) -> TargetResult<(), Self>;
}
-define_ext!(ConfigureASLROps, ConfigureASLR);
+define_ext!(ConfigureAslrOps, ConfigureAslr);
-/// Set/Remove/Reset the Environment variables for spawned processes.
+/// 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.
///
@@ -226,7 +232,8 @@ pub trait ConfigureEnv: ExtendedMode {
define_ext!(ConfigureEnvOps, ConfigureEnv);
-/// Configure if spawned processes should be spawned using a shell.
+/// 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 {
@@ -239,7 +246,8 @@ pub trait ConfigureStartupShell: ExtendedMode {
define_ext!(ConfigureStartupShellOps, ConfigureStartupShell);
-/// Configure the working directory for spawned processes.
+/// 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 {
diff --git a/src/target/ext/mod.rs b/src/target/ext/mod.rs
index 9289655..80342de 100644
--- a/src/target/ext/mod.rs
+++ b/src/target/ext/mod.rs
@@ -1,63 +1,35 @@
//! Extensions to [`Target`](super::Target) which add support for various
//! subsets of the GDB Remote Serial Protocol.
//!
-//! On it's own, the [`Target`](super::Target) trait doesn't actually include
-//! any methods to debug the target. Instead, `Target` uses a collection of
-//! "Inlineable Dyn Extension Traits" (IDETs) to optionally implement various
-//! subsets of the GDB protocol. For more details on IDETs, scroll down to the
-//! [How Protocol Extensions Work - Inlineable Dyn Extension Traits
-//! (IDETs)](#how-protocol-extensions-work---inlineable-dyn-extension-traits-idets)
-//! section below.
-//!
-//! As a starting point, consider implementing some of the extensions under
-//! [`breakpoints`]. For example, adding support for Software Breakpoints would
-//! require implementing the
-//! [`breakpoints::SwBreakpoint`](breakpoints::SwBreakpoint) extension, and
-//! overriding the `Target::sw_breakpoint` method to return `Some(self)`.
-//!
//! ### Note: Missing Protocol Extensions
//!
-//! `gdbstub`'s development is guided by the needs of it's contributors, with
+//! `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 feature you need that hasn't been implemented yet, (e.g:
-//! remote filesystem access, tracepoint support, etc...), consider opening an
-//! issue / filing a PR on Github!
+//! 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 GitHub!
//!
//! 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.
//!
-//! ### Note: What's with all the `<Self::Arch as Arch>::` syntax?
-//!
-//! Many of the method signatures across the `Target` extension traits include
-//! some pretty gnarly type syntax.
-//!
-//! If [rust-lang/rust#38078](https://github.com/rust-lang/rust/issues/38078)
-//! gets fixed, then types like `<Self::Arch as Arch>::Foo` could be simplified
-//! to just `Self::Arch::Foo`. Until then, the much more explicit
-//! [fully qualified syntax](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#fully-qualified-syntax-for-disambiguation-calling-methods-with-the-same-name)
-//! must be used instead.
-//!
-//! When you come across this syntax, it's highly recommended to use the
-//! concrete type instead. e.g: on a 32-bit target, instead of cluttering up
-//! the implementation with `<Self::Arch as Arch>::Usize`, just use `u32`
-//! directly.
-//!
//! ## How Protocol Extensions Work - Inlineable Dyn Extension Traits (IDETs)
//!
//! The GDB protocol is massive, and contains all sorts of optional
-//! functionality. In previous versions of `gdbstub`, the `Target` trait would
-//! directly have a method for _every single protocol extension_, resulting in
-//! literally _hundreds_ of associated methods!
+//! functionality. In the early versions of `gdbstub`, the `Target` trait
+//! directly had a method for _every single protocol extension_, which if taken
+//! to the extreme, would have resulted in literally _hundreds_ of associated
+//! methods!
//!
-//! This approach had numerous drawbacks:
+//! 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.
-//! - Required the `GdbStub` implementation to include runtime checks to deal
-//! with incorrectly implemented `Target`s.
+//! - `GdbStub`'s internal implementation needed to include _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
@@ -67,16 +39,29 @@
//! simpler API than for multi-threaded targets, but it would be
//! incorrect for a target to implement both.
//!
-//! Starting from version `0.4.0`, `gdbstub` is taking a new approach to
-//! implementing and enumerating available Target features, using a technique
-//! called **Inlineable Dyn Extension Traits**.
+//! 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
+//! would 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.
//!
-//! _Author's note:_ As far as I can tell, this isn't a very well-known trick,
+//! 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:
//!
@@ -89,7 +74,7 @@
//!
//! 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-query-able optional trait methods!
+//! runtime-enumerable optional trait methods!
//!
//! #### Technical overview
//!
@@ -97,109 +82,135 @@
//! explained though example:
//!
//! Lets say we want to add an optional protocol extension described by an
-//! `OptExt` trait to the `Target` trait. How would we do that using IDETs?
+//! `ProtocolExt` trait to a base `Protocol` trait. How would we do that using
+//! IDETs?
//!
-//! - (library) Define a `trait OptExt: Target { ... }` with all the optional
-//! methods:
-//! - Making `OptExt` a subtrait of `Target` enables using `Target`'s
-//! associated types.
+//! - (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 OptExt: Target {
+//! trait ProtocolExt: Protocol {
//! fn foo(&self);
//! // can use associated types in method signature!
//! fn bar(&mut self) -> Result<(), Self::Error>;
//! }
//! ```
//!
-//! - (library) "Tie" the `OptExt` extension trait to the original `Target`
-//! trait by adding a new `Target` method that simply returns `self` cast to a
-//! `&mut dyn OptExt`:
+//! - (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 Target {
+//! trait Protocol {
+//! // ... other methods ...
+//!
//! // Optional extension
-//! fn ext_optfeat(&mut self) -> Option<OptExtOps<Self>> {
+//! #[inline(always)]
+//! fn get_protocol_ext(&mut self) -> Option<ProtocolExtOps<Self>> {
//! // disabled by default
//! None
//! }
+//!
//! // Mutually-exclusive extensions
-//! fn ext_a_or_b(&mut self) -> EitherOrExt<Self::Arch, Self::Error>;
+//! fn get_ext_a_or_b(&mut self) -> EitherOrExt<Self::Arch, Self::Error>;
//! }
//!
//! // Using a typedef for readability
-//! type OptExtOps<T> =
-//! &'a mut dyn OptExt<Arch = <T as Target>::Arch, Error = <T as Target>::Error>;
+//! type ProtocolExtOps<T> =
+//! &'a mut dyn ProtocolExt<Arch = <T as Protocol>::Arch, Error = <T as Protocol>::Error>;
//!
//! enum EitherOrExt<A, E> {
-//! OptExtA(&'a mut dyn OptExtA<Arch = A, Error = E>),
-//! OptExtB(&'a mut dyn OptExtB<Arch = A, Error = E>),
+//! ProtocolExtA(&'a mut dyn ProtocolExtA<Arch = A, Error = E>),
+//! ProtocolExtB(&'a mut dyn ProtocolExtB<Arch = A, Error = E>),
//! }
//! ```
//!
-//! - (user) Implements the `OptExt` extension for their target (just like a
-//! normal trait).
+//! - (user) Implements the `ProtocolExt` extension for their target (just like
+//! a normal trait).
//!
//! ```rust,ignore
-//! impl OptExt for Target {
+//! impl ProtocolExt for MyTarget {
//! fn foo(&self) { ... }
//! fn bar(&mut self) -> Result<(), Self::Error> { ... }
//! }
//! ```
//!
-//! - (user) Implements the base `Target` trait, returning `Some(self)` to
-//! "enable" an extension, or `None` to leave it disabled.
+//! - (user) Implements the base `Protocol` trait, overriding the
+//! `get_protocol_ext` method to return `Some(self)`, which will effectively
+//! "enable" the extension.
//!
//! ```rust,ignore
-//! impl Target for MyTarget {
-//! // Optional extension - Always enabled
-//! fn ext_optfeat(&mut self) -> Option<OptExtOps<Self>> {
-//! Some(self) // will not compile unless `MyTarget` also implements `OptExt`
+//! impl Protocol for MyTarget {
+//! // Optional extension
+//! #[inline(always)]
+//! fn get_protocol_ext(&mut self) -> Option<ProtocolExtOps<Self>> {
+//! Some(self) // will not compile unless `MyTarget` also implements `ProtocolExt`
//! }
+//!
//! // Mutually-exclusive extensions
-//! fn ext_a_or_b(&mut self) -> EitherOrExt<Self::Arch, Self::Error> {
-//! EitherOrExt::OptExtA(self)
+//! #[inline(always)]
+//! fn get_ext_a_or_b(&mut self) -> EitherOrExt<Self::Arch, Self::Error> {
+//! EitherOrExt::ProtocolExtA(self)
//! }
//! }
//! ```
//!
-//! If the user didn't implement `OptExt`, but tried to return `Some(self)`,
-//! they'll get an error similar to:
+//! > 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
+//! `get_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: OptExt` is not satisfied
+//! error[E0277]: the trait bound `MyTarget: ProtocolExt` is not satisfied
//! --> path/to/implementation.rs:44:14
//! |
//! 44 | Some(self)
-//! | ^^^^ the trait `OptExt` is not implemented for `MyTarget`
+//! | ^^^^ the trait `ProtocolExt` is not implemented for `MyTarget`
//! |
-//! = note: required for the cast to the object type `dyn OptExt<Arch = ..., Error = ...>`
+//! = note: required for the cast to the object type `dyn ProtocolExt<Arch = ..., Error = ...>`
//! ```
//!
-//! - (library) Can now _query_ whether or not the extension is available,
+//! 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
-//! // in a method that accepts `target: impl Target`
-//! match target.ext_optfeat() {
-//! Some(ops) => ops.cool_feature(),
-//! None => { /* do nothing */ }
+//! fn execute_protocol(mut target: impl Target) {
+//! match target.get_protocol_ext() {
+//! Some(ops) => ops.foo(),
+//! None => { /* fallback when not enabled */ }
+//! }
//! }
//! ```
//!
-//! Moreover, if you take a look at the generated assembly (e.g: using
-//! godbolt.org), you'll find that the compiler is able to efficiently inline
-//! and devirtualize all the single-line `ext_` methods, which in-turn allows
-//! the dead-code-eliminator to work it's magic, and remove the unused branches
-//! from the generated code! i.e: If a target didn't implement the `OptExt`
-//! extension, then that `match` statement would be converted into a noop!
+//! 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 `get_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!
//!
-//! Check out [daniel5151/optional-trait-methods](https://github.com/daniel5151/optional-trait-methods)
-//! for some sample code that shows off the power of IDETs. It includes code
-//! snippets which can be pasted into godbolt.org directly to confirm the
-//! optimizations described above.
+//! 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.
//!
-//! Optimizing compilers really are magic!
+//! Long story short: Optimizing compilers really are magic!
//!
//! #### Summary: The Benefits of IDETs
//!
@@ -223,7 +234,7 @@
//! guardrail.
//! - **Enforce dead-code-elimination _without_ `cargo` feature flags**
//! - This is a really awesome trick: by wrapping code in a `if
-//! target.ext_optfeat().is_some()` block, it's possible to specify
+//! target.get_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.
diff --git a/src/target/mod.rs b/src/target/mod.rs
index e2139fb..d0e2483 100644
--- a/src/target/mod.rs
+++ b/src/target/mod.rs
@@ -1,4 +1,4 @@
-//! Everything related to the [`Target`] trait + associated extension traits.
+//! The core [`Target`] trait, and all its various protocol extension traits.
//!
//! The [`Target`] trait describes how to control and modify a system's
//! execution state during a GDB debugging session, and serves as the
@@ -10,23 +10,25 @@
//!
//! # Implementing `Target`
//!
-//! `gdbstub` uses a technique called "Inlineable Dyn Extension Traits" (IDETs)
-//! to expose an ergonomic and extensible interface to the GDB protocol. It's
-//! not a very common pattern, and can seem a little "weird" at first glance,
-//! but it's actually very straightforward to use!
+//! `gdbstub` uses a technique called ["Inlineable Dyn Extension Traits"](ext)
+//! (IDETs) to expose an ergonomic and extensible interface to the GDB protocol.
+//! It's not a very common pattern, and can seem a little "weird" at first
+//! glance, but IDETs are actually very straightforward to use!
//!
-//! Please refer to the [documentation in the `ext` module](ext) for more
-//! information on IDETs, and how they're used to implement `Target` and it's
-//! various extension traits.
+//! **TL;DR:** Whenever you see a method that returns something that looks like
+//! `Option<ProtocolExtOps>`, you can enable that protocol extension by
+//! implementing the `ProtocolExt` type on your target, and overriding the
+//! `Option<ProtocolExtOps>` method to return `Some(self)`.
//!
-//! **TL;DR:** Whenever you see a method that has `Option<FooOps>` in the return
-//! type, that method should return `Some(self)` if the extension is
-//! implemented, or `None` if it's unimplemented / disabled.
+//! Please refer to the [documentation in the `ext` module](ext) for more
+//! information on IDETs, including a more in-depth explanation of how they
+//! work, and how `Target` leverages them to provide fine grained control over
+//! enabled protocol features.
//!
//! ## Associated Types
//!
//! - The [`Target::Arch`](trait.Target.html#associatedtype.Arch) associated
-//! type encodes information about the target's architecture, such as it's
+//! type encodes information about the target's architecture, such as its
//! pointer size, register layout, etc... `gdbstub` comes with several
//! built-in architecture definitions, which can be found under the
//! [`arch`](../arch/index.html) module.
@@ -45,39 +47,140 @@
//! becomes `fn resume(&mut self, ...) -> Result<_, MyEmuError>`, which makes it
//! possible to preserve the target-specific error while using `gdbstub`!
//!
-//! ## Required Methods
+//! > _Aside:_ What's with all the `<Self::Arch as Arch>::` syntax?
+//!
+//! > As you explore `Target` and its many extension traits, you'll enounter
+//! many method signatures that use this pretty gnarly bit of Rust type syntax.
+//!
+//! > If [rust-lang/rust#38078](https://github.com/rust-lang/rust/issues/38078)
+//! gets fixed, then types like `<Self::Arch as Arch>::Foo` could be simplified
+//! to just `Self::Arch::Foo`, but until then, the much more explicit
+//! [fully qualified syntax](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#fully-qualified-syntax-for-disambiguation-calling-methods-with-the-same-name)
+//! must be used instead.
+//!
+//! > To improve the readability and maintainability of your own implementation,
+//! it'd be best to swap out the fully qualified syntax with whatever concrete
+//! type is being used. e.g: on a 32-bit target, instead of cluttering up a
+//! method implementation with a parameter passed as `(addr: <Self::Arch as
+//! Arch>::Usize)`, just write `(addr: u32)` directly.
//!
-//! The [`Target::base_ops`](trait.Target.html#tymethod.base_ops) method
-//! describes the base debugging operations that must be implemented by any
-//! target. These are things such as starting/stopping execution,
-//! reading/writing memory, etc..
+//! ## Required Methods (Base Protocol)
+//!
+//! A minimal `Target` implementation only needs to implement a single method:
+//! [`Target::base_ops`](trait.Target.html#tymethod.base_ops). This method is
+//! used to select which set of [`base`](crate::target::ext::base)
+//! debugging operations will be used to control the target. These are
+//! fundamental operations such as starting/stopping execution, reading/writing
+//! memory, etc...
//!
//! All other methods are entirely optional! Check out the
-//! [`target_ext`](../target_ext/index.html) module for a full list of currently
-//! supported protocol extensions.
+//! [`ext`] module for a full list of currently supported protocol extensions.
//!
-//! ## Example: A Bare-Minimum Single Threaded `Target`
+//! ### Example: A Bare-Minimum Single Threaded `Target`
//!
-//! ```rust,ignore
-//! use gdbstub::target::Target;
+//! ```rust
+//! use gdbstub::target::{Target, TargetResult};
+//! use gdbstub::target::ext::base::BaseOps;
//! use gdbstub::target::ext::base::singlethread::SingleThreadOps;
+//! use gdbstub::target::ext::base::singlethread::{ResumeAction, GdbInterrupt, StopReason};
//!
-//! impl SingleThreadOps for MyTarget {
-//! // ... omitted for brevity
-//! }
+//! struct MyTarget;
//!
//! impl Target for MyTarget {
-//! fn base_ops(&mut self) -> base::BaseOps<Self::Arch, Self::Error> {
-//! base::BaseOps::SingleThread(self)
+//! type Error = ();
+//! type Arch = gdbstub_arch::arm::Armv4t; // as an example
+//!
+//! fn base_ops(&mut self) -> BaseOps<Self::Arch, Self::Error> {
+//! BaseOps::SingleThread(self)
//! }
//! }
+//!
+//! impl SingleThreadOps for MyTarget {
+//! fn resume(
+//! &mut self,
+//! action: ResumeAction,
+//! gdb_interrupt: GdbInterrupt<'_>,
+//! ) -> Result<StopReason<u32>, ()> { todo!() }
+//!
+//! fn read_registers(
+//! &mut self,
+//! regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs,
+//! ) -> TargetResult<(), Self> { todo!() }
+//!
+//! fn write_registers(
+//! &mut self,
+//! regs: &gdbstub_arch::arm::reg::ArmCoreRegs
+//! ) -> TargetResult<(), Self> { todo!() }
+//!
+//! fn read_addrs(
+//! &mut self,
+//! start_addr: u32,
+//! data: &mut [u8],
+//! ) -> TargetResult<(), Self> { todo!() }
+//!
+//! fn write_addrs(
+//! &mut self,
+//! start_addr: u32,
+//! data: &[u8],
+//! ) -> TargetResult<(), Self> { todo!() }
+//! }
//! ```
+//!
+//! ## Optional Methods (Protocol Extensions)
+//!
+//! The GDB protocol is _massive_, and there are plenty of optional protocol
+//! extensions that targets can implement to enhance the base debugging
+//! experience. These protocol extensions range from relatively mundane things
+//! such as setting/removing breakpoints or reading/writing individual
+//! registers, but also include fancy things such as support for time travel
+//! debugging, running shell commands remotely, or even performing file IO on
+//! the target!
+//!
+//! As a starting point, consider implementing some of the breakpoint related
+//! extensions under [`breakpoints`](crate::target::ext::breakpoints). While
+//! setting/removing breakpoints is technically an "optional" part of the GDB
+//! protocol, I'm sure you'd be hard pressed to find a debugger that doesn't
+//! support breakpoints.
+//!
+//! Please make sure to read and understand [the documentation](ext) regarding
+//! how IDETs work!
+//!
+//! ### 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 GitHub!
+//!
+//! 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.
+//!
+//! ## A note on error handling
+//!
+//! As you explore the various protocol extension traits, you'll often find that
+//! functions don't return a typical [`Result<T, Self::Error>`],
+//! and will instead return a [`TargetResult<T, Self>`].
+//!
+//! At first glance, this might look a bit strange, since it might look as
+//! though the `Err` variant of `TargetResult` is actually `Self` instead of
+//! `Self::Error`! Thankfully, there's a good reason for why that's the case,
+//! which you can read about as part of the [`TargetError`] documentation.
+//!
+//! In a nutshell, `TargetResult` wraps a typical `Result<T, Self::Error>` with
+//! a few additional error types which can be reported back to the GDB client
+//! via the GDB RSP. For example, if the GDB client tried to read memory from
+//! invalid memory, instead of immediately terminating the entire debugging
+//! session, it's possible to simply return a `Err(TargetError::Errno(14)) //
+//! EFAULT`, which will notify the GDB client that the operation has failed.
use crate::arch::Arch;
pub mod ext;
-/// The error type for various methods on `Target` and it's assorted associated
+/// The error type for various methods on `Target` and its assorted associated
/// extension traits.
///
/// # Error Handling over the GDB Remote Serial Protocol
@@ -114,13 +217,13 @@ pub mod ext;
///
/// Unfortunately, a blanket impl such as `impl<T: Target> From<T::Error> for
/// TargetError<T::Error>` isn't possible, as it could result in impl conflicts.
-/// For example, if a Target decided to use `()` as it's fatal error type, then
+/// For example, if a Target decided to use `()` as its fatal error type, then
/// there would be conflict with the existing `From<()>` impl.
#[non_exhaustive]
pub enum TargetError<E> {
/// A non-specific, non-fatal error has occurred.
NonFatal,
- /// I/O Error.
+ /// I/O Error. Only available when the `std` feature is enabled.
///
/// At the moment, this is just shorthand for
/// `TargetError::NonFatal(e.raw_os_err().unwrap_or(121))`. Error code `121`
@@ -130,8 +233,6 @@ pub enum TargetError<E> {
/// LLDB protocol extension, which would allow sending additional error
/// context (in the form of an ASCII string) when an I/O error occurs. If
/// this is something you're interested in, consider opening a PR!
- ///
- /// Only available when the `std` feature is enabled.
#[cfg(feature = "std")]
Io(std::io::Error),
/// An operation-specific non-fatal error code.
@@ -140,6 +241,7 @@ pub enum TargetError<E> {
///
/// **WARNING:** Returning this error will immediately halt the target's
/// execution and return a `GdbStubError::TargetError` from `GdbStub::run`!
+ ///
/// Note that the debugging session will will _not_ be terminated, and can
/// be resumed by calling `GdbStub::run` after resolving the error and/or
/// setting up a post-mortem debugging environment.
@@ -178,8 +280,8 @@ pub type TargetResult<T, Tgt> = Result<T, TargetError<<Tgt as Target>::Error>>;
/// **`Target` is the most important trait in `gdbstub`, and must be implemented
/// by anyone who uses the library!**
///
-/// Please refer to the the documentation in the [`target` module](index.html)
-/// for more information on how to implement and work with `Target` and it's
+/// Please refer to the the documentation in the [`target` module](self)
+/// for more information on how to implement and work with `Target` and its
/// various extension traits.
pub trait Target {
/// The target's architecture.
@@ -209,38 +311,33 @@ pub trait Target {
/// ```
fn base_ops(&mut self) -> ext::base::BaseOps<Self::Arch, Self::Error>;
- /// Set/Remote software breakpoints.
- fn sw_breakpoint(&mut self) -> Option<ext::breakpoints::SwBreakpointOps<Self>> {
- None
- }
-
- /// Set/Remote hardware breakpoints.
- fn hw_breakpoint(&mut self) -> Option<ext::breakpoints::HwBreakpointOps<Self>> {
- None
- }
-
- /// Set/Remote hardware watchpoints.
- fn hw_watchpoint(&mut self) -> Option<ext::breakpoints::HwWatchpointOps<Self>> {
+ /// Set/Remove software breakpoints.
+ #[inline(always)]
+ fn breakpoints(&mut self) -> Option<ext::breakpoints::BreakpointsOps<Self>> {
None
}
/// Handle custom GDB `monitor` commands.
+ #[inline(always)]
fn monitor_cmd(&mut self) -> Option<ext::monitor_cmd::MonitorCmdOps<Self>> {
None
}
/// Support for Extended Mode operations.
+ #[inline(always)]
fn extended_mode(&mut self) -> Option<ext::extended_mode::ExtendedModeOps<Self>> {
None
}
/// Handle requests to get the target's current section (or segment)
/// offsets.
+ #[inline(always)]
fn section_offsets(&mut self) -> Option<ext::section_offsets::SectionOffsetsOps<Self>> {
None
}
/// Override the target description XML specified by `Target::Arch`.
+ #[inline(always)]
fn target_description_xml_override(
&mut self,
) -> Option<ext::target_description_xml_override::TargetDescriptionXmlOverrideOps<Self>> {
@@ -258,34 +355,32 @@ macro_rules! impl_dyn_target {
type Arch = A;
type Error = E;
+ #[inline(always)]
fn base_ops(&mut self) -> ext::base::BaseOps<Self::Arch, Self::Error> {
(**self).base_ops()
}
- fn sw_breakpoint(&mut self) -> Option<ext::breakpoints::SwBreakpointOps<Self>> {
- (**self).sw_breakpoint()
- }
-
- fn hw_breakpoint(&mut self) -> Option<ext::breakpoints::HwBreakpointOps<Self>> {
- (**self).hw_breakpoint()
- }
-
- fn hw_watchpoint(&mut self) -> Option<ext::breakpoints::HwWatchpointOps<Self>> {
- (**self).hw_watchpoint()
+ #[inline(always)]
+ fn breakpoints(&mut self) -> Option<ext::breakpoints::BreakpointsOps<Self>> {
+ (**self).breakpoints()
}
+ #[inline(always)]
fn monitor_cmd(&mut self) -> Option<ext::monitor_cmd::MonitorCmdOps<Self>> {
(**self).monitor_cmd()
}
+ #[inline(always)]
fn extended_mode(&mut self) -> Option<ext::extended_mode::ExtendedModeOps<Self>> {
(**self).extended_mode()
}
+ #[inline(always)]
fn section_offsets(&mut self) -> Option<ext::section_offsets::SectionOffsetsOps<Self>> {
(**self).section_offsets()
}
+ #[inline(always)]
fn target_description_xml_override(
&mut self,
) -> Option<ext::target_description_xml_override::TargetDescriptionXmlOverrideOps<Self>>
diff --git a/src/util/managed_vec.rs b/src/util/managed_vec.rs
index f43fc66..fd8b8a4 100644
--- a/src/util/managed_vec.rs
+++ b/src/util/managed_vec.rs
@@ -5,8 +5,6 @@ use managed::ManagedSlice;
pub struct CapacityError<Element>(pub Element);
/// Wraps a ManagedSlice in a vec-like interface.
-///
-/// TODO?: Upstream ManagedVec into the main `managed` crate?
pub struct ManagedVec<'a, 'b, T: 'a> {
buf: &'b mut ManagedSlice<'a, T>,
len: usize,
@@ -18,29 +16,30 @@ impl<'a, 'b, T> ManagedVec<'a, 'b, T> {
}
pub fn clear(&mut self) {
- match &mut self.buf {
- ManagedSlice::Borrowed(_) => self.len = 0,
- #[cfg(feature = "alloc")]
- ManagedSlice::Owned(buf) => buf.clear(),
- }
+ // While it's very tempting to just call `Vec::clear` in the `Owned` case, doing
+ // so would modify the length of the underlying `ManagedSlice`, which isn't
+ // desirable.
+ self.len = 0;
}
pub fn push(&mut self, value: T) -> Result<(), CapacityError<T>> {
- match &mut self.buf {
- ManagedSlice::Borrowed(buf) => {
- if self.len < buf.len() {
- buf[self.len] = value;
- self.len += 1;
+ if self.len < self.buf.len() {
+ self.buf[self.len] = value;
+ self.len += 1;
+ Ok(())
+ } else {
+ match &mut self.buf {
+ ManagedSlice::Borrowed(_) => Err(CapacityError(value)),
+ #[cfg(feature = "alloc")]
+ ManagedSlice::Owned(buf) => {
+ buf.push(value);
Ok(())
- } else {
- Err(CapacityError(value))
}
}
- #[cfg(feature = "alloc")]
- ManagedSlice::Owned(buf) => {
- buf.push(value);
- Ok(())
- }
}
}
+
+ pub fn as_slice<'c: 'b>(&'c self) -> &'b [T] {
+ &self.buf[..self.len]
+ }
}