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 @@ -309,7 +309,7 @@ return response_or_request else: if response_or_request['command'] == 'runInTerminal': - subprocess.Popen(response_or_request['arguments']['args'], + subprocess.Popen(response_or_request['arguments']['args'], env=response_or_request['arguments']['env']) self.send_packet({ "type": "response", 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 @@ -17,7 +17,7 @@ mydir = TestBase.compute_mydir(__file__) - @skipUnlessDarwin + @skipIfWindows @skipIfRemote def test_runInTerminal(self): ''' 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 " + "--pid-file must also be specified.">; +def: Separate<["-"], "t">, + Alias, + HelpText<"Alias for --launch-target">; + +def pid_file: Separate<["--", "-"], "pid-file">, + MetaVarName<"">, + HelpText<"File to write the pid of the target launched with the " + "--launch-target option.">; +def: Separate<["-"], "f">, + Alias, + HelpText<"Alias for --pid-file">; 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 @@ -77,6 +77,7 @@ }; struct VSCode { + std::string debug_adaptor_path; InputStream input; OutputStream output; lldb::SBDebugger debugger; 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 @@ -26,6 +26,7 @@ #include #else #include +#include #include #include #endif @@ -1441,6 +1442,47 @@ g_vsc.SendJSON(llvm::json::Value(std::move(response))); } +std::string CreateRunInTerminalPidFile() { + llvm::SmallString<256> temp_file; + if (std::error_code EC = llvm::sys::fs::getPotentiallyUniqueTempFileName( + "lldb-vscode-run-in-terminal", "pid", temp_file)) { + llvm::errs() << "Error making unique filename: " << EC.message() << "!\n"; + exit(1); + } + std::string pid_file = temp_file.str().str(); + // We'll use a fifo file to communicate the target's pid with the debug + // adaptor. +#if !defined(_WIN32) + if (int err = mkfifo(pid_file.c_str(), 0666)) { + perror("mkfifo failed"); + exit(1); + } +#endif + return pid_file; +} + +void CleanUpRunInTerminalPidFile(const std::string &pid_file) { +#if !defined(_WIN32) + unlink(pid_file.c_str()); +#endif +} + +lldb::pid_t GetRunInTerminalDebuggeePid(const std::string &pid_file) { + lldb::pid_t pid; + std::ifstream pid_file_reader(pid_file, std::ifstream::in); + pid_file_reader >> pid; + return pid; +} + +void NotifyRunInTerminalDebuggeeWasAttached(const std::string &pid_file) { + // We create a file to notify the debuggee that we attached. + std::ofstream did_attach_file(pid_file + ".attached"); +} + +bool RunInTerminalDebugeeWasAttached(const std::string &pid_file) { + return llvm::sys::fs::exists(pid_file + ".attached"); +} + 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 @@ -1449,11 +1491,12 @@ 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); + // The debuggee will write its pid to this file + std::string pid_file = CreateRunInTerminalPidFile(); + + llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest( + launch_request, g_vsc.debug_adaptor_path, pid_file); llvm::json::Object reverse_response; lldb_vscode::PacketStatus status = g_vsc.SendReverseRequest(reverse_request, reverse_response); @@ -1461,24 +1504,32 @@ error.SetErrorString("Process cannot be launched by IDE."); 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)); + lldb::pid_t pid = GetRunInTerminalDebuggeePid(pid_file); + attach_info.SetProcessID(pid); + + g_vsc.debugger.SetAsync(false); + g_vsc.target.Attach(attach_info, error); 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); + else { + NotifyRunInTerminalDebuggeeWasAttached(pid_file); + // We resume the process to let stopOnEntry decide whether to stop the + // process or not. + g_vsc.target.GetProcess().Continue(); + } + g_vsc.debugger.SetAsync(true); } + CleanUpRunInTerminalPidFile(pid_file); + if (error.Fail()) { launch_response["success"] = llvm::json::Value(false); EmplaceSafeString(launch_response, "message", std::string(error.GetCString())); } else { + SendProcessEvent(Attach); launch_response["success"] = llvm::json::Value(true); g_vsc.SendJSON(CreateEventObject("initialized")); } @@ -1556,12 +1607,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,6 +1642,13 @@ // Run any pre run LLDB commands the user specified in the launch.json g_vsc.RunPreRunCommands(); + + if (GetBoolean(arguments, "runInTerminal", false)) { + request_runInTerminal(request, response); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + return; + } + 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 @@ -2949,6 +3001,36 @@ } int main(int argc, char *argv[]) { + g_vsc.debug_adaptor_path = argv[0]; + + LLDBVSCodeOptTable T; + unsigned MAI, MAC; + llvm::ArrayRef ArgsArr = llvm::makeArrayRef(argv + 1, argc); + llvm::opt::InputArgList input_args = T.ParseArgs(ArgsArr, MAI, MAC); + +#if !defined(_WIN32) + if (auto *target_arg = input_args.getLastArg(OPT_launch_target)) { + std::string pid_file = input_args.getLastArg(OPT_pid_file)->getValue(); + std::ofstream pid_file_writer(pid_file, std::ofstream::out); + pid_file_writer << getpid() << std::endl; + pid_file_writer.close(); + + // 20 seconds waiting to be attached. We don't wait indefinitely using a + // signal to prevent being paused forever. + for (int i = 0; i < 2000; i++) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + if (RunInTerminalDebugeeWasAttached(pid_file)) { + const char *target = target_arg->getValue(); + execv(target, argv + target_arg->getIndex() + 2); + perror("Couldn't launch target"); + exit(EXIT_FAILURE); + } + } + + llvm::outs() << "lldb-vscode didn't attach to this process"; + exit(EXIT_FAILURE); + } +#endif // Initialize LLDB first before we do anything. lldb::SBDebugger::Initialize(); @@ -2957,11 +3039,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; diff --git a/lldb/tools/lldb-vscode/package.json b/lldb/tools/lldb-vscode/package.json --- a/lldb/tools/lldb-vscode/package.json +++ b/lldb/tools/lldb-vscode/package.json @@ -178,7 +178,7 @@ }, "runInTerminal": { "type": "boolean", - "description": "Launch the program inside an integrated terminal in the IDE. Useful for debugging interactive command line programs", + "description": "Launch the program inside an integrated terminal in the IDE. Useful for debugging interactive command line programs. If the client IDE returns the \"processId\" as part of the \"runInTerminal\" reverse request, then it's suggested that the client IDE includes the field '\"runInTerminalRespondsWithPid\": true' as part of the \"launch\" request, as LLDB can guarantee attachment to the process this way.", "default": false } }