Skip to content

Commit

Permalink
Simplify MI command filtering, update README with MI commands, add ex…
Browse files Browse the repository at this point in the history
…ec from args
  • Loading branch information
dd86k committed Aug 9, 2024
1 parent f5e852f commit 85ef31f
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 90 deletions.
64 changes: 39 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ Why?

## DAP

The [Debugger Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) (DAP)
is a protocol introduced in Visual Studio Code.
[Debugger Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) (DAP)
is a HTTP-like protocol using JSON that was introduced in
[vscode-debugadapter-node](https://github.com/microsoft/vscode-debugadapter-node)
and was readapted as a
[standalone protocol](https://github.com/microsoft/debug-adapter-protocol)
for debugging various processes and runtimes in Visual Studio Code.

The protocol leaves a lot of nuance regarding implementation
details, which can be a little infuriating to work with.
Expand All @@ -28,24 +32,30 @@ This chapter reuses terminology from DAP, such as _Integer_ meaning, strictly
speaking, a 32-bit integer number (`int`), and _Number_ meaning a 64-bit
double-precision floating-point number (`double`, IEEE 754).

DAP is capable of initiating multiple debugging sessions.
DAP is capable of initiating multiple debugging sessions, also known as a
multi-session configuration

Aliceserver does not yet support multi-session.

### Connection Details

By default, single-session mode is used, where standard streams are used
to communicate with the client (tool).
By default, single-session mode is used. A client may request to initiate a new
debugging session by emiting the `startDebugging` request, which turns the server
configuration into a multi-session mode.

Messages are encoded in JSON using an HTTP-like wrapper.
In either modes, the client spawns the server and uses the standard streams (stdio)
to communicate with the server.

In single-session mode, the server starts by reading a line from the program's
_standard input stream_ ("stdin") by reading characters until a newline is
seen, then reads an empty line.
Messages are encoded as HTTP messages using JSON for its body, where the header and body
of the message are separated by two HTTP newlines ("\r\n").

This is used to get the (currently only) HTTP-like header field, `Content-Length`,
describing the size of the HTTP body. Then, the server reads N amount of bytes
described by the `Content-Length` field as an Integer.
Currently, there is only one header field, `Content-Length`, that determines the
length of the message. This includes requests, replies, and events. This field is
read as an Integer (32-bit integer number, `int`).

This is important since streams are of inderminate sizes, unlike TCP packets.

The body of the message is encoded using [JSON](https://json.org).

A typical request may look like this:

Expand All @@ -66,7 +76,7 @@ Content-Length: 81\r\n
Both client and server maintain their own sequence number, starting at 1.

NOTE: lldb-vscode starts their seq number at 0, while not as per specification,
it poses no changes to its usage.
it poses no difference to its usage.

Multi-session mode is not currently supported.

Expand Down Expand Up @@ -148,8 +158,8 @@ Command support:

## MI

The [Machine Interface](https://sourceware.org/gdb/current/onlinedocs/gdb.html/GDB_002fMI.html)
protocol is a line-oriented protocol introduced in GDB.
[Machine Interface](https://sourceware.org/gdb/current/onlinedocs/gdb.html/GDB_002fMI.html)
is a line-oriented protocol introduced in GDB 5.1.

To my knowledge, MI is not capable of multi-session.

Expand All @@ -158,11 +168,10 @@ To my knowledge, MI is not capable of multi-session.
In a typical setting, MI uses the standard streams to communicate with the child
process.

Once the server starts running, it may already emiting log streams,
until `(gdb)\n` is printed, indicating that the server is ready to receive
commands.
Once the server starts running, it may already emit console streams, until
`(gdb)\n` is printed, indicating that the server is ready to receive commands.

Commands are roughly the same as you would use on GDB:
Commands are almost the same as you would use on GDB:

```text
attach 12345\n
Expand All @@ -174,7 +183,7 @@ Replies to commands start with a `^` character:
^done\n
```

Or on error (note: `\\n` and `\\"` denote c-string formatting as-is):
Or on error (note: `\\n` and `\\"` denote c-string formatting):

```text
^error,msg="Example text.\\n\\nValue: \\"Test\\""\n
Expand All @@ -191,20 +200,25 @@ using c-string formatting.
| Exec | `*` | Async execution state changed. |
| Notify | `=` | Async notification related to the debugger. |
| Status | `+` | Async status change. |
| Console Stream | `~` | Console output. |
| Target Stream | `@` | |
| Log Stream | `&` | Typically for repeating commands as interpreted by the server. |
| Console Stream | `~` | Console informational message from debugger. |
| Target Stream | `@` | Program output. |
| Log Stream | `&` | Server repeated command for logging purposes. |

Some commands may start with `-`.

### Supported Requests

NOTE: LLDB command variants currently not supported.
NOTE: Command focus is on GDB, lldb-mi commands may work.

| Request | Commands | Supported? | Comments |
|---|---|---|---|
| Attach | `attach` | ✔️ | |
| Launch | `exec-run` || |
| Launch | `-exec-run`, `target exec`, `-exec-arguments` | ✔️ | |
| Continue | `-exec-continue` | ✔️ | |
| Terminate | `-exec-abort` | ✔️ | |
| Detach | `-exec-detach`, `detach` | ✔️ | |
| Set working directory | `environment-directory` | ✔️ | |
| Disconnect | `q`, `quit`, `-gdb-exit` | ✔️ | |

### Supported Events

Expand Down
148 changes: 93 additions & 55 deletions source/adapters/mi.d
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import adapters.base;
import transports.base : ITransport;
import logging;
import config;
import core.vararg;
import std.conv;
import std.format;
import server : serverExec;
import std.conv : to;
import std.format : format;
import std.file : chdir;
import std.array : replace, split;
import std.ascii : isWhite;

// TODO: Function to separate shell-like arguments (with quotes)

// NOTE: GDB/MI versions
//
// MI GDB Breaking changes
Expand Down Expand Up @@ -85,16 +87,16 @@ enum MIType : char
command = '-',
}

enum MIVariant { gdb }

private immutable string gdbString = "(gdb)\n";
private immutable string doneMsg = "^done\n";

/*
private
string parseCString(string cstr)
{
return "";
}
*/

private
string formatCString(A...)(string fmt, A args)
Expand All @@ -108,42 +110,14 @@ unittest
assert(formatCString("Thing: \"hi\"\n") == `Thing: \"hi\"\n`);
}


class MIAdapter : Adapter
{
// TODO: version parameter
this(ITransport t, MIVariant mivariant = MIVariant.gdb, int miversion = 1)
this(ITransport t, int version_ = 1)
{
super(t);

variant = mivariant;
version_ = miversion;

final switch (mivariant) {
case MIVariant.gdb:
requests["q"] = RequestType.close;
requests["quit"] = RequestType.close;
requests["-gdb-exit"] = RequestType.close;
requests["-gdb-detach"] = RequestType.detach;
requests["attach"] = RequestType.attach;
requests["exec-run"] = RequestType.go;
requests["exec-continue"] = RequestType.go;
//requests["exec-interrupt"] = RequestType.pause;
//requests["exec-next"] = RequestType.go; // [--reverse]
//requests["exec-step"] = RequestType.instructionStep; // [--reverse]
//requests["exec-finish"] = RequestType.instructionStepOut; // [--reverse]
//requests["break-insert"] = RequestType.breakFunction; // -f FUNCTION
//requests["break-condition"] = RequestType.breakCondition; // num condition
//requests["file-exec-and-symbols"] = RequestType.;
requests["environment-directory"] = RequestType.currentWorkingDirectory;
// goto:
// break-insert -t TARGET
// exec-jump TARGET
//requests["goto"] = RequestType.instructionStepOut;
// change variable: gdb-set var REGISTER = VALUE
//requests["gdb-set"] = RequestType.instructionStepOut;
break;
}
miversion = version_;

send(gdbString); // Ready!
}
Expand All @@ -169,16 +143,51 @@ class MIAdapter : Adapter

// Recognized requests
string requestCommand = args[0];
RequestType *req = requestCommand in requests;

// TODO: These commands
// - -exec-finish: functionOut
// - -exec-next: nextLine
// - -exec-interrupt: pause
// - -exec-step: instructionStep
// - -exec-finish: instructionStepOut
// - break-insert: insert breakpoint
// - break-condition: change condition to breakpoint
// - file-exec-and-symbols: set exec and symbols
// - goto: break-insert -t TARGET or exec-jump TARGET

// Filter by recognized requests
AdapterRequest request;
if (req) switch (*req) {
/*
case RequestType.launch:
switch (requestCommand) {
case "-exec-run":
request.type = RequestType.launch;

// If we saved the exec target
if (exec)
{
request.launchOptions.path = exec;
return request;
}

// If server got exec specified earlier
string exec2 = serverExec();
if (exec2)
{
request.launchOptions.path = exec2;
return request;
}

reply(AdapterError("No executable to run."));
goto Lread;
case "-exec-continue":
request.type = RequestType.go;
return request;
case "-exec-abort":
request.type = RequestType.terminate;
return request;
*/
case RequestType.attach:
case "-gdb-detach", "detach":
request.type = RequestType.detach;
return request;
case "attach":
if (args.length < 2)
{
reply(AdapterError("Missing process-id argument."));
Expand All @@ -194,7 +203,7 @@ class MIAdapter : Adapter

request.type = RequestType.attach;
return request;
case RequestType.currentWorkingDirectory:
case "environment-directory":
if (args.length < 2)
{
reply(AdapterError("Missing process-id argument."));
Expand All @@ -205,17 +214,37 @@ class MIAdapter : Adapter
chdir(dir);
reply(AdapterReply());
goto Lread;
case RequestType.close:
request.type = RequestType.close;
return request;
default: // Not an official request, likely more GDB related
}
case "target":
if (args.length < 2)
{
reply(AdapterError("Need target type"));
goto Lread;
}

string targetType = args[1];
switch (targetType) {
case "exec":
if (args.length < 3)
{
reply(AdapterError("Need target executable path"));
goto Lread;
}

exec = args[2].dup;
reply(AdapterReply());
goto Lread;
default:
reply(AdapterError(format("Invalid target type: %s", targetType)));
}
goto Lread;
case "-exec-arguments":
// If arguments given, set, otherwise, clear.
execArguments = args.length >= 1 ? args[1..$].dup : null;
reply(AdapterReply());
goto Lread;
// TODO: print exec arguments
//case "-exec-show-arguments":

// Filter by specific GDB or LLDB command
switch (requestCommand) {
case "exec-arguments":
// TODO: Save arguments
break;
//case "mi-async": // TODO: mi-async
case "show":
// NOTE: "show" alone makes GDB show everything
Expand All @@ -239,6 +268,9 @@ class MIAdapter : Adapter

reply(AdapterError(format(`Unknown show command: "%s"`, showCommand)));
break;
case "q", "quit", "-gdb-exit":
request.type = RequestType.close;
return request;
// Ignore list
case "gdb-set", "inferior-tty-set": goto Lread;
default:
Expand Down Expand Up @@ -275,6 +307,11 @@ class MIAdapter : Adapter
// args=[{name="argc",value="1"},{name="argv",value="0xbfc4d4d4"}],
// file="myprog.c",fullname="/home/nickrob/myprog.c",line="68",
// arch="i386:x86_64"}
// - *stopped,reason="exited",exit-code="01"
// - *stopped,reason="exited-signalled",signal-name="SIGINT",
// signal-meaning="Interrupt"
// - @Hello world!
// - ~"Message from debugger\n"
switch (msg.type) with (EventType) {
/*case output:
send(format("~\"%s\"\n", formatCString( msg. )));
Expand All @@ -297,7 +334,8 @@ class MIAdapter : Adapter
}

private:
MIVariant variant;
int version_;
RequestType[string] requests;
int miversion;

string exec;
string[] execArguments;
}
2 changes: 1 addition & 1 deletion source/main.d
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ void main(string[] args)
}

// Run server
try startServer(adapter);
try startServer(adapter, args);
catch (Exception ex)
{
debug logCritical("Unhandled Exception: %s", ex);
Expand Down
Loading

0 comments on commit 85ef31f

Please sign in to comment.