https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/174635
>From cda9c2c8635f0d6dd07f4f23e523ad2eaee68605 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Wed, 7 Jan 2026 19:39:39 +0000 Subject: [PATCH 01/16] [lldb-dap][windows] add support for the integratedTerminal flag --- .../Host/windows/ProcessLauncherWindows.h | 16 +- .../runInTerminal/TestDAP_runInTerminal.py | 154 +++++++++++++----- lldb/tools/lldb-dap/FifoFiles.cpp | 80 ++++++++- lldb/tools/lldb-dap/FifoFiles.h | 14 +- .../tools/lldb-dap/Handler/RequestHandler.cpp | 4 +- lldb/tools/lldb-dap/RunInTerminal.cpp | 23 ++- lldb/tools/lldb-dap/RunInTerminal.h | 3 + lldb/tools/lldb-dap/tool/lldb-dap.cpp | 130 +++++++++++++-- lldb/unittests/DAP/FifoFilesTest.cpp | 8 +- 9 files changed, 351 insertions(+), 81 deletions(-) diff --git a/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h b/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h index 7e1402f9a7d93..06085bb36c36c 100644 --- a/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h +++ b/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h @@ -81,7 +81,6 @@ class ProcessLauncherWindows : public ProcessLauncher { HostProcess LaunchProcess(const ProcessLaunchInfo &launch_info, Status &error) override; -protected: /// Get the list of Windows handles that should be inherited by the child /// process and update `STARTUPINFOEXW` with the handle list. /// @@ -153,6 +152,21 @@ llvm::ErrorOr<std::wstring> GetFlattenedWindowsCommandStringW(const Args &args); llvm::ErrorOr<std::wstring> GetFlattenedWindowsCommandStringW(llvm::ArrayRef<const char *> args); + +/// Allocate and initialize a PROC_THREAD_ATTRIBUTE_LIST structure +/// that can be used with CreateProcess to specify extended process creation +/// attributes (such as inherited handles). +/// +/// \param[in] startupinfoex The STARTUPINFOEXW structure whose lpAttributeList +/// will +/// be initialized. +/// +/// \return On success, returns a scope_exit cleanup object that will +/// automatically +/// delete and free the attribute list when it goes out of scope. +/// On failure, returns the corresponding Windows error code. +llvm::ErrorOr<llvm::scope_exit<std::function<void()>>> +SetupProcThreadAttributeList(STARTUPINFOEXW &startupinfoex); } #endif diff --git a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py index 0fdc719b6cb76..aa2688464faf8 100644 --- a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py +++ b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py @@ -2,6 +2,7 @@ Test lldb-dap runInTerminal reverse request """ +from contextlib import contextmanager from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import line_number import lldbdap_testcase @@ -9,27 +10,82 @@ import subprocess import json +@contextmanager +def fifo(*args, **kwargs): + if sys.platform == "win32": + comm_file = r"\\.\pipe\lldb-dap-run-in-terminal-comm" + + import ctypes + PIPE_ACCESS_DUPLEX = 0x00000003 + PIPE_TYPE_BYTE = 0x00000000 + PIPE_READMODE_BYTE = 0x00000000 + PIPE_WAIT = 0x00000000 + PIPE_UNLIMITED_INSTANCES = 255 + kernel32 = ctypes.windll.kernel32 + + pipe = kernel32.CreateNamedPipeW( + comm_file, + PIPE_ACCESS_DUPLEX, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, None + ) + else: + comm_file = os.path.join(kwargs["directory"], "comm-file") + pipe = None + os.mkfifo(comm_file) + + try: + yield comm_file, pipe + finally: + if pipe is not None: + kernel32.DisconnectNamedPipe(pipe) + kernel32.CloseHandle(pipe) @skipIfBuildType(["debug"]) class TestDAP_runInTerminal(lldbdap_testcase.DAPTestCaseBase): SHARED_BUILD_TESTCASE = False - def read_pid_message(self, fifo_file): - with open(fifo_file, "r") as file: - self.assertIn("pid", file.readline()) + def read_pid_message(self, fifo_file, pipe): + if sys.platform == "win32": + import ctypes + buffer = ctypes.create_string_buffer(4096) + bytes_read = ctypes.wintypes.DWORD() + kernel32 = ctypes.windll.kernel32 + kernel32.ConnectNamedPipe(pipe, None) + kernel32.ReadFile(pipe, buffer, 4096, ctypes.byref(bytes_read), None) + self.assertIn("pid", buffer.value.decode()) + else: + with open(fifo_file, "r") as file: + self.assertIn("pid", file.readline()) @staticmethod - def send_did_attach_message(fifo_file): - with open(fifo_file, "w") as file: - file.write(json.dumps({"kind": "didAttach"}) + "\n") + def send_did_attach_message(fifo_file, pipe=None): + message = json.dumps({"kind": "didAttach"}) + "\n" + if sys.platform == "win32": + import ctypes + kernel32 = ctypes.windll.kernel32 + bytes_written = ctypes.wintypes.DWORD() + kernel32.ConnectNamedPipe(pipe, None) + kernel32.WriteFile(pipe, message.encode(), len(message), ctypes.byref(bytes_written), None) + else: + with open(fifo_file, "w") as file: + file.write(message) @staticmethod - def read_error_message(fifo_file): - with open(fifo_file, "r") as file: - return file.readline() + def read_error_message(fifo_file, pipe=None): + if sys.platform == "win32": + import ctypes + buffer = ctypes.create_string_buffer(4096) + bytes_read = ctypes.wintypes.DWORD() + kernel32 = ctypes.windll.kernel32 + kernel32.ConnectNamedPipe(pipe, None) + kernel32.ReadFile(pipe, buffer, 4096, ctypes.byref(bytes_read), None) + return buffer.value.decode() + else: + with open(fifo_file, "r") as file: + return file.readline() @skipIfAsan - @skipIfWindows def test_runInTerminal(self): """ Tests the "runInTerminal" reverse request. It makes sure that the IDE can @@ -77,7 +133,6 @@ def test_runInTerminal(self): self.continue_to_exit() @skipIfAsan - @skipIfWindows def test_runInTerminalWithObjectEnv(self): """ Tests the "runInTerminal" reverse request. It makes sure that the IDE can @@ -101,7 +156,6 @@ def test_runInTerminalWithObjectEnv(self): self.continue_to_exit() - @skipIfWindows def test_runInTerminalInvalidTarget(self): self.build_and_create_debug_adapter() response = self.launch_and_configurationDone( @@ -116,7 +170,6 @@ def test_runInTerminalInvalidTarget(self): response["body"]["error"]["format"], ) - @skipIfWindows def test_missingArgInRunInTerminalLauncher(self): proc = subprocess.run( [self.lldbDAPExec, "--launch-target", "INVALIDPROGRAM"], @@ -152,44 +205,55 @@ def test_FakeAttachedRunInTerminalLauncherWithInvalidProgram(self): _, stderr = proc.communicate() self.assertIn("No such file or directory", stderr) - @skipIfWindows - def test_FakeAttachedRunInTerminalLauncherWithValidProgram(self): - comm_file = os.path.join(self.getBuildDir(), "comm-file") - os.mkfifo(comm_file) - - proc = subprocess.Popen( - [ - self.lldbDAPExec, - "--comm-file", - comm_file, - "--launch-target", - "echo", - "foo", - ], - universal_newlines=True, - stdout=subprocess.PIPE, - ) + @skipUnlessWindows + def test_FakeAttachedRunInTerminalLauncherWithInvalidProgramWindows(self): + with fifo(directory=self.getBuildDir()) as (comm_file, pipe): + subprocess.Popen( + [ + self.lldbDAPExec, + "--comm-file", + comm_file, + "--launch-target", + "INVALIDPROGRAM", + ], + universal_newlines=True, + stderr=subprocess.PIPE, + ) + + self.assertIn("Failed to launch target process", self.read_error_message(comm_file, pipe)) - self.read_pid_message(comm_file) - self.send_did_attach_message(comm_file) + def test_FakeAttachedRunInTerminalLauncherWithValidProgram(self): + with fifo(directory=self.getBuildDir()) as (comm_file, pipe): + proc = subprocess.Popen( + [ + self.lldbDAPExec, + "--comm-file", + comm_file, + "--launch-target", + "echo", + "foo", + ], + universal_newlines=True, + stdout=subprocess.PIPE, + ) + + self.read_pid_message(comm_file, pipe) + self.send_did_attach_message(comm_file, pipe) stdout, _ = proc.communicate() self.assertIn("foo", stdout) - @skipIfWindows def test_FakeAttachedRunInTerminalLauncherAndCheckEnvironment(self): - comm_file = os.path.join(self.getBuildDir(), "comm-file") - os.mkfifo(comm_file) - - proc = subprocess.Popen( - [self.lldbDAPExec, "--comm-file", comm_file, "--launch-target", "env"], - universal_newlines=True, - stdout=subprocess.PIPE, - env={**os.environ, "FOO": "BAR"}, - ) - - self.read_pid_message(comm_file) - self.send_did_attach_message(comm_file) + with fifo(directory=self.getBuildDir()) as (comm_file, pipe): + proc = subprocess.Popen( + [self.lldbDAPExec, "--comm-file", comm_file, "--launch-target", "env"], + universal_newlines=True, + stdout=subprocess.PIPE, + env={**os.environ, "FOO": "BAR"}, + ) + + self.read_pid_message(comm_file, pipe) + self.send_did_attach_message(comm_file, pipe) stdout, _ = proc.communicate() self.assertIn("FOO=BAR", stdout) diff --git a/lldb/tools/lldb-dap/FifoFiles.cpp b/lldb/tools/lldb-dap/FifoFiles.cpp index 1f1bba80bd3b1..3775e2f6b0143 100644 --- a/lldb/tools/lldb-dap/FifoFiles.cpp +++ b/lldb/tools/lldb-dap/FifoFiles.cpp @@ -9,7 +9,11 @@ #include "FifoFiles.h" #include "JSONUtils.h" -#if !defined(_WIN32) +#ifdef _WIN32 +#include "lldb/Host/windows/PipeWindows.h" +#include "lldb/Host/windows/windows.h" +#include "llvm/Support/Path.h" +#else #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> @@ -24,7 +28,14 @@ using namespace llvm; namespace lldb_dap { -FifoFile::FifoFile(StringRef path) : m_path(path) {} +FifoFile::FifoFile(StringRef path, lldb::pipe_t pipe) : m_path(path) { +#ifdef _WIN32 + if (pipe == INVALID_HANDLE_VALUE) + pipe = CreateFileA(m_path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); +#endif + m_pipe = pipe; +} FifoFile::~FifoFile() { #if !defined(_WIN32) @@ -32,9 +43,63 @@ FifoFile::~FifoFile() { #endif } +void FifoFile::WriteLine(std::string line) { +#ifdef _WIN32 + DWORD written; + line += "\n"; + WriteFile(m_pipe, line.c_str(), static_cast<DWORD>(line.size()), &written, + NULL); + FlushFileBuffers(m_pipe); +#else + std::ofstream writer(m_path, std::ofstream::out); + writer << line << std::endl; +#endif +} + +void FifoFile::Connect() { +#ifdef _WIN32 + ConnectNamedPipe(m_pipe, NULL); +#endif +} + +std::string FifoFile::ReadLine() { +#ifdef _WIN32 + std::string buffer; + char read_buffer[4096]; + DWORD bytes_read; + + if (ReadFile(m_pipe, read_buffer, sizeof(read_buffer) - 1, &bytes_read, + NULL) && + bytes_read > 0) { + read_buffer[bytes_read] = '\0'; + buffer = read_buffer; + } + + return buffer; +#else + std::ifstream reader(m_path, std::ifstream::in); + std::string buffer; + std::getline(reader, buffer); + return buffer; +#endif +} + Expected<std::shared_ptr<FifoFile>> CreateFifoFile(StringRef path) { #if defined(_WIN32) - return createStringError(inconvertibleErrorCode(), "Unimplemented"); + assert(path.starts_with("\\\\.\\pipe\\") && + "FifoFile path should start with '\\\\.\\pipe\\'"); + HANDLE pipe_handle = + CreateNamedPipeA(path.data(), PIPE_ACCESS_DUPLEX, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, NULL); + + if (pipe_handle == INVALID_HANDLE_VALUE) { + DWORD error = GetLastError(); + return createStringError(std::error_code(error, std::system_category()), + "Couldn't create named pipe: %s", path.data()); + } + + return std::make_shared<FifoFile>(path, pipe_handle); #else if (int err = mkfifo(path.data(), 0600)) return createStringError(std::error_code(err, std::generic_category()), @@ -43,7 +108,7 @@ Expected<std::shared_ptr<FifoFile>> CreateFifoFile(StringRef path) { #endif } -FifoFileIO::FifoFileIO(StringRef fifo_file, StringRef other_endpoint_name) +FifoFileIO::FifoFileIO(FifoFile fifo_file, StringRef other_endpoint_name) : m_fifo_file(fifo_file), m_other_endpoint_name(other_endpoint_name) {} Expected<json::Value> FifoFileIO::ReadJSON(std::chrono::milliseconds timeout) { @@ -52,9 +117,7 @@ Expected<json::Value> FifoFileIO::ReadJSON(std::chrono::milliseconds timeout) { std::optional<std::string> line; std::future<void> *future = new std::future<void>(std::async(std::launch::async, [&]() { - std::ifstream reader(m_fifo_file, std::ifstream::in); - std::string buffer; - std::getline(reader, buffer); + std::string buffer = m_fifo_file.ReadLine(); if (!buffer.empty()) line = buffer; })); @@ -78,8 +141,7 @@ Error FifoFileIO::SendJSON(const json::Value &json, bool done = false; std::future<void> *future = new std::future<void>(std::async(std::launch::async, [&]() { - std::ofstream writer(m_fifo_file, std::ofstream::out); - writer << JSONToString(json) << std::endl; + m_fifo_file.WriteLine(JSONToString(json)); done = true; })); if (future->wait_for(timeout) == std::future_status::timeout || !done) { diff --git a/lldb/tools/lldb-dap/FifoFiles.h b/lldb/tools/lldb-dap/FifoFiles.h index 633ebeb2aedd4..e21471fcc78c0 100644 --- a/lldb/tools/lldb-dap/FifoFiles.h +++ b/lldb/tools/lldb-dap/FifoFiles.h @@ -9,6 +9,7 @@ #ifndef LLDB_TOOLS_LLDB_DAP_FIFOFILES_H #define LLDB_TOOLS_LLDB_DAP_FIFOFILES_H +#include "lldb/Host/windows/PipeWindows.h" #include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" @@ -20,11 +21,18 @@ namespace lldb_dap { /// /// The file is destroyed when the destructor is invoked. struct FifoFile { - FifoFile(llvm::StringRef path); + FifoFile(llvm::StringRef path, lldb::pipe_t pipe = LLDB_INVALID_PIPE); ~FifoFile(); + void Connect(); + + void WriteLine(std::string line); + + std::string ReadLine(); + std::string m_path; + lldb::pipe_t m_pipe; }; /// Create a fifo file in the filesystem. @@ -45,7 +53,7 @@ class FifoFileIO { /// \param[in] other_endpoint_name /// A human readable name for the other endpoint that will communicate /// using this file. This is used for error messages. - FifoFileIO(llvm::StringRef fifo_file, llvm::StringRef other_endpoint_name); + FifoFileIO(FifoFile fifo_file, llvm::StringRef other_endpoint_name); /// Read the next JSON object from the underlying input fifo file. /// @@ -76,7 +84,7 @@ class FifoFileIO { std::chrono::milliseconds timeout = std::chrono::milliseconds(20000)); private: - std::string m_fifo_file; + FifoFile m_fifo_file; std::string m_other_endpoint_name; }; diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index 5e8c2163c838f..a2051d0d3f5ac 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -105,7 +105,7 @@ RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) { return comm_file_or_err.takeError(); FifoFile &comm_file = *comm_file_or_err.get(); - RunInTerminalDebugAdapterCommChannel comm_channel(comm_file.m_path); + RunInTerminalDebugAdapterCommChannel comm_channel(comm_file); lldb::pid_t debugger_pid = LLDB_INVALID_PROCESS_ID; #if !defined(_WIN32) @@ -118,7 +118,7 @@ RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) { arguments.console == protocol::eConsoleExternalTerminal); dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal", std::move(reverse_request)); - + comm_file.Connect(); // we need to wait for the client to connect to the pipe. if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid()) attach_info.SetProcessID(*pid); else diff --git a/lldb/tools/lldb-dap/RunInTerminal.cpp b/lldb/tools/lldb-dap/RunInTerminal.cpp index 9f309dd78221a..7d20ca1c13987 100644 --- a/lldb/tools/lldb-dap/RunInTerminal.cpp +++ b/lldb/tools/lldb-dap/RunInTerminal.cpp @@ -9,7 +9,9 @@ #include "RunInTerminal.h" #include "JSONUtils.h" -#if !defined(_WIN32) +#ifdef _WIN32 +#include "lldb/Host/windows/windows.h" +#else #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> @@ -97,7 +99,7 @@ static Error ToError(const RunInTerminalMessage &message) { RunInTerminalLauncherCommChannel::RunInTerminalLauncherCommChannel( StringRef comm_file) - : m_io(comm_file, "debug adapter") {} + : m_io(FifoFileIO(FifoFile(comm_file), "debug adapter")) {} Error RunInTerminalLauncherCommChannel::WaitUntilDebugAdapterAttaches( std::chrono::milliseconds timeout) { @@ -112,7 +114,11 @@ Error RunInTerminalLauncherCommChannel::WaitUntilDebugAdapterAttaches( } Error RunInTerminalLauncherCommChannel::NotifyPid() { - return m_io.SendJSON(RunInTerminalMessagePid(getpid()).ToJSON()); + return NotifyPid(getpid()); +} + +Error RunInTerminalLauncherCommChannel::NotifyPid(int pid) { + return m_io.SendJSON(RunInTerminalMessagePid(pid).ToJSON()); } void RunInTerminalLauncherCommChannel::NotifyError(StringRef error) { @@ -125,6 +131,10 @@ RunInTerminalDebugAdapterCommChannel::RunInTerminalDebugAdapterCommChannel( StringRef comm_file) : m_io(comm_file, "runInTerminal launcher") {} +RunInTerminalDebugAdapterCommChannel::RunInTerminalDebugAdapterCommChannel( + FifoFile comm_file) + : m_io(comm_file, "runInTerminal launcher") {} + // Can't use \a std::future<llvm::Error> because it doesn't compile on Windows std::future<lldb::SBError> RunInTerminalDebugAdapterCommChannel::NotifyDidAttach() { @@ -159,12 +169,19 @@ std::string RunInTerminalDebugAdapterCommChannel::GetLauncherError() { Expected<std::shared_ptr<FifoFile>> CreateRunInTerminalCommFile() { SmallString<256> comm_file; +#if _WIN32 + char pipe_name[MAX_PATH]; + sprintf(pipe_name, "\\\\.\\pipe\\lldb-dap-run-in-terminal-comm-%d", + GetCurrentProcessId()); + return CreateFifoFile(pipe_name); +#else if (std::error_code EC = sys::fs::getPotentiallyUniqueTempFileName( "lldb-dap-run-in-terminal-comm", "", comm_file)) return createStringError(EC, "Error making unique file name for " "runInTerminal communication files"); return CreateFifoFile(comm_file.str()); +#endif } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/RunInTerminal.h b/lldb/tools/lldb-dap/RunInTerminal.h index 457850c8ea538..ff727603709d3 100644 --- a/lldb/tools/lldb-dap/RunInTerminal.h +++ b/lldb/tools/lldb-dap/RunInTerminal.h @@ -89,6 +89,8 @@ class RunInTerminalLauncherCommChannel { /// out. llvm::Error NotifyPid(); + llvm::Error NotifyPid(int pid); + /// Notify the debug adapter that there's been an error. void NotifyError(llvm::StringRef error); @@ -99,6 +101,7 @@ class RunInTerminalLauncherCommChannel { class RunInTerminalDebugAdapterCommChannel { public: RunInTerminalDebugAdapterCommChannel(llvm::StringRef comm_file); + RunInTerminalDebugAdapterCommChannel(FifoFile comm_file); /// Notify the runInTerminal launcher that it was attached. /// diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index afbc2c7f4c28c..e47fcfb610c6e 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -71,6 +71,9 @@ #include <io.h> typedef int socklen_t; #include "lldb/Host/windows/PythonPathSetup/PythonPathSetup.h" +#include "lldb/Host/windows/ProcessLauncherWindows.h" +#include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/Program.h" #else #include <netinet/in.h> #include <sys/socket.h> @@ -179,6 +182,22 @@ static llvm::Error LaunchClient(const llvm::opt::InputArgList &args) { return ClientLauncher::GetLauncher(*client)->Launch(launch_args); } +llvm::Error +notifyError(RunInTerminalLauncherCommChannel &comm_channel, std::string message, + std::optional<std::error_code> error_code = std::nullopt) { + comm_channel.NotifyError(message); + + std::error_code ec = error_code.value_or( +#ifdef _WIN32 + std::error_code(GetLastError(), std::system_category()) +#else + llvm::inconvertibleErrorCode() +#endif + ); + + return llvm::createStringError(ec, std::move(message)); +} + #if not defined(_WIN32) struct FDGroup { int GetFlags() const { @@ -270,11 +289,12 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, llvm::StringRef comm_file, lldb::pid_t debugger_pid, llvm::StringRef stdio, - char *argv[]) { -#if defined(_WIN32) - return llvm::createStringError( - "runInTerminal is only supported on POSIX systems"); -#else + char *argv[], + int argc) { + // This env var should be used only for tests. + const char *timeout_env_var = getenv("LLDB_DAP_RIT_TIMEOUT_IN_MS"); + int timeout_in_ms = + timeout_env_var != nullptr ? atoi(timeout_env_var) : 20000; // On Linux with the Yama security module enabled, a process can only attach // to its descendants by default. In the runInTerminal case the target @@ -285,6 +305,95 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, #endif lldb_private::FileSystem::Initialize(); + +#ifdef _WIN32 + RunInTerminalLauncherCommChannel comm_channel(comm_file); + + llvm::ArrayRef<const char *> args_arr = llvm::ArrayRef(argv + 1, argc); + auto wcommandLineOrErr = + lldb_private::GetFlattenedWindowsCommandStringW(args_arr); + if (!wcommandLineOrErr) + return notifyError(comm_channel, "Failed to process arguments"); + + STARTUPINFOEXW startupinfoex = {}; + startupinfoex.StartupInfo.cb = sizeof(STARTUPINFOEXW); + startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + + HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE); + HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); + llvm::scope_exit close_handles([&] { + if (stdin_handle) + ::CloseHandle(stdin_handle); + if (stdout_handle) + ::CloseHandle(stdout_handle); + if (stderr_handle) + ::CloseHandle(stderr_handle); + }); + + auto attributelist_cleanup_or_err = + lldb_private::SetupProcThreadAttributeList(startupinfoex); + if (!attributelist_cleanup_or_err) { + return notifyError(comm_channel, "Could not open inherited handles", + attributelist_cleanup_or_err.getError()); + } + auto attributelist_cleanup = std::move(*attributelist_cleanup_or_err); + + if (!stdio.empty()) { + llvm::SmallVector<llvm::StringRef, 3> files; + stdio.split(files, ':'); + while (files.size() < 3) + files.push_back(files.back()); + + stdin_handle = lldb_private::ProcessLauncherWindows::GetStdioHandle( + files[0], STDIN_FILENO); + stdout_handle = lldb_private::ProcessLauncherWindows::GetStdioHandle( + files[1], STDOUT_FILENO); + stderr_handle = lldb_private::ProcessLauncherWindows::GetStdioHandle( + files[2], STDERR_FILENO); + } + auto inherited_handles_or_err = + lldb_private::ProcessLauncherWindows::GetInheritedHandles( + startupinfoex, /*launch_info*=*/nullptr, stdout_handle, stderr_handle, + stdin_handle); + + if (!inherited_handles_or_err) + return notifyError(comm_channel, "Failed to get inherited handles", + inherited_handles_or_err.getError()); + std::vector<HANDLE> inherited_handles = std::move(*inherited_handles_or_err); + + PROCESS_INFORMATION pi = {}; + + // Start the process in a suspended state, while we attach the debugger. + BOOL result = CreateProcessW( + /*lpApplicationName=*/NULL, /*lpCommandLine=*/&(*wcommandLineOrErr)[0], + /*lpProcessAttributes=*/NULL, /*lpThreadAttributes=*/NULL, + /*bInheritHandles=*/!inherited_handles.empty(), + /*dwCreationFlags=*/CREATE_SUSPENDED, /*lpEnvironment=*/NULL, + /*lpCurrentDirectory=*/NULL, + /*lpStartupInfo=*/reinterpret_cast<STARTUPINFOW *>(&startupinfoex), + /*lpProcessInformation=*/&pi); + + if (!result) + return notifyError(comm_channel, "Failed to launch target process"); + + // Notify the pid of the process to debug to the debugger. It will attach to + // the newly created process. + if (llvm::Error err = comm_channel.NotifyPid(pi.dwProcessId)) + return err; + + if (llvm::Error err = comm_channel.WaitUntilDebugAdapterAttaches( + std::chrono::milliseconds(timeout_in_ms))) + return err; + + // The debugger attached to the process. We can resume it. + if (!ResumeThread(pi.hThread)) + return notifyError(comm_channel, "Failed to resume the target process"); + + // Wait for child to complete to match POSIX behavior. + WaitForSingleObject(pi.hProcess, INFINITE); + ExitProcess(0); // TODO: Should we return the exit code of the process? +#else if (!stdio.empty()) { constexpr size_t num_of_stdio = 3; llvm::SmallVector<llvm::StringRef, num_of_stdio> stdio_files; @@ -310,10 +419,6 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, // We will wait to be attached with a timeout. We don't wait indefinitely // using a signal to prevent being paused forever. - // This env var should be used only for tests. - const char *timeout_env_var = getenv("LLDB_DAP_RIT_TIMEOUT_IN_MS"); - int timeout_in_ms = - timeout_env_var != nullptr ? atoi(timeout_env_var) : 20000; if (llvm::Error err = comm_channel.WaitUntilDebugAdapterAttaches( std::chrono::milliseconds(timeout_in_ms))) { return err; @@ -322,10 +427,7 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, const char *target = target_arg.getValue(); execvp(target, argv); - std::string error = std::strerror(errno); - comm_channel.NotifyError(error); - return llvm::createStringError(llvm::inconvertibleErrorCode(), - std::move(error)); + return notifyError(comm_channel, std::strerror(errno)); #endif } @@ -615,7 +717,7 @@ int main(int argc, char *argv[]) { llvm::StringRef stdio = input_args.getLastArgValue(OPT_stdio); if (llvm::Error err = LaunchRunInTerminalTarget(*target_arg, comm_file->getValue(), pid, - stdio, argv + target_args_pos)) { + stdio, argv + target_args_pos, argc - target_args_pos)) { llvm::errs() << llvm::toString(std::move(err)) << '\n'; return EXIT_FAILURE; } diff --git a/lldb/unittests/DAP/FifoFilesTest.cpp b/lldb/unittests/DAP/FifoFilesTest.cpp index bbc1b608e91bd..bd8aa64ef1ecd 100644 --- a/lldb/unittests/DAP/FifoFilesTest.cpp +++ b/lldb/unittests/DAP/FifoFilesTest.cpp @@ -45,8 +45,8 @@ TEST(FifoFilesTest, SendAndReceiveJSON) { auto fifo = CreateFifoFile(fifo_path); EXPECT_THAT_EXPECTED(fifo, llvm::Succeeded()); - FifoFileIO writer(fifo_path, "writer"); - FifoFileIO reader(fifo_path, "reader"); + FifoFileIO writer(*fifo.get(), "writer"); + FifoFileIO reader(*fifo.get(), "reader"); llvm::json::Object obj; obj["foo"] = "bar"; @@ -79,7 +79,7 @@ TEST(FifoFilesTest, ReadTimeout) { auto fifo = CreateFifoFile(fifo_path); EXPECT_THAT_EXPECTED(fifo, llvm::Succeeded()); - FifoFileIO reader(fifo_path, "reader"); + FifoFileIO reader(*fifo.get(), "reader"); // No writer, should timeout. auto result = reader.ReadJSON(std::chrono::milliseconds(100)); @@ -91,7 +91,7 @@ TEST(FifoFilesTest, WriteTimeout) { auto fifo = CreateFifoFile(fifo_path); EXPECT_THAT_EXPECTED(fifo, llvm::Succeeded()); - FifoFileIO writer(fifo_path, "writer"); + FifoFileIO writer(*fifo.get(), "writer"); // No reader, should timeout. llvm::json::Object obj; >From eab33d0229b6b06a857d809c75b4ccd0fc2f7738 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Wed, 7 Jan 2026 19:46:28 +0000 Subject: [PATCH 02/16] fix formatting --- .../runInTerminal/TestDAP_runInTerminal.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py index aa2688464faf8..9d16bc6c981de 100644 --- a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py +++ b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py @@ -10,12 +10,14 @@ import subprocess import json + @contextmanager def fifo(*args, **kwargs): if sys.platform == "win32": comm_file = r"\\.\pipe\lldb-dap-run-in-terminal-comm" import ctypes + PIPE_ACCESS_DUPLEX = 0x00000003 PIPE_TYPE_BYTE = 0x00000000 PIPE_READMODE_BYTE = 0x00000000 @@ -27,7 +29,11 @@ def fifo(*args, **kwargs): comm_file, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, - PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, None + PIPE_UNLIMITED_INSTANCES, + 4096, + 4096, + 0, + None, ) else: comm_file = os.path.join(kwargs["directory"], "comm-file") @@ -41,6 +47,7 @@ def fifo(*args, **kwargs): kernel32.DisconnectNamedPipe(pipe) kernel32.CloseHandle(pipe) + @skipIfBuildType(["debug"]) class TestDAP_runInTerminal(lldbdap_testcase.DAPTestCaseBase): SHARED_BUILD_TESTCASE = False @@ -48,6 +55,7 @@ class TestDAP_runInTerminal(lldbdap_testcase.DAPTestCaseBase): def read_pid_message(self, fifo_file, pipe): if sys.platform == "win32": import ctypes + buffer = ctypes.create_string_buffer(4096) bytes_read = ctypes.wintypes.DWORD() kernel32 = ctypes.windll.kernel32 @@ -63,10 +71,13 @@ def send_did_attach_message(fifo_file, pipe=None): message = json.dumps({"kind": "didAttach"}) + "\n" if sys.platform == "win32": import ctypes + kernel32 = ctypes.windll.kernel32 bytes_written = ctypes.wintypes.DWORD() kernel32.ConnectNamedPipe(pipe, None) - kernel32.WriteFile(pipe, message.encode(), len(message), ctypes.byref(bytes_written), None) + kernel32.WriteFile( + pipe, message.encode(), len(message), ctypes.byref(bytes_written), None + ) else: with open(fifo_file, "w") as file: file.write(message) @@ -220,7 +231,10 @@ def test_FakeAttachedRunInTerminalLauncherWithInvalidProgramWindows(self): stderr=subprocess.PIPE, ) - self.assertIn("Failed to launch target process", self.read_error_message(comm_file, pipe)) + self.assertIn( + "Failed to launch target process", + self.read_error_message(comm_file, pipe), + ) def test_FakeAttachedRunInTerminalLauncherWithValidProgram(self): with fifo(directory=self.getBuildDir()) as (comm_file, pipe): >From 91116ff86878a4c729fa67d6ebcc8e173f90026f Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Wed, 7 Jan 2026 19:49:10 +0000 Subject: [PATCH 03/16] fixup! fix formatting --- .../API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py index 9d16bc6c981de..455b6715d8cce 100644 --- a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py +++ b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py @@ -14,10 +14,9 @@ @contextmanager def fifo(*args, **kwargs): if sys.platform == "win32": - comm_file = r"\\.\pipe\lldb-dap-run-in-terminal-comm" - import ctypes + comm_file = r"\\.\pipe\lldb-dap-run-in-terminal-comm" PIPE_ACCESS_DUPLEX = 0x00000003 PIPE_TYPE_BYTE = 0x00000000 PIPE_READMODE_BYTE = 0x00000000 @@ -86,6 +85,7 @@ def send_did_attach_message(fifo_file, pipe=None): def read_error_message(fifo_file, pipe=None): if sys.platform == "win32": import ctypes + buffer = ctypes.create_string_buffer(4096) bytes_read = ctypes.wintypes.DWORD() kernel32 = ctypes.windll.kernel32 >From fa774eb87707c9c414ea86ef676fb832c3feb412 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 8 Jan 2026 16:17:59 +0000 Subject: [PATCH 04/16] address comments --- .../runInTerminal/TestDAP_runInTerminal.py | 62 +++++++++---------- lldb/tools/lldb-dap/FifoFiles.cpp | 17 +++-- lldb/tools/lldb-dap/FifoFiles.h | 16 ++++- .../tools/lldb-dap/Handler/RequestHandler.cpp | 7 ++- lldb/tools/lldb-dap/RunInTerminal.cpp | 8 +-- lldb/tools/lldb-dap/RunInTerminal.h | 2 +- lldb/tools/lldb-dap/tool/lldb-dap.cpp | 47 +++++++++----- 7 files changed, 93 insertions(+), 66 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py index 455b6715d8cce..1c2c53f0beb52 100644 --- a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py +++ b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py @@ -192,34 +192,9 @@ def test_missingArgInRunInTerminalLauncher(self): '"--launch-target" requires "--comm-file" to be specified', proc.stderr ) - @skipIfWindows def test_FakeAttachedRunInTerminalLauncherWithInvalidProgram(self): - comm_file = os.path.join(self.getBuildDir(), "comm-file") - os.mkfifo(comm_file) - - proc = subprocess.Popen( - [ - self.lldbDAPExec, - "--comm-file", - comm_file, - "--launch-target", - "INVALIDPROGRAM", - ], - universal_newlines=True, - stderr=subprocess.PIPE, - ) - - self.read_pid_message(comm_file) - self.send_did_attach_message(comm_file) - self.assertIn("No such file or directory", self.read_error_message(comm_file)) - - _, stderr = proc.communicate() - self.assertIn("No such file or directory", stderr) - - @skipUnlessWindows - def test_FakeAttachedRunInTerminalLauncherWithInvalidProgramWindows(self): with fifo(directory=self.getBuildDir()) as (comm_file, pipe): - subprocess.Popen( + proc = subprocess.Popen( [ self.lldbDAPExec, "--comm-file", @@ -230,11 +205,19 @@ def test_FakeAttachedRunInTerminalLauncherWithInvalidProgramWindows(self): universal_newlines=True, stderr=subprocess.PIPE, ) - - self.assertIn( - "Failed to launch target process", - self.read_error_message(comm_file, pipe), - ) + if sys.platform == "win32": + _, stderr = proc.communicate() + self.assertIn("Failed to launch target process", stderr) + else: + self.read_pid_message(comm_file, pipe) + self.send_did_attach_message(comm_file, pipe) + self.assertIn( + "No such file or directory", + self.read_error_message(comm_file, pipe), + ) + + _, stderr = proc.communicate() + self.assertIn("No such file or directory", stderr) def test_FakeAttachedRunInTerminalLauncherWithValidProgram(self): with fifo(directory=self.getBuildDir()) as (comm_file, pipe): @@ -272,10 +255,21 @@ def test_FakeAttachedRunInTerminalLauncherAndCheckEnvironment(self): stdout, _ = proc.communicate() self.assertIn("FOO=BAR", stdout) - @skipIfWindows def test_NonAttachedRunInTerminalLauncher(self): - comm_file = os.path.join(self.getBuildDir(), "comm-file") - os.mkfifo(comm_file) + with fifo(directory=self.getBuildDir()) as (comm_file, pipe): + proc = subprocess.Popen( + [ + self.lldbDAPExec, + "--comm-file", + comm_file, + "--launch-target", + "echo", + "foo", + ], + universal_newlines=True, + stderr=subprocess.PIPE, + env={**os.environ, "LLDB_DAP_RIT_TIMEOUT_IN_MS": "1000"}, + ) proc = subprocess.Popen( [ diff --git a/lldb/tools/lldb-dap/FifoFiles.cpp b/lldb/tools/lldb-dap/FifoFiles.cpp index 3775e2f6b0143..a5fa8121260eb 100644 --- a/lldb/tools/lldb-dap/FifoFiles.cpp +++ b/lldb/tools/lldb-dap/FifoFiles.cpp @@ -38,7 +38,12 @@ FifoFile::FifoFile(StringRef path, lldb::pipe_t pipe) : m_path(path) { } FifoFile::~FifoFile() { -#if !defined(_WIN32) +#ifdef _WIN32 + if (m_pipe != INVALID_HANDLE_VALUE) { + DisconnectNamedPipe(m_pipe); + CloseHandle(m_pipe); + } +#else unlink(m_path.c_str()); #endif } @@ -108,8 +113,10 @@ Expected<std::shared_ptr<FifoFile>> CreateFifoFile(StringRef path) { #endif } -FifoFileIO::FifoFileIO(FifoFile fifo_file, StringRef other_endpoint_name) - : m_fifo_file(fifo_file), m_other_endpoint_name(other_endpoint_name) {} +FifoFileIO::FifoFileIO(std::shared_ptr<FifoFile> fifo_file, + StringRef other_endpoint_name) + : m_fifo_file(std::move(fifo_file)), + m_other_endpoint_name(other_endpoint_name) {} Expected<json::Value> FifoFileIO::ReadJSON(std::chrono::milliseconds timeout) { // We use a pointer for this future, because otherwise its normal destructor @@ -117,7 +124,7 @@ Expected<json::Value> FifoFileIO::ReadJSON(std::chrono::milliseconds timeout) { std::optional<std::string> line; std::future<void> *future = new std::future<void>(std::async(std::launch::async, [&]() { - std::string buffer = m_fifo_file.ReadLine(); + std::string buffer = m_fifo_file->ReadLine(); if (!buffer.empty()) line = buffer; })); @@ -141,7 +148,7 @@ Error FifoFileIO::SendJSON(const json::Value &json, bool done = false; std::future<void> *future = new std::future<void>(std::async(std::launch::async, [&]() { - m_fifo_file.WriteLine(JSONToString(json)); + m_fifo_file->WriteLine(JSONToString(json)); done = true; })); if (future->wait_for(timeout) == std::future_status::timeout || !done) { diff --git a/lldb/tools/lldb-dap/FifoFiles.h b/lldb/tools/lldb-dap/FifoFiles.h index e21471fcc78c0..9d3dc7314627f 100644 --- a/lldb/tools/lldb-dap/FifoFiles.h +++ b/lldb/tools/lldb-dap/FifoFiles.h @@ -9,7 +9,9 @@ #ifndef LLDB_TOOLS_LLDB_DAP_FIFOFILES_H #define LLDB_TOOLS_LLDB_DAP_FIFOFILES_H +#ifdef _WIN32 #include "lldb/Host/windows/PipeWindows.h" +#endif #include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" @@ -31,6 +33,15 @@ struct FifoFile { std::string ReadLine(); + llvm::StringRef GetPath() { return m_path; } + + /// FifoFile is not copyable. + /// @{ + FifoFile(const FifoFile &rhs) = delete; + void operator=(const FifoFile &rhs) = delete; + /// @} + +protected: std::string m_path; lldb::pipe_t m_pipe; }; @@ -53,7 +64,8 @@ class FifoFileIO { /// \param[in] other_endpoint_name /// A human readable name for the other endpoint that will communicate /// using this file. This is used for error messages. - FifoFileIO(FifoFile fifo_file, llvm::StringRef other_endpoint_name); + FifoFileIO(std::shared_ptr<FifoFile> fifo_file, + llvm::StringRef other_endpoint_name); /// Read the next JSON object from the underlying input fifo file. /// @@ -84,7 +96,7 @@ class FifoFileIO { std::chrono::milliseconds timeout = std::chrono::milliseconds(20000)); private: - FifoFile m_fifo_file; + std::shared_ptr<FifoFile> m_fifo_file; std::string m_other_endpoint_name; }; diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index a2051d0d3f5ac..48f9f287819b3 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -103,7 +103,7 @@ RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) { CreateRunInTerminalCommFile(); if (!comm_file_or_err) return comm_file_or_err.takeError(); - FifoFile &comm_file = *comm_file_or_err.get(); + std::shared_ptr<FifoFile> comm_file = *comm_file_or_err; RunInTerminalDebugAdapterCommChannel comm_channel(comm_file); @@ -114,11 +114,12 @@ RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) { llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest( arguments.configuration.program, arguments.args, arguments.env, - arguments.cwd, comm_file.m_path, debugger_pid, arguments.stdio, + arguments.cwd, comm_file->GetPath(), debugger_pid, arguments.stdio, arguments.console == protocol::eConsoleExternalTerminal); dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal", std::move(reverse_request)); - comm_file.Connect(); // we need to wait for the client to connect to the pipe. + comm_file + ->Connect(); // we need to wait for the client to connect to the pipe. if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid()) attach_info.SetProcessID(*pid); else diff --git a/lldb/tools/lldb-dap/RunInTerminal.cpp b/lldb/tools/lldb-dap/RunInTerminal.cpp index 7d20ca1c13987..490a4f4361ee8 100644 --- a/lldb/tools/lldb-dap/RunInTerminal.cpp +++ b/lldb/tools/lldb-dap/RunInTerminal.cpp @@ -99,7 +99,7 @@ static Error ToError(const RunInTerminalMessage &message) { RunInTerminalLauncherCommChannel::RunInTerminalLauncherCommChannel( StringRef comm_file) - : m_io(FifoFileIO(FifoFile(comm_file), "debug adapter")) {} + : m_io(std::make_shared<FifoFile>(comm_file), "debug adapter") {} Error RunInTerminalLauncherCommChannel::WaitUntilDebugAdapterAttaches( std::chrono::milliseconds timeout) { @@ -129,11 +129,11 @@ void RunInTerminalLauncherCommChannel::NotifyError(StringRef error) { RunInTerminalDebugAdapterCommChannel::RunInTerminalDebugAdapterCommChannel( StringRef comm_file) - : m_io(comm_file, "runInTerminal launcher") {} + : m_io(std::make_shared<FifoFile>(comm_file), "runInTerminal launcher") {} RunInTerminalDebugAdapterCommChannel::RunInTerminalDebugAdapterCommChannel( - FifoFile comm_file) - : m_io(comm_file, "runInTerminal launcher") {} + std::shared_ptr<FifoFile> comm_file) + : m_io(std::move(comm_file), "runInTerminal launcher") {} // Can't use \a std::future<llvm::Error> because it doesn't compile on Windows std::future<lldb::SBError> diff --git a/lldb/tools/lldb-dap/RunInTerminal.h b/lldb/tools/lldb-dap/RunInTerminal.h index ff727603709d3..1c6d92bbbc774 100644 --- a/lldb/tools/lldb-dap/RunInTerminal.h +++ b/lldb/tools/lldb-dap/RunInTerminal.h @@ -101,7 +101,7 @@ class RunInTerminalLauncherCommChannel { class RunInTerminalDebugAdapterCommChannel { public: RunInTerminalDebugAdapterCommChannel(llvm::StringRef comm_file); - RunInTerminalDebugAdapterCommChannel(FifoFile comm_file); + RunInTerminalDebugAdapterCommChannel(std::shared_ptr<FifoFile> comm_file); /// Notify the runInTerminal launcher that it was attached. /// diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index e47fcfb610c6e..d45809f99abed 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -322,22 +322,13 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE); HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); - llvm::scope_exit close_handles([&] { - if (stdin_handle) - ::CloseHandle(stdin_handle); - if (stdout_handle) - ::CloseHandle(stdout_handle); - if (stderr_handle) - ::CloseHandle(stderr_handle); - }); - auto attributelist_cleanup_or_err = - lldb_private::SetupProcThreadAttributeList(startupinfoex); - if (!attributelist_cleanup_or_err) { + auto attributelist_or_err = + lldb_private::ProcThreadAttributeList::Create(startupinfoex); + if (!attributelist_or_err) { return notifyError(comm_channel, "Could not open inherited handles", - attributelist_cleanup_or_err.getError()); + attributelist_or_err.getError()); } - auto attributelist_cleanup = std::move(*attributelist_cleanup_or_err); if (!stdio.empty()) { llvm::SmallVector<llvm::StringRef, 3> files; @@ -351,6 +342,15 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, files[1], STDOUT_FILENO); stderr_handle = lldb_private::ProcessLauncherWindows::GetStdioHandle( files[2], STDERR_FILENO); + // Only close the handles we created + llvm::scope_exit close_handles([&] { + if (stdin_handle) + CloseHandle(stdin_handle); + if (stdout_handle) + CloseHandle(stdout_handle); + if (stderr_handle) + CloseHandle(stderr_handle); + }); } auto inherited_handles_or_err = lldb_private::ProcessLauncherWindows::GetInheritedHandles( @@ -377,22 +377,35 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, if (!result) return notifyError(comm_channel, "Failed to launch target process"); + auto cleanupAndReturn = [&](llvm::Error err) -> llvm::Error { + if (pi.hProcess) + TerminateProcess(pi.hProcess, 1); + if (pi.hThread) + CloseHandle(pi.hThread); + if (pi.hProcess) + CloseHandle(pi.hProcess); + return err; + }; + // Notify the pid of the process to debug to the debugger. It will attach to // the newly created process. if (llvm::Error err = comm_channel.NotifyPid(pi.dwProcessId)) - return err; + return cleanupAndReturn(std::move(err)); if (llvm::Error err = comm_channel.WaitUntilDebugAdapterAttaches( std::chrono::milliseconds(timeout_in_ms))) - return err; + return cleanupAndReturn(std::move(err)); // The debugger attached to the process. We can resume it. if (!ResumeThread(pi.hThread)) - return notifyError(comm_channel, "Failed to resume the target process"); + return cleanupAndReturn( + notifyError(comm_channel, "Failed to resume the target process")); // Wait for child to complete to match POSIX behavior. WaitForSingleObject(pi.hProcess, INFINITE); - ExitProcess(0); // TODO: Should we return the exit code of the process? + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return llvm::Error::success(); #else if (!stdio.empty()) { constexpr size_t num_of_stdio = 3; >From bf8a1ee1b70c86e8aca1716b8330d71a8633cd8c Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 8 Jan 2026 16:36:08 +0000 Subject: [PATCH 05/16] remove declaration --- .../lldb/Host/windows/ProcessLauncherWindows.h | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h b/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h index 06085bb36c36c..876cff12468ef 100644 --- a/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h +++ b/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h @@ -152,21 +152,6 @@ llvm::ErrorOr<std::wstring> GetFlattenedWindowsCommandStringW(const Args &args); llvm::ErrorOr<std::wstring> GetFlattenedWindowsCommandStringW(llvm::ArrayRef<const char *> args); - -/// Allocate and initialize a PROC_THREAD_ATTRIBUTE_LIST structure -/// that can be used with CreateProcess to specify extended process creation -/// attributes (such as inherited handles). -/// -/// \param[in] startupinfoex The STARTUPINFOEXW structure whose lpAttributeList -/// will -/// be initialized. -/// -/// \return On success, returns a scope_exit cleanup object that will -/// automatically -/// delete and free the attribute list when it goes out of scope. -/// On failure, returns the corresponding Windows error code. -llvm::ErrorOr<llvm::scope_exit<std::function<void()>>> -SetupProcThreadAttributeList(STARTUPINFOEXW &startupinfoex); } #endif >From d6dafd6b358c6ebf51e3750f560f00c0dd31ca03 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 8 Jan 2026 16:43:26 +0000 Subject: [PATCH 06/16] fix build issue linux --- lldb/tools/lldb-dap/FifoFiles.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lldb/tools/lldb-dap/FifoFiles.h b/lldb/tools/lldb-dap/FifoFiles.h index 9d3dc7314627f..87ac2c66e6382 100644 --- a/lldb/tools/lldb-dap/FifoFiles.h +++ b/lldb/tools/lldb-dap/FifoFiles.h @@ -9,9 +9,7 @@ #ifndef LLDB_TOOLS_LLDB_DAP_FIFOFILES_H #define LLDB_TOOLS_LLDB_DAP_FIFOFILES_H -#ifdef _WIN32 -#include "lldb/Host/windows/PipeWindows.h" -#endif +#include "lldb/Host/Pipe.h" #include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" >From b20ce6b99ff7eb8e997c33c80bbbb2ee1a3f722d Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 8 Jan 2026 17:05:47 +0000 Subject: [PATCH 07/16] fix unit test linux --- lldb/unittests/DAP/FifoFilesTest.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lldb/unittests/DAP/FifoFilesTest.cpp b/lldb/unittests/DAP/FifoFilesTest.cpp index bd8aa64ef1ecd..4931cfaa30d42 100644 --- a/lldb/unittests/DAP/FifoFilesTest.cpp +++ b/lldb/unittests/DAP/FifoFilesTest.cpp @@ -45,8 +45,8 @@ TEST(FifoFilesTest, SendAndReceiveJSON) { auto fifo = CreateFifoFile(fifo_path); EXPECT_THAT_EXPECTED(fifo, llvm::Succeeded()); - FifoFileIO writer(*fifo.get(), "writer"); - FifoFileIO reader(*fifo.get(), "reader"); + FifoFileIO writer(std::move(*fifo), "writer"); + FifoFileIO reader(std::move(*fifo), "reader"); llvm::json::Object obj; obj["foo"] = "bar"; @@ -79,7 +79,7 @@ TEST(FifoFilesTest, ReadTimeout) { auto fifo = CreateFifoFile(fifo_path); EXPECT_THAT_EXPECTED(fifo, llvm::Succeeded()); - FifoFileIO reader(*fifo.get(), "reader"); + FifoFileIO reader(std::move(*fifo), "reader"); // No writer, should timeout. auto result = reader.ReadJSON(std::chrono::milliseconds(100)); @@ -91,7 +91,7 @@ TEST(FifoFilesTest, WriteTimeout) { auto fifo = CreateFifoFile(fifo_path); EXPECT_THAT_EXPECTED(fifo, llvm::Succeeded()); - FifoFileIO writer(*fifo.get(), "writer"); + FifoFileIO writer(std::move(*fifo), "writer"); // No reader, should timeout. llvm::json::Object obj; >From cd4c672f3038719391629a60d2064f2a8599a777 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 8 Jan 2026 18:06:12 +0000 Subject: [PATCH 08/16] remove move --- lldb/unittests/DAP/FifoFilesTest.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lldb/unittests/DAP/FifoFilesTest.cpp b/lldb/unittests/DAP/FifoFilesTest.cpp index 4931cfaa30d42..e59fc54cc9543 100644 --- a/lldb/unittests/DAP/FifoFilesTest.cpp +++ b/lldb/unittests/DAP/FifoFilesTest.cpp @@ -45,8 +45,8 @@ TEST(FifoFilesTest, SendAndReceiveJSON) { auto fifo = CreateFifoFile(fifo_path); EXPECT_THAT_EXPECTED(fifo, llvm::Succeeded()); - FifoFileIO writer(std::move(*fifo), "writer"); - FifoFileIO reader(std::move(*fifo), "reader"); + FifoFileIO writer(*fifo, "writer"); + FifoFileIO reader(*fifo, "reader"); llvm::json::Object obj; obj["foo"] = "bar"; @@ -79,7 +79,7 @@ TEST(FifoFilesTest, ReadTimeout) { auto fifo = CreateFifoFile(fifo_path); EXPECT_THAT_EXPECTED(fifo, llvm::Succeeded()); - FifoFileIO reader(std::move(*fifo), "reader"); + FifoFileIO reader(*fifo, "reader"); // No writer, should timeout. auto result = reader.ReadJSON(std::chrono::milliseconds(100)); @@ -91,7 +91,7 @@ TEST(FifoFilesTest, WriteTimeout) { auto fifo = CreateFifoFile(fifo_path); EXPECT_THAT_EXPECTED(fifo, llvm::Succeeded()); - FifoFileIO writer(std::move(*fifo), "writer"); + FifoFileIO writer(*fifo, "writer"); // No reader, should timeout. llvm::json::Object obj; >From dd429026517d7693e9913f73fa000b577ff3960e Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Mon, 12 Jan 2026 18:30:47 +0000 Subject: [PATCH 09/16] fix early CloseHandle call --- lldb/tools/lldb-dap/tool/lldb-dap.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index d45809f99abed..aa299ce55df09 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -342,16 +342,20 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, files[1], STDOUT_FILENO); stderr_handle = lldb_private::ProcessLauncherWindows::GetStdioHandle( files[2], STDERR_FILENO); - // Only close the handles we created - llvm::scope_exit close_handles([&] { - if (stdin_handle) - CloseHandle(stdin_handle); - if (stdout_handle) - CloseHandle(stdout_handle); - if (stderr_handle) - CloseHandle(stderr_handle); - }); } + + llvm::scope_exit close_handles([&] { + // Only close the handles we created + if (stdio.empty()) + return; + if (stdin_handle) + CloseHandle(stdin_handle); + if (stdout_handle) + CloseHandle(stdout_handle); + if (stderr_handle) + CloseHandle(stderr_handle); + }); + auto inherited_handles_or_err = lldb_private::ProcessLauncherWindows::GetInheritedHandles( startupinfoex, /*launch_info*=*/nullptr, stdout_handle, stderr_handle, >From 27bba0e3d1ec3d613d48f08d34799c97af888f19 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Tue, 27 Jan 2026 11:42:32 +0000 Subject: [PATCH 10/16] switch to a ; delimiter on Windows --- lldb/tools/lldb-dap/JSONUtils.cpp | 4 ++++ lldb/tools/lldb-dap/tool/Options.td | 3 ++- lldb/tools/lldb-dap/tool/lldb-dap.cpp | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index d75b4e23d0e46..54f0bb75dbfb9 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -386,7 +386,11 @@ llvm::json::Object CreateRunInTerminalReverseRequest( std::stringstream ss; std::string_view delimiter; for (const std::optional<protocol::String> &file : stdio) { + #ifdef _WIN32 + ss << std::exchange(delimiter, ";"); + #else ss << std::exchange(delimiter, ":"); + #endif if (file) ss << file->str(); } diff --git a/lldb/tools/lldb-dap/tool/Options.td b/lldb/tools/lldb-dap/tool/Options.td index f8adef7a19672..048021cd4457f 100644 --- a/lldb/tools/lldb-dap/tool/Options.td +++ b/lldb/tools/lldb-dap/tool/Options.td @@ -54,7 +54,8 @@ def debugger_pid: S<"debugger-pid">, def stdio: S<"stdio">, MetaVarName<"<stdin:stdout:stderr:...>">, HelpText<"An array of file paths for redirecting the program's standard IO " - "streams. A colon-separated list of entries. Empty value means no " + "streams. On Windows, a semicolon-separated list of entries, a " + "colon-separated list on other platforms. Empty value means no " "redirection.">; def repl_mode diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index aa299ce55df09..72505e68c4073 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -332,7 +332,7 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, if (!stdio.empty()) { llvm::SmallVector<llvm::StringRef, 3> files; - stdio.split(files, ':'); + stdio.split(files, ';'); while (files.size() < 3) files.push_back(files.back()); >From 3570be64809bbf5856cd9c05f799cf605ec841a4 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Tue, 27 Jan 2026 12:53:08 +0000 Subject: [PATCH 11/16] fix formatting --- lldb/tools/lldb-dap/JSONUtils.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 54f0bb75dbfb9..ada7813c769c5 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -386,11 +386,11 @@ llvm::json::Object CreateRunInTerminalReverseRequest( std::stringstream ss; std::string_view delimiter; for (const std::optional<protocol::String> &file : stdio) { - #ifdef _WIN32 +#ifdef _WIN32 ss << std::exchange(delimiter, ";"); - #else +#else ss << std::exchange(delimiter, ":"); - #endif +#endif if (file) ss << file->str(); } >From be3777b1e36b89f88675fc5060985d4ab21df934 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Tue, 17 Feb 2026 16:09:59 +0000 Subject: [PATCH 12/16] enable more tests --- .../launch/TestDAP_launch_stdio_redirection_and_console.py | 1 - lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_stdio_redirection_and_console.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_stdio_redirection_and_console.py index bec76fb4ef5e1..5b836e99a48fb 100644 --- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_stdio_redirection_and_console.py +++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_stdio_redirection_and_console.py @@ -19,7 +19,6 @@ class TestDAP_launch_stdio_redirection_and_console(lldbdap_testcase.DAPTestCaseB """ @skipIfAsan - @skipIfWindows # https://github.com/llvm/llvm-project/issues/62336 @skipIf(oslist=["linux"], archs=no_match(["x86_64"])) @skipIfBuildType(["debug"]) def test(self): diff --git a/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py b/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py index e98216b2144f2..068fc5146c73b 100644 --- a/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py +++ b/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py @@ -12,7 +12,6 @@ @skipIfBuildType(["debug"]) class TestDAP_restart_console(lldbdap_testcase.DAPTestCaseBase): @skipIfAsan - @skipIfWindows @skipIf(oslist=["linux"], archs=["arm$"]) # Always times out on buildbot def test_basic_functionality(self): """ @@ -61,7 +60,6 @@ def test_basic_functionality(self): self.continue_to_exit() @skipIfAsan - @skipIfWindows @skipIf(oslist=["linux"], archs=["arm$"]) # Always times out on buildbot def test_stopOnEntry(self): """ >From 4a787b030ef546b0860462011467cfbdba1557ec Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Tue, 24 Feb 2026 18:41:27 +0000 Subject: [PATCH 13/16] exit with debuggee's return code --- .../restart/TestDAP_restart_console.py | 2 ++ .../runInTerminal/TestDAP_runInTerminal.py | 16 +--------- lldb/tools/lldb-dap/tool/lldb-dap.cpp | 32 ++++++++++++------- tmp.txt | 1 + 4 files changed, 24 insertions(+), 27 deletions(-) create mode 100644 tmp.txt diff --git a/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py b/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py index 068fc5146c73b..c80a3cdc10d95 100644 --- a/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py +++ b/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py @@ -12,6 +12,7 @@ @skipIfBuildType(["debug"]) class TestDAP_restart_console(lldbdap_testcase.DAPTestCaseBase): @skipIfAsan + @expectedFailureWindows @skipIf(oslist=["linux"], archs=["arm$"]) # Always times out on buildbot def test_basic_functionality(self): """ @@ -60,6 +61,7 @@ def test_basic_functionality(self): self.continue_to_exit() @skipIfAsan + @expectedFailureWindows @skipIf(oslist=["linux"], archs=["arm$"]) # Always times out on buildbot def test_stopOnEntry(self): """ diff --git a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py index 1c2c53f0beb52..b81f3f7704ac0 100644 --- a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py +++ b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py @@ -271,21 +271,7 @@ def test_NonAttachedRunInTerminalLauncher(self): env={**os.environ, "LLDB_DAP_RIT_TIMEOUT_IN_MS": "1000"}, ) - proc = subprocess.Popen( - [ - self.lldbDAPExec, - "--comm-file", - comm_file, - "--launch-target", - "echo", - "foo", - ], - universal_newlines=True, - stderr=subprocess.PIPE, - env={**os.environ, "LLDB_DAP_RIT_TIMEOUT_IN_MS": "1000"}, - ) - - self.read_pid_message(comm_file) + self.read_pid_message(comm_file, pipe) _, stderr = proc.communicate() self.assertIn("Timed out trying to get messages from the debug adapter", stderr) diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index 72505e68c4073..22be9b5b27183 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -285,12 +285,11 @@ SetupIORedirection(const llvm::SmallVectorImpl<llvm::StringRef> &files) { // // In case of errors launching the target, a suitable error message will be // emitted to the debug adapter. -static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, - llvm::StringRef comm_file, - lldb::pid_t debugger_pid, - llvm::StringRef stdio, - char *argv[], - int argc) { +static llvm::Expected<int> LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, + llvm::StringRef comm_file, + lldb::pid_t debugger_pid, + llvm::StringRef stdio, + char *argv[], int argc) { // This env var should be used only for tests. const char *timeout_env_var = getenv("LLDB_DAP_RIT_TIMEOUT_IN_MS"); int timeout_in_ms = @@ -381,7 +380,7 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, if (!result) return notifyError(comm_channel, "Failed to launch target process"); - auto cleanupAndReturn = [&](llvm::Error err) -> llvm::Error { + auto cleanupAndReturn = [&](llvm::Error err) -> llvm::Expected<int> { if (pi.hProcess) TerminateProcess(pi.hProcess, 1); if (pi.hThread) @@ -407,9 +406,14 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, // Wait for child to complete to match POSIX behavior. WaitForSingleObject(pi.hProcess, INFINITE); + DWORD code = 0; + if (!::GetExitCodeProcess(pi.hProcess, &code)) + return cleanupAndReturn(notifyError( + comm_channel, "Failed to get the target's process return code")); + CloseHandle(pi.hThread); CloseHandle(pi.hProcess); - return llvm::Error::success(); + return code; #else if (!stdio.empty()) { constexpr size_t num_of_stdio = 3; @@ -732,12 +736,16 @@ int main(int argc, char *argv[]) { } } llvm::StringRef stdio = input_args.getLastArgValue(OPT_stdio); - if (llvm::Error err = - LaunchRunInTerminalTarget(*target_arg, comm_file->getValue(), pid, - stdio, argv + target_args_pos, argc - target_args_pos)) { - llvm::errs() << llvm::toString(std::move(err)) << '\n'; + auto return_code_or_err = LaunchRunInTerminalTarget( + *target_arg, comm_file->getValue(), pid, stdio, + argv + target_args_pos, argc - target_args_pos); + if (!return_code_or_err) { + llvm::errs() << llvm::toString( + std::move(return_code_or_err.takeError())) + << '\n'; return EXIT_FAILURE; } + return *return_code_or_err; } else { llvm::errs() << "\"--launch-target\" requires \"--comm-file\" to be " "specified\n"; diff --git a/tmp.txt b/tmp.txt new file mode 100644 index 0000000000000..a862a628171ba --- /dev/null +++ b/tmp.txt @@ -0,0 +1 @@ +ERR >From a7d067d6e4f3d71e5e681ef8cc90162aabef66f4 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Tue, 24 Feb 2026 18:44:58 +0000 Subject: [PATCH 14/16] fixup! exit with debuggee's return code --- lldb/tools/lldb-dap/tool/lldb-dap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index 22be9b5b27183..509f5de5bfcff 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -70,8 +70,8 @@ #undef GetObject #include <io.h> typedef int socklen_t; -#include "lldb/Host/windows/PythonPathSetup/PythonPathSetup.h" #include "lldb/Host/windows/ProcessLauncherWindows.h" +#include "lldb/Host/windows/PythonPathSetup/PythonPathSetup.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Program.h" #else >From eb1d6ac4c850f565b4dd7323beab1d4f1a3b0123 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Tue, 24 Feb 2026 21:21:30 +0000 Subject: [PATCH 15/16] fixup! exit with debuggee's return code --- tmp.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 tmp.txt diff --git a/tmp.txt b/tmp.txt deleted file mode 100644 index a862a628171ba..0000000000000 --- a/tmp.txt +++ /dev/null @@ -1 +0,0 @@ -ERR >From 8a311d8cb6b35894cfae88f54fd087c86e415da8 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Fri, 6 Mar 2026 18:25:42 +0000 Subject: [PATCH 16/16] refactor GetFlattenedWindowsCommandStringW --- lldb/source/Host/windows/ProcessLauncherWindows.cpp | 4 +--- .../lldb-dap/runInTerminal/TestDAP_runInTerminal.py | 9 ++++++--- lldb/tools/lldb-dap/tool/lldb-dap.cpp | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp index fb091eb75d9b8..86acc5e2d3d5e 100644 --- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp +++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp @@ -81,9 +81,7 @@ GetFlattenedWindowsCommandStringW(llvm::ArrayRef<const char *> args) { if (args.empty()) return L""; - std::vector<llvm::StringRef> args_ref; - for (int i = 0; args[i] != nullptr; ++i) - args_ref.push_back(args[i]); + std::vector<llvm::StringRef> args_ref(args.begin(), args.end()); return llvm::sys::flattenWindowsCommandLine(args_ref); } diff --git a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py index b81f3f7704ac0..c4e405e1c3abb 100644 --- a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py +++ b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py @@ -237,7 +237,8 @@ def test_FakeAttachedRunInTerminalLauncherWithValidProgram(self): self.read_pid_message(comm_file, pipe) self.send_did_attach_message(comm_file, pipe) - stdout, _ = proc.communicate() + stdout, _ = proc.communicate() + self.assertIn("foo", stdout) def test_FakeAttachedRunInTerminalLauncherAndCheckEnvironment(self): @@ -252,7 +253,8 @@ def test_FakeAttachedRunInTerminalLauncherAndCheckEnvironment(self): self.read_pid_message(comm_file, pipe) self.send_did_attach_message(comm_file, pipe) - stdout, _ = proc.communicate() + stdout, _ = proc.communicate() + self.assertIn("FOO=BAR", stdout) def test_NonAttachedRunInTerminalLauncher(self): @@ -273,7 +275,8 @@ def test_NonAttachedRunInTerminalLauncher(self): self.read_pid_message(comm_file, pipe) - _, stderr = proc.communicate() + _, stderr = proc.communicate() + self.assertIn("Timed out trying to get messages from the debug adapter", stderr) def test_client_missing_runInTerminal_feature(self): diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index 509f5de5bfcff..c92a82215b312 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -308,7 +308,7 @@ static llvm::Expected<int> LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, #ifdef _WIN32 RunInTerminalLauncherCommChannel comm_channel(comm_file); - llvm::ArrayRef<const char *> args_arr = llvm::ArrayRef(argv + 1, argc); + llvm::ArrayRef<const char *> args_arr = llvm::ArrayRef(argv, argc); auto wcommandLineOrErr = lldb_private::GetFlattenedWindowsCommandStringW(args_arr); if (!wcommandLineOrErr) _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
