diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py @@ -282,7 +282,8 @@ trace=False, initCommands=None, preRunCommands=None, stopCommands=None, exitCommands=None, terminateCommands=None, sourcePath=None, debuggerRoot=None, launchCommands=None, - sourceMap=None, disconnectAutomatically=True, runInTerminal=False): + sourceMap=None, disconnectAutomatically=True, runInTerminal=False, + expectFailure=False): '''Sending launch request to vscode ''' @@ -317,7 +318,12 @@ debuggerRoot=debuggerRoot, launchCommands=launchCommands, sourceMap=sourceMap, - runInTerminal=runInTerminal) + runInTerminal=runInTerminal, + expectFailure=expectFailure) + + if expectFailure: + return response + if not (response and response['success']): self.assertTrue(response['success'], 'launch failed (%s)' % (response['message'])) @@ -325,6 +331,7 @@ # attached a runInTerminal process to finish initialization. if runInTerminal: self.vscode.request_configurationDone() + return response def build_and_launch(self, program, args=None, cwd=None, env=None, diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py @@ -617,7 +617,7 @@ stopCommands=None, exitCommands=None, terminateCommands=None ,sourcePath=None, debuggerRoot=None, launchCommands=None, sourceMap=None, - runInTerminal=False): + runInTerminal=False, expectFailure=False): args_dict = { 'program': program } @@ -665,9 +665,10 @@ } response = self.send_recv(command_dict) - # Wait for a 'process' and 'initialized' event in any order - self.wait_for_event(filter=['process', 'initialized']) - self.wait_for_event(filter=['process', 'initialized']) + if not expectFailure: + # Wait for a 'process' and 'initialized' event in any order + self.wait_for_event(filter=['process', 'initialized']) + self.wait_for_event(filter=['process', 'initialized']) return response def request_next(self, threadId): diff --git a/lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py b/lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py --- a/lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py +++ b/lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py @@ -11,13 +11,14 @@ import lldbvscode_testcase import time import os +import subprocess class TestVSCode_runInTerminal(lldbvscode_testcase.VSCodeTestCaseBase): mydir = TestBase.compute_mydir(__file__) - @skipUnlessDarwin + @skipIfWindows @skipIfRemote def test_runInTerminal(self): ''' @@ -46,3 +47,37 @@ # We verify we were able to set the environment env = self.vscode.request_evaluate('foo')['body']['result'] self.assertIn('bar', env) + + @skipIfWindows + @skipIfRemote + def test_runInTerminalInvalidTarget(self): + self.build_and_create_debug_adaptor() + response = self.launch( + "INVALIDPROGRAM", stopOnEntry=True, runInTerminal=True, args=["foobar"], env=["FOO=bar"], expectFailure=True) + self.assertFalse(response['success']) + self.assertIn("Could not create a target for a program 'INVALIDPROGRAM': unable to find executable", + response['message']) + + @skipIfWindows + @skipIfRemote + def test_missingArgInRunInTerminalLauncher(self): + proc = subprocess.run([self.lldbVSCodeExec, "--launch-target", "INVALIDPROGRAM"], + capture_output=True, universal_newlines=True) + self.assertTrue(proc.returncode != 0) + self.assertIn('"--launch-target" requires "--comm-files-prefix" to be specified', proc.stderr) + + @skipIfWindows + @skipIfRemote + def test_NonAttachedRunInTerminalLauncher(self): + prefix = "/tmp/lldb-vscode-runInTerminal-test" + # We clear the error file + with open(prefix + ".err", "w") as f: + pass + + proc = subprocess.run([self.lldbVSCodeExec, "--comm-files-prefix", prefix, "--launch-target", "INVALIDPROGRAM"], + capture_output=True, universal_newlines=True, env={"LLDB_VSCODE_RIT_TIMEOUT_IN_MS": "1000"}) + + # We check the error message in both stderr and the error file + self.assertIn("lldb-vscode didn't attach to this process", proc.stderr) + with open(prefix + ".err", "r") as f: + self.assertIn("lldb-vscode didn't attach to this process", f.readline()) diff --git a/lldb/tools/lldb-vscode/CMakeLists.txt b/lldb/tools/lldb-vscode/CMakeLists.txt --- a/lldb/tools/lldb-vscode/CMakeLists.txt +++ b/lldb/tools/lldb-vscode/CMakeLists.txt @@ -31,6 +31,7 @@ IOStream.cpp JSONUtils.cpp LLDBUtils.cpp + RunInTerminal.cpp SourceBreakpoint.cpp VSCode.cpp diff --git a/lldb/tools/lldb-vscode/JSONUtils.h b/lldb/tools/lldb-vscode/JSONUtils.h --- a/lldb/tools/lldb-vscode/JSONUtils.h +++ b/lldb/tools/lldb-vscode/JSONUtils.h @@ -449,11 +449,20 @@ /// The original launch_request object whose fields are used to construct /// the reverse request object. /// +/// \param[in] debug_adaptor_path +/// Path to the current debug adaptor. It will be used to delegate the +/// launch of the target. +/// +/// \param[in] pid_file +/// A file that will be used to communicate the pid of the debuggee. +/// /// \return /// A "runInTerminal" JSON object that follows the specification outlined by /// Microsoft. llvm::json::Object -CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request); +CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request, + llvm::StringRef debug_adaptor_path, + llvm::StringRef pid_file); } // namespace lldb_vscode diff --git a/lldb/tools/lldb-vscode/JSONUtils.cpp b/lldb/tools/lldb-vscode/JSONUtils.cpp --- a/lldb/tools/lldb-vscode/JSONUtils.cpp +++ b/lldb/tools/lldb-vscode/JSONUtils.cpp @@ -1001,7 +1001,9 @@ /// See /// https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal llvm::json::Object -CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request) { +CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request, + llvm::StringRef debug_adaptor_path, + llvm::StringRef pid_file) { llvm::json::Object reverse_request; reverse_request.try_emplace("type", "request"); reverse_request.try_emplace("command", "runInTerminal"); @@ -1012,10 +1014,13 @@ run_in_terminal_args.try_emplace("kind", "integrated"); auto launch_request_arguments = launch_request.getObject("arguments"); - std::vector args = GetStrings(launch_request_arguments, "args"); // The program path must be the first entry in the "args" field - args.insert(args.begin(), - GetString(launch_request_arguments, "program").str()); + std::vector args = { + debug_adaptor_path.str(), "-f", pid_file.str(), "-t", + GetString(launch_request_arguments, "program").str()}; + std::vector target_args = + GetStrings(launch_request_arguments, "args"); + args.insert(args.end(), target_args.begin(), target_args.end()); run_in_terminal_args.try_emplace("args", args); const auto cwd = GetString(launch_request_arguments, "cwd"); diff --git a/lldb/tools/lldb-vscode/Options.td b/lldb/tools/lldb-vscode/Options.td --- a/lldb/tools/lldb-vscode/Options.td +++ b/lldb/tools/lldb-vscode/Options.td @@ -23,3 +23,20 @@ def: Separate<["-"], "p">, Alias, HelpText<"Alias for --port">; + +def launch_target: Separate<["--", "-"], "launch-target">, + MetaVarName<"">, + HelpText<"Launch a target for the launchInTerminal request. Any argument " + "provided after this one will be passed to the target. The parameter " + "--comm-files-prefix must also be specified.">; +def: Separate<["-"], "t">, + Alias, + HelpText<"Alias for --launch-target">; + +def comm_files_prefix: Separate<["--", "-"], "comm-files-prefix">, + MetaVarName<"">, + HelpText<"Prefix to files used to communicate with the debug adaptor when " + "using --launch-target.">; +def: Separate<["-"], "f">, + Alias, + HelpText<"Alias for --comm-files-prefix">; diff --git a/lldb/tools/lldb-vscode/RunInTerminal.h b/lldb/tools/lldb-vscode/RunInTerminal.h new file mode 100644 --- /dev/null +++ b/lldb/tools/lldb-vscode/RunInTerminal.h @@ -0,0 +1,92 @@ +//===-- RunInTerminal.h ----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TOOLS_LLDB_VSCODE_RUNINTERMINAL_H +#define LLDB_TOOLS_LLDB_VSCODE_RUNINTERMINAL_H + +// TODO: In order to implement Windows support for this feature, basically the +// mkfifo API should be replaced by CreateNamedPipe. +#if !defined(_WIN32) + +#include + +#include "llvm/Support/Error.h" + +namespace lldb_vscode { + +/// Struct holding a list of files used to communicate the runInTerminal +/// launcher with the debug adaptor. All these files have a common prefix for +/// convenience. +struct RunInTerminalCommunicationFiles { + RunInTerminalCommunicationFiles(llvm::StringRef file_prefix); + + void DeleteFiles(); + + /// Prefix path for all the files in this struct + std::string prefix; + /// File the runInTerminal launcher can write its pid to + std::string pid_file; + /// File the runInTerminal launcher can write error messages to + std::string err_file; + /// File whose existance indicates that the debug adaptor has attached to the + /// runInTerminal launcher. + std::string did_attach_file; +}; + +class RunInTerminalDebugAdaptorCommChannel; + +using RunInTerminalDebugAdaptorCommChannelUP = + std::unique_ptr; + +class RunInTerminalDebugAdaptorCommChannel { +public: + static llvm::Expected Create(); + + ~RunInTerminalDebugAdaptorCommChannel(); + + /// Get the common path prefix for all files used for communication. + llvm::StringRef GetCommFilesPrefix(); + + /// Notify the runInTerminal launcher that it was attached. + void NotifyDidAttach(); + + /// Fetch the pid of the runInTerminal launcher or return an error + /// if a certain timeout if reached. + llvm::Expected GetLauncherPid(); + + /// Fetch any errors emitted by the runInTerminal launcher or return a + /// default error message if a certain timeout if reached. + std::string GetLauncherError(); + +private: + RunInTerminalDebugAdaptorCommChannel(llvm::StringRef prefix); + + RunInTerminalCommunicationFiles m_comm_files; +}; + +class RunInTerminalLauncherCommChannel { +public: + RunInTerminalLauncherCommChannel(llvm::StringRef prefix); + + /// Check if the debug adaptor has attach to the current process. + bool HasDebugAdaptorAttached(); + + /// Notify the debug adaptor this process' pid. + void NotifyPid(); + + /// Notify the debug adaptor that there's been an error. + void NotifyError(llvm::StringRef error); + +private: + RunInTerminalCommunicationFiles m_comm_files; +}; + +} // namespace lldb_vscode + +#endif +#endif // LLDB_TOOLS_LLDB_VSCODE_RUNINTERMINAL_H diff --git a/lldb/tools/lldb-vscode/RunInTerminal.cpp b/lldb/tools/lldb-vscode/RunInTerminal.cpp new file mode 100644 --- /dev/null +++ b/lldb/tools/lldb-vscode/RunInTerminal.cpp @@ -0,0 +1,141 @@ +//===-- RunInTerminal.cpp ---------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#if !defined(_WIN32) + +#include +#include +#include + +#include +#include +#include +#include + +#include "llvm/Support/FileSystem.h" + +#include "lldb/lldb-defines.h" + +#include "RunInTerminal.h" + +namespace lldb_vscode { + +static llvm::Error CreateFifoFile(const std::string &path) { + if (int err = mkfifo(path.c_str(), 0666)) + return llvm::createStringError( + std::error_code(err, std::generic_category()), + "Couldn't create fifo file: %s", path.c_str()); + return llvm::Error::success(); +} + +RunInTerminalCommunicationFiles::RunInTerminalCommunicationFiles( + llvm::StringRef file_prefix) + : prefix(file_prefix.str()) { + pid_file = prefix + ".pid"; + err_file = prefix + ".err"; + did_attach_file = prefix + ".attached"; +} + +void RunInTerminalCommunicationFiles::DeleteFiles() { + unlink(pid_file.c_str()); + unlink(err_file.c_str()); + unlink(did_attach_file.c_str()); +} + +llvm::Expected +RunInTerminalDebugAdaptorCommChannel::Create() { + llvm::SmallString<256> comm_files_prefix; + if (std::error_code EC = llvm::sys::fs::getPotentiallyUniqueTempFileName( + "lldb-vscode-run-in-terminal-", "", comm_files_prefix)) + return llvm::createStringError( + EC, + "Error making unique filename for runInTerminal communication files"); + + // Can't use std::make_unique because the constructor is private + RunInTerminalDebugAdaptorCommChannelUP comm_channel( + new RunInTerminalDebugAdaptorCommChannel(comm_files_prefix.str())); + llvm::Error err = + llvm::joinErrors(CreateFifoFile(comm_channel->m_comm_files.pid_file), + CreateFifoFile(comm_channel->m_comm_files.err_file)); + if (err) + return std::move(err); + return comm_channel; +} + +RunInTerminalDebugAdaptorCommChannel::~RunInTerminalDebugAdaptorCommChannel() { + m_comm_files.DeleteFiles(); +} + +llvm::StringRef RunInTerminalDebugAdaptorCommChannel::GetCommFilesPrefix() { + return m_comm_files.prefix; +} + +void RunInTerminalDebugAdaptorCommChannel::NotifyDidAttach() { + std::ofstream did_attach_file_write(m_comm_files.did_attach_file); +} + +llvm::Expected +RunInTerminalDebugAdaptorCommChannel::GetLauncherPid() { + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + std::thread pid_reader([&]() { + std::ifstream reader(m_comm_files.pid_file, std::ifstream::in); + reader >> pid; + }); + auto future = std::async(std::launch::async, &std::thread::join, &pid_reader); + // We try to read with a timeout, in case the launcher is borked. + if (future.wait_for(std::chrono::seconds(10)) == std::future_status::timeout) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Timed out trying to get the pid from the runInTerminal launcher\n"); + if (pid == LLDB_INVALID_PROCESS_ID) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Couldn't get a valid pid from the runInTerminal launcher\n"); + return pid; +} + +std::string RunInTerminalDebugAdaptorCommChannel::GetLauncherError() { + std::string error_message; + std::thread error_reader([&]() { + std::ifstream reader(m_comm_files.err_file, std::ifstream::in); + std::getline(reader, error_message); + }); + auto future = + std::async(std::launch::async, &std::thread::join, &error_reader); + // We try to read with a timeout, in case the launcher is borked. + // We don't need to wait long, as we already know there's been an error. + if (future.wait_for(std::chrono::seconds(2)) == std::future_status::timeout) + return "Timed out trying to get error messages from the runInTerminal " + "launcher"; + return error_message; +} + +RunInTerminalDebugAdaptorCommChannel::RunInTerminalDebugAdaptorCommChannel( + llvm::StringRef prefix) + : m_comm_files(prefix.str()) {} + +RunInTerminalLauncherCommChannel::RunInTerminalLauncherCommChannel( + llvm::StringRef prefix) + : m_comm_files(prefix) {} + +bool RunInTerminalLauncherCommChannel::HasDebugAdaptorAttached() { + return llvm::sys::fs::exists(m_comm_files.did_attach_file); +} + +void RunInTerminalLauncherCommChannel::NotifyPid() { + std::ofstream writer(m_comm_files.pid_file, std::ofstream::out); + writer << getpid() << std::endl; +} + +void RunInTerminalLauncherCommChannel::NotifyError(llvm::StringRef error) { + std::ofstream writer(m_comm_files.err_file, std::ofstream::out); + writer << error.str() << std::endl; +} +} // namespace lldb_vscode + +#endif diff --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h --- a/lldb/tools/lldb-vscode/VSCode.h +++ b/lldb/tools/lldb-vscode/VSCode.h @@ -47,6 +47,7 @@ #include "ExceptionBreakpoint.h" #include "FunctionBreakpoint.h" #include "IOStream.h" +#include "RunInTerminal.h" #include "SourceBreakpoint.h" #include "SourceReference.h" @@ -77,6 +78,7 @@ }; struct VSCode { + std::string debug_adaptor_path; InputStream input; OutputStream output; lldb::SBDebugger debugger; @@ -104,7 +106,6 @@ bool is_attach; uint32_t reverse_request_seq; std::map request_handlers; - std::condition_variable request_in_terminal_cv; bool waiting_for_run_in_terminal; // Keep track of the last stop thread index IDs as threads won't go away // unless we send a "thread" event to indicate the thread exited. diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp --- a/lldb/tools/lldb-vscode/lldb-vscode.cpp +++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp @@ -384,12 +384,7 @@ break; case lldb::eStateSuspended: break; - case lldb::eStateStopped: { - if (g_vsc.waiting_for_run_in_terminal) { - g_vsc.waiting_for_run_in_terminal = false; - g_vsc.request_in_terminal_cv.notify_one(); - } - } + case lldb::eStateStopped: // Only report a stopped event if the process was not restarted. if (!lldb::SBProcess::GetRestartedFromEvent(event)) { SendStdOutStdErr(process); @@ -461,7 +456,7 @@ } body.try_emplace("module", module_value); module_event.try_emplace("body", std::move(body)); - g_vsc.SendJSON(llvm::json::Value(std::move(module_event))); + // g_vsc.SendJSON(llvm::json::Value(std::move(module_event))); } } } else if (event.BroadcasterMatchesRef(g_vsc.broadcaster)) { @@ -1379,9 +1374,11 @@ filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp)); } body.try_emplace("exceptionBreakpointFilters", std::move(filters)); - // The debug adapter supports launching a debugee in intergrated VSCode - // terminal. +#if !defined(_WIN32) body.try_emplace("supportsRunInTerminalRequest", true); +#else + body.try_emplace("supportsRunInTerminalRequest", false); +#endif // The debug adapter supports stepping back via the stepBack and // reverseContinue requests. body.try_emplace("supportsStepBack", false); @@ -1441,47 +1438,61 @@ g_vsc.SendJSON(llvm::json::Value(std::move(response))); } -void request_runInTerminal(const llvm::json::Object &launch_request, - llvm::json::Object &launch_response) { - // We have already created a target that has a valid "program" path to the - // executable. We will attach to the next process whose name matches that - // of the target's. +llvm::Error request_runInTerminal(const llvm::json::Object &launch_request) { +#if defined(_WIN32) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "\"runInTerminal\" is not supported on Windows"); +#else g_vsc.is_attach = true; lldb::SBAttachInfo attach_info; - lldb::SBError error; - attach_info.SetWaitForLaunch(true, /*async*/ true); - g_vsc.target.Attach(attach_info, error); - llvm::json::Object reverse_request = - CreateRunInTerminalReverseRequest(launch_request); + llvm::Expected comm_channel_or_err = + RunInTerminalDebugAdaptorCommChannel::Create(); + if (!comm_channel_or_err) + return comm_channel_or_err.takeError(); + RunInTerminalDebugAdaptorCommChannel &comm_channel = + *comm_channel_or_err.get(); + + llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest( + launch_request, g_vsc.debug_adaptor_path, + comm_channel.GetCommFilesPrefix()); llvm::json::Object reverse_response; lldb_vscode::PacketStatus status = g_vsc.SendReverseRequest(reverse_request, reverse_response); if (status != lldb_vscode::PacketStatus::Success) - error.SetErrorString("Process cannot be launched by IDE."); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Process cannot be launched by the IDE. %s", + comm_channel.GetLauncherError().c_str()); - if (error.Success()) { - // Wait for the attach stop event to happen or for a timeout. - g_vsc.waiting_for_run_in_terminal = true; - static std::mutex mutex; - std::unique_lock locker(mutex); - g_vsc.request_in_terminal_cv.wait_for(locker, std::chrono::seconds(10)); + if (llvm::Expected pid = comm_channel.GetLauncherPid()) + attach_info.SetProcessID(*pid); + else + return pid.takeError(); - auto attached_pid = g_vsc.target.GetProcess().GetProcessID(); - if (attached_pid == LLDB_INVALID_PROCESS_ID) - error.SetErrorString("Failed to attach to a process"); - else - SendProcessEvent(Attach); - } + g_vsc.debugger.SetAsync(false); + lldb::SBError error; + g_vsc.target.Attach(attach_info, error); - if (error.Fail()) { - launch_response["success"] = llvm::json::Value(false); - EmplaceSafeString(launch_response, "message", - std::string(error.GetCString())); - } else { - launch_response["success"] = llvm::json::Value(true); - g_vsc.SendJSON(CreateEventObject("initialized")); - } + if (error.Fail()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Failed to attach to the target process. %s", + comm_channel.GetLauncherError().c_str()); + // We just attached to the runInTerminal launcher, which was waiting to be + // attached. After resuming, it will exec and become the original target the + // user provided. + + // This will notify the runInTerminal launcher that we attached. + comm_channel.NotifyDidAttach(); + + // Here, we resume the process so that it can exec. It'll be stopped at the + // exec point. + g_vsc.target.GetProcess().Continue(); + + g_vsc.debugger.SetAsync(true); + + return llvm::Error::success(); +#endif } // "LaunchRequest": { @@ -1556,12 +1567,6 @@ return; } - if (GetBoolean(arguments, "runInTerminal", false)) { - request_runInTerminal(request, response); - g_vsc.SendJSON(llvm::json::Value(std::move(response))); - return; - } - // Instantiate a launch info instance for the target. auto launch_info = g_vsc.target.GetLaunchInfo(); @@ -1597,7 +1602,11 @@ // Run any pre run LLDB commands the user specified in the launch.json g_vsc.RunPreRunCommands(); - if (launchCommands.empty()) { + + if (GetBoolean(arguments, "runInTerminal", false)) { + if (llvm::Error err = request_runInTerminal(request)) + error.SetErrorString(llvm::toString(std::move(err)).c_str()); + } else if (launchCommands.empty()) { // Disable async events so the launch will be successful when we return from // the launch call and the launch will happen synchronously g_vsc.debugger.SetAsync(false); @@ -1616,10 +1625,11 @@ } g_vsc.SendJSON(llvm::json::Value(std::move(response))); - SendProcessEvent(Launch); + if (g_vsc.is_attach) + SendProcessEvent(Attach); // this happens when doing runInTerminal + else + SendProcessEvent(Launch); g_vsc.SendJSON(llvm::json::Value(CreateEventObject("initialized"))); - // Reenable async events and start the event thread to catch async events. - // g_vsc.debugger.SetAsync(true); } // "NextRequest": { @@ -2948,7 +2958,81 @@ llvm::outs() << examples; } +// If --launch-target is provided, this instance of lldb-vscode becomes a +// runInTerminal launcher. It will ultimately launch the program specified in +// the --launch-target argument, which is the original program the user wanted +// to debug. This is done in such a way that the actual debug adaptor can +// place breakpoints at the beginning of the program. +// +// The launcher will communicate with the debug adaptor using files whose path +// prefix is specified in the --comm-files-prefix argument. Both this launcher +// and the debug adaptor should use the same path prefix to guarantee correct +// communication. +// +// Regarding the actual flow, this launcher will first notify the debug adaptor +// its pid. Then, the launcher will be in a pending state waiting to be attached +// by the adaptor. +// +// Once attached and resumed, the launcher will exec and become the program +// specified by --launch-target, which is the original target the +// user wanted to run. +// +// In case of errors launching the target, a suitable error message will be +// emitted to the debug adaptor. +[[noreturn]] void LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, + llvm::StringRef comm_files_prefix, + char *argv[]) { +#if defined(_WIN32) + llvm::errs() << "\"--launch-target\" is not supported on Windows\n"; + exit(EXIT_FAILURE); +#else + RunInTerminalLauncherCommChannel comm_channel(comm_files_prefix); + comm_channel.NotifyPid(); + + // Some seconds waiting to be attached. We don't wait indefinitely using a + // signal to prevent being paused forever. + const char *timeout_env_var = getenv("LLDB_VSCODE_RIT_TIMEOUT_IN_MS"); + int timeout_in_ms = + timeout_env_var != nullptr ? atoi(timeout_env_var) : 10000; + for (int i = 0; i < timeout_in_ms / 10; i++) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + if (comm_channel.HasDebugAdaptorAttached()) { + const char *target = target_arg.getValue(); + execv(target, argv + target_arg.getIndex() + 2); + + comm_channel.NotifyError(std::strerror(errno)); + llvm::errs() << std::strerror(errno) << "\n"; + + exit(EXIT_FAILURE); + } + } + + comm_channel.NotifyError("lldb-vscode didn't attach to this process"); + llvm::errs() << "lldb-vscode didn't attach to this process\n"; + exit(EXIT_FAILURE); +#endif +} + int main(int argc, char *argv[]) { + llvm::SmallString<256> program_path(argv[0]); + llvm::sys::fs::make_absolute(program_path); + g_vsc.debug_adaptor_path = program_path.str().str(); + + LLDBVSCodeOptTable T; + unsigned MAI, MAC; + llvm::ArrayRef ArgsArr = llvm::makeArrayRef(argv + 1, argc); + llvm::opt::InputArgList input_args = T.ParseArgs(ArgsArr, MAI, MAC); + + if (auto *target_arg = input_args.getLastArg(OPT_launch_target)) { + if (auto *comm_file_prefix = input_args.getLastArg(OPT_comm_files_prefix)) + LaunchRunInTerminalTarget(*target_arg, comm_file_prefix->getValue(), + argv); + else { + llvm::errs() << "\"--launch-target\" requires \"--comm-files-prefix\" to " + "be specified\n"; + exit(EXIT_FAILURE); + } + } // Initialize LLDB first before we do anything. lldb::SBDebugger::Initialize(); @@ -2957,11 +3041,6 @@ int portno = -1; - LLDBVSCodeOptTable T; - unsigned MAI, MAC; - llvm::ArrayRef ArgsArr = llvm::makeArrayRef(argv + 1, argc); - llvm::opt::InputArgList input_args = T.ParseArgs(ArgsArr, MAI, MAC); - if (input_args.hasArg(OPT_help)) { printHelp(T, llvm::sys::path::filename(argv[0])); return 0;