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 <jo...@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); _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits