An ergonomic and easy-to-integrate implementation of the GDB Remote Serial Protocol in Rust, with full #![no_std]
support.
Why gdbstub
?
gdbstub
tries to abstract as much of the raw GDB protocol details from the user.
gdbstub
comes with built-in register definitions for most common architectures!gdbstub
makes extensive use of Rust's powerful type system + generics to enforce protocol invariants at compile time, minimizing the number of tricky protocol details end users have to worry about.gdbstub
's API is designed to be as unobtrusive as possible, and shouldn't require any large refactoring effort to integrate into an existing project. It doesn't require taking direct ownership of any key data structures, and aims to be a "drop in" solution when you need to add debugging to a project.#![no_std]
Ready & Size Optimized
gdbstub
does not depend on alloc
.gdbstub
is transport-layer agnostic, and uses a basic Connection
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.gdbstub
's binary and RAM footprints.
min-sized-rust
, a baseline gdbstub
implementation weighs in at roughly 10kb of .text
and negligible .rodata
! ** Exact numbers vary by target platform, compiler version, and gdbstub
revision. Data was collected using the included example_no_std
project compiled on x86_64.
gdbstub
is particularly well suited for emulation, making it easy to add powerful, non-intrusive debugging support to an emulated system. Just provide an implementation of the Target
trait for your target platform, and you're ready to start debugging!
gdsbtub
in Production?Yes, as long as you don't mind some API churn until 1.0.0
is released.
gdbstub
has been integrated into many projects since its initial 0.1.0
release, and thusfar, no major bugs have been reported. Reported issues have typically been the result of faulty Target
implementations (e.g: forgetting to adjust the PC after a breakpoint is hit), or were related to certain unimplemented GDB protocol features.
That being said, due to gdbstub
's heavy use of Rust's type system in enforcing GDB protocol invariants at compile time, it's often been the case that implementing new GDB protocol features has required making some breaking Trait/Type changes (e.g: adding the RegId
associated type to Arch
to support addressing individual registers). While these changes are typically quite minor, they are nonetheless breaking, and may require a code-change when moving between versions.
See the Future Plans + Roadmap to 1.0.0
for more information on what features gdbstub
still needs to implement before committing to API stability with version 1.0.0
.
The GDB Remote Serial Protocol is surprisingly complex, supporting advanced features such as remote file I/O, spawning new processes, "rewinding" program execution, and much, much more. Thankfully, most of these features are completely optional, and getting a basic debugging session up-and-running only requires implementing a few basic methods:
Of course, most use-cases will want to support additional debugging features as well. At the moment, gdbstub
implements the following GDB protocol extensions:
monitor
Commands
monitor
commandNote: Which GDB features are implemented are decided on an as-needed basis by gdbstub
's contributors. If there's a missing GDB feature that you'd like gdbstub
to implement, please file an issue / open a PR! Check out the GDB Remote Configuration Docs for a table of GDB commands + their corresponding Remote Serial Protocol packets.
Using a technique called Inlineable Dyn Extension Traits (IDETs), gdbstub
is able to leverage the Rust compiler's powerful optimization passes to ensure any unused features are dead-code-eliminated in release builds without having to rely on compile-time features flags!
For example, if your target doesn't implement a custom GDB monitor
command handler, the resulting binary won't include any code related to parsing / handling the underlying qRcmd
packet!
If you're interested in the low-level technical details of how IDETs work, I've included a brief writeup in the documentation here.
By default, the std
and alloc
features are enabled.
When using gdbstub
in #![no_std]
contexts, make sure to set default-features = false
.
alloc
Connection
for Box<dyn Connection>
.log::trace!
(uses a heap-allocated output buffer).GdbStub
(if none is provided via GdbStubBuilder::with_packet_buffer
).ConsoleOutput
.ExtendedMode::query_if_attached
.std
(implies alloc
)
Connection
for TcpStream
and UnixStream
.std::error::Error
for gdbstub::Error
.TargetError::Io
variant to simplify std::io::Error
handling from Target methods.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 add it to this list!
These examples are built as part of the CI, and are guaranteed to be kept up to date with the latest version of gdbstub
's API.
armv4t
- ./examples/armv4t/
gdbstub
support.armv4t
implements (almost) all available target::ext
features.armv4t_multicore
- ./examples/armv4t_multicore/
armv4t
example.gdbstub
's multithread extensions API, but not much else.example_no_std
- ./example_no_std
gdbstub
can be used in a #![no_std]
project.armv4t/armv4t_multicore
examples, this project does not include a working emulator, and stubs-out all gdbstub
functions.gdbstub
's approximate binary footprint (via the check_size.sh
script)gdbstub
on bare-metal hardwareQuite a bit of work has gone into making gdbstub
optimized for #![no_std]
, which means it should be entirely possible to implement a Target
which uses low-level trap instructions + context switching to debug bare-metal code.
If you happen to stumble across this crate and end up using it to debug some bare-metal code, please let me know! I'd love to link to your project, and/or create a simplified example based off your code!
unsafe
in gdbstub
gdbstub
"core" only has 2 instances of unsafe code:
NonZeroUsize::new_unchecked()
when defining internal constants.str::from_utf8_unchecked()
when working with incoming GDB packets (the underlying &[u8]
buffer is checked with is_ascii()
prior to the call).With the std
feature enabled, there is one additional instance of unsafe
code:
gdbstub
includes an implementation of UnixStream::peek
which uses libc::recv
. This will be removed once rust-lang/rust#73761 is merged and stabilized.1.0.0
Before gdbstub
can comfortably commit to a stable 1.0.0
API, there are several outstanding features that should be implemented and questions that need to be addressed. Due to gdbstub
's heavy reliance on the Rust type system to enforce GDB protocol invariants, it's likely that a certain subset of yet-unimplemented protocol features may require breaking API changes.
Notably, the vast majority of GDB protocol features (e.g: remote filesystem support, tracepoint packets, most query packets, etc...) should not require breaking API changes, and could most likely be implemented using the standard backwards-compatible protocol extension approach.
The following features are most likely to require breaking API changes, and should therefore be implemented prior to 1.0.0
.
Arch
trait
base::multiprocess
API.gdbstub
already implements multiprocess extensions "under-the-hood", and just hard-codes a fake PID.target extended-remote
)gdbstub
running in a "bare-metal" #![no_std]
environment (e.g: debugging a hobby OS via serial).
Additionally, while not strict "blockers" to 1.0.0
, it would be good to explore these features as well:
async/await
interface
check_gdb_interrupt
callback in Target::resume()
could be modeled as a future.