[Lldb-commits] [lldb] [NFC][lldb][Windows] Move ProcessIOHandler in Target (PR #202353)

Charles Zablit via lldb-commits lldb-commits at lists.llvm.org
Mon Jun 8 08:02:56 PDT 2026


https://github.com/charles-zablit created https://github.com/llvm/llvm-project/pull/202353

This patch merges `IOHandlerProcessSTDIO` and `IOHandlerProcessSTDIOWindows` in one file: `ProcessIOHandler.h`.

This is an NFC change which reduces the size of `Process.cpp` and allows to reuse `IOHandlerProcessSTDIOWindows` in GDBRemote, ahead of https://github.com/llvm/llvm-project/pull/201638.

>From 8ebbf2ab9773db68948aa4dc7832039a0bc46664 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] [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



More information about the lldb-commits mailing list