Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support GDB Agent Expressions [for conditional breakpoints, breakpoint commands, tracepoints, etc...] #40

Open
daniel5151 opened this issue Feb 25, 2021 · 2 comments
Labels
design-required Getting this right will require some thought new-protocol-extension New feature or request

Comments

@daniel5151
Copy link
Owner

See https://sourceware.org/gdb/current/onlinedocs/gdb/Agent-Expressions.html#Agent-Expressions

In some applications, it is not feasible for the debugger to interrupt the program’s execution long enough for the developer to learn anything helpful about its behavior. If the program’s correctness depends on its real-time behavior, delays introduced by a debugger might cause the program to fail, even when the code itself is correct. It is useful to be able to observe the program’s behavior without interrupting it.

When GDB is debugging a remote target, the GDB agent code running on the target computes the values of the expressions itself. To avoid having a full symbolic expression evaluator on the agent, GDB translates expressions in the source language into a simpler bytecode language, and then sends the bytecode to the agent; the agent then executes the bytecode, and records the values for GDB to retrieve later.

The bytecode language is simple; there are forty-odd opcodes, the bulk of which are the usual vocabulary of C operands (addition, subtraction, shifts, and so on) and various sizes of literals and memory reference operations. The bytecode interpreter operates strictly on machine-level values — various sizes of integers and floating point numbers — and requires no information about types or symbols; thus, the interpreter’s internal data structures are simple, and each bytecode requires only a few native machine instructions to implement it. The interpreter is small, and strict limits on the memory and time required to evaluate an expression are easy to determine, making it suitable for use by the debugging agent in real-time applications.

One notable application of Agent Expressions not included in this overview is that breakpoint packets support specifying conditions and/or commands as bytecode expressions to be executed directly on the device. This enables significantly faster conditional breakpoints, as the target does not have to stop execution, communicate with the remote GDB client, and wait for the client to execute the condition.


There are several considerations to keep in mind while working on this feature and designing its API:

  • The existing breakpoint packet parser must be augmented with zero cost support for parsing Agent Expressions when the appropriate feature is enabled.
    • This is weird, as most packets are not parsed differently based on which protocol features are being used.
  • The Agent Expression API must be written such that users should have control over how and where incoming bytecode expressions (which are of variable-length) are stored. gdbstub should parse packets with bytecode expressions and extract the bytecode expression slices, but should not decide how they are stored / retrieved.
    • This could be done by having the Agent API rely on a generic BytecodeStorage trait, which users provide the implementation of.
    • gdbstub must include at least one "batteries included" implementation to make it as easy as possible to get up and running with the feature. e.g: if the std feature is enabled, provide a HashMap-based implementation.
  • gdbstub should not lock users into a single bytecode interpreter. Users should have the flexibility to decide how/when bytecode expressions are be executed.
    • This could be done by having the Agent API rely on a generic BytecodeExecutor trait, which users provide the implementation of.
    • gdbstub should offer a "batteries included" interpreter for those that just want to get up and running quickly (but this wouldn't be a feature blocker)
    • e.g: users looking to get the absolute lowest overhead when running agent expressions should be able to use their own implementations. e.g: they might want to JIT incoming bytecode expressions into target-specific assembly which can then be executed as part of their breakpoint handler routines.
  • And lastly, as always, the API should be difficult to misuse, and zero-copy.
    • zero-copy in the sense that users should not be forced to copy bytecode expressions into the bytecode executor.

Some final things of note:

  • There's already an existing GDB Agent Bytecode interpreter written in Rust! @khuey's https://github.com/khuey/gdb-agent
    • This is excellent, as that means there's already an easy-to-use, off-the-shelf BytecodeExecutor implementation that can be used to validate the feature is working as intended!
    • It would still be nice if gdbstub included an "in-house" Agent Bytecode executor (with no_std support), but that's not a feature blocker
  • This feature would tie-into any future Tracepoint Packet support, so it would be a good idea to at least skim through those docs and make sure the API isn't being designed in a way that won't "play nice" if tracepoint support is added.
@daniel5151 daniel5151 added the new-protocol-extension New feature or request label Feb 25, 2021
@daniel5151
Copy link
Owner Author

daniel5151 commented Feb 25, 2021

I have spent quite a bit of time toying around with this, and ended up hacking together an implementation that works, but misses the mark in terms of API ergonomics. The code is available on the feature-draft/agent branch.

The biggest issue with that implementation is that the API forces users to do some "ownership gymnastics" in order to work correctly. In a nutshell, I structured the Agent API the same way I would any other protocol extension: as a direct IDET of the Target trait. This had the unfortunate side-effect that it tied that BytecodeStorage and BytecodeExecutor lifetimes directly to the target, which is really bad, since the BytecodeExecutor needs to mutably modify the target while at the same time immutably borrowing the bytecode expression from the BytecodeStorage - which is part of the target itself!

I spent a bit more time poking away at the implementation, and while I feel like it is salvageable, I don't think I have the mental energy to keep iterating on it. As such, I've extracted the relevant code and put it into the aforementioned feature-draft/agent branch, with the expectation that I will salvage bits-and-pieces of the implementation when I eventually get around to re-visiting this feature.

That said, it does in-fact work, and if you run the armv4t example on that branch, you should be able to set conditional breakpoints that get executed on the target! It also demonstrates how to use khuey/gdb-agent as a bytecode executor.


If you stumble across this issue, and you're interested in seeing this get implemented, feel free to leave a comment and/or help out with an implementation!

@khuey
Copy link

khuey commented Feb 25, 2021

FWIW I think the heavy part of making gdb-agent work in no_std is dealing with the error handling. Beyond that I doubt it would be too bad.

@daniel5151 daniel5151 added the design-required Getting this right will require some thought label Jul 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design-required Getting this right will require some thought new-protocol-extension New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants