https://github.com/charles-zablit updated 
https://github.com/llvm/llvm-project/pull/180561

>From a04da056223bc2221186a21a638c642b4686aba9 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Mon, 9 Feb 2026 17:15:06 +0000
Subject: [PATCH] [lldb][windows] add STDIN and STDOUT forwarding support

---
 .../Host/windows/ProcessLauncherWindows.cpp   |   3 +-
 .../Process/Windows/Common/ProcessWindows.cpp | 124 +++++++++++++-----
 .../Shell/Settings/TestFrameFormatColor.test  |   2 +-
 .../Settings/TestFrameFormatNoColor.test      |   2 +-
 4 files changed, 96 insertions(+), 35 deletions(-)

diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp 
b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
index 6483e668d73b3..cfd84731f0eb6 100644
--- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp
+++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
@@ -203,7 +203,8 @@ ProcessLauncherWindows::LaunchProcess(const 
ProcessLaunchInfo &launch_info,
 
   BOOL result = ::CreateProcessW(
       wexecutable.c_str(), pwcommandLine, NULL, NULL,
-      /*bInheritHandles=*/!inherited_handles.empty(), flags, 
environment.data(),
+      /*bInheritHandles=*/!inherited_handles.empty() || use_pty, flags,
+      environment.data(),
       wworkingDirectory.size() == 0 ? NULL : wworkingDirectory.c_str(),
       reinterpret_cast<STARTUPINFOW *>(&startupinfoex), &pi);
 
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 226cc147aadae..373729c952071 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -958,21 +958,60 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
                   IOHandler::Type::ProcessIO),
         m_process(process),
         m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false),
-        m_write_file(conpty_input) {
-    m_pipe.CreateNew();
+        m_write_file(conpty_input),
+        m_interrupt_event(
+            CreateEvent(/*lpEventAttributes=*/NULL, /*bManualReset=*/FALSE,
+                        /*bInitialState=*/FALSE, /*lpName=*/NULL)) {}
+
+  ~IOHandlerProcessSTDIOWindows() override {
+    if (m_interrupt_event != INVALID_HANDLE_VALUE)
+      ::CloseHandle(m_interrupt_event);
   }
 
-  ~IOHandlerProcessSTDIOWindows() override = default;
-
   void SetIsRunning(bool running) {
     std::lock_guard<std::mutex> guard(m_mutex);
     SetIsDone(!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> ConsoleHasTextInput(const HANDLE hStdin) {
+    // Check if there are already characters buffered. Pressing enter counts as
+    // 2 characters '\r\n' and only one of them is a keyDown event.
+    DWORD bytesAvailable = 0;
+    if (PeekNamedPipe(hStdin, NULL, 0, NULL, &bytesAvailable, NULL)) {
+      if (bytesAvailable > 0)
+        return true;
+    }
+
+    while (true) {
+      INPUT_RECORD inputRecord;
+      DWORD numRead = 0;
+      if (!PeekConsoleInput(hStdin, &inputRecord, 1, &numRead))
+        return llvm::createStringError("Failed to peek standard input.");
+
+      if (numRead == 0)
+        return false;
+
+      if (inputRecord.EventType == KEY_EVENT &&
+          inputRecord.Event.KeyEvent.bKeyDown &&
+          inputRecord.Event.KeyEvent.uChar.AsciiChar != 0)
+        return true;
+
+      if (!ReadConsoleInput(hStdin, &inputRecord, 1, &numRead))
+        return llvm::createStringError("Failed to read standard input.");
+    }
+  }
+
   void Run() override {
-    if (!m_read_file.IsValid() || m_write_file == INVALID_HANDLE_VALUE ||
-        !m_pipe.CanRead() || !m_pipe.CanWrite()) {
+    if (!m_read_file.IsValid() || m_write_file == INVALID_HANDLE_VALUE) {
       SetIsDone(true);
       return;
     }
@@ -980,9 +1019,18 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
     SetIsDone(false);
     SetIsRunning(true);
 
-    HANDLE hStdin = (HANDLE)_get_osfhandle(m_read_file.GetDescriptor());
-    HANDLE hInterrupt = (HANDLE)_get_osfhandle(m_pipe.GetReadFileDescriptor());
-    HANDLE waitHandles[2] = {hStdin, hInterrupt};
+    HANDLE hStdin = m_read_file.GetWaitableHandle();
+    HANDLE waitHandles[2] = {hStdin, m_interrupt_event};
+
+    DWORD consoleMode;
+    bool isConsole = GetConsoleMode(hStdin, &consoleMode) != 0;
+    // With ENABLE_LINE_INPUT, ReadFile returns only when a carriage return is
+    // read. This will block lldb in ReadFile until the user hits enter. Save
+    // the previous console mode to restore it later and remove
+    // ENABLE_LINE_INPUT.
+    DWORD oldConsoleMode = consoleMode;
+    SetConsoleMode(hStdin,
+                   consoleMode & ~ENABLE_LINE_INPUT & ~ENABLE_ECHO_INPUT);
 
     while (true) {
       {
@@ -996,6 +1044,20 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
       case WAIT_FAILED:
         goto exit_loop;
       case WAIT_OBJECT_0: {
+        if (isConsole) {
+          auto hasInputOrErr = ConsoleHasTextInput(hStdin);
+          if (!hasInputOrErr) {
+            Log *log = GetLog(WindowsLog::Process);
+            LLDB_LOG_ERROR(log, hasInputOrErr.takeError(),
+                           "failed to process debuggee's IO: {0}");
+            goto exit_loop;
+          }
+
+          // If no text input is ready, go back to waiting.
+          if (!*hasInputOrErr)
+            continue;
+        }
+
         char ch = 0;
         DWORD read = 0;
         if (!ReadFile(hStdin, &ch, 1, &read, nullptr) || read != 1)
@@ -1007,14 +1069,10 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
         break;
       }
       case WAIT_OBJECT_0 + 1: {
-        char ch = 0;
-        DWORD read = 0;
-        if (!ReadFile(hInterrupt, &ch, 1, &read, nullptr) || read != 1)
-          goto exit_loop;
-
-        if (ch == eControlOpQuit)
+        ControlOp op = m_pending_op.exchange(eControlOpNone);
+        if (op == eControlOpQuit)
           goto exit_loop;
-        if (ch == eControlOpInterrupt &&
+        if (op == eControlOpInterrupt &&
             StateIsRunningState(m_process->GetState()))
           m_process->SendAsyncInterrupt();
         break;
@@ -1026,24 +1084,24 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
 
   exit_loop:;
     SetIsRunning(false);
+    SetIsDone(true);
+    SetConsoleMode(hStdin, oldConsoleMode);
   }
 
   void Cancel() override {
     std::lock_guard<std::mutex> guard(m_mutex);
     SetIsDone(true);
     if (m_is_running) {
-      char ch = eControlOpQuit;
-      if (llvm::Error err = m_pipe.Write(&ch, 1).takeError()) {
-        LLDB_LOG_ERROR(GetLog(LLDBLog::Process), std::move(err),
-                       "Pipe write failed: {0}");
-      }
+      m_pending_op.store(eControlOpQuit);
+      ::SetEvent(m_interrupt_event);
     }
   }
 
   bool Interrupt() override {
     if (m_active) {
-      char ch = eControlOpInterrupt;
-      return !errorToBool(m_pipe.Write(&ch, 1).takeError());
+      m_pending_op.store(eControlOpInterrupt);
+      ::SetEvent(m_interrupt_event);
+      return true;
     }
     if (StateIsRunningState(m_process->GetState())) {
       m_process->SendAsyncInterrupt();
@@ -1055,19 +1113,21 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
   void GotEOF() override {}
 
 private:
-  Process *m_process;
-  NativeFile m_read_file; // Read from this file (usually actual STDIN for LLDB
-  HANDLE m_write_file =
-      INVALID_HANDLE_VALUE; // 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;
-
   enum ControlOp : char {
     eControlOpQuit = 'q',
     eControlOpInterrupt = 'i',
+    eControlOpNone = 0,
   };
+
+  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)
+  HANDLE m_write_file = INVALID_HANDLE_VALUE;
+  HANDLE m_interrupt_event = INVALID_HANDLE_VALUE;
+  std::atomic<ControlOp> m_pending_op{eControlOpNone};
+  std::mutex m_mutex;
+  bool m_is_running = false;
 };
 
 void ProcessWindows::SetPseudoConsoleHandle(
diff --git a/lldb/test/Shell/Settings/TestFrameFormatColor.test 
b/lldb/test/Shell/Settings/TestFrameFormatColor.test
index 970d7238e7512..f30dafadf5919 100644
--- a/lldb/test/Shell/Settings/TestFrameFormatColor.test
+++ b/lldb/test/Shell/Settings/TestFrameFormatColor.test
@@ -9,4 +9,4 @@ c
 q
 
 # Check the ASCII escape code
-# CHECK: 
+# CHECK: {{\[[0-9;]+m}}
diff --git a/lldb/test/Shell/Settings/TestFrameFormatNoColor.test 
b/lldb/test/Shell/Settings/TestFrameFormatNoColor.test
index 2bcdb8e82bd9d..37906311c4f69 100644
--- a/lldb/test/Shell/Settings/TestFrameFormatNoColor.test
+++ b/lldb/test/Shell/Settings/TestFrameFormatNoColor.test
@@ -9,4 +9,4 @@ c
 q
 
 # Check the ASCII escape code
-# CHECK-NOT: 
+# CHECK-NOT: {{\[[0-9;]+m}}

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to