[Lldb-commits] [lldb] [lldb][Windows] add stdin support to lldb-server (PR #201638)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Mon Jun 8 07:57:19 PDT 2026
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 <c_zablit at apple.com>
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 <c_zablit at apple.com>
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 <c_zablit at apple.com>
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 <c_zablit at apple.com>
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__)
More information about the lldb-commits
mailing list