Module gdbstub::target

source ·
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 the arch module.

  • The Target::Error associated type allows implementors to plumb-through their own project-specific fatal error type into the Target trait. This is a big-boost to library ergonomics, as it enables consumers of gdbstub to preserve target-specific context while using gdbstub, 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 for Target operations. Supports reporting non-fatal errors back to the GDB client.