RemedyBG's Debug Protocol
RemedyBG's debug-protocol for requesting and receiving data has now been fleshed out. The way it works is pretty straight-forward and was inspired by Casey's discussion of debugger architecture from a while back.
To open a new inspector, the user makes a single call into RemedyBG. This call takes an inspector expression along with a user provided callback that gets triggered in a few different cases.
First, we'll talk a little bit about the inspector expression strings themselves. Briefly, an expression is structured as
where 'expression' can be any of the pseudo-expressions (%callstack, %modules, %threads, %watches, %breakpoints, %registers, or %processes), a C-style array, or a linked-list in which the expression is augmented with the name of the field to allow traversal as
The pseudo-expressions act as if they were C-structs with their own fields. For example, %callstack has the fields 'ModuleName', 'FunctionName', 'Parameters', and 'LineNumber'. These fields can be specified in any order and are all optional. If you only want to see the function and line-number, the expression would look like
One additional twist is that the fields can themselves be enumerable expressions! For instance, in the case of %callstack, the 'Parameters' field is actually an enumerable expression in which you can pick and choose which fields you are interested in. For example, to request all the (currently) available fields and sub-fields of the %callstack pseudo-expression you would pass in
That is how you ask RemedyBG to give you the data you want. After the expression is parsed and validated, the user receives various callbacks. The data sent in these callbacks are sent as binary blobs. I decided to send the data as a binary blob for a couple of reasons. First, sending the data back as a data structure with pointers (or even just offsets) was messy and error-prone. Sending a blob made the construction and parsing of the data on the other end much simpler. Second, this will allow the future possibility of remote debugging without having to change a bunch of stuff. In any case, sending the data as a binary blob made more sense.
The first reason the user supplied callback can be triggered, after RemedyBG parses the inspector expression and does some validation, is the description of the data layout. This includes the number of fields, their display names, and so forth. This is only, at the moment, triggered once since the data layout cannot change.
The user supplied callback can also be triggered whenever the data changes (pretty much anytime the debugger halts). This data contains the actual types (in the case a client wants to render a type in a certain way, for instance) along with their values. Though not currently implemented, this data will also contain flags about whether the data is actually valid, or stale, and so forth.
With those two callbacks a client can create a generic inspect window that doesn't have to know anything about call stacks or breakpoints or anything else. To put this idea to the test while developing the protocol I implemented the call stack inspect window. The code used to display the call stack is completely generic and will be able to handle the rest of the enumerable expressions as they are implemented without any change. This also includes the fields that are themselves enumerable. For instance, %callstack's 'Parameters' field (you can see this in the new screen shot I posted for the project page).