Upgrade rust/crates/gdbstub to 0.6.3

Bug: 242056749
Test: make
Change-Id: I205dc8835b7206ecf407e32e38e869a759a4eaa1
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 @@
 
 ### 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 @@
  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 @@
         with:
           profile: minimal
           toolchain: stable
+          override: true
       - name: cargo clippy
         uses: actions-rs/cargo@v1
         with:
@@ -33,6 +34,7 @@
         with:
           command: doc
           args: --workspace --features=std
+
   rustfmt:
     name: rustfmt (nightly)
     runs-on: ubuntu-latest
@@ -42,6 +44,7 @@
         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 @@
     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 @@
         "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 @@
 
 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 @@
 [[example]]
 name = "armv4t_multicore"
 required-features = ["std"]
+
 [dependencies.bitflags]
 version = "1.3"
 
@@ -50,6 +65,7 @@
 
 [dependencies.paste]
 version = "1.0"
+
 [dev-dependencies.armv4t_emu]
 version = "0.1"
 
@@ -62,7 +78,10 @@
 [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 @@
   }
   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 @@
 -   **`#![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 @@
     -   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 @@
 
 ### 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 @@
 -   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 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 @@
     }
 
     #[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 @@
                 );
                 Ok(buf.len())
             }
+            custom_arch::ArmCoreRegIdCustom::Unavailable => Ok(0),
         }
     }
 
@@ -332,7 +342,8 @@
                 Ok(())
             }
             // ignore writes
-            custom_arch::ArmCoreRegIdCustom::Time => Ok(()),
+            custom_arch::ArmCoreRegIdCustom::Unavailable
+            | custom_arch::ArmCoreRegIdCustom::Time => Ok(()),
         }
     }
 }
@@ -369,6 +380,7 @@
 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 @@
         // 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 @@
             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 @@
             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 @@
         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::{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 @@
     ) -> 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 @@
         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::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 @@
         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 @@
     #[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 @@
 
         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 @@
                 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 @@
                         }
                     }
 
+                    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 @@
                             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 @@
     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 @@
 }
 
 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 @@
 }
 
 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 @@
 }
 
 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 @@
 }
 
 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 @@
 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 @@
 }
 
 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 @@
 }
 
 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 @@
 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 @@
 }
 
 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 @@
 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 @@
 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 @@
 }
 
 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 @@
 }
 
 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 @@
 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 @@
 }
 
 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 @@
 }
 
 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 @@
 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 @@
 }
 
 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 @@
         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 @@
 }
 
 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 @@
 }
 
 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 @@
 }
 
 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 @@
 }
 
 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 @@
 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 @@
 }
 
 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 @@
 }
 
 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 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 @@
 }
 
 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 @@
 }
 
 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 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 @@
 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 @@
 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 @@
 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 @@
 }
 
 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 @@
 }
 
 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 @@
 }
 
 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 @@
 }
 
 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 @@
         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 @@
 }
 
 impl<'a> VContKind<'a> {
+    #[inline(always)]
     fn from_bytes(s: &[u8]) -> Option<VContKind<'_>> {
         use self::VContKind::*;
 
@@ -134,6 +138,8 @@
     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 @@
 }
 
 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 @@
             [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 @@
 }
 
 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 @@
             [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 @@
 }
 
 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 @@
                 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 @@
 }
 
 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 @@
                 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 @@
 }
 
 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 @@
 }
 
 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 @@
             [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 @@
 }
 
 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 @@
                     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 @@
 }
 
 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 @@
             [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 @@
 }
 
 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 @@
 }
 
 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 @@
 }
 
 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 @@
 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 @@
 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 @@
     NotAscii,
 }
 
+#[inline]
 fn ascii2byte(c: u8) -> Option<u8> {
     match c {
         b'0'..=b'9' => Some(c - b'0'),
@@ -65,8 +66,9 @@
 }
 
 /// 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 @@
         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 @@
 }
 
 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 @@
         })
     }
 }
+
+/// 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 @@
 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 @@
 #[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 @@
     }
 
     /// 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 @@
         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 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 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 @@
             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 @@
                 // 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 @@
             // 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 @@
                     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 @@
 
                 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 @@
     ///
     /// 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 @@
     /// 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 @@
     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 @@
     /// 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 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 @@
         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 @@
         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 @@
                 (**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 @@
                 (**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 @@
 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, ">"));
     };
 }