Expand description
The core Target
trait, and all its various protocol extension traits.
The Target
trait describes how to control and modify a system’s
execution state during a GDB debugging session, and serves as the
primary bridge between gdbstub
’s generic protocol implementation and a
target’s project/platform-specific code.
Target
is the most important trait in gdbstub
, and must be implemented
by all consumers of the library!
Implementing Target
gdbstub
uses a technique called “Inlineable Dyn Extension Traits”
(IDETs) to expose an ergonomic and extensible interface to the GDB protocol.
It’s not a very common pattern, and can seem a little “weird” at first
glance, but IDETs are actually very straightforward to use!
TL;DR: Whenever you see a method that returns something that looks like
Option<ProtocolExtOps>
, you can enable that protocol extension by
implementing the ProtocolExt
type on your target, and overriding the
Option<ProtocolExtOps>
method to return Some(self)
.
Please refer to the documentation in the ext
module for more
information on IDETs, including a more in-depth explanation of how they
work, and how Target
leverages them to provide fine grained control over
enabled protocol features.
Associated Types
-
The
Target::Arch
associated type encodes information about the target’s architecture, such as its pointer size, register layout, etc…gdbstub
comes with several built-in architecture definitions, which can be found under thearch
module. -
The
Target::Error
associated type allows implementors to plumb-through their own project-specific fatal error type into theTarget
trait. This is a big-boost to library ergonomics, as it enables consumers ofgdbstub
to preserve target-specific context while usinggdbstub
, without having to do any “error-stashing”.
For example: consider an emulated target where certain devices might return
a MyEmuError::ContractViolation
error whenever they’re accessed
“improperly” (e.g: setting registers in the wrong order). By setting type Error = MyEmuError
, the method signature of the Target
’s resume
method
becomes fn resume(&mut self, ...) -> Result<_, MyEmuError>
, which makes it
possible to preserve the target-specific error while using gdbstub
!
Required Methods (Base Protocol)
A minimal Target
implementation only needs to implement a single method:
Target::base_ops
. This method is
used to select which set of base
debugging operations will be used to control the target. These are
fundamental operations such as reading/writing memory, etc…
All other methods are entirely optional! Check out the
ext
module for a full list of currently supported protocol
extensions.
Optional Protocol Extensions
The GDB protocol is massive, and there are plenty of optional protocol extensions that targets can implement to enhance the base debugging experience.
These protocol extensions range from relatively mundane things such as setting/removing breakpoints or reading/writing individual registers, but also include fancy things such as support for time travel debugging, running shell commands remotely, or even performing file IO on the target!
gdbstub
uses a somewhat unique approach to exposing these many features,
called Inlinable Dyn Extension Traits (IDETs). While this might sound a
bit daunting, the API is actually quite straightforward, and described in
great detail under the ext
module’s documentation.
After getting the base protocol up and running, do take a moment to skim
through and familiarize yourself with the [many different protocol
extensions](ext# modules) that gdbstub
implements. There are some really
nifty ones that you might not even realize you need!
As a suggestion on where to start, consider implementing some of the
breakpoint related extensions under
breakpoints
. While setting/removing
breakpoints is technically an “optional” part of the GDB protocol, I’m sure
you’d be hard pressed to find a debugger that doesn’t support breakpoints.
Note: Missing Protocol Extensions
gdbstub
’s development is guided by the needs of its contributors, with
new features being added on an “as-needed” basis.
If there’s a GDB protocol extensions you’re interested in that hasn’t been
implemented in gdbstub
yet, (e.g: remote filesystem access, tracepoint
support, etc…), consider opening an issue / filing a PR on the
gdbstub
GitHub repo.
Check out the GDB Remote Configuration Docs for a table of GDB commands + their corresponding Remote Serial Protocol packets.
Example: A fairly minimal Single Threaded Target
This example includes a handful of required and optional target features, and shows off the basics of how to work with IDETs.
use gdbstub::common::Signal;
use gdbstub::target::{Target, TargetResult};
use gdbstub::target::ext::base::BaseOps;
use gdbstub::target::ext::base::singlethread::{
SingleThreadResumeOps, SingleThreadSingleStepOps
};
use gdbstub::target::ext::base::singlethread::{
SingleThreadBase, SingleThreadResume, SingleThreadSingleStep
};
use gdbstub::target::ext::breakpoints::{Breakpoints, SwBreakpoint};
use gdbstub::target::ext::breakpoints::{BreakpointsOps, SwBreakpointOps};
struct MyTarget;
impl Target for MyTarget {
type Error = ();
type Arch = gdbstub_arch::arm::Armv4t; // as an example
#[inline(always)]
fn base_ops(&mut self) -> BaseOps<Self::Arch, Self::Error> {
BaseOps::SingleThread(self)
}
// opt-in to support for setting/removing breakpoints
#[inline(always)]
fn support_breakpoints(&mut self) -> Option<BreakpointsOps<Self>> {
Some(self)
}
}
impl SingleThreadBase for MyTarget {
fn read_registers(
&mut self,
regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs,
) -> TargetResult<(), Self> { todo!() }
fn write_registers(
&mut self,
regs: &gdbstub_arch::arm::reg::ArmCoreRegs
) -> TargetResult<(), Self> { todo!() }
fn read_addrs(
&mut self,
start_addr: u32,
data: &mut [u8],
) -> TargetResult<usize, Self> { todo!() }
fn write_addrs(
&mut self,
start_addr: u32,
data: &[u8],
) -> TargetResult<(), Self> { todo!() }
// most targets will want to support at resumption as well...
#[inline(always)]
fn support_resume(&mut self) -> Option<SingleThreadResumeOps<Self>> {
Some(self)
}
}
impl SingleThreadResume for MyTarget {
fn resume(
&mut self,
signal: Option<Signal>,
) -> Result<(), Self::Error> { todo!() }
// ...and if the target supports resumption, it'll likely want to support
// single-step resume as well
#[inline(always)]
fn support_single_step(
&mut self
) -> Option<SingleThreadSingleStepOps<'_, Self>> {
Some(self)
}
}
impl SingleThreadSingleStep for MyTarget {
fn step(
&mut self,
signal: Option<Signal>,
) -> Result<(), Self::Error> { todo!() }
}
impl Breakpoints for MyTarget {
// there are several kinds of breakpoints - this target uses software breakpoints
#[inline(always)]
fn support_sw_breakpoint(&mut self) -> Option<SwBreakpointOps<Self>> {
Some(self)
}
}
impl SwBreakpoint for MyTarget {
fn add_sw_breakpoint(
&mut self,
addr: u32,
kind: gdbstub_arch::arm::ArmBreakpointKind,
) -> TargetResult<bool, Self> { todo!() }
fn remove_sw_breakpoint(
&mut self,
addr: u32,
kind: gdbstub_arch::arm::ArmBreakpointKind,
) -> TargetResult<bool, Self> { todo!() }
}
A note on error handling
As you explore the various protocol extension traits, you’ll often find that
functions don’t return a typical Result<T, Self::Error>
,
and will instead return a TargetResult<T, Self>
.
At first glance this might look a bit strange, since it looks like the Err
variant of TargetResult
is Self
instead of Self::Error
!
Thankfully, there’s a good reason for why that’s the case. In a nutshell,
TargetResult
wraps a typical Result<T, Self::Error>
with a few
additional error types which can be reported back to the GDB client via the
GDB RSP.
For example, if the GDB client tried to read memory from invalid memory,
instead of immediately terminating the entire debugging session, it’s
possible to simply return a Err(TargetError::Errno(14)) // EFAULT
, which
will notify the GDB client that the operation has failed.
See the TargetError
docs for more details.
A note on all the <Self::Arch as Arch>::
syntax
As you explore Target
and its many extension traits, you’ll enounter
many method signatures that use this pretty gnarly bit of Rust type syntax.
If rust-lang/rust#38078
gets fixed, then types like <Self::Arch as Arch>::Foo
could be simplified
to just Self::Arch::Foo
, but until then, the much more explicit
fully qualified syntax
must be used instead.
To improve the readability and maintainability of your own implementation,
it’d be best to swap out the fully qualified syntax with whatever concrete
type is being used. e.g: on a 32-bit target, instead of cluttering up a
method implementation with a parameter passed as (addr: <Self::Arch as Arch>::Usize)
, just write (addr: u32)
directly.
Modules
- Extensions to
Target
which add support for various subsets of the GDB Remote Serial Protocol.
Enums
- The error type for various methods on
Target
and its assorted associated extension traits.
Traits
- Describes the architecture and capabilities of a target which can be debugged by
GdbStub
.
Type Aliases
- A specialized
Result
type forTarget
operations. Supports reporting non-fatal errors back to the GDB client.