diff options
author | Pierre-Clément Tosi <ptosi@google.com> | 2022-08-26 10:45:40 +0100 |
---|---|---|
committer | Pierre-Clément Tosi <ptosi@google.com> | 2022-08-31 10:36:32 +0100 |
commit | f15aee5b6c91fa7d85b3b77c2dc068162f1c3e94 (patch) | |
tree | 8bd104e2eb03d2ffae5008fb6bb8bbaf51717c48 | |
parent | 3536edfc369ab4da721b92021429b6b58db03bd3 (diff) | |
download | gdbstub-main-16k.tar.gz |
Upgrade rust/crates/gdbstub to 0.6.3main-16k
Bug: 242056749
Test: make
Change-Id: I205dc8835b7206ecf407e32e38e869a759a4eaa1
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 @@ -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 @@ -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" @@ -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 } } @@ -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, + } +} @@ -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, ">")); }; } |