Author: John Harrison Date: 2025-05-08T14:12:49-07:00 New Revision: 03896403d3bf330c8163aa9ae3fe2aa284e273be
URL: https://github.com/llvm/llvm-project/commit/03896403d3bf330c8163aa9ae3fe2aa284e273be DIFF: https://github.com/llvm/llvm-project/commit/03896403d3bf330c8163aa9ae3fe2aa284e273be.diff LOG: [lldb-dap] Migrate attach to typed RequestHandler. (#137911) This updates the `attach` request to the typed `RequestHandler<protocol::AttachRequestArguments, protocol::AttachResponse>`. Added a few more overlapping configurations to `lldb_dap::protocol::Configuration` that are shared between launching and attaching. There may be some additional code we could clean-up that is no longer referenced now that this has migrated to use well defined types. Added: Modified: lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py lldb/tools/lldb-dap/DAP.cpp lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp lldb/tools/lldb-dap/Handler/RequestHandler.cpp lldb/tools/lldb-dap/Handler/RequestHandler.h lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp lldb/tools/lldb-dap/Protocol/ProtocolRequests.h lldb/tools/lldb-dap/package.json Removed: ################################################################################ diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py index b5569642f9d34..a9218d3c3dde3 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py @@ -78,7 +78,7 @@ def test_by_name(self): def test_by_name_waitFor(self): """ Tests attaching to a process by process name and waiting for the - next instance of a process to be launched, ingoring all current + next instance of a process to be launched, ignoring all current ones. """ program = self.build_and_create_debug_adapter_for_attach() @@ -101,7 +101,7 @@ def test_commands(self): that can be passed during attach. "initCommands" are a list of LLDB commands that get executed - before the targt is created. + before the target is created. "preRunCommands" are a list of LLDB commands that get executed after the target has been created and before the launch. "stopCommands" are a list of LLDB commands that get executed each @@ -179,6 +179,24 @@ def test_commands(self): self.verify_commands("exitCommands", output, exitCommands) self.verify_commands("terminateCommands", output, terminateCommands) + def test_attach_command_process_failures(self): + """ + Tests that a 'attachCommands' is expected to leave the debugger's + selected target with a valid process. + """ + program = self.build_and_create_debug_adapter_for_attach() + attachCommands = ['script print("oops, forgot to attach to a process...")'] + resp = self.attach( + program=program, + attachCommands=attachCommands, + expectFailure=True, + ) + self.assertFalse(resp["success"]) + self.assertIn( + "attachCommands failed to attach to a process", + resp["body"]["error"]["format"], + ) + @skipIfNetBSD # Hangs on NetBSD as well @skipIf( archs=["arm", "aarch64"] diff --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py index acbe0366d1ecc..7c85f05c1ba45 100644 --- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py +++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py @@ -56,7 +56,7 @@ def test_failing_launch_commands_and_run_in_terminal(self): self.assertFalse(response["success"]) self.assertTrue(self.get_dict_value(response, ["body", "error", "showUser"])) self.assertEqual( - "launchCommands and runInTerminal are mutually exclusive", + "'launchCommands' and 'runInTerminal' are mutually exclusive", self.get_dict_value(response, ["body", "error", "format"]), ) diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 62c60cc3a9b3b..1b118bb8c6a2b 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -676,10 +676,10 @@ lldb::SBTarget DAP::CreateTarget(lldb::SBError &error) { // omitted at all), so it is good to leave the user an opportunity to specify // those. Any of those three can be left empty. auto target = this->debugger.CreateTarget( - configuration.program.value_or("").data(), - configuration.targetTriple.value_or("").data(), - configuration.platformName.value_or("").data(), - true, // Add dependent modules. + /*filename=*/configuration.program.data(), + /*target_triple=*/configuration.targetTriple.data(), + /*platform_name=*/configuration.platformName.data(), + /*add_dependent_modules=*/true, // Add dependent modules. error); return target; @@ -1203,7 +1203,7 @@ bool SendEventRequestHandler::DoExecute(lldb::SBDebugger debugger, } void DAP::ConfigureSourceMaps() { - if (configuration.sourceMap.empty() && !configuration.sourcePath) + if (configuration.sourceMap.empty() && configuration.sourcePath.empty()) return; std::string sourceMapCommand; @@ -1214,8 +1214,8 @@ void DAP::ConfigureSourceMaps() { for (const auto &kv : configuration.sourceMap) { strm << "\"" << kv.first << "\" \"" << kv.second << "\" "; } - } else if (configuration.sourcePath) { - strm << "\".\" \"" << *configuration.sourcePath << "\""; + } else if (!configuration.sourcePath.empty()) { + strm << "\".\" \"" << configuration.sourcePath << "\""; } RunLLDBCommands("Setting source map:", {sourceMapCommand}); @@ -1224,6 +1224,7 @@ void DAP::ConfigureSourceMaps() { void DAP::SetConfiguration(const protocol::Configuration &config, bool is_attach) { configuration = config; + stop_at_entry = config.stopOnEntry; this->is_attach = is_attach; if (configuration.customFrameFormat) @@ -1243,8 +1244,6 @@ void DAP::SetConfigurationDone() { } void DAP::SetFrameFormat(llvm::StringRef format) { - if (format.empty()) - return; lldb::SBError error; frame_format = lldb::SBFormat(format.str().c_str(), error); if (error.Fail()) { @@ -1257,8 +1256,6 @@ void DAP::SetFrameFormat(llvm::StringRef format) { } void DAP::SetThreadFormat(llvm::StringRef format) { - if (format.empty()) - return; lldb::SBError error; thread_format = lldb::SBFormat(format.str().c_str(), error); if (error.Fail()) { diff --git a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp index 5dc9c3f9772e3..0293ffbd0c922 100644 --- a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp @@ -10,183 +10,138 @@ #include "EventHelper.h" #include "JSONUtils.h" #include "LLDBUtils.h" +#include "Protocol/ProtocolRequests.h" #include "RequestHandler.h" +#include "lldb/API/SBAttachInfo.h" #include "lldb/API/SBListener.h" +#include "lldb/lldb-defines.h" +#include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" +using namespace llvm; +using namespace lldb_dap::protocol; + namespace lldb_dap { -// "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 AttachRequestHandler::operator()(const llvm::json::Object &request) const { - dap.is_attach = true; - llvm::json::Object response; - lldb::SBError error; - FillResponse(request, response); - const int invalid_port = 0; - const auto *arguments = request.getObject("arguments"); - const lldb::pid_t pid = - GetInteger<uint64_t>(arguments, "pid").value_or(LLDB_INVALID_PROCESS_ID); - const auto gdb_remote_port = - GetInteger<uint64_t>(arguments, "gdb-remote-port").value_or(invalid_port); - const auto gdb_remote_hostname = - GetString(arguments, "gdb-remote-hostname").value_or("localhost"); - const auto wait_for = GetBoolean(arguments, "waitFor").value_or(false); - dap.configuration.initCommands = GetStrings(arguments, "initCommands"); - dap.configuration.preRunCommands = GetStrings(arguments, "preRunCommands"); - dap.configuration.postRunCommands = GetStrings(arguments, "postRunCommands"); - dap.configuration.stopCommands = GetStrings(arguments, "stopCommands"); - dap.configuration.exitCommands = GetStrings(arguments, "exitCommands"); - dap.configuration.terminateCommands = - GetStrings(arguments, "terminateCommands"); - auto attachCommands = GetStrings(arguments, "attachCommands"); - llvm::StringRef core_file = GetString(arguments, "coreFile").value_or(""); - const uint64_t timeout_seconds = - GetInteger<uint64_t>(arguments, "timeout").value_or(30); - dap.stop_at_entry = core_file.empty() - ? GetBoolean(arguments, "stopOnEntry").value_or(false) - : true; - const llvm::StringRef debuggerRoot = - GetString(arguments, "debuggerRoot").value_or(""); - dap.configuration.enableAutoVariableSummaries = - GetBoolean(arguments, "enableAutoVariableSummaries").value_or(false); - dap.configuration.enableSyntheticChildDebugging = - GetBoolean(arguments, "enableSyntheticChildDebugging").value_or(false); - dap.configuration.displayExtendedBacktrace = - GetBoolean(arguments, "displayExtendedBacktrace").value_or(false); - dap.configuration.commandEscapePrefix = - GetString(arguments, "commandEscapePrefix").value_or("`"); - dap.configuration.program = GetString(arguments, "program"); - dap.configuration.targetTriple = GetString(arguments, "targetTriple"); - dap.configuration.platformName = GetString(arguments, "platformName"); - dap.SetFrameFormat(GetString(arguments, "customFrameFormat").value_or("")); - dap.SetThreadFormat(GetString(arguments, "customThreadFormat").value_or("")); +/// The `attach` request is sent from the client to the debug adapter to attach +/// to a debuggee that is already running. +/// +/// Since attaching is debugger/runtime specific, the arguments for this request +/// are not part of this specification. +Error AttachRequestHandler::Run(const AttachRequestArguments &args) const { + // Validate that we have a well formed attach request. + if (args.attachCommands.empty() && args.coreFile.empty() && + args.configuration.program.empty() && + args.pid == LLDB_INVALID_PROCESS_ID && + args.gdbRemotePort == LLDB_DAP_INVALID_PORT) + return make_error<DAPError>( + "expected one of 'pid', 'program', 'attachCommands', " + "'coreFile' or 'gdb-remote-port' to be specified"); + + // Check if we have mutually exclusive arguments. + if ((args.pid != LLDB_INVALID_PROCESS_ID) && + (args.gdbRemotePort != LLDB_DAP_INVALID_PORT)) + return make_error<DAPError>( + "'pid' and 'gdb-remote-port' are mutually exclusive"); + + dap.SetConfiguration(args.configuration, /*is_attach=*/true); + if (!args.coreFile.empty()) + dap.stop_at_entry = true; PrintWelcomeMessage(); // 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); + // 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 (!dap.configuration.debuggerRoot.empty()) + sys::fs::set_current_path(dap.configuration.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; - } + if (llvm::Error err = dap.RunInitCommands()) + return err; - SetSourceMapFromArguments(*arguments); + dap.ConfigureSourceMaps(); - lldb::SBError status; - dap.SetTarget(dap.CreateTarget(status)); - if (status.Fail()) { - response["success"] = llvm::json::Value(false); - EmplaceSafeString(response, "message", status.GetCString()); - dap.SendJSON(llvm::json::Value(std::move(response))); - return; - } + lldb::SBError error; + lldb::SBTarget target = dap.CreateTarget(error); + if (error.Fail()) + return ToError(error); + + dap.SetTarget(target); // 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 (Error err = dap.RunPreRunCommands()) + return err; - 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()); + if ((args.pid == LLDB_INVALID_PROCESS_ID || + args.gdbRemotePort == LLDB_DAP_INVALID_PORT) && + args.waitFor) { dap.SendOutput(OutputType::Console, - llvm::StringRef(attach_msg, attach_msg_len)); + llvm::formatv("Waiting to attach to \"{0}\"...", + dap.target.GetExecutable().GetFilename()) + .str()); } { // Perform the launch in synchronous mode so that we don't have to worry // about process state changes during the launch. ScopeSyncMode scope_sync_mode(dap.debugger); - if (attachCommands.empty()) { - // No "attachCommands", just attach normally. - 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 pid or process name. - lldb::SBAttachInfo attach_info; - if (pid != LLDB_INVALID_PROCESS_ID) - attach_info.SetProcessID(pid); - else if (dap.configuration.program.has_value()) - attach_info.SetExecutable(dap.configuration.program->data()); - attach_info.SetWaitForLaunch(wait_for, false /*async*/); - dap.target.Attach(attach_info, error); - } - } else { - dap.target.LoadCore(core_file.data(), error); - } - } 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. + + if (!args.attachCommands.empty()) { + // Run the attach commands, after which we expect the debugger's selected + // target to contain a valid and stopped process. Otherwise inform the + // user that their command failed or the debugger is in an unexpected + // state. + if (llvm::Error err = dap.RunAttachCommands(args.attachCommands)) + return err; + dap.target = dap.debugger.GetSelectedTarget(); + + // Validate the attachCommand results. + if (!dap.target.GetProcess().IsValid()) + return make_error<DAPError>( + "attachCommands failed to attach to a process"); + } else if (!args.coreFile.empty()) { + dap.target.LoadCore(args.coreFile.data(), error); + } else if (args.gdbRemotePort != LLDB_DAP_INVALID_PORT) { + 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}:", args.gdbRemoteHostname); + connect_url += std::to_string(args.gdbRemotePort); + dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", + error); + } else { + // Attach by pid or process name. + lldb::SBAttachInfo attach_info; + if (args.pid != LLDB_INVALID_PROCESS_ID) + attach_info.SetProcessID(args.pid); + else if (!dap.configuration.program.empty()) + attach_info.SetExecutable(dap.configuration.program.data()); + attach_info.SetWaitForLaunch(args.waitFor, /*async=*/false); + dap.target.Attach(attach_info, error); } } // Make sure the process is attached and stopped. - error = dap.WaitForProcessToStop(std::chrono::seconds(timeout_seconds)); + error = dap.WaitForProcessToStop(args.configuration.timeout); + if (error.Fail()) + return ToError(error); + + if (args.coreFile.empty() && !dap.target.GetProcess().IsValid()) + return make_error<DAPError>("failed to attach to process"); + + dap.RunPostRunCommands(); + + return Error::success(); +} + +void AttachRequestHandler::PostRun() const { + if (!dap.target.GetProcess().IsValid()) + return; // Clients can request a baseline of currently existing threads after // we acknowledge the configurationDone request. @@ -197,36 +152,12 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) const { dap.initial_thread_list = GetThreads(dap.target.GetProcess(), dap.thread_format); - 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))); + SendProcessEvent(dap, Attach); - // FIXME: Move this into PostRun. - if (error.Success()) { - if (dap.target.GetProcess().IsValid()) { - SendProcessEvent(dap, Attach); - - if (dap.stop_at_entry) - SendThreadStoppedEvent(dap); - else - dap.target.GetProcess().Continue(); - } - } + if (dap.stop_at_entry) + SendThreadStoppedEvent(dap); + else + dap.target.GetProcess().Continue(); } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp index aa947d3cb5ab9..6d1e2c7402f4c 100644 --- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp @@ -262,7 +262,7 @@ static void EventThreadFunction(DAP &dap) { } /// Initialize request; value of command field is 'initialize'. -llvm::Expected<InitializeResponseBody> InitializeRequestHandler::Run( +llvm::Expected<InitializeResponse> InitializeRequestHandler::Run( const InitializeRequestArguments &arguments) const { dap.clientFeatures = arguments.supportedFeatures; diff --git a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp index 7e0e76935dd02..22d1a090187d8 100644 --- a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp @@ -22,13 +22,13 @@ namespace lldb_dap { /// Launch request; value of command field is 'launch'. Error LaunchRequestHandler::Run(const LaunchRequestArguments &arguments) const { - dap.SetConfiguration(arguments.configuration, /*is_attach=*/false); - dap.last_launch_request = arguments; - dap.stop_at_entry = arguments.stopOnEntry; - + // Validate that we have a well formed launch request. if (!arguments.launchCommands.empty() && arguments.runInTerminal) return make_error<DAPError>( - "launchCommands and runInTerminal are mutually exclusive"); + "'launchCommands' and 'runInTerminal' are mutually exclusive"); + + dap.SetConfiguration(arguments.configuration, /*is_attach=*/false); + dap.last_launch_request = arguments; PrintWelcomeMessage(); @@ -36,9 +36,8 @@ Error LaunchRequestHandler::Run(const LaunchRequestArguments &arguments) const { // 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. - const std::string debugger_root = dap.configuration.debuggerRoot.value_or(""); - if (!debugger_root.empty()) - sys::fs::set_current_path(debugger_root); + if (!dap.configuration.debuggerRoot.empty()) + sys::fs::set_current_path(dap.configuration.debuggerRoot); // Run any initialize LLDB commands the user specified in the launch.json. // This is run before target is created, so commands can't do anything with @@ -68,15 +67,25 @@ Error LaunchRequestHandler::Run(const LaunchRequestArguments &arguments) const { } void LaunchRequestHandler::PostRun() const { - if (dap.target.GetProcess().IsValid()) { - // Attach happens when launching with runInTerminal. - SendProcessEvent(dap, dap.is_attach ? Attach : Launch); - - if (dap.stop_at_entry) - SendThreadStoppedEvent(dap); - else - dap.target.GetProcess().Continue(); - } + if (!dap.target.GetProcess().IsValid()) + return; + + // Clients can request a baseline of currently existing threads after + // we acknowledge the configurationDone request. + // Client requests the baseline of currently existing threads after + // a successful or attach by sending a 'threads' request + // right after receiving the configurationDone response. + // Obtain the list of threads before we resume the process + dap.initial_thread_list = + GetThreads(dap.target.GetProcess(), dap.thread_format); + + // Attach happens when launching with runInTerminal. + SendProcessEvent(dap, dap.is_attach ? Attach : Launch); + + 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 282c5f4ab15a5..93bc80a38e29d 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -51,56 +51,6 @@ static uint32_t SetLaunchFlag(uint32_t flags, bool flag, return flags; } -// Both attach and launch take either a sourcePath or a sourceMap -// argument (or neither), from which we need to set the target.source-map. -void BaseRequestHandler::SetSourceMapFromArguments( - const llvm::json::Object &arguments) const { - 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").value_or(""); - - // 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}); - } -} - static llvm::Error RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) { if (!dap.clientFeatures.contains( @@ -108,8 +58,7 @@ RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) { return llvm::make_error<DAPError>("Cannot use runInTerminal, feature is " "not supported by the connected client"); - if (!arguments.configuration.program || - arguments.configuration.program->empty()) + if (arguments.configuration.program.empty()) return llvm::make_error<DAPError>( "program must be set to when using runInTerminal"); @@ -130,8 +79,8 @@ RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) { #endif llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest( - *arguments.configuration.program, arguments.args, arguments.env, - arguments.cwd.value_or(""), comm_file.m_path, debugger_pid); + arguments.configuration.program, arguments.args, arguments.env, + arguments.cwd, comm_file.m_path, debugger_pid); dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal", std::move(reverse_request)); @@ -211,9 +160,8 @@ llvm::Error BaseRequestHandler::LaunchProcess( // Grab the current working directory if there is one and set it in the // launch info. - const auto cwd = arguments.cwd.value_or(""); - if (!cwd.empty()) - launch_info.SetWorkingDirectory(cwd.data()); + if (!arguments.cwd.empty()) + launch_info.SetWorkingDirectory(arguments.cwd.data()); // Extract any extra arguments and append them to our program arguments for // when we launch @@ -251,7 +199,7 @@ llvm::Error BaseRequestHandler::LaunchProcess( lldb::SBError error; dap.target.Launch(launch_info, error); if (error.Fail()) - return llvm::make_error<DAPError>(error.GetCString()); + return ToError(error); } else { // Set the launch info so that run commands can access the configured // launch details. @@ -267,18 +215,10 @@ llvm::Error BaseRequestHandler::LaunchProcess( // Make sure the process is launched and stopped at the entry point before // proceeding. - lldb::SBError error = dap.WaitForProcessToStop(arguments.timeout); + lldb::SBError error = + dap.WaitForProcessToStop(arguments.configuration.timeout); if (error.Fail()) - return llvm::make_error<DAPError>(error.GetCString()); - - // Clients can request a baseline of currently existing threads after - // we acknowledge the configurationDone request. - // Client requests the baseline of currently existing threads after - // a successful or attach by sending a 'threads' request - // right after receiving the configurationDone response. - // Obtain the list of threads before we resume the process - dap.initial_thread_list = - GetThreads(dap.target.GetProcess(), dap.thread_format); + return ToError(error); return llvm::Error::success(); } diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h index 25534b5675e45..948a29a005aa7 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -59,10 +59,6 @@ class BaseRequestHandler { /// FIXME: Move these into the DAP class? /// @{ - /// 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 SetSourceMapFromArguments(const llvm::json::Object &arguments) const; - /// Prints a welcome message on the editor if the preprocessor variable /// LLDB_DAP_WELCOME_MESSAGE is defined. void PrintWelcomeMessage() const; @@ -172,6 +168,9 @@ class RequestHandler : public BaseRequestHandler { /// A hook for a request handler to run additional operations after the /// request response is sent but before the next request handler. + /// + /// *NOTE*: PostRun will be invoked even if the `Run` operation returned an + /// error. virtual void PostRun() const {}; protocol::ErrorResponseBody ToResponse(llvm::Error err) const { @@ -197,11 +196,14 @@ class RequestHandler : public BaseRequestHandler { } }; -class AttachRequestHandler : public LegacyRequestHandler { +class AttachRequestHandler + : public RequestHandler<protocol::AttachRequestArguments, + protocol::AttachResponse> { public: - using LegacyRequestHandler::LegacyRequestHandler; + using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "attach"; } - void operator()(const llvm::json::Object &request) const override; + llvm::Error Run(const protocol::AttachRequestArguments &args) const override; + void PostRun() const override; }; class BreakpointLocationsRequestHandler : public LegacyRequestHandler { @@ -279,18 +281,18 @@ class ExceptionInfoRequestHandler : public LegacyRequestHandler { class InitializeRequestHandler : public RequestHandler<protocol::InitializeRequestArguments, - llvm::Expected<protocol::InitializeResponseBody>> { + llvm::Expected<protocol::InitializeResponse>> { public: using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "initialize"; } - llvm::Expected<protocol::InitializeResponseBody> + llvm::Expected<protocol::InitializeResponse> Run(const protocol::InitializeRequestArguments &args) const override; void PostRun() const override; }; class LaunchRequestHandler : public RequestHandler<protocol::LaunchRequestArguments, - protocol::LaunchResponseBody> { + protocol::LaunchResponse> { public: using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "launch"; } @@ -508,9 +510,8 @@ class ReadMemoryRequestHandler : public LegacyRequestHandler { void operator()(const llvm::json::Object &request) const override; }; -class CancelRequestHandler - : public RequestHandler<protocol::CancelArguments, - protocol::CancelResponseBody> { +class CancelRequestHandler : public RequestHandler<protocol::CancelArguments, + protocol::CancelResponse> { public: using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "cancel"; } diff --git a/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp index 47fedf9dd779c..58091b622f7e5 100644 --- a/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp @@ -109,7 +109,6 @@ void RestartRequestHandler::operator()( // Update DAP configuration based on the latest copy of the launch // arguments. dap.SetConfiguration(updated_arguments.configuration, false); - dap.stop_at_entry = updated_arguments.stopOnEntry; dap.ConfigureSourceMaps(); } } diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp index 950e8d17e3489..cd507692f0c21 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -223,26 +223,29 @@ bool fromJSON(const json::Value &Params, InitializeRequestArguments &IRA, bool fromJSON(const json::Value &Params, Configuration &C, json::Path P) { json::ObjectMapper O(Params, P); - return O.map("debuggerRoot", C.debuggerRoot) && + return O.mapOptional("debuggerRoot", C.debuggerRoot) && O.mapOptional("enableAutoVariableSummaries", C.enableAutoVariableSummaries) && O.mapOptional("enableSyntheticChildDebugging", C.enableSyntheticChildDebugging) && O.mapOptional("displayExtendedBacktrace", C.displayExtendedBacktrace) && + O.mapOptional("stopOnEntry", C.stopOnEntry) && O.mapOptional("commandEscapePrefix", C.commandEscapePrefix) && - O.map("customFrameFormat", C.customFrameFormat) && - O.map("customThreadFormat", C.customThreadFormat) && - O.map("sourcePath", C.sourcePath) && + O.mapOptional("customFrameFormat", C.customFrameFormat) && + O.mapOptional("customThreadFormat", C.customThreadFormat) && + O.mapOptional("sourcePath", C.sourcePath) && O.mapOptional("initCommands", C.initCommands) && O.mapOptional("preRunCommands", C.preRunCommands) && O.mapOptional("postRunCommands", C.postRunCommands) && O.mapOptional("stopCommands", C.stopCommands) && O.mapOptional("exitCommands", C.exitCommands) && O.mapOptional("terminateCommands", C.terminateCommands) && - O.map("program", C.program) && O.map("targetTriple", C.targetTriple) && - O.map("platformName", C.platformName) && - parseSourceMap(Params, C.sourceMap, P); + O.mapOptional("program", C.program) && + O.mapOptional("targetTriple", C.targetTriple) && + O.mapOptional("platformName", C.platformName) && + parseSourceMap(Params, C.sourceMap, P) && + parseTimeout(Params, C.timeout, P); } bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA, @@ -251,14 +254,26 @@ bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA, return O && fromJSON(Params, LRA.configuration, P) && O.mapOptional("noDebug", LRA.noDebug) && O.mapOptional("launchCommands", LRA.launchCommands) && - O.map("cwd", LRA.cwd) && O.mapOptional("args", LRA.args) && + O.mapOptional("cwd", LRA.cwd) && O.mapOptional("args", LRA.args) && O.mapOptional("detachOnError", LRA.detachOnError) && O.mapOptional("disableASLR", LRA.disableASLR) && O.mapOptional("disableSTDIO", LRA.disableSTDIO) && O.mapOptional("shellExpandArguments", LRA.shellExpandArguments) && - O.mapOptional("stopOnEntry", LRA.stopOnEntry) && + O.mapOptional("runInTerminal", LRA.runInTerminal) && - parseEnv(Params, LRA.env, P) && parseTimeout(Params, LRA.timeout, P); + parseEnv(Params, LRA.env, P); +} + +bool fromJSON(const json::Value &Params, AttachRequestArguments &ARA, + json::Path P) { + json::ObjectMapper O(Params, P); + return O && fromJSON(Params, ARA.configuration, P) && + O.mapOptional("attachCommands", ARA.attachCommands) && + O.mapOptional("pid", ARA.pid) && + O.mapOptional("waitFor", ARA.waitFor) && + O.mapOptional("gdb-remote-port", ARA.gdbRemotePort) && + O.mapOptional("gdb-remote-hostname", ARA.gdbRemoteHostname) && + O.mapOptional("coreFile", ARA.coreFile); } bool fromJSON(const llvm::json::Value &Params, ContinueArguments &CA, diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index 18222d61f9a14..acc3358736e70 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -23,6 +23,7 @@ #include "Protocol/ProtocolBase.h" #include "Protocol/ProtocolTypes.h" #include "lldb/lldb-defines.h" +#include "lldb/lldb-types.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/StringMap.h" #include "llvm/Support/JSON.h" @@ -51,7 +52,7 @@ bool fromJSON(const llvm::json::Value &, CancelArguments &, llvm::json::Path); /// Response to `cancel` request. This is just an acknowledgement, so no body /// field is required. -using CancelResponseBody = VoidResponse; +using CancelResponse = VoidResponse; /// Arguments for `disconnect` request. struct DisconnectArguments { @@ -139,15 +140,18 @@ bool fromJSON(const llvm::json::Value &, InitializeRequestArguments &, llvm::json::Path); /// Response to `initialize` request. The capabilities of this debug adapter. -using InitializeResponseBody = std::optional<Capabilities>; +using InitializeResponse = std::optional<Capabilities>; /// DAP Launch and Attach common configurations. +/// +/// See package.json debuggers > configurationAttributes > launch or attach > +/// properties for common configurations. struct Configuration { /// Specify a working directory to use when launching `lldb-dap`. If the debug /// information in your executable contains relative paths, this option can be /// used so that `lldb-dap` can find source files and object files that have /// relative paths. - std::optional<std::string> debuggerRoot; + std::string debuggerRoot; /// Enable auto generated summaries for variables when no summaries exist for /// a given type. This feature can cause performance delays in large projects @@ -156,13 +160,20 @@ struct Configuration { /// If a variable is displayed using a synthetic children, also display the /// actual contents of the variable at the end under a [raw] entry. This is - /// useful when creating sythetic child plug-ins as it lets you see the actual - /// contents of the variable. + /// useful when creating synthetic child plug-ins as it lets you see the + /// actual contents of the variable. bool enableSyntheticChildDebugging = false; /// Enable language specific extended backtraces. bool displayExtendedBacktrace = false; + /// Stop at the entry point of the program when launching or attaching. + bool stopOnEntry = false; + + /// Optional timeout when waiting for the program to `runInTerminal` or + /// attach. + std::chrono::seconds timeout = std::chrono::seconds(30); + /// The escape prefix to use for executing regular LLDB commands in the Debug /// Console, instead of printing variables. Defaults to a backtick. If it's an /// empty string, then all expression in the Debug Console are treated as @@ -183,7 +194,7 @@ struct Configuration { /// Specify a source path to remap "./" to allow full paths to be used when /// setting breakpoints in binaries that have relative source paths. - std::optional<std::string> sourcePath; + std::string sourcePath; /// Specify an array of path re-mappings. Each element in the array must be a /// two element array containing a source and destination pathname. Overrides @@ -219,15 +230,15 @@ struct Configuration { /// /// *NOTE:* When launching, either `launchCommands` or `program` must be /// configured. If both are configured then `launchCommands` takes priority. - std::optional<std::string> program; + std::string program; /// Target triple for the program (arch-vendor-os). If not set, inferred from /// the binary. - std::optional<std::string> targetTriple; + std::string targetTriple; /// Specify name of the platform to use for this target, creating the platform /// if necessary. - std::optional<std::string> platformName; + std::string platformName; }; /// lldb-dap specific launch arguments. @@ -240,6 +251,9 @@ struct LaunchRequestArguments { bool noDebug = false; /// Launch specific operations. + /// + /// See package.json debuggers > configurationAttributes > launch > + /// properties. /// @{ /// LLDB commands executed to launch the program. @@ -250,7 +264,7 @@ struct LaunchRequestArguments { std::vector<std::string> launchCommands; /// The program working directory. - std::optional<std::string> cwd; + std::string cwd; /// An array of command line argument strings to be passed to the program /// being launched. @@ -261,8 +275,8 @@ struct LaunchRequestArguments { /// with values or just "VAR" for environment variables with no values. llvm::StringMap<std::string> env; - /// If set, then the client stub should detach rather than killing the debugee - /// if it loses connection with lldb. + /// If set, then the client stub should detach rather than killing the + /// debuggee if it loses connection with lldb. bool detachOnError = false; /// Disable ASLR (Address Space Layout Randomization) when launching the @@ -275,16 +289,10 @@ struct LaunchRequestArguments { /// Set whether to shell expand arguments to the process when launching. bool shellExpandArguments = false; - /// Stop at the entry point of the program when launching a process. - bool stopOnEntry = false; - /// Launch the program inside an integrated terminal in the IDE. Useful for /// debugging interactive command line programs. bool runInTerminal = false; - /// Optional timeout for `runInTerminal` requests. - std::chrono::seconds timeout = std::chrono::seconds(30); - /// @} }; bool fromJSON(const llvm::json::Value &, LaunchRequestArguments &, @@ -292,7 +300,52 @@ bool fromJSON(const llvm::json::Value &, LaunchRequestArguments &, /// Response to `launch` request. This is just an acknowledgement, so no body /// field is required. -using LaunchResponseBody = VoidResponse; +using LaunchResponse = VoidResponse; + +#define LLDB_DAP_INVALID_PORT -1 + +/// lldb-dap specific attach arguments. +struct AttachRequestArguments { + /// Common lldb-dap configuration values for launching/attaching operations. + Configuration configuration; + + /// Attach specific operations. + /// + /// See package.json debuggers > configurationAttributes > attach > + /// properties. + /// @{ + + /// Custom commands that are executed instead of attaching to a process ID or + /// to a process by name. These commands may optionally create a new target + /// and must perform an attach. A valid process must exist after these + /// commands complete or the `"attach"` will fail. + std::vector<std::string> attachCommands; + + /// System process ID to attach to. + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + + /// Wait for the process to launch. + bool waitFor = false; + + /// TCP/IP port to attach to a remote system. Specifying both pid and port is + /// an error. + int32_t gdbRemotePort = LLDB_DAP_INVALID_PORT; + + /// The hostname to connect to a remote system. The default hostname being + /// used `localhost`. + std::string gdbRemoteHostname = "localhost"; + + /// Path to the core file to debug. + std::string coreFile; + + /// @} +}; +bool fromJSON(const llvm::json::Value &, AttachRequestArguments &, + llvm::json::Path); + +/// Response to `attach` request. This is just an acknowledgement, so no body +/// field is required. +using AttachResponse = VoidResponse; /// Arguments for `continue` request. struct ContinueArguments { diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index f66badc2a930f..a7631464d236a 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -552,10 +552,7 @@ "description": "Path to the program to attach to." }, "pid": { - "type": [ - "number", - "string" - ], + "type": "number", "description": "System process ID to attach to." }, "waitFor": { _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits