[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
Fri Feb 21 17:21:03 PST 2025


https://github.com/JDevlieghere created https://github.com/llvm/llvm-project/pull/128262

None

>From 03bf2fafb0b8b7e1dc08f6510802b73b5fe4dde7 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] [lldb-dap] Move requests into their own object/file

---
 lldb/tools/lldb-dap/CMakeLists.txt            |   5 +
 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              |   4 +
 6 files changed, 365 insertions(+)
 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.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..b8e950b2980fa 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"
@@ -4998,7 +4999,10 @@ void request_setInstructionBreakpoints(DAP &dap,
 }
 
 void RegisterRequestCallbacks(DAP &dap) {
+  dap.RegisterRequest<AttachRequest>();
+
   dap.RegisterRequestCallback("attach", request_attach);
+
   dap.RegisterRequestCallback("breakpointLocations",
                               request_breakpointLocations);
   dap.RegisterRequestCallback("completions", request_completions);



More information about the lldb-commits mailing list