ashgti created this revision.
Herald added a project: All.
ashgti updated this revision to Diff 533349.
ashgti added a comment.
ashgti published this revision for review.
ashgti added reviewers: wallace, ivanhernandez13.
ashgti added a project: LLDB.
Herald added subscribers: lldb-commits, JDevlieghere.
Corrected a makefile reference.
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.
Repository:
rG LLVM Github Monorepo
https://reviews.llvm.org/D153447
Files:
lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
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
lldb/tools/lldb-vscode/JSONUtils.cpp
lldb/tools/lldb-vscode/VSCode.cpp
lldb/tools/lldb-vscode/VSCode.h
lldb/tools/lldb-vscode/lldb-vscode.cpp
Index: lldb/tools/lldb-vscode/lldb-vscode.cpp
===================================================================
--- lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -1471,6 +1471,13 @@
g_vsc.debugger =
lldb::SBDebugger::Create(source_init_file, log_cb, nullptr);
+ auto cmd = g_vsc.debugger.GetCommandInterpreter().AddMultiwordCommand(
+ "dap", nullptr);
+ cmd.AddCommand(
+ "startDebugging", &g_vsc.start_debugging_request_handler,
+ "Sends a startDebugging from the debug adapter to the client to start "
+ "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 @@
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,10 +1590,10 @@
#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)
+ auto sent =
+ g_vsc.SendReverseRequest("runInTerminal", std::move(reverse_request));
+ if (std::future_status::ready !=
+ sent.wait_for(std::chrono::seconds(timeout_seconds)))
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"Process cannot be launched by the IDE. %s",
comm_channel.GetLauncherError().c_str());
@@ -1676,7 +1684,7 @@
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 +3472,13 @@
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;
}
Index: lldb/tools/lldb-vscode/VSCode.h
===================================================================
--- lldb/tools/lldb-vscode/VSCode.h
+++ 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>
@@ -121,6 +123,11 @@
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 +153,7 @@
// 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 +161,20 @@
// 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<
+ /* request_seq */ int,
+ /* response promise */ std::promise<llvm::Expected<llvm::json::Value>>>
+ inflight_reverse_requests;
+ StartDebuggingRequestHandler start_debugging_request_handler;
+
VSCode();
~VSCode();
VSCode(const VSCode &rhs) = delete;
@@ -224,19 +238,21 @@
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);
+ /// A future that resolves to the response object indicating the sucess or
+ /// failure of the request.
+ std::future<llvm::Expected<llvm::json::Value>>
+ SendReverseRequest(llvm::StringRef command, llvm::json::Value arguments);
/// Registers a callback handler for a Debug Adapter Protocol request
///
Index: lldb/tools/lldb-vscode/VSCode.cpp
===================================================================
--- lldb/tools/lldb-vscode/VSCode.cpp
+++ lldb/tools/lldb-vscode/VSCode.cpp
@@ -41,10 +41,10 @@
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,91 @@
return false; // Fail
}
}
+
+ if (packet_type == "response") {
+ auto id = GetSigned(object, "request_seq", 0);
+ std::promise<llvm::Expected<llvm::json::Value>> response_promise;
+
+ {
+ std::lock_guard<std::mutex> locker(call_mutex);
+ if (inflight_reverse_requests.find(id) !=
+ inflight_reverse_requests.end()) {
+ response_promise =
+ std::move(inflight_reverse_requests.find(id)->second);
+ } else {
+ if (log) {
+ *log << "Unhandled reply " << id << std::endl;
+ }
+ return true;
+ }
+ }
+
+ // 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_promise.set_value(std::move(Result));
+ } else {
+ llvm::StringRef message = GetString(object, "message");
+ if (message.empty()) {
+ message = "Unknown error, response failed";
+ }
+ response_promise.set_value(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();
+}
+
+std::future<llvm::Expected<llvm::json::Value>>
+VSCode::SendReverseRequest(llvm::StringRef command,
+ llvm::json::Value arguments) {
+ int64_t id;
+ std::future<llvm::Expected<llvm::json::Value>> response_future;
+ {
+ std::lock_guard<std::mutex> locker(call_mutex);
+ id = ++reverse_request_seq;
+ std::promise<llvm::Expected<llvm::json::Value>> response_promise;
+ response_future = response_promise.get_future();
+ inflight_reverse_requests.emplace(id, std::move(response_promise));
+ }
+
+ SendJSON(llvm::json::Object{
+ {"type", "request"},
+ {"seq", id},
+ {"command", command},
+ {"arguments", std::move(arguments)},
+ });
+
+ return response_future;
}
void VSCode::RegisterRequestCallback(std::string request,
@@ -610,4 +677,60 @@
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]};
+ llvm::StringRef raw_configuration{command[1]};
+
+ 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;
+ }
+
+ auto future = g_vsc.SendReverseRequest(
+ "startDebugging",
+ llvm::json::Object{{"request", request},
+ {"configuration", std::move(*configuration)}});
+
+ future.wait();
+ auto sent = future.get();
+ if (!sent) {
+ llvm::Error err = configuration.takeError();
+ std::string msg = "reverse start debugging request failed: " +
+ llvm::toString(std::move(err));
+ result.SetError(msg.c_str());
+ result.SetStatus(lldb::eReturnStatusFailed);
+ return false;
+ }
+
+ return true;
+}
+
} // namespace lldb_vscode
Index: lldb/tools/lldb-vscode/JSONUtils.cpp
===================================================================
--- lldb/tools/lldb-vscode/JSONUtils.cpp
+++ lldb/tools/lldb-vscode/JSONUtils.cpp
@@ -1104,10 +1104,6 @@
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 @@
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
Index: lldb/test/API/tools/lldb-vscode/startDebugging/main.c
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/startDebugging/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(int argc, char const *argv[]) {
+ printf("server/client example\n"); // breakpoint 1
+ return 0;
+}
Index: lldb/test/API/tools/lldb-vscode/startDebugging/TestVSCode_startDebugging.py
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/startDebugging/TestVSCode_startDebugging.py
@@ -0,0 +1,33 @@
+"""
+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
+import os
+import subprocess
+import json
+
+
+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,
+ # Ensure we can start child debug session.
+ postRunCommands='dap startDebugging launch {"program":"{}"}'.format(program)
+ )
+
+ breakpoint_line = line_number(source, "// breakpoint")
+
+ self.set_source_breakpoints(source, [breakpoint_line])
+ self.continue_to_next_stop()
\ No newline at end of file
Index: lldb/test/API/tools/lldb-vscode/startDebugging/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/startDebugging/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
Index: lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
+++ lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
@@ -340,6 +340,18 @@
},
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"' % (
response_or_request["command"]
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits