[][src]Trait gdbstub::Target

pub trait Target {
    type Arch: Arch;
    type Error;
    fn resume(
        &mut self,
        actions: Actions<'_>,
        check_gdb_interrupt: &mut dyn FnMut() -> bool
    ) -> Result<(Tid, StopReason<<Self::Arch as Arch>::Usize>), Self::Error>;
fn read_registers(
        &mut self,
        regs: &mut <Self::Arch as Arch>::Registers
    ) -> Result<(), Self::Error>;
fn write_registers(
        &mut self,
        regs: &<Self::Arch as Arch>::Registers
    ) -> Result<(), Self::Error>;
fn read_addrs(
        &mut self,
        start_addr: <Self::Arch as Arch>::Usize,
        data: &mut [u8]
    ) -> Result<bool, Self::Error>;
fn write_addrs(
        &mut self,
        start_addr: <Self::Arch as Arch>::Usize,
        data: &[u8]
    ) -> Result<bool, Self::Error>;
fn update_sw_breakpoint(
        &mut self,
        addr: <Self::Arch as Arch>::Usize,
        op: BreakOp
    ) -> Result<bool, Self::Error>; fn read_register(
        &mut self,
        reg_id: <<Self::Arch as Arch>::Registers as Registers>::RegId,
        dst: &mut [u8]
    ) -> OptResult<(), Self::Error> { ... }
fn write_register(
        &mut self,
        reg_id: <<Self::Arch as Arch>::Registers as Registers>::RegId,
        val: &[u8]
    ) -> OptResult<(), Self::Error> { ... }
fn update_hw_breakpoint(
        &mut self,
        addr: <Self::Arch as Arch>::Usize,
        op: BreakOp
    ) -> OptResult<bool, Self::Error> { ... }
fn update_hw_watchpoint(
        &mut self,
        addr: <Self::Arch as Arch>::Usize,
        op: BreakOp,
        kind: WatchKind
    ) -> OptResult<bool, Self::Error> { ... }
fn handle_monitor_cmd(
        &mut self,
        cmd: &[u8],
        out: ConsoleOutput<'_>
    ) -> OptResult<(), Self::Error> { ... }
fn list_active_threads(
        &mut self,
        thread_is_active: &mut dyn FnMut(Tid)
    ) -> Result<(), Self::Error> { ... }
fn set_current_thread(&mut self, tid: Tid) -> OptResult<(), Self::Error> { ... }
fn is_thread_alive(&mut self, tid: Tid) -> OptResult<bool, Self::Error> { ... } }

A collection of methods and metadata a GdbStub can use to control and debug a system.

There are several provided methods that can optionally be implemented to enable additional advanced GDB debugging functionality.

Handling Threads on Bare-Metal Hardware

On bare-metal targets (such as microcontrollers or emulators), it's common to treat individual CPU cores as a separate "threads". e.g: in a dual-core system, [CPU0, CPU1] might be mapped to [TID1, TID2] (note that TIDs cannot be zero).

What's with the <Self::Arch as Arch>:: syntax?

Yeah, sorry about that!

If rust-lang/rust#38078 every gets fixed, <Self::Arch as Arch>::Foo will be simplified to just Self::Arch::Foo.

Until then, when implementing Target, it's recommended to use the concrete type directly. e.g: on a 32-bit platform, instead of writing <Self::Arch as Arch>::Usize, use u32 directly.

Associated Types

type Arch: Arch

The target's architecture.

type Error

A target-specific fatal error.

Loading content...

Required methods

fn resume(
    &mut self,
    actions: Actions<'_>,
    check_gdb_interrupt: &mut dyn FnMut() -> bool
) -> Result<(Tid, StopReason<<Self::Arch as Arch>::Usize>), Self::Error>

Resume execution on the target.

actions specifies how various threads should be resumed (i.e: single-step vs. resume). It is guaranteed to contain at least one action.

The check_gdb_interrupt callback can be invoked to check if GDB sent an Interrupt packet (i.e: the user pressed Ctrl-C). It's recommended to invoke this callback every-so-often while the system is running (e.g: every X cycles/milliseconds). Periodically checking for incoming interrupt packets is not required, but it is recommended.

Implementation requirements

These requirements cannot be satisfied by gdbstub internally, and must be handled on a per-target basis.

Adjusting PC after a breakpoint is hit

The GDB remote serial protocol documentation notes the following:

On some architectures, such as x86, at the architecture level, when a breakpoint instruction executes the program counter points at the breakpoint address plus an offset. On such targets, the stub is responsible for adjusting the PC to point back at the breakpoint address.

Omitting PC adjustment may result in unexpected execution flow and/or breakpoints not appearing to work correctly.

Kinds of Targets

Single-Threaded Targets

For single-threaded Target's (i.e: those that have not implemented any (optional|multithreading) features), it's safe to ignore the TidSelector component of the actions iterator entirely. Moreover, it's safe to assume that there will only ever be a single action returned by the actions iterator. As such, the following snippet should never panic:

let (_, action) = actions.next().unwrap();

Lastly, when returning a (Tid, StopReason) pair, the gdbstub::SINGLE_THREAD_TID constant should be used for the Tid field.

Multi-Threaded Targets

If a Target ever lists more than one thread as active in list_active_threads, gdbstub switches to multithreaded mode. In this mode, the actions iterator may return more than one action.

At the moment, gdbstub only supports GDB's "All-Stop" mode, whereby all threads should be stopped when returning from resume (not just the thread responsible for the StopReason).

Bare-Metal Targets

See the section above on how to use "threads" on bare-metal (threadless) targets to debug individual CPU cores.

fn read_registers(
    &mut self,
    regs: &mut <Self::Arch as Arch>::Registers
) -> Result<(), Self::Error>

Read the target's registers.

On multi-threaded systems, this method must respect the currently selected thread (set via the set_current_thread method).

fn write_registers(
    &mut self,
    regs: &<Self::Arch as Arch>::Registers
) -> Result<(), Self::Error>

Write the target's registers.

On multi-threaded systems, this method must respect the currently selected thread (set via the set_current_thread method).

fn read_addrs(
    &mut self,
    start_addr: <Self::Arch as Arch>::Usize,
    data: &mut [u8]
) -> Result<bool, Self::Error>

Read bytes from the specified address range.

Handling non-fatal invalid memory reads

If the requested address range could not be accessed (e.g: due to MMU protection, unhanded page fault, etc...), return Ok(false) to signal that the requested memory could not be read.

As a reminder, Err(Self::Error) should only be returned if a memory read results in a fatal target error.

fn write_addrs(
    &mut self,
    start_addr: <Self::Arch as Arch>::Usize,
    data: &[u8]
) -> Result<bool, Self::Error>

Write bytes to the specified address range.

Handling non-fatal invalid memory writes

If the requested address range could not be accessed (e.g: due to MMU protection, unhanded page fault, etc...), return Ok(false) to signal that the requested memory could not be written to.

As a reminder, Err(Self::Error) should only be returned if a memory write results in a fatal target error.

fn update_sw_breakpoint(
    &mut self,
    addr: <Self::Arch as Arch>::Usize,
    op: BreakOp
) -> Result<bool, Self::Error>

Set/remove a software breakpoint. Return Ok(false) if the operation could not be completed.

See this stackoverflow discussion about the differences between hardware and software breakpoints.

Author's recommendation: If you're implementing Target for an emulator using an interpreted CPU (as opposed to a JIT), the simplest way to implement "software" breakpoints is to check the PC value after each CPU cycle.

Loading content...

Provided methods

fn read_register(
    &mut self,
    reg_id: <<Self::Arch as Arch>::Registers as Registers>::RegId,
    dst: &mut [u8]
) -> OptResult<(), Self::Error>

Read a single register on the target.

Implementations should write the value of the register using target's native byte order in the buffer dst.

On multi-threaded systems, this method must respect the currently selected thread (set via the set_current_thread method).

fn write_register(
    &mut self,
    reg_id: <<Self::Arch as Arch>::Registers as Registers>::RegId,
    val: &[u8]
) -> OptResult<(), Self::Error>

Write a single register on the target.

The val buffer contains the new value of the register in the target's native byte order.

On multi-threaded systems, this method must respect the currently selected thread (set via the set_current_thread method).

fn update_hw_breakpoint(
    &mut self,
    addr: <Self::Arch as Arch>::Usize,
    op: BreakOp
) -> OptResult<bool, Self::Error>

(optional) Set/remove a hardware breakpoint. Return Ok(false) if the operation could not be completed.

See this stackoverflow discussion about the differences between hardware and software breakpoints.

Author's recommendation: If you're implementing Target for an emulator using an interpreted CPU (as opposed to a JIT), there shouldn't be any reason to implement this method (as software breakpoints are likely to be just-as-fast).

fn update_hw_watchpoint(
    &mut self,
    addr: <Self::Arch as Arch>::Usize,
    op: BreakOp,
    kind: WatchKind
) -> OptResult<bool, Self::Error>

(optional) Set/remove a hardware watchpoint. Return Ok(false) if the operation could not be completed.

See the GDB documentation regarding watchpoints for how they're supposed to work.

NOTE: If this method isn't implemented, GDB will default to using software watchpoints, which tend to be excruciatingly slow (as they are implemented by single-stepping the system, and reading the watched memory location after each step).

fn handle_monitor_cmd(
    &mut self,
    cmd: &[u8],
    out: ConsoleOutput<'_>
) -> OptResult<(), Self::Error>

(optional) Handle custom commands sent using the monitor command.

The GDB remote serial protocol includes a built-in mechanism to send arbitrary commands to the remote stub: the monitor command. For example, running monitor dbg from the GDB client will invoke handle_monitor_cmd with cmd = b"dbg".

Commands are not guaranteed to be valid UTF-8, hence the use of &[u8] as opposed to &str.

Intermediate console output can be written back to the GDB client using the provided ConsoleOutput object + the gdbstub::output! macro.

Note: The maximum length of incoming commands is limited by the size of the packet buffer provided to the GdbStub. Specifically, commands can only be up to (buf.len() - 10) / 2 bytes.

fn list_active_threads(
    &mut self,
    thread_is_active: &mut dyn FnMut(Tid)
) -> Result<(), Self::Error>

(optional|multithreading) List all currently active threads.

See the section above on implementing thread-related methods on bare-metal (threadless) targets.

fn set_current_thread(&mut self, tid: Tid) -> OptResult<(), Self::Error>

(optional|multithreading) Select a specific thread to perform subsequent operations on (e.g: read/write registers, access memory, etc...)

This method must be implemented if list_active_threads ever returns more than one thread!

fn is_thread_alive(&mut self, tid: Tid) -> OptResult<bool, Self::Error>

(optional|multithreading) Check if the specified thread is alive.

As a convenience, this method provides a default implementation which 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.

Loading content...

Trait Implementations

impl<A, E, '_> Target for &'_ mut dyn Target<Arch = A, Error = E> where
    A: Arch
[src]

type Arch = A

The target's architecture.

type Error = E

A target-specific fatal error.

Implementations on Foreign Types

impl<A, E> Target for Box<dyn Target<Arch = A, Error = E>> where
    A: Arch
[src]

type Arch = A

type Error = E

Loading content...

Implementors

impl<A, E, '_> Target for &'_ mut dyn Target<Arch = A, Error = E> where
    A: Arch
[src]

type Arch = A

type Error = E

Loading content...