-
Notifications
You must be signed in to change notification settings - Fork 415
JSON API reference
Voltron clients communicate with the back end by means of an HTTP/JSON API which is exposed over a UNIX domain socket (~/.voltron/voltron.sock
), and/or a TCP socket.
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"
}
}
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 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"
}
}
Requests may also specify a block
parameter. Some debugger hosts support asynchronous requests (LLDB) and some only support synchronous requests (GDB). This capability is indicated by the capabilities
field returned in a version
response. If block
is true, the request will be deferred until the next time the debugger stops (for example, until the user steps over another instruction, or until the user interrupts the debugger or a breakpoint is hit if the inferior is currently running). The debugger then processes any blocking requests on its main thread, and the response will be sent.
The request may also specify a timeout
parameter for blocking requests. This defaults to 30 seconds if not specified. If block
is true, the HTTP server will block until either the request has been processed, or the timeout is hit.
If the request times out, an error response will be returned. If the request is fulfilled, the appropriate response will be returned.
Here is an example of a blocking request with a timeout of 10 seconds:
{
"type": "request",
"request": "registers",
"block": true,
"timeout": 10,
"data": {
"target_id":0
}
}
If the timeout is hit, an error response like this is sent:
{
"type": "response",
"status": "error",
"data": {
"message": "The request timed out",
"code": 4100
}
}
Voltron's built-in views support both asynchronous and blocking methods, which means that they can update immediately upon connecting to LLDB, or wait until the debugger is stepped for GDB (so we don't crash GDB by poking at the inferior from a background thread). Have a look at them for examples of how to support both methods.
The following requests are defined in the core API:
All requests may return a busy error if the debugger is busy and unable to respond.
Get the API and debugger host version.
{
"type": "request",
"request": "version"
}
{
"type": "response",
"status": "success",
"data": {
"api_version": 1.0,
"host_version": "lldb-something"
}
}
Get a the state of a debugger target.
{
"type": "request",
"request": "state",
"data": {
"target_id": 0
}
}
{
"type": "response",
"status": "success",
"data": {
"state": "stopped"
}
}
Get a list of the debugger's targets.
{
"type": "request",
"request": "targets"
}
{
"type": "response",
"status": "success",
"data": {
"targets": [
{
"id": 0,
"file": "/bin/ls",
"arch": "x86_64"
}
]
}
}
Get the values of the CPU registers for a given target and thread.
{
"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.
{
"type": "response",
"status": "success",
"data": {
"registers": {
"rip": 0xffffff8012341234,
"rax": 0x4141414141414141,
...
}
}
}
Read memory from the inferior.
{
"type": "request",
"request": "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.
{
"type": "response",
"status": "success",
"data": {
"memory": "\x41\x41\x41\x41...",
"bytes": 1024
}
}
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..."
}
}
Read memory starting from the value contained in the inferior's stack pointer register.
{
"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.
See Memory.
Disassemble instructions from the inferior's memory.
{
"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.
{
"type": "response",
"status": "success",
"data": {
"output": "mov blah blah",
"bytes": 1024
}
}
Execute a command in the debugger host and return the output.
{
"type": "request",
"request": "command",
"data": {
"command": "x/32x $rsp"
}
}
command
- the command to execute.
{
"type": "response",
"status": "success",
"data": {
"output": "0x12341234 0x12341234..."
}
}
Get a list of the current stack of function calls in a given thread.
XXX: Not implemented yet
{
"type": "request",
"request": "backtrace",
"data": {
"target_id": 0,
"thread_id": 1234
}
}
{
"type": "response",
"status": "success",
"data": {
"frames": [
etc
]
}
}
Get a list of the breakpoints set in an inferior.
{
"type": "request",
"request": "breakpoints",
"data": {
"target_id": 0
}
}
{
"type": "response",
"status": "success",
"data": {
"breakpoints": [
{
"id": 1,
"enabled": True,
"one_shot": False,
"hit_count": 5,
"locations": [
{
"address": 0x100000cf0,
"name": "main"
}
]
}
]
}
}
XXX: List all the error types here