Author: John Harrison Date: 2025-04-25T09:50:42-07:00 New Revision: a0aa5f8933043c0939c3a8f301fed655be2a3732
URL: https://github.com/llvm/llvm-project/commit/a0aa5f8933043c0939c3a8f301fed655be2a3732 DIFF: https://github.com/llvm/llvm-project/commit/a0aa5f8933043c0939c3a8f301fed655be2a3732.diff LOG: [lldb-dap] Refactoring lldb-dap 'launch' request to use typed RequestHandler<>. (#133624) This converts a number of json::Value's into well defined types that are used throughout lldb-dap and updates the 'launch' command to use the new well defined types. --------- Co-authored-by: Jonas Devlieghere <jo...@devlieghere.com> Added: Modified: lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py lldb/tools/lldb-dap/DAP.cpp lldb/tools/lldb-dap/DAP.h lldb/tools/lldb-dap/Handler/AttachRequestHandler.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/JSONUtils.cpp lldb/tools/lldb-dap/JSONUtils.h lldb/tools/lldb-dap/LLDBUtils.cpp lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp lldb/tools/lldb-dap/Protocol/ProtocolRequests.h Removed: ################################################################################ diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index dadf6b1f8774c..0f8a84461c9e7 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -860,7 +860,8 @@ def request_launch( args_dict["enableAutoVariableSummaries"] = enableAutoVariableSummaries args_dict["enableSyntheticChildDebugging"] = enableSyntheticChildDebugging args_dict["displayExtendedBacktrace"] = displayExtendedBacktrace - args_dict["commandEscapePrefix"] = commandEscapePrefix + if commandEscapePrefix: + args_dict["commandEscapePrefix"] = commandEscapePrefix command_dict = {"command": "launch", "type": "request", "arguments": args_dict} response = self.send_recv(command_dict) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index b5b55b336d535..2671d65031203 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -453,7 +453,8 @@ def cleanup(): if not (response and response["success"]): self.assertTrue( - response["success"], "launch failed (%s)" % (response["message"]) + response["success"], + "launch failed (%s)" % (response["body"]["error"]["format"]), ) return response 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 4303f2d3a5193..e20a9db377f7f 100644 --- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py +++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py @@ -27,6 +27,34 @@ def test_default(self): lines = output.splitlines() self.assertIn(program, lines[0], "make sure program path is in first argument") + def test_failing_launch_program(self): + """ + Tests launching with an invalid program. + """ + program = self.getBuildArtifact("a.out") + self.create_debug_adapter() + response = self.launch(program, expectFailure=True) + self.assertFalse(response["success"]) + self.assertEqual( + "'{0}' does not exist".format(program), response["body"]["error"]["format"] + ) + + def test_failing_launch_commands_and_run_in_terminal(self): + """ + Tests launching with an invalid program. + """ + program = self.getBuildArtifact("a.out") + self.create_debug_adapter() + response = self.launch( + program, launchCommands=["a b c"], runInTerminal=True, expectFailure=True + ) + self.assertFalse(response["success"]) + self.assertTrue(self.get_dict_value(response, ["body", "error", "showUser"])) + self.assertEqual( + "launchCommands and runInTerminal are mutually exclusive", + self.get_dict_value(response, ["body", "error", "format"]), + ) + @skipIfWindows def test_termination(self): """ @@ -42,7 +70,9 @@ def test_termination(self): self.dap_server.request_disconnect() # Wait until the underlying lldb-dap process dies. - self.dap_server.process.wait(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval) + self.dap_server.process.wait( + timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval + ) # Check the return code self.assertEqual(self.dap_server.process.poll(), 0) @@ -460,7 +490,7 @@ def test_failing_launch_commands(self): self.assertFalse(response["success"]) self.assertRegex( - response["message"], + response["body"]["error"]["format"], r"Failed to run launch commands\. See the Debug Console for more details", ) diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 7657ab5b564a1..185b475cfcad6 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -660,9 +660,7 @@ void DAP::RunTerminateCommands() { configuration.terminateCommands); } -lldb::SBTarget -DAP::CreateTargetFromArguments(const llvm::json::Object &arguments, - lldb::SBError &error) { +lldb::SBTarget DAP::CreateTarget(lldb::SBError &error) { // Grab the name of the program we need to debug and create a target using // the given program as an argument. Executable file can be a source of target // architecture and platform, if they diff er from the host. Setting exe path @@ -671,25 +669,15 @@ DAP::CreateTargetFromArguments(const llvm::json::Object &arguments, // creation. We also use target triple and platform from the launch // configuration, if given, since in some cases ELF file doesn't contain // enough information to determine correct arch and platform (or ELF can be - // omitted at all), so it is good to leave the user an apportunity to specify + // omitted at all), so it is good to leave the user an opportunity to specify // those. Any of those three can be left empty. - const llvm::StringRef target_triple = - GetString(arguments, "targetTriple").value_or(""); - const llvm::StringRef platform_name = - GetString(arguments, "platformName").value_or(""); - const llvm::StringRef program = GetString(arguments, "program").value_or(""); auto target = this->debugger.CreateTarget( - program.data(), target_triple.data(), platform_name.data(), + configuration.program.value_or("").data(), + configuration.targetTriple.value_or("").data(), + configuration.platformName.value_or("").data(), true, // Add dependent modules. error); - if (error.Fail()) { - // Update message if there was an error. - error.SetErrorStringWithFormat( - "Could not create a target for a program '%s': %s.", program.data(), - error.GetCString()); - } - return target; } @@ -945,7 +933,7 @@ llvm::Error DAP::Loop() { return queue_reader.get(); } -lldb::SBError DAP::WaitForProcessToStop(uint32_t seconds) { +lldb::SBError DAP::WaitForProcessToStop(std::chrono::seconds seconds) { lldb::SBError error; lldb::SBProcess process = target.GetProcess(); if (!process.IsValid()) { @@ -980,8 +968,8 @@ lldb::SBError DAP::WaitForProcessToStop(uint32_t seconds) { } std::this_thread::sleep_for(std::chrono::microseconds(250)); } - error.SetErrorStringWithFormat("process failed to stop within %u seconds", - seconds); + error.SetErrorStringWithFormat("process failed to stop within %lld seconds", + seconds.count()); return error; } @@ -1178,6 +1166,36 @@ bool SendEventRequestHandler::DoExecute(lldb::SBDebugger debugger, return true; } +void DAP::ConfigureSourceMaps() { + if (configuration.sourceMap.empty() && !configuration.sourcePath) + return; + + std::string sourceMapCommand; + llvm::raw_string_ostream strm(sourceMapCommand); + strm << "settings set target.source-map "; + + if (!configuration.sourceMap.empty()) { + for (const auto &kv : configuration.sourceMap) { + strm << "\"" << kv.first << "\" \"" << kv.second << "\" "; + } + } else if (configuration.sourcePath) { + strm << "\".\" \"" << *configuration.sourcePath << "\""; + } + + RunLLDBCommands("Setting source map:", {sourceMapCommand}); +} + +void DAP::SetConfiguration(const protocol::Configuration &config, + bool is_attach) { + configuration = config; + this->is_attach = is_attach; + + if (configuration.customFrameFormat) + SetFrameFormat(*configuration.customFrameFormat); + if (configuration.customThreadFormat) + SetThreadFormat(*configuration.customThreadFormat); +} + void DAP::SetFrameFormat(llvm::StringRef format) { if (format.empty()) return; diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 727e5c00623e8..f5cc41bf646bb 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -35,6 +35,7 @@ #include "lldb/lldb-types.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/FunctionExtras.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" @@ -175,9 +176,9 @@ struct DAP { llvm::once_flag init_exception_breakpoints_flag; // Map step in target id to list of function targets that user can choose. llvm::DenseMap<lldb::addr_t, std::string> step_in_targets; - // A copy of the last LaunchRequest or AttachRequest so we can reuse its - // arguments if we get a RestartRequest. - std::optional<llvm::json::Object> last_launch_or_attach_request; + // A copy of the last LaunchRequest so we can reuse its arguments if we get a + // RestartRequest. Restarting an AttachRequest is not supported. + std::optional<protocol::LaunchRequestArguments> last_launch_request; lldb::tid_t focus_tid; bool disconnecting = false; llvm::once_flag terminated_event_flag; @@ -199,7 +200,6 @@ struct DAP { llvm::SmallDenseMap<int64_t, std::unique_ptr<ResponseHandler>> inflight_reverse_requests; ReplMode repl_mode; - lldb::SBFormat frame_format; lldb::SBFormat thread_format; // This is used to allow request_evaluate to handle empty expressions @@ -248,6 +248,12 @@ struct DAP { /// Stop event handler threads. void StopEventHandlers(); + /// Configures the debug adapter for launching/attaching. + void SetConfiguration(const protocol::Configuration &confing, bool is_attach); + + /// Configure source maps based on the current `DAPConfiguration`. + void ConfigureSourceMaps(); + /// Serialize the JSON value into a string and send the JSON packet to the /// "out" stream. void SendJSON(const llvm::json::Value &json); @@ -311,8 +317,6 @@ struct DAP { void RunTerminateCommands(); /// Create a new SBTarget object from the given request arguments. - /// \param[in] arguments - /// Launch configuration arguments. /// /// \param[out] error /// An SBError object that will contain an error description if @@ -320,8 +324,7 @@ struct DAP { /// /// \return /// An SBTarget object. - lldb::SBTarget CreateTargetFromArguments(const llvm::json::Object &arguments, - lldb::SBError &error); + lldb::SBTarget CreateTarget(lldb::SBError &error); /// Set given target object as a current target for lldb-dap and start /// listeing for its breakpoint events. @@ -395,7 +398,7 @@ struct DAP { /// The number of seconds to poll the process to wait until it is stopped. /// /// \return Error if waiting for the process fails, no error if succeeds. - lldb::SBError WaitForProcessToStop(uint32_t seconds); + lldb::SBError WaitForProcessToStop(std::chrono::seconds seconds); void SetFrameFormat(llvm::StringRef format); diff --git a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp index 5e622f3d3dcd4..3ef87cbef873c 100644 --- a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp @@ -43,10 +43,8 @@ namespace lldb_dap { // acknowledgement, so no body field is required." // }] // } - void AttachRequestHandler::operator()(const llvm::json::Object &request) const { dap.is_attach = true; - dap.last_launch_or_attach_request = request; llvm::json::Object response; lldb::SBError error; FillResponse(request, response); @@ -65,6 +63,7 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) const { attach_info.SetWaitForLaunch(wait_for, false /*async*/); 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 = @@ -76,7 +75,6 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) const { dap.stop_at_entry = core_file.empty() ? GetBoolean(arguments, "stopOnEntry").value_or(false) : true; - dap.configuration.postRunCommands = GetStrings(arguments, "postRunCommands"); const llvm::StringRef debuggerRoot = GetString(arguments, "debuggerRoot").value_or(""); dap.configuration.enableAutoVariableSummaries = @@ -87,6 +85,9 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) const { 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("")); @@ -110,7 +111,7 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) const { SetSourceMapFromArguments(*arguments); lldb::SBError status; - dap.SetTarget(dap.CreateTargetFromArguments(*arguments, status)); + dap.SetTarget(dap.CreateTarget(status)); if (status.Fail()) { response["success"] = llvm::json::Value(false); EmplaceSafeString(response, "message", status.GetCString()); @@ -180,7 +181,7 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) const { // 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); + error = dap.WaitForProcessToStop(std::chrono::seconds(timeout_seconds)); } if (error.Success() && core_file.empty()) { diff --git a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp index 5f14cb074e37e..3e4532e754ec6 100644 --- a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp @@ -9,71 +9,26 @@ #include "DAP.h" #include "EventHelper.h" #include "JSONUtils.h" +#include "LLDBUtils.h" +#include "Protocol/ProtocolRequests.h" #include "RequestHandler.h" +#include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" +using namespace llvm; +using namespace lldb_dap::protocol; + namespace lldb_dap { -// "LaunchRequest": { -// "allOf": [ { "$ref": "#/definitions/Request" }, { -// "type": "object", -// "description": "Launch request; value of command field is 'launch'.", -// "properties": { -// "command": { -// "type": "string", -// "enum": [ "launch" ] -// }, -// "arguments": { -// "$ref": "#/definitions/LaunchRequestArguments" -// } -// }, -// "required": [ "command", "arguments" ] -// }] -// }, -// "LaunchRequestArguments": { -// "type": "object", -// "description": "Arguments for 'launch' request.", -// "properties": { -// "noDebug": { -// "type": "boolean", -// "description": "If noDebug is true the launch request should launch -// the program without enabling debugging." -// } -// } -// }, -// "LaunchResponse": { -// "allOf": [ { "$ref": "#/definitions/Response" }, { -// "type": "object", -// "description": "Response to 'launch' request. This is just an -// acknowledgement, so no body field is required." -// }] -// } -void LaunchRequestHandler::operator()(const llvm::json::Object &request) const { - dap.is_attach = false; - dap.last_launch_or_attach_request = request; - llvm::json::Object response; - FillResponse(request, response); - const auto *arguments = request.getObject("arguments"); - dap.configuration.initCommands = GetStrings(arguments, "initCommands"); - dap.configuration.preRunCommands = GetStrings(arguments, "preRunCommands"); - dap.configuration.stopCommands = GetStrings(arguments, "stopCommands"); - dap.configuration.exitCommands = GetStrings(arguments, "exitCommands"); - dap.configuration.terminateCommands = - GetStrings(arguments, "terminateCommands"); - dap.configuration.postRunCommands = GetStrings(arguments, "postRunCommands"); - dap.stop_at_entry = GetBoolean(arguments, "stopOnEntry").value_or(false); - 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.SetFrameFormat(GetString(arguments, "customFrameFormat").value_or("")); - dap.SetThreadFormat(GetString(arguments, "customThreadFormat").value_or("")); +/// 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; + + if (!arguments.launchCommands.empty() && arguments.runInTerminal) + return make_error<DAPError>( + "launchCommands and runInTerminal are mutually exclusive"); PrintWelcomeMessage(); @@ -81,55 +36,44 @@ void LaunchRequestHandler::operator()(const llvm::json::Object &request) 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. - if (!debuggerRoot.empty()) - llvm::sys::fs::set_current_path(debuggerRoot); + const std::string debugger_root = dap.configuration.debuggerRoot.value_or(""); + if (!debugger_root.empty()) + sys::fs::set_current_path(debugger_root); // 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 // the targets - preRunCommands are run with the target. - 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 (Error err = dap.RunInitCommands()) + return err; - SetSourceMapFromArguments(*arguments); + dap.ConfigureSourceMaps(); - 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; - } + 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; - status = LaunchProcess(request); + if (Error err = LaunchProcess(arguments)) + return err; - if (status.Fail()) { - response["success"] = llvm::json::Value(false); - EmplaceSafeString(response, "message", std::string(status.GetCString())); - } else { - dap.RunPostRunCommands(); - } + dap.RunPostRunCommands(); - dap.SendJSON(llvm::json::Value(std::move(response))); + return Error::success(); +} - if (!status.Fail()) { - if (dap.is_attach) - SendProcessEvent(dap, Attach); // this happens when doing runInTerminal - else - SendProcessEvent(dap, Launch); +void LaunchRequestHandler::PostRun() const { + if (dap.target.GetProcess().IsValid()) { + // Attach happens when launching with runInTerminal. + SendProcessEvent(dap, dap.is_attach ? Attach : Launch); } + dap.SendJSON(CreateEventObject("initialized")); } + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index be9273963654a..b7d3c8ced69f1 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -10,9 +10,11 @@ #include "DAP.h" #include "Handler/ResponseHandler.h" #include "JSONUtils.h" -#include "LLDBUtils.h" #include "Protocol/ProtocolBase.h" +#include "Protocol/ProtocolRequests.h" #include "RunInTerminal.h" +#include "lldb/API/SBDefines.h" +#include "lldb/API/SBEnvironment.h" #include "llvm/Support/Error.h" #include <mutex> @@ -37,14 +39,12 @@ MakeArgv(const llvm::ArrayRef<std::string> &strs) { return argv; } -static uint32_t SetLaunchFlag(uint32_t flags, const llvm::json::Object *obj, - llvm::StringRef key, lldb::LaunchFlags mask) { - if (const auto opt_value = GetBoolean(obj, key)) { - if (*opt_value) - flags |= mask; - else - flags &= ~mask; - } +static uint32_t SetLaunchFlag(uint32_t flags, bool flag, + lldb::LaunchFlags mask) { + if (flag) + flags |= mask; + else + flags &= ~mask; return flags; } @@ -99,14 +99,18 @@ void BaseRequestHandler::SetSourceMapFromArguments( } } -static llvm::Error RunInTerminal(DAP &dap, - const llvm::json::Object &launch_request, - const uint64_t timeout_seconds) { +static llvm::Error +RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) { if (!dap.clientFeatures.contains( protocol::eClientFeatureRunInTerminalRequest)) return llvm::make_error<DAPError>("Cannot use runInTerminal, feature is " "not supported by the connected client"); + if (!arguments.configuration.program || + arguments.configuration.program->empty()) + return llvm::make_error<DAPError>( + "program must be set to when using runInTerminal"); + dap.is_attach = true; lldb::SBAttachInfo attach_info; @@ -122,8 +126,10 @@ static llvm::Error RunInTerminal(DAP &dap, #if !defined(_WIN32) debugger_pid = getpid(); #endif + llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest( - launch_request, comm_file.m_path, debugger_pid); + *arguments.configuration.program, arguments.args, arguments.env, + arguments.cwd.value_or(""), comm_file.m_path, debugger_pid); dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal", std::move(reverse_request)); @@ -194,74 +200,74 @@ void BaseRequestHandler::Run(const Request &request) { // mark the response as cancelled. } -lldb::SBError -BaseRequestHandler::LaunchProcess(const llvm::json::Object &request) const { - lldb::SBError error; - const auto *arguments = request.getObject("arguments"); - auto launchCommands = GetStrings(arguments, "launchCommands"); +llvm::Error BaseRequestHandler::LaunchProcess( + const protocol::LaunchRequestArguments &arguments) const { + auto launchCommands = arguments.launchCommands; // Instantiate a launch info instance for the target. auto launch_info = dap.target.GetLaunchInfo(); // Grab the current working directory if there is one and set it in the // launch info. - const auto cwd = GetString(arguments, "cwd").value_or(""); + const auto cwd = arguments.cwd.value_or(""); if (!cwd.empty()) launch_info.SetWorkingDirectory(cwd.data()); // Extract any extra arguments and append them to our program arguments for // when we launch - auto args = GetStrings(arguments, "args"); - if (!args.empty()) - launch_info.SetArguments(MakeArgv(args).data(), true); + if (!arguments.args.empty()) + launch_info.SetArguments(MakeArgv(arguments.args).data(), true); // Pass any environment variables along that the user specified. - const auto envs = GetEnvironmentFromArguments(*arguments); - launch_info.SetEnvironment(envs, true); + if (!arguments.env.empty()) { + lldb::SBEnvironment env; + for (const auto &kv : arguments.env) + env.Set(kv.first().data(), kv.second.c_str(), true); + launch_info.SetEnvironment(env, true); + } - auto flags = launch_info.GetLaunchFlags(); + launch_info.SetDetachOnError(arguments.detachOnError); + launch_info.SetShellExpandArguments(arguments.shellExpandArguments); - flags = SetLaunchFlag(flags, arguments, "disableASLR", - lldb::eLaunchFlagDisableASLR); - flags = SetLaunchFlag(flags, arguments, "disableSTDIO", + auto flags = launch_info.GetLaunchFlags(); + flags = + SetLaunchFlag(flags, arguments.disableASLR, lldb::eLaunchFlagDisableASLR); + flags = SetLaunchFlag(flags, arguments.disableSTDIO, lldb::eLaunchFlagDisableSTDIO); - flags = SetLaunchFlag(flags, arguments, "shellExpandArguments", - lldb::eLaunchFlagShellExpandArguments); - - const bool detachOnError = - GetBoolean(arguments, "detachOnError").value_or(false); - launch_info.SetDetachOnError(detachOnError); launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug | lldb::eLaunchFlagStopAtEntry); - const auto timeout_seconds = - GetInteger<uint64_t>(arguments, "timeout").value_or(30); - if (GetBoolean(arguments, "runInTerminal").value_or(false)) { - if (llvm::Error err = RunInTerminal(dap, request, timeout_seconds)) - error.SetErrorString(llvm::toString(std::move(err)).c_str()); + if (arguments.runInTerminal) { + if (llvm::Error err = RunInTerminal(dap, arguments)) + return err; } else if (launchCommands.empty()) { + lldb::SBError error; // Disable async events so the launch will be successful when we return from // the launch call and the launch will happen synchronously dap.debugger.SetAsync(false); dap.target.Launch(launch_info, error); dap.debugger.SetAsync(true); + if (error.Fail()) + return llvm::make_error<DAPError>(error.GetCString()); } else { // Set the launch info so that run commands can access the configured // launch details. dap.target.SetLaunchInfo(launch_info); - if (llvm::Error err = dap.RunLaunchCommands(launchCommands)) { - error.SetErrorString(llvm::toString(std::move(err)).c_str()); - return error; - } + if (llvm::Error err = dap.RunLaunchCommands(launchCommands)) + return err; + // 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 launched and stopped at the entry point before // proceeding as the launch commands are not run using the synchronous // mode. - error = dap.WaitForProcessToStop(timeout_seconds); + lldb::SBError error = dap.WaitForProcessToStop(arguments.timeout); + if (error.Fail()) + return llvm::make_error<DAPError>(error.GetCString()); } - return error; + + return llvm::Error::success(); } void BaseRequestHandler::PrintWelcomeMessage() const { diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h index e13f7a3749e00..6a641c14397d6 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -71,7 +71,8 @@ class BaseRequestHandler { // runInTerminal if applicable. It doesn't do any of the additional // initialization and bookkeeping stuff that is needed for `request_launch`. // This way we can reuse the process launching logic for RestartRequest too. - lldb::SBError LaunchProcess(const llvm::json::Object &request) const; + llvm::Error + LaunchProcess(const protocol::LaunchRequestArguments &request) const; // Check if the step-granularity is `instruction`. bool HasInstructionGranularity(const llvm::json::Object &request) const; @@ -163,12 +164,21 @@ class RequestHandler : public BaseRequestHandler { } dap.Send(response); + + PostRun(); }; virtual Resp Run(const Args &) const = 0; + /// A hook for a request handler to run additional operations after the + /// request response is sent but before the next request handler. + virtual void PostRun() const {}; + protocol::ErrorResponseBody ToResponse(llvm::Error err) const { protocol::ErrorMessage error_message; + // Default to showing the user errors unless otherwise specified by a + // DAPError. + error_message.showUser = true; error_message.sendTelemetry = false; if (llvm::Error unhandled = llvm::handleErrors( std::move(err), [&](const DAPError &E) -> llvm::Error { @@ -178,8 +188,9 @@ class RequestHandler : public BaseRequestHandler { error_message.url = E.getURL(); error_message.urlLabel = E.getURLLabel(); return llvm::Error::success(); - })) + })) { error_message.format = llvm::toString(std::move(unhandled)); + } protocol::ErrorResponseBody body; body.error = error_message; return body; @@ -273,11 +284,15 @@ class InitializeRequestHandler Run(const protocol::InitializeRequestArguments &args) const override; }; -class LaunchRequestHandler : public LegacyRequestHandler { +class LaunchRequestHandler + : public RequestHandler<protocol::LaunchRequestArguments, + protocol::LaunchResponseBody> { public: - using LegacyRequestHandler::LegacyRequestHandler; + using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "launch"; } - void operator()(const llvm::json::Object &request) const override; + llvm::Error + Run(const protocol::LaunchRequestArguments &arguments) const override; + void PostRun() const override; }; class RestartRequestHandler : public LegacyRequestHandler { diff --git a/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp index c8f43b7a76e8b..e6f2d9ec669cb 100644 --- a/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp @@ -9,9 +9,10 @@ #include "DAP.h" #include "EventHelper.h" #include "JSONUtils.h" +#include "Protocol/ProtocolRequests.h" #include "RequestHandler.h" -#include "lldb/API/SBListener.h" -#include "llvm/Support/FileSystem.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/raw_ostream.h" namespace lldb_dap { @@ -60,7 +61,7 @@ void RestartRequestHandler::operator()( const llvm::json::Object &request) const { llvm::json::Object response; FillResponse(request, response); - if (!dap.last_launch_or_attach_request) { + if (!dap.target.GetProcess().IsValid()) { response["success"] = llvm::json::Value(false); EmplaceSafeString(response, "message", "Restart request received but no process was launched."); @@ -77,7 +78,7 @@ void RestartRequestHandler::operator()( // implementation detail. The adapter *did* launch the process in response to // a "launch" command, so we can still stop it and re-run it. This is why we // don't just check `dap.is_attach`. - if (GetString(*dap.last_launch_or_attach_request, "command") == "attach") { + if (!dap.last_launch_request) { response["success"] = llvm::json::Value(false); EmplaceSafeString(response, "message", "Restarting an \"attach\" session is not supported."); @@ -85,15 +86,30 @@ void RestartRequestHandler::operator()( return; } - // The optional `arguments` field in RestartRequest can contain an updated - // version of the launch arguments. If there's one, use it. - const auto *restart_arguments = request.getObject("arguments"); - if (restart_arguments) { - const auto *launch_request_arguments = - restart_arguments->getObject("arguments"); - if (launch_request_arguments) { - (*dap.last_launch_or_attach_request)["arguments"] = - llvm::json::Value(llvm::json::Object(*launch_request_arguments)); + const llvm::json::Object *arguments = request.getObject("arguments"); + if (arguments) { + // The optional `arguments` field in RestartRequest can contain an updated + // version of the launch arguments. If there's one, use it. + if (const llvm::json::Value *restart_arguments = + arguments->get("arguments")) { + protocol::LaunchRequestArguments updated_arguments; + llvm::json::Path::Root root; + if (!fromJSON(*restart_arguments, updated_arguments, root)) { + response["success"] = llvm::json::Value(false); + EmplaceSafeString( + response, "message", + llvm::formatv("Failed to parse updated launch arguments: {0}", + llvm::toString(root.getError())) + .str()); + dap.SendJSON(llvm::json::Value(std::move(response))); + return; + } + dap.last_launch_request = updated_arguments; + // 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(); } } @@ -116,7 +132,15 @@ void RestartRequestHandler::operator()( dap.thread_ids.clear(); } dap.debugger.SetAsync(true); - LaunchProcess(*dap.last_launch_or_attach_request); + + // FIXME: Should we run 'preRunCommands'? + // FIXME: Should we add a 'preRestartCommands'? + if (llvm::Error err = LaunchProcess(*dap.last_launch_request)) { + response["success"] = llvm::json::Value(false); + EmplaceSafeString(response, "message", llvm::toString(std::move(err))); + dap.SendJSON(llvm::json::Value(std::move(response))); + return; + } // This is normally done after receiving a "configuration done" request. // Because we're restarting, configuration has already happened so we can @@ -129,4 +153,5 @@ void RestartRequestHandler::operator()( dap.SendJSON(llvm::json::Value(std::move(response))); } + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 4b8e43cf23960..e4351c0f86cf4 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -1423,47 +1423,39 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit) { /// See /// https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal -llvm::json::Object -CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request, - llvm::StringRef comm_file, - lldb::pid_t debugger_pid) { +llvm::json::Object CreateRunInTerminalReverseRequest( + llvm::StringRef program, const std::vector<std::string> &args, + const llvm::StringMap<std::string> env, llvm::StringRef cwd, + llvm::StringRef comm_file, lldb::pid_t debugger_pid) { llvm::json::Object run_in_terminal_args; // This indicates the IDE to open an embedded terminal, instead of opening // the terminal in a new window. run_in_terminal_args.try_emplace("kind", "integrated"); - const auto *launch_request_arguments = launch_request.getObject("arguments"); // The program path must be the first entry in the "args" field - std::vector<std::string> args = {DAP::debug_adapter_path.str(), "--comm-file", - comm_file.str()}; + std::vector<std::string> req_args = {DAP::debug_adapter_path.str(), + "--comm-file", comm_file.str()}; if (debugger_pid != LLDB_INVALID_PROCESS_ID) { - args.push_back("--debugger-pid"); - args.push_back(std::to_string(debugger_pid)); + req_args.push_back("--debugger-pid"); + req_args.push_back(std::to_string(debugger_pid)); } - args.push_back("--launch-target"); - args.push_back( - GetString(launch_request_arguments, "program").value_or("").str()); - std::vector<std::string> target_args = - GetStrings(launch_request_arguments, "args"); - args.insert(args.end(), target_args.begin(), target_args.end()); + req_args.push_back("--launch-target"); + req_args.push_back(program.str()); + req_args.insert(req_args.end(), args.begin(), args.end()); run_in_terminal_args.try_emplace("args", args); - const auto cwd = GetString(launch_request_arguments, "cwd").value_or(""); if (!cwd.empty()) run_in_terminal_args.try_emplace("cwd", cwd); - auto envs = GetEnvironmentFromArguments(*launch_request_arguments); - llvm::json::Object env_json; - for (size_t index = 0, env_count = envs.GetNumValues(); index < env_count; - index++) { - llvm::StringRef key = envs.GetNameAtIndex(index); - llvm::StringRef value = envs.GetValueAtIndex(index); - - if (!key.empty()) - env_json.try_emplace(key, value); + if (!env.empty()) { + llvm::json::Object env_json; + for (const auto &kv : env) { + if (!kv.first().empty()) + env_json.try_emplace(kv.first(), kv.second); + } + run_in_terminal_args.try_emplace("env", + llvm::json::Value(std::move(env_json))); } - run_in_terminal_args.try_emplace("env", - llvm::json::Value(std::move(env_json))); return run_in_terminal_args; } diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index b8c53353bf42d..3eb445d8c2f40 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -18,6 +18,7 @@ #include "lldb/API/SBType.h" #include "lldb/API/SBValue.h" #include "lldb/lldb-types.h" +#include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/JSON.h" #include <cstdint> @@ -563,9 +564,17 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit); /// Create a runInTerminal reverse request object /// -/// \param[in] launch_request -/// The original launch_request object whose fields are used to construct -/// the reverse request object. +/// \param[in] program +/// Path to the program to run in the terminal. +/// +/// \param[in] args +/// The arguments for the program. +/// +/// \param[in] env +/// The environment variables to set in the terminal. +/// +/// \param[in] cwd +/// The working directory for the run in terminal request. /// /// \param[in] comm_file /// The fifo file used to communicate the with the target launcher. @@ -578,10 +587,10 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit); /// \return /// A "runInTerminal" JSON object that follows the specification outlined by /// Microsoft. -llvm::json::Object -CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request, - llvm::StringRef comm_file, - lldb::pid_t debugger_pid); +llvm::json::Object CreateRunInTerminalReverseRequest( + llvm::StringRef program, const std::vector<std::string> &args, + const llvm::StringMap<std::string> env, llvm::StringRef cwd, + llvm::StringRef comm_file, lldb::pid_t debugger_pid); /// Create a "Terminated" JSON object that contains statistics /// diff --git a/lldb/tools/lldb-dap/LLDBUtils.cpp b/lldb/tools/lldb-dap/LLDBUtils.cpp index f096477cc7b49..eaae433e7b020 100644 --- a/lldb/tools/lldb-dap/LLDBUtils.cpp +++ b/lldb/tools/lldb-dap/LLDBUtils.cpp @@ -7,11 +7,19 @@ //===----------------------------------------------------------------------===// #include "LLDBUtils.h" -#include "DAP.h" #include "JSONUtils.h" +#include "lldb/API/SBCommandInterpreter.h" +#include "lldb/API/SBCommandReturnObject.h" +#include "lldb/API/SBDebugger.h" +#include "lldb/API/SBFrame.h" #include "lldb/API/SBStringList.h" - +#include "lldb/API/SBThread.h" +#include "lldb/lldb-enumerations.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/raw_ostream.h" #include <mutex> +#include <system_error> namespace lldb_dap { diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp index ee7c653ee9f1b..a49efde390871 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Protocol/ProtocolRequests.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/JSON.h" @@ -14,6 +15,129 @@ using namespace llvm; +// The 'env' field is either an object as a map of strings or as an array of +// strings formatted like 'key=value'. +static bool parseEnv(const json::Value &Params, StringMap<std::string> &env, + json::Path P) { + const json::Object *O = Params.getAsObject(); + if (!O) { + P.report("expected object"); + return false; + } + + const json::Value *value = O->get("env"); + if (!value) + return true; + + if (const json::Object *env_obj = value->getAsObject()) { + for (const auto &kv : *env_obj) { + const std::optional<StringRef> value = kv.second.getAsString(); + if (!value) { + P.field("env").field(kv.first).report("expected string value"); + return false; + } + env.insert({kv.first.str(), value->str()}); + } + return true; + } + + if (const json::Array *env_arr = value->getAsArray()) { + for (size_t i = 0; i < env_arr->size(); ++i) { + const std::optional<StringRef> value = (*env_arr)[i].getAsString(); + if (!value) { + P.field("env").index(i).report("expected string"); + return false; + } + std::pair<StringRef, StringRef> kv = value->split("="); + env.insert({kv.first, kv.second.str()}); + } + + return true; + } + + P.field("env").report("invalid format, expected array or object"); + return false; +} + +static bool parseTimeout(const json::Value &Params, std::chrono::seconds &S, + json::Path P) { + const json::Object *O = Params.getAsObject(); + if (!O) { + P.report("expected object"); + return false; + } + + const json::Value *value = O->get("timeout"); + if (!value) + return true; + std::optional<double> timeout = value->getAsNumber(); + if (!timeout) { + P.field("timeout").report("expected number"); + return false; + } + + S = std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::duration<double>(*value->getAsNumber())); + return true; +} + +static bool +parseSourceMap(const json::Value &Params, + std::vector<std::pair<std::string, std::string>> &sourceMap, + json::Path P) { + const json::Object *O = Params.getAsObject(); + if (!O) { + P.report("expected object"); + return false; + } + + const json::Value *value = O->get("sourceMap"); + if (!value) + return true; + + if (const json::Object *map_obj = value->getAsObject()) { + for (const auto &kv : *map_obj) { + const std::optional<StringRef> value = kv.second.getAsString(); + if (!value) { + P.field("sourceMap").field(kv.first).report("expected string value"); + return false; + } + sourceMap.emplace_back(std::make_pair(kv.first.str(), value->str())); + } + return true; + } + + if (const json::Array *env_arr = value->getAsArray()) { + for (size_t i = 0; i < env_arr->size(); ++i) { + const json::Array *kv = (*env_arr)[i].getAsArray(); + if (!kv) { + P.field("sourceMap").index(i).report("expected array"); + return false; + } + if (kv->size() != 2) { + P.field("sourceMap").index(i).report("expected array of pairs"); + return false; + } + const std::optional<StringRef> first = (*kv)[0].getAsString(); + if (!first) { + P.field("sourceMap").index(0).report("expected string"); + return false; + } + const std::optional<StringRef> second = (*kv)[1].getAsString(); + if (!second) { + P.field("sourceMap").index(1).report("expected string"); + return false; + } + sourceMap.emplace_back(std::make_pair(*first, second->str())); + } + + return true; + } + + P.report("invalid format, expected array or object"); + return false; +} + namespace lldb_dap::protocol { bool fromJSON(const llvm::json::Value &Params, CancelArguments &CA, @@ -31,8 +155,7 @@ bool fromJSON(const json::Value &Params, DisconnectArguments &DA, O.map("suspendDebuggee", DA.suspendDebuggee); } -bool fromJSON(const llvm::json::Value &Params, PathFormat &PF, - llvm::json::Path P) { +bool fromJSON(const json::Value &Params, PathFormat &PF, json::Path P) { auto rawPathFormat = Params.getAsString(); if (!rawPathFormat) { P.report("expected a string"); @@ -53,7 +176,7 @@ bool fromJSON(const llvm::json::Value &Params, PathFormat &PF, return true; } -static const llvm::StringMap<ClientFeature> ClientFeatureByKey{ +static const StringMap<ClientFeature> ClientFeatureByKey{ {"supportsVariableType", eClientFeatureVariableType}, {"supportsVariablePaging", eClientFeatureVariablePaging}, {"supportsRunInTerminalRequest", eClientFeatureRunInTerminalRequest}, @@ -66,8 +189,8 @@ static const llvm::StringMap<ClientFeature> ClientFeatureByKey{ {"supportsStartDebuggingRequest", eClientFeatureStartDebuggingRequest}, {"supportsANSIStyling", eClientFeatureANSIStyling}}; -bool fromJSON(const llvm::json::Value &Params, InitializeRequestArguments &IRA, - llvm::json::Path P) { +bool fromJSON(const json::Value &Params, InitializeRequestArguments &IRA, + json::Path P) { json::ObjectMapper OM(Params, P); if (!OM) return false; @@ -98,6 +221,46 @@ bool fromJSON(const llvm::json::Value &Params, InitializeRequestArguments &IRA, OM.map("$__lldb_sourceInitFile", IRA.lldbExtSourceInitFile); } +bool fromJSON(const json::Value &Params, Configuration &C, json::Path P) { + json::ObjectMapper O(Params, P); + return O.map("debuggerRoot", C.debuggerRoot) && + O.mapOptional("enableAutoVariableSummaries", + C.enableAutoVariableSummaries) && + O.mapOptional("enableSyntheticChildDebugging", + C.enableSyntheticChildDebugging) && + O.mapOptional("displayExtendedBacktrace", + C.displayExtendedBacktrace) && + O.mapOptional("commandEscapePrefix", C.commandEscapePrefix) && + O.map("customFrameFormat", C.customFrameFormat) && + O.map("customThreadFormat", C.customThreadFormat) && + O.map("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); +} + +bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA, + json::Path P) { + json::ObjectMapper O(Params, P); + 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("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); +} + bool fromJSON(const json::Value &Params, SourceArguments &SA, json::Path P) { json::ObjectMapper O(Params, P); return O && O.map("source", SA.source) && diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index 50c16c15cef32..fcb5dbddb2e28 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -24,7 +24,9 @@ #include "Protocol/ProtocolTypes.h" #include "lldb/lldb-defines.h" #include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/StringMap.h" #include "llvm/Support/JSON.h" +#include <chrono> #include <cstdint> #include <optional> #include <string> @@ -212,8 +214,86 @@ struct Configuration { /// LLDB commands executed when the debugging session ends. std::vector<std::string> terminateCommands; + + /// Path to the executable. + /// + /// *NOTE:* When launching, either `launchCommands` or `program` must be + /// configured. If both are configured then `launchCommands` takes priority. + std::optional<std::string> program; + + /// Target triple for the program (arch-vendor-os). If not set, inferred from + /// the binary. + std::optional<std::string> targetTriple; + + /// Specify name of the platform to use for this target, creating the platform + /// if necessary. + std::optional<std::string> platformName; }; +/// lldb-dap specific launch arguments. +struct LaunchRequestArguments { + /// Common lldb-dap configuration values for launching/attaching operations. + Configuration configuration; + + /// If true, the launch request should launch the program without enabling + /// debugging. + bool noDebug = false; + + /// Launch specific operations. + /// @{ + + /// LLDB commands executed to launch the program. + /// + /// *NOTE:* Either launchCommands or program must be configured. + /// + /// If set, takes priority over the 'program' when launching the target. + std::vector<std::string> launchCommands; + + /// The program working directory. + std::optional<std::string> cwd; + + /// An array of command line argument strings to be passed to the program + /// being launched. + std::vector<std::string> args; + + /// Environment variables to set when launching the program. The format of + /// each environment variable string is "VAR=VALUE" for environment variables + /// 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. + bool detachOnError = false; + + /// Disable ASLR (Address Space Layout Randomization) when launching the + /// process. + bool disableASLR = true; + + /// Do not set up for terminal I/O to go to running process. + bool disableSTDIO = false; + + /// 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 &, + llvm::json::Path); + +/// Response to `launch` request. This is just an acknowledgement, so no body +/// field is required. +using LaunchResponseBody = VoidResponse; + /// Arguments for `source` request. struct SourceArguments { /// Specifies the source content to load. Either `source.path` or _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits