Initial docs for newtron

snare 2014-08-02 06:28:14 +10:00
parent 0b3f177165
commit bc41f844db
8 changed files with 577 additions and 1 deletions

3
Building-API-plugins.md Normal file

@ -0,0 +1,3 @@
# Building API plugins
To be completed.

@ -0,0 +1,3 @@
# Building debugger adaptor plugins
To be completed.

3
Building-view-plugins.md Normal file

@ -0,0 +1,3 @@
# Building view plugins
To be completed.

3
Building-web-plugins.md Normal file

@ -0,0 +1,3 @@
# Building web plugins
To be completed.

15
Extending.md Normal file

@ -0,0 +1,15 @@
# Extending Voltron
[Plugin architecture](Plugin-architecture)
[Building debugger adaptor plugins](Building-debugger-adaptor-plugins)
[Building API plugins](Building-API-plugins)
[Building view plugins](Building-view-plugins)
[Building web plugins](Building-web-plugins)
[Building custom clients](Building-custom-clients)
[JSON API reference](JSON-API-reference)

@ -1 +1,6 @@
Welcome to the voltron wiki!
# Voltron
Voltron is a debugger thingy.
The info on this wiki relates to the new version on the `lionforce` branch.

453
JSON-API-reference.md Normal file

@ -0,0 +1,453 @@
# JSON API
Voltron clients communicate with the back end by means of a JSON API which is exposed over a UNIX domain socket (`~/.voltron/voltron.sock`), and optionally a TCP socket and HTTP server. All of these methods expose the same API.
## API messages
All API messages are valid JSON objects containing at least a `type` field, which specifies whether the message is a request or response message.
Messages may contain an optional `data` field, which is a hash containing message-specific data.
{
"type": "<message_type>",
...
"data": {
"<message_specific_field>": "some data"
}
}
### Requests
A request contains at least a `type` field (which is always "request") and a `request` field that defines the type of request.
For example, a `state` request to get the current state of a debugger target:
{
"type": "request",
"request": "state"
}
Requests may also contain a `data` section which is used to pass request-specific parameters to the back end. For example, many request types accept a `target_id` field in the `data` section to specify a debugger target ("inferior" in GDB parlance).
For example, a `registers` request with a `target_id` field:
{
"type": "request",
"request": "registers",
"data": {
"target_id":0
}
}
### Responses
Responses always have a `data` section, which either contains the result of the request, or an error code and message.
For example, the response to a `targets` request with an array containing the info for one target:
{
"type": "response",
"status": "success",
"data": {
"targets": [
{
"id": 0,
"file": "/bin/ls",
"arch": "x86_64"
}
]
}
}
An error response:
{
"type": "response",
"status": "error",
"data": {
"code": 0x1000,
"message": "An error occurred"
}
}
## Request types
The following request types are defined in the core API:
1. [Version](#version)
2. [Wait](#wait)
3. [State](#state)
4. [Targets](#targets)
5. [Registers](#registers)
6. [Memory](#memory)
7. [Stack](#stack)
8. [Disassemble](#disassemble)
9. [Command](#command)
10. [Backtrace](#backtrace)
11. [Breakpoints](#breakpoints)
12. [Connect FD](#connect_fd)
All requests (except `wait`) can return a busy error if the debugger is busy and unable to respond.
## <a name="version"/>Version
Get the API and debugger host version.
### Request
{
"type": "request",
"request": "version"
}
### Response
{
"type": "response",
"status": "success",
"data": {
"api_version": 1.0,
"host_version": "lldb-something"
}
}
## <a name="wait"/>Wait
Block until one of the requested state changes occurs or the timeout is up, in which case it will return an error.
### Request
{
"type": "request",
"request": "wait",
"data" {
"state_changes": [
"stopped"
],
"timeout": 10
}
}
`state_changes` - an array of state changes to wait for. Currently only "stopped" is supported. Optional. Default is `["stopped"]`.
`timeout` - timeout value after which the server will return an error response. Optional. Default is block indefinitely.
### Success response
{
"type": "response",
"status": "success",
"data": {
"state": "stopped"
}
}
### Error response if the timeout is reached
{
"type": "response",
"status": "error",
"data": {
"message": "The request timed out",
"code": 4100
},
}
## <a name="state"/>State
Get a the state of a debugger target.
### Request
{
"type": "request",
"request": "state",
"data": {
"target_id": 0
}
}
### Success response
{
"type": "response",
"status": "success",
"data": {
"state": "stopped"
}
}
## <a name="targets"/>Targets
Get a list of the debugger's targets.
### Request
{
"type": "request",
"request": "targets"
}
### Success response
{
"type": "response",
"status": "success",
"data": {
"targets": [
{
"id": 0,
"file": "/bin/ls",
"arch": "x86_64"
}
]
}
}
## <a name="registers"/>Registers
Get the values of the CPU registers for a given target and thread.
### Request
{
"type": "request",
"request": "registers",
"data": {
"target_id": 0,
"thread_id": 1234
}
}
`target_id` - the target ID from which to read register values. Optional. Default is the first target.
`thread_id` - the thread ID from which to read register values. Optional. Default is the current thread.
### Response
{
"type": "response",
"status": "success",
"data": {
"registers": {
"rip": 0xffffff8012341234,
"rax": 0x4141414141414141,
...
}
}
}
## <a name="memory"/>Memory
Read memory from the inferior.
### Request
{
"type": "request",
"request": "read_memory",
"data": {
"target_id": 0,
"address": 0xffffff8012341234,
"bytes": 1024,
}
}
`target_id` - the target ID from which to read memory. Optional. Default is the first target.
`bytes` - the number of bytes to read. Required.
`address` - the address at which to start reading.
`register` - the register which contains the address at which to start reading.
Either `address` or `register` must be specified.
### Response
{
"type": "response",
"status": "success",
"data": {
"memory": "\x41\x41\x41\x41...",
"bytes": 1024
}
}
### Error response with partial read:
XXX: This isn't implemented yet, probably do it
{
"type": "response",
"status": "error",
"data": {
"code": 666,
"message": "Read failed at 0xffffff8012341266, only 50 bytes read",
"bytes": 50,
"memory": "\x41\x41\x41\x41..."
}
}
## <a name="stack"/>Stack
Read memory starting from the value contained in the inferior's stack pointer register.
### Request
{
"type": "request",
"request": "stack",
"data": {
"target_id": 0,
"bytes": 512
}
}
`target_id` - the target ID from which to read stack memory. Optional. Default is the first target.
`bytes` - the number of bytes to read. Required.
### Response
See [Memory](#memory).
## <a name="disassemble"/>Disassemble
Disassemble instructions from the inferior's memory.
### Request
{
"type": "request",
"request": "disassemble",
"data": {
"target_id": 0,
"address": 0xffffff8012341234,
"count": 16
}
}
`target_id` - the target ID. Optional.
`address` - the address at which to start disassembling. Optional.
`count` - the number of instructions to disassemble. Required.
### Response
{
"type": "response",
"status": "success",
"data": {
"output": "mov blah blah",
"bytes": 1024
}
}
## <a name="command"/>Command
Execute a command in the debugger host and return the output.
### Request
{
"type": "request",
"request": "execute_command",
"data": {
"command": "x/32x $rsp"
}
}
`command` - the command to execute.
### Response
{
"type": "response",
"status": "success",
"data": {
"output": "0x12341234 0x12341234..."
}
}
## <a name="backtrace"/>Backtrace
Get a list of the current stack of function calls in a given thread.
XXX: Not implemented yet
### Request
{
"type": "request",
"request": "backtrace",
"data": {
"target_id": 0,
"thread_id": 1234
}
}
### Response
{
"type": "response",
"status": "success",
"data": {
"frames": [
etc
]
}
}
## <a name="breakpoints"/>Breakpoints
Get a list of the breakpoints set in an inferior.
XXX: Not implemented yet
### Request
{
"type": "request",
"request": "list_breakpoints",
"data": {
"target_id": 0
}
}
### Response
{
"type": "response",
"status": "success",
"data": {
"breakpoints": [
{"address": 0xffffff8012341234, "enabled": true}
]
}
}
## <a name="connect_fd"/>Connect FD
This is a special request that connects this client session directly to a file descriptor belonging to the inferior.
Once the response has been sent the socket will be connected to the file descriptor. Once a session is connected to an fd, it cannot be disconnected without closing the socket.
If the request fails, an error response will be sent to the client.
XXX: Not implemented yet
### Request
{
"type": "request",
"request": "connect_fd",
"data": {
"fd": 0
}
}
### Response
{
"type": "response",
"status": "success"
}
# Errors
XXX: List all the error types here

91
Plugin-architecture.md Normal file

@ -0,0 +1,91 @@
# Plugin architecture
Voltron uses a plugin architecture for implementing debugger host suppprt, API methods, and UI views. Much of the core of Voltron is implemented using this plugin architecture, and it can also be used to extend Voltron to support new debugger hosts and add new custom API methods and views.
## Types of plugins
There are four types of plugins supported in Voltron.
### Debugger adaptor plugins
Debugger adaptor plugins implement the majority of the support for a given debugger host platform. There are two core debugger adaptor plugins included with Voltron which provide support for LLDB and GDB. These plugins both implement a common set of methods that are used by the core API plugins. If you want to add support for a new debugger host, you'll need to look at the code for one of these plugins in `voltron/plugins/debugger/{dbg_lldb|dbg_gdb}.py` and implement the same methods. They are reasonably well-documented and should provide a reasonable starting point.
More information on building API plugins can be found [here](Building-debugger-adaptor-plugins).
### API plugins
API plugins implement an API method which is accessible via the various configured listeners. These define a request and response class, and interact with a package-wide instance of one of the above debugger adaptor plugins. When an incoming API request is received, its `request` field is used to look up the appropraite API plugin to handle the request. See the included core API plugins in `voltron/plugins/api/*.py`.
More information on building API plugins can be found [here](Building-API-plugins).
### View plugins
View plugins provide views that can be run from the `voltron` command-line entry point. Typically these are, as are the included views, terminal-based. These plugins define a view class, which is instantiated and run by the command-line entry point program. See the included views in `voltron/plugins/views/*.py`.
More information on building API plugins can be found [here](Building-view-plugins).
### Web plugins
Web plugins provide views or other features that are accessible via the embedded CherryPy web server. They typically define a Flask app or just provide a directory of static which is served by the embedded web server, and use JavaScript to talk to the API back end. No web plugins are included in the core at the moment, but see the `examples` directory.
More information on building API plugins can be found [here](Building-web-plugins).
## Anatomy of a plugin
The bare minimum for a Voltron plugin is a Python module containing a subclass of one of the plugin classes - `APIPlugin`, `DebuggerAdaptorPlugin`, `ViewPlugin` or `WebPlugin` (see `voltron/plugin.py`). The plugin class is the top-level object in a plugin. It usually has a name and references to other resources in the plugin. For example an `APIPlugin` has a name like `wait`, and references to an `APIRequest` subclass and an `APIResponse` subclass used to handle the API request. This is what a sample API plugin class might look like:
class SampleAPIPlugin(APIPlugin):
name = 'sample'
request_class = SampleAPIRequest
response_class = SampleAPIResponse
The plugin class itself typically does not contain any methods, it is just used as the top-level object to reference a plugin. The common parent of all the plugin classes, `Plugin` (in [Scruffy](https://github.com/snarez/scruffy)) utilises a metaclass, `PluginRegistry`, to collect all its subclasses as they're loaded by Scruffy. Voltron implements its own `PluginManager` class which is used as an interface to the `PluginRegistry`.
## Installing plugins
### User plugins directory
When Voltron is loaded into the debugger, the [Scruffy](https://github.com/snarez/scruffy) environment is initialised and plugins are discovered in two locations: the `plugins` directory within the `voltron` package, and the user plugins directory in `~/.voltron/plugins`. The latter is where custom plugins should be installed. These directories are searched recursively, and any Python module discovered within is loaded. Classes in these modules that inherit from one of the Voltron plugin classes will be recognised and loaded into the plugin registry.
### Registering programmatically
Plugins can also be loaded and registered programmatically. This may be useful for custom clients that aren't using the standard view architecture, or for loading plugins from the debugger's init script.
After Voltron has been loaded into the debugger, a package-wide shared instance of the `PluginManager` class will be available at `voltron.plugin.pm`. This instance can be used to programmatically register plugins like so:
from myplugin import MyAPIPlugin, MyAPIRequest, MyAPIResponse
voltron.plugin.pm.register_plugin(MyAPIPlugin)
This will make an instance of the plugin class available through the shared `PluginManager` instance.
### Accessing plugins
The shared `PluginManager` instance `voltron.plugin.pm` can also be used to access plugins programmatically from your custom plugins or Voltron clients. There are convenience methods in `voltron/plugin.py` to make this easier.
For example, to instantiate an API request:
req = api_request('disassemble', count=16)
An API response:
res = api_response('disassemble', disassembly="push r...")
To allocate a view class:
view = view('register')
To allocate a new debugger adaptor instance (this would only really be useful if you were building another tool on top of Voltron):
adaptor = debugger_adaptor('lldb')
If you want to access the plugin classes themselves directly, you can call the `PluginManager` methods:
plugin = voltron.plugin.pm.api_plugin_for_request('wait')
plugin = voltron.plugin.pm.debugger_plugin_for_host('lldb')
plugin = voltron.plugin.pm.view_plugin_with_name('register')
plugin = voltron.plugin.pm.web_plugin_with_name('angularview')
The package-wide debugger adaptor is located at `voltron.debugger`. This can be used from your custom API plugins to query the debugger host back end. See some of the core API plugins and the included LLDB and GDB host adaptor plugins for usage.