• Stars
    star
    286
  • Rank 144,690 (Top 3 %)
  • Language
    Rust
  • License
    Other
  • Created about 5 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An ergonomic, featureful, and easy-to-integrate implementation of the GDB Remote Serial Protocol in Rust (with no-compromises #![no_std] support)

gdbstub

An ergonomic and easy-to-integrate implementation of the GDB Remote Serial Protocol in Rust, with full #![no_std] support.

gdbstub makes it easy to integrate powerful guest debugging support to your emulator / hypervisor / debugger / embedded project. By implementing just a few basic methods of the gdbstub::Target trait, you can have a rich GDB debugging session up and running in no time!

gdbstub's API makes extensive use of a technique called Inlineable Dyn Extension Traits (IDETs) to expose fine-grained, zero-cost control over enabled GDB protocol features without relying on compile-time features flags. Aside from making it effortless to toggle enabled protocol features, IDETs also ensure that any unimplemented features are guaranteed to be dead-code-eliminated in release builds!

If you're looking for a quick snippet of example code to see what a typical gdbstub integration might look like, check out examples/armv4t/gdb/mod.rs

Why use gdbstub?

  • Excellent Ergonomics
    • Instead of simply exposing the underlying GDB protocol "warts and all", gdbstub tries to abstract as much of the raw GDB protocol details from the user.
      • Instead of having to dig through obscure XML files deep the GDB codebase just to read/write from CPU/architecture registers, gdbstub comes with a community-curated collection of built-in architecture definitions for most popular platforms!
      • Organizes GDB's countless optional protocol extensions into a coherent, understandable, and type-safe hierarchy of traits.
      • Automatically handles client/server protocol feature negotiation, without needing to micro-manage the specific qSupported packet response.
    • gdbstub makes extensive use of Rust's powerful type system + generics to enforce protocol invariants at compile time, minimizing the number of tricky protocol details end users have to worry about.
    • Using a novel technique called Inlineable Dyn Extension Traits (IDETs), gdbstub enables fine-grained control over active protocol extensions without relying on clunky cargo features or the use of unsafe code!
  • Easy to Integrate
    • gdbstub's API is designed to be a "drop in" solution when you want to add debugging support into a project, and shouldn't require any large refactoring effort to integrate into an existing project.
  • #![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 platforms.
    • gdbstub is entirely panic free in most minimal configurations*, resulting in substantially smaller and more robust code.
    • 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!
    • "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 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.
      • When compiled in release mode, using all the tricks outlined in min-sized-rust, a baseline gdbstub implementation can weigh in at less than 10kb of .text + .rodata! *
      • *Exact numbers vary by target platform, compiler version, and gdbstub revision. In mixed-language projects, cross-language LTO may be required (#101). Data was collected using the included example_no_std project compiled on x86_64.

Can I Use gdbstub in Production?

Yes, as long as you don't mind some API churn until 1.0.0 is released.

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 API changes. While these changes are typically quite minor, they are nonetheless semver-breaking, and may require a code-change when moving between versions. Any particularly involved changes will typically be documented in a dedicated transition guide document.

That being said, gdbstub has already been integrated into many real-world projects since its initial 0.1 release, and empirical evidence suggests that it seems to be doing its job quite well! Thusfar, most reported issues have been caused by improperly implemented Target and/or Arch implementations, while the core gdbstub library itself has proven to be reasonably bug-free.

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.

Debugging Features

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:

  • Base GDB Protocol
    • Read/Write memory
    • Read/Write registers
    • Enumerating threads

Yep, that's right! That's all it takes to get gdb connected!

Of course, most use-cases will want to support additional debugging features as well. At the moment, gdbstub implements the following GDB protocol extensions:

  • Automatic target architecture + feature configuration
  • Resume
    • Continue
    • Single Step
    • Range Step
    • Reverse Step/Continue
  • Breakpoints
    • Software Breakpoints
    • Hardware Breakpoints
    • Read/Write/Access Watchpoints (i.e: value breakpoints)
  • Extended Mode
    • Launch new processes
    • Attach to an existing process
    • Kill an existing process
    • Pass env vars + args to spawned processes
    • Change working directory
    • Enable/disable ASLR
  • Read Memory Map (info mem)
  • Read Section/Segment relocation offsets
  • Handle custom monitor Commands
    • Extend the GDB protocol with custom debug commands using GDB's monitor command!
  • Host I/O
    • 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!

For a full list of GDB remote features, check out the GDB Remote Configuration Docs for a table of GDB commands + their corresponding Remote Serial Protocol packets.

Zero-overhead Protocol Extensions

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.

Feature flags

By default, the std and alloc features are enabled.

When using gdbstub in #![no_std] contexts, make sure to set default-features = false.

  • alloc
    • Implement Connection for Box<dyn Connection>.
    • Log outgoing packets via log::trace! (uses a heap-allocated output buffer).
    • Provide built-in implementations for certain protocol features:
      • Use a heap-allocated packet buffer in GdbStub (if none is provided via GdbStubBuilder::with_packet_buffer).
      • (Monitor Command) Use a heap-allocated output buffer in ConsoleOutput.
  • std (implies alloc)
    • Implement Connection for TcpStream and UnixStream.
    • Implement std::error::Error for gdbstub::Error.
    • Add a TargetError::Io variant to simplify std::io::Error handling from Target methods.
  • paranoid_unsafe

Examples

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!

In-tree "Toy" Examples

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/
    • An incredibly simple ARMv4T-based system emulator with gdbstub support.
    • Implements (almost) all available target::ext features. This makes it a great resource when first implementing a new protocol extension!
  • armv4t_multicore - ./examples/armv4t_multicore/
    • A dual-core variation of the armv4t example.
    • Implements the core of gdbstub's multithread extensions API, but not much else.
  • example_no_std - ./example_no_std
    • An extremely minimal example which shows off how gdbstub can be used in a #![no_std] project.
    • Unlike the armv4t/armv4t_multicore examples, this project does not include a working emulator, and simply stubs all gdbstub functions.
    • Doubles as a test-bed for tracking gdbstub's approximate binary footprint (via the check_size.sh script), as well as validating certain dead-code-elimination optimizations.

unsafe in gdbstub

gdbstub limits its use of unsafe to a bare minimum, with all uses of unsafe required to have a corresponding // SAFETY comment as justification.

For those paranoid about trusting third-party unsafe code, gdbstub comes with an opt-in paranoid_unsafe feature, which enables #![forbid(unsafe_code)] on the entire gdbstub crate, swapping out all instances of unsafe code with equivalent (albeit less-performant) alternatives.

The following list exhaustively documents all uses of unsafe in gdbstub:

  • With default features

    • Don't emit provably unreachable panics
      • src/protocol/packet.rs: Method in PacketBuf that use index using stored sub-Range<usize> into the buffer
      • src/protocol/common/hex.rs: decode_hex_buf
    • Don't emit large match-arm LUT
      • src/common.rs: Checked transmute of u8 to Signal
  • When the std feature is enabled:

    • src/connection/impls/unixstream.rs: An implementation of UnixStream::peek which uses libc::recv. Will be removed once rust-lang/rust#76923 stabilizes this feature in the stdlib.

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 mean checking generated asm output.

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!

As such, gdbstub promises to introduce zero additional panics into an existing project, subject to the following conditions:

  1. The binary is compiled in release mode
    • *subject to the specific rustc version being used (codegen and optimization vary between versions)
    • *different hardware architectures may be subject to different compiler optimizations
      • i.e: at this time, only x86 is actively tested to be panic-free
  2. gdbstub's paranoid_unsafe cargo feature is disabled
    • LLVM is unable to omit certain panic checks without requiring a bit of unsafe code
    • See the 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 have determined that gdbstub is at fault for introducing a panicking code path, 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.

Not that this is not an exhaustive list, and is subject to change.

  • Allow fine-grained control over target features via the Arch trait (#12)
  • Implement GDB's various high-level operating modes:
    • Single/Multi Thread debugging
    • Multiprocess Debugging (#124
      • Requires adding a new target::ext::base::multiprocess API.
      • Note: gdbstub already implements multiprocess extensions "under-the-hood", and just hard-codes a fake PID, so this is mostly a matter of "putting in the work".
    • Extended Mode (target extended-remote)
    • Non-Stop Mode
  • Have a working example of gdbstub running in a "bare-metal" #![no_std] environment.

Additionally, while not strict blockers to 1.0.0, it would be good to explore these features as well:

  • Should gdbstub commit to a MSRV?
  • Remove lingering instances of RawRegId from gdbstub_arch (#29)
  • Exposing async/await interfaces (particularly wrt. handling GDB client interrupts) (#36)
  • How/if to support LLDB extensions (#99)
  • Supporting multi-arch debugging via a single target
    • e.g: debugging x86 and ARM processes on macOS
  • Proper handling of "nack" packets (for spotty connections) (#137)

License

gdbstub is free and open source! All code in this repository is dual-licensed under either:

at your option. This means you can select the license you prefer! This dual-licensing approach is the de-facto standard in the Rust ecosystem and there are very good reasons to include both.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

More Repositories

1

ANESE

Another NES Emulator - written for fun & learning - first implementation of wideNES
C++
396
star
2

clicky

A clickwheel iPod emulator (WIP)
Rust
159
star
3

surface-dial-linux

A Linux userspace controller for the Microsoft Surface Dial. Requires Linux Kernel 4.19 or higher.
Rust
71
star
4

ts7200

A high-level emulator for the TS-7200 Single Board Computer, as used in CS 452 - Real-Time Programming at the University of Waterloo
Rust
30
star
5

inlinable-dyn-extension-traits

An exploration into the various ways optional trait methods can be implemented in Rust.
Rust
29
star
6

spotify-car-thing-bt

code to connect + communicate with a Spotify Car Thing
Rust
27
star
7

armv4t_emu

An emulator for the ARMv4t instruction set written in Rust
Rust
25
star
8

libc_alloc

A simple global allocator for Rust which hooks into `libc`. Useful in `no_std` contexts.
Rust
21
star
9

compressed-emoji-shortcodes

A Quest to Find a Highly Compressed Emoji :shortcode: Lookup Function
Rust
16
star
10

analog_literals

Multi-Dimensional Analog Literals in Rust
Rust
15
star
11

osxhidtouch

User-space HID multitouch touchscreen driver for Mac OS X (Adapted for XPS 15 9560 from kyewei/osxhidtouch)
Objective-C
14
star
12

uwmips

A simulator for the UW MIPS instruction set, with a time-traveling debugger
Rust
10
star
13

AC8E

A Chip8 emulator written in Rust for fun and learning
Rust
9
star
14

mips241

A emulator and disassembler for the version of MIPS used in CS 241
C++
8
star
15

fusion-kbd-controller-rs

Control the RGB Fusion Keyboard of the Gigabyte AERO 15X
Rust
7
star
16

dicesiege

DiceWars for the JavaScript century
4
star
17

1212

1212! is a game inspired by 1010! where you place pieces, make lines, score points, and last as long as you can.
JavaScript
3
star
18

gdb-optional-step-bug

GDB client doesn't respect optional single-step support on certain target architectures
Rust
3
star
19

antigravity

https://xkcd.com/353/
Rust
3
star
20

vrai-tracer

Following along with "Ray Tracing in One Weekend" in Rust.
Rust
2
star
21

bconsole

A better console for node.js
JavaScript
2
star
22

wide-libretro

A "shim" around existing libretro cores that adds faux-widescreen support to retro games
Rust
2
star
23

embcrusted

A z-machine interpreter for running Infocom-era text adventure games on embedded hardware.
Rust
2
star
24

blog

If I write something interesting, it will probably be published over here!
HTML
1
star
25

rl-inventory-prices

Calculate trading value for each item in a player's Rocket League inventory
Rust
1
star
26

clap-shell-completions

A hand-rolled `clap_complete`, with support for dynamic completions driven by callbacks
Rust
1
star