-
Notifications
You must be signed in to change notification settings - Fork 7
Debugger Protocol
The new haxe interpreter target, called eval
, that is now used for macros and --interp
runs has debugging support built-in. This page describes the protocol used by the debugger so a tool author can implement a frontend for it.
Debugger interacts with the frontend by connecting to a TCP port supplied through a compiler define flag eval-debugger
. The value format for the flag is <host>:<port>
(for example, haxe build.hxml -D eval-debugger=127.0.0.1:6666
). The frontend must open a listening socket at this address, launch haxe with specified arguments and wait for eval debugger to connect. After connection is established, a two-way message exchange between debugger and frontend is active.
[ebishton: We shouldn't rely simply on a socket connection being established. We probably want to specify a connect packet format, including a protocol version number, to ensure that we're talking to a debugger and not some random thing listening on the socket, and an ACK packet, with a (list of?) protocol version supported. ]
Frontend and debugger send and receive messages to each other using the following simple packet format: <len><body>
, where <len>
is the byte length of the following <body>
, encoded as 32-bit little-endian unsigned integer. The <body>
is a JSON-encoded message, encoded to bytes using UTF-8 encoding. So <len>
represents the number of bytes of a utf-8 encoded json string of the <body>
.
[nicolas : Requiring UTF-8 is also a PITA if we want to send binary data. if the protocol is binary it should use <len:32 bits>body:binary]
The protocol is based on the JSON-RPC 2.0 with the following considerations:
- batch requests are not supported
[ebishton: We are also missing thread commands: list. We can probably resist adding others by putting adding thread IDs to the other commands (like Get stack trace, continue, step, etc.). ]
Name: setBreakpoint
Params: {file:String, line:Int, ?column:Int, ?condition:String}
. Line and column are 1-based. If column is omitted, the interpreter will stop at the first expression on given line. If condition is set, the interpreter will stop only if it evaluates to true
.
Result: {id:Int}
. Returns id
of a created breakpoint.
Name: removeBreakpoint
Argument: {id:Int}
(breakpoint id returned by the "set breakpoint" command).
Result: null
[ebishton: Need a "list breakpoints" command.]
Name: setBreakpoints
Argument: {file:String, breakpoints:Array<{line:Int, ?column:Int}>}
.
Result: Array<{id:Int}>
(array of the breakpoint ids in the same order as in the argument)
This command replaces all breakpoints for a given file in a single command, clearing all old breakpoints and setting new ones with given coordinates. So, to clear breakpoints for a file, one should pass empty array.
This is useful for editors that manage breakpoint lists themselves, like VS Code.
Name: setFunctionBreakpoints
Argument: [{breakpoint:{name:String}}]
This command clears the current function breakpoints and replaces them with the ones described in the argument. The name is expected to have the form Type.field
or package.Type.field
. While such a breakpoint is active, execution will stop whenever the function denoted by the argument is entered.
Result: same as setBreakpoints
Name: continue
Argument: none
Result: null
Name: next
Argument: none
Result: null
[ebishton: Perhaps the result from step over/in/out should include the top if the stack, given that each step is implicitly a breakpoint, and the first thing that any GUI is going to do is request the stack state and the top frame. ]
Name: stepIn
Argument: none
Result: null
Name: stepOut
Argument: none
Result: null
[ebishton: Need a "halt" or "pause" command. ]
[ebishton: Maybe "kill" and "restart" commands? ]
Name: stackTrace
Argument: none
Result: Array<StackFrameInfo>
. Array of stack frame information objects with the following structure:
typedef StackFrameInfo = {
var id:Int;
var name:String;
var source:String;
var line:Int;
var column:Int;
var endLine:Int;
var endColumn:Int;
var artificial:Bool;
}
[ebishton: Also need to deal with incorrect state. For example, a stack is requested but the app/thread is not stopped. ]
Name: switchFrame
Argument: {id:Int}
(id, the value of the field id
in the StackFrameInfo
object described above)
Result: null
When stop happens, the debugger is always at frame 0
, so explicit switching to it is not necessary.
[ebishton: I'm not sure why the debug server needs to track this state. The GUI/CLI can keep track of this by itself. If a particular stack frame is needed in order to inspect variables, then I would expect that frame ID to be included in the "evaluate expression" command. Oh, yeah. We need one of those, too. ]
Name: getScopes
Argument: none
Result: Array<ScopeInfo>
. Array of objects describing non-empty variable scopes for the current frame.
/** Info about a scope **/
typedef ScopeInfo = {
/** Scope identifier to use for the `vars` request. **/
var id:Int;
/** Name of the scope (e.g. Locals, Captures, etc) **/
var name:String;
/** Position information about scope boundaries, if present **/
@:optional var pos:{source:String, line:Int, column:Int, endLine:Int, endColumn:Int};
}
Scopes are created by the eval interpreter for each function and block expression. This function returns only ones containing variables.
Name: getScopeVariables
Argument: {id:Int}
(the id of a variable scope within current frame, returned in the id
field of the scopes
request described above)
Result: Array<VarInfo>
. Array of variable info objects with the following structure:
typedef VarInfo = {
/** Variable/field name, for array elements or enum ctor arguments looks like `[0]` **/
var name:String;
/** Value type **/
var type:String;
/** Current value to display (structured child values are rendered with `...`) **/
var value:String;
/** True if this variable is structured, meaning that we can request "subvariables" (fields/elements) **/
var structured:Bool;
/** Access expression used to reference this variable.
For scope-level vars it's the same as name, for child vars it's an expression like `a.b[0].c[1]`.
**/
var access:String;
}
Name: getStructure
Argument: {expr:String}
(an expression pointing to a specific variable as returned in the access
field of the VarInfo
object described above.
Result: Array<VarInfo>
. Array of variable info objects with the structure described as the VarInfo
object above.
This effectively allows getting the inner structure of a local variable at any level by supplying proper path.
Name: evaluate
Argument: {expr:String}
Result: VarInfo
. Object describing the evaluated value.
Parses the provided expression and interprets it as a value. If successful, a string representation of that value is returned.
Name: setVariable
Argument: {expr:String, value:String}
, where expr
is an expression pointing to a specific variable as returned in the access
field of the VarInfo
object described above, and value
is a value expression supported by Haxe.
Result: VarInfo
. Object describing this variable after the successful change.
Name: getCompletion
Argument: {text:String, column:String}
Result: Array<{label:String, kind:String, ?start:Int}>
Parses the given text while assuming that the display position is at the given column. If successful, the expression is converted to a value (like in evaluate
) and the output is a list of information applicable to the value:
- For identifiers, a list of all available identifiers is returned.
- For dot access completion (
expr.|
), a list of available fields is returned. - In an array access expression (
expr[|]
), the available indices are returned.
If the expression cannot be parsed, an error "No completion point found"
is returned. If it can be parsed, but there is no meaningful information for the resulting value available, an empty array is returned.
Name: setExceptionOptions
Argument: Array<String>
Result: null
Sets the options for how to handle exceptions:
- If the argument contains
"all"
, then all exceptions cause execution to halt. - Otherwise, if the argument contains
"uncaught"
, only exceptions which are not caught cause execution to halt. - Otherwise exceptions do not halt execution.
Name: breakpointStop
Result: none
Name: exceptionStop
Result: {text:String}
. Object describing the exception that caused the stop.