Skip to content

[lldb-dap] Creating an API for sending custom dap events from lldb-dap. #112384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1267,7 +1267,7 @@ def run_vscode(dbg, args, options):
def main():
parser = optparse.OptionParser(
description=(
"A testing framework for the Visual Studio Code Debug " "Adaptor protocol"
"A testing framework for the Visual Studio Code Debug Adaptor protocol"
)
)

Expand Down
3 changes: 3 additions & 0 deletions lldb/test/API/tools/lldb-dap/send-event/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
C_SOURCES := main.c

include Makefile.rules
67 changes: 67 additions & 0 deletions lldb/test/API/tools/lldb-dap/send-event/TestDAP_sendEvent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Test lldb-dap send-event integration.
"""

import json

from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
import lldbdap_testcase


class TestDAP_sendEvent(lldbdap_testcase.DAPTestCaseBase):
def test_send_event(self):
"""
Test sending a custom event.
"""
program = self.getBuildArtifact("a.out")
source = "main.c"
custom_event_body = {
"key": 321,
"arr": [True],
}
self.build_and_launch(
program,
stopCommands=[
"lldb-dap send-event my-custom-event-no-body",
"lldb-dap send-event my-custom-event '{}'".format(
json.dumps(custom_event_body)
),
],
)

breakpoint_line = line_number(source, "// breakpoint")

self.set_source_breakpoints(source, [breakpoint_line])
self.continue_to_next_stop()

custom_event = self.dap_server.wait_for_event(
filter=["my-custom-event-no-body"]
)
self.assertEquals(custom_event["event"], "my-custom-event-no-body")
self.assertIsNone(custom_event.get("body", None))

custom_event = self.dap_server.wait_for_event(filter=["my-custom-event"])
self.assertEquals(custom_event["event"], "my-custom-event")
self.assertEquals(custom_event["body"], custom_event_body)

def test_send_internal_event(self):
"""
Test sending an internal event produces an error.
"""
program = self.getBuildArtifact("a.out")
source = "main.c"
self.build_and_launch(program)

breakpoint_line = line_number(source, "// breakpoint")

self.set_source_breakpoints(source, [breakpoint_line])
self.continue_to_next_stop()

resp = self.dap_server.request_evaluate(
"`lldb-dap send-event stopped", context="repl"
)
self.assertRegex(
resp["body"]["result"],
r"Invalid use of lldb-dap send-event, event \"stopped\" should be handled by lldb-dap internally.",
)
6 changes: 6 additions & 0 deletions lldb/test/API/tools/lldb-dap/send-event/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <stdio.h>

int main(int argc, char const *argv[]) {
printf("example\n"); // breakpoint 1
return 0;
}
62 changes: 62 additions & 0 deletions lldb/tools/lldb-dap/DAP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,68 @@ bool ReplModeRequestHandler::DoExecute(lldb::SBDebugger debugger,
return true;
}

// Sends a DAP event with an optional body.
//
// See
// https://code.visualstudio.com/api/references/vscode-api#debug.onDidReceiveDebugSessionCustomEvent
bool SendEventRequestHandler::DoExecute(lldb::SBDebugger debugger,
char **command,
lldb::SBCommandReturnObject &result) {
// Command format like: `send-event <name> <body>?`
if (!command || !command[0] || llvm::StringRef(command[0]).empty()) {
result.SetError("Not enough arguments found, expected format "
"`lldb-dap send-event <name> <body>?`.");
return false;
}

llvm::StringRef name{command[0]};
// Events that are stateful and should be handled by lldb-dap internally.
const std::array internal_events{"breakpoint", "capabilities", "continued",
"exited", "initialize", "loadedSource",
"module", "process", "stopped",
"terminated", "thread"};
if (std::find(internal_events.begin(), internal_events.end(), name) !=
std::end(internal_events)) {
std::string msg =
llvm::formatv("Invalid use of lldb-dap send-event, event \"{0}\" "
"should be handled by lldb-dap internally.",
name)
.str();
result.SetError(msg.c_str());
return false;
}

llvm::json::Object event(CreateEventObject(name));

if (command[1] && !llvm::StringRef(command[1]).empty()) {
// See if we have unused arguments.
if (command[2]) {
result.SetError(
"Additional arguments found, expected `lldb-dap send-event "
"<name> <body>?`.");
return false;
}

llvm::StringRef raw_body{command[1]};

llvm::Expected<llvm::json::Value> body = llvm::json::parse(raw_body);

if (!body) {
llvm::Error err = body.takeError();
std::string msg = "Failed to parse custom event body: " +
llvm::toString(std::move(err));
result.SetError(msg.c_str());
return false;
}

event.try_emplace("body", std::move(*body));
}

g_dap.SendJSON(llvm::json::Value(std::move(event)));
result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult);
return true;
}

void DAP::SetFrameFormat(llvm::StringRef format) {
if (format.empty())
return;
Expand Down
5 changes: 5 additions & 0 deletions lldb/tools/lldb-dap/DAP.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ struct ReplModeRequestHandler : public lldb::SBCommandPluginInterface {
lldb::SBCommandReturnObject &result) override;
};

struct SendEventRequestHandler : public lldb::SBCommandPluginInterface {
bool DoExecute(lldb::SBDebugger debugger, char **command,
lldb::SBCommandReturnObject &result) override;
};

struct DAP {
std::string debug_adaptor_path;
InputStream input;
Expand Down
31 changes: 31 additions & 0 deletions lldb/tools/lldb-dap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,37 @@ The initial repl-mode can be configured with the cli flag `--repl-mode=<mode>`
and may also be adjusted at runtime using the lldb command
`lldb-dap repl-mode <mode>`.

#### `lldb-dap send-event`

lldb-dap includes a command to trigger a Debug Adapter Protocol event
from a script.

The event maybe a custom DAP event or a standard event, if the event is not
handled internally by `lldb-dap`.

This command has the format:

```
lldb-dap send-event <name> <body>?
```

For example you can use a launch configuration hook to trigger custom events like:

```json
{
"program": "exe",
"stopCommands": [
"lldb-dap send-event MyStopEvent",
"lldb-dap send-event MyStopEvent '{\"key\": 321}",
]
}
```

[See the specification](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_Event)
for more details on Debug Adapter Protocol events and the VS Code
[debug.onDidReceiveDebugSessionCustomEvent](https://code.visualstudio.com/api/references/vscode-api#debug.onDidReceiveDebugSessionCustomEvent)
API for handling a custom event from an extension.

## Contributing

`lldb-dap` and `lldb` are developed under the umbrella of the [LLVM project](https://llvm.org/).
Expand Down
2 changes: 2 additions & 0 deletions lldb/tools/lldb-dap/lldb-dap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,8 @@ void request_initialize(const llvm::json::Object &request) {
cmd.AddCommand(
"repl-mode", new ReplModeRequestHandler(),
"Get or set the repl behavior of lldb-dap evaluation requests.");
cmd.AddCommand("send-event", new SendEventRequestHandler(),
"Sends an DAP event to the client.");

g_dap.progress_event_thread = std::thread(ProgressEventThreadFunction);

Expand Down
Loading