https://github.com/charles-zablit created https://github.com/llvm/llvm-project/pull/174635
None >From f1065389eeb8e8d5fc2a3fab7c9277c5deac934e Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Tue, 6 Jan 2026 19:22:35 +0000 Subject: [PATCH] [lldb-dap][windows] add integratedTerminal support --- lldb/tools/lldb-dap/FifoFiles.cpp | 86 ++++++++-- lldb/tools/lldb-dap/FifoFiles.h | 13 +- .../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 | 149 +++++++++++++++++- lldb/unittests/DAP/FifoFilesTest.cpp | 8 +- 7 files changed, 264 insertions(+), 22 deletions(-) diff --git a/lldb/tools/lldb-dap/FifoFiles.cpp b/lldb/tools/lldb-dap/FifoFiles.cpp index 1f1bba80bd3b1..30acb00e4e1da 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,11 @@ using namespace llvm; namespace lldb_dap { -FifoFile::FifoFile(StringRef path) : m_path(path) {} +FifoFile::FifoFile(StringRef path) : m_path(path) { + m_pipe = CreateFileA(m_path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); +} +FifoFile::FifoFile(StringRef path, HANDLE pipe) : m_path(path), m_pipe(pipe) {} FifoFile::~FifoFile() { #if !defined(_WIN32) @@ -32,9 +40,72 @@ FifoFile::~FifoFile() { #endif } +void FifoFile::WriteLine(std::string line) { +#if defined(_WIN32) + if (m_pipe == INVALID_HANDLE_VALUE) + return; + DWORD written; + line += "\n"; + WriteFile(m_pipe, line.c_str(), static_cast<DWORD>(line.size()), &written, + NULL); + FlushFileBuffers(m_pipe); // Ensure data is sent +#else + std::ofstream writer(m_path, std::ofstream::out); + writer << line << std::endl; +#endif +} + +void FifoFile::Connect() { ConnectNamedPipe(m_pipe, NULL); } + +std::string FifoFile::ReadLine() { +#ifdef _WIN32 + std::string buffer; + if (m_pipe == INVALID_HANDLE_VALUE) + return 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'; + + // TODO: Verify that we are not discarding the text after the new line here + // (this looks like it). what happens if the read_buffer is too small? + for (DWORD i = 0; i < bytes_read; ++i) { + if (read_buffer[i] == '\n') + break; + if (read_buffer[i] != '\r') + buffer += read_buffer[i]; + } + } + + 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\\") && + "FiFo 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 +114,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 +123,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 +147,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..356877317bb0e 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" @@ -21,10 +22,18 @@ namespace lldb_dap { /// The file is destroyed when the destructor is invoked. struct FifoFile { FifoFile(llvm::StringRef path); + FifoFile(llvm::StringRef path, HANDLE pipe); ~FifoFile(); + void Connect(); + + void WriteLine(std::string line); + + std::string ReadLine(); + std::string m_path; + HANDLE m_pipe; }; /// Create a fifo file in the filesystem. @@ -45,7 +54,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 +85,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 c60c86219f67d..b0a4e52cbeedb 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -99,7 +99,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) @@ -112,7 +112,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..3100f261b891f 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) { @@ -115,6 +117,10 @@ Error RunInTerminalLauncherCommChannel::NotifyPid() { return m_io.SendJSON(RunInTerminalMessagePid(getpid()).ToJSON()); } +Error RunInTerminalLauncherCommChannel::NotifyPid(int pid) { + return m_io.SendJSON(RunInTerminalMessagePid(pid).ToJSON()); +} + void RunInTerminalLauncherCommChannel::NotifyError(StringRef error) { if (Error err = m_io.SendJSON(RunInTerminalMessageError(error).ToJSON(), std::chrono::seconds(2))) @@ -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() { @@ -139,7 +149,7 @@ RunInTerminalDebugAdapterCommChannel::NotifyDidAttach() { Expected<lldb::pid_t> RunInTerminalDebugAdapterCommChannel::GetLauncherPid() { if (Expected<RunInTerminalMessageUP> message = - GetNextMessage(m_io, std::chrono::seconds(20))) { + GetNextMessage(m_io, std::chrono::seconds(2000))) { if (message.get()->kind == eRunInTerminalMessageKindPID) return message.get()->GetAsPidMessage()->pid; return ToError(*message.get()); @@ -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 e3b9d57e7d3a1..ef271ce6979fe 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -271,9 +271,154 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, lldb::pid_t debugger_pid, llvm::StringRef stdio, char *argv[]) { + 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 defined(_WIN32) - return llvm::createStringError( - "runInTerminal is only supported on POSIX systems"); + lldb_private::FileSystem::Initialize(); + + RunInTerminalLauncherCommChannel comm_channel(comm_file); + + std::string cmdline; + for (int i = 0; argv[i] != nullptr; ++i) { + if (i > 0) + cmdline += " "; + + std::string arg = argv[i]; + if (arg.find(' ') != std::string::npos) { + cmdline += "\"" + arg + "\""; + } else { + cmdline += arg; + } + } + + STARTUPINFOA startup_info = {}; + startup_info.cb = sizeof(startup_info); + + HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE); + HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); + bool handles_inherited = false; + + if (!stdio.empty()) { + llvm::SmallVector<llvm::StringRef, 3> files; + stdio.split(files, ':'); + while (files.size() < 3) + files.push_back(files.back()); + + SECURITY_ATTRIBUTES sa = {}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + + // STDIN + if (!files[0].empty()) { + stdin_handle = CreateFileA(files[0].str().c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (stdin_handle == INVALID_HANDLE_VALUE) { + std::string error = "Failed to open stdin file: " + files[0].str(); + comm_channel.NotifyError(error); + return llvm::createStringError( + std::error_code(GetLastError(), std::system_category()), + std::move(error)); + } + } + + // STDOUT + if (!files[1].empty()) { + stdout_handle = CreateFileA(files[1].str().c_str(), GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (stdout_handle == INVALID_HANDLE_VALUE) { + if (stdin_handle != GetStdHandle(STD_INPUT_HANDLE)) + CloseHandle(stdin_handle); + std::string error = "Failed to open stdout file: " + files[1].str(); + comm_channel.NotifyError(error); + return llvm::createStringError( + std::error_code(GetLastError(), std::system_category()), + std::move(error)); + } + } + + // STDERR + if (!files[2].empty()) { + if (files[2] == files[1]) { + if (!DuplicateHandle(GetCurrentProcess(), stdout_handle, + GetCurrentProcess(), &stderr_handle, 0, TRUE, + DUPLICATE_SAME_ACCESS)) { + if (stdin_handle != GetStdHandle(STD_INPUT_HANDLE)) + CloseHandle(stdin_handle); + if (stdout_handle != GetStdHandle(STD_OUTPUT_HANDLE)) + CloseHandle(stdout_handle); + std::string error = "Failed to duplicate stdout handle for stderr"; + comm_channel.NotifyError(error); + return llvm::createStringError( + std::error_code(GetLastError(), std::system_category()), + std::move(error)); + } + } else { + stderr_handle = CreateFileA(files[2].str().c_str(), GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (stderr_handle == INVALID_HANDLE_VALUE) { + if (stdin_handle != GetStdHandle(STD_INPUT_HANDLE)) + CloseHandle(stdin_handle); + if (stdout_handle != GetStdHandle(STD_OUTPUT_HANDLE)) + CloseHandle(stdout_handle); + std::string error = "Failed to open stderr file: " + files[2].str(); + comm_channel.NotifyError(error); + return llvm::createStringError( + std::error_code(GetLastError(), std::system_category()), + std::move(error)); + } + } + } + + startup_info.dwFlags |= STARTF_USESTDHANDLES; + startup_info.hStdInput = stdin_handle; + startup_info.hStdOutput = stdout_handle; + startup_info.hStdError = stderr_handle; + handles_inherited = true; + } + + PROCESS_INFORMATION process_info = {}; + + // Start the process in a suspended state, while we attach the debugger. + BOOL success = CreateProcessA(NULL, const_cast<char *>(cmdline.c_str()), NULL, + NULL, handles_inherited, CREATE_SUSPENDED, NULL, + NULL, &startup_info, &process_info); + + if (stdin_handle != GetStdHandle(STD_INPUT_HANDLE)) + CloseHandle(stdin_handle); + if (stdout_handle != GetStdHandle(STD_OUTPUT_HANDLE)) + CloseHandle(stdout_handle); + if (stderr_handle != GetStdHandle(STD_ERROR_HANDLE)) + CloseHandle(stderr_handle); + + if (!success) { + DWORD error_code = GetLastError(); + std::string error = "Failed to launch target process"; + comm_channel.NotifyError(error); + return llvm::createStringError( + std::error_code(error_code, std::system_category()), std::move(error)); + } + + // 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(process_info.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. + ResumeThread(process_info.hThread); + + // Wait for child to complete to match POSIX behavior. + WaitForSingleObject(process_info.hProcess, INFINITE); + ExitProcess(0); // TODO: Should we return the exit code of the process? #else // On Linux with the Yama security module enabled, a process can only attach 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; _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
