[Lldb-commits] [lldb] 227b218 - Creating a startDebugging reverse DAP request handler in lldb-vscode.
David Goldman via lldb-commits
lldb-commits at lists.llvm.org
Thu Jun 29 11:46:56 PDT 2023
Author: John Harrison
Date: 2023-06-29T14:45:57-04:00
New Revision: 227b2180eb2be94986d63c75c144f88be13fc52f
URL: https://github.com/llvm/llvm-project/commit/227b2180eb2be94986d63c75c144f88be13fc52f
DIFF: https://github.com/llvm/llvm-project/commit/227b2180eb2be94986d63c75c144f88be13fc52f.diff
LOG: Creating a startDebugging reverse DAP request handler in lldb-vscode.
Adds support for a reverse DAP request to startDebugging. The new request can be used to launch child processes from lldb scripts, for example it would be start forward to configure a debug configuration for a server and a client allowing you to launch both processes with a single debug configuraiton.
Reviewed By: wallace, ivanhernandez13
Differential Revision: https://reviews.llvm.org/D153447
Added:
lldb/test/API/tools/lldb-vscode/startDebugging/Makefile
lldb/test/API/tools/lldb-vscode/startDebugging/TestVSCode_startDebugging.py
lldb/test/API/tools/lldb-vscode/startDebugging/main.c
Modified:
lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
lldb/tools/lldb-vscode/JSONUtils.cpp
lldb/tools/lldb-vscode/README.md
lldb/tools/lldb-vscode/VSCode.cpp
lldb/tools/lldb-vscode/VSCode.h
lldb/tools/lldb-vscode/lldb-vscode.cpp
Removed:
################################################################################
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
index 236e26c6b9313f..1c77fabeb4afe8 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
@@ -126,6 +126,7 @@ def __init__(self, recv, send, init_commands, log_file=None):
self.thread_stop_reasons = {}
self.breakpoint_events = []
self.progress_events = []
+ self.reverse_requests = []
self.sequence = 1
self.threads = None
self.recv_thread.start()
@@ -324,6 +325,7 @@ def send_recv(self, command):
self.validate_response(command, response_or_request)
return response_or_request
else:
+ self.reverse_requests.append(response_or_request)
if response_or_request["command"] == "runInTerminal":
subprocess.Popen(
response_or_request["arguments"]["args"],
@@ -340,8 +342,20 @@ def send_recv(self, command):
},
set_sequence=False,
)
+ elif response_or_request["command"] == "startDebugging":
+ self.send_packet(
+ {
+ "type": "response",
+ "seq": -1,
+ "request_seq": response_or_request["seq"],
+ "success": True,
+ "command": "startDebugging",
+ "body": {},
+ },
+ set_sequence=False,
+ )
else:
- desc = 'unkonwn reverse request "%s"' % (
+ desc = 'unknown reverse request "%s"' % (
response_or_request["command"]
)
raise ValueError(desc)
diff --git a/lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py b/lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
index a9a9c252908616..bd349a6e5eeead 100644
--- a/lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
+++ b/lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
@@ -59,6 +59,18 @@ def test_runInTerminal(self):
program, runInTerminal=True, args=["foobar"], env=["FOO=bar"]
)
+ self.assertEqual(
+ len(self.vscode.reverse_requests),
+ 1,
+ "make sure we got a reverse request"
+ )
+
+ request = self.vscode.reverse_requests[0]
+ self.assertIn(self.lldbVSCodeExec, request["arguments"]["args"])
+ self.assertIn(program, request["arguments"]["args"])
+ self.assertIn("foobar", request["arguments"]["args"])
+ self.assertIn("FOO", request["arguments"]["env"])
+
breakpoint_line = line_number(source, "// breakpoint")
self.set_source_breakpoints(source, [breakpoint_line])
diff --git a/lldb/test/API/tools/lldb-vscode/startDebugging/Makefile b/lldb/test/API/tools/lldb-vscode/startDebugging/Makefile
new file mode 100644
index 00000000000000..10495940055b63
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/startDebugging/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-vscode/startDebugging/TestVSCode_startDebugging.py b/lldb/test/API/tools/lldb-vscode/startDebugging/TestVSCode_startDebugging.py
new file mode 100644
index 00000000000000..e1fc7f7fa17fb9
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/startDebugging/TestVSCode_startDebugging.py
@@ -0,0 +1,39 @@
+"""
+Test lldb-vscode startDebugging reverse request
+"""
+
+
+import vscode
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+import lldbvscode_testcase
+
+
+class TestVSCode_startDebugging(lldbvscode_testcase.VSCodeTestCaseBase):
+ def test_startDebugging(self):
+ """
+ Tests the "startDebugging" reverse request. It makes sure that the IDE can
+ start a child debug session.
+ """
+ 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()
+ self.vscode.request_evaluate(
+ '`lldb-vscode startDebugging attach \'{"pid":321}\'', context='repl'
+ )
+
+ self.assertEqual(
+ len(self.vscode.reverse_requests),
+ 1,
+ "make sure we got a reverse request"
+ )
+
+ request = self.vscode.reverse_requests[0]
+ self.assertEqual(request["arguments"]["configuration"]["pid"], 321)
+ self.assertEqual(request["arguments"]["request"], "attach")
\ No newline at end of file
diff --git a/lldb/test/API/tools/lldb-vscode/startDebugging/main.c b/lldb/test/API/tools/lldb-vscode/startDebugging/main.c
new file mode 100644
index 00000000000000..27bc22b94794b6
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/startDebugging/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(int argc, char const *argv[]) {
+ printf("example\n"); // breakpoint 1
+ return 0;
+}
diff --git a/lldb/tools/lldb-vscode/JSONUtils.cpp b/lldb/tools/lldb-vscode/JSONUtils.cpp
index d854bc6608aca9..bf7f766a58e3da 100644
--- a/lldb/tools/lldb-vscode/JSONUtils.cpp
+++ b/lldb/tools/lldb-vscode/JSONUtils.cpp
@@ -1104,10 +1104,6 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
llvm::StringRef debug_adaptor_path,
llvm::StringRef comm_file,
lldb::pid_t debugger_pid) {
- llvm::json::Object reverse_request;
- reverse_request.try_emplace("type", "request");
- reverse_request.try_emplace("command", "runInTerminal");
-
llvm::json::Object run_in_terminal_args;
// This indicates the IDE to open an embedded terminal, instead of opening the
// terminal in a new window.
@@ -1143,9 +1139,7 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
run_in_terminal_args.try_emplace("env",
llvm::json::Value(std::move(environment)));
- reverse_request.try_emplace(
- "arguments", llvm::json::Value(std::move(run_in_terminal_args)));
- return reverse_request;
+ return run_in_terminal_args;
}
// Keep all the top level items from the statistics dump, except for the
diff --git a/lldb/tools/lldb-vscode/README.md b/lldb/tools/lldb-vscode/README.md
index f82293daa51281..67dfa54ed4519b 100644
--- a/lldb/tools/lldb-vscode/README.md
+++ b/lldb/tools/lldb-vscode/README.md
@@ -11,6 +11,8 @@
- [Attach to process using process ID](#attach-using-pid)
- [Attach to process by name](#attach-by-name)
- [Loading a core file](#loading-a-core-file)
+- [Custom Debugger Commands](#custom-debugger-commands)
+ - [startDebugging](#startDebugging)
# Introduction
@@ -203,3 +205,33 @@ This loads the coredump file `/cores/123.core` associated with the program
"program": "/tmp/a.out"
}
```
+
+# Custom debugger commands
+
+The `lldb-vscode` tool includes additional custom commands to support the Debug
+Adapter Protocol features.
+
+## startDebugging
+
+Using the command `lldb-vscode startDebugging` it is possible to trigger a
+reverse request to the client requesting a child debug session with the
+specified configuration. For example, this can be used to attached to forked or
+spawned processes. For more information see
+[Reverse Requests StartDebugging](https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_StartDebugging).
+
+The custom command has the following format:
+
+```
+lldb-vscode startDebugging <launch|attach> <configuration>
+```
+
+This will launch a server and then request a child debug session for a client.
+
+```javascript
+{
+ "program": "server",
+ "postRunCommand": [
+ "lldb-vscode startDebugging launch '{\"program\":\"client\"}'"
+ ]
+}
+```
diff --git a/lldb/tools/lldb-vscode/VSCode.cpp b/lldb/tools/lldb-vscode/VSCode.cpp
index ca1a626e514c91..b1c6817c2128b9 100644
--- a/lldb/tools/lldb-vscode/VSCode.cpp
+++ b/lldb/tools/lldb-vscode/VSCode.cpp
@@ -41,10 +41,10 @@ VSCode::VSCode()
focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false),
stop_at_entry(false), is_attach(false),
restarting_process_id(LLDB_INVALID_PROCESS_ID),
- configuration_done_sent(false), reverse_request_seq(0),
- waiting_for_run_in_terminal(false),
+ configuration_done_sent(false), waiting_for_run_in_terminal(false),
progress_event_reporter(
- [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }) {
+ [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),
+ reverse_request_seq(0) {
const char *log_file_path = getenv("LLDBVSCODE_LOG");
#if defined(_WIN32)
// Windows opens stdout and stdin in text mode which converts \n to 13,10
@@ -505,24 +505,83 @@ bool VSCode::HandleObject(const llvm::json::Object &object) {
return false; // Fail
}
}
+
+ if (packet_type == "response") {
+ auto id = GetSigned(object, "request_seq", 0);
+ ResponseCallback response_handler = [](llvm::Expected<llvm::json::Value>) {
+ llvm::errs() << "Unhandled response\n";
+ };
+
+ {
+ std::lock_guard<std::mutex> locker(call_mutex);
+ auto inflight = inflight_reverse_requests.find(id);
+ if (inflight != inflight_reverse_requests.end()) {
+ response_handler = std::move(inflight->second);
+ inflight_reverse_requests.erase(inflight);
+ }
+ }
+
+ // Result should be given, use null if not.
+ if (GetBoolean(object, "success", false)) {
+ llvm::json::Value Result = nullptr;
+ if (auto *B = object.get("body")) {
+ Result = std::move(*B);
+ }
+ response_handler(Result);
+ } else {
+ llvm::StringRef message = GetString(object, "message");
+ if (message.empty()) {
+ message = "Unknown error, response failed";
+ }
+ response_handler(llvm::createStringError(
+ std::error_code(-1, std::generic_category()), message));
+ }
+
+ return true;
+ }
+
return false;
}
-PacketStatus VSCode::SendReverseRequest(llvm::json::Object request,
- llvm::json::Object &response) {
- request.try_emplace("seq", ++reverse_request_seq);
- SendJSON(llvm::json::Value(std::move(request)));
- while (true) {
- PacketStatus status = GetNextObject(response);
- const auto packet_type = GetString(response, "type");
- if (packet_type == "response")
- return status;
- else {
- // Not our response, we got another packet
- HandleObject(response);
+llvm::Error VSCode::Loop() {
+ while (!sent_terminated_event) {
+ llvm::json::Object object;
+ lldb_vscode::PacketStatus status = GetNextObject(object);
+
+ if (status == lldb_vscode::PacketStatus::EndOfFile) {
+ break;
+ }
+
+ if (status != lldb_vscode::PacketStatus::Success) {
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "failed to send packet");
+ }
+
+ if (!HandleObject(object)) {
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "unhandled packet");
}
}
- return PacketStatus::EndOfFile;
+
+ return llvm::Error::success();
+}
+
+void VSCode::SendReverseRequest(llvm::StringRef command,
+ llvm::json::Value arguments,
+ ResponseCallback callback) {
+ int64_t id;
+ {
+ std::lock_guard<std::mutex> locker(call_mutex);
+ id = ++reverse_request_seq;
+ inflight_reverse_requests.emplace(id, std::move(callback));
+ }
+
+ SendJSON(llvm::json::Object{
+ {"type", "request"},
+ {"seq", id},
+ {"command", command},
+ {"arguments", std::move(arguments)},
+ });
}
void VSCode::RegisterRequestCallback(std::string request,
@@ -610,4 +669,63 @@ int64_t Variables::InsertExpandableVariable(lldb::SBValue variable,
return var_ref;
}
+bool StartDebuggingRequestHandler::DoExecute(
+ lldb::SBDebugger debugger, char **command,
+ lldb::SBCommandReturnObject &result) {
+ // Command format like: `startDebugging <launch|attach> <configuration>`
+ if (!command) {
+ result.SetError("Invalid use of startDebugging");
+ result.SetStatus(lldb::eReturnStatusFailed);
+ return false;
+ }
+
+ if (!command[0] || llvm::StringRef(command[0]).empty()) {
+ result.SetError("startDebugging request type missing.");
+ result.SetStatus(lldb::eReturnStatusFailed);
+ return false;
+ }
+
+ if (!command[1] || llvm::StringRef(command[1]).empty()) {
+ result.SetError("configuration missing.");
+ result.SetStatus(lldb::eReturnStatusFailed);
+ return false;
+ }
+
+ llvm::StringRef request{command[0]};
+ std::string raw_configuration{command[1]};
+
+ int i = 2;
+ while (command[i]) {
+ raw_configuration.append(" ").append(command[i]);
+ }
+
+ llvm::Expected<llvm::json::Value> configuration =
+ llvm::json::parse(raw_configuration);
+
+ if (!configuration) {
+ llvm::Error err = configuration.takeError();
+ std::string msg =
+ "Failed to parse json configuration: " + llvm::toString(std::move(err));
+ result.SetError(msg.c_str());
+ result.SetStatus(lldb::eReturnStatusFailed);
+ return false;
+ }
+
+ g_vsc.SendReverseRequest(
+ "startDebugging",
+ llvm::json::Object{{"request", request},
+ {"configuration", std::move(*configuration)}},
+ [](llvm::Expected<llvm::json::Value> value) {
+ if (!value) {
+ llvm::Error err = value.takeError();
+ llvm::errs() << "reverse start debugging request failed: "
+ << llvm::toString(std::move(err)) << "\n";
+ }
+ });
+
+ result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult);
+
+ return true;
+}
+
} // namespace lldb_vscode
diff --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h
index 48bef18de2a23f..2d67da2bbc092e 100644
--- a/lldb/tools/lldb-vscode/VSCode.h
+++ b/lldb/tools/lldb-vscode/VSCode.h
@@ -11,8 +11,10 @@
#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX
+#include <atomic>
#include <condition_variable>
#include <cstdio>
+#include <future>
#include <iosfwd>
#include <map>
#include <optional>
@@ -73,6 +75,7 @@ enum VSCodeBroadcasterBits {
};
typedef void (*RequestCallback)(const llvm::json::Object &command);
+typedef void (*ResponseCallback)(llvm::Expected<llvm::json::Value> value);
enum class PacketStatus {
Success = 0,
@@ -121,6 +124,11 @@ struct Variables {
void Clear();
};
+struct StartDebuggingRequestHandler : public lldb::SBCommandPluginInterface {
+ bool DoExecute(lldb::SBDebugger debugger, char **command,
+ lldb::SBCommandReturnObject &result) override;
+};
+
struct VSCode {
std::string debug_adaptor_path;
InputStream input;
@@ -146,7 +154,7 @@ struct VSCode {
// arguments if we get a RestartRequest.
std::optional<llvm::json::Object> last_launch_or_attach_request;
lldb::tid_t focus_tid;
- bool sent_terminated_event;
+ std::atomic<bool> sent_terminated_event;
bool stop_at_entry;
bool is_attach;
// The process event thread normally responds to process exited events by
@@ -154,13 +162,18 @@ struct VSCode {
// the old process here so we can detect this case and keep running.
lldb::pid_t restarting_process_id;
bool configuration_done_sent;
- uint32_t reverse_request_seq;
std::map<std::string, RequestCallback> request_handlers;
bool waiting_for_run_in_terminal;
ProgressEventReporter progress_event_reporter;
// Keep track of the last stop thread index IDs as threads won't go away
// unless we send a "thread" event to indicate the thread exited.
llvm::DenseSet<lldb::tid_t> thread_ids;
+ uint32_t reverse_request_seq;
+ std::mutex call_mutex;
+ std::map<int /* request_seq */, ResponseCallback /* reply handler */>
+ inflight_reverse_requests;
+ StartDebuggingRequestHandler start_debugging_request_handler;
+
VSCode();
~VSCode();
VSCode(const VSCode &rhs) = delete;
@@ -224,19 +237,20 @@ struct VSCode {
PacketStatus GetNextObject(llvm::json::Object &object);
bool HandleObject(const llvm::json::Object &object);
- /// Send a Debug Adapter Protocol reverse request to the IDE
+ llvm::Error Loop();
+
+ /// Send a Debug Adapter Protocol reverse request to the IDE.
///
- /// \param[in] request
- /// The payload of the request to send.
+ /// \param[in] command
+ /// The reverse request command.
///
- /// \param[out] response
- /// The response of the IDE. It might be undefined if there was an error.
+ /// \param[in] arguments
+ /// The reverse request arguements.
///
- /// \return
- /// A \a PacketStatus object indicating the sucess or failure of the
- /// request.
- PacketStatus SendReverseRequest(llvm::json::Object request,
- llvm::json::Object &response);
+ /// \param[in] callback
+ /// A callback to execute when the response arrives.
+ void SendReverseRequest(llvm::StringRef command, llvm::json::Value arguments,
+ ResponseCallback callback);
/// Registers a callback handler for a Debug Adapter Protocol request
///
diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp
index 126719e1494e5f..b9757d166958bf 100644
--- a/lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -1471,6 +1471,13 @@ void request_initialize(const llvm::json::Object &request) {
g_vsc.debugger =
lldb::SBDebugger::Create(source_init_file, log_cb, nullptr);
+ auto cmd = g_vsc.debugger.GetCommandInterpreter().AddMultiwordCommand(
+ "lldb-vscode", nullptr);
+ cmd.AddCommand(
+ "startDebugging", &g_vsc.start_debugging_request_handler,
+ "Sends a startDebugging request from the debug adapter to the client to "
+ "start a child debug session of the same type as the caller.");
+
g_vsc.progress_event_thread = std::thread(ProgressEventThreadFunction);
// Start our event thread so we can receive events from the debugger, target,
@@ -1564,7 +1571,8 @@ void request_initialize(const llvm::json::Object &request) {
g_vsc.SendJSON(llvm::json::Value(std::move(response)));
}
-llvm::Error request_runInTerminal(const llvm::json::Object &launch_request) {
+llvm::Error request_runInTerminal(const llvm::json::Object &launch_request,
+ const uint64_t timeout_seconds) {
g_vsc.is_attach = true;
lldb::SBAttachInfo attach_info;
@@ -1582,13 +1590,15 @@ llvm::Error request_runInTerminal(const llvm::json::Object &launch_request) {
#endif
llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest(
launch_request, g_vsc.debug_adaptor_path, comm_file.m_path, debugger_pid);
- llvm::json::Object reverse_response;
- lldb_vscode::PacketStatus status =
- g_vsc.SendReverseRequest(reverse_request, reverse_response);
- if (status != lldb_vscode::PacketStatus::Success)
- return llvm::createStringError(llvm::inconvertibleErrorCode(),
- "Process cannot be launched by the IDE. %s",
- comm_channel.GetLauncherError().c_str());
+ g_vsc.SendReverseRequest("runInTerminal", std::move(reverse_request),
+ [](llvm::Expected<llvm::json::Value> value) {
+ if (!value) {
+ llvm::Error err = value.takeError();
+ llvm::errs()
+ << "runInTerminal request failed: "
+ << llvm::toString(std::move(err)) << "\n";
+ }
+ });
if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid())
attach_info.SetProcessID(*pid);
@@ -1676,7 +1686,7 @@ lldb::SBError LaunchProcess(const llvm::json::Object &request) {
const uint64_t timeout_seconds = GetUnsigned(arguments, "timeout", 30);
if (GetBoolean(arguments, "runInTerminal", false)) {
- if (llvm::Error err = request_runInTerminal(request))
+ if (llvm::Error err = request_runInTerminal(request, timeout_seconds))
error.SetErrorString(llvm::toString(std::move(err)).c_str());
} else if (launchCommands.empty()) {
// Disable async events so the launch will be successful when we return from
@@ -3464,17 +3474,13 @@ int main(int argc, char *argv[]) {
g_vsc.output.descriptor = StreamDescriptor::from_file(new_stdout_fd, false);
}
- while (!g_vsc.sent_terminated_event) {
- llvm::json::Object object;
- lldb_vscode::PacketStatus status = g_vsc.GetNextObject(object);
- if (status == lldb_vscode::PacketStatus::EndOfFile)
- break;
- if (status != lldb_vscode::PacketStatus::Success)
- return 1; // Fatal error
-
- if (!g_vsc.HandleObject(object))
- return 1;
+ bool CleanExit = true;
+ if (auto Err = g_vsc.Loop()) {
+ if (g_vsc.log)
+ *g_vsc.log << "Transport Error: " << llvm::toString(std::move(Err))
+ << "\n";
+ CleanExit = false;
}
- return EXIT_SUCCESS;
+ return CleanExit ? EXIT_SUCCESS : EXIT_FAILURE;
}
More information about the lldb-commits
mailing list