https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/201638
>From 488a6ce990aa489d73306795d3e40112a499bc9e Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 4 Jun 2026 18:09:30 +0100 Subject: [PATCH 1/4] [lldb][Windows] add stdin support to lldb-server --- .../lldb/Host/common/NativeProcessProtocol.h | 6 ++++- .../Host/windows/ConnectionConPTYWindows.cpp | 27 ++++++++++++++++++- .../Windows/Common/NativeProcessWindows.cpp | 15 +++++++++++ .../Windows/Common/NativeProcessWindows.h | 7 +++++ .../Process/Windows/Common/ProcessWindows.cpp | 1 + .../GDBRemoteCommunicationServerLLGS.cpp | 9 ++++++- .../Process/gdb-remote/ProcessGDBRemote.cpp | 16 ++++++++++- 7 files changed, 77 insertions(+), 4 deletions(-) diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h index 4e4804623b1d8..435185a38f3f9 100644 --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -247,8 +247,12 @@ class NativeProcessProtocol { // Access to inferior stdio virtual int GetTerminalFileDescriptor() { return m_terminal_fd; } - // Stop id interface + /// Write up to \p len bytes from \p buf to the inferior's stdin. + virtual size_t WriteStdin(const void *buf, size_t len, Status &error) { + return 0; + } + // Stop id interface uint32_t GetStopID() const; // Callbacks for low-level process state changes diff --git a/lldb/source/Host/windows/ConnectionConPTYWindows.cpp b/lldb/source/Host/windows/ConnectionConPTYWindows.cpp index e9d026fc7dc9f..f7e90421653e3 100644 --- a/lldb/source/Host/windows/ConnectionConPTYWindows.cpp +++ b/lldb/source/Host/windows/ConnectionConPTYWindows.cpp @@ -61,5 +61,30 @@ size_t ConnectionConPTY::Read(void *dst, size_t dst_len, size_t ConnectionConPTY::Write(const void *src, size_t src_len, lldb::ConnectionStatus &status, Status *error_ptr) { - llvm_unreachable("not implemented"); + if (!m_pty || !m_pty->IsConnected()) { + status = eConnectionStatusNoConnection; + if (error_ptr) + *error_ptr = Status::FromErrorString("ConPTY not connected"); + return 0; + } + HANDLE stdin_handle = m_pty->GetSTDINHandle(); + if (stdin_handle == INVALID_HANDLE_VALUE || stdin_handle == nullptr) { + status = eConnectionStatusNoConnection; + if (error_ptr) + *error_ptr = Status::FromErrorString("ConPTY STDIN handle is invalid"); + return 0; + } + DWORD written = 0; + if (!::WriteFile(stdin_handle, src, static_cast<DWORD>(src_len), &written, + nullptr)) { + DWORD err = ::GetLastError(); + status = (err == ERROR_BROKEN_PIPE || err == ERROR_NO_DATA) + ? eConnectionStatusEndOfFile + : eConnectionStatusError; + if (error_ptr) + *error_ptr = Status(err, lldb::eErrorTypeWin32); + return written; + } + status = eConnectionStatusSuccess; + return written; } diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp index b235ab281bad6..bf447c9ea81f5 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp @@ -749,4 +749,19 @@ void NativeProcessWindows::STDIOReadThreadBytesReceived(void *baton, self->m_delegate.NewProcessOutput( self, llvm::StringRef(static_cast<const char *>(src), src_len)); } + +size_t NativeProcessWindows::WriteStdin(const void *buf, size_t len, + Status &error) { + if (!m_stdio_communication.HasConnection()) { + error = Status::FromErrorString( + "no ConPTY connection on this NativeProcessWindows"); + return 0; + } + ConnectionStatus status; + size_t written = m_stdio_communication.Write(buf, len, status, &error); + if (status != eConnectionStatusSuccess && error.Success()) + error = Status::FromErrorStringWithFormatv( + "ConPTY stdin write returned status {0}", static_cast<int>(status)); + return written; +} } // namespace lldb_private diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h index 95b85754ebdeb..cac3920d82aa9 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h @@ -107,6 +107,13 @@ class NativeProcessWindows : public NativeProcessProtocol, bool HasPendingLibraryEvents() override; + /// Forward bytes from the gdb-remote `I` packet into the inferior's + /// ConPTY-backed stdin via `m_stdio_communication.Write` → + /// `ConnectionConPTY::Write` → `WriteFile` on the parent-side STDIN + /// HANDLE. Returns the number of bytes written (0 if the PTY is + /// disconnected or write fails). + size_t WriteStdin(const void *buf, size_t len, Status &error) override; + // ProcessDebugger Overrides void OnExitProcess(uint32_t exit_code) override; void OnDebuggerConnected(lldb::addr_t image_base) override; diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp index 941c25e9ecb77..d6776260806f3 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp @@ -46,6 +46,7 @@ #include "ForwardDecl.h" #include "IOHandlerProcessSTDIOWindows.h" #include "LocalDebugDelegate.h" +#include "Plugins/Process/Utility/IOHandlerProcessSTDIOWindows.h" #include "ProcessWindowsLog.h" #include "TargetThreadWindows.h" diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp index d56026e011564..077144f94227d 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -2569,12 +2569,19 @@ GDBRemoteCommunicationServerLLGS::Handle_I(StringExtractorGDBRemote &packet) { // write directly to stdin *this might block if stdin buffer is full* // TODO: enqueue this block in circular buffer and send window size to // remote host - ConnectionStatus status; Status error; + +#if defined(_WIN32) + // On Windows the inferior's stdio is owned by NativeProcessWindows. + if (m_current_process->WriteStdin(tmp, read, error) != read || error.Fail()) + return SendErrorResponse(0x15); +#else + ConnectionStatus status; m_stdio_communication.WriteAll(tmp, read, status, &error); if (error.Fail()) { return SendErrorResponse(0x15); } +#endif } return SendOKResponse(); diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index f6eaf5851338b..1cded3f428e0b 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -95,6 +95,10 @@ #include "llvm/Support/Threading.h" #include "llvm/Support/raw_ostream.h" +#ifdef _WIN32 +#include "Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.h" +#endif + #if defined(__APPLE__) #define DEBUGSERVER_BASENAME "debugserver" #elif defined(_WIN32) @@ -873,8 +877,18 @@ Status ProcessGDBRemote::DoLaunch(lldb_private::Module *exe_module, SetPrivateState(SetThreadStopInfo(response)); if (!disable_stdio) { - if (pty.GetPrimaryFileDescriptor() != PseudoTerminal::invalid_fd) + if (pty.GetPrimaryFileDescriptor() != PseudoTerminal::invalid_fd) { SetSTDIOFileDescriptor(pty.ReleasePrimaryFileDescriptor()); + } +#ifdef _WIN32 + else if (m_stdin_forward) { + // No client-side PTY FD on Windows. + std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); + if (!m_process_input_reader) + m_process_input_reader = + std::make_shared<IOHandlerProcessSTDIOWindows>(this); + } +#endif } } } else { >From f81f3cf8724598610203f43b1b8510b201bce234 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Fri, 5 Jun 2026 17:38:51 +0100 Subject: [PATCH 2/4] fixup! [lldb][Windows] add stdin support to lldb-server --- lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp index d6776260806f3..941c25e9ecb77 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp @@ -46,7 +46,6 @@ #include "ForwardDecl.h" #include "IOHandlerProcessSTDIOWindows.h" #include "LocalDebugDelegate.h" -#include "Plugins/Process/Utility/IOHandlerProcessSTDIOWindows.h" #include "ProcessWindowsLog.h" #include "TargetThreadWindows.h" >From 0fa24671fcd77e97c08c7e5ff40314e52d8f9931 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Mon, 8 Jun 2026 13:33:39 +0100 Subject: [PATCH 3/4] [lldb][Windows] Move IOHandlerProcessSTDIOWindows in Target --- .../lldb/Target/ProcessIOHandler.h} | 54 ++++++- .../Process/Windows/Common/CMakeLists.txt | 1 - .../Process/Windows/Common/ProcessWindows.cpp | 2 +- lldb/source/Target/CMakeLists.txt | 1 + lldb/source/Target/Process.cpp | 146 +---------------- .../ProcessIOHandler.cpp} | 150 ++++++++++++++++-- 6 files changed, 190 insertions(+), 164 deletions(-) rename lldb/{source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.h => include/lldb/Target/ProcessIOHandler.h} (53%) rename lldb/source/{Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.cpp => Target/ProcessIOHandler.cpp} (50%) diff --git a/lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.h b/lldb/include/lldb/Target/ProcessIOHandler.h similarity index 53% rename from lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.h rename to lldb/include/lldb/Target/ProcessIOHandler.h index 79a414eeda12f..90d9555e08334 100644 --- a/lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.h +++ b/lldb/include/lldb/Target/ProcessIOHandler.h @@ -6,17 +6,53 @@ // //===----------------------------------------------------------------------===// -#ifndef LIBLLDB_PLUGINS_PROCESS_WINDOWS_COMMON_IO_HANDLER_PROCESS_STDIO_WINDOWS_H_ -#define LIBLLDB_PLUGINS_PROCESS_WINDOWS_COMMON_IO_HANDLER_PROCESS_STDIO_WINDOWS_H_ +#ifndef LLDB_TARGET_PROCESSIOHANDLER_H +#define LLDB_TARGET_PROCESSIOHANDLER_H #include "lldb/Core/IOHandler.h" #include "lldb/Host/File.h" +#include "lldb/Host/Pipe.h" #include "lldb/Target/Process.h" -using HANDLE = void *; +namespace lldb_private { + +/// Forwards lldb's STDIN to the inferior's pty (or anything writable) and +/// supports asynchronous interrupt via an internal pipe. Used on POSIX hosts +/// and as a no-op stub on Windows. +class IOHandlerProcessSTDIO : public IOHandler { +public: + IOHandlerProcessSTDIO(Process *process, int write_fd); + + ~IOHandlerProcessSTDIO() override = default; -using namespace lldb_private; + void SetIsRunning(bool running); + + void Run() override; + + void Cancel() override; + + bool Interrupt() override; + + void GotEOF() override {} +protected: + Process *m_process; + /// Read from this file (usually actual STDIN for LLDB) + NativeFile m_read_file; + /// Write to this file (usually the primary pty for getting io to debuggee) + NativeFile m_write_file; + Pipe m_pipe; + std::mutex m_mutex; + bool m_is_running = false; +}; + +#ifdef _WIN32 + +using HANDLE = void *; + +/// Forwards lldb's STDIN to the inferior on Windows hosts. Reads from the +/// console (handling the line-buffering quirks of the Windows console) and +/// writes the bytes into the process via Process::PutSTDIN. class IOHandlerProcessSTDIOWindows : public IOHandler { public: IOHandlerProcessSTDIOWindows(Process *process); @@ -25,8 +61,8 @@ class IOHandlerProcessSTDIOWindows : public IOHandler { void SetIsRunning(bool running); - /// Peek the console for input. If it has any, drain the pipe until text input - /// is found or the pipe is empty. + /// Peek the console for input. If it has any, drain the pipe until text + /// input is found or the pipe is empty. /// /// \param hStdin /// The handle to the standard input's pipe. @@ -60,4 +96,8 @@ class IOHandlerProcessSTDIOWindows : public IOHandler { bool m_is_running = false; }; -#endif // LIBLLDB_PLUGINS_PROCESS_WINDOWS_COMMON_IO_HANDLER_PROCESS_STDIO_WINDOWS_H_ +#endif // _WIN32 + +} // namespace lldb_private + +#endif // LLDB_TARGET_PROCESSIOHANDLER_H diff --git a/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt b/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt index bd076d5d64df1..9854b79fbb8d6 100644 --- a/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt +++ b/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt @@ -1,6 +1,5 @@ add_lldb_library(lldbPluginProcessWindowsCommon PLUGIN - IOHandlerProcessSTDIOWindows.cpp DebuggerThread.cpp LocalDebugDelegate.cpp NativeProcessWindows.cpp diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp index 941c25e9ecb77..8ec6ca21b34a2 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp @@ -28,6 +28,7 @@ #include "lldb/Host/windows/HostThreadWindows.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Target/DynamicLoader.h" +#include "lldb/Target/ProcessIOHandler.h" #include "lldb/Target/MemoryRegionInfo.h" #include "lldb/Target/StopInfo.h" #include "lldb/Target/Target.h" @@ -44,7 +45,6 @@ #include "DebuggerThread.h" #include "ExceptionRecord.h" #include "ForwardDecl.h" -#include "IOHandlerProcessSTDIOWindows.h" #include "LocalDebugDelegate.h" #include "ProcessWindowsLog.h" #include "TargetThreadWindows.h" diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt index ee9b68525e201..704de84e25169 100644 --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -32,6 +32,7 @@ add_lldb_library(lldbTarget PathMappingList.cpp Platform.cpp Process.cpp + ProcessIOHandler.cpp ProcessTrace.cpp Queue.cpp QueueItem.cpp diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index ac1357f7d00a1..6e20703f65a45 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -53,6 +53,7 @@ #include "lldb/Target/OperatingSystem.h" #include "lldb/Target/Platform.h" #include "lldb/Target/Process.h" +#include "lldb/Target/ProcessIOHandler.h" #include "lldb/Target/RegisterContext.h" #include "lldb/Target/StopInfo.h" #include "lldb/Target/StructuredDataPlugin.h" @@ -4961,151 +4962,6 @@ void Process::STDIOReadThreadBytesReceived(void *baton, const void *src, process->AppendSTDOUT(static_cast<const char *>(src), src_len); } -class IOHandlerProcessSTDIO : public IOHandler { -public: - IOHandlerProcessSTDIO(Process *process, int write_fd) - : IOHandler(process->GetTarget().GetDebugger(), - IOHandler::Type::ProcessIO), - m_process(process), - m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false), - m_write_file(write_fd, File::eOpenOptionWriteOnly, false) { - m_pipe.CreateNew(); - } - - ~IOHandlerProcessSTDIO() override = default; - - void SetIsRunning(bool running) { - std::lock_guard<std::mutex> guard(m_mutex); - SetIsDone(!running); - m_is_running = running; - } - - // Each IOHandler gets to run until it is done. It should read data from the - // "in" and place output into "out" and "err and return when done. - void Run() override { - if (!m_read_file.IsValid() || !m_write_file.IsValid() || - !m_pipe.CanRead() || !m_pipe.CanWrite()) { - SetIsDone(true); - return; - } - - SetIsDone(false); - const int read_fd = m_read_file.GetDescriptor(); - Terminal terminal(read_fd); - TerminalState terminal_state(terminal, false); - // FIXME: error handling? - llvm::consumeError(terminal.SetCanonical(false)); - llvm::consumeError(terminal.SetEcho(false)); -// FD_ZERO, FD_SET are not supported on windows -#ifndef _WIN32 - const int pipe_read_fd = m_pipe.GetReadFileDescriptor(); - SetIsRunning(true); - while (true) { - { - std::lock_guard<std::mutex> guard(m_mutex); - if (GetIsDone()) - break; - } - - SelectHelper select_helper; - select_helper.FDSetRead(read_fd); - select_helper.FDSetRead(pipe_read_fd); - Status error = select_helper.Select(); - - if (error.Fail()) - break; - - char ch = 0; - size_t n; - if (select_helper.FDIsSetRead(read_fd)) { - n = 1; - if (m_read_file.Read(&ch, n).Success() && n == 1) { - if (m_write_file.Write(&ch, n).Fail() || n != 1) - break; - } else - break; - } - - if (select_helper.FDIsSetRead(pipe_read_fd)) { - // Consume the interrupt byte - if (llvm::Expected<size_t> bytes_read = m_pipe.Read(&ch, 1)) { - if (ch == 'q') - break; - if (ch == 'i') - if (StateIsRunningState(m_process->GetState())) - m_process->SendAsyncInterrupt(); - } else { - LLDB_LOG_ERROR(GetLog(LLDBLog::Process), bytes_read.takeError(), - "Pipe read failed: {0}"); - } - } - } - SetIsRunning(false); -#endif - } - - void Cancel() override { - std::lock_guard<std::mutex> guard(m_mutex); - SetIsDone(true); - // Only write to our pipe to cancel if we are in - // IOHandlerProcessSTDIO::Run(). We can end up with a python command that - // is being run from the command interpreter: - // - // (lldb) step_process_thousands_of_times - // - // In this case the command interpreter will be in the middle of handling - // the command and if the process pushes and pops the IOHandler thousands - // of times, we can end up writing to m_pipe without ever consuming the - // bytes from the pipe in IOHandlerProcessSTDIO::Run() and end up - // deadlocking when the pipe gets fed up and blocks until data is consumed. - if (m_is_running) { - char ch = 'q'; // Send 'q' for quit - if (llvm::Error err = m_pipe.Write(&ch, 1).takeError()) { - LLDB_LOG_ERROR(GetLog(LLDBLog::Process), std::move(err), - "Pipe write failed: {0}"); - } - } - } - - bool Interrupt() override { - // Do only things that are safe to do in an interrupt context (like in a - // SIGINT handler), like write 1 byte to a file descriptor. This will - // interrupt the IOHandlerProcessSTDIO::Run() and we can look at the byte - // that was written to the pipe and then call - // m_process->SendAsyncInterrupt() from a much safer location in code. - if (m_active) { - char ch = 'i'; // Send 'i' for interrupt - return !errorToBool(m_pipe.Write(&ch, 1).takeError()); - } else { - // This IOHandler might be pushed on the stack, but not being run - // currently so do the right thing if we aren't actively watching for - // STDIN by sending the interrupt to the process. Otherwise the write to - // the pipe above would do nothing. This can happen when the command - // interpreter is running and gets a "expression ...". It will be on the - // IOHandler thread and sending the input is complete to the delegate - // which will cause the expression to run, which will push the process IO - // handler, but not run it. - - if (StateIsRunningState(m_process->GetState())) { - m_process->SendAsyncInterrupt(); - return true; - } - } - return false; - } - - void GotEOF() override {} - -protected: - Process *m_process; - NativeFile m_read_file; // Read from this file (usually actual STDIN for LLDB - NativeFile m_write_file; // Write to this file (usually the primary pty for - // getting io to debuggee) - Pipe m_pipe; - std::mutex m_mutex; - bool m_is_running = false; -}; - void Process::SetSTDIOFileDescriptor(int fd) { // First set up the Read Thread for reading/handling process I/O m_stdio_communication.SetConnection( diff --git a/lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.cpp b/lldb/source/Target/ProcessIOHandler.cpp similarity index 50% rename from lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.cpp rename to lldb/source/Target/ProcessIOHandler.cpp index 2b9ab9ce0812b..e4f752057d539 100644 --- a/lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.cpp +++ b/lldb/source/Target/ProcessIOHandler.cpp @@ -6,15 +6,151 @@ // //===----------------------------------------------------------------------===// -#include "IOHandlerProcessSTDIOWindows.h" +#include "lldb/Target/ProcessIOHandler.h" -#include "lldb/Host/windows/windows.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Host/Terminal.h" +#include "lldb/Target/Target.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" +#include "lldb/Utility/SelectHelper.h" #include "lldb/Utility/State.h" +#include "lldb/Utility/Status.h" using namespace lldb_private; +IOHandlerProcessSTDIO::IOHandlerProcessSTDIO(Process *process, int write_fd) + : IOHandler(process->GetTarget().GetDebugger(), IOHandler::Type::ProcessIO), + m_process(process), + m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false), + m_write_file(write_fd, File::eOpenOptionWriteOnly, false) { + m_pipe.CreateNew(); +} + +void IOHandlerProcessSTDIO::SetIsRunning(bool running) { + std::lock_guard<std::mutex> guard(m_mutex); + SetIsDone(!running); + m_is_running = running; +} + +// Each IOHandler gets to run until it is done. It should read data from the +// "in" and place output into "out" and "err and return when done. +void IOHandlerProcessSTDIO::Run() { + if (!m_read_file.IsValid() || !m_write_file.IsValid() || !m_pipe.CanRead() || + !m_pipe.CanWrite()) { + SetIsDone(true); + return; + } + + SetIsDone(false); + const int read_fd = m_read_file.GetDescriptor(); + Terminal terminal(read_fd); + TerminalState terminal_state(terminal, false); + // FIXME: error handling? + llvm::consumeError(terminal.SetCanonical(false)); + llvm::consumeError(terminal.SetEcho(false)); +// FD_ZERO, FD_SET are not supported on windows +#ifndef _WIN32 + const int pipe_read_fd = m_pipe.GetReadFileDescriptor(); + SetIsRunning(true); + while (true) { + { + std::lock_guard<std::mutex> guard(m_mutex); + if (GetIsDone()) + break; + } + + SelectHelper select_helper; + select_helper.FDSetRead(read_fd); + select_helper.FDSetRead(pipe_read_fd); + Status error = select_helper.Select(); + + if (error.Fail()) + break; + + char ch = 0; + size_t n; + if (select_helper.FDIsSetRead(read_fd)) { + n = 1; + if (m_read_file.Read(&ch, n).Success() && n == 1) { + if (m_write_file.Write(&ch, n).Fail() || n != 1) + break; + } else + break; + } + + if (select_helper.FDIsSetRead(pipe_read_fd)) { + // Consume the interrupt byte + if (llvm::Expected<size_t> bytes_read = m_pipe.Read(&ch, 1)) { + if (ch == 'q') + break; + if (ch == 'i') + if (StateIsRunningState(m_process->GetState())) + m_process->SendAsyncInterrupt(); + } else { + LLDB_LOG_ERROR(GetLog(LLDBLog::Process), bytes_read.takeError(), + "Pipe read failed: {0}"); + } + } + } + SetIsRunning(false); +#endif +} + +void IOHandlerProcessSTDIO::Cancel() { + std::lock_guard<std::mutex> guard(m_mutex); + SetIsDone(true); + // Only write to our pipe to cancel if we are in + // IOHandlerProcessSTDIO::Run(). We can end up with a python command that + // is being run from the command interpreter: + // + // (lldb) step_process_thousands_of_times + // + // In this case the command interpreter will be in the middle of handling + // the command and if the process pushes and pops the IOHandler thousands + // of times, we can end up writing to m_pipe without ever consuming the + // bytes from the pipe in IOHandlerProcessSTDIO::Run() and end up + // deadlocking when the pipe gets fed up and blocks until data is consumed. + if (m_is_running) { + char ch = 'q'; // Send 'q' for quit + if (llvm::Error err = m_pipe.Write(&ch, 1).takeError()) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Process), std::move(err), + "Pipe write failed: {0}"); + } + } +} + +bool IOHandlerProcessSTDIO::Interrupt() { + // Do only things that are safe to do in an interrupt context (like in a + // SIGINT handler), like write 1 byte to a file descriptor. This will + // interrupt the IOHandlerProcessSTDIO::Run() and we can look at the byte + // that was written to the pipe and then call + // m_process->SendAsyncInterrupt() from a much safer location in code. + if (m_active) { + char ch = 'i'; // Send 'i' for interrupt + return !errorToBool(m_pipe.Write(&ch, 1).takeError()); + } else { + // This IOHandler might be pushed on the stack, but not being run + // currently so do the right thing if we aren't actively watching for + // STDIN by sending the interrupt to the process. Otherwise the write to + // the pipe above would do nothing. This can happen when the command + // interpreter is running and gets a "expression ...". It will be on the + // IOHandler thread and sending the input is complete to the delegate + // which will cause the expression to run, which will push the process IO + // handler, but not run it. + + if (StateIsRunningState(m_process->GetState())) { + m_process->SendAsyncInterrupt(); + return true; + } + } + return false; +} + +#ifdef _WIN32 + +#include "lldb/Host/windows/windows.h" + IOHandlerProcessSTDIOWindows::IOHandlerProcessSTDIOWindows(Process *process) : IOHandler(process->GetTarget().GetDebugger(), IOHandler::Type::ProcessIO), m_process(process), @@ -34,14 +170,6 @@ void IOHandlerProcessSTDIOWindows::SetIsRunning(bool running) { m_is_running = running; } -/// Peek the console for input. If it has any, drain the pipe until text input -/// is found or the pipe is empty. -/// -/// \param hStdin -/// The handle to the standard input's pipe. -/// -/// \return -/// true if the pipe has text input. llvm::Expected<bool> IOHandlerProcessSTDIOWindows::ConsoleHasTextInput(const HANDLE hStdin) { // Check if there are already characters buffered. Pressing enter counts as @@ -170,3 +298,5 @@ bool IOHandlerProcessSTDIOWindows::Interrupt() { } return false; } + +#endif // _WIN32 >From feb8099035e6c8ed1ec0a34aadc6bf89e0217abc Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Mon, 8 Jun 2026 15:56:52 +0100 Subject: [PATCH 4/4] fix include --- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index 68f3eb0752973..a82e2a31b8c10 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -102,7 +102,7 @@ #include "llvm/Support/raw_ostream.h" #ifdef _WIN32 -#include "Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.h" +#include "lldb/Target/ProcessIOHandler.h" #endif #if defined(__APPLE__) _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
