aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre-Clément Tosi <ptosi@google.com>2022-08-26 10:45:40 +0100
committerPierre-Clément Tosi <ptosi@google.com>2022-08-31 10:36:32 +0100
commitf15aee5b6c91fa7d85b3b77c2dc068162f1c3e94 (patch)
tree8bd104e2eb03d2ffae5008fb6bb8bbaf51717c48
parent3536edfc369ab4da721b92021429b6b58db03bd3 (diff)
downloadgdbstub-main-16k.tar.gz
Upgrade rust/crates/gdbstub to 0.6.3main-16k
Bug: 242056749 Test: make Change-Id: I205dc8835b7206ecf407e32e38e869a759a4eaa1
-rw-r--r--.cargo/config.toml59
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--.github/pull_request_template.md93
-rw-r--r--.github/workflows/ci.yml3
-rw-r--r--Android.bp6
-rw-r--r--CHANGELOG.md26
-rw-r--r--Cargo.toml29
-rw-r--r--Cargo.toml.orig2
-rw-r--r--METADATA8
-rw-r--r--README.md61
-rw-r--r--examples/armv4t/gdb/lldb_register_info_override.rs103
-rw-r--r--examples/armv4t/gdb/mod.rs110
-rw-r--r--examples/armv4t/gdb/target_description_xml_override.rs8
-rw-r--r--examples/armv4t/main.rs8
-rw-r--r--examples/armv4t_multicore/README.md2
-rw-r--r--examples/armv4t_multicore/gdb.rs24
-rw-r--r--examples/armv4t_multicore/main.rs7
-rw-r--r--src/arch.rs151
-rw-r--r--src/lib.rs17
-rw-r--r--src/protocol/commands.rs32
-rw-r--r--src/protocol/commands/_QAgent.rs1
-rw-r--r--src/protocol/commands/_QCatchSyscalls.rs1
-rw-r--r--src/protocol/commands/_QDisableRandomization.rs1
-rw-r--r--src/protocol/commands/_QEnvironmentHexEncoded.rs1
-rw-r--r--src/protocol/commands/_QEnvironmentReset.rs1
-rw-r--r--src/protocol/commands/_QEnvironmentUnset.rs1
-rw-r--r--src/protocol/commands/_QSetWorkingDir.rs1
-rw-r--r--src/protocol/commands/_QStartNoAckMode.rs1
-rw-r--r--src/protocol/commands/_QStartupWithShell.rs1
-rw-r--r--src/protocol/commands/_bc.rs1
-rw-r--r--src/protocol/commands/_bs.rs1
-rw-r--r--src/protocol/commands/_c.rs1
-rw-r--r--src/protocol/commands/_d_upcase.rs1
-rw-r--r--src/protocol/commands/_g.rs1
-rw-r--r--src/protocol/commands/_g_upcase.rs1
-rw-r--r--src/protocol/commands/_h_upcase.rs1
-rw-r--r--src/protocol/commands/_k.rs1
-rw-r--r--src/protocol/commands/_m.rs3
-rw-r--r--src/protocol/commands/_m_upcase.rs1
-rw-r--r--src/protocol/commands/_p.rs1
-rw-r--r--src/protocol/commands/_p_upcase.rs1
-rw-r--r--src/protocol/commands/_qAttached.rs1
-rw-r--r--src/protocol/commands/_qOffsets.rs1
-rw-r--r--src/protocol/commands/_qRcmd.rs1
-rw-r--r--src/protocol/commands/_qRegisterInfo.rs17
-rw-r--r--src/protocol/commands/_qSupported.rs1
-rw-r--r--src/protocol/commands/_qThreadExtraInfo.rs32
-rw-r--r--src/protocol/commands/_qXfer_auxv_read.rs1
-rw-r--r--src/protocol/commands/_qXfer_exec_file.rs1
-rw-r--r--src/protocol/commands/_qXfer_features_read.rs1
-rw-r--r--src/protocol/commands/_qXfer_memory_map.rs1
-rw-r--r--src/protocol/commands/_qfThreadInfo.rs1
-rw-r--r--src/protocol/commands/_qsThreadInfo.rs1
-rw-r--r--src/protocol/commands/_r_upcase.rs1
-rw-r--r--src/protocol/commands/_s.rs1
-rw-r--r--src/protocol/commands/_t_upcase.rs1
-rw-r--r--src/protocol/commands/_vAttach.rs1
-rw-r--r--src/protocol/commands/_vCont.rs6
-rw-r--r--src/protocol/commands/_vFile_close.rs3
-rw-r--r--src/protocol/commands/_vFile_fstat.rs3
-rw-r--r--src/protocol/commands/_vFile_open.rs9
-rw-r--r--src/protocol/commands/_vFile_pread.rs12
-rw-r--r--src/protocol/commands/_vFile_pwrite.rs1
-rw-r--r--src/protocol/commands/_vFile_readlink.rs3
-rw-r--r--src/protocol/commands/_vFile_setfs.rs3
-rw-r--r--src/protocol/commands/_vFile_unlink.rs3
-rw-r--r--src/protocol/commands/_vKill.rs1
-rw-r--r--src/protocol/commands/_vRun.rs1
-rw-r--r--src/protocol/commands/_x_upcase.rs1
-rw-r--r--src/protocol/commands/exclamation_mark.rs1
-rw-r--r--src/protocol/commands/question_mark.rs1
-rw-r--r--src/protocol/common/hex.rs7
-rw-r--r--src/protocol/common/qxfer.rs1
-rw-r--r--src/protocol/common/thread_id.rs34
-rw-r--r--src/protocol/mod.rs2
-rw-r--r--src/protocol/response_writer.rs42
-rw-r--r--src/stub/core_impl.rs6
-rw-r--r--src/stub/core_impl/base.rs12
-rw-r--r--src/stub/core_impl/breakpoints.rs1
-rw-r--r--src/stub/core_impl/lldb_register_info.rs140
-rw-r--r--src/stub/core_impl/single_register_access.rs18
-rw-r--r--src/stub/core_impl/thread_extra_info.rs37
-rw-r--r--src/target/ext/base/multithread.rs13
-rw-r--r--src/target/ext/base/single_register_access.rs3
-rw-r--r--src/target/ext/lldb_register_info_override.rs52
-rw-r--r--src/target/ext/mod.rs2
-rw-r--r--src/target/ext/thread_extra_info.rs22
-rw-r--r--src/target/mod.rs35
-rw-r--r--src/util/dead_code_marker.rs2
89 files changed, 1225 insertions, 89 deletions
diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000..86e7347
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,59 @@
+[target.'cfg(all())']
+rustflags = [
+ "-Dfuture_incompatible",
+ "-Dnonstandard_style",
+ "-Drust_2018_idioms",
+
+ "-Wmissing_docs",
+ "-Wunsafe_op_in_unsafe_fn",
+
+ "-Wclippy::dbg_macro",
+ "-Wclippy::debug_assert_with_mut_call",
+ "-Wclippy::disallowed_types",
+ "-Wclippy::filter_map_next",
+ "-Wclippy::fn_params_excessive_bools",
+ "-Wclippy::imprecise_flops",
+ "-Wclippy::inefficient_to_string",
+ "-Wclippy::let_unit_value",
+ "-Wclippy::linkedlist",
+ "-Wclippy::lossy_float_literal",
+ "-Wclippy::macro_use_imports",
+ "-Wclippy::map_flatten",
+ "-Wclippy::match_on_vec_items",
+ "-Wclippy::mismatched_target_os",
+ "-Wclippy::needless_borrow",
+ "-Wclippy::needless_continue",
+ "-Wclippy::option_option",
+ "-Wclippy::ref_option_ref",
+ "-Wclippy::rest_pat_in_fully_bound_structs",
+ "-Wclippy::string_to_string",
+ "-Wclippy::suboptimal_flops",
+ "-Wclippy::verbose_file_reads",
+# "-Wclippy::unused_self", # might be interesting to explore this...
+
+ # deny exlicit panic paths
+ "-Wclippy::panic",
+ "-Wclippy::todo",
+ "-Wclippy::unimplemented",
+ "-Wclippy::unreachable",
+
+ "-Aclippy::collapsible_else_if",
+ "-Aclippy::collapsible_if",
+ "-Aclippy::too_many_arguments",
+ "-Aclippy::type_complexity",
+ "-Aclippy::bool_assert_comparison",
+ # Primarily due to rust-lang/rust#8995
+ #
+ # If this ever gets fixed, it's be possible to rewrite complex types using
+ # inherent associated type aliases.
+ #
+ # For example, instead of writing this monstrosity:
+ #
+ # Result<Option<MultiThreadStopReason<<Self::Arch as Arch>::Usize>>, Self::Error>
+ #
+ # ...it could be rewritten as:
+ #
+ # type StopReason = MultiThreadStopReason<<Self::Arch as Arch>::Usize>>;
+ # Result<Option<StopReason>, Self::Error>
+ "-Aclippy::type_complexity",
+]
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index a740986..4a80e34 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
{
"git": {
- "sha1": "1e4bd678a71de84981c01cf5100eaf8f754e24b6"
+ "sha1": "eb9d7be4a4095440171986b41d6ffc9fb3be1d74"
},
"path_in_vcs": ""
} \ No newline at end of file
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 876020e..55dd65f 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -14,24 +14,22 @@ Closes #(issue number) <!-- if appropriate -->
### Checklist
-- Implementation
- - [ ] `cargo build` compiles without `errors` or `warnings`
- - [ ] `cargo clippy` runs without `errors` or `warnings`
- - [ ] `cargo fmt` was run
- - [ ] All tests pass
+<!-- CI takes care of a lot of things, but there are some things that have yet to be automated -->
+
- Documentation
- - [ ] rustdoc + approprate inline code comments
- - [ ] Updated CHANGELOG.md
+ - [ ] Ensured any public-facing `rustdoc` formatting looks good (via `cargo doc`)
- [ ] (if appropriate) Added feature to "Debugging Features" in README.md
+- Validation
+ - [ ] Included output of running `examples/armv4t` with `RUST_LOG=trace` + any relevant GDB output under the "Validation" section below
+ - [ ] Included output of running `./example_no_std/check_size.sh` before/after changes under the "Validation" section below
- _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")
+ - [ ] IDET can be optimized out (confirmed via `./example_no_std/check_size.sh`)
+ - [ ] **OR** implementation requires introducing 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! -->
+<!-- Oh, and if you're integrating `gdbstub` in an open-source project, do consider updating the README.md's "Real World Examples" section to link back to your project! -->
### Validation
@@ -147,3 +145,76 @@ GDB queried if it was attached to a process with PID 1
TRACE gdbstub::protocol::response_writer > --> $00000000#7e
```
</details>
+
+<details>
+<summary>Before/After `./example_no_std/check_size.sh` output</summary>
+
+### Before
+
+```
+!!!!! EXAMPLE OUTPUT !!!!!
+
+target/release/gdbstub-nostd :
+section size addr
+.interp 28 680
+.note.gnu.build-id 36 708
+.note.ABI-tag 32 744
+.gnu.hash 36 776
+.dynsym 360 816
+.dynstr 193 1176
+.gnu.version 30 1370
+.gnu.version_r 48 1400
+.rela.dyn 408 1448
+.init 27 4096
+.plt 16 4128
+.plt.got 8 4144
+.text 15253 4160
+.fini 13 19416
+.rodata 906 20480
+.eh_frame_hdr 284 21388
+.eh_frame 1432 21672
+.init_array 8 28072
+.fini_array 8 28080
+.dynamic 448 28088
+.got 136 28536
+.data 8 28672
+.bss 8 28680
+.comment 43 0
+Total 19769
+```
+
+### After
+
+```
+!!!!! EXAMPLE OUTPUT !!!!!
+
+target/release/gdbstub-nostd :
+section size addr
+.interp 28 680
+.note.gnu.build-id 36 708
+.note.ABI-tag 32 744
+.gnu.hash 36 776
+.dynsym 360 816
+.dynstr 193 1176
+.gnu.version 30 1370
+.gnu.version_r 48 1400
+.rela.dyn 408 1448
+.init 27 4096
+.plt 16 4128
+.plt.got 8 4144
+.text 15253 4160
+.fini 13 19416
+.rodata 906 20480
+.eh_frame_hdr 284 21388
+.eh_frame 1432 21672
+.init_array 8 28072
+.fini_array 8 28080
+.dynamic 448 28088
+.got 136 28536
+.data 8 28672
+.bss 8 28680
+.comment 43 0
+Total 19769
+```
+
+</details>
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 478b222..3c2166a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,6 +12,7 @@ jobs:
with:
profile: minimal
toolchain: stable
+ override: true
- name: cargo clippy
uses: actions-rs/cargo@v1
with:
@@ -33,6 +34,7 @@ jobs:
with:
command: doc
args: --workspace --features=std
+
rustfmt:
name: rustfmt (nightly)
runs-on: ubuntu-latest
@@ -42,6 +44,7 @@ jobs:
with:
profile: minimal
toolchain: nightly
+ override: true
components: rustfmt
- name: cargo +nightly fmt
uses: actions-rs/cargo@v1
diff --git a/Android.bp b/Android.bp
index 8c60f47..c7537d7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,7 +23,7 @@ rust_library {
host_supported: true,
crate_name: "gdbstub",
cargo_env_compat: true,
- cargo_pkg_version: "0.6.1",
+ cargo_pkg_version: "0.6.3",
srcs: ["src/lib.rs"],
edition: "2018",
features: [
@@ -40,4 +40,8 @@ rust_library {
"libnum_traits",
],
proc_macros: ["libpaste"],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab1e028..5166f1e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,8 +2,34 @@ 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.6.3
+
+#### New Features
+
+- `SingleRegisterAccess`: Support reporting unavailable regs [\#107](https://github.com/daniel5151/gdbstub/pull/107) ([ptosi](https://github.com/ptosi))
+
+# 0.6.2
+
+#### New Protocol Extensions
+
+- `MultiThreadBase > ThreadExtraInfo` - Provide extra information per-thread. [\#106](https://github.com/daniel5151/gdbstub/pull/106) ([thefaxman](https://github.com/thefaxman))
+- `LldbRegisterInfo` - (LLDB specific) Report register information in the LLDB format. [\#103](https://github.com/daniel5151/gdbstub/pull/103) ([jawilk](https://github.com/jawilk))
+ - This information can be statically included as part of the `Arch` implemention, or dynamically reported via the `LldbRegisterInfoOverride` IDET.
+
+#### Bugfixes
+
+- Report thread ID in response to `?` packet. [\#105](https://github.com/daniel5151/gdbstub/pull/105) ([thefaxman](https://github.com/thefaxman))
+
+#### Internal Improvements
+
+- Tweak enabled clippy lints
+- Added a light dusting of `#[inline]` across the packet parsing code, crunching the code down even further
+- Expanded on "no-panic guarantee" docs
+
# 0.6.1
+#### New Features
+
- add LLDB-specific HostIoOpenFlags [\#100](https://github.com/daniel5151/gdbstub/pull/100) ([mrk](https://github.com/mrk-its))
# 0.6.0
diff --git a/Cargo.toml b/Cargo.toml
index 94363bf..3b54beb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,15 +12,29 @@
[package]
edition = "2018"
name = "gdbstub"
-version = "0.6.1"
+version = "0.6.3"
authors = ["Daniel Prilik <danielprilik@gmail.com>"]
-exclude = ["examples/**/*.elf", "examples/**/*.o"]
+exclude = [
+ "examples/**/*.elf",
+ "examples/**/*.o",
+]
description = "An implementation of the GDB Remote Serial Protocol in Rust"
homepage = "https://github.com/daniel5151/gdbstub"
documentation = "https://docs.rs/gdbstub"
readme = "README.md"
-keywords = ["gdb", "emulation", "no_std", "debugging"]
-categories = ["development-tools::debugging", "embedded", "emulators", "network-programming", "no-std"]
+keywords = [
+ "gdb",
+ "emulation",
+ "no_std",
+ "debugging",
+]
+categories = [
+ "development-tools::debugging",
+ "embedded",
+ "emulators",
+ "network-programming",
+ "no-std",
+]
license = "MIT OR Apache-2.0"
repository = "https://github.com/daniel5151/gdbstub"
@@ -31,6 +45,7 @@ required-features = ["std"]
[[example]]
name = "armv4t_multicore"
required-features = ["std"]
+
[dependencies.bitflags]
version = "1.3"
@@ -50,6 +65,7 @@ default-features = false
[dependencies.paste]
version = "1.0"
+
[dev-dependencies.armv4t_emu]
version = "0.1"
@@ -62,7 +78,10 @@ version = "0.4"
[features]
__dead_code_marker = []
alloc = ["managed/alloc"]
-default = ["std", "trace-pkt"]
+default = [
+ "std",
+ "trace-pkt",
+]
paranoid_unsafe = []
std = ["alloc"]
trace-pkt = ["alloc"]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 5c83903..af1bb73 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.6.1"
+version = "0.6.3"
license = "MIT OR Apache-2.0"
edition = "2018"
readme = "README.md"
diff --git a/METADATA b/METADATA
index 535454a..88e139b 100644
--- a/METADATA
+++ b/METADATA
@@ -7,13 +7,13 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://static.crates.io/crates/gdbstub/gdbstub-0.6.1.crate"
+ value: "https://static.crates.io/crates/gdbstub/gdbstub-0.6.3.crate"
}
- version: "0.6.1"
+ version: "0.6.3"
license_type: NOTICE
last_upgrade_date {
year: 2022
- month: 4
- day: 19
+ month: 8
+ day: 26
}
}
diff --git a/README.md b/README.md
index feb54f4..3e11a06 100644
--- a/README.md
+++ b/README.md
@@ -31,9 +31,8 @@ Why use `gdbstub`?
- **`#![no_std]` Ready & Size Optimized**
- `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 entirely **panic free** in most minimal configurations\*
- - \*when compiled in release mode, without the `paranoid_unsafe` cargo feature, on certain platforms.
- - Validated by inspecting the asm output of the in-tree `example_no_std`.
+ - `gdbstub` is entirely **panic free** in most minimal configurations\*, resulting in substantially smaller and more robust code.
+ - \*See the [Writing panic-free code](#writing-panic-free-code) section below for more details.
- `gdbstub` is transport-layer agnostic, and uses a basic [`Connection`](https://docs.rs/gdbstub/latest/gdbstub/conn/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": All code related to parsing/handling protocol extensions is guaranteed to be dead-code-eliminated from an optimized binary if left unimplemented. See the [Zero-overhead Protocol Extensions](#zero-overhead-protocol-extensions) section below for more details.
- `gdbstub`'s minimal configuration has an incredibly low binary size + RAM overhead, enabling it to be used on even the most resource-constrained microcontrollers.
@@ -88,6 +87,7 @@ Of course, most use-cases will want to support additional debugging features as
- Access the remote target's filesystem to read/write file
- Can be used to automatically read the remote executable on attach (using `ExecFile`)
- Read auxiliary vector (`info auxv`)
+- Extra thread info (`info threads`)
_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!
@@ -124,25 +124,29 @@ When using `gdbstub` in `#![no_std]` contexts, make sure to set `default-feature
### Real-World Examples
+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 adding it to this list!
+
- Virtual Machine Monitors (VMMs)
- - [crosvm](https://google.github.io/crosvm/running_crosvm/usage.html#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/2333)
+ - [crosvm](https://google.github.io/crosvm/running_crosvm/advanced_usage.html#gdb-support) - The Chrome OS VMM
+ - [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) - A VMM for modern cloud workloads
+ - [Firecracker](https://firecracker-microvm.github.io/) - A lightweight VMM developed by AWS (feature is in [PR](https://github.com/firecracker-microvm/firecracker/pull/2333))
- [uhyve](https://github.com/hermitcore/uhyve) - A minimal hypervisor for [RustyHermit](https://github.com/hermitcore/rusty-hermit)
- OS Kernels (using `gdbstub` on `no_std`)
- [`vmware-labs/node-replicated-kernel`](https://github.com/vmware-labs/node-replicated-kernel/tree/4326704/kernel/src/arch/x86_64/gdb) - An (experimental) research OS kernel for x86-64 (amd64) machines
- [`betrusted-io/xous-core`](https://github.com/betrusted-io/xous-core/blob/7d3d710/kernel/src/debug/gdb_server.rs) - The Xous microkernel operating system
-- 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 (ARMv4T)
+- Emulators
+ - [bevy-atari](https://github.com/mrk-its/bevy-atari) - An Atari XL/XE Emulator (MOS 6502)
+ - [rmips](https://github.com/starfleetcadet75/rmips) - MIPS R3000 virtual machine simulator
+ - [clicky](https://github.com/daniel5151/clicky/) - Emulator for classic clickwheel iPods (dual-core ARMv4T)
+ - [ts7200](https://github.com/daniel5151/ts7200/) - Emulator for the TS-7200 SoC (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
+ - [rustyboyadvance-ng](https://github.com/michelhe/rustboyadvance-ng/) - Nintendo GameBoy Advance emulator and debugger (ARMv4T)
+ - [microcorruption-emu](https://github.com/sapir/microcorruption-emu) - Emulator for the microcorruption.com ctf (MSP430)
- Other
- - [memflow](https://github.com/memflow/memflow-cli) - A physical memory introspection framework (part of `memflow-cli`)
-
-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 adding it to this list!
+ - [udbserver](https://github.com/bet4it/udbserver) - Plug-in GDB debugging for the [Unicorn Engine](https://www.unicorn-engine.org/) (Multi Architecture)
+ - [enarx](https://github.com/enarx/enarx) - An open source framework for running applications in Trusted Execution Environments
### In-tree "Toy" Examples
@@ -178,6 +182,33 @@ Enabling the `paranoid_unsafe` feature will swap out a handful of unsafe `get_un
- When the `std` feature is enabled:
- `src/connection/impls/unixstream.rs`: 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.
+## Writing panic-free code
+
+Ideally, the Rust compiler would have some way to opt-in to a strict "no-panic" mode. Unfortunately, at the time of writing (2022/04/24), no such mode exists. As such, the only way to avoid the Rust compiler + stdlib's implicit panics is by being _very careful_ when writing code, and _manually checking_ that those panicking paths get optimized out!
+
+And when I say "manually checking", I actually mean "reading through [generated assembly](example_no_std/dump_asm.sh)".
+
+Why even go through this effort?
+
+- Panic infrastructure can be _expensive_, and when you're optimizing for embedded, `no_std` use-cases, panic infrastructure brings in hundreds of additional bytes into the final binary.
+- `gdbstub` can be used to implement low-level debuggers, and if the debugger itself panics, well... it's not like you can debug it all that easily!
+
+In conclusion, here is the `gdbstub` promise regarding panicking code:
+
+`gdbstub` will not introduce any additional panics into an existing binary, subject to the following conditions:
+
+1. The binary is compiled in _release_ mode
+ - Subject to the specific `rustc` version being used (as codegen and optimization can vary wildly between versions)
+ - _Note:_ different hardware architectures may be subject to different compiler optimizations.
+ - At this time, only `x86` has been confirmed panic-free
+2. `gdbstub`'s `paranoid_unsafe` cargo feature is _disabled_
+ - See the [`unsafe` in `gdbstub`](#unsafe-in-gdbstub) section for more details.
+3. The `Arch` implementation being used doesn't include panicking code
+ - _Note:_ The arch implementations under `gdbstub_arch` are _not_ guaranteed to be panic free!
+ - If you do spot a panicking arch in `gdbstub_arch`, consider opening a PR to fix it
+
+If you're using `gdbstub` in a no-panic project and found that `gdbstub` has introduced some panicking code, please file an issue!
+
## Future Plans + Roadmap to `1.0.0`
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, the following features will most likely require at least some breaking API changes, and should therefore be implemented prior to `1.0.0`.
diff --git a/examples/armv4t/gdb/lldb_register_info_override.rs b/examples/armv4t/gdb/lldb_register_info_override.rs
new file mode 100644
index 0000000..664cb45
--- /dev/null
+++ b/examples/armv4t/gdb/lldb_register_info_override.rs
@@ -0,0 +1,103 @@
+use gdbstub::arch::lldb::{Encoding, Format, Generic, Register};
+use gdbstub::arch::RegId;
+use gdbstub::target;
+use gdbstub::target::ext::lldb_register_info_override::{Callback, CallbackToken};
+use gdbstub_arch::arm::reg::id::ArmCoreRegId;
+
+use crate::gdb::custom_arch::ArmCoreRegIdCustom;
+use crate::gdb::Emu;
+
+// (LLDB extension) This implementation is for illustrative purposes only.
+//
+// Note: In this implementation, we have r0-pc from 0-16 but cpsr is at offset
+// 25*4 in the 'g'/'G' packets, so we add 8 padding registers here. Please see
+// gdbstub/examples/armv4t/gdb/target_description_xml_override.rs for more info.
+impl target::ext::lldb_register_info_override::LldbRegisterInfoOverride for Emu {
+ fn lldb_register_info<'a>(
+ &mut self,
+ reg_id: usize,
+ reg_info: Callback<'a>,
+ ) -> Result<CallbackToken<'a>, Self::Error> {
+ match ArmCoreRegIdCustom::from_raw_id(reg_id) {
+ Some((_, None)) | None => Ok(reg_info.done()),
+ Some((r, Some(size))) => {
+ let name: String = match r {
+ // For the purpose of demonstration, we end the qRegisterInfo packet exchange
+ // when reaching the Time register id, so that this register can only be
+ // explicitly queried via the single-register read packet.
+ ArmCoreRegIdCustom::Time => return Ok(reg_info.done()),
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Gpr(i)) => match i {
+ 0 => "r0",
+ 1 => "r1",
+ 2 => "r2",
+ 3 => "r3",
+ 4 => "r4",
+ 5 => "r5",
+ 6 => "r6",
+ 7 => "r7",
+ 8 => "r8",
+ 9 => "r9",
+ 10 => "r10",
+ 11 => "r11",
+ 12 => "r12",
+ _ => "unknown",
+ },
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Sp) => "sp",
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Lr) => "lr",
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Pc) => "pc",
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Fpr(_i)) => "padding",
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Fps) => "padding",
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Cpsr) => "cpsr",
+ ArmCoreRegIdCustom::Custom => "custom",
+ _ => "unknown",
+ }
+ .into();
+ let encoding = match r {
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Gpr(_i)) => Encoding::Uint,
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Sp)
+ | ArmCoreRegIdCustom::Core(ArmCoreRegId::Pc)
+ | ArmCoreRegIdCustom::Core(ArmCoreRegId::Cpsr)
+ | ArmCoreRegIdCustom::Custom => Encoding::Uint,
+ _ => Encoding::Vector,
+ };
+ let format = match r {
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Gpr(_i)) => Format::Hex,
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Sp)
+ | ArmCoreRegIdCustom::Core(ArmCoreRegId::Pc)
+ | ArmCoreRegIdCustom::Core(ArmCoreRegId::Cpsr)
+ | ArmCoreRegIdCustom::Custom => Format::Hex,
+ _ => Format::VectorUInt8,
+ };
+ let set: String = match r {
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Gpr(_i)) => "General Purpose Registers",
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Sp)
+ | ArmCoreRegIdCustom::Core(ArmCoreRegId::Pc)
+ | ArmCoreRegIdCustom::Core(ArmCoreRegId::Cpsr)
+ | ArmCoreRegIdCustom::Custom => "General Purpose Registers",
+ _ => "Floating Point Registers",
+ }
+ .into();
+ let generic = match r {
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Sp) => Some(Generic::Sp),
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Pc) => Some(Generic::Pc),
+ _ => None,
+ };
+ let reg = Register {
+ name: &name,
+ alt_name: None,
+ bitsize: (usize::from(size)) * 8,
+ offset: reg_id * (usize::from(size)),
+ encoding,
+ format,
+ set: &set,
+ gcc: None,
+ dwarf: Some(reg_id),
+ generic,
+ container_regs: None,
+ invalidate_regs: None,
+ };
+ Ok(reg_info.write(reg))
+ }
+ }
+ }
+}
diff --git a/examples/armv4t/gdb/mod.rs b/examples/armv4t/gdb/mod.rs
index 155fff3..97f7b2d 100644
--- a/examples/armv4t/gdb/mod.rs
+++ b/examples/armv4t/gdb/mod.rs
@@ -17,6 +17,7 @@ mod catch_syscalls;
mod exec_file;
mod extended_mode;
mod host_io;
+mod lldb_register_info_override;
mod memory_map;
mod monitor_cmd;
mod section_offsets;
@@ -116,6 +117,14 @@ impl Target for Emu {
}
#[inline(always)]
+ fn support_lldb_register_info_override(
+ &mut self,
+ ) -> Option<target::ext::lldb_register_info_override::LldbRegisterInfoOverrideOps<'_, Self>>
+ {
+ Some(self)
+ }
+
+ #[inline(always)]
fn support_memory_map(&mut self) -> Option<target::ext::memory_map::MemoryMapOps<'_, Self>> {
Some(self)
}
@@ -305,6 +314,7 @@ impl target::ext::base::single_register_access::SingleRegisterAccess<()> for Emu
);
Ok(buf.len())
}
+ custom_arch::ArmCoreRegIdCustom::Unavailable => Ok(0),
}
}
@@ -332,7 +342,8 @@ impl target::ext::base::single_register_access::SingleRegisterAccess<()> for Emu
Ok(())
}
// ignore writes
- custom_arch::ArmCoreRegIdCustom::Time => Ok(()),
+ custom_arch::ArmCoreRegIdCustom::Unavailable
+ | custom_arch::ArmCoreRegIdCustom::Time => Ok(()),
}
}
}
@@ -369,6 +380,7 @@ impl target::ext::base::singlethread::SingleThreadRangeStepping for Emu {
mod custom_arch {
use core::num::NonZeroUsize;
+ use gdbstub::arch::lldb::{Encoding, Format, Generic, Register, RegisterInfo};
use gdbstub::arch::{Arch, RegId, Registers, SingleStepGdbBehavior};
use gdbstub_arch::arm::reg::id::ArmCoreRegId;
@@ -450,6 +462,8 @@ mod custom_arch {
// not sent as part of `struct ArmCoreRegsCustom`, and only accessible via the single
// register read/write functions
Time,
+ /// This pseudo-register is valid but never available
+ Unavailable,
}
impl RegId for ArmCoreRegIdCustom {
@@ -457,6 +471,7 @@ mod custom_arch {
let reg = match id {
26 => Self::Custom,
27 => Self::Time,
+ 28 => Self::Unavailable,
_ => {
let (reg, size) = ArmCoreRegId::from_raw_id(id)?;
return Some((Self::Core(reg), size));
@@ -482,6 +497,99 @@ mod custom_arch {
Some("never gets returned")
}
+ // (LLDB extension)
+ //
+ // for _purely demonstrative purposes_, even though this provides a working
+ // example, it will get overwritten by RegisterInfoOverride.
+ //
+ // See `examples/armv4t/gdb/register_info_override.rs`
+ fn lldb_register_info(reg_id: usize) -> Option<RegisterInfo<'static>> {
+ match ArmCoreRegIdCustom::from_raw_id(reg_id) {
+ Some((_, None)) | None => Some(RegisterInfo::Done),
+ Some((r, Some(size))) => {
+ let name = match r {
+ // For the purpose of demonstration, we end the qRegisterInfo packet
+ // exchange when reaching the Time register id, so that this register can
+ // only be explicitly queried via the single-register read packet.
+ ArmCoreRegIdCustom::Time => return Some(RegisterInfo::Done),
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Gpr(i)) => match i {
+ 0 => "r0",
+ 1 => "r1",
+ 2 => "r2",
+ 3 => "r3",
+ 4 => "r4",
+ 5 => "r5",
+ 6 => "r6",
+ 7 => "r7",
+ 8 => "r8",
+ 9 => "r9",
+ 10 => "r10",
+ 11 => "r11",
+ 12 => "r12",
+ _ => "unknown",
+ },
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Sp) => "sp",
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Lr) => "lr",
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Pc) => "pc",
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Fpr(_i)) => "padding",
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Fps) => "padding",
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Cpsr) => "cpsr",
+ ArmCoreRegIdCustom::Custom => "custom",
+ ArmCoreRegIdCustom::Unavailable => "Unavailable",
+ _ => "unknown",
+ };
+ let encoding = match r {
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Gpr(_i)) => Encoding::Uint,
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Sp)
+ | ArmCoreRegIdCustom::Core(ArmCoreRegId::Pc)
+ | ArmCoreRegIdCustom::Core(ArmCoreRegId::Cpsr)
+ | ArmCoreRegIdCustom::Unavailable
+ | ArmCoreRegIdCustom::Custom => Encoding::Uint,
+ _ => Encoding::Vector,
+ };
+ let format = match r {
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Gpr(_i)) => Format::Hex,
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Sp)
+ | ArmCoreRegIdCustom::Core(ArmCoreRegId::Pc)
+ | ArmCoreRegIdCustom::Core(ArmCoreRegId::Cpsr)
+ | ArmCoreRegIdCustom::Unavailable
+ | ArmCoreRegIdCustom::Custom => Format::Hex,
+ _ => Format::VectorUInt8,
+ };
+ let set = match r {
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Gpr(_i)) => {
+ "General Purpose Registers"
+ }
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Sp)
+ | ArmCoreRegIdCustom::Core(ArmCoreRegId::Pc)
+ | ArmCoreRegIdCustom::Core(ArmCoreRegId::Cpsr)
+ | ArmCoreRegIdCustom::Unavailable
+ | ArmCoreRegIdCustom::Custom => "General Purpose Registers",
+ _ => "Floating Point Registers",
+ };
+ let generic = match r {
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Sp) => Some(Generic::Sp),
+ ArmCoreRegIdCustom::Core(ArmCoreRegId::Pc) => Some(Generic::Pc),
+ _ => None,
+ };
+ let reg = Register {
+ name,
+ alt_name: None,
+ bitsize: (usize::from(size)) * 8,
+ offset: reg_id * (usize::from(size)),
+ encoding,
+ format,
+ set,
+ gcc: None,
+ dwarf: Some(reg_id),
+ generic,
+ container_regs: None,
+ invalidate_regs: None,
+ };
+ Some(RegisterInfo::Register(reg))
+ }
+ }
+ }
// armv4t supports optional single stepping.
//
// notably, x86 is an example of an arch that does _not_ support
diff --git a/examples/armv4t/gdb/target_description_xml_override.rs b/examples/armv4t/gdb/target_description_xml_override.rs
index 294b513..2c5348c 100644
--- a/examples/armv4t/gdb/target_description_xml_override.rs
+++ b/examples/armv4t/gdb/target_description_xml_override.rs
@@ -96,5 +96,13 @@ const EXTRA_XML: &str = r#"
this register via the 'p' and 'P' packets respectively
-->
<reg name="time" bitsize="32" type="uint32"/>
+
+ <!--
+ pseudo-register that is always unavailable.
+
+ it is supposed to be reported as 'x'-ed bytes in replies to 'p' packets
+ and shown by the GDB client as "<unavailable>".
+ -->
+ <reg name="unavailable" bitsize="32" type="uint32"/>
</feature>
"#;
diff --git a/examples/armv4t/main.rs b/examples/armv4t/main.rs
index 91d1a4f..96edaef 100644
--- a/examples/armv4t/main.rs
+++ b/examples/armv4t/main.rs
@@ -1,4 +1,6 @@
-#![deny(rust_2018_idioms, future_incompatible, nonstandard_style)]
+//! An incredibly simple emulator to run elf binaries compiled with
+//! `arm-none-eabi-cc -march=armv4t`. It's not modeled after any real-world
+//! system.
use std::net::{TcpListener, TcpStream};
@@ -11,9 +13,9 @@ use gdbstub::stub::SingleThreadStopReason;
use gdbstub::stub::{run_blocking, DisconnectReason, GdbStub, GdbStubError};
use gdbstub::target::Target;
-pub type DynResult<T> = Result<T, Box<dyn std::error::Error>>;
+type DynResult<T> = Result<T, Box<dyn std::error::Error>>;
-pub static TEST_PROGRAM_ELF: &[u8] = include_bytes!("test_bin/test.elf");
+const TEST_PROGRAM_ELF: &[u8] = include_bytes!("test_bin/test.elf");
mod emu;
mod gdb;
diff --git a/examples/armv4t_multicore/README.md b/examples/armv4t_multicore/README.md
index 4eef502..ae5cb96 100644
--- a/examples/armv4t_multicore/README.md
+++ b/examples/armv4t_multicore/README.md
@@ -1,4 +1,4 @@
-# armv4t
+# armv4t-multicore
An incredibly simple emulator to run elf binaries compiled with `arm-none-eabi-cc -march=armv4t`. Uses a dual-core architecture to show off `gdbstub`'s multi-process support. It's not modeled after any real-world system.
diff --git a/examples/armv4t_multicore/gdb.rs b/examples/armv4t_multicore/gdb.rs
index 9559d3d..3c8cd75 100644
--- a/examples/armv4t_multicore/gdb.rs
+++ b/examples/armv4t_multicore/gdb.rs
@@ -126,6 +126,13 @@ impl MultiThreadBase for Emu {
) -> Option<target::ext::base::multithread::MultiThreadResumeOps<'_, Self>> {
Some(self)
}
+
+ #[inline(always)]
+ fn support_thread_extra_info(
+ &mut self,
+ ) -> Option<gdbstub::target::ext::thread_extra_info::ThreadExtraInfoOps<'_, Self>> {
+ Some(self)
+ }
}
impl MultiThreadResume for Emu {
@@ -272,3 +279,20 @@ impl target::ext::breakpoints::HwWatchpoint for Emu {
Ok(true)
}
}
+
+impl target::ext::thread_extra_info::ThreadExtraInfo for Emu {
+ fn thread_extra_info(&self, tid: Tid, buf: &mut [u8]) -> Result<usize, Self::Error> {
+ let cpu_id = tid_to_cpuid(tid)?;
+ let info = format!("CPU {:?}", cpu_id);
+
+ Ok(copy_to_buf(info.as_bytes(), buf))
+ }
+}
+
+/// Copy all bytes of `data` to `buf`.
+/// Return the size of data copied.
+pub fn copy_to_buf(data: &[u8], buf: &mut [u8]) -> usize {
+ let len = buf.len().min(data.len());
+ buf[..len].copy_from_slice(&data[..len]);
+ len
+}
diff --git a/examples/armv4t_multicore/main.rs b/examples/armv4t_multicore/main.rs
index 7e3cd04..4f799a7 100644
--- a/examples/armv4t_multicore/main.rs
+++ b/examples/armv4t_multicore/main.rs
@@ -1,4 +1,7 @@
-#![deny(rust_2018_idioms, future_incompatible, nonstandard_style)]
+//! An incredibly simple emulator to run elf binaries compiled with
+//! `arm-none-eabi-cc -march=armv4t`. Uses a dual-core architecture to show off
+//! `gdbstub`'s multi-process support. It's not modeled after any real-world
+//! system.
use std::net::{TcpListener, TcpStream};
@@ -10,7 +13,7 @@ use gdbstub::conn::{Connection, ConnectionExt};
use gdbstub::stub::{run_blocking, DisconnectReason, GdbStub, GdbStubError, MultiThreadStopReason};
use gdbstub::target::Target;
-pub type DynResult<T> = Result<T, Box<dyn std::error::Error>>;
+type DynResult<T> = Result<T, Box<dyn std::error::Error>>;
static TEST_PROGRAM_ELF: &[u8] = include_bytes!("test_bin/test.elf");
diff --git a/src/arch.rs b/src/arch.rs
index 06cb49e..f13adb0 100644
--- a/src/arch.rs
+++ b/src/arch.rs
@@ -159,6 +159,35 @@ pub trait Arch {
None
}
+ /// (optional) (LLDB extension) Return register info for the specified
+ /// register.
+ ///
+ /// Implementing this method enables LLDB to dynamically query the target's
+ /// register information one by one.
+ ///
+ /// Some targets don't have register context in the compiled version of the
+ /// debugger. Help the debugger by dynamically supplying the register info
+ /// from the target. The debugger will request the register info in a
+ /// sequential manner till an error packet is received. In LLDB, the
+ /// register info search has the following
+ /// [order](https://github.com/llvm/llvm-project/blob/369ce54bb302f209239b8ebc77ad824add9df089/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp#L397-L402):
+ ///
+ /// 1. Use the target definition python file if one is specified.
+ /// 2. If the target definition doesn't have any of the info from the
+ /// target.xml (registers) then proceed to read the `target.xml`.
+ /// 3. Fall back on the `qRegisterInfo` packets.
+ /// 4. Use hardcoded defaults if available.
+ ///
+ /// See the LLDB [gdb-remote docs](https://github.com/llvm-mirror/lldb/blob/d01083a850f577b85501a0902b52fd0930de72c7/docs/lldb-gdb-remote.txt#L396)
+ /// for more details on the available information that a single register can
+ /// be described by and [#99](https://github.com/daniel5151/gdbstub/issues/99)
+ /// for more information on LLDB compatibility.
+ #[inline(always)]
+ fn lldb_register_info(reg_id: usize) -> Option<lldb::RegisterInfo<'static>> {
+ let _ = reg_id;
+ None
+ }
+
/// Encode how the mainline GDB client handles target support for
/// single-step on this particular architecture.
///
@@ -255,3 +284,125 @@ pub enum SingleStepGdbBehavior {
#[doc(hidden)]
Unknown,
}
+
+/// LLDB-specific types supporting [`Arch::lldb_register_info`] and
+/// [`LldbRegisterInfoOverride`] APIs.
+///
+/// [`LldbRegisterInfoOverride`]: crate::target::ext::lldb_register_info_override::LldbRegisterInfoOverride
+pub mod lldb {
+ /// The architecture's register information of a single register.
+ pub enum RegisterInfo<'a> {
+ /// The register info of a single register that should be written.
+ Register(Register<'a>),
+ /// The `qRegisterInfo` query shall be concluded.
+ Done,
+ }
+
+ /// Describes the register info for a single register of
+ /// the target.
+ pub struct Register<'a> {
+ /// The primary register name.
+ pub name: &'a str,
+ /// An alternate name for the register.
+ pub alt_name: Option<&'a str>,
+ /// Size in bits of a register.
+ pub bitsize: usize,
+ /// The offset within the 'g' and 'G' packet of the register data for
+ /// this register.
+ pub offset: usize,
+ /// The encoding type of the register.
+ pub encoding: Encoding,
+ /// The preferred format for display of this register.
+ pub format: Format,
+ /// The register set name this register belongs to.
+ pub set: &'a str,
+ /// The GCC compiler registers number for this register.
+ ///
+ /// _Note:_ This denotes the same `KEY:VALUE;` pair as `ehframe:VALUE;`.
+ /// See the LLDB [source](https://github.com/llvm/llvm-project/blob/b92436efcb7813fc481b30f2593a4907568d917a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp#L493).
+ pub gcc: Option<usize>,
+ /// The DWARF register number for this register that is used for this
+ /// register in the debug information.
+ pub dwarf: Option<usize>,
+ /// Specify as a generic register.
+ pub generic: Option<Generic>,
+ /// Other concrete register values this register is contained in.
+ pub container_regs: Option<&'a [usize]>,
+ /// Specifies which register values should be invalidated when this
+ /// register is modified.
+ pub invalidate_regs: Option<&'a [usize]>,
+ }
+
+ /// Describes the encoding type of the register.
+ #[non_exhaustive]
+ pub enum Encoding {
+ /// Unsigned integer
+ Uint,
+ /// Signed integer
+ Sint,
+ /// IEEE 754 float
+ IEEE754,
+ /// Vector register
+ Vector,
+ }
+
+ /// Describes the preferred format for display of this register.
+ #[non_exhaustive]
+ pub enum Format {
+ /// Binary format
+ Binary,
+ /// Decimal format
+ Decimal,
+ /// Hexadecimal format
+ Hex,
+ /// Floating point format
+ Float,
+ /// 8 bit signed int vector
+ VectorSInt8,
+ /// 8 bit unsigned int vector
+ VectorUInt8,
+ /// 16 bit signed int vector
+ VectorSInt16,
+ /// 16 bit unsigned int vector
+ VectorUInt16,
+ /// 32 bit signed int vector
+ VectorSInt32,
+ /// 32 bit unsigned int vector
+ VectorUInt32,
+ /// 32 bit floating point vector
+ VectorFloat32,
+ /// 128 bit unsigned int vector
+ VectorUInt128,
+ }
+
+ /// Describes the generic types that most CPUs have.
+ #[non_exhaustive]
+ pub enum Generic {
+ /// Program counter register
+ Pc,
+ /// Stack pointer register
+ Sp,
+ /// Frame pointer register
+ Fp,
+ /// Return address register
+ Ra,
+ /// CPU flags register
+ Flags,
+ /// Function argument 1
+ Arg1,
+ /// Function argument 2
+ Arg2,
+ /// Function argument 3
+ Arg3,
+ /// Function argument 4
+ Arg4,
+ /// Function argument 5
+ Arg5,
+ /// Function argument 6
+ Arg6,
+ /// Function argument 7
+ Arg7,
+ /// Function argument 8
+ Arg8,
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 065e7ce..d51c23b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -303,23 +303,6 @@
//! [`BlockingEventLoop`]: stub::run_blocking::BlockingEventLoop
#![cfg_attr(not(feature = "std"), no_std)]
-#![deny(missing_docs)]
-#![deny(rust_2018_idioms, future_incompatible, nonstandard_style)]
-// Primarily due to rust-lang/rust#8995
-//
-// If this ever gets fixed, it's be possible to rewrite complex types using inherent associated type
-// aliases.
-//
-// For example, instead of writing this monstrosity:
-//
-// Result<Option<MultiThreadStopReason<<Self::Arch as Arch>::Usize>>, Self::Error>
-//
-// ...it could be rewritten as:
-//
-// type StopReason = MultiThreadStopReason<<Self::Arch as Arch>::Usize>>;
-//
-// Result<Option<StopReason>, Self::Error>
-#![allow(clippy::type_complexity)]
#[cfg(feature = "alloc")]
extern crate alloc;
diff --git a/src/protocol/commands.rs b/src/protocol/commands.rs
index a0fcf80..e00fc05 100644
--- a/src/protocol/commands.rs
+++ b/src/protocol/commands.rs
@@ -51,7 +51,7 @@ macro_rules! commands {
pub mod ext {
$(
- #[allow(non_camel_case_types)]
+ #[allow(non_camel_case_types, clippy::enum_variant_names)]
pub enum [<$ext:camel>] $(<$lt>)? {
$($command(super::$mod::$command<$($lifetime)?>),)*
}
@@ -87,11 +87,13 @@ macro_rules! commands {
trait Hack {
fn support_base(&mut self) -> Option<()>;
fn support_target_xml(&mut self) -> Option<()>;
+ fn support_lldb_register_info(&mut self) -> Option<()>;
fn support_resume(&mut self) -> Option<()>;
fn support_single_register_access(&mut self) -> Option<()>;
fn support_reverse_step(&mut self) -> Option<()>;
fn support_reverse_cont(&mut self) -> Option<()>;
fn support_x_upcase_packet(&mut self) -> Option<()>;
+ fn support_thread_extra_info(&mut self) -> Option<()>;
}
impl<T: Target> Hack for T {
@@ -111,6 +113,18 @@ macro_rules! commands {
}
}
+ fn support_lldb_register_info(&mut self) -> Option<()> {
+ use crate::arch::Arch;
+ if self.use_lldb_register_info()
+ && (T::Arch::lldb_register_info(usize::max_value()).is_some()
+ || self.support_lldb_register_info_override().is_some())
+ {
+ Some(())
+ } else {
+ None
+ }
+ }
+
fn support_resume(&mut self) -> Option<()> {
self.base_ops().resume_ops().map(drop)
}
@@ -146,6 +160,14 @@ macro_rules! commands {
None
}
}
+
+ fn support_thread_extra_info(&mut self) -> Option<()> {
+ use crate::target::ext::base::BaseOps;
+ match self.base_ops() {
+ BaseOps::SingleThread(_) => None,
+ BaseOps::MultiThread(ops) => ops.support_thread_extra_info().map(drop),
+ }
+ }
}
// TODO?: use tries for more efficient longest prefix matching
@@ -288,4 +310,12 @@ commands! {
catch_syscalls use 'a {
"QCatchSyscalls" => _QCatchSyscalls::QCatchSyscalls<'a>,
}
+
+ thread_extra_info use 'a {
+ "qThreadExtraInfo" => _qThreadExtraInfo::qThreadExtraInfo<'a>,
+ }
+
+ lldb_register_info {
+ "qRegisterInfo" => _qRegisterInfo::qRegisterInfo,
+ }
}
diff --git a/src/protocol/commands/_QAgent.rs b/src/protocol/commands/_QAgent.rs
index a37669b..5ca45eb 100644
--- a/src/protocol/commands/_QAgent.rs
+++ b/src/protocol/commands/_QAgent.rs
@@ -6,6 +6,7 @@ pub struct QAgent {
}
impl<'a> ParseCommand<'a> for QAgent {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
let value = match body as &[u8] {
diff --git a/src/protocol/commands/_QCatchSyscalls.rs b/src/protocol/commands/_QCatchSyscalls.rs
index 873f39a..26a27a1 100644
--- a/src/protocol/commands/_QCatchSyscalls.rs
+++ b/src/protocol/commands/_QCatchSyscalls.rs
@@ -10,6 +10,7 @@ pub enum QCatchSyscalls<'a> {
}
impl<'a> ParseCommand<'a> for QCatchSyscalls<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
diff --git a/src/protocol/commands/_QDisableRandomization.rs b/src/protocol/commands/_QDisableRandomization.rs
index 8ed6a8b..140d68f 100644
--- a/src/protocol/commands/_QDisableRandomization.rs
+++ b/src/protocol/commands/_QDisableRandomization.rs
@@ -6,6 +6,7 @@ pub struct QDisableRandomization {
}
impl<'a> ParseCommand<'a> for QDisableRandomization {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
let value = match body as &[u8] {
diff --git a/src/protocol/commands/_QEnvironmentHexEncoded.rs b/src/protocol/commands/_QEnvironmentHexEncoded.rs
index af64e9c..dec706b 100644
--- a/src/protocol/commands/_QEnvironmentHexEncoded.rs
+++ b/src/protocol/commands/_QEnvironmentHexEncoded.rs
@@ -7,6 +7,7 @@ pub struct QEnvironmentHexEncoded<'a> {
}
impl<'a> ParseCommand<'a> for QEnvironmentHexEncoded<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
diff --git a/src/protocol/commands/_QEnvironmentReset.rs b/src/protocol/commands/_QEnvironmentReset.rs
index 454fce0..38f8c44 100644
--- a/src/protocol/commands/_QEnvironmentReset.rs
+++ b/src/protocol/commands/_QEnvironmentReset.rs
@@ -4,6 +4,7 @@ use super::prelude::*;
pub struct QEnvironmentReset;
impl<'a> ParseCommand<'a> for QEnvironmentReset {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
if !buf.into_body().is_empty() {
return None;
diff --git a/src/protocol/commands/_QEnvironmentUnset.rs b/src/protocol/commands/_QEnvironmentUnset.rs
index d890bc4..0c07c31 100644
--- a/src/protocol/commands/_QEnvironmentUnset.rs
+++ b/src/protocol/commands/_QEnvironmentUnset.rs
@@ -6,6 +6,7 @@ pub struct QEnvironmentUnset<'a> {
}
impl<'a> ParseCommand<'a> for QEnvironmentUnset<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
let key = match body {
diff --git a/src/protocol/commands/_QSetWorkingDir.rs b/src/protocol/commands/_QSetWorkingDir.rs
index ac85c99..5b0ea66 100644
--- a/src/protocol/commands/_QSetWorkingDir.rs
+++ b/src/protocol/commands/_QSetWorkingDir.rs
@@ -6,6 +6,7 @@ pub struct QSetWorkingDir<'a> {
}
impl<'a> ParseCommand<'a> for QSetWorkingDir<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
let dir = match body {
diff --git a/src/protocol/commands/_QStartNoAckMode.rs b/src/protocol/commands/_QStartNoAckMode.rs
index 0ec92dc..4c22168 100644
--- a/src/protocol/commands/_QStartNoAckMode.rs
+++ b/src/protocol/commands/_QStartNoAckMode.rs
@@ -4,6 +4,7 @@ use super::prelude::*;
pub struct QStartNoAckMode;
impl<'a> ParseCommand<'a> for QStartNoAckMode {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
if !buf.into_body().is_empty() {
return None;
diff --git a/src/protocol/commands/_QStartupWithShell.rs b/src/protocol/commands/_QStartupWithShell.rs
index 59cac2a..7dcda36 100644
--- a/src/protocol/commands/_QStartupWithShell.rs
+++ b/src/protocol/commands/_QStartupWithShell.rs
@@ -6,6 +6,7 @@ pub struct QStartupWithShell {
}
impl<'a> ParseCommand<'a> for QStartupWithShell {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
let value = match body as &[u8] {
diff --git a/src/protocol/commands/_bc.rs b/src/protocol/commands/_bc.rs
index d2d30d5..5e658b4 100644
--- a/src/protocol/commands/_bc.rs
+++ b/src/protocol/commands/_bc.rs
@@ -4,6 +4,7 @@ use super::prelude::*;
pub struct bc;
impl<'a> ParseCommand<'a> for bc {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
if !buf.into_body().is_empty() {
return None;
diff --git a/src/protocol/commands/_bs.rs b/src/protocol/commands/_bs.rs
index 30ef412..a4cea68 100644
--- a/src/protocol/commands/_bs.rs
+++ b/src/protocol/commands/_bs.rs
@@ -4,6 +4,7 @@ use super::prelude::*;
pub struct bs;
impl<'a> ParseCommand<'a> for bs {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
if !buf.into_body().is_empty() {
return None;
diff --git a/src/protocol/commands/_c.rs b/src/protocol/commands/_c.rs
index 64b0786..9063ec7 100644
--- a/src/protocol/commands/_c.rs
+++ b/src/protocol/commands/_c.rs
@@ -6,6 +6,7 @@ pub struct c<'a> {
}
impl<'a> ParseCommand<'a> for c<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
if body.is_empty() {
diff --git a/src/protocol/commands/_d_upcase.rs b/src/protocol/commands/_d_upcase.rs
index 5003f4e..c532532 100644
--- a/src/protocol/commands/_d_upcase.rs
+++ b/src/protocol/commands/_d_upcase.rs
@@ -8,6 +8,7 @@ pub struct D {
}
impl<'a> ParseCommand<'a> for D {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
let pid = match body {
diff --git a/src/protocol/commands/_g.rs b/src/protocol/commands/_g.rs
index a795424..8134be9 100644
--- a/src/protocol/commands/_g.rs
+++ b/src/protocol/commands/_g.rs
@@ -4,6 +4,7 @@ use super::prelude::*;
pub struct g;
impl<'a> ParseCommand<'a> for g {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
if !buf.into_body().is_empty() {
return None;
diff --git a/src/protocol/commands/_g_upcase.rs b/src/protocol/commands/_g_upcase.rs
index 68298ca..9756ffc 100644
--- a/src/protocol/commands/_g_upcase.rs
+++ b/src/protocol/commands/_g_upcase.rs
@@ -6,6 +6,7 @@ pub struct G<'a> {
}
impl<'a> ParseCommand<'a> for G<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
Some(G {
vals: decode_hex_buf(buf.into_body()).ok()?,
diff --git a/src/protocol/commands/_h_upcase.rs b/src/protocol/commands/_h_upcase.rs
index 0fdb3ac..3e23ced 100644
--- a/src/protocol/commands/_h_upcase.rs
+++ b/src/protocol/commands/_h_upcase.rs
@@ -15,6 +15,7 @@ pub struct H {
}
impl<'a> ParseCommand<'a> for H {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
if body.is_empty() {
diff --git a/src/protocol/commands/_k.rs b/src/protocol/commands/_k.rs
index 4fd1fc2..b15f2fa 100644
--- a/src/protocol/commands/_k.rs
+++ b/src/protocol/commands/_k.rs
@@ -4,6 +4,7 @@ use super::prelude::*;
pub struct k;
impl<'a> ParseCommand<'a> for k {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
if !buf.into_body().is_empty() {
return None;
diff --git a/src/protocol/commands/_m.rs b/src/protocol/commands/_m.rs
index 4b879f4..59ebdb7 100644
--- a/src/protocol/commands/_m.rs
+++ b/src/protocol/commands/_m.rs
@@ -9,6 +9,7 @@ pub struct m<'a> {
}
impl<'a> ParseCommand<'a> for m<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
// the total packet buffer currently looks like:
//
@@ -37,8 +38,6 @@ impl<'a> ParseCommand<'a> for m<'a> {
let addr_len = addr.len();
let len = decode_hex(body.next()?).ok()?;
- drop(body);
-
// ensures that `split_at_mut` doesn't panic
if buf.len() < body_range.start + addr_len {
return None;
diff --git a/src/protocol/commands/_m_upcase.rs b/src/protocol/commands/_m_upcase.rs
index 0e4f9a6..b436a65 100644
--- a/src/protocol/commands/_m_upcase.rs
+++ b/src/protocol/commands/_m_upcase.rs
@@ -8,6 +8,7 @@ pub struct M<'a> {
}
impl<'a> ParseCommand<'a> for M<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
diff --git a/src/protocol/commands/_p.rs b/src/protocol/commands/_p.rs
index 6405622..1f09e05 100644
--- a/src/protocol/commands/_p.rs
+++ b/src/protocol/commands/_p.rs
@@ -8,6 +8,7 @@ pub struct p<'a> {
}
impl<'a> ParseCommand<'a> for p<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let (buf, body_range) = buf.into_raw_buf();
let body = buf.get(body_range.start..body_range.end)?;
diff --git a/src/protocol/commands/_p_upcase.rs b/src/protocol/commands/_p_upcase.rs
index dd721d5..4f61906 100644
--- a/src/protocol/commands/_p_upcase.rs
+++ b/src/protocol/commands/_p_upcase.rs
@@ -7,6 +7,7 @@ pub struct P<'a> {
}
impl<'a> ParseCommand<'a> for P<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
let mut body = body.split_mut(|&b| b == b'=');
diff --git a/src/protocol/commands/_qAttached.rs b/src/protocol/commands/_qAttached.rs
index a5429aa..3655559 100644
--- a/src/protocol/commands/_qAttached.rs
+++ b/src/protocol/commands/_qAttached.rs
@@ -8,6 +8,7 @@ pub struct qAttached {
}
impl<'a> ParseCommand<'a> for qAttached {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
let pid = match body {
diff --git a/src/protocol/commands/_qOffsets.rs b/src/protocol/commands/_qOffsets.rs
index a66e701..718e3f2 100644
--- a/src/protocol/commands/_qOffsets.rs
+++ b/src/protocol/commands/_qOffsets.rs
@@ -4,6 +4,7 @@ use super::prelude::*;
pub struct qOffsets;
impl<'a> ParseCommand<'a> for qOffsets {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
crate::__dead_code_marker!("qOffsets", "from_packet");
diff --git a/src/protocol/commands/_qRcmd.rs b/src/protocol/commands/_qRcmd.rs
index 7d2ec11..62f1d0d 100644
--- a/src/protocol/commands/_qRcmd.rs
+++ b/src/protocol/commands/_qRcmd.rs
@@ -6,6 +6,7 @@ pub struct qRcmd<'a> {
}
impl<'a> ParseCommand<'a> for qRcmd<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
crate::__dead_code_marker!("qRcmd", "from_packet");
diff --git a/src/protocol/commands/_qRegisterInfo.rs b/src/protocol/commands/_qRegisterInfo.rs
new file mode 100644
index 0000000..4b9e59d
--- /dev/null
+++ b/src/protocol/commands/_qRegisterInfo.rs
@@ -0,0 +1,17 @@
+use super::prelude::*;
+
+#[derive(Debug)]
+pub struct qRegisterInfo {
+ pub reg_id: usize,
+}
+
+impl<'a> ParseCommand<'a> for qRegisterInfo {
+ #[inline(always)]
+ fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
+ let body = buf.into_body();
+
+ let reg_id = decode_hex(body).ok()?;
+
+ Some(qRegisterInfo { reg_id })
+ }
+}
diff --git a/src/protocol/commands/_qSupported.rs b/src/protocol/commands/_qSupported.rs
index 368566d..c436d09 100644
--- a/src/protocol/commands/_qSupported.rs
+++ b/src/protocol/commands/_qSupported.rs
@@ -7,6 +7,7 @@ pub struct qSupported<'a> {
}
impl<'a> ParseCommand<'a> for qSupported<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let packet_buffer_len = buf.full_len();
let body = buf.into_body();
diff --git a/src/protocol/commands/_qThreadExtraInfo.rs b/src/protocol/commands/_qThreadExtraInfo.rs
new file mode 100644
index 0000000..9fe6200
--- /dev/null
+++ b/src/protocol/commands/_qThreadExtraInfo.rs
@@ -0,0 +1,32 @@
+use super::prelude::*;
+
+use crate::protocol::common::thread_id::ThreadId;
+use crate::protocol::ConcreteThreadId;
+
+#[derive(Debug)]
+pub struct qThreadExtraInfo<'a> {
+ pub id: ConcreteThreadId,
+
+ pub buf: &'a mut [u8],
+}
+
+impl<'a> ParseCommand<'a> for qThreadExtraInfo<'a> {
+ #[inline(always)]
+ fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
+ let (buf, body_range) = buf.into_raw_buf();
+ let body = buf.get(body_range.start..body_range.end)?;
+
+ if body.is_empty() {
+ return None;
+ }
+
+ match body {
+ [b',', body @ ..] => {
+ let id = ConcreteThreadId::try_from(ThreadId::try_from(body).ok()?).ok()?;
+
+ Some(qThreadExtraInfo { id, buf })
+ }
+ _ => None,
+ }
+ }
+}
diff --git a/src/protocol/commands/_qXfer_auxv_read.rs b/src/protocol/commands/_qXfer_auxv_read.rs
index 1ed4967..15a8c17 100644
--- a/src/protocol/commands/_qXfer_auxv_read.rs
+++ b/src/protocol/commands/_qXfer_auxv_read.rs
@@ -8,6 +8,7 @@ pub type qXferAuxvRead<'a> = QXferReadBase<'a, AuxvAnnex>;
pub struct AuxvAnnex;
impl<'a> ParseAnnex<'a> for AuxvAnnex {
+ #[inline(always)]
fn from_buf(buf: &[u8]) -> Option<Self> {
if buf != b"" {
return None;
diff --git a/src/protocol/commands/_qXfer_exec_file.rs b/src/protocol/commands/_qXfer_exec_file.rs
index df1aa1d..8280bda 100644
--- a/src/protocol/commands/_qXfer_exec_file.rs
+++ b/src/protocol/commands/_qXfer_exec_file.rs
@@ -11,6 +11,7 @@ pub struct ExecFileAnnex {
}
impl<'a> ParseAnnex<'a> for ExecFileAnnex {
+ #[inline(always)]
fn from_buf(buf: &[u8]) -> Option<Self> {
let pid = match buf {
[] => None,
diff --git a/src/protocol/commands/_qXfer_features_read.rs b/src/protocol/commands/_qXfer_features_read.rs
index 8fe9650..73f96e5 100644
--- a/src/protocol/commands/_qXfer_features_read.rs
+++ b/src/protocol/commands/_qXfer_features_read.rs
@@ -10,6 +10,7 @@ pub struct FeaturesAnnex<'a> {
}
impl<'a> ParseAnnex<'a> for FeaturesAnnex<'a> {
+ #[inline(always)]
fn from_buf(buf: &'a [u8]) -> Option<Self> {
Some(FeaturesAnnex { name: buf })
}
diff --git a/src/protocol/commands/_qXfer_memory_map.rs b/src/protocol/commands/_qXfer_memory_map.rs
index 01aa21a..169d1b4 100644
--- a/src/protocol/commands/_qXfer_memory_map.rs
+++ b/src/protocol/commands/_qXfer_memory_map.rs
@@ -8,6 +8,7 @@ pub type qXferMemoryMapRead<'a> = QXferReadBase<'a, MemoryMapAnnex>;
pub struct MemoryMapAnnex;
impl<'a> ParseAnnex<'a> for MemoryMapAnnex {
+ #[inline(always)]
fn from_buf(buf: &[u8]) -> Option<Self> {
if buf != b"" {
return None;
diff --git a/src/protocol/commands/_qfThreadInfo.rs b/src/protocol/commands/_qfThreadInfo.rs
index eff541f..5276ddf 100644
--- a/src/protocol/commands/_qfThreadInfo.rs
+++ b/src/protocol/commands/_qfThreadInfo.rs
@@ -4,6 +4,7 @@ use super::prelude::*;
pub struct qfThreadInfo;
impl<'a> ParseCommand<'a> for qfThreadInfo {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
if !buf.into_body().is_empty() {
return None;
diff --git a/src/protocol/commands/_qsThreadInfo.rs b/src/protocol/commands/_qsThreadInfo.rs
index d4347ff..dc1e23e 100644
--- a/src/protocol/commands/_qsThreadInfo.rs
+++ b/src/protocol/commands/_qsThreadInfo.rs
@@ -4,6 +4,7 @@ use super::prelude::*;
pub struct qsThreadInfo;
impl<'a> ParseCommand<'a> for qsThreadInfo {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
if !buf.into_body().is_empty() {
return None;
diff --git a/src/protocol/commands/_r_upcase.rs b/src/protocol/commands/_r_upcase.rs
index ff4ec49..72b9fcc 100644
--- a/src/protocol/commands/_r_upcase.rs
+++ b/src/protocol/commands/_r_upcase.rs
@@ -4,6 +4,7 @@ use super::prelude::*;
pub struct R;
impl<'a> ParseCommand<'a> for R {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
crate::__dead_code_marker!("R", "from_packet");
diff --git a/src/protocol/commands/_s.rs b/src/protocol/commands/_s.rs
index 83fe4ce..817241e 100644
--- a/src/protocol/commands/_s.rs
+++ b/src/protocol/commands/_s.rs
@@ -6,6 +6,7 @@ pub struct s<'a> {
}
impl<'a> ParseCommand<'a> for s<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
if body.is_empty() {
diff --git a/src/protocol/commands/_t_upcase.rs b/src/protocol/commands/_t_upcase.rs
index 7ae257c..e7c4636 100644
--- a/src/protocol/commands/_t_upcase.rs
+++ b/src/protocol/commands/_t_upcase.rs
@@ -8,6 +8,7 @@ pub struct T {
}
impl<'a> ParseCommand<'a> for T {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
Some(T {
diff --git a/src/protocol/commands/_vAttach.rs b/src/protocol/commands/_vAttach.rs
index 81bc924..252db54 100644
--- a/src/protocol/commands/_vAttach.rs
+++ b/src/protocol/commands/_vAttach.rs
@@ -8,6 +8,7 @@ pub struct vAttach {
}
impl<'a> ParseCommand<'a> for vAttach {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
crate::__dead_code_marker!("vAttach", "from_packet");
diff --git a/src/protocol/commands/_vCont.rs b/src/protocol/commands/_vCont.rs
index 0a8d3c0..b90c9e6 100644
--- a/src/protocol/commands/_vCont.rs
+++ b/src/protocol/commands/_vCont.rs
@@ -19,6 +19,7 @@ pub enum vCont<'a> {
}
impl<'a> ParseCommand<'a> for vCont<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
match body as &[u8] {
@@ -40,10 +41,12 @@ impl<'a> Actions<'a> {
Actions::Buf(ActionsBuf(buf))
}
+ #[inline(always)]
pub fn new_step(tid: SpecificThreadId) -> Actions<'a> {
Actions::FixedStep(tid)
}
+ #[inline(always)]
pub fn new_continue(tid: SpecificThreadId) -> Actions<'a> {
Actions::FixedCont(tid)
}
@@ -101,6 +104,7 @@ pub enum VContKind<'a> {
}
impl<'a> VContKind<'a> {
+ #[inline(always)]
fn from_bytes(s: &[u8]) -> Option<VContKind<'_>> {
use self::VContKind::*;
@@ -134,6 +138,8 @@ where
B: Iterator<Item = T>,
{
type Item = T;
+
+ #[inline(always)]
fn next(&mut self) -> Option<T> {
match self {
EitherIter::A(a) => a.next(),
diff --git a/src/protocol/commands/_vFile_close.rs b/src/protocol/commands/_vFile_close.rs
index 5bddfd2..8f85354 100644
--- a/src/protocol/commands/_vFile_close.rs
+++ b/src/protocol/commands/_vFile_close.rs
@@ -6,6 +6,7 @@ pub struct vFileClose {
}
impl<'a> ParseCommand<'a> for vFileClose {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
if body.is_empty() {
@@ -16,7 +17,7 @@ impl<'a> ParseCommand<'a> for vFileClose {
[b':', body @ ..] => {
let fd = decode_hex(body).ok()?;
Some(vFileClose { fd })
- },
+ }
_ => None,
}
}
diff --git a/src/protocol/commands/_vFile_fstat.rs b/src/protocol/commands/_vFile_fstat.rs
index 63e5bbd..301dd77 100644
--- a/src/protocol/commands/_vFile_fstat.rs
+++ b/src/protocol/commands/_vFile_fstat.rs
@@ -6,6 +6,7 @@ pub struct vFileFstat {
}
impl<'a> ParseCommand<'a> for vFileFstat {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
if body.is_empty() {
@@ -16,7 +17,7 @@ impl<'a> ParseCommand<'a> for vFileFstat {
[b':', body @ ..] => {
let fd = decode_hex(body).ok()?;
Some(vFileFstat { fd })
- },
+ }
_ => None,
}
}
diff --git a/src/protocol/commands/_vFile_open.rs b/src/protocol/commands/_vFile_open.rs
index 290772d..d49995a 100644
--- a/src/protocol/commands/_vFile_open.rs
+++ b/src/protocol/commands/_vFile_open.rs
@@ -10,6 +10,7 @@ pub struct vFileOpen<'a> {
}
impl<'a> ParseCommand<'a> for vFileOpen<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
if body.is_empty() {
@@ -22,8 +23,12 @@ impl<'a> ParseCommand<'a> for vFileOpen<'a> {
let filename = decode_hex_buf(body.next()?).ok()?;
let flags = HostIoOpenFlags::from_bits(decode_hex(body.next()?).ok()?)?;
let mode = HostIoOpenMode::from_bits(decode_hex(body.next()?).ok()?)?;
- Some(vFileOpen { filename, flags, mode })
- },
+ Some(vFileOpen {
+ filename,
+ flags,
+ mode,
+ })
+ }
_ => None,
}
}
diff --git a/src/protocol/commands/_vFile_pread.rs b/src/protocol/commands/_vFile_pread.rs
index e8fc743..7e40f50 100644
--- a/src/protocol/commands/_vFile_pread.rs
+++ b/src/protocol/commands/_vFile_pread.rs
@@ -10,6 +10,7 @@ pub struct vFilePread<'a> {
}
impl<'a> ParseCommand<'a> for vFilePread<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let (buf, body_range) = buf.into_raw_buf();
let body = buf.get_mut(body_range.start..body_range.end)?;
@@ -25,10 +26,13 @@ impl<'a> ParseCommand<'a> for vFilePread<'a> {
let count = decode_hex(body.next()?).ok()?;
let offset = decode_hex(body.next()?).ok()?;
- drop(body);
-
- Some(vFilePread { fd, count, offset, buf })
- },
+ Some(vFilePread {
+ fd,
+ count,
+ offset,
+ buf,
+ })
+ }
_ => None,
}
}
diff --git a/src/protocol/commands/_vFile_pwrite.rs b/src/protocol/commands/_vFile_pwrite.rs
index 62f2ea9..2dbf4b4 100644
--- a/src/protocol/commands/_vFile_pwrite.rs
+++ b/src/protocol/commands/_vFile_pwrite.rs
@@ -10,6 +10,7 @@ pub struct vFilePwrite<'a> {
}
impl<'a> ParseCommand<'a> for vFilePwrite<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
if body.is_empty() {
diff --git a/src/protocol/commands/_vFile_readlink.rs b/src/protocol/commands/_vFile_readlink.rs
index 9a0ae76..4488e47 100644
--- a/src/protocol/commands/_vFile_readlink.rs
+++ b/src/protocol/commands/_vFile_readlink.rs
@@ -8,6 +8,7 @@ pub struct vFileReadlink<'a> {
}
impl<'a> ParseCommand<'a> for vFileReadlink<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let (buf, body_range) = buf.into_raw_buf();
// TODO: rewrite to avoid panic
@@ -21,7 +22,7 @@ impl<'a> ParseCommand<'a> for vFileReadlink<'a> {
[b':', body @ ..] => {
let filename = decode_hex_buf(body).ok()?;
Some(vFileReadlink { filename, buf })
- },
+ }
_ => None,
}
}
diff --git a/src/protocol/commands/_vFile_setfs.rs b/src/protocol/commands/_vFile_setfs.rs
index c027d6e..41a3100 100644
--- a/src/protocol/commands/_vFile_setfs.rs
+++ b/src/protocol/commands/_vFile_setfs.rs
@@ -8,6 +8,7 @@ pub struct vFileSetfs {
}
impl<'a> ParseCommand<'a> for vFileSetfs {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
if body.is_empty() {
@@ -21,7 +22,7 @@ impl<'a> ParseCommand<'a> for vFileSetfs {
Some(pid) => FsKind::Pid(pid),
};
Some(vFileSetfs { fs })
- },
+ }
_ => None,
}
}
diff --git a/src/protocol/commands/_vFile_unlink.rs b/src/protocol/commands/_vFile_unlink.rs
index 2a5a4e6..9f064df 100644
--- a/src/protocol/commands/_vFile_unlink.rs
+++ b/src/protocol/commands/_vFile_unlink.rs
@@ -6,6 +6,7 @@ pub struct vFileUnlink<'a> {
}
impl<'a> ParseCommand<'a> for vFileUnlink<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
if body.is_empty() {
@@ -16,7 +17,7 @@ impl<'a> ParseCommand<'a> for vFileUnlink<'a> {
[b':', body @ ..] => {
let filename = decode_hex_buf(body).ok()?;
Some(vFileUnlink { filename })
- },
+ }
_ => None,
}
}
diff --git a/src/protocol/commands/_vKill.rs b/src/protocol/commands/_vKill.rs
index c7cf462..a1d687b 100644
--- a/src/protocol/commands/_vKill.rs
+++ b/src/protocol/commands/_vKill.rs
@@ -8,6 +8,7 @@ pub struct vKill {
}
impl<'a> ParseCommand<'a> for vKill {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
let pid = match body {
diff --git a/src/protocol/commands/_vRun.rs b/src/protocol/commands/_vRun.rs
index 4683dc4..26ced11 100644
--- a/src/protocol/commands/_vRun.rs
+++ b/src/protocol/commands/_vRun.rs
@@ -9,6 +9,7 @@ pub struct vRun<'a> {
}
impl<'a> ParseCommand<'a> for vRun<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
diff --git a/src/protocol/commands/_x_upcase.rs b/src/protocol/commands/_x_upcase.rs
index 2be4c8e..32fe697 100644
--- a/src/protocol/commands/_x_upcase.rs
+++ b/src/protocol/commands/_x_upcase.rs
@@ -10,6 +10,7 @@ pub struct X<'a> {
}
impl<'a> ParseCommand<'a> for X<'a> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
diff --git a/src/protocol/commands/exclamation_mark.rs b/src/protocol/commands/exclamation_mark.rs
index f99252d..fb56202 100644
--- a/src/protocol/commands/exclamation_mark.rs
+++ b/src/protocol/commands/exclamation_mark.rs
@@ -4,6 +4,7 @@ use super::prelude::*;
pub struct ExclamationMark;
impl<'a> ParseCommand<'a> for ExclamationMark {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
if !buf.into_body().is_empty() {
return None;
diff --git a/src/protocol/commands/question_mark.rs b/src/protocol/commands/question_mark.rs
index c0cf6ce..fa7d601 100644
--- a/src/protocol/commands/question_mark.rs
+++ b/src/protocol/commands/question_mark.rs
@@ -4,6 +4,7 @@ use super::prelude::*;
pub struct QuestionMark;
impl<'a> ParseCommand<'a> for QuestionMark {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
if !buf.into_body().is_empty() {
return None;
diff --git a/src/protocol/common/hex.rs b/src/protocol/common/hex.rs
index e30d88c..5c58605 100644
--- a/src/protocol/common/hex.rs
+++ b/src/protocol/common/hex.rs
@@ -54,6 +54,7 @@ pub enum DecodeHexBufError {
NotAscii,
}
+#[inline]
fn ascii2byte(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
@@ -65,8 +66,9 @@ fn ascii2byte(c: u8) -> Option<u8> {
}
/// Check if the byte `c` is a valid GDB hex digit `[0-9a-fA-FxX]`
-#[allow(clippy::match_like_matches_macro)]
+#[inline]
pub fn is_hex(c: u8) -> bool {
+ #[allow(clippy::match_like_matches_macro)] // mirror ascii2byte
match c {
b'0'..=b'9' => true,
b'a'..=b'f' => true,
@@ -239,7 +241,8 @@ pub fn encode_hex_buf(buf: &mut [u8], start_idx: usize) -> Result<&mut [u8], Enc
buf[i] = match nybble {
0x0..=0x9 => b'0' + nybble,
0xa..=0xf => b'A' + (nybble - 0xa),
- _ => unreachable!(), // could be unreachable_unchecked...
+ #[allow(clippy::unreachable)] // will be optimized out
+ _ => unreachable!(), // TODO: use unreachable_unchecked?
};
}
diff --git a/src/protocol/common/qxfer.rs b/src/protocol/common/qxfer.rs
index 1d14aff..af27ae2 100644
--- a/src/protocol/common/qxfer.rs
+++ b/src/protocol/common/qxfer.rs
@@ -18,6 +18,7 @@ pub struct QXferReadBase<'a, T: ParseAnnex<'a>> {
}
impl<'a, T: ParseAnnex<'a>> ParseCommand<'a> for QXferReadBase<'a, T> {
+ #[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let (buf, body_range) = buf.into_raw_buf();
diff --git a/src/protocol/common/thread_id.rs b/src/protocol/common/thread_id.rs
index d3afe5b..36a3ea9 100644
--- a/src/protocol/common/thread_id.rs
+++ b/src/protocol/common/thread_id.rs
@@ -125,3 +125,37 @@ impl TryFrom<ThreadId> for SpecificThreadId {
})
}
}
+
+/// Like [`ThreadId`], without the `Any`, or `All` variants.
+#[derive(Debug, Copy, Clone)]
+pub struct ConcreteThreadId {
+ /// Process ID (may or may not be present).
+ pub pid: Option<NonZeroUsize>,
+ /// Thread ID.
+ pub tid: NonZeroUsize,
+}
+
+impl TryFrom<ThreadId> for ConcreteThreadId {
+ type Error = ();
+
+ fn try_from(thread: ThreadId) -> Result<ConcreteThreadId, ()> {
+ Ok(ConcreteThreadId {
+ pid: match thread.pid {
+ None => None,
+ Some(id_kind) => Some(id_kind.try_into()?),
+ },
+ tid: thread.tid.try_into()?,
+ })
+ }
+}
+
+impl TryFrom<IdKind> for NonZeroUsize {
+ type Error = ();
+
+ fn try_from(value: IdKind) -> Result<NonZeroUsize, ()> {
+ match value {
+ IdKind::WithId(v) => Ok(v),
+ _ => Err(()),
+ }
+ }
+}
diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs
index 1c85778..6dd8166 100644
--- a/src/protocol/mod.rs
+++ b/src/protocol/mod.rs
@@ -11,7 +11,7 @@ mod response_writer;
pub(crate) mod commands;
pub(crate) mod recv_packet;
-pub(crate) use common::thread_id::{IdKind, SpecificIdKind, SpecificThreadId};
+pub(crate) use common::thread_id::{ConcreteThreadId, IdKind, SpecificIdKind, SpecificThreadId};
pub(crate) use packet::Packet;
pub(crate) use response_writer::{Error as ResponseWriterError, ResponseWriter};
diff --git a/src/protocol/response_writer.rs b/src/protocol/response_writer.rs
index 09ea352..3dea22a 100644
--- a/src/protocol/response_writer.rs
+++ b/src/protocol/response_writer.rs
@@ -3,7 +3,8 @@ use alloc::string::String;
#[cfg(feature = "trace-pkt")]
use alloc::vec::Vec;
-use num_traits::PrimInt;
+use num_traits::identities::one;
+use num_traits::{CheckedRem, PrimInt};
use crate::conn::Connection;
use crate::internal::BeBytes;
@@ -156,7 +157,7 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
}
/// Write an entire string over the connection.
- pub fn write_str(&mut self, s: &'static str) -> Result<(), Error<C::Error>> {
+ pub fn write_str(&mut self, s: &str) -> Result<(), Error<C::Error>> {
for b in s.as_bytes().iter() {
self.write(*b)?;
}
@@ -228,6 +229,43 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
Ok(())
}
+ /// Write a number as a decimal string, converting every digit to an ascii
+ /// char.
+ pub fn write_dec<D: PrimInt + CheckedRem>(
+ &mut self,
+ mut digit: D,
+ ) -> Result<(), Error<C::Error>> {
+ if digit.is_zero() {
+ return self.write(b'0');
+ }
+
+ let one: D = one();
+ let ten = (one << 3) + (one << 1);
+ let mut d = digit;
+ let mut pow_10 = one;
+ // Get the number of digits in digit
+ while d >= ten {
+ d = d / ten;
+ pow_10 = pow_10 * ten;
+ }
+
+ // Write every digit from left to right as an ascii char
+ while !pow_10.is_zero() {
+ let mut byte = 0;
+ // We have a single digit here which uses up to 4 bit
+ for i in 0..4 {
+ if !((digit / pow_10) & (one << i)).is_zero() {
+ byte += 1 << i;
+ }
+ }
+ self.write(b'0' + byte)?;
+ digit = digit % pow_10;
+ pow_10 = pow_10 / ten;
+ }
+ Ok(())
+ }
+
+ #[inline]
fn write_specific_id_kind(&mut self, tid: SpecificIdKind) -> Result<(), Error<C::Error>> {
match tid {
SpecificIdKind::All => self.write_str("-1")?,
diff --git a/src/stub/core_impl.rs b/src/stub/core_impl.rs
index 82f3657..31ab884 100644
--- a/src/stub/core_impl.rs
+++ b/src/stub/core_impl.rs
@@ -28,6 +28,7 @@ mod catch_syscalls;
mod exec_file;
mod extended_mode;
mod host_io;
+mod lldb_register_info;
mod memory_map;
mod monitor_cmd;
mod resume;
@@ -35,6 +36,7 @@ mod reverse_exec;
mod section_offsets;
mod single_register_access;
mod target_xml;
+mod thread_extra_info;
mod x_upcase_packet;
pub(crate) use resume::FinishExecStatus;
@@ -207,6 +209,8 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
Command::HostIo(cmd) => self.handle_host_io(res, target, cmd),
Command::ExecFile(cmd) => self.handle_exec_file(res, target, cmd),
Command::Auxv(cmd) => self.handle_auxv(res, target, cmd),
+ Command::ThreadExtraInfo(cmd) => self.handle_thread_extra_info(res, target, cmd),
+ Command::LldbRegisterInfo(cmd) => self.handle_lldb_register_info(res, target, cmd),
// in the worst case, the command could not be parsed...
Command::Unknown(cmd) => {
// HACK: if the user accidentally sends a resume command to a
@@ -214,7 +218,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
// return a dummy stop reason.
if target.base_ops().resume_ops().is_none() && target.use_resume_stub() {
let is_resume_pkt = cmd
- .get(0)
+ .first()
.map(|c| matches!(c, b'c' | b'C' | b's' | b'S'))
.unwrap_or(false);
diff --git a/src/stub/core_impl/base.rs b/src/stub/core_impl/base.rs
index 8809338..a87483b 100644
--- a/src/stub/core_impl/base.rs
+++ b/src/stub/core_impl/base.rs
@@ -159,7 +159,17 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
// 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")?;
+ // Reply with a valid thread-id or GDB issues a warning when more
+ // than one thread is active
+ res.write_str("T05thread:")?;
+ res.write_specific_thread_id(SpecificThreadId {
+ pid: self
+ .features
+ .multiprocess()
+ .then(|| SpecificIdKind::WithId(FAKE_PID)),
+ tid: SpecificIdKind::WithId(self.get_sane_any_tid(target)?),
+ })?;
+ res.write_str(";")?;
HandlerStatus::Handled
}
Base::qAttached(cmd) => {
diff --git a/src/stub/core_impl/breakpoints.rs b/src/stub/core_impl/breakpoints.rs
index aeb176a..f23c4b2 100644
--- a/src/stub/core_impl/breakpoints.rs
+++ b/src/stub/core_impl/breakpoints.rs
@@ -50,6 +50,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
2 => WatchKind::Write,
3 => WatchKind::Read,
4 => WatchKind::ReadWrite,
+ #[allow(clippy::unreachable)] // will be optimized out
_ => unreachable!(),
};
let len = <T::Arch as Arch>::Usize::from_be_bytes(cmd.kind)
diff --git a/src/stub/core_impl/lldb_register_info.rs b/src/stub/core_impl/lldb_register_info.rs
new file mode 100644
index 0000000..2b27f7c
--- /dev/null
+++ b/src/stub/core_impl/lldb_register_info.rs
@@ -0,0 +1,140 @@
+use super::prelude::*;
+use crate::protocol::commands::ext::LldbRegisterInfo;
+
+use crate::arch::lldb::{Encoding, Format, Generic, Register, RegisterInfo as LLDBRegisterInfo};
+use crate::arch::Arch;
+
+impl<T: Target, C: Connection> GdbStubImpl<T, C> {
+ pub(crate) fn handle_lldb_register_info(
+ &mut self,
+ res: &mut ResponseWriter<'_, C>,
+ target: &mut T,
+ command: LldbRegisterInfo,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ if !target.use_lldb_register_info() {
+ return Ok(HandlerStatus::Handled);
+ }
+
+ let handler_status = match command {
+ LldbRegisterInfo::qRegisterInfo(cmd) => {
+ let mut err = Ok(());
+ let cb = &mut |reg: Option<Register<'_>>| {
+ let res = match reg {
+ // TODO: replace this with a try block (once stabilized)
+ Some(reg) => (|| {
+ res.write_str("name:")?;
+ res.write_str(reg.name)?;
+ if let Some(alt_name) = reg.alt_name {
+ res.write_str(";alt-name:")?;
+ res.write_str(alt_name)?;
+ }
+ res.write_str(";bitsize:")?;
+ res.write_dec(reg.bitsize)?;
+ res.write_str(";offset:")?;
+ res.write_dec(reg.offset)?;
+ res.write_str(";encoding:")?;
+ res.write_str(match reg.encoding {
+ Encoding::Uint => "uint",
+ Encoding::Sint => "sint",
+ Encoding::IEEE754 => "ieee754",
+ Encoding::Vector => "vector",
+ })?;
+ res.write_str(";format:")?;
+ res.write_str(match reg.format {
+ Format::Binary => "binary",
+ Format::Decimal => "decimal",
+ Format::Hex => "hex",
+ Format::Float => "float",
+ Format::VectorSInt8 => "vector-sint8",
+ Format::VectorUInt8 => "vector-uint8",
+ Format::VectorSInt16 => "vector-sint16",
+ Format::VectorUInt16 => "vector-uint16",
+ Format::VectorSInt32 => "vector-sint32",
+ Format::VectorUInt32 => "vector-uint32",
+ Format::VectorFloat32 => "vector-float32",
+ Format::VectorUInt128 => "vector-uint128",
+ })?;
+ res.write_str(";set:")?;
+ res.write_str(reg.set)?;
+ if let Some(gcc) = reg.gcc {
+ res.write_str(";gcc:")?;
+ res.write_dec(gcc)?;
+ }
+ if let Some(dwarf) = reg.dwarf {
+ res.write_str(";dwarf:")?;
+ res.write_dec(dwarf)?;
+ }
+ if let Some(generic) = reg.generic {
+ res.write_str(";generic:")?;
+ res.write_str(match generic {
+ Generic::Pc => "pc",
+ Generic::Sp => "sp",
+ Generic::Fp => "fp",
+ Generic::Ra => "ra",
+ Generic::Flags => "flags",
+ Generic::Arg1 => "arg1",
+ Generic::Arg2 => "arg2",
+ Generic::Arg3 => "arg3",
+ Generic::Arg4 => "arg4",
+ Generic::Arg5 => "arg5",
+ Generic::Arg6 => "arg6",
+ Generic::Arg7 => "arg7",
+ Generic::Arg8 => "arg8",
+ })?;
+ }
+ if let Some(c_regs) = reg.container_regs {
+ res.write_str(";container-regs:")?;
+ res.write_num(c_regs[0])?;
+ for reg in c_regs.iter().skip(1) {
+ res.write_str(",")?;
+ res.write_num(*reg)?;
+ }
+ }
+ if let Some(i_regs) = reg.invalidate_regs {
+ res.write_str(";invalidate-regs:")?;
+ res.write_num(i_regs[0])?;
+ for reg in i_regs.iter().skip(1) {
+ res.write_str(",")?;
+ res.write_num(*reg)?;
+ }
+ }
+ res.write_str(";")
+ })(),
+ // In fact, this doesn't has to be E45! It could equally well be any
+ // other error code or even an eOk, eAck or eNack! It turns out that
+ // 0x45 == 69, so presumably the LLDB people were just having some fun
+ // here. For a little discussion on this and LLDB source code pointers,
+ // see https://github.com/daniel5151/gdbstub/pull/103#discussion_r888590197
+ _ => res.write_str("E45"),
+ };
+ if let Err(e) = res {
+ err = Err(e);
+ }
+ };
+ if let Some(ops) = target.support_lldb_register_info_override() {
+ use crate::target::ext::lldb_register_info_override::{
+ Callback, CallbackToken,
+ };
+
+ ops.lldb_register_info(
+ cmd.reg_id,
+ Callback {
+ cb,
+ token: CallbackToken(core::marker::PhantomData),
+ },
+ )
+ .map_err(Error::TargetError)?;
+ err?;
+ } else if let Some(reg) = T::Arch::lldb_register_info(cmd.reg_id) {
+ match reg {
+ LLDBRegisterInfo::Register(reg) => cb(Some(reg)),
+ LLDBRegisterInfo::Done => cb(None),
+ };
+ }
+ HandlerStatus::Handled
+ }
+ };
+
+ Ok(handler_status)
+ }
+}
diff --git a/src/stub/core_impl/single_register_access.rs b/src/stub/core_impl/single_register_access.rs
index fd61c58..15ee90c 100644
--- a/src/stub/core_impl/single_register_access.rs
+++ b/src/stub/core_impl/single_register_access.rs
@@ -33,14 +33,24 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
let len = ops.read_register(id, reg_id, buf).handle_error()?;
- if let Some(size) = reg_size {
- if size.get() != len {
+ if len == 0 {
+ if let Some(size) = reg_size {
+ for _ in 0..size.get() {
+ res.write_str("xx")?;
+ }
+ } else {
return Err(Error::TargetMismatch);
}
} else {
- buf = buf.get_mut(..len).ok_or(Error::PacketBufferOverflow)?;
+ if let Some(size) = reg_size {
+ if size.get() != len {
+ return Err(Error::TargetMismatch);
+ }
+ } else {
+ buf = buf.get_mut(..len).ok_or(Error::PacketBufferOverflow)?;
+ }
+ res.write_hex_buf(buf)?;
}
- res.write_hex_buf(buf)?;
HandlerStatus::Handled
}
SingleRegisterAccess::P(p) => {
diff --git a/src/stub/core_impl/thread_extra_info.rs b/src/stub/core_impl/thread_extra_info.rs
new file mode 100644
index 0000000..2e57137
--- /dev/null
+++ b/src/stub/core_impl/thread_extra_info.rs
@@ -0,0 +1,37 @@
+use super::prelude::*;
+use crate::protocol::commands::ext::ThreadExtraInfo;
+use crate::target::ext::base::BaseOps;
+
+impl<T: Target, C: Connection> GdbStubImpl<T, C> {
+ pub(crate) fn handle_thread_extra_info<'a>(
+ &mut self,
+ res: &mut ResponseWriter<'_, C>,
+ target: &mut T,
+ command: ThreadExtraInfo<'a>,
+ ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
+ let ops = match target.base_ops() {
+ BaseOps::SingleThread(_) => return Ok(HandlerStatus::Handled),
+ BaseOps::MultiThread(ops) => match ops.support_thread_extra_info() {
+ Some(ops) => ops,
+ None => return Ok(HandlerStatus::Handled),
+ },
+ };
+
+ crate::__dead_code_marker!("thread_extra_info", "impl");
+
+ let handler_status = match command {
+ ThreadExtraInfo::qThreadExtraInfo(info) => {
+ let size = ops
+ .thread_extra_info(info.id.tid, info.buf)
+ .map_err(Error::TargetError)?;
+ let data = info.buf.get(..size).ok_or(Error::PacketBufferOverflow)?;
+
+ res.write_hex_buf(data)?;
+
+ HandlerStatus::Handled
+ }
+ };
+
+ Ok(handler_status)
+ }
+}
diff --git a/src/target/ext/base/multithread.rs b/src/target/ext/base/multithread.rs
index 423f57b..693030e 100644
--- a/src/target/ext/base/multithread.rs
+++ b/src/target/ext/base/multithread.rs
@@ -71,6 +71,10 @@ pub trait MultiThreadBase: Target {
///
/// See [the section above](#bare-metal-targets) on implementing
/// thread-related methods on bare-metal (threadless) targets.
+ ///
+ /// _Note_: Implementors should mark this method as `#[inline(always)]`, as
+ /// this will result in better codegen (namely, by sidestepping any of the
+ /// `dyn FnMut` closure machinery).
fn list_active_threads(
&mut self,
thread_is_active: &mut dyn FnMut(Tid),
@@ -82,6 +86,7 @@ pub trait MultiThreadBase: Target {
/// uses `list_active_threads` to do a linear-search through all active
/// threads. On thread-heavy systems, it may be more efficient
/// to override this method with a more direct query.
+ #[allow(clippy::wrong_self_convention)] // requires breaking change to fix
fn is_thread_alive(&mut self, tid: Tid) -> Result<bool, Self::Error> {
let mut found = false;
self.list_active_threads(&mut |active_tid| {
@@ -97,6 +102,14 @@ pub trait MultiThreadBase: Target {
fn support_resume(&mut self) -> Option<MultiThreadResumeOps<'_, Self>> {
None
}
+
+ /// Support for providing thread extra information.
+ #[inline(always)]
+ fn support_thread_extra_info(
+ &mut self,
+ ) -> Option<crate::target::ext::thread_extra_info::ThreadExtraInfoOps<'_, Self>> {
+ None
+ }
}
/// Target extension - support for resuming multi threaded targets.
diff --git a/src/target/ext/base/single_register_access.rs b/src/target/ext/base/single_register_access.rs
index f7a9b16..2887fef 100644
--- a/src/target/ext/base/single_register_access.rs
+++ b/src/target/ext/base/single_register_access.rs
@@ -29,7 +29,8 @@ where
/// Implementations should write the value of the register using target's
/// native byte order in the buffer `buf`.
///
- /// Return the number of bytes written into `buf`.
+ /// Return the number of bytes written into `buf` or `0` if the register is
+ /// valid but unavailable.
///
/// If the requested register could not be accessed, an appropriate
/// non-fatal error should be returned.
diff --git a/src/target/ext/lldb_register_info_override.rs b/src/target/ext/lldb_register_info_override.rs
new file mode 100644
index 0000000..ccd1358
--- /dev/null
+++ b/src/target/ext/lldb_register_info_override.rs
@@ -0,0 +1,52 @@
+//! (LLDB extension) Override the register info specified by `Target::Arch`.
+
+use crate::arch::lldb::Register;
+use crate::target::Target;
+
+/// This type serves as a "proof of callback", ensuring that either
+/// `reg_info.done()` or `reg_info.write()` have been called from within the
+/// `register_info` function. The only way to obtain a valid instance of this
+/// type is by invoking one of those two methods.
+pub struct CallbackToken<'a>(pub(crate) core::marker::PhantomData<&'a *mut ()>);
+
+/// `register_info` callbacks
+pub struct Callback<'a> {
+ pub(crate) cb: &'a mut dyn FnMut(Option<Register<'_>>),
+ pub(crate) token: CallbackToken<'a>,
+}
+
+impl<'a> Callback<'a> {
+ /// The `qRegisterInfo` query shall be concluded.
+ #[inline(always)]
+ pub fn done(self) -> CallbackToken<'a> {
+ (self.cb)(None);
+ self.token
+ }
+
+ /// Write the register info of a single register.
+ #[inline(always)]
+ pub fn write(self, reg: Register<'_>) -> CallbackToken<'a> {
+ (self.cb)(Some(reg));
+ self.token
+ }
+}
+
+/// Target Extension - Override the target register info specified by
+/// `Target::Arch`.
+///
+/// _Note:_ Unless you're working with a particularly dynamic,
+/// runtime-configurable target, it's unlikely that you'll need to implement
+/// this extension.
+pub trait LldbRegisterInfoOverride: Target {
+ /// Invoke `reg_info.write(reg)` where `reg` is a
+ /// [`Register`](crate::arch::lldb::Register) struct to write information of
+ /// a single register or `reg_info.done()` if you want to end the
+ /// `qRegisterInfo` packet exchange.
+ fn lldb_register_info<'a>(
+ &mut self,
+ reg_id: usize,
+ reg_info: Callback<'a>,
+ ) -> Result<CallbackToken<'a>, Self::Error>;
+}
+
+define_ext!(LldbRegisterInfoOverrideOps, LldbRegisterInfoOverride);
diff --git a/src/target/ext/mod.rs b/src/target/ext/mod.rs
index 8198936..b7e2d8f 100644
--- a/src/target/ext/mod.rs
+++ b/src/target/ext/mod.rs
@@ -265,7 +265,9 @@ pub mod catch_syscalls;
pub mod exec_file;
pub mod extended_mode;
pub mod host_io;
+pub mod lldb_register_info_override;
pub mod memory_map;
pub mod monitor_cmd;
pub mod section_offsets;
pub mod target_description_xml_override;
+pub mod thread_extra_info;
diff --git a/src/target/ext/thread_extra_info.rs b/src/target/ext/thread_extra_info.rs
new file mode 100644
index 0000000..9923d4d
--- /dev/null
+++ b/src/target/ext/thread_extra_info.rs
@@ -0,0 +1,22 @@
+//! Provide extra information for a thread
+use crate::common::Tid;
+use crate::target::Target;
+
+/// Target Extension - Provide extra information for a thread
+pub trait ThreadExtraInfo: Target {
+ /// Provide extra information about a thread
+ ///
+ /// GDB queries for extra information for a thread as part of the
+ /// `info threads` command. This function will be called once
+ /// for each active thread.
+ ///
+ /// A string can be copied into `buf` that will then be displayed
+ /// to the client. The string is displayed as `(value)`, such as:
+ ///
+ /// `Thread 1.1 (value)`
+ ///
+ /// Return the number of bytes written into `buf`.
+ fn thread_extra_info(&self, tid: Tid, buf: &mut [u8]) -> Result<usize, Self::Error>;
+}
+
+define_ext!(ThreadExtraInfoOps, ThreadExtraInfo);
diff --git a/src/target/mod.rs b/src/target/mod.rs
index eaed427..a5d912b 100644
--- a/src/target/mod.rs
+++ b/src/target/mod.rs
@@ -597,6 +597,21 @@ pub trait Target {
true
}
+ /// (LLDB extension) Whether to send register information to the client.
+ ///
+ /// Setting this to `false` will override both
+ /// [`Target::support_lldb_register_info_override`] and the associated
+ /// [`Arch::lldb_register_info`].
+ ///
+ /// _Author's note:_ Having the LLDB client autodetect your target's
+ /// register set is really useful, so unless you're _really_ trying to
+ /// squeeze `gdbstub` onto a particularly resource-constrained platform, you
+ /// may as well leave this enabled.
+ #[inline(always)]
+ fn use_lldb_register_info(&self) -> bool {
+ true
+ }
+
/// Support for setting / removing breakpoints.
#[inline(always)]
fn support_breakpoints(&mut self) -> Option<ext::breakpoints::BreakpointsOps<'_, Self>> {
@@ -634,6 +649,15 @@ pub trait Target {
None
}
+ /// (LLDB extension) Support for overriding the register info specified by
+ /// `Target::Arch`.
+ #[inline(always)]
+ fn support_lldb_register_info_override(
+ &mut self,
+ ) -> Option<ext::lldb_register_info_override::LldbRegisterInfoOverrideOps<'_, Self>> {
+ None
+ }
+
/// Support for reading the target's memory map.
#[inline(always)]
fn support_memory_map(&mut self) -> Option<ext::memory_map::MemoryMapOps<'_, Self>> {
@@ -704,6 +728,10 @@ macro_rules! impl_dyn_target {
(**self).use_target_description_xml()
}
+ fn use_lldb_register_info(&self) -> bool {
+ (**self).use_lldb_register_info()
+ }
+
fn support_breakpoints(
&mut self,
) -> Option<ext::breakpoints::BreakpointsOps<'_, Self>> {
@@ -734,6 +762,13 @@ macro_rules! impl_dyn_target {
(**self).support_target_description_xml_override()
}
+ fn support_lldb_register_info_override(
+ &mut self,
+ ) -> Option<ext::lldb_register_info_override::LldbRegisterInfoOverrideOps<'_, Self>>
+ {
+ (**self).support_lldb_register_info_override()
+ }
+
fn support_memory_map(&mut self) -> Option<ext::memory_map::MemoryMapOps<'_, Self>> {
(**self).support_memory_map()
}
diff --git a/src/util/dead_code_marker.rs b/src/util/dead_code_marker.rs
index 2e6c6e7..a852378 100644
--- a/src/util/dead_code_marker.rs
+++ b/src/util/dead_code_marker.rs
@@ -32,6 +32,6 @@ pub fn black_box<T>(dummy: T) -> T {
macro_rules! __dead_code_marker {
($feature:literal, $ctx:literal) => {
#[cfg(feature = "__dead_code_marker")]
- crate::util::dead_code_marker::black_box(concat!("<", $feature, ",", $ctx, ">"));
+ $crate::util::dead_code_marker::black_box(concat!("<", $feature, ",", $ctx, ">"));
};
}