[Lldb-commits] [lldb] [lldb-dap] Move requests into their own object/file (PR #128262)
Jonas Devlieghere via lldb-commits
lldb-commits at lists.llvm.org
Sat Feb 22 18:30:05 PST 2025
https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/128262
>From e6cbcc3ab7a278c3ef01646db22872589486eb91 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Fri, 21 Feb 2025 19:20:36 -0600
Subject: [PATCH 01/11] [lldb-dap] Move requests into their own object/file
---
lldb/tools/lldb-dap/CMakeLists.txt | 5 +
lldb/tools/lldb-dap/DAP.cpp | 23 +-
lldb/tools/lldb-dap/DAP.h | 8 +
lldb/tools/lldb-dap/Request/AttachRequest.cpp | 211 ++++++++++++++++++
lldb/tools/lldb-dap/Request/Request.cpp | 92 ++++++++
lldb/tools/lldb-dap/Request/Request.h | 45 ++++
lldb/tools/lldb-dap/lldb-dap.cpp | 190 +---------------
7 files changed, 380 insertions(+), 194 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Request/AttachRequest.cpp
create mode 100644 lldb/tools/lldb-dap/Request/Request.cpp
create mode 100644 lldb/tools/lldb-dap/Request/Request.h
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index 43fc18873feb3..e97f7ecdbd31b 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -36,6 +36,9 @@ add_lldb_tool(lldb-dap
SourceBreakpoint.cpp
Watchpoint.cpp
+ Request/Request.cpp
+ Request/AttachRequest.cpp
+
LINK_LIBS
liblldb
lldbHost
@@ -46,6 +49,8 @@ add_lldb_tool(lldb-dap
Support
)
+target_include_directories(lldb-dap PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+
if(LLDB_DAP_WELCOME_MESSAGE)
target_compile_definitions(lldb-dap
PRIVATE
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index a67abe582abd4..80f083b1ab592 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -744,16 +744,25 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
const auto packet_type = GetString(object, "type");
if (packet_type == "request") {
const auto command = GetString(object, "command");
+
+ // Try the new request handler first.
+ auto new_handler_pos = new_request_handlers.find(command);
+ if (new_handler_pos != new_request_handlers.end()) {
+ (*new_handler_pos->second)(object);
+ return true; // Success
+ }
+
+ // FIXME: Remove request_handlers once everything has been migrated.
auto handler_pos = request_handlers.find(command);
- if (handler_pos == request_handlers.end()) {
- if (log)
- *log << "error: unhandled command \"" << command.data() << "\""
- << std::endl;
- return false; // Fail
+ if (handler_pos != request_handlers.end()) {
+ handler_pos->second(*this, object);
+ return true; // Success
}
- handler_pos->second(*this, object);
- return true; // Success
+ if (log)
+ *log << "error: unhandled command \"" << command.data() << "\""
+ << std::endl;
+ return false; // Fail
}
if (packet_type == "response") {
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index b23be68ea002f..33507061e0750 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -16,6 +16,7 @@
#include "InstructionBreakpoint.h"
#include "OutputRedirector.h"
#include "ProgressEvent.h"
+#include "Request/Request.h"
#include "SourceBreakpoint.h"
#include "lldb/API/SBBroadcaster.h"
#include "lldb/API/SBCommandInterpreter.h"
@@ -37,6 +38,7 @@
#include "llvm/Support/JSON.h"
#include "llvm/Support/Threading.h"
#include <map>
+#include <memory>
#include <mutex>
#include <optional>
#include <thread>
@@ -184,6 +186,7 @@ struct DAP {
lldb::pid_t restarting_process_id;
bool configuration_done_sent;
std::map<std::string, RequestCallback, std::less<>> request_handlers;
+ llvm::StringMap<std::unique_ptr<Request>> new_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
@@ -330,6 +333,11 @@ struct DAP {
/// IDE.
void RegisterRequestCallback(std::string request, RequestCallback callback);
+ /// Registers a request handler.
+ template <typename Request> void RegisterRequest() {
+ new_request_handlers[Request::getName()] = std::make_unique<Request>(*this);
+ }
+
/// Debuggee will continue from stopped state.
void WillContinue() { variables.Clear(); }
diff --git a/lldb/tools/lldb-dap/Request/AttachRequest.cpp b/lldb/tools/lldb-dap/Request/AttachRequest.cpp
new file mode 100644
index 0000000000000..f1b3bfc878427
--- /dev/null
+++ b/lldb/tools/lldb-dap/Request/AttachRequest.cpp
@@ -0,0 +1,211 @@
+//===-- AttachRequest.cpp -------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "JSONUtils.h"
+#include "Request.h"
+#include "lldb/API/SBListener.h"
+#include "llvm/Support/FileSystem.h"
+
+namespace lldb_dap {
+/// Prints a welcome message on the editor if the preprocessor variable
+/// LLDB_DAP_WELCOME_MESSAGE is defined.
+static void PrintWelcomeMessage(DAP &dap) {
+#ifdef LLDB_DAP_WELCOME_MESSAGE
+ dap.SendOutput(OutputType::Console, LLDB_DAP_WELCOME_MESSAGE);
+#endif
+}
+
+// "AttachRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Attach request; value of command field is 'attach'.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "attach" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/AttachRequestArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "AttachRequestArguments": {
+// "type": "object",
+// "description": "Arguments for 'attach' request.\nThe attach request has no
+// standardized attributes."
+// },
+// "AttachResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'attach' request. This is just an
+// acknowledgement, so no body field is required."
+// }]
+// }
+
+void AttachRequest::operator()(const llvm::json::Object &request) {
+ dap.is_attach = true;
+ dap.last_launch_or_attach_request = request;
+ llvm::json::Object response;
+ lldb::SBError error;
+ FillResponse(request, response);
+ lldb::SBAttachInfo attach_info;
+ const int invalid_port = 0;
+ const auto *arguments = request.getObject("arguments");
+ const lldb::pid_t pid =
+ GetUnsigned(arguments, "pid", LLDB_INVALID_PROCESS_ID);
+ const auto gdb_remote_port =
+ GetUnsigned(arguments, "gdb-remote-port", invalid_port);
+ const auto gdb_remote_hostname =
+ GetString(arguments, "gdb-remote-hostname", "localhost");
+ if (pid != LLDB_INVALID_PROCESS_ID)
+ attach_info.SetProcessID(pid);
+ const auto wait_for = GetBoolean(arguments, "waitFor", false);
+ attach_info.SetWaitForLaunch(wait_for, false /*async*/);
+ dap.init_commands = GetStrings(arguments, "initCommands");
+ dap.pre_run_commands = GetStrings(arguments, "preRunCommands");
+ dap.stop_commands = GetStrings(arguments, "stopCommands");
+ dap.exit_commands = GetStrings(arguments, "exitCommands");
+ dap.terminate_commands = GetStrings(arguments, "terminateCommands");
+ auto attachCommands = GetStrings(arguments, "attachCommands");
+ llvm::StringRef core_file = GetString(arguments, "coreFile");
+ const uint64_t timeout_seconds = GetUnsigned(arguments, "timeout", 30);
+ dap.stop_at_entry =
+ core_file.empty() ? GetBoolean(arguments, "stopOnEntry", false) : true;
+ dap.post_run_commands = GetStrings(arguments, "postRunCommands");
+ const llvm::StringRef debuggerRoot = GetString(arguments, "debuggerRoot");
+ dap.enable_auto_variable_summaries =
+ GetBoolean(arguments, "enableAutoVariableSummaries", false);
+ dap.enable_synthetic_child_debugging =
+ GetBoolean(arguments, "enableSyntheticChildDebugging", false);
+ dap.display_extended_backtrace =
+ GetBoolean(arguments, "displayExtendedBacktrace", false);
+ dap.command_escape_prefix = GetString(arguments, "commandEscapePrefix", "`");
+ dap.SetFrameFormat(GetString(arguments, "customFrameFormat"));
+ dap.SetThreadFormat(GetString(arguments, "customThreadFormat"));
+
+ PrintWelcomeMessage(dap);
+
+ // This is a hack for loading DWARF in .o files on Mac where the .o files
+ // in the debug map of the main executable have relative paths which require
+ // the lldb-dap binary to have its working directory set to that relative
+ // root for the .o files in order to be able to load debug info.
+ if (!debuggerRoot.empty())
+ llvm::sys::fs::set_current_path(debuggerRoot);
+
+ // Run any initialize LLDB commands the user specified in the launch.json
+ if (llvm::Error err = dap.RunInitCommands()) {
+ response["success"] = false;
+ EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+
+ SetSourceMapFromArguments(*arguments);
+
+ lldb::SBError status;
+ dap.SetTarget(dap.CreateTargetFromArguments(*arguments, status));
+ if (status.Fail()) {
+ response["success"] = llvm::json::Value(false);
+ EmplaceSafeString(response, "message", status.GetCString());
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+
+ // Run any pre run LLDB commands the user specified in the launch.json
+ if (llvm::Error err = dap.RunPreRunCommands()) {
+ response["success"] = false;
+ EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+
+ if ((pid == LLDB_INVALID_PROCESS_ID || gdb_remote_port == invalid_port) &&
+ wait_for) {
+ char attach_msg[256];
+ auto attach_msg_len = snprintf(attach_msg, sizeof(attach_msg),
+ "Waiting to attach to \"%s\"...",
+ dap.target.GetExecutable().GetFilename());
+ dap.SendOutput(OutputType::Console,
+ llvm::StringRef(attach_msg, attach_msg_len));
+ }
+ if (attachCommands.empty()) {
+ // No "attachCommands", just attach normally.
+ // Disable async events so the attach will be successful when we return from
+ // the launch call and the launch will happen synchronously
+ dap.debugger.SetAsync(false);
+ if (core_file.empty()) {
+ if ((pid != LLDB_INVALID_PROCESS_ID) &&
+ (gdb_remote_port != invalid_port)) {
+ // If both pid and port numbers are specified.
+ error.SetErrorString("The user can't specify both pid and port");
+ } else if (gdb_remote_port != invalid_port) {
+ // If port is specified and pid is not.
+ lldb::SBListener listener = dap.debugger.GetListener();
+
+ // If the user hasn't provided the hostname property, default localhost
+ // being used.
+ std::string connect_url =
+ llvm::formatv("connect://{0}:", gdb_remote_hostname);
+ connect_url += std::to_string(gdb_remote_port);
+ dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote",
+ error);
+ } else {
+ // Attach by process name or id.
+ dap.target.Attach(attach_info, error);
+ }
+ } else
+ dap.target.LoadCore(core_file.data(), error);
+ // Reenable async events
+ dap.debugger.SetAsync(true);
+ } else {
+ // We have "attachCommands" that are a set of commands that are expected
+ // to execute the commands after which a process should be created. If there
+ // is no valid process after running these commands, we have failed.
+ if (llvm::Error err = dap.RunAttachCommands(attachCommands)) {
+ response["success"] = false;
+ EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+ // The custom commands might have created a new target so we should use the
+ // selected target after these commands are run.
+ dap.target = dap.debugger.GetSelectedTarget();
+
+ // Make sure the process is attached and stopped before proceeding as the
+ // the launch commands are not run using the synchronous mode.
+ error = dap.WaitForProcessToStop(timeout_seconds);
+ }
+
+ if (error.Success() && core_file.empty()) {
+ auto attached_pid = dap.target.GetProcess().GetProcessID();
+ if (attached_pid == LLDB_INVALID_PROCESS_ID) {
+ if (attachCommands.empty())
+ error.SetErrorString("failed to attach to a process");
+ else
+ error.SetErrorString("attachCommands failed to attach to a process");
+ }
+ }
+
+ if (error.Fail()) {
+ response["success"] = llvm::json::Value(false);
+ EmplaceSafeString(response, "message", std::string(error.GetCString()));
+ } else {
+ dap.RunPostRunCommands();
+ }
+
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ if (error.Success()) {
+ SendProcessEvent(Attach);
+ dap.SendJSON(CreateEventObject("initialized"));
+ }
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Request/Request.cpp b/lldb/tools/lldb-dap/Request/Request.cpp
new file mode 100644
index 0000000000000..9fe2445be9d32
--- /dev/null
+++ b/lldb/tools/lldb-dap/Request/Request.cpp
@@ -0,0 +1,92 @@
+//===-- Request.cpp -------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Request.h"
+#include "DAP.h"
+#include "JSONUtils.h"
+#include "lldb/API/SBFileSpec.h"
+
+namespace lldb_dap {
+
+void Request::SendProcessEvent(Request::LaunchMethod launch_method) {
+ lldb::SBFileSpec exe_fspec = dap.target.GetExecutable();
+ char exe_path[PATH_MAX];
+ exe_fspec.GetPath(exe_path, sizeof(exe_path));
+ llvm::json::Object event(CreateEventObject("process"));
+ llvm::json::Object body;
+ EmplaceSafeString(body, "name", std::string(exe_path));
+ const auto pid = dap.target.GetProcess().GetProcessID();
+ body.try_emplace("systemProcessId", (int64_t)pid);
+ body.try_emplace("isLocalProcess", true);
+ const char *startMethod = nullptr;
+ switch (launch_method) {
+ case Launch:
+ startMethod = "launch";
+ break;
+ case Attach:
+ startMethod = "attach";
+ break;
+ case AttachForSuspendedLaunch:
+ startMethod = "attachForSuspendedLaunch";
+ break;
+ }
+ body.try_emplace("startMethod", startMethod);
+ event.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(event)));
+}
+
+// Both attach and launch take a either a sourcePath or sourceMap
+// argument (or neither), from which we need to set the target.source-map.
+void Request::SetSourceMapFromArguments(const llvm::json::Object &arguments) {
+ const char *sourceMapHelp =
+ "source must be be an array of two-element arrays, "
+ "each containing a source and replacement path string.\n";
+
+ std::string sourceMapCommand;
+ llvm::raw_string_ostream strm(sourceMapCommand);
+ strm << "settings set target.source-map ";
+ const auto sourcePath = GetString(arguments, "sourcePath");
+
+ // sourceMap is the new, more general form of sourcePath and overrides it.
+ constexpr llvm::StringRef sourceMapKey = "sourceMap";
+
+ if (const auto *sourceMapArray = arguments.getArray(sourceMapKey)) {
+ for (const auto &value : *sourceMapArray) {
+ const auto *mapping = value.getAsArray();
+ if (mapping == nullptr || mapping->size() != 2 ||
+ (*mapping)[0].kind() != llvm::json::Value::String ||
+ (*mapping)[1].kind() != llvm::json::Value::String) {
+ dap.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp));
+ return;
+ }
+ const auto mapFrom = GetAsString((*mapping)[0]);
+ const auto mapTo = GetAsString((*mapping)[1]);
+ strm << "\"" << mapFrom << "\" \"" << mapTo << "\" ";
+ }
+ } else if (const auto *sourceMapObj = arguments.getObject(sourceMapKey)) {
+ for (const auto &[key, value] : *sourceMapObj) {
+ if (value.kind() == llvm::json::Value::String) {
+ strm << "\"" << key.str() << "\" \"" << GetAsString(value) << "\" ";
+ }
+ }
+ } else {
+ if (ObjectContainsKey(arguments, sourceMapKey)) {
+ dap.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp));
+ return;
+ }
+ if (sourcePath.empty())
+ return;
+ // Do any source remapping needed before we create our targets
+ strm << "\".\" \"" << sourcePath << "\"";
+ }
+ if (!sourceMapCommand.empty()) {
+ dap.RunLLDBCommands("Setting source map:", {sourceMapCommand});
+ }
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Request/Request.h b/lldb/tools/lldb-dap/Request/Request.h
new file mode 100644
index 0000000000000..339c3b1fb323f
--- /dev/null
+++ b/lldb/tools/lldb-dap/Request/Request.h
@@ -0,0 +1,45 @@
+//===-- Request.h ---------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TOOLS_LLDB_DAP_REQUEST_REQUEST_H
+#define LLDB_TOOLS_LLDB_DAP_REQUEST_REQUEST_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/JSON.h"
+
+namespace lldb_dap {
+struct DAP;
+
+class Request {
+public:
+ Request(DAP &dap) : dap(dap) {}
+ virtual ~Request() = default;
+
+ virtual void operator()(const llvm::json::Object &request) = 0;
+
+ static llvm::StringLiteral getName() { return "invalid"; };
+
+ enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch };
+
+ void SendProcessEvent(LaunchMethod launch_method);
+ void SetSourceMapFromArguments(const llvm::json::Object &arguments);
+
+protected:
+ DAP &dap;
+};
+
+class AttachRequest : public Request {
+public:
+ using Request::Request;
+ static llvm::StringLiteral getName() { return "attach"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+} // namespace lldb_dap
+
+#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index e323990d8b6ed..997b654011b64 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -10,6 +10,7 @@
#include "FifoFiles.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
+#include "Request/Request.h"
#include "RunInTerminal.h"
#include "Watchpoint.h"
#include "lldb/API/SBDeclaration.h"
@@ -726,192 +727,6 @@ bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
return reached_end_of_stack;
}
-// "AttachRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Attach request; value of command field is 'attach'.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "attach" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/AttachRequestArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "AttachRequestArguments": {
-// "type": "object",
-// "description": "Arguments for 'attach' request.\nThe attach request has no
-// standardized attributes."
-// },
-// "AttachResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'attach' request. This is just an
-// acknowledgement, so no body field is required."
-// }]
-// }
-void request_attach(DAP &dap, const llvm::json::Object &request) {
- dap.is_attach = true;
- dap.last_launch_or_attach_request = request;
- llvm::json::Object response;
- lldb::SBError error;
- FillResponse(request, response);
- lldb::SBAttachInfo attach_info;
- const int invalid_port = 0;
- const auto *arguments = request.getObject("arguments");
- const lldb::pid_t pid =
- GetUnsigned(arguments, "pid", LLDB_INVALID_PROCESS_ID);
- const auto gdb_remote_port =
- GetUnsigned(arguments, "gdb-remote-port", invalid_port);
- const auto gdb_remote_hostname =
- GetString(arguments, "gdb-remote-hostname", "localhost");
- if (pid != LLDB_INVALID_PROCESS_ID)
- attach_info.SetProcessID(pid);
- const auto wait_for = GetBoolean(arguments, "waitFor", false);
- attach_info.SetWaitForLaunch(wait_for, false /*async*/);
- dap.init_commands = GetStrings(arguments, "initCommands");
- dap.pre_run_commands = GetStrings(arguments, "preRunCommands");
- dap.stop_commands = GetStrings(arguments, "stopCommands");
- dap.exit_commands = GetStrings(arguments, "exitCommands");
- dap.terminate_commands = GetStrings(arguments, "terminateCommands");
- auto attachCommands = GetStrings(arguments, "attachCommands");
- llvm::StringRef core_file = GetString(arguments, "coreFile");
- const uint64_t timeout_seconds = GetUnsigned(arguments, "timeout", 30);
- dap.stop_at_entry =
- core_file.empty() ? GetBoolean(arguments, "stopOnEntry", false) : true;
- dap.post_run_commands = GetStrings(arguments, "postRunCommands");
- const llvm::StringRef debuggerRoot = GetString(arguments, "debuggerRoot");
- dap.enable_auto_variable_summaries =
- GetBoolean(arguments, "enableAutoVariableSummaries", false);
- dap.enable_synthetic_child_debugging =
- GetBoolean(arguments, "enableSyntheticChildDebugging", false);
- dap.display_extended_backtrace =
- GetBoolean(arguments, "displayExtendedBacktrace", false);
- dap.command_escape_prefix = GetString(arguments, "commandEscapePrefix", "`");
- dap.SetFrameFormat(GetString(arguments, "customFrameFormat"));
- dap.SetThreadFormat(GetString(arguments, "customThreadFormat"));
-
- PrintWelcomeMessage(dap);
-
- // This is a hack for loading DWARF in .o files on Mac where the .o files
- // in the debug map of the main executable have relative paths which require
- // the lldb-dap binary to have its working directory set to that relative
- // root for the .o files in order to be able to load debug info.
- if (!debuggerRoot.empty())
- llvm::sys::fs::set_current_path(debuggerRoot);
-
- // Run any initialize LLDB commands the user specified in the launch.json
- if (llvm::Error err = dap.RunInitCommands()) {
- response["success"] = false;
- EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
-
- SetSourceMapFromArguments(dap, *arguments);
-
- lldb::SBError status;
- dap.SetTarget(dap.CreateTargetFromArguments(*arguments, status));
- if (status.Fail()) {
- response["success"] = llvm::json::Value(false);
- EmplaceSafeString(response, "message", status.GetCString());
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
-
- // Run any pre run LLDB commands the user specified in the launch.json
- if (llvm::Error err = dap.RunPreRunCommands()) {
- response["success"] = false;
- EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
-
- if ((pid == LLDB_INVALID_PROCESS_ID || gdb_remote_port == invalid_port) &&
- wait_for) {
- char attach_msg[256];
- auto attach_msg_len = snprintf(attach_msg, sizeof(attach_msg),
- "Waiting to attach to \"%s\"...",
- dap.target.GetExecutable().GetFilename());
- dap.SendOutput(OutputType::Console,
- llvm::StringRef(attach_msg, attach_msg_len));
- }
- if (attachCommands.empty()) {
- // No "attachCommands", just attach normally.
- // Disable async events so the attach will be successful when we return from
- // the launch call and the launch will happen synchronously
- dap.debugger.SetAsync(false);
- if (core_file.empty()) {
- if ((pid != LLDB_INVALID_PROCESS_ID) &&
- (gdb_remote_port != invalid_port)) {
- // If both pid and port numbers are specified.
- error.SetErrorString("The user can't specify both pid and port");
- } else if (gdb_remote_port != invalid_port) {
- // If port is specified and pid is not.
- lldb::SBListener listener = dap.debugger.GetListener();
-
- // If the user hasn't provided the hostname property, default localhost
- // being used.
- std::string connect_url =
- llvm::formatv("connect://{0}:", gdb_remote_hostname);
- connect_url += std::to_string(gdb_remote_port);
- dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote",
- error);
- } else {
- // Attach by process name or id.
- dap.target.Attach(attach_info, error);
- }
- } else
- dap.target.LoadCore(core_file.data(), error);
- // Reenable async events
- dap.debugger.SetAsync(true);
- } else {
- // We have "attachCommands" that are a set of commands that are expected
- // to execute the commands after which a process should be created. If there
- // is no valid process after running these commands, we have failed.
- if (llvm::Error err = dap.RunAttachCommands(attachCommands)) {
- response["success"] = false;
- EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
- // The custom commands might have created a new target so we should use the
- // selected target after these commands are run.
- dap.target = dap.debugger.GetSelectedTarget();
-
- // Make sure the process is attached and stopped before proceeding as the
- // the launch commands are not run using the synchronous mode.
- error = dap.WaitForProcessToStop(timeout_seconds);
- }
-
- if (error.Success() && core_file.empty()) {
- auto attached_pid = dap.target.GetProcess().GetProcessID();
- if (attached_pid == LLDB_INVALID_PROCESS_ID) {
- if (attachCommands.empty())
- error.SetErrorString("failed to attach to a process");
- else
- error.SetErrorString("attachCommands failed to attach to a process");
- }
- }
-
- if (error.Fail()) {
- response["success"] = llvm::json::Value(false);
- EmplaceSafeString(response, "message", std::string(error.GetCString()));
- } else {
- dap.RunPostRunCommands();
- }
-
- dap.SendJSON(llvm::json::Value(std::move(response)));
- if (error.Success()) {
- SendProcessEvent(dap, Attach);
- dap.SendJSON(CreateEventObject("initialized"));
- }
-}
-
// "BreakpointLocationsRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@@ -4998,7 +4813,8 @@ void request_setInstructionBreakpoints(DAP &dap,
}
void RegisterRequestCallbacks(DAP &dap) {
- dap.RegisterRequestCallback("attach", request_attach);
+ dap.RegisterRequest<AttachRequest>();
+
dap.RegisterRequestCallback("breakpointLocations",
request_breakpointLocations);
dap.RegisterRequestCallback("completions", request_completions);
>From 92322aee828df65ae4ca7523d17c60162e3802b3 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sat, 22 Feb 2025 12:10:00 -0600
Subject: [PATCH 02/11] Address review feedback
---
lldb/tools/lldb-dap/CMakeLists.txt | 4 ++--
lldb/tools/lldb-dap/DAP.h | 9 ++++----
.../AttachHandler.cpp} | 6 ++---
.../RequestHandler.cpp} | 10 ++++----
.../Request.h => Handler/RequestHandler.h} | 23 ++++++++++---------
lldb/tools/lldb-dap/lldb-dap.cpp | 4 ++--
6 files changed, 30 insertions(+), 26 deletions(-)
rename lldb/tools/lldb-dap/{Request/AttachRequest.cpp => Handler/AttachHandler.cpp} (98%)
rename lldb/tools/lldb-dap/{Request/Request.cpp => Handler/RequestHandler.cpp} (92%)
rename lldb/tools/lldb-dap/{Request/Request.h => Handler/RequestHandler.h} (65%)
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index e97f7ecdbd31b..a3c605bf72391 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -36,8 +36,8 @@ add_lldb_tool(lldb-dap
SourceBreakpoint.cpp
Watchpoint.cpp
- Request/Request.cpp
- Request/AttachRequest.cpp
+ Handler/RequestHandler.cpp
+ Handler/AttachHandler.cpp
LINK_LIBS
liblldb
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 33507061e0750..5abd48d6363cf 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -12,11 +12,11 @@
#include "DAPForward.h"
#include "ExceptionBreakpoint.h"
#include "FunctionBreakpoint.h"
+#include "Handler/RequestHandler.h"
#include "IOStream.h"
#include "InstructionBreakpoint.h"
#include "OutputRedirector.h"
#include "ProgressEvent.h"
-#include "Request/Request.h"
#include "SourceBreakpoint.h"
#include "lldb/API/SBBroadcaster.h"
#include "lldb/API/SBCommandInterpreter.h"
@@ -186,7 +186,7 @@ struct DAP {
lldb::pid_t restarting_process_id;
bool configuration_done_sent;
std::map<std::string, RequestCallback, std::less<>> request_handlers;
- llvm::StringMap<std::unique_ptr<Request>> new_request_handlers;
+ llvm::StringMap<std::unique_ptr<RequestHandler>> new_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
@@ -334,8 +334,9 @@ struct DAP {
void RegisterRequestCallback(std::string request, RequestCallback callback);
/// Registers a request handler.
- template <typename Request> void RegisterRequest() {
- new_request_handlers[Request::getName()] = std::make_unique<Request>(*this);
+ template <typename Handler> void RegisterRequest() {
+ new_request_handlers[Handler::getCommand()] =
+ std::make_unique<Handler>(*this);
}
/// Debuggee will continue from stopped state.
diff --git a/lldb/tools/lldb-dap/Request/AttachRequest.cpp b/lldb/tools/lldb-dap/Handler/AttachHandler.cpp
similarity index 98%
rename from lldb/tools/lldb-dap/Request/AttachRequest.cpp
rename to lldb/tools/lldb-dap/Handler/AttachHandler.cpp
index f1b3bfc878427..3dcde2144246e 100644
--- a/lldb/tools/lldb-dap/Request/AttachRequest.cpp
+++ b/lldb/tools/lldb-dap/Handler/AttachHandler.cpp
@@ -1,4 +1,4 @@
-//===-- AttachRequest.cpp -------------------------------------------------===//
+//===-- AttachHandler.cpp -------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -8,7 +8,7 @@
#include "DAP.h"
#include "JSONUtils.h"
-#include "Request.h"
+#include "RequestHandler.h"
#include "lldb/API/SBListener.h"
#include "llvm/Support/FileSystem.h"
@@ -50,7 +50,7 @@ static void PrintWelcomeMessage(DAP &dap) {
// }]
// }
-void AttachRequest::operator()(const llvm::json::Object &request) {
+void AttachRequestHandler::operator()(const llvm::json::Object &request) {
dap.is_attach = true;
dap.last_launch_or_attach_request = request;
llvm::json::Object response;
diff --git a/lldb/tools/lldb-dap/Request/Request.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
similarity index 92%
rename from lldb/tools/lldb-dap/Request/Request.cpp
rename to lldb/tools/lldb-dap/Handler/RequestHandler.cpp
index 9fe2445be9d32..ef091a805902d 100644
--- a/lldb/tools/lldb-dap/Request/Request.cpp
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
@@ -1,4 +1,4 @@
-//===-- Request.cpp -------------------------------------------------------===//
+//===-- Handler.cpp -------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -6,14 +6,15 @@
//
//===----------------------------------------------------------------------===//
-#include "Request.h"
+#include "RequestHandler.h"
#include "DAP.h"
#include "JSONUtils.h"
#include "lldb/API/SBFileSpec.h"
namespace lldb_dap {
-void Request::SendProcessEvent(Request::LaunchMethod launch_method) {
+void RequestHandler::SendProcessEvent(
+ RequestHandler::LaunchMethod launch_method) {
lldb::SBFileSpec exe_fspec = dap.target.GetExecutable();
char exe_path[PATH_MAX];
exe_fspec.GetPath(exe_path, sizeof(exe_path));
@@ -42,7 +43,8 @@ void Request::SendProcessEvent(Request::LaunchMethod launch_method) {
// Both attach and launch take a either a sourcePath or sourceMap
// argument (or neither), from which we need to set the target.source-map.
-void Request::SetSourceMapFromArguments(const llvm::json::Object &arguments) {
+void RequestHandler::SetSourceMapFromArguments(
+ const llvm::json::Object &arguments) {
const char *sourceMapHelp =
"source must be be an array of two-element arrays, "
"each containing a source and replacement path string.\n";
diff --git a/lldb/tools/lldb-dap/Request/Request.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
similarity index 65%
rename from lldb/tools/lldb-dap/Request/Request.h
rename to lldb/tools/lldb-dap/Handler/RequestHandler.h
index 339c3b1fb323f..4a60a7f3d1142 100644
--- a/lldb/tools/lldb-dap/Request/Request.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -6,8 +6,8 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLDB_TOOLS_LLDB_DAP_REQUEST_REQUEST_H
-#define LLDB_TOOLS_LLDB_DAP_REQUEST_REQUEST_H
+#ifndef LLDB_TOOLS_LLDB_DAP_HANDLER_HANDLER_H
+#define LLDB_TOOLS_LLDB_DAP_HANDLER_HANDLER_H
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/JSON.h"
@@ -15,28 +15,29 @@
namespace lldb_dap {
struct DAP;
-class Request {
+class RequestHandler {
public:
- Request(DAP &dap) : dap(dap) {}
- virtual ~Request() = default;
+ RequestHandler(DAP &dap) : dap(dap) {}
+ virtual ~RequestHandler() = default;
virtual void operator()(const llvm::json::Object &request) = 0;
- static llvm::StringLiteral getName() { return "invalid"; };
-
+ /// Helpers used by multiple request handlers.
+ /// FIXME: Move these into the DAP class.
+ /// @{
enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch };
-
void SendProcessEvent(LaunchMethod launch_method);
void SetSourceMapFromArguments(const llvm::json::Object &arguments);
+ /// @}
protected:
DAP &dap;
};
-class AttachRequest : public Request {
+class AttachRequestHandler : public RequestHandler {
public:
- using Request::Request;
- static llvm::StringLiteral getName() { return "attach"; }
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "attach"; }
void operator()(const llvm::json::Object &request) override;
};
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 997b654011b64..c84eb5d7c7419 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -8,9 +8,9 @@
#include "DAP.h"
#include "FifoFiles.h"
+#include "Handler/RequestHandler.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
-#include "Request/Request.h"
#include "RunInTerminal.h"
#include "Watchpoint.h"
#include "lldb/API/SBDeclaration.h"
@@ -4813,7 +4813,7 @@ void request_setInstructionBreakpoints(DAP &dap,
}
void RegisterRequestCallbacks(DAP &dap) {
- dap.RegisterRequest<AttachRequest>();
+ dap.RegisterRequest<AttachRequestHandler>();
dap.RegisterRequestCallback("breakpointLocations",
request_breakpointLocations);
>From 86302e50898db397d8ed731921e0cab861c5f648 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sat, 22 Feb 2025 13:54:22 -0600
Subject: [PATCH 03/11] Address review feedback
---
lldb/tools/lldb-dap/Handler/RequestHandler.h | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 4a60a7f3d1142..94acc1241243a 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -18,6 +18,13 @@ struct DAP;
class RequestHandler {
public:
RequestHandler(DAP &dap) : dap(dap) {}
+
+ /// RequestHandler are not copyable.
+ /// @{
+ RequestHandler(const RequestHandler &) = delete;
+ RequestHandler &operator=(const RequestHandler &) = delete;
+ /// @}
+
virtual ~RequestHandler() = default;
virtual void operator()(const llvm::json::Object &request) = 0;
>From 9235950649593bdd47a32b1966fefe83d985e478 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sat, 22 Feb 2025 19:21:57 -0600
Subject: [PATCH 04/11] Adopt BreakpointLocationsHandler
---
lldb/tools/lldb-dap/CMakeLists.txt | 3 +-
.../Handler/BreakpointLocationsHandler.cpp | 206 ++++++++++++++++++
lldb/tools/lldb-dap/Handler/RequestHandler.h | 7 +
lldb/tools/lldb-dap/lldb-dap.cpp | 193 +---------------
4 files changed, 216 insertions(+), 193 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Handler/BreakpointLocationsHandler.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index a3c605bf72391..b0208578e0566 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -36,8 +36,9 @@ add_lldb_tool(lldb-dap
SourceBreakpoint.cpp
Watchpoint.cpp
- Handler/RequestHandler.cpp
Handler/AttachHandler.cpp
+ Handler/BreakpointLocationsHandler.cpp
+ Handler/RequestHandler.cpp
LINK_LIBS
liblldb
diff --git a/lldb/tools/lldb-dap/Handler/BreakpointLocationsHandler.cpp b/lldb/tools/lldb-dap/Handler/BreakpointLocationsHandler.cpp
new file mode 100644
index 0000000000000..a324e59999ab1
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/BreakpointLocationsHandler.cpp
@@ -0,0 +1,206 @@
+//===-- BreakpointLocationsHandler..cpp -----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "BreakpointLocationsRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "The `breakpointLocations` request returns all possible
+// locations for source breakpoints in a given range.\nClients should only
+// call this request if the corresponding capability
+// `supportsBreakpointLocationsRequest` is true.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "breakpointLocations" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/BreakpointLocationsArguments"
+// }
+// },
+// "required": [ "command" ]
+// }]
+// },
+// "BreakpointLocationsArguments": {
+// "type": "object",
+// "description": "Arguments for `breakpointLocations` request.",
+// "properties": {
+// "source": {
+// "$ref": "#/definitions/Source",
+// "description": "The source location of the breakpoints; either
+// `source.path` or `source.sourceReference` must be specified."
+// },
+// "line": {
+// "type": "integer",
+// "description": "Start line of range to search possible breakpoint
+// locations in. If only the line is specified, the request returns all
+// possible locations in that line."
+// },
+// "column": {
+// "type": "integer",
+// "description": "Start position within `line` to search possible
+// breakpoint locations in. It is measured in UTF-16 code units and the
+// client capability `columnsStartAt1` determines whether it is 0- or
+// 1-based. If no column is given, the first position in the start line is
+// assumed."
+// },
+// "endLine": {
+// "type": "integer",
+// "description": "End line of range to search possible breakpoint
+// locations in. If no end line is given, then the end line is assumed to
+// be the start line."
+// },
+// "endColumn": {
+// "type": "integer",
+// "description": "End position within `endLine` to search possible
+// breakpoint locations in. It is measured in UTF-16 code units and the
+// client capability `columnsStartAt1` determines whether it is 0- or
+// 1-based. If no end column is given, the last position in the end line
+// is assumed."
+// }
+// },
+// "required": [ "source", "line" ]
+// },
+// "BreakpointLocationsResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to `breakpointLocations` request.\nContains
+// possible locations for source breakpoints.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "breakpoints": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/BreakpointLocation"
+// },
+// "description": "Sorted set of possible breakpoint locations."
+// }
+// },
+// "required": [ "breakpoints" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// },
+// "BreakpointLocation": {
+// "type": "object",
+// "description": "Properties of a breakpoint location returned from the
+// `breakpointLocations` request.",
+// "properties": {
+// "line": {
+// "type": "integer",
+// "description": "Start line of breakpoint location."
+// },
+// "column": {
+// "type": "integer",
+// "description": "The start position of a breakpoint location. Position
+// is measured in UTF-16 code units and the client capability
+// `columnsStartAt1` determines whether it is 0- or 1-based."
+// },
+// "endLine": {
+// "type": "integer",
+// "description": "The end line of breakpoint location if the location
+// covers a range."
+// },
+// "endColumn": {
+// "type": "integer",
+// "description": "The end position of a breakpoint location (if the
+// location covers a range). Position is measured in UTF-16 code units and
+// the client capability `columnsStartAt1` determines whether it is 0- or
+// 1-based."
+// }
+// },
+// "required": [ "line" ]
+// },
+void BreakpointLocationsRequestHandler::operator()(
+ const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ auto *arguments = request.getObject("arguments");
+ auto *source = arguments->getObject("source");
+ std::string path = GetString(source, "path").str();
+ uint64_t start_line = GetUnsigned(arguments, "line", 0);
+ uint64_t start_column = GetUnsigned(arguments, "column", 0);
+ uint64_t end_line = GetUnsigned(arguments, "endLine", start_line);
+ uint64_t end_column =
+ GetUnsigned(arguments, "endColumn", std::numeric_limits<uint64_t>::max());
+
+ lldb::SBFileSpec file_spec(path.c_str(), true);
+ lldb::SBSymbolContextList compile_units =
+ dap.target.FindCompileUnits(file_spec);
+
+ // Find all relevant lines & columns
+ llvm::SmallVector<std::pair<uint32_t, uint32_t>, 8> locations;
+ for (uint32_t c_idx = 0, c_limit = compile_units.GetSize(); c_idx < c_limit;
+ ++c_idx) {
+ const lldb::SBCompileUnit &compile_unit =
+ compile_units.GetContextAtIndex(c_idx).GetCompileUnit();
+ if (!compile_unit.IsValid())
+ continue;
+ lldb::SBFileSpec primary_file_spec = compile_unit.GetFileSpec();
+
+ // Go through the line table and find all matching lines / columns
+ for (uint32_t l_idx = 0, l_limit = compile_unit.GetNumLineEntries();
+ l_idx < l_limit; ++l_idx) {
+ lldb::SBLineEntry line_entry = compile_unit.GetLineEntryAtIndex(l_idx);
+
+ // Filter by line / column
+ uint32_t line = line_entry.GetLine();
+ if (line < start_line || line > end_line)
+ continue;
+ uint32_t column = line_entry.GetColumn();
+ if (column == LLDB_INVALID_COLUMN_NUMBER)
+ continue;
+ if (line == start_line && column < start_column)
+ continue;
+ if (line == end_line && column > end_column)
+ continue;
+
+ // Make sure we are in the right file.
+ // We might have a match on line & column range and still
+ // be in the wrong file, e.g. for included files.
+ // Given that the involved pointers point into LLDB's string pool,
+ // we can directly compare the `const char*` pointers.
+ if (line_entry.GetFileSpec().GetFilename() !=
+ primary_file_spec.GetFilename() ||
+ line_entry.GetFileSpec().GetDirectory() !=
+ primary_file_spec.GetDirectory())
+ continue;
+
+ locations.emplace_back(line, column);
+ }
+ }
+
+ // The line entries are sorted by addresses, but we must return the list
+ // ordered by line / column position.
+ std::sort(locations.begin(), locations.end());
+ locations.erase(std::unique(locations.begin(), locations.end()),
+ locations.end());
+
+ llvm::json::Array locations_json;
+ for (auto &l : locations) {
+ llvm::json::Object location;
+ location.try_emplace("line", l.first);
+ location.try_emplace("column", l.second);
+ locations_json.emplace_back(std::move(location));
+ }
+
+ llvm::json::Object body;
+ body.try_emplace("breakpoints", std::move(locations_json));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 94acc1241243a..854fb88c36b86 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -48,6 +48,13 @@ class AttachRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};
+class BreakpointLocationsRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "breakpointLocations"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index c84eb5d7c7419..fe91f38609ff0 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -727,196 +727,6 @@ bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
return reached_end_of_stack;
}
-// "BreakpointLocationsRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "The `breakpointLocations` request returns all possible
-// locations for source breakpoints in a given range.\nClients should only
-// call this request if the corresponding capability
-// `supportsBreakpointLocationsRequest` is true.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "breakpointLocations" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/BreakpointLocationsArguments"
-// }
-// },
-// "required": [ "command" ]
-// }]
-// },
-// "BreakpointLocationsArguments": {
-// "type": "object",
-// "description": "Arguments for `breakpointLocations` request.",
-// "properties": {
-// "source": {
-// "$ref": "#/definitions/Source",
-// "description": "The source location of the breakpoints; either
-// `source.path` or `source.sourceReference` must be specified."
-// },
-// "line": {
-// "type": "integer",
-// "description": "Start line of range to search possible breakpoint
-// locations in. If only the line is specified, the request returns all
-// possible locations in that line."
-// },
-// "column": {
-// "type": "integer",
-// "description": "Start position within `line` to search possible
-// breakpoint locations in. It is measured in UTF-16 code units and the
-// client capability `columnsStartAt1` determines whether it is 0- or
-// 1-based. If no column is given, the first position in the start line is
-// assumed."
-// },
-// "endLine": {
-// "type": "integer",
-// "description": "End line of range to search possible breakpoint
-// locations in. If no end line is given, then the end line is assumed to
-// be the start line."
-// },
-// "endColumn": {
-// "type": "integer",
-// "description": "End position within `endLine` to search possible
-// breakpoint locations in. It is measured in UTF-16 code units and the
-// client capability `columnsStartAt1` determines whether it is 0- or
-// 1-based. If no end column is given, the last position in the end line
-// is assumed."
-// }
-// },
-// "required": [ "source", "line" ]
-// },
-// "BreakpointLocationsResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to `breakpointLocations` request.\nContains
-// possible locations for source breakpoints.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "breakpoints": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/BreakpointLocation"
-// },
-// "description": "Sorted set of possible breakpoint locations."
-// }
-// },
-// "required": [ "breakpoints" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// },
-// "BreakpointLocation": {
-// "type": "object",
-// "description": "Properties of a breakpoint location returned from the
-// `breakpointLocations` request.",
-// "properties": {
-// "line": {
-// "type": "integer",
-// "description": "Start line of breakpoint location."
-// },
-// "column": {
-// "type": "integer",
-// "description": "The start position of a breakpoint location. Position
-// is measured in UTF-16 code units and the client capability
-// `columnsStartAt1` determines whether it is 0- or 1-based."
-// },
-// "endLine": {
-// "type": "integer",
-// "description": "The end line of breakpoint location if the location
-// covers a range."
-// },
-// "endColumn": {
-// "type": "integer",
-// "description": "The end position of a breakpoint location (if the
-// location covers a range). Position is measured in UTF-16 code units and
-// the client capability `columnsStartAt1` determines whether it is 0- or
-// 1-based."
-// }
-// },
-// "required": [ "line" ]
-// },
-void request_breakpointLocations(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- auto *arguments = request.getObject("arguments");
- auto *source = arguments->getObject("source");
- std::string path = GetString(source, "path").str();
- uint64_t start_line = GetUnsigned(arguments, "line", 0);
- uint64_t start_column = GetUnsigned(arguments, "column", 0);
- uint64_t end_line = GetUnsigned(arguments, "endLine", start_line);
- uint64_t end_column =
- GetUnsigned(arguments, "endColumn", std::numeric_limits<uint64_t>::max());
-
- lldb::SBFileSpec file_spec(path.c_str(), true);
- lldb::SBSymbolContextList compile_units =
- dap.target.FindCompileUnits(file_spec);
-
- // Find all relevant lines & columns
- llvm::SmallVector<std::pair<uint32_t, uint32_t>, 8> locations;
- for (uint32_t c_idx = 0, c_limit = compile_units.GetSize(); c_idx < c_limit;
- ++c_idx) {
- const lldb::SBCompileUnit &compile_unit =
- compile_units.GetContextAtIndex(c_idx).GetCompileUnit();
- if (!compile_unit.IsValid())
- continue;
- lldb::SBFileSpec primary_file_spec = compile_unit.GetFileSpec();
-
- // Go through the line table and find all matching lines / columns
- for (uint32_t l_idx = 0, l_limit = compile_unit.GetNumLineEntries();
- l_idx < l_limit; ++l_idx) {
- lldb::SBLineEntry line_entry = compile_unit.GetLineEntryAtIndex(l_idx);
-
- // Filter by line / column
- uint32_t line = line_entry.GetLine();
- if (line < start_line || line > end_line)
- continue;
- uint32_t column = line_entry.GetColumn();
- if (column == LLDB_INVALID_COLUMN_NUMBER)
- continue;
- if (line == start_line && column < start_column)
- continue;
- if (line == end_line && column > end_column)
- continue;
-
- // Make sure we are in the right file.
- // We might have a match on line & column range and still
- // be in the wrong file, e.g. for included files.
- // Given that the involved pointers point into LLDB's string pool,
- // we can directly compare the `const char*` pointers.
- if (line_entry.GetFileSpec().GetFilename() !=
- primary_file_spec.GetFilename() ||
- line_entry.GetFileSpec().GetDirectory() !=
- primary_file_spec.GetDirectory())
- continue;
-
- locations.emplace_back(line, column);
- }
- }
-
- // The line entries are sorted by addresses, but we must return the list
- // ordered by line / column position.
- std::sort(locations.begin(), locations.end());
- locations.erase(std::unique(locations.begin(), locations.end()),
- locations.end());
-
- llvm::json::Array locations_json;
- for (auto &l : locations) {
- llvm::json::Object location;
- location.try_emplace("line", l.first);
- location.try_emplace("column", l.second);
- locations_json.emplace_back(std::move(location));
- }
-
- llvm::json::Object body;
- body.try_emplace("breakpoints", std::move(locations_json));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
// "ContinueRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@@ -4814,9 +4624,8 @@ void request_setInstructionBreakpoints(DAP &dap,
void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<AttachRequestHandler>();
+ dap.RegisterRequest<BreakpointLocationsRequestHandler>();
- dap.RegisterRequestCallback("breakpointLocations",
- request_breakpointLocations);
dap.RegisterRequestCallback("completions", request_completions);
dap.RegisterRequestCallback("continue", request_continue);
dap.RegisterRequestCallback("configurationDone", request_configurationDone);
>From 65576580f595428e65f07533e0ed380c4bb0b452 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sat, 22 Feb 2025 19:27:26 -0600
Subject: [PATCH 05/11] Adopt CompletionsHandler
---
lldb/tools/lldb-dap/CMakeLists.txt | 1 +
.../lldb-dap/Handler/CompletionsHandler.cpp | 221 ++++++++++++++++++
lldb/tools/lldb-dap/Handler/RequestHandler.h | 7 +
lldb/tools/lldb-dap/lldb-dap.cpp | 207 +---------------
4 files changed, 230 insertions(+), 206 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index b0208578e0566..d41baae1ef5d9 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -38,6 +38,7 @@ add_lldb_tool(lldb-dap
Handler/AttachHandler.cpp
Handler/BreakpointLocationsHandler.cpp
+ Handler/CompletionsHandler.cpp
Handler/RequestHandler.cpp
LINK_LIBS
diff --git a/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp b/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
new file mode 100644
index 0000000000000..b7cafb3c47ac6
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
@@ -0,0 +1,221 @@
+//===-- CompletionsHandler.cpp --------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+#include "lldb/API/SBStringList.h"
+
+namespace lldb_dap {
+
+// "CompletionsRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Returns a list of possible completions for a given caret
+// position and text.\nThe CompletionsRequest may only be called if the
+// 'supportsCompletionsRequest' capability exists and is true.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "completions" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/CompletionsArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "CompletionsArguments": {
+// "type": "object",
+// "description": "Arguments for 'completions' request.",
+// "properties": {
+// "frameId": {
+// "type": "integer",
+// "description": "Returns completions in the scope of this stack frame.
+// If not specified, the completions are returned for the global scope."
+// },
+// "text": {
+// "type": "string",
+// "description": "One or more source lines. Typically this is the text a
+// user has typed into the debug console before he asked for completion."
+// },
+// "column": {
+// "type": "integer",
+// "description": "The character position for which to determine the
+// completion proposals."
+// },
+// "line": {
+// "type": "integer",
+// "description": "An optional line for which to determine the completion
+// proposals. If missing the first line of the text is assumed."
+// }
+// },
+// "required": [ "text", "column" ]
+// },
+// "CompletionsResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'completions' request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "targets": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/CompletionItem"
+// },
+// "description": "The possible completions for ."
+// }
+// },
+// "required": [ "targets" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// },
+// "CompletionItem": {
+// "type": "object",
+// "description": "CompletionItems are the suggestions returned from the
+// CompletionsRequest.", "properties": {
+// "label": {
+// "type": "string",
+// "description": "The label of this completion item. By default this is
+// also the text that is inserted when selecting this completion."
+// },
+// "text": {
+// "type": "string",
+// "description": "If text is not falsy then it is inserted instead of the
+// label."
+// },
+// "sortText": {
+// "type": "string",
+// "description": "A string that should be used when comparing this item
+// with other items. When `falsy` the label is used."
+// },
+// "type": {
+// "$ref": "#/definitions/CompletionItemType",
+// "description": "The item's type. Typically the client uses this
+// information to render the item in the UI with an icon."
+// },
+// "start": {
+// "type": "integer",
+// "description": "This value determines the location (in the
+// CompletionsRequest's 'text' attribute) where the completion text is
+// added.\nIf missing the text is added at the location specified by the
+// CompletionsRequest's 'column' attribute."
+// },
+// "length": {
+// "type": "integer",
+// "description": "This value determines how many characters are
+// overwritten by the completion text.\nIf missing the value 0 is assumed
+// which results in the completion text being inserted."
+// }
+// },
+// "required": [ "label" ]
+// },
+// "CompletionItemType": {
+// "type": "string",
+// "description": "Some predefined types for the CompletionItem. Please note
+// that not all clients have specific icons for all of them.", "enum": [
+// "method", "function", "constructor", "field", "variable", "class",
+// "interface", "module", "property", "unit", "value", "enum", "keyword",
+// "snippet", "text", "color", "file", "reference", "customcolor" ]
+// }
+void CompletionsRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ llvm::json::Object body;
+ const auto *arguments = request.getObject("arguments");
+
+ // If we have a frame, try to set the context for variable completions.
+ lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
+ if (frame.IsValid()) {
+ frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
+ frame.GetThread().SetSelectedFrame(frame.GetFrameID());
+ }
+
+ std::string text = GetString(arguments, "text").str();
+ auto original_column = GetSigned(arguments, "column", text.size());
+ auto original_line = GetSigned(arguments, "line", 1);
+ auto offset = original_column - 1;
+ if (original_line > 1) {
+ llvm::SmallVector<::llvm::StringRef, 2> lines;
+ llvm::StringRef(text).split(lines, '\n');
+ for (int i = 0; i < original_line - 1; i++) {
+ offset += lines[i].size();
+ }
+ }
+ llvm::json::Array targets;
+
+ bool had_escape_prefix =
+ llvm::StringRef(text).starts_with(dap.command_escape_prefix);
+ ReplMode completion_mode = dap.DetectReplMode(frame, text, true);
+
+ // Handle the offset change introduced by stripping out the
+ // `command_escape_prefix`.
+ if (had_escape_prefix) {
+ if (offset < static_cast<int64_t>(dap.command_escape_prefix.size())) {
+ body.try_emplace("targets", std::move(targets));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+ offset -= dap.command_escape_prefix.size();
+ }
+
+ // While the user is typing then we likely have an incomplete input and cannot
+ // reliably determine the precise intent (command vs variable), try completing
+ // the text as both a command and variable expression, if applicable.
+ const std::string expr_prefix = "expression -- ";
+ std::array<std::tuple<ReplMode, std::string, uint64_t>, 2> exprs = {
+ {std::make_tuple(ReplMode::Command, text, offset),
+ std::make_tuple(ReplMode::Variable, expr_prefix + text,
+ offset + expr_prefix.size())}};
+ for (const auto &[mode, line, cursor] : exprs) {
+ if (completion_mode != ReplMode::Auto && completion_mode != mode)
+ continue;
+
+ lldb::SBStringList matches;
+ lldb::SBStringList descriptions;
+ if (!dap.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions(
+ line.c_str(), cursor, 0, 100, matches, descriptions))
+ continue;
+
+ // The first element is the common substring after the cursor position for
+ // all the matches. The rest of the elements are the matches so ignore the
+ // first result.
+ for (size_t i = 1; i < matches.GetSize(); i++) {
+ std::string match = matches.GetStringAtIndex(i);
+ std::string description = descriptions.GetStringAtIndex(i);
+
+ llvm::json::Object item;
+ llvm::StringRef match_ref = match;
+ for (llvm::StringRef commit_point : {".", "->"}) {
+ if (match_ref.contains(commit_point)) {
+ match_ref = match_ref.rsplit(commit_point).second;
+ }
+ }
+ EmplaceSafeString(item, "text", match_ref);
+
+ if (description.empty())
+ EmplaceSafeString(item, "label", match);
+ else
+ EmplaceSafeString(item, "label", match + " -- " + description);
+
+ targets.emplace_back(std::move(item));
+ }
+ }
+
+ body.try_emplace("targets", std::move(targets));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 854fb88c36b86..76130ade76f32 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -55,6 +55,13 @@ class BreakpointLocationsRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};
+class CompletionsRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "completions"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index fe91f38609ff0..e20b1c5272336 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -19,7 +19,6 @@
#include "lldb/API/SBListener.h"
#include "lldb/API/SBMemoryRegionInfo.h"
#include "lldb/API/SBStream.h"
-#include "lldb/API/SBStringList.h"
#include "lldb/Host/Config.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
@@ -1080,210 +1079,6 @@ void request_exceptionInfo(DAP &dap, const llvm::json::Object &request) {
dap.SendJSON(llvm::json::Value(std::move(response)));
}
-// "CompletionsRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Returns a list of possible completions for a given caret
-// position and text.\nThe CompletionsRequest may only be called if the
-// 'supportsCompletionsRequest' capability exists and is true.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "completions" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/CompletionsArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "CompletionsArguments": {
-// "type": "object",
-// "description": "Arguments for 'completions' request.",
-// "properties": {
-// "frameId": {
-// "type": "integer",
-// "description": "Returns completions in the scope of this stack frame.
-// If not specified, the completions are returned for the global scope."
-// },
-// "text": {
-// "type": "string",
-// "description": "One or more source lines. Typically this is the text a
-// user has typed into the debug console before he asked for completion."
-// },
-// "column": {
-// "type": "integer",
-// "description": "The character position for which to determine the
-// completion proposals."
-// },
-// "line": {
-// "type": "integer",
-// "description": "An optional line for which to determine the completion
-// proposals. If missing the first line of the text is assumed."
-// }
-// },
-// "required": [ "text", "column" ]
-// },
-// "CompletionsResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'completions' request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "targets": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/CompletionItem"
-// },
-// "description": "The possible completions for ."
-// }
-// },
-// "required": [ "targets" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// },
-// "CompletionItem": {
-// "type": "object",
-// "description": "CompletionItems are the suggestions returned from the
-// CompletionsRequest.", "properties": {
-// "label": {
-// "type": "string",
-// "description": "The label of this completion item. By default this is
-// also the text that is inserted when selecting this completion."
-// },
-// "text": {
-// "type": "string",
-// "description": "If text is not falsy then it is inserted instead of the
-// label."
-// },
-// "sortText": {
-// "type": "string",
-// "description": "A string that should be used when comparing this item
-// with other items. When `falsy` the label is used."
-// },
-// "type": {
-// "$ref": "#/definitions/CompletionItemType",
-// "description": "The item's type. Typically the client uses this
-// information to render the item in the UI with an icon."
-// },
-// "start": {
-// "type": "integer",
-// "description": "This value determines the location (in the
-// CompletionsRequest's 'text' attribute) where the completion text is
-// added.\nIf missing the text is added at the location specified by the
-// CompletionsRequest's 'column' attribute."
-// },
-// "length": {
-// "type": "integer",
-// "description": "This value determines how many characters are
-// overwritten by the completion text.\nIf missing the value 0 is assumed
-// which results in the completion text being inserted."
-// }
-// },
-// "required": [ "label" ]
-// },
-// "CompletionItemType": {
-// "type": "string",
-// "description": "Some predefined types for the CompletionItem. Please note
-// that not all clients have specific icons for all of them.", "enum": [
-// "method", "function", "constructor", "field", "variable", "class",
-// "interface", "module", "property", "unit", "value", "enum", "keyword",
-// "snippet", "text", "color", "file", "reference", "customcolor" ]
-// }
-void request_completions(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Object body;
- const auto *arguments = request.getObject("arguments");
-
- // If we have a frame, try to set the context for variable completions.
- lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
- if (frame.IsValid()) {
- frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
- frame.GetThread().SetSelectedFrame(frame.GetFrameID());
- }
-
- std::string text = GetString(arguments, "text").str();
- auto original_column = GetSigned(arguments, "column", text.size());
- auto original_line = GetSigned(arguments, "line", 1);
- auto offset = original_column - 1;
- if (original_line > 1) {
- llvm::SmallVector<::llvm::StringRef, 2> lines;
- llvm::StringRef(text).split(lines, '\n');
- for (int i = 0; i < original_line - 1; i++) {
- offset += lines[i].size();
- }
- }
- llvm::json::Array targets;
-
- bool had_escape_prefix =
- llvm::StringRef(text).starts_with(dap.command_escape_prefix);
- ReplMode completion_mode = dap.DetectReplMode(frame, text, true);
-
- // Handle the offset change introduced by stripping out the
- // `command_escape_prefix`.
- if (had_escape_prefix) {
- if (offset < static_cast<int64_t>(dap.command_escape_prefix.size())) {
- body.try_emplace("targets", std::move(targets));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
- offset -= dap.command_escape_prefix.size();
- }
-
- // While the user is typing then we likely have an incomplete input and cannot
- // reliably determine the precise intent (command vs variable), try completing
- // the text as both a command and variable expression, if applicable.
- const std::string expr_prefix = "expression -- ";
- std::array<std::tuple<ReplMode, std::string, uint64_t>, 2> exprs = {
- {std::make_tuple(ReplMode::Command, text, offset),
- std::make_tuple(ReplMode::Variable, expr_prefix + text,
- offset + expr_prefix.size())}};
- for (const auto &[mode, line, cursor] : exprs) {
- if (completion_mode != ReplMode::Auto && completion_mode != mode)
- continue;
-
- lldb::SBStringList matches;
- lldb::SBStringList descriptions;
- if (!dap.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions(
- line.c_str(), cursor, 0, 100, matches, descriptions))
- continue;
-
- // The first element is the common substring after the cursor position for
- // all the matches. The rest of the elements are the matches so ignore the
- // first result.
- for (size_t i = 1; i < matches.GetSize(); i++) {
- std::string match = matches.GetStringAtIndex(i);
- std::string description = descriptions.GetStringAtIndex(i);
-
- llvm::json::Object item;
- llvm::StringRef match_ref = match;
- for (llvm::StringRef commit_point : {".", "->"}) {
- if (match_ref.contains(commit_point)) {
- match_ref = match_ref.rsplit(commit_point).second;
- }
- }
- EmplaceSafeString(item, "text", match_ref);
-
- if (description.empty())
- EmplaceSafeString(item, "label", match);
- else
- EmplaceSafeString(item, "label", match + " -- " + description);
-
- targets.emplace_back(std::move(item));
- }
- }
-
- body.try_emplace("targets", std::move(targets));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
// "EvaluateRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
@@ -4625,8 +4420,8 @@ void request_setInstructionBreakpoints(DAP &dap,
void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<AttachRequestHandler>();
dap.RegisterRequest<BreakpointLocationsRequestHandler>();
+ dap.RegisterRequest<CompletionsRequestHandler>();
- dap.RegisterRequestCallback("completions", request_completions);
dap.RegisterRequestCallback("continue", request_continue);
dap.RegisterRequestCallback("configurationDone", request_configurationDone);
dap.RegisterRequestCallback("disconnect", request_disconnect);
>From bf4e686f7ec263ea757e3142a9d09d655f7f6d29 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sat, 22 Feb 2025 19:30:19 -0600
Subject: [PATCH 06/11] Adopt ContinueRequestHandler
---
lldb/tools/lldb-dap/CMakeLists.txt | 1 +
.../Handler/ContinueRequestHandler.cpp | 79 +++++++++++++++++++
lldb/tools/lldb-dap/Handler/RequestHandler.h | 7 ++
lldb/tools/lldb-dap/lldb-dap.cpp | 67 +---------------
4 files changed, 88 insertions(+), 66 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Handler/ContinueRequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index d41baae1ef5d9..56a234b990729 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -39,6 +39,7 @@ add_lldb_tool(lldb-dap
Handler/AttachHandler.cpp
Handler/BreakpointLocationsHandler.cpp
Handler/CompletionsHandler.cpp
+ Handler/ContinueRequestHandler.cpp
Handler/RequestHandler.cpp
LINK_LIBS
diff --git a/lldb/tools/lldb-dap/Handler/ContinueRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ContinueRequestHandler.cpp
new file mode 100644
index 0000000000000..743858f87901f
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/ContinueRequestHandler.cpp
@@ -0,0 +1,79 @@
+//===-- CompletionsHandler.cpp --------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "ContinueRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Continue request; value of command field is 'continue'.
+// The request starts the debuggee to run again.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "continue" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/ContinueArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "ContinueArguments": {
+// "type": "object",
+// "description": "Arguments for 'continue' request.",
+// "properties": {
+// "threadId": {
+// "type": "integer",
+// "description": "Continue execution for the specified thread (if
+// possible). If the backend cannot continue on a single
+// thread but will continue on all threads, it should
+// set the allThreadsContinued attribute in the response
+// to true."
+// }
+// },
+// "required": [ "threadId" ]
+// },
+// "ContinueResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'continue' request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "allThreadsContinued": {
+// "type": "boolean",
+// "description": "If true, the continue request has ignored the
+// specified thread and continued all threads
+// instead. If this attribute is missing a value
+// of 'true' is assumed for backward
+// compatibility."
+// }
+// }
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+void ContinueRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ lldb::SBProcess process = dap.target.GetProcess();
+ lldb::SBError error = process.Continue();
+ llvm::json::Object body;
+ body.try_emplace("allThreadsContinued", true);
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 76130ade76f32..5b0d9071a73ac 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -62,6 +62,13 @@ class CompletionsRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};
+class ContinueRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "continue"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index e20b1c5272336..cf03efacf5735 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -726,71 +726,6 @@ bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
return reached_end_of_stack;
}
-// "ContinueRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Continue request; value of command field is 'continue'.
-// The request starts the debuggee to run again.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "continue" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/ContinueArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "ContinueArguments": {
-// "type": "object",
-// "description": "Arguments for 'continue' request.",
-// "properties": {
-// "threadId": {
-// "type": "integer",
-// "description": "Continue execution for the specified thread (if
-// possible). If the backend cannot continue on a single
-// thread but will continue on all threads, it should
-// set the allThreadsContinued attribute in the response
-// to true."
-// }
-// },
-// "required": [ "threadId" ]
-// },
-// "ContinueResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'continue' request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "allThreadsContinued": {
-// "type": "boolean",
-// "description": "If true, the continue request has ignored the
-// specified thread and continued all threads
-// instead. If this attribute is missing a value
-// of 'true' is assumed for backward
-// compatibility."
-// }
-// }
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-void request_continue(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- lldb::SBProcess process = dap.target.GetProcess();
- lldb::SBError error = process.Continue();
- llvm::json::Object body;
- body.try_emplace("allThreadsContinued", true);
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
// "ConfigurationDoneRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@@ -4421,8 +4356,8 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<AttachRequestHandler>();
dap.RegisterRequest<BreakpointLocationsRequestHandler>();
dap.RegisterRequest<CompletionsRequestHandler>();
+ dap.RegisterRequest<ContinueRequestHandler>();
- dap.RegisterRequestCallback("continue", request_continue);
dap.RegisterRequestCallback("configurationDone", request_configurationDone);
dap.RegisterRequestCallback("disconnect", request_disconnect);
dap.RegisterRequestCallback("evaluate", request_evaluate);
>From 25504a81a23b2a7c264546b3914560ea1bed3003 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sat, 22 Feb 2025 19:44:22 -0600
Subject: [PATCH 07/11] Adopt ConfigurationDoneRequestHandler
---
lldb/tools/lldb-dap/CMakeLists.txt | 2 +
lldb/tools/lldb-dap/EventHelper.cpp | 184 +++++++++++++++
lldb/tools/lldb-dap/EventHelper.h | 23 ++
lldb/tools/lldb-dap/Handler/AttachHandler.cpp | 3 +-
.../ConfigurationDoneRequestHandler.cpp | 59 +++++
.../tools/lldb-dap/Handler/RequestHandler.cpp | 28 ---
lldb/tools/lldb-dap/Handler/RequestHandler.h | 11 +-
lldb/tools/lldb-dap/lldb-dap.cpp | 215 +-----------------
8 files changed, 280 insertions(+), 245 deletions(-)
create mode 100644 lldb/tools/lldb-dap/EventHelper.cpp
create mode 100644 lldb/tools/lldb-dap/EventHelper.h
create mode 100644 lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index 56a234b990729..eda34792b66cb 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -23,6 +23,7 @@ add_lldb_tool(lldb-dap
Breakpoint.cpp
BreakpointBase.cpp
DAP.cpp
+ EventHelper.cpp
ExceptionBreakpoint.cpp
FifoFiles.cpp
FunctionBreakpoint.cpp
@@ -39,6 +40,7 @@ add_lldb_tool(lldb-dap
Handler/AttachHandler.cpp
Handler/BreakpointLocationsHandler.cpp
Handler/CompletionsHandler.cpp
+ Handler/ConfigurationDoneRequestHandler.cpp
Handler/ContinueRequestHandler.cpp
Handler/RequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp
new file mode 100644
index 0000000000000..d1ce8901a6a67
--- /dev/null
+++ b/lldb/tools/lldb-dap/EventHelper.cpp
@@ -0,0 +1,184 @@
+//===-- EventHelper.h -----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "EventHelper.h"
+#include "DAP.h"
+#include "JSONUtils.h"
+#include "LLDBUtils.h"
+#include "lldb/API/SBFileSpec.h"
+
+namespace lldb_dap {
+
+static void SendThreadExitedEvent(DAP &dap, lldb::tid_t tid) {
+ llvm::json::Object event(CreateEventObject("thread"));
+ llvm::json::Object body;
+ body.try_emplace("reason", "exited");
+ body.try_emplace("threadId", (int64_t)tid);
+ event.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(event)));
+}
+
+// "ProcessEvent": {
+// "allOf": [
+// { "$ref": "#/definitions/Event" },
+// {
+// "type": "object",
+// "description": "Event message for 'process' event type. The event
+// indicates that the debugger has begun debugging a
+// new process. Either one that it has launched, or one
+// that it has attached to.",
+// "properties": {
+// "event": {
+// "type": "string",
+// "enum": [ "process" ]
+// },
+// "body": {
+// "type": "object",
+// "properties": {
+// "name": {
+// "type": "string",
+// "description": "The logical name of the process. This is
+// usually the full path to process's executable
+// file. Example: /home/myproj/program.js."
+// },
+// "systemProcessId": {
+// "type": "integer",
+// "description": "The system process id of the debugged process.
+// This property will be missing for non-system
+// processes."
+// },
+// "isLocalProcess": {
+// "type": "boolean",
+// "description": "If true, the process is running on the same
+// computer as the debug adapter."
+// },
+// "startMethod": {
+// "type": "string",
+// "enum": [ "launch", "attach", "attachForSuspendedLaunch" ],
+// "description": "Describes how the debug engine started
+// debugging this process.",
+// "enumDescriptions": [
+// "Process was launched under the debugger.",
+// "Debugger attached to an existing process.",
+// "A project launcher component has launched a new process in
+// a suspended state and then asked the debugger to attach."
+// ]
+// }
+// },
+// "required": [ "name" ]
+// }
+// },
+// "required": [ "event", "body" ]
+// }
+// ]
+// }
+void SendProcessEvent(DAP &dap, LaunchMethod launch_method) {
+ lldb::SBFileSpec exe_fspec = dap.target.GetExecutable();
+ char exe_path[PATH_MAX];
+ exe_fspec.GetPath(exe_path, sizeof(exe_path));
+ llvm::json::Object event(CreateEventObject("process"));
+ llvm::json::Object body;
+ EmplaceSafeString(body, "name", std::string(exe_path));
+ const auto pid = dap.target.GetProcess().GetProcessID();
+ body.try_emplace("systemProcessId", (int64_t)pid);
+ body.try_emplace("isLocalProcess", true);
+ const char *startMethod = nullptr;
+ switch (launch_method) {
+ case Launch:
+ startMethod = "launch";
+ break;
+ case Attach:
+ startMethod = "attach";
+ break;
+ case AttachForSuspendedLaunch:
+ startMethod = "attachForSuspendedLaunch";
+ break;
+ }
+ body.try_emplace("startMethod", startMethod);
+ event.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(event)));
+}
+
+// Send a thread stopped event for all threads as long as the process
+// is stopped.
+void SendThreadStoppedEvent(DAP &dap) {
+ lldb::SBProcess process = dap.target.GetProcess();
+ if (process.IsValid()) {
+ auto state = process.GetState();
+ if (state == lldb::eStateStopped) {
+ llvm::DenseSet<lldb::tid_t> old_thread_ids;
+ old_thread_ids.swap(dap.thread_ids);
+ uint32_t stop_id = process.GetStopID();
+ const uint32_t num_threads = process.GetNumThreads();
+
+ // First make a pass through the threads to see if the focused thread
+ // has a stop reason. In case the focus thread doesn't have a stop
+ // reason, remember the first thread that has a stop reason so we can
+ // set it as the focus thread if below if needed.
+ lldb::tid_t first_tid_with_reason = LLDB_INVALID_THREAD_ID;
+ uint32_t num_threads_with_reason = 0;
+ bool focus_thread_exists = false;
+ for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) {
+ lldb::SBThread thread = process.GetThreadAtIndex(thread_idx);
+ const lldb::tid_t tid = thread.GetThreadID();
+ const bool has_reason = ThreadHasStopReason(thread);
+ // If the focus thread doesn't have a stop reason, clear the thread ID
+ if (tid == dap.focus_tid) {
+ focus_thread_exists = true;
+ if (!has_reason)
+ dap.focus_tid = LLDB_INVALID_THREAD_ID;
+ }
+ if (has_reason) {
+ ++num_threads_with_reason;
+ if (first_tid_with_reason == LLDB_INVALID_THREAD_ID)
+ first_tid_with_reason = tid;
+ }
+ }
+
+ // We will have cleared dap.focus_tid if the focus thread doesn't have
+ // a stop reason, so if it was cleared, or wasn't set, or doesn't exist,
+ // then set the focus thread to the first thread with a stop reason.
+ if (!focus_thread_exists || dap.focus_tid == LLDB_INVALID_THREAD_ID)
+ dap.focus_tid = first_tid_with_reason;
+
+ // If no threads stopped with a reason, then report the first one so
+ // we at least let the UI know we stopped.
+ if (num_threads_with_reason == 0) {
+ lldb::SBThread thread = process.GetThreadAtIndex(0);
+ dap.focus_tid = thread.GetThreadID();
+ dap.SendJSON(CreateThreadStopped(dap, thread, stop_id));
+ } else {
+ for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) {
+ lldb::SBThread thread = process.GetThreadAtIndex(thread_idx);
+ dap.thread_ids.insert(thread.GetThreadID());
+ if (ThreadHasStopReason(thread)) {
+ dap.SendJSON(CreateThreadStopped(dap, thread, stop_id));
+ }
+ }
+ }
+
+ for (auto tid : old_thread_ids) {
+ auto end = dap.thread_ids.end();
+ auto pos = dap.thread_ids.find(tid);
+ if (pos == end)
+ SendThreadExitedEvent(dap, tid);
+ }
+ } else {
+ if (dap.log)
+ *dap.log << "error: SendThreadStoppedEvent() when process"
+ " isn't stopped ("
+ << lldb::SBDebugger::StateAsCString(state) << ')' << std::endl;
+ }
+ } else {
+ if (dap.log)
+ *dap.log << "error: SendThreadStoppedEvent() invalid process"
+ << std::endl;
+ }
+ dap.RunStopCommands();
+}
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h
new file mode 100644
index 0000000000000..dee36b4ad8554
--- /dev/null
+++ b/lldb/tools/lldb-dap/EventHelper.h
@@ -0,0 +1,23 @@
+//===-- EventHelper.h -----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TOOLS_LLDB_DAP_EVENTHELPER_H
+#define LLDB_TOOLS_LLDB_DAP_EVENTHELPER_H
+
+namespace lldb_dap {
+struct DAP;
+
+enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch };
+
+void SendProcessEvent(DAP &dap, LaunchMethod launch_method);
+
+void SendThreadStoppedEvent(DAP &dap);
+
+} // namespace lldb_dap
+
+#endif
diff --git a/lldb/tools/lldb-dap/Handler/AttachHandler.cpp b/lldb/tools/lldb-dap/Handler/AttachHandler.cpp
index 3dcde2144246e..872146cc45d69 100644
--- a/lldb/tools/lldb-dap/Handler/AttachHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/AttachHandler.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "DAP.h"
+#include "EventHelper.h"
#include "JSONUtils.h"
#include "RequestHandler.h"
#include "lldb/API/SBListener.h"
@@ -203,7 +204,7 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) {
dap.SendJSON(llvm::json::Value(std::move(response)));
if (error.Success()) {
- SendProcessEvent(Attach);
+ SendProcessEvent(dap, Attach);
dap.SendJSON(CreateEventObject("initialized"));
}
}
diff --git a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp
new file mode 100644
index 0000000000000..429bcd21eab30
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp
@@ -0,0 +1,59 @@
+//===-- ConfigurationDoneRequestHandler..cpp ------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "ConfigurationDoneRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "ConfigurationDone request; value of command field
+// is 'configurationDone'.\nThe client of the debug protocol must
+// send this request at the end of the sequence of configuration
+// requests (which was started by the InitializedEvent).",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "configurationDone" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/ConfigurationDoneArguments"
+// }
+// },
+// "required": [ "command" ]
+// }]
+// },
+// "ConfigurationDoneArguments": {
+// "type": "object",
+// "description": "Arguments for 'configurationDone' request.\nThe
+// configurationDone request has no standardized attributes."
+// },
+// "ConfigurationDoneResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'configurationDone' request. This is
+// just an acknowledgement, so no body field is required."
+// }]
+// },
+void ConfigurationDoneRequestHandler::operator()(
+ const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ dap.configuration_done_sent = true;
+ if (dap.stop_at_entry)
+ SendThreadStoppedEvent(dap);
+ else
+ dap.target.GetProcess().Continue();
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
index ef091a805902d..bf2b82f518713 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
@@ -13,34 +13,6 @@
namespace lldb_dap {
-void RequestHandler::SendProcessEvent(
- RequestHandler::LaunchMethod launch_method) {
- lldb::SBFileSpec exe_fspec = dap.target.GetExecutable();
- char exe_path[PATH_MAX];
- exe_fspec.GetPath(exe_path, sizeof(exe_path));
- llvm::json::Object event(CreateEventObject("process"));
- llvm::json::Object body;
- EmplaceSafeString(body, "name", std::string(exe_path));
- const auto pid = dap.target.GetProcess().GetProcessID();
- body.try_emplace("systemProcessId", (int64_t)pid);
- body.try_emplace("isLocalProcess", true);
- const char *startMethod = nullptr;
- switch (launch_method) {
- case Launch:
- startMethod = "launch";
- break;
- case Attach:
- startMethod = "attach";
- break;
- case AttachForSuspendedLaunch:
- startMethod = "attachForSuspendedLaunch";
- break;
- }
- body.try_emplace("startMethod", startMethod);
- event.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(event)));
-}
-
// Both attach and launch take a either a sourcePath or sourceMap
// argument (or neither), from which we need to set the target.source-map.
void RequestHandler::SetSourceMapFromArguments(
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 5b0d9071a73ac..51bd834011b24 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -30,10 +30,8 @@ class RequestHandler {
virtual void operator()(const llvm::json::Object &request) = 0;
/// Helpers used by multiple request handlers.
- /// FIXME: Move these into the DAP class.
+ /// FIXME: Move these into the DAP class?
/// @{
- enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch };
- void SendProcessEvent(LaunchMethod launch_method);
void SetSourceMapFromArguments(const llvm::json::Object &arguments);
/// @}
@@ -69,6 +67,13 @@ class ContinueRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};
+class ConfigurationDoneRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "configurationDone"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index cf03efacf5735..9b214d98adb99 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "DAP.h"
+#include "EventHelper.h"
#include "FifoFiles.h"
#include "Handler/RequestHandler.h"
#include "JSONUtils.h"
@@ -116,8 +117,6 @@ class LLDBDAPOptTable : public llvm::opt::GenericOptTable {
typedef void (*RequestCallback)(const llvm::json::Object &command);
-enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch };
-
/// Page size used for reporting addtional frames in the 'stackTrace' request.
constexpr int StackPageSize = 20;
@@ -200,15 +199,6 @@ void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) {
dap.SendJSON(llvm::json::Value(std::move(event)));
}
-void SendThreadExitedEvent(DAP &dap, lldb::tid_t tid) {
- llvm::json::Object event(CreateEventObject("thread"));
- llvm::json::Object body;
- body.try_emplace("reason", "exited");
- body.try_emplace("threadId", (int64_t)tid);
- event.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(event)));
-}
-
// Send a "continued" event to indicate the process is in the running state.
void SendContinuedEvent(DAP &dap) {
lldb::SBProcess process = dap.target.GetProcess();
@@ -242,165 +232,6 @@ void SendTerminatedEvent(DAP &dap) {
});
}
-// Send a thread stopped event for all threads as long as the process
-// is stopped.
-void SendThreadStoppedEvent(DAP &dap) {
- lldb::SBProcess process = dap.target.GetProcess();
- if (process.IsValid()) {
- auto state = process.GetState();
- if (state == lldb::eStateStopped) {
- llvm::DenseSet<lldb::tid_t> old_thread_ids;
- old_thread_ids.swap(dap.thread_ids);
- uint32_t stop_id = process.GetStopID();
- const uint32_t num_threads = process.GetNumThreads();
-
- // First make a pass through the threads to see if the focused thread
- // has a stop reason. In case the focus thread doesn't have a stop
- // reason, remember the first thread that has a stop reason so we can
- // set it as the focus thread if below if needed.
- lldb::tid_t first_tid_with_reason = LLDB_INVALID_THREAD_ID;
- uint32_t num_threads_with_reason = 0;
- bool focus_thread_exists = false;
- for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) {
- lldb::SBThread thread = process.GetThreadAtIndex(thread_idx);
- const lldb::tid_t tid = thread.GetThreadID();
- const bool has_reason = ThreadHasStopReason(thread);
- // If the focus thread doesn't have a stop reason, clear the thread ID
- if (tid == dap.focus_tid) {
- focus_thread_exists = true;
- if (!has_reason)
- dap.focus_tid = LLDB_INVALID_THREAD_ID;
- }
- if (has_reason) {
- ++num_threads_with_reason;
- if (first_tid_with_reason == LLDB_INVALID_THREAD_ID)
- first_tid_with_reason = tid;
- }
- }
-
- // We will have cleared dap.focus_tid if the focus thread doesn't have
- // a stop reason, so if it was cleared, or wasn't set, or doesn't exist,
- // then set the focus thread to the first thread with a stop reason.
- if (!focus_thread_exists || dap.focus_tid == LLDB_INVALID_THREAD_ID)
- dap.focus_tid = first_tid_with_reason;
-
- // If no threads stopped with a reason, then report the first one so
- // we at least let the UI know we stopped.
- if (num_threads_with_reason == 0) {
- lldb::SBThread thread = process.GetThreadAtIndex(0);
- dap.focus_tid = thread.GetThreadID();
- dap.SendJSON(CreateThreadStopped(dap, thread, stop_id));
- } else {
- for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) {
- lldb::SBThread thread = process.GetThreadAtIndex(thread_idx);
- dap.thread_ids.insert(thread.GetThreadID());
- if (ThreadHasStopReason(thread)) {
- dap.SendJSON(CreateThreadStopped(dap, thread, stop_id));
- }
- }
- }
-
- for (auto tid : old_thread_ids) {
- auto end = dap.thread_ids.end();
- auto pos = dap.thread_ids.find(tid);
- if (pos == end)
- SendThreadExitedEvent(dap, tid);
- }
- } else {
- if (dap.log)
- *dap.log << "error: SendThreadStoppedEvent() when process"
- " isn't stopped ("
- << lldb::SBDebugger::StateAsCString(state) << ')' << std::endl;
- }
- } else {
- if (dap.log)
- *dap.log << "error: SendThreadStoppedEvent() invalid process"
- << std::endl;
- }
- dap.RunStopCommands();
-}
-
-// "ProcessEvent": {
-// "allOf": [
-// { "$ref": "#/definitions/Event" },
-// {
-// "type": "object",
-// "description": "Event message for 'process' event type. The event
-// indicates that the debugger has begun debugging a
-// new process. Either one that it has launched, or one
-// that it has attached to.",
-// "properties": {
-// "event": {
-// "type": "string",
-// "enum": [ "process" ]
-// },
-// "body": {
-// "type": "object",
-// "properties": {
-// "name": {
-// "type": "string",
-// "description": "The logical name of the process. This is
-// usually the full path to process's executable
-// file. Example: /home/myproj/program.js."
-// },
-// "systemProcessId": {
-// "type": "integer",
-// "description": "The system process id of the debugged process.
-// This property will be missing for non-system
-// processes."
-// },
-// "isLocalProcess": {
-// "type": "boolean",
-// "description": "If true, the process is running on the same
-// computer as the debug adapter."
-// },
-// "startMethod": {
-// "type": "string",
-// "enum": [ "launch", "attach", "attachForSuspendedLaunch" ],
-// "description": "Describes how the debug engine started
-// debugging this process.",
-// "enumDescriptions": [
-// "Process was launched under the debugger.",
-// "Debugger attached to an existing process.",
-// "A project launcher component has launched a new process in
-// a suspended state and then asked the debugger to attach."
-// ]
-// }
-// },
-// "required": [ "name" ]
-// }
-// },
-// "required": [ "event", "body" ]
-// }
-// ]
-// }
-void SendProcessEvent(DAP &dap, LaunchMethod launch_method) {
- lldb::SBFileSpec exe_fspec = dap.target.GetExecutable();
- char exe_path[PATH_MAX];
- exe_fspec.GetPath(exe_path, sizeof(exe_path));
- llvm::json::Object event(CreateEventObject("process"));
- llvm::json::Object body;
- EmplaceSafeString(body, "name", std::string(exe_path));
- const auto pid = dap.target.GetProcess().GetProcessID();
- body.try_emplace("systemProcessId", (int64_t)pid);
- body.try_emplace("isLocalProcess", true);
- const char *startMethod = nullptr;
- switch (launch_method) {
- case Launch:
- startMethod = "launch";
- break;
- case Attach:
- startMethod = "attach";
- break;
- case AttachForSuspendedLaunch:
- startMethod = "attachForSuspendedLaunch";
- break;
- }
- body.try_emplace("startMethod", startMethod);
- event.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(event)));
-}
-
// Grab any STDOUT and STDERR from the process and send it up to VS Code
// via an "output" event to the "stdout" and "stderr" categories.
void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process) {
@@ -726,48 +557,6 @@ bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
return reached_end_of_stack;
}
-// "ConfigurationDoneRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "ConfigurationDone request; value of command field
-// is 'configurationDone'.\nThe client of the debug protocol must
-// send this request at the end of the sequence of configuration
-// requests (which was started by the InitializedEvent).",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "configurationDone" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/ConfigurationDoneArguments"
-// }
-// },
-// "required": [ "command" ]
-// }]
-// },
-// "ConfigurationDoneArguments": {
-// "type": "object",
-// "description": "Arguments for 'configurationDone' request.\nThe
-// configurationDone request has no standardized attributes."
-// },
-// "ConfigurationDoneResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'configurationDone' request. This is
-// just an acknowledgement, so no body field is required."
-// }]
-// },
-void request_configurationDone(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- dap.SendJSON(llvm::json::Value(std::move(response)));
- dap.configuration_done_sent = true;
- if (dap.stop_at_entry)
- SendThreadStoppedEvent(dap);
- else
- dap.target.GetProcess().Continue();
-}
-
// "DisconnectRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@@ -4357,8 +4146,8 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<BreakpointLocationsRequestHandler>();
dap.RegisterRequest<CompletionsRequestHandler>();
dap.RegisterRequest<ContinueRequestHandler>();
+ dap.RegisterRequest<ConfigurationDoneRequestHandler>();
- dap.RegisterRequestCallback("configurationDone", request_configurationDone);
dap.RegisterRequestCallback("disconnect", request_disconnect);
dap.RegisterRequestCallback("evaluate", request_evaluate);
dap.RegisterRequestCallback("exceptionInfo", request_exceptionInfo);
>From 670701d3171389d79533c37b326965a060c10e00 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sat, 22 Feb 2025 19:49:28 -0600
Subject: [PATCH 08/11] Adopt DisconnectRequestHandler
---
lldb/tools/lldb-dap/CMakeLists.txt | 1 +
lldb/tools/lldb-dap/EventHelper.cpp | 13 +++
lldb/tools/lldb-dap/EventHelper.h | 2 +
.../Handler/DisconnectRequestHandler.cpp | 104 ++++++++++++++++++
lldb/tools/lldb-dap/Handler/RequestHandler.h | 7 ++
lldb/tools/lldb-dap/lldb-dap.cpp | 103 +----------------
6 files changed, 128 insertions(+), 102 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Handler/DisconnectRequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index eda34792b66cb..bd3aae6a03db3 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -42,6 +42,7 @@ add_lldb_tool(lldb-dap
Handler/CompletionsHandler.cpp
Handler/ConfigurationDoneRequestHandler.cpp
Handler/ContinueRequestHandler.cpp
+ Handler/DisconnectRequestHandler.cpp
Handler/RequestHandler.cpp
LINK_LIBS
diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp
index d1ce8901a6a67..f59c73a6593c1 100644
--- a/lldb/tools/lldb-dap/EventHelper.cpp
+++ b/lldb/tools/lldb-dap/EventHelper.cpp
@@ -181,4 +181,17 @@ void SendThreadStoppedEvent(DAP &dap) {
}
dap.RunStopCommands();
}
+
+// Send a "terminated" event to indicate the process is done being
+// debugged.
+void SendTerminatedEvent(DAP &dap) {
+ // Prevent races if the process exits while we're being asked to disconnect.
+ llvm::call_once(dap.terminated_event_flag, [&] {
+ dap.RunTerminateCommands();
+ // Send a "terminated" event
+ llvm::json::Object event(CreateTerminatedEventObject(dap.target));
+ dap.SendJSON(llvm::json::Value(std::move(event)));
+ });
+}
+
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h
index dee36b4ad8554..0c5978e1db49b 100644
--- a/lldb/tools/lldb-dap/EventHelper.h
+++ b/lldb/tools/lldb-dap/EventHelper.h
@@ -18,6 +18,8 @@ void SendProcessEvent(DAP &dap, LaunchMethod launch_method);
void SendThreadStoppedEvent(DAP &dap);
+void SendTerminatedEvent(DAP &dap);
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/Handler/DisconnectRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/DisconnectRequestHandler.cpp
new file mode 100644
index 0000000000000..ade337dac0390
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/DisconnectRequestHandler.cpp
@@ -0,0 +1,104 @@
+//===-- DisconnectRequestHandler.cpp --------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "DisconnectRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Disconnect request; value of command field is
+// 'disconnect'.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "disconnect" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/DisconnectArguments"
+// }
+// },
+// "required": [ "command" ]
+// }]
+// },
+// "DisconnectArguments": {
+// "type": "object",
+// "description": "Arguments for 'disconnect' request.",
+// "properties": {
+// "terminateDebuggee": {
+// "type": "boolean",
+// "description": "Indicates whether the debuggee should be terminated
+// when the debugger is disconnected. If unspecified,
+// the debug adapter is free to do whatever it thinks
+// is best. A client can only rely on this attribute
+// being properly honored if a debug adapter returns
+// true for the 'supportTerminateDebuggee' capability."
+// },
+// "restart": {
+// "type": "boolean",
+// "description": "Indicates whether the debuggee should be restart
+// the process."
+// }
+// }
+// },
+// "DisconnectResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'disconnect' request. This is just an
+// acknowledgement, so no body field is required."
+// }]
+// }
+void DisconnectRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ const auto *arguments = request.getObject("arguments");
+
+ bool defaultTerminateDebuggee = dap.is_attach ? false : true;
+ bool terminateDebuggee =
+ GetBoolean(arguments, "terminateDebuggee", defaultTerminateDebuggee);
+ lldb::SBProcess process = dap.target.GetProcess();
+ auto state = process.GetState();
+ switch (state) {
+ case lldb::eStateInvalid:
+ case lldb::eStateUnloaded:
+ case lldb::eStateDetached:
+ case lldb::eStateExited:
+ break;
+ case lldb::eStateConnected:
+ case lldb::eStateAttaching:
+ case lldb::eStateLaunching:
+ case lldb::eStateStepping:
+ case lldb::eStateCrashed:
+ case lldb::eStateSuspended:
+ case lldb::eStateStopped:
+ case lldb::eStateRunning:
+ dap.debugger.SetAsync(false);
+ lldb::SBError error = terminateDebuggee ? process.Kill() : process.Detach();
+ if (!error.Success())
+ EmplaceSafeString(response, "error", error.GetCString());
+ dap.debugger.SetAsync(true);
+ break;
+ }
+ SendTerminatedEvent(dap);
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ if (dap.event_thread.joinable()) {
+ dap.broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread);
+ dap.event_thread.join();
+ }
+ if (dap.progress_event_thread.joinable()) {
+ dap.broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);
+ dap.progress_event_thread.join();
+ }
+ dap.StopIO();
+ dap.disconnecting = true;
+}
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 51bd834011b24..e4e5c0ec5af41 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -74,6 +74,13 @@ class ConfigurationDoneRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};
+class DisconnectRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "disconnect"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 9b214d98adb99..efec6b0786691 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -220,18 +220,6 @@ void SendContinuedEvent(DAP &dap) {
dap.SendJSON(llvm::json::Value(std::move(event)));
}
-// Send a "terminated" event to indicate the process is done being
-// debugged.
-void SendTerminatedEvent(DAP &dap) {
- // Prevent races if the process exits while we're being asked to disconnect.
- llvm::call_once(dap.terminated_event_flag, [&] {
- dap.RunTerminateCommands();
- // Send a "terminated" event
- llvm::json::Object event(CreateTerminatedEventObject(dap.target));
- dap.SendJSON(llvm::json::Value(std::move(event)));
- });
-}
-
// Grab any STDOUT and STDERR from the process and send it up to VS Code
// via an "output" event to the "stdout" and "stderr" categories.
void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process) {
@@ -557,95 +545,6 @@ bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
return reached_end_of_stack;
}
-// "DisconnectRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Disconnect request; value of command field is
-// 'disconnect'.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "disconnect" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/DisconnectArguments"
-// }
-// },
-// "required": [ "command" ]
-// }]
-// },
-// "DisconnectArguments": {
-// "type": "object",
-// "description": "Arguments for 'disconnect' request.",
-// "properties": {
-// "terminateDebuggee": {
-// "type": "boolean",
-// "description": "Indicates whether the debuggee should be terminated
-// when the debugger is disconnected. If unspecified,
-// the debug adapter is free to do whatever it thinks
-// is best. A client can only rely on this attribute
-// being properly honored if a debug adapter returns
-// true for the 'supportTerminateDebuggee' capability."
-// },
-// "restart": {
-// "type": "boolean",
-// "description": "Indicates whether the debuggee should be restart
-// the process."
-// }
-// }
-// },
-// "DisconnectResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'disconnect' request. This is just an
-// acknowledgement, so no body field is required."
-// }]
-// }
-void request_disconnect(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- const auto *arguments = request.getObject("arguments");
-
- bool defaultTerminateDebuggee = dap.is_attach ? false : true;
- bool terminateDebuggee =
- GetBoolean(arguments, "terminateDebuggee", defaultTerminateDebuggee);
- lldb::SBProcess process = dap.target.GetProcess();
- auto state = process.GetState();
- switch (state) {
- case lldb::eStateInvalid:
- case lldb::eStateUnloaded:
- case lldb::eStateDetached:
- case lldb::eStateExited:
- break;
- case lldb::eStateConnected:
- case lldb::eStateAttaching:
- case lldb::eStateLaunching:
- case lldb::eStateStepping:
- case lldb::eStateCrashed:
- case lldb::eStateSuspended:
- case lldb::eStateStopped:
- case lldb::eStateRunning:
- dap.debugger.SetAsync(false);
- lldb::SBError error = terminateDebuggee ? process.Kill() : process.Detach();
- if (!error.Success())
- EmplaceSafeString(response, "error", error.GetCString());
- dap.debugger.SetAsync(true);
- break;
- }
- SendTerminatedEvent(dap);
- dap.SendJSON(llvm::json::Value(std::move(response)));
- if (dap.event_thread.joinable()) {
- dap.broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread);
- dap.event_thread.join();
- }
- if (dap.progress_event_thread.joinable()) {
- dap.broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);
- dap.progress_event_thread.join();
- }
- dap.StopIO();
- dap.disconnecting = true;
-}
-
// "ExceptionInfoRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@@ -4147,8 +4046,8 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<CompletionsRequestHandler>();
dap.RegisterRequest<ContinueRequestHandler>();
dap.RegisterRequest<ConfigurationDoneRequestHandler>();
+ dap.RegisterRequest<DisconnectRequestHandler>();
- dap.RegisterRequestCallback("disconnect", request_disconnect);
dap.RegisterRequestCallback("evaluate", request_evaluate);
dap.RegisterRequestCallback("exceptionInfo", request_exceptionInfo);
dap.RegisterRequestCallback("initialize", request_initialize);
>From dfb3b0c8903fda5c02f7c808cb171d0d95d5fa0e Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sat, 22 Feb 2025 19:52:51 -0600
Subject: [PATCH 09/11] Adopt EvaluateRequestHandler
---
lldb/tools/lldb-dap/CMakeLists.txt | 1 +
.../Handler/EvaluateRequestHandler.cpp | 227 ++++++++++++++++++
lldb/tools/lldb-dap/Handler/RequestHandler.h | 7 +
lldb/tools/lldb-dap/lldb-dap.cpp | 212 +---------------
4 files changed, 236 insertions(+), 211 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index bd3aae6a03db3..8942051c9b234 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -43,6 +43,7 @@ add_lldb_tool(lldb-dap
Handler/ConfigurationDoneRequestHandler.cpp
Handler/ContinueRequestHandler.cpp
Handler/DisconnectRequestHandler.cpp
+ Handler/EvaluateRequestHandler.cpp
Handler/RequestHandler.cpp
LINK_LIBS
diff --git a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
new file mode 100644
index 0000000000000..ed4a13a66c488
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
@@ -0,0 +1,227 @@
+//===-- EvaluateRequestHandler.cpp ----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "LLDBUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "EvaluateRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Evaluate request; value of command field is 'evaluate'.
+// Evaluates the given expression in the context of the
+// top most stack frame. The expression has access to any
+// variables and arguments that are in scope.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "evaluate" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/EvaluateArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "EvaluateArguments": {
+// "type": "object",
+// "description": "Arguments for 'evaluate' request.",
+// "properties": {
+// "expression": {
+// "type": "string",
+// "description": "The expression to evaluate."
+// },
+// "frameId": {
+// "type": "integer",
+// "description": "Evaluate the expression in the scope of this stack
+// frame. If not specified, the expression is evaluated
+// in the global scope."
+// },
+// "context": {
+// "type": "string",
+// "_enum": [ "watch", "repl", "hover" ],
+// "enumDescriptions": [
+// "evaluate is run in a watch.",
+// "evaluate is run from REPL console.",
+// "evaluate is run from a data hover."
+// ],
+// "description": "The context in which the evaluate request is run."
+// },
+// "format": {
+// "$ref": "#/definitions/ValueFormat",
+// "description": "Specifies details on how to format the Evaluate
+// result."
+// }
+// },
+// "required": [ "expression" ]
+// },
+// "EvaluateResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'evaluate' request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "result": {
+// "type": "string",
+// "description": "The result of the evaluate request."
+// },
+// "type": {
+// "type": "string",
+// "description": "The optional type of the evaluate result."
+// },
+// "presentationHint": {
+// "$ref": "#/definitions/VariablePresentationHint",
+// "description": "Properties of a evaluate result that can be
+// used to determine how to render the result in
+// the UI."
+// },
+// "variablesReference": {
+// "type": "number",
+// "description": "If variablesReference is > 0, the evaluate
+// result is structured and its children can be
+// retrieved by passing variablesReference to the
+// VariablesRequest."
+// },
+// "namedVariables": {
+// "type": "number",
+// "description": "The number of named child variables. The
+// client can use this optional information to
+// present the variables in a paged UI and fetch
+// them in chunks."
+// },
+// "indexedVariables": {
+// "type": "number",
+// "description": "The number of indexed child variables. The
+// client can use this optional information to
+// present the variables in a paged UI and fetch
+// them in chunks."
+// },
+// "valueLocationReference": {
+// "type": "integer",
+// "description": "A reference that allows the client to request
+// the location where the returned value is
+// declared. For example, if a function pointer is
+// returned, the adapter may be able to look up the
+// function's location. This should be present only
+// if the adapter is likely to be able to resolve
+// the location.\n\nThis reference shares the same
+// lifetime as the `variablesReference`. See
+// 'Lifetime of Object References' in the
+// Overview section for details."
+// }
+// "memoryReference": {
+// "type": "string",
+// "description": "A memory reference to a location appropriate
+// for this result. For pointer type eval
+// results, this is generally a reference to the
+// memory address contained in the pointer. This
+// attribute may be returned by a debug adapter
+// if corresponding capability
+// `supportsMemoryReferences` is true."
+// },
+// },
+// "required": [ "result", "variablesReference" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+void EvaluateRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ llvm::json::Object body;
+ const auto *arguments = request.getObject("arguments");
+ lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
+ std::string expression = GetString(arguments, "expression").str();
+ llvm::StringRef context = GetString(arguments, "context");
+ bool repeat_last_command =
+ expression.empty() && dap.last_nonempty_var_expression.empty();
+
+ if (context == "repl" &&
+ (repeat_last_command ||
+ (!expression.empty() &&
+ dap.DetectReplMode(frame, expression, false) == ReplMode::Command))) {
+ // Since the current expression is not for a variable, clear the
+ // last_nonempty_var_expression field.
+ dap.last_nonempty_var_expression.clear();
+ // If we're evaluating a command relative to the current frame, set the
+ // focus_tid to the current frame for any thread related events.
+ if (frame.IsValid()) {
+ dap.focus_tid = frame.GetThread().GetThreadID();
+ }
+ auto result = RunLLDBCommandsVerbatim(dap.debugger, llvm::StringRef(),
+ {std::string(expression)});
+ EmplaceSafeString(body, "result", result);
+ body.try_emplace("variablesReference", (int64_t)0);
+ } else {
+ if (context == "repl") {
+ // If the expression is empty and the last expression was for a
+ // variable, set the expression to the previous expression (repeat the
+ // evaluation); otherwise save the current non-empty expression for the
+ // next (possibly empty) variable expression.
+ if (expression.empty())
+ expression = dap.last_nonempty_var_expression;
+ else
+ dap.last_nonempty_var_expression = expression;
+ }
+ // Always try to get the answer from the local variables if possible. If
+ // this fails, then if the context is not "hover", actually evaluate an
+ // expression using the expression parser.
+ //
+ // "frame variable" is more reliable than the expression parser in
+ // many cases and it is faster.
+ lldb::SBValue value = frame.GetValueForVariablePath(
+ expression.data(), lldb::eDynamicDontRunTarget);
+
+ // Freeze dry the value in case users expand it later in the debug console
+ if (value.GetError().Success() && context == "repl")
+ value = value.Persist();
+
+ if (value.GetError().Fail() && context != "hover")
+ value = frame.EvaluateExpression(expression.data());
+
+ if (value.GetError().Fail()) {
+ response["success"] = llvm::json::Value(false);
+ // This error object must live until we're done with the pointer returned
+ // by GetCString().
+ lldb::SBError error = value.GetError();
+ const char *error_cstr = error.GetCString();
+ if (error_cstr && error_cstr[0])
+ EmplaceSafeString(response, "message", std::string(error_cstr));
+ else
+ EmplaceSafeString(response, "message", "evaluate failed");
+ } else {
+ VariableDescription desc(value, dap.enable_auto_variable_summaries);
+ EmplaceSafeString(body, "result", desc.GetResult(context));
+ EmplaceSafeString(body, "type", desc.display_type_name);
+ int64_t var_ref = 0;
+ if (value.MightHaveChildren() || ValuePointsToCode(value))
+ var_ref = dap.variables.InsertVariable(
+ value, /*is_permanent=*/context == "repl");
+ if (value.MightHaveChildren())
+ body.try_emplace("variablesReference", var_ref);
+ else
+ body.try_emplace("variablesReference", (int64_t)0);
+ if (lldb::addr_t addr = value.GetLoadAddress();
+ addr != LLDB_INVALID_ADDRESS)
+ body.try_emplace("memoryReference", EncodeMemoryReference(addr));
+ if (ValuePointsToCode(value))
+ body.try_emplace("valueLocationReference", var_ref);
+ }
+ }
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index e4e5c0ec5af41..43f6aac8608dd 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -81,6 +81,13 @@ class DisconnectRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};
+class EvaluateRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "evaluate"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index efec6b0786691..184c6755e145d 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -703,216 +703,6 @@ void request_exceptionInfo(DAP &dap, const llvm::json::Object &request) {
}
-// "EvaluateRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Evaluate request; value of command field is 'evaluate'.
-// Evaluates the given expression in the context of the
-// top most stack frame. The expression has access to any
-// variables and arguments that are in scope.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "evaluate" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/EvaluateArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "EvaluateArguments": {
-// "type": "object",
-// "description": "Arguments for 'evaluate' request.",
-// "properties": {
-// "expression": {
-// "type": "string",
-// "description": "The expression to evaluate."
-// },
-// "frameId": {
-// "type": "integer",
-// "description": "Evaluate the expression in the scope of this stack
-// frame. If not specified, the expression is evaluated
-// in the global scope."
-// },
-// "context": {
-// "type": "string",
-// "_enum": [ "watch", "repl", "hover" ],
-// "enumDescriptions": [
-// "evaluate is run in a watch.",
-// "evaluate is run from REPL console.",
-// "evaluate is run from a data hover."
-// ],
-// "description": "The context in which the evaluate request is run."
-// },
-// "format": {
-// "$ref": "#/definitions/ValueFormat",
-// "description": "Specifies details on how to format the Evaluate
-// result."
-// }
-// },
-// "required": [ "expression" ]
-// },
-// "EvaluateResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'evaluate' request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "result": {
-// "type": "string",
-// "description": "The result of the evaluate request."
-// },
-// "type": {
-// "type": "string",
-// "description": "The optional type of the evaluate result."
-// },
-// "presentationHint": {
-// "$ref": "#/definitions/VariablePresentationHint",
-// "description": "Properties of a evaluate result that can be
-// used to determine how to render the result in
-// the UI."
-// },
-// "variablesReference": {
-// "type": "number",
-// "description": "If variablesReference is > 0, the evaluate
-// result is structured and its children can be
-// retrieved by passing variablesReference to the
-// VariablesRequest."
-// },
-// "namedVariables": {
-// "type": "number",
-// "description": "The number of named child variables. The
-// client can use this optional information to
-// present the variables in a paged UI and fetch
-// them in chunks."
-// },
-// "indexedVariables": {
-// "type": "number",
-// "description": "The number of indexed child variables. The
-// client can use this optional information to
-// present the variables in a paged UI and fetch
-// them in chunks."
-// },
-// "valueLocationReference": {
-// "type": "integer",
-// "description": "A reference that allows the client to request
-// the location where the returned value is
-// declared. For example, if a function pointer is
-// returned, the adapter may be able to look up the
-// function's location. This should be present only
-// if the adapter is likely to be able to resolve
-// the location.\n\nThis reference shares the same
-// lifetime as the `variablesReference`. See
-// 'Lifetime of Object References' in the
-// Overview section for details."
-// }
-// "memoryReference": {
-// "type": "string",
-// "description": "A memory reference to a location appropriate
-// for this result. For pointer type eval
-// results, this is generally a reference to the
-// memory address contained in the pointer. This
-// attribute may be returned by a debug adapter
-// if corresponding capability
-// `supportsMemoryReferences` is true."
-// },
-// },
-// "required": [ "result", "variablesReference" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-void request_evaluate(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Object body;
- const auto *arguments = request.getObject("arguments");
- lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
- std::string expression = GetString(arguments, "expression").str();
- llvm::StringRef context = GetString(arguments, "context");
- bool repeat_last_command =
- expression.empty() && dap.last_nonempty_var_expression.empty();
-
- if (context == "repl" &&
- (repeat_last_command ||
- (!expression.empty() &&
- dap.DetectReplMode(frame, expression, false) == ReplMode::Command))) {
- // Since the current expression is not for a variable, clear the
- // last_nonempty_var_expression field.
- dap.last_nonempty_var_expression.clear();
- // If we're evaluating a command relative to the current frame, set the
- // focus_tid to the current frame for any thread related events.
- if (frame.IsValid()) {
- dap.focus_tid = frame.GetThread().GetThreadID();
- }
- auto result = RunLLDBCommandsVerbatim(dap.debugger, llvm::StringRef(),
- {std::string(expression)});
- EmplaceSafeString(body, "result", result);
- body.try_emplace("variablesReference", (int64_t)0);
- } else {
- if (context == "repl") {
- // If the expression is empty and the last expression was for a
- // variable, set the expression to the previous expression (repeat the
- // evaluation); otherwise save the current non-empty expression for the
- // next (possibly empty) variable expression.
- if (expression.empty())
- expression = dap.last_nonempty_var_expression;
- else
- dap.last_nonempty_var_expression = expression;
- }
- // Always try to get the answer from the local variables if possible. If
- // this fails, then if the context is not "hover", actually evaluate an
- // expression using the expression parser.
- //
- // "frame variable" is more reliable than the expression parser in
- // many cases and it is faster.
- lldb::SBValue value = frame.GetValueForVariablePath(
- expression.data(), lldb::eDynamicDontRunTarget);
-
- // Freeze dry the value in case users expand it later in the debug console
- if (value.GetError().Success() && context == "repl")
- value = value.Persist();
-
- if (value.GetError().Fail() && context != "hover")
- value = frame.EvaluateExpression(expression.data());
-
- if (value.GetError().Fail()) {
- response["success"] = llvm::json::Value(false);
- // This error object must live until we're done with the pointer returned
- // by GetCString().
- lldb::SBError error = value.GetError();
- const char *error_cstr = error.GetCString();
- if (error_cstr && error_cstr[0])
- EmplaceSafeString(response, "message", std::string(error_cstr));
- else
- EmplaceSafeString(response, "message", "evaluate failed");
- } else {
- VariableDescription desc(value, dap.enable_auto_variable_summaries);
- EmplaceSafeString(body, "result", desc.GetResult(context));
- EmplaceSafeString(body, "type", desc.display_type_name);
- int64_t var_ref = 0;
- if (value.MightHaveChildren() || ValuePointsToCode(value))
- var_ref = dap.variables.InsertVariable(
- value, /*is_permanent=*/context == "repl");
- if (value.MightHaveChildren())
- body.try_emplace("variablesReference", var_ref);
- else
- body.try_emplace("variablesReference", (int64_t)0);
- if (lldb::addr_t addr = value.GetLoadAddress();
- addr != LLDB_INVALID_ADDRESS)
- body.try_emplace("memoryReference", EncodeMemoryReference(addr));
- if (ValuePointsToCode(value))
- body.try_emplace("valueLocationReference", var_ref);
- }
- }
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
// "compileUnitsRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
@@ -4047,8 +3837,8 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<ContinueRequestHandler>();
dap.RegisterRequest<ConfigurationDoneRequestHandler>();
dap.RegisterRequest<DisconnectRequestHandler>();
+ dap.RegisterRequest<EvaluateRequestHandler>();
- dap.RegisterRequestCallback("evaluate", request_evaluate);
dap.RegisterRequestCallback("exceptionInfo", request_exceptionInfo);
dap.RegisterRequestCallback("initialize", request_initialize);
dap.RegisterRequestCallback("launch", request_launch);
>From a8d212db406b898c87c0c11d5d0588169bf6a049 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sat, 22 Feb 2025 19:56:33 -0600
Subject: [PATCH 10/11] Adopt ExceptionInfoRequestHandler
---
lldb/tools/lldb-dap/CMakeLists.txt | 1 +
.../Handler/ExceptionInfoRequestHandler.cpp | 174 ++++++++++++++++++
lldb/tools/lldb-dap/Handler/RequestHandler.h | 7 +
lldb/tools/lldb-dap/lldb-dap.cpp | 161 +---------------
4 files changed, 183 insertions(+), 160 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index 8942051c9b234..8357da56ab5ff 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -44,6 +44,7 @@ add_lldb_tool(lldb-dap
Handler/ContinueRequestHandler.cpp
Handler/DisconnectRequestHandler.cpp
Handler/EvaluateRequestHandler.cpp
+ Handler/ExceptionInfoRequestHandler.cpp
Handler/RequestHandler.cpp
LINK_LIBS
diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp
new file mode 100644
index 0000000000000..ca12e505e0630
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp
@@ -0,0 +1,174 @@
+//===-- ExceptionInfoRequestHandler.cpp -----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+#include "lldb/API/SBStream.h"
+
+namespace lldb_dap {
+
+// "ExceptionInfoRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Retrieves the details of the exception that
+// caused this event to be raised. Clients should only call this request if
+// the corresponding capability `supportsExceptionInfoRequest` is true.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "exceptionInfo" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/ExceptionInfoArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "ExceptionInfoArguments": {
+// "type": "object",
+// "description": "Arguments for `exceptionInfo` request.",
+// "properties": {
+// "threadId": {
+// "type": "integer",
+// "description": "Thread for which exception information should be
+// retrieved."
+// }
+// },
+// "required": [ "threadId" ]
+// },
+// "ExceptionInfoResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to `exceptionInfo` request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "exceptionId": {
+// "type": "string",
+// "description": "ID of the exception that was thrown."
+// },
+// "description": {
+// "type": "string",
+// "description": "Descriptive text for the exception."
+// },
+// "breakMode": {
+// "$ref": "#/definitions/ExceptionBreakMode",
+// "description": "Mode that caused the exception notification to
+// be raised."
+// },
+// "details": {
+// "$ref": "#/definitions/ExceptionDetails",
+// "description": "Detailed information about the exception."
+// }
+// },
+// "required": [ "exceptionId", "breakMode" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+// "ExceptionDetails": {
+// "type": "object",
+// "description": "Detailed information about an exception that has
+// occurred.", "properties": {
+// "message": {
+// "type": "string",
+// "description": "Message contained in the exception."
+// },
+// "typeName": {
+// "type": "string",
+// "description": "Short type name of the exception object."
+// },
+// "fullTypeName": {
+// "type": "string",
+// "description": "Fully-qualified type name of the exception object."
+// },
+// "evaluateName": {
+// "type": "string",
+// "description": "An expression that can be evaluated in the current
+// scope to obtain the exception object."
+// },
+// "stackTrace": {
+// "type": "string",
+// "description": "Stack trace at the time the exception was thrown."
+// },
+// "innerException": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/ExceptionDetails"
+// },
+// "description": "Details of the exception contained by this exception,
+// if any."
+// }
+// }
+// },
+void ExceptionInfoRequestHandler::operator()(
+ const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ const auto *arguments = request.getObject("arguments");
+ llvm::json::Object body;
+ lldb::SBThread thread = dap.GetLLDBThread(*arguments);
+ if (thread.IsValid()) {
+ auto stopReason = thread.GetStopReason();
+ if (stopReason == lldb::eStopReasonSignal)
+ body.try_emplace("exceptionId", "signal");
+ else if (stopReason == lldb::eStopReasonBreakpoint) {
+ ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread);
+ if (exc_bp) {
+ EmplaceSafeString(body, "exceptionId", exc_bp->filter);
+ EmplaceSafeString(body, "description", exc_bp->label);
+ } else {
+ body.try_emplace("exceptionId", "exception");
+ }
+ } else {
+ body.try_emplace("exceptionId", "exception");
+ }
+ if (!ObjectContainsKey(body, "description")) {
+ char description[1024];
+ if (thread.GetStopDescription(description, sizeof(description))) {
+ EmplaceSafeString(body, "description", std::string(description));
+ }
+ }
+ body.try_emplace("breakMode", "always");
+ auto exception = thread.GetCurrentException();
+ if (exception.IsValid()) {
+ llvm::json::Object details;
+ lldb::SBStream stream;
+ if (exception.GetDescription(stream)) {
+ EmplaceSafeString(details, "message", stream.GetData());
+ }
+
+ auto exceptionBacktrace = thread.GetCurrentExceptionBacktrace();
+ if (exceptionBacktrace.IsValid()) {
+ lldb::SBStream stream;
+ exceptionBacktrace.GetDescription(stream);
+ for (uint32_t i = 0; i < exceptionBacktrace.GetNumFrames(); i++) {
+ lldb::SBFrame frame = exceptionBacktrace.GetFrameAtIndex(i);
+ frame.GetDescription(stream);
+ }
+ EmplaceSafeString(details, "stackTrace", stream.GetData());
+ }
+
+ body.try_emplace("details", std::move(details));
+ }
+ // auto excInfoCount = thread.GetStopReasonDataCount();
+ // for (auto i=0; i<excInfoCount; ++i) {
+ // uint64_t exc_data = thread.GetStopReasonDataAtIndex(i);
+ // }
+ } else {
+ response["success"] = llvm::json::Value(false);
+ }
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 43f6aac8608dd..a151fc19d7fbb 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -88,6 +88,13 @@ class EvaluateRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};
+class ExceptionInfoRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "exceptionInfo"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 184c6755e145d..ab904bbee52be 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -545,165 +545,6 @@ bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
return reached_end_of_stack;
}
-// "ExceptionInfoRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Retrieves the details of the exception that
-// caused this event to be raised. Clients should only call this request if
-// the corresponding capability `supportsExceptionInfoRequest` is true.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "exceptionInfo" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/ExceptionInfoArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "ExceptionInfoArguments": {
-// "type": "object",
-// "description": "Arguments for `exceptionInfo` request.",
-// "properties": {
-// "threadId": {
-// "type": "integer",
-// "description": "Thread for which exception information should be
-// retrieved."
-// }
-// },
-// "required": [ "threadId" ]
-// },
-// "ExceptionInfoResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to `exceptionInfo` request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "exceptionId": {
-// "type": "string",
-// "description": "ID of the exception that was thrown."
-// },
-// "description": {
-// "type": "string",
-// "description": "Descriptive text for the exception."
-// },
-// "breakMode": {
-// "$ref": "#/definitions/ExceptionBreakMode",
-// "description": "Mode that caused the exception notification to
-// be raised."
-// },
-// "details": {
-// "$ref": "#/definitions/ExceptionDetails",
-// "description": "Detailed information about the exception."
-// }
-// },
-// "required": [ "exceptionId", "breakMode" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-// "ExceptionDetails": {
-// "type": "object",
-// "description": "Detailed information about an exception that has
-// occurred.", "properties": {
-// "message": {
-// "type": "string",
-// "description": "Message contained in the exception."
-// },
-// "typeName": {
-// "type": "string",
-// "description": "Short type name of the exception object."
-// },
-// "fullTypeName": {
-// "type": "string",
-// "description": "Fully-qualified type name of the exception object."
-// },
-// "evaluateName": {
-// "type": "string",
-// "description": "An expression that can be evaluated in the current
-// scope to obtain the exception object."
-// },
-// "stackTrace": {
-// "type": "string",
-// "description": "Stack trace at the time the exception was thrown."
-// },
-// "innerException": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/ExceptionDetails"
-// },
-// "description": "Details of the exception contained by this exception,
-// if any."
-// }
-// }
-// },
-void request_exceptionInfo(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- const auto *arguments = request.getObject("arguments");
- llvm::json::Object body;
- lldb::SBThread thread = dap.GetLLDBThread(*arguments);
- if (thread.IsValid()) {
- auto stopReason = thread.GetStopReason();
- if (stopReason == lldb::eStopReasonSignal)
- body.try_emplace("exceptionId", "signal");
- else if (stopReason == lldb::eStopReasonBreakpoint) {
- ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread);
- if (exc_bp) {
- EmplaceSafeString(body, "exceptionId", exc_bp->filter);
- EmplaceSafeString(body, "description", exc_bp->label);
- } else {
- body.try_emplace("exceptionId", "exception");
- }
- } else {
- body.try_emplace("exceptionId", "exception");
- }
- if (!ObjectContainsKey(body, "description")) {
- char description[1024];
- if (thread.GetStopDescription(description, sizeof(description))) {
- EmplaceSafeString(body, "description", std::string(description));
- }
- }
- body.try_emplace("breakMode", "always");
- auto exception = thread.GetCurrentException();
- if (exception.IsValid()) {
- llvm::json::Object details;
- lldb::SBStream stream;
- if (exception.GetDescription(stream)) {
- EmplaceSafeString(details, "message", stream.GetData());
- }
-
- auto exceptionBacktrace = thread.GetCurrentExceptionBacktrace();
- if (exceptionBacktrace.IsValid()) {
- lldb::SBStream stream;
- exceptionBacktrace.GetDescription(stream);
- for (uint32_t i = 0; i < exceptionBacktrace.GetNumFrames(); i++) {
- lldb::SBFrame frame = exceptionBacktrace.GetFrameAtIndex(i);
- frame.GetDescription(stream);
- }
- EmplaceSafeString(details, "stackTrace", stream.GetData());
- }
-
- body.try_emplace("details", std::move(details));
- }
- // auto excInfoCount = thread.GetStopReasonDataCount();
- // for (auto i=0; i<excInfoCount; ++i) {
- // uint64_t exc_data = thread.GetStopReasonDataAtIndex(i);
- // }
- } else {
- response["success"] = llvm::json::Value(false);
- }
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-
-
// "compileUnitsRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@@ -3838,8 +3679,8 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<ConfigurationDoneRequestHandler>();
dap.RegisterRequest<DisconnectRequestHandler>();
dap.RegisterRequest<EvaluateRequestHandler>();
+ dap.RegisterRequest<ExceptionInfoRequestHandler>();
- dap.RegisterRequestCallback("exceptionInfo", request_exceptionInfo);
dap.RegisterRequestCallback("initialize", request_initialize);
dap.RegisterRequestCallback("launch", request_launch);
dap.RegisterRequestCallback("next", request_next);
>From feae281935860be7d10258dca017e850f9cb79ec Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sat, 22 Feb 2025 20:08:04 -0600
Subject: [PATCH 11/11] Adopt InitializeRequestHandler
---
lldb/tools/lldb-dap/CMakeLists.txt | 1 +
lldb/tools/lldb-dap/EventHelper.cpp | 41 ++
lldb/tools/lldb-dap/EventHelper.h | 8 +
.../Handler/InitializeRequestHandler.cpp | 412 +++++++++++++++++
lldb/tools/lldb-dap/Handler/RequestHandler.h | 7 +
lldb/tools/lldb-dap/lldb-dap.cpp | 434 +-----------------
6 files changed, 470 insertions(+), 433 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index 8357da56ab5ff..141c555eb4ae0 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -43,6 +43,7 @@ add_lldb_tool(lldb-dap
Handler/ConfigurationDoneRequestHandler.cpp
Handler/ContinueRequestHandler.cpp
Handler/DisconnectRequestHandler.cpp
+ Handler/InitializeRequestHandler.cpp
Handler/EvaluateRequestHandler.cpp
Handler/ExceptionInfoRequestHandler.cpp
Handler/RequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp
index f59c73a6593c1..20845fbc5b916 100644
--- a/lldb/tools/lldb-dap/EventHelper.cpp
+++ b/lldb/tools/lldb-dap/EventHelper.cpp
@@ -194,4 +194,45 @@ void SendTerminatedEvent(DAP &dap) {
});
}
+// Grab any STDOUT and STDERR from the process and send it up to VS Code
+// via an "output" event to the "stdout" and "stderr" categories.
+void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process) {
+ char buffer[OutputBufferSize];
+ size_t count;
+ while ((count = process.GetSTDOUT(buffer, sizeof(buffer))) > 0)
+ dap.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count));
+ while ((count = process.GetSTDERR(buffer, sizeof(buffer))) > 0)
+ dap.SendOutput(OutputType::Stderr, llvm::StringRef(buffer, count));
+}
+
+// Send a "continued" event to indicate the process is in the running state.
+void SendContinuedEvent(DAP &dap) {
+ lldb::SBProcess process = dap.target.GetProcess();
+ if (!process.IsValid()) {
+ return;
+ }
+
+ // If the focus thread is not set then we haven't reported any thread status
+ // to the client, so nothing to report.
+ if (!dap.configuration_done_sent || dap.focus_tid == LLDB_INVALID_THREAD_ID) {
+ return;
+ }
+
+ llvm::json::Object event(CreateEventObject("continued"));
+ llvm::json::Object body;
+ body.try_emplace("threadId", (int64_t)dap.focus_tid);
+ body.try_emplace("allThreadsContinued", true);
+ event.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(event)));
+}
+
+// Send a "exited" event to indicate the process has exited.
+void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) {
+ llvm::json::Object event(CreateEventObject("exited"));
+ llvm::json::Object body;
+ body.try_emplace("exitCode", (int64_t)process.GetExitStatus());
+ event.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(event)));
+}
+
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h
index 0c5978e1db49b..90b009c73089e 100644
--- a/lldb/tools/lldb-dap/EventHelper.h
+++ b/lldb/tools/lldb-dap/EventHelper.h
@@ -9,6 +9,8 @@
#ifndef LLDB_TOOLS_LLDB_DAP_EVENTHELPER_H
#define LLDB_TOOLS_LLDB_DAP_EVENTHELPER_H
+#include "DAPForward.h"
+
namespace lldb_dap {
struct DAP;
@@ -20,6 +22,12 @@ void SendThreadStoppedEvent(DAP &dap);
void SendTerminatedEvent(DAP &dap);
+void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process);
+
+void SendContinuedEvent(DAP &dap);
+
+void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process);
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
new file mode 100644
index 0000000000000..9823678b77f18
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
@@ -0,0 +1,412 @@
+//===-- InitializeRequestHandler.cpp --------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+#include "lldb/API/SBEvent.h"
+#include "lldb/API/SBListener.h"
+#include "lldb/API/SBStream.h"
+
+using namespace lldb;
+
+namespace lldb_dap {
+
+static void ProgressEventThreadFunction(DAP &dap) {
+ lldb::SBListener listener("lldb-dap.progress.listener");
+ dap.debugger.GetBroadcaster().AddListener(
+ listener, lldb::SBDebugger::eBroadcastBitProgress |
+ lldb::SBDebugger::eBroadcastBitExternalProgress);
+ dap.broadcaster.AddListener(listener, eBroadcastBitStopProgressThread);
+ lldb::SBEvent event;
+ bool done = false;
+ while (!done) {
+ if (listener.WaitForEvent(1, event)) {
+ const auto event_mask = event.GetType();
+ if (event.BroadcasterMatchesRef(dap.broadcaster)) {
+ if (event_mask & eBroadcastBitStopProgressThread) {
+ done = true;
+ }
+ } else {
+ uint64_t progress_id = 0;
+ uint64_t completed = 0;
+ uint64_t total = 0;
+ bool is_debugger_specific = false;
+ const char *message = lldb::SBDebugger::GetProgressFromEvent(
+ event, progress_id, completed, total, is_debugger_specific);
+ if (message)
+ dap.SendProgressEvent(progress_id, message, completed, total);
+ }
+ }
+ }
+}
+
+// All events from the debugger, target, process, thread and frames are
+// received in this function that runs in its own thread. We are using a
+// "FILE *" to output packets back to VS Code and they have mutexes in them
+// them prevent multiple threads from writing simultaneously so no locking
+// is required.
+static void EventThreadFunction(DAP &dap) {
+ lldb::SBEvent event;
+ lldb::SBListener listener = dap.debugger.GetListener();
+ bool done = false;
+ while (!done) {
+ if (listener.WaitForEvent(1, event)) {
+ const auto event_mask = event.GetType();
+ if (lldb::SBProcess::EventIsProcessEvent(event)) {
+ lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event);
+ if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) {
+ auto state = lldb::SBProcess::GetStateFromEvent(event);
+ switch (state) {
+ case lldb::eStateInvalid:
+ // Not a state event
+ break;
+ case lldb::eStateUnloaded:
+ break;
+ case lldb::eStateConnected:
+ break;
+ case lldb::eStateAttaching:
+ break;
+ case lldb::eStateLaunching:
+ break;
+ case lldb::eStateStepping:
+ break;
+ case lldb::eStateCrashed:
+ break;
+ case lldb::eStateDetached:
+ break;
+ case lldb::eStateSuspended:
+ break;
+ case lldb::eStateStopped:
+ // We launch and attach in synchronous mode then the first stop
+ // event will not be delivered. If we use "launchCommands" during a
+ // launch or "attachCommands" during an attach we might some process
+ // stop events which we do not want to send an event for. We will
+ // manually send a stopped event in request_configurationDone(...)
+ // so don't send any before then.
+ if (dap.configuration_done_sent) {
+ // Only report a stopped event if the process was not
+ // automatically restarted.
+ if (!lldb::SBProcess::GetRestartedFromEvent(event)) {
+ SendStdOutStdErr(dap, process);
+ SendThreadStoppedEvent(dap);
+ }
+ }
+ break;
+ case lldb::eStateRunning:
+ dap.WillContinue();
+ SendContinuedEvent(dap);
+ break;
+ case lldb::eStateExited:
+ lldb::SBStream stream;
+ process.GetStatus(stream);
+ dap.SendOutput(OutputType::Console, stream.GetData());
+
+ // When restarting, we can get an "exited" event for the process we
+ // just killed with the old PID, or even with no PID. In that case
+ // we don't have to terminate the session.
+ if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID ||
+ process.GetProcessID() == dap.restarting_process_id) {
+ dap.restarting_process_id = LLDB_INVALID_PROCESS_ID;
+ } else {
+ // Run any exit LLDB commands the user specified in the
+ // launch.json
+ dap.RunExitCommands();
+ SendProcessExitedEvent(dap, process);
+ SendTerminatedEvent(dap);
+ done = true;
+ }
+ break;
+ }
+ } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) ||
+ (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) {
+ SendStdOutStdErr(dap, process);
+ }
+ } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) {
+ if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) {
+ auto event_type =
+ lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event);
+ auto bp = Breakpoint(
+ dap, lldb::SBBreakpoint::GetBreakpointFromEvent(event));
+ // If the breakpoint was originated from the IDE, it will have the
+ // BreakpointBase::GetBreakpointLabel() label attached. Regardless
+ // of wether the locations were added or removed, the breakpoint
+ // ins't going away, so we the reason is always "changed".
+ if ((event_type & lldb::eBreakpointEventTypeLocationsAdded ||
+ event_type & lldb::eBreakpointEventTypeLocationsRemoved) &&
+ bp.MatchesName(BreakpointBase::GetBreakpointLabel())) {
+ auto bp_event = CreateEventObject("breakpoint");
+ llvm::json::Object body;
+ // As VSCode already knows the path of this breakpoint, we don't
+ // need to send it back as part of a "changed" event. This
+ // prevent us from sending to VSCode paths that should be source
+ // mapped. Note that CreateBreakpoint doesn't apply source mapping.
+ // Besides, the current implementation of VSCode ignores the
+ // "source" element of breakpoint events.
+ llvm::json::Value source_bp = CreateBreakpoint(&bp);
+ source_bp.getAsObject()->erase("source");
+
+ body.try_emplace("breakpoint", source_bp);
+ body.try_emplace("reason", "changed");
+ bp_event.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(bp_event)));
+ }
+ }
+ } else if (event.BroadcasterMatchesRef(dap.broadcaster)) {
+ if (event_mask & eBroadcastBitStopEventThread) {
+ done = true;
+ }
+ }
+ }
+ }
+}
+
+// "InitializeRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Initialize request; value of command field is
+// 'initialize'.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "initialize" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/InitializeRequestArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "InitializeRequestArguments": {
+// "type": "object",
+// "description": "Arguments for 'initialize' request.",
+// "properties": {
+// "clientID": {
+// "type": "string",
+// "description": "The ID of the (frontend) client using this adapter."
+// },
+// "adapterID": {
+// "type": "string",
+// "description": "The ID of the debug adapter."
+// },
+// "locale": {
+// "type": "string",
+// "description": "The ISO-639 locale of the (frontend) client using
+// this adapter, e.g. en-US or de-CH."
+// },
+// "linesStartAt1": {
+// "type": "boolean",
+// "description": "If true all line numbers are 1-based (default)."
+// },
+// "columnsStartAt1": {
+// "type": "boolean",
+// "description": "If true all column numbers are 1-based (default)."
+// },
+// "pathFormat": {
+// "type": "string",
+// "_enum": [ "path", "uri" ],
+// "description": "Determines in what format paths are specified. The
+// default is 'path', which is the native format."
+// },
+// "supportsVariableType": {
+// "type": "boolean",
+// "description": "Client supports the optional type attribute for
+// variables."
+// },
+// "supportsVariablePaging": {
+// "type": "boolean",
+// "description": "Client supports the paging of variables."
+// },
+// "supportsRunInTerminalRequest": {
+// "type": "boolean",
+// "description": "Client supports the runInTerminal request."
+// }
+// },
+// "required": [ "adapterID" ]
+// },
+// "InitializeResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'initialize' request.",
+// "properties": {
+// "body": {
+// "$ref": "#/definitions/Capabilities",
+// "description": "The capabilities of this debug adapter."
+// }
+// }
+// }]
+// }
+void InitializeRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ llvm::json::Object body;
+
+ const auto *arguments = request.getObject("arguments");
+ // sourceInitFile option is not from formal DAP specification. It is only
+ // used by unit tests to prevent sourcing .lldbinit files from environment
+ // which may affect the outcome of tests.
+ bool source_init_file = GetBoolean(arguments, "sourceInitFile", true);
+
+ // Do not source init files until in/out/err are configured.
+ dap.debugger = lldb::SBDebugger::Create(false);
+ dap.debugger.SetInputFile(dap.in);
+ auto out_fd = dap.out.GetWriteFileDescriptor();
+ if (llvm::Error err = out_fd.takeError()) {
+ response["success"] = false;
+ EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+ dap.debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false));
+ auto err_fd = dap.err.GetWriteFileDescriptor();
+ if (llvm::Error err = err_fd.takeError()) {
+ response["success"] = false;
+ EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+ dap.debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false));
+
+ auto interp = dap.debugger.GetCommandInterpreter();
+
+ if (source_init_file) {
+ dap.debugger.SkipLLDBInitFiles(false);
+ dap.debugger.SkipAppInitFiles(false);
+ lldb::SBCommandReturnObject init;
+ interp.SourceInitFileInGlobalDirectory(init);
+ interp.SourceInitFileInHomeDirectory(init);
+ }
+
+ if (llvm::Error err = dap.RunPreInitCommands()) {
+ response["success"] = false;
+ EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+
+ dap.PopulateExceptionBreakpoints();
+ auto cmd = dap.debugger.GetCommandInterpreter().AddMultiwordCommand(
+ "lldb-dap", "Commands for managing lldb-dap.");
+ if (GetBoolean(arguments, "supportsStartDebuggingRequest", false)) {
+ cmd.AddCommand(
+ "start-debugging", new StartDebuggingRequestHandler(dap),
+ "Sends a startDebugging request from the debug adapter to the client "
+ "to start a child debug session of the same type as the caller.");
+ }
+ cmd.AddCommand(
+ "repl-mode", new ReplModeRequestHandler(dap),
+ "Get or set the repl behavior of lldb-dap evaluation requests.");
+ cmd.AddCommand("send-event", new SendEventRequestHandler(dap),
+ "Sends an DAP event to the client.");
+
+ dap.progress_event_thread =
+ std::thread(ProgressEventThreadFunction, std::ref(dap));
+
+ // Start our event thread so we can receive events from the debugger, target,
+ // process and more.
+ dap.event_thread = std::thread(EventThreadFunction, std::ref(dap));
+
+ // The debug adapter supports the configurationDoneRequest.
+ body.try_emplace("supportsConfigurationDoneRequest", true);
+ // The debug adapter supports function breakpoints.
+ body.try_emplace("supportsFunctionBreakpoints", true);
+ // The debug adapter supports conditional breakpoints.
+ body.try_emplace("supportsConditionalBreakpoints", true);
+ // The debug adapter supports breakpoints that break execution after a
+ // specified number of hits.
+ body.try_emplace("supportsHitConditionalBreakpoints", true);
+ // The debug adapter supports a (side effect free) evaluate request for
+ // data hovers.
+ body.try_emplace("supportsEvaluateForHovers", true);
+ // Available filters or options for the setExceptionBreakpoints request.
+ llvm::json::Array filters;
+ for (const auto &exc_bp : *dap.exception_breakpoints) {
+ filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp));
+ }
+ body.try_emplace("exceptionBreakpointFilters", std::move(filters));
+ // The debug adapter supports launching a debugee in intergrated VSCode
+ // terminal.
+ body.try_emplace("supportsRunInTerminalRequest", true);
+ // The debug adapter supports stepping back via the stepBack and
+ // reverseContinue requests.
+ body.try_emplace("supportsStepBack", false);
+ // The debug adapter supports setting a variable to a value.
+ body.try_emplace("supportsSetVariable", true);
+ // The debug adapter supports restarting a frame.
+ body.try_emplace("supportsRestartFrame", false);
+ // The debug adapter supports the gotoTargetsRequest.
+ body.try_emplace("supportsGotoTargetsRequest", false);
+ // The debug adapter supports the stepInTargetsRequest.
+ body.try_emplace("supportsStepInTargetsRequest", true);
+ // The debug adapter supports the completions request.
+ body.try_emplace("supportsCompletionsRequest", true);
+ // The debug adapter supports the disassembly request.
+ body.try_emplace("supportsDisassembleRequest", true);
+ // The debug adapter supports the `breakpointLocations` request.
+ body.try_emplace("supportsBreakpointLocationsRequest", true);
+ // The debug adapter supports stepping granularities (argument `granularity`)
+ // for the stepping requests.
+ body.try_emplace("supportsSteppingGranularity", true);
+ // The debug adapter support for instruction breakpoint.
+ body.try_emplace("supportsInstructionBreakpoints", true);
+
+ llvm::json::Array completion_characters;
+ completion_characters.emplace_back(".");
+ completion_characters.emplace_back(" ");
+ completion_characters.emplace_back("\t");
+ body.try_emplace("completionTriggerCharacters",
+ std::move(completion_characters));
+
+ // The debug adapter supports the modules request.
+ body.try_emplace("supportsModulesRequest", true);
+ // The set of additional module information exposed by the debug adapter.
+ // body.try_emplace("additionalModuleColumns"] = ColumnDescriptor
+ // Checksum algorithms supported by the debug adapter.
+ // body.try_emplace("supportedChecksumAlgorithms"] = ChecksumAlgorithm
+ // The debug adapter supports the RestartRequest. In this case a client
+ // should not implement 'restart' by terminating and relaunching the adapter
+ // but by calling the RestartRequest.
+ body.try_emplace("supportsRestartRequest", true);
+ // The debug adapter supports 'exceptionOptions' on the
+ // setExceptionBreakpoints request.
+ body.try_emplace("supportsExceptionOptions", true);
+ // The debug adapter supports a 'format' attribute on the stackTraceRequest,
+ // variablesRequest, and evaluateRequest.
+ body.try_emplace("supportsValueFormattingOptions", true);
+ // The debug adapter supports the exceptionInfo request.
+ body.try_emplace("supportsExceptionInfoRequest", true);
+ // The debug adapter supports the 'terminateDebuggee' attribute on the
+ // 'disconnect' request.
+ body.try_emplace("supportTerminateDebuggee", true);
+ // The debug adapter supports the delayed loading of parts of the stack,
+ // which requires that both the 'startFrame' and 'levels' arguments and the
+ // 'totalFrames' result of the 'StackTrace' request are supported.
+ body.try_emplace("supportsDelayedStackTraceLoading", true);
+ // The debug adapter supports the 'loadedSources' request.
+ body.try_emplace("supportsLoadedSourcesRequest", false);
+ // The debug adapter supports sending progress reporting events.
+ body.try_emplace("supportsProgressReporting", true);
+ // The debug adapter supports 'logMessage' in breakpoint.
+ body.try_emplace("supportsLogPoints", true);
+ // The debug adapter supports data watchpoints.
+ body.try_emplace("supportsDataBreakpoints", true);
+ // The debug adapter supports the `readMemory` request.
+ body.try_emplace("supportsReadMemoryRequest", true);
+
+ // Put in non-DAP specification lldb specific information.
+ llvm::json::Object lldb_json;
+ lldb_json.try_emplace("version", dap.debugger.GetVersionString());
+ body.try_emplace("__lldb", std::move(lldb_json));
+
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index a151fc19d7fbb..af0fb63606759 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -95,6 +95,13 @@ class ExceptionInfoRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};
+class InitializeRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "initialize"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index ab904bbee52be..eb5a23fe155e7 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -190,196 +190,6 @@ std::vector<const char *> MakeArgv(const llvm::ArrayRef<std::string> &strs) {
return argv;
}
-// Send a "exited" event to indicate the process has exited.
-void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) {
- llvm::json::Object event(CreateEventObject("exited"));
- llvm::json::Object body;
- body.try_emplace("exitCode", (int64_t)process.GetExitStatus());
- event.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(event)));
-}
-
-// Send a "continued" event to indicate the process is in the running state.
-void SendContinuedEvent(DAP &dap) {
- lldb::SBProcess process = dap.target.GetProcess();
- if (!process.IsValid()) {
- return;
- }
-
- // If the focus thread is not set then we haven't reported any thread status
- // to the client, so nothing to report.
- if (!dap.configuration_done_sent || dap.focus_tid == LLDB_INVALID_THREAD_ID) {
- return;
- }
-
- llvm::json::Object event(CreateEventObject("continued"));
- llvm::json::Object body;
- body.try_emplace("threadId", (int64_t)dap.focus_tid);
- body.try_emplace("allThreadsContinued", true);
- event.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(event)));
-}
-
-// Grab any STDOUT and STDERR from the process and send it up to VS Code
-// via an "output" event to the "stdout" and "stderr" categories.
-void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process) {
- char buffer[OutputBufferSize];
- size_t count;
- while ((count = process.GetSTDOUT(buffer, sizeof(buffer))) > 0)
- dap.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count));
- while ((count = process.GetSTDERR(buffer, sizeof(buffer))) > 0)
- dap.SendOutput(OutputType::Stderr, llvm::StringRef(buffer, count));
-}
-
-void ProgressEventThreadFunction(DAP &dap) {
- lldb::SBListener listener("lldb-dap.progress.listener");
- dap.debugger.GetBroadcaster().AddListener(
- listener, lldb::SBDebugger::eBroadcastBitProgress |
- lldb::SBDebugger::eBroadcastBitExternalProgress);
- dap.broadcaster.AddListener(listener, eBroadcastBitStopProgressThread);
- lldb::SBEvent event;
- bool done = false;
- while (!done) {
- if (listener.WaitForEvent(1, event)) {
- const auto event_mask = event.GetType();
- if (event.BroadcasterMatchesRef(dap.broadcaster)) {
- if (event_mask & eBroadcastBitStopProgressThread) {
- done = true;
- }
- } else {
- uint64_t progress_id = 0;
- uint64_t completed = 0;
- uint64_t total = 0;
- bool is_debugger_specific = false;
- const char *message = lldb::SBDebugger::GetProgressFromEvent(
- event, progress_id, completed, total, is_debugger_specific);
- if (message)
- dap.SendProgressEvent(progress_id, message, completed, total);
- }
- }
- }
-}
-
-// All events from the debugger, target, process, thread and frames are
-// received in this function that runs in its own thread. We are using a
-// "FILE *" to output packets back to VS Code and they have mutexes in them
-// them prevent multiple threads from writing simultaneously so no locking
-// is required.
-void EventThreadFunction(DAP &dap) {
- lldb::SBEvent event;
- lldb::SBListener listener = dap.debugger.GetListener();
- bool done = false;
- while (!done) {
- if (listener.WaitForEvent(1, event)) {
- const auto event_mask = event.GetType();
- if (lldb::SBProcess::EventIsProcessEvent(event)) {
- lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event);
- if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) {
- auto state = lldb::SBProcess::GetStateFromEvent(event);
- switch (state) {
- case lldb::eStateInvalid:
- // Not a state event
- break;
- case lldb::eStateUnloaded:
- break;
- case lldb::eStateConnected:
- break;
- case lldb::eStateAttaching:
- break;
- case lldb::eStateLaunching:
- break;
- case lldb::eStateStepping:
- break;
- case lldb::eStateCrashed:
- break;
- case lldb::eStateDetached:
- break;
- case lldb::eStateSuspended:
- break;
- case lldb::eStateStopped:
- // We launch and attach in synchronous mode then the first stop
- // event will not be delivered. If we use "launchCommands" during a
- // launch or "attachCommands" during an attach we might some process
- // stop events which we do not want to send an event for. We will
- // manually send a stopped event in request_configurationDone(...)
- // so don't send any before then.
- if (dap.configuration_done_sent) {
- // Only report a stopped event if the process was not
- // automatically restarted.
- if (!lldb::SBProcess::GetRestartedFromEvent(event)) {
- SendStdOutStdErr(dap, process);
- SendThreadStoppedEvent(dap);
- }
- }
- break;
- case lldb::eStateRunning:
- dap.WillContinue();
- SendContinuedEvent(dap);
- break;
- case lldb::eStateExited:
- lldb::SBStream stream;
- process.GetStatus(stream);
- dap.SendOutput(OutputType::Console, stream.GetData());
-
- // When restarting, we can get an "exited" event for the process we
- // just killed with the old PID, or even with no PID. In that case
- // we don't have to terminate the session.
- if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID ||
- process.GetProcessID() == dap.restarting_process_id) {
- dap.restarting_process_id = LLDB_INVALID_PROCESS_ID;
- } else {
- // Run any exit LLDB commands the user specified in the
- // launch.json
- dap.RunExitCommands();
- SendProcessExitedEvent(dap, process);
- SendTerminatedEvent(dap);
- done = true;
- }
- break;
- }
- } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) ||
- (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) {
- SendStdOutStdErr(dap, process);
- }
- } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) {
- if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) {
- auto event_type =
- lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event);
- auto bp = Breakpoint(
- dap, lldb::SBBreakpoint::GetBreakpointFromEvent(event));
- // If the breakpoint was originated from the IDE, it will have the
- // BreakpointBase::GetBreakpointLabel() label attached. Regardless
- // of wether the locations were added or removed, the breakpoint
- // ins't going away, so we the reason is always "changed".
- if ((event_type & lldb::eBreakpointEventTypeLocationsAdded ||
- event_type & lldb::eBreakpointEventTypeLocationsRemoved) &&
- bp.MatchesName(BreakpointBase::GetBreakpointLabel())) {
- auto bp_event = CreateEventObject("breakpoint");
- llvm::json::Object body;
- // As VSCode already knows the path of this breakpoint, we don't
- // need to send it back as part of a "changed" event. This
- // prevent us from sending to VSCode paths that should be source
- // mapped. Note that CreateBreakpoint doesn't apply source mapping.
- // Besides, the current implementation of VSCode ignores the
- // "source" element of breakpoint events.
- llvm::json::Value source_bp = CreateBreakpoint(&bp);
- source_bp.getAsObject()->erase("source");
-
- body.try_emplace("breakpoint", source_bp);
- body.try_emplace("reason", "changed");
- bp_event.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(bp_event)));
- }
- }
- } else if (event.BroadcasterMatchesRef(dap.broadcaster)) {
- if (event_mask & eBroadcastBitStopEventThread) {
- done = true;
- }
- }
- }
- }
-}
-
lldb::SBValue FindVariable(DAP &dap, uint64_t variablesReference,
llvm::StringRef name) {
lldb::SBValue variable;
@@ -651,248 +461,6 @@ void request_modules(DAP &dap, const llvm::json::Object &request) {
dap.SendJSON(llvm::json::Value(std::move(response)));
}
-// "InitializeRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Initialize request; value of command field is
-// 'initialize'.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "initialize" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/InitializeRequestArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "InitializeRequestArguments": {
-// "type": "object",
-// "description": "Arguments for 'initialize' request.",
-// "properties": {
-// "clientID": {
-// "type": "string",
-// "description": "The ID of the (frontend) client using this adapter."
-// },
-// "adapterID": {
-// "type": "string",
-// "description": "The ID of the debug adapter."
-// },
-// "locale": {
-// "type": "string",
-// "description": "The ISO-639 locale of the (frontend) client using
-// this adapter, e.g. en-US or de-CH."
-// },
-// "linesStartAt1": {
-// "type": "boolean",
-// "description": "If true all line numbers are 1-based (default)."
-// },
-// "columnsStartAt1": {
-// "type": "boolean",
-// "description": "If true all column numbers are 1-based (default)."
-// },
-// "pathFormat": {
-// "type": "string",
-// "_enum": [ "path", "uri" ],
-// "description": "Determines in what format paths are specified. The
-// default is 'path', which is the native format."
-// },
-// "supportsVariableType": {
-// "type": "boolean",
-// "description": "Client supports the optional type attribute for
-// variables."
-// },
-// "supportsVariablePaging": {
-// "type": "boolean",
-// "description": "Client supports the paging of variables."
-// },
-// "supportsRunInTerminalRequest": {
-// "type": "boolean",
-// "description": "Client supports the runInTerminal request."
-// }
-// },
-// "required": [ "adapterID" ]
-// },
-// "InitializeResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'initialize' request.",
-// "properties": {
-// "body": {
-// "$ref": "#/definitions/Capabilities",
-// "description": "The capabilities of this debug adapter."
-// }
-// }
-// }]
-// }
-void request_initialize(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Object body;
-
- const auto *arguments = request.getObject("arguments");
- // sourceInitFile option is not from formal DAP specification. It is only
- // used by unit tests to prevent sourcing .lldbinit files from environment
- // which may affect the outcome of tests.
- bool source_init_file = GetBoolean(arguments, "sourceInitFile", true);
-
- // Do not source init files until in/out/err are configured.
- dap.debugger = lldb::SBDebugger::Create(false);
- dap.debugger.SetInputFile(dap.in);
- auto out_fd = dap.out.GetWriteFileDescriptor();
- if (llvm::Error err = out_fd.takeError()) {
- response["success"] = false;
- EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
- dap.debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false));
- auto err_fd = dap.err.GetWriteFileDescriptor();
- if (llvm::Error err = err_fd.takeError()) {
- response["success"] = false;
- EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
- dap.debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false));
-
- auto interp = dap.debugger.GetCommandInterpreter();
-
- if (source_init_file) {
- dap.debugger.SkipLLDBInitFiles(false);
- dap.debugger.SkipAppInitFiles(false);
- lldb::SBCommandReturnObject init;
- interp.SourceInitFileInGlobalDirectory(init);
- interp.SourceInitFileInHomeDirectory(init);
- }
-
- if (llvm::Error err = dap.RunPreInitCommands()) {
- response["success"] = false;
- EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
-
- dap.PopulateExceptionBreakpoints();
- auto cmd = dap.debugger.GetCommandInterpreter().AddMultiwordCommand(
- "lldb-dap", "Commands for managing lldb-dap.");
- if (GetBoolean(arguments, "supportsStartDebuggingRequest", false)) {
- cmd.AddCommand(
- "start-debugging", new StartDebuggingRequestHandler(dap),
- "Sends a startDebugging request from the debug adapter to the client "
- "to start a child debug session of the same type as the caller.");
- }
- cmd.AddCommand(
- "repl-mode", new ReplModeRequestHandler(dap),
- "Get or set the repl behavior of lldb-dap evaluation requests.");
- cmd.AddCommand("send-event", new SendEventRequestHandler(dap),
- "Sends an DAP event to the client.");
-
- dap.progress_event_thread =
- std::thread(ProgressEventThreadFunction, std::ref(dap));
-
- // Start our event thread so we can receive events from the debugger, target,
- // process and more.
- dap.event_thread = std::thread(EventThreadFunction, std::ref(dap));
-
- // The debug adapter supports the configurationDoneRequest.
- body.try_emplace("supportsConfigurationDoneRequest", true);
- // The debug adapter supports function breakpoints.
- body.try_emplace("supportsFunctionBreakpoints", true);
- // The debug adapter supports conditional breakpoints.
- body.try_emplace("supportsConditionalBreakpoints", true);
- // The debug adapter supports breakpoints that break execution after a
- // specified number of hits.
- body.try_emplace("supportsHitConditionalBreakpoints", true);
- // The debug adapter supports a (side effect free) evaluate request for
- // data hovers.
- body.try_emplace("supportsEvaluateForHovers", true);
- // Available filters or options for the setExceptionBreakpoints request.
- llvm::json::Array filters;
- for (const auto &exc_bp : *dap.exception_breakpoints) {
- filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp));
- }
- body.try_emplace("exceptionBreakpointFilters", std::move(filters));
- // The debug adapter supports launching a debugee in intergrated VSCode
- // terminal.
- body.try_emplace("supportsRunInTerminalRequest", true);
- // The debug adapter supports stepping back via the stepBack and
- // reverseContinue requests.
- body.try_emplace("supportsStepBack", false);
- // The debug adapter supports setting a variable to a value.
- body.try_emplace("supportsSetVariable", true);
- // The debug adapter supports restarting a frame.
- body.try_emplace("supportsRestartFrame", false);
- // The debug adapter supports the gotoTargetsRequest.
- body.try_emplace("supportsGotoTargetsRequest", false);
- // The debug adapter supports the stepInTargetsRequest.
- body.try_emplace("supportsStepInTargetsRequest", true);
- // The debug adapter supports the completions request.
- body.try_emplace("supportsCompletionsRequest", true);
- // The debug adapter supports the disassembly request.
- body.try_emplace("supportsDisassembleRequest", true);
- // The debug adapter supports the `breakpointLocations` request.
- body.try_emplace("supportsBreakpointLocationsRequest", true);
- // The debug adapter supports stepping granularities (argument `granularity`)
- // for the stepping requests.
- body.try_emplace("supportsSteppingGranularity", true);
- // The debug adapter support for instruction breakpoint.
- body.try_emplace("supportsInstructionBreakpoints", true);
-
- llvm::json::Array completion_characters;
- completion_characters.emplace_back(".");
- completion_characters.emplace_back(" ");
- completion_characters.emplace_back("\t");
- body.try_emplace("completionTriggerCharacters",
- std::move(completion_characters));
-
- // The debug adapter supports the modules request.
- body.try_emplace("supportsModulesRequest", true);
- // The set of additional module information exposed by the debug adapter.
- // body.try_emplace("additionalModuleColumns"] = ColumnDescriptor
- // Checksum algorithms supported by the debug adapter.
- // body.try_emplace("supportedChecksumAlgorithms"] = ChecksumAlgorithm
- // The debug adapter supports the RestartRequest. In this case a client
- // should not implement 'restart' by terminating and relaunching the adapter
- // but by calling the RestartRequest.
- body.try_emplace("supportsRestartRequest", true);
- // The debug adapter supports 'exceptionOptions' on the
- // setExceptionBreakpoints request.
- body.try_emplace("supportsExceptionOptions", true);
- // The debug adapter supports a 'format' attribute on the stackTraceRequest,
- // variablesRequest, and evaluateRequest.
- body.try_emplace("supportsValueFormattingOptions", true);
- // The debug adapter supports the exceptionInfo request.
- body.try_emplace("supportsExceptionInfoRequest", true);
- // The debug adapter supports the 'terminateDebuggee' attribute on the
- // 'disconnect' request.
- body.try_emplace("supportTerminateDebuggee", true);
- // The debug adapter supports the delayed loading of parts of the stack,
- // which requires that both the 'startFrame' and 'levels' arguments and the
- // 'totalFrames' result of the 'StackTrace' request are supported.
- body.try_emplace("supportsDelayedStackTraceLoading", true);
- // The debug adapter supports the 'loadedSources' request.
- body.try_emplace("supportsLoadedSourcesRequest", false);
- // The debug adapter supports sending progress reporting events.
- body.try_emplace("supportsProgressReporting", true);
- // The debug adapter supports 'logMessage' in breakpoint.
- body.try_emplace("supportsLogPoints", true);
- // The debug adapter supports data watchpoints.
- body.try_emplace("supportsDataBreakpoints", true);
- // The debug adapter supports the `readMemory` request.
- body.try_emplace("supportsReadMemoryRequest", true);
-
- // Put in non-DAP specification lldb specific information.
- llvm::json::Object lldb_json;
- lldb_json.try_emplace("version", dap.debugger.GetVersionString());
- body.try_emplace("__lldb", std::move(lldb_json));
-
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
llvm::Error request_runInTerminal(DAP &dap,
const llvm::json::Object &launch_request,
const uint64_t timeout_seconds) {
@@ -3680,8 +3248,8 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<DisconnectRequestHandler>();
dap.RegisterRequest<EvaluateRequestHandler>();
dap.RegisterRequest<ExceptionInfoRequestHandler>();
+ dap.RegisterRequest<InitializeRequestHandler>();
- dap.RegisterRequestCallback("initialize", request_initialize);
dap.RegisterRequestCallback("launch", request_launch);
dap.RegisterRequestCallback("next", request_next);
dap.RegisterRequestCallback("pause", request_pause);
More information about the lldb-commits
mailing list