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

>From dc27faa0750f8a9a77dc4a0edd387da61ef15d29 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Tue, 13 Jan 2026 18:54:43 +0000
Subject: [PATCH 01/15] [lldb][windows] prevent IOHandlerProcessSTDIOWindows
 from consuming non text inputs

---
 .../Process/Windows/Common/ProcessWindows.cpp   | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 226cc147aadae..e135628d9009b 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -984,6 +984,9 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
     HANDLE hInterrupt = (HANDLE)_get_osfhandle(m_pipe.GetReadFileDescriptor());
     HANDLE waitHandles[2] = {hStdin, hInterrupt};
 
+    DWORD consoleMode;
+    bool isConsole = GetConsoleMode(hStdin, &consoleMode) != 0;
+
     while (true) {
       {
         std::lock_guard<std::mutex> guard(m_mutex);
@@ -996,6 +999,20 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
       case WAIT_FAILED:
         goto exit_loop;
       case WAIT_OBJECT_0: {
+        if (isConsole) {
+          INPUT_RECORD inputRecord;
+          DWORD numRead = 0;
+          if (!PeekConsoleInput(hStdin, &inputRecord, 1, &numRead) ||
+              numRead == 0)
+            goto exit_loop;
+          // We only care about text input. Ignore all non text input events 
and
+          // let other IOHandlers handle them.
+          if (inputRecord.EventType != KEY_EVENT ||
+              !inputRecord.Event.KeyEvent.bKeyDown ||
+              inputRecord.Event.KeyEvent.uChar.AsciiChar == 0)
+            break;
+        }
+
         char ch = 0;
         DWORD read = 0;
         if (!ReadFile(hStdin, &ch, 1, &read, nullptr) || read != 1)

>From 319724127263f589eedded0c9acbd9ccbd6c63f8 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Mon, 19 Jan 2026 13:50:09 +0000
Subject: [PATCH 02/15] consume non text input

---
 .../Process/Windows/Common/ProcessWindows.cpp | 28 +++++++++++--------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index e135628d9009b..71dbb3d964804 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -1000,17 +1000,23 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
         goto exit_loop;
       case WAIT_OBJECT_0: {
         if (isConsole) {
-          INPUT_RECORD inputRecord;
-          DWORD numRead = 0;
-          if (!PeekConsoleInput(hStdin, &inputRecord, 1, &numRead) ||
-              numRead == 0)
-            goto exit_loop;
-          // We only care about text input. Ignore all non text input events 
and
-          // let other IOHandlers handle them.
-          if (inputRecord.EventType != KEY_EVENT ||
-              !inputRecord.Event.KeyEvent.bKeyDown ||
-              inputRecord.Event.KeyEvent.uChar.AsciiChar == 0)
-            break;
+          while (true) {
+            INPUT_RECORD inputRecord;
+            DWORD numRead = 0;
+            if (!PeekConsoleInput(hStdin, &inputRecord, 1, &numRead) ||
+                numRead == 0)
+              goto exit_loop;
+
+            // We only care about text input. Consume all non text input 
events before letting ReadFile handle text input.
+            if (inputRecord.EventType == KEY_EVENT &&
+                inputRecord.Event.KeyEvent.bKeyDown &&
+                inputRecord.Event.KeyEvent.uChar.AsciiChar != 0)
+              break;
+
+            if (!ReadConsoleInput(hStdin, &inputRecord, 1, &numRead) ||
+                numRead == 0)
+              goto exit_loop;
+          }
         }
 
         char ch = 0;

>From 01d9cebd8c3ab18553ecf71c86bb85fbb7bf78dd Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Mon, 19 Jan 2026 13:57:50 +0000
Subject: [PATCH 03/15] fix formatting

---
 lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 71dbb3d964804..0cdb51cc5a086 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -1007,7 +1007,8 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
                 numRead == 0)
               goto exit_loop;
 
-            // We only care about text input. Consume all non text input 
events before letting ReadFile handle text input.
+            // We only care about text input. Consume all non text input events
+            // before letting ReadFile handle text input.
             if (inputRecord.EventType == KEY_EVENT &&
                 inputRecord.Event.KeyEvent.bKeyDown &&
                 inputRecord.Event.KeyEvent.uChar.AsciiChar != 0)

>From 071b55b80df6d0976b58cd384db56e39dd6d8171 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Mon, 19 Jan 2026 15:50:09 +0000
Subject: [PATCH 04/15] fix control flow

---
 lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 0cdb51cc5a086..0d68240c2749d 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -1003,8 +1003,7 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
           while (true) {
             INPUT_RECORD inputRecord;
             DWORD numRead = 0;
-            if (!PeekConsoleInput(hStdin, &inputRecord, 1, &numRead) ||
-                numRead == 0)
+            if (!PeekConsoleInput(hStdin, &inputRecord, 1, &numRead))
               goto exit_loop;
 
             // We only care about text input. Consume all non text input events

>From 33cb556acde1c6710cf7a2b421c2aa2ac7110d61 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Mon, 19 Jan 2026 17:32:34 +0000
Subject: [PATCH 05/15] fix control flow

---
 .../Plugins/Process/Windows/Common/ProcessWindows.cpp    | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 0d68240c2749d..a4b46e65617eb 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -988,6 +988,7 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
     bool isConsole = GetConsoleMode(hStdin, &consoleMode) != 0;
 
     while (true) {
+    read_loop:;
       {
         std::lock_guard<std::mutex> guard(m_mutex);
         if (GetIsDone())
@@ -1006,6 +1007,9 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
             if (!PeekConsoleInput(hStdin, &inputRecord, 1, &numRead))
               goto exit_loop;
 
+            if (numRead == 0)
+              goto read_loop;
+
             // We only care about text input. Consume all non text input events
             // before letting ReadFile handle text input.
             if (inputRecord.EventType == KEY_EVENT &&
@@ -1013,8 +1017,7 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
                 inputRecord.Event.KeyEvent.uChar.AsciiChar != 0)
               break;
 
-            if (!ReadConsoleInput(hStdin, &inputRecord, 1, &numRead) ||
-                numRead == 0)
+            if (!ReadConsoleInput(hStdin, &inputRecord, 1, &numRead))
               goto exit_loop;
           }
         }
@@ -1048,7 +1051,7 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
     }
 
   exit_loop:;
-    SetIsRunning(false);
+  SetIsRunning(false);
   }
 
   void Cancel() override {

>From 84959c70fa48e4172e9d1574540e73ecc493de82 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Tue, 20 Jan 2026 11:15:27 +0000
Subject: [PATCH 06/15] add comment

---
 lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index a4b46e65617eb..940c953089166 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -1007,6 +1007,8 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
             if (!PeekConsoleInput(hStdin, &inputRecord, 1, &numRead))
               goto exit_loop;
 
+            // If the pipe is empty, go back to waiting on
+            // WaitForMultipleObjects rather than ReadFile.
             if (numRead == 0)
               goto read_loop;
 
@@ -1051,7 +1053,7 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
     }
 
   exit_loop:;
-  SetIsRunning(false);
+    SetIsRunning(false);
   }
 
   void Cancel() override {

>From 91e75a5b6c7ec1d6c639ebaf29ec58504257517a Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Tue, 20 Jan 2026 14:33:32 +0000
Subject: [PATCH 07/15] remove secondary pipe

---
 .../Process/Windows/Common/ProcessWindows.cpp | 109 ++++++++++--------
 1 file changed, 58 insertions(+), 51 deletions(-)

diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 940c953089166..d16e84b6d307c 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -959,7 +959,7 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
         m_process(process),
         m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false),
         m_write_file(conpty_input) {
-    m_pipe.CreateNew();
+    m_interrupt_event = INVALID_HANDLE_VALUE;
   }
 
   ~IOHandlerProcessSTDIOWindows() override = default;
@@ -970,9 +970,36 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
     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[in, out] hStdin
+  ///     The handle to the standard input's pipe.
+  ///
+  /// \return
+  ///     true if the pipe has text input.
+  llvm::Expected<bool> ConsoleHasTextInput(HANDLE hStdin) {
+    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,15 +1007,13 @@ 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;
 
     while (true) {
-    read_loop:;
       {
         std::lock_guard<std::mutex> guard(m_mutex);
         if (GetIsDone())
@@ -1001,27 +1026,13 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
         goto exit_loop;
       case WAIT_OBJECT_0: {
         if (isConsole) {
-          while (true) {
-            INPUT_RECORD inputRecord;
-            DWORD numRead = 0;
-            if (!PeekConsoleInput(hStdin, &inputRecord, 1, &numRead))
-              goto exit_loop;
-
-            // If the pipe is empty, go back to waiting on
-            // WaitForMultipleObjects rather than ReadFile.
-            if (numRead == 0)
-              goto read_loop;
-
-            // We only care about text input. Consume all non text input events
-            // before letting ReadFile handle text input.
-            if (inputRecord.EventType == KEY_EVENT &&
-                inputRecord.Event.KeyEvent.bKeyDown &&
-                inputRecord.Event.KeyEvent.uChar.AsciiChar != 0)
-              break;
-
-            if (!ReadConsoleInput(hStdin, &inputRecord, 1, &numRead))
-              goto exit_loop;
-          }
+          auto hasInputOrErr = ConsoleHasTextInput(hStdin);
+          if (hasInputOrErr.takeError())
+            goto exit_loop;
+
+          // If no text input is ready, go back to waiting.
+          if (!*hasInputOrErr)
+            continue;
         }
 
         char ch = 0;
@@ -1035,14 +1046,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)
+        ControlOp op = m_pending_op.exchange(eControlOpNone);
+        if (op == eControlOpQuit)
           goto exit_loop;
-
-        if (ch == eControlOpQuit)
-          goto exit_loop;
-        if (ch == eControlOpInterrupt &&
+        if (op == eControlOpInterrupt &&
             StateIsRunningState(m_process->GetState()))
           m_process->SendAsyncInterrupt();
         break;
@@ -1060,18 +1067,16 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
     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();
@@ -1083,19 +1088,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(

>From 9ddd179d5c6193e666071d3c0faa1b800b925533 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Tue, 20 Jan 2026 14:49:09 +0000
Subject: [PATCH 08/15] edit signature

---
 .../Plugins/Process/Windows/Common/ProcessWindows.cpp     | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index d16e84b6d307c..21909fb1b3eba 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -958,9 +958,7 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
                   IOHandler::Type::ProcessIO),
         m_process(process),
         m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false),
-        m_write_file(conpty_input) {
-    m_interrupt_event = INVALID_HANDLE_VALUE;
-  }
+        m_write_file(conpty_input), m_interrupt_event(INVALID_HANDLE_VALUE) {}
 
   ~IOHandlerProcessSTDIOWindows() override = default;
 
@@ -973,12 +971,12 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
   /// Peek the console for input. If it has any, drain the pipe until text 
input
   /// is found or the pipe is empty.
   ///
-  /// \param[in, out] hStdin
+  /// \param hStdin
   ///     The handle to the standard input's pipe.
   ///
   /// \return
   ///     true if the pipe has text input.
-  llvm::Expected<bool> ConsoleHasTextInput(HANDLE hStdin) {
+  llvm::Expected<bool> ConsoleHasTextInput(const HANDLE hStdin) {
     while (true) {
       INPUT_RECORD inputRecord;
       DWORD numRead = 0;

>From 7fbc30bae0d4a0951b89bf07d93ed715ac35b78e Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Wed, 21 Jan 2026 15:49:16 +0000
Subject: [PATCH 09/15] fix stdin

---
 .../Host/windows/ProcessLauncherWindows.cpp   |  7 ++---
 .../Process/Windows/Common/ProcessWindows.cpp | 29 ++++++++++++++++---
 2 files changed, 28 insertions(+), 8 deletions(-)

diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp 
b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
index 76feceadf46f3..a8b27e6a30528 100644
--- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp
+++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
@@ -116,9 +116,8 @@ ProcessLauncherWindows::LaunchProcess(const 
ProcessLaunchInfo &launch_info,
   startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
 
   HPCON hPC = launch_info.GetPTY().GetPseudoTerminalHandle();
-  bool use_pty = hPC != INVALID_HANDLE_VALUE &&
-                 launch_info.GetNumFileActions() == 0 &&
-                 launch_info.GetFlags().Test(lldb::eLaunchFlagLaunchInTTY);
+  bool use_pty =
+      hPC != INVALID_HANDLE_VALUE && launch_info.GetNumFileActions() == 0;
 
   HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO);
   HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO);
@@ -199,7 +198,7 @@ ProcessLauncherWindows::LaunchProcess(const 
ProcessLaunchInfo &launch_info,
 
   BOOL result = ::CreateProcessW(
       wexecutable.c_str(), pwcommandLine, NULL, NULL,
-      /*bInheritHandles=*/!inherited_handles.empty(), flags, 
environment.data(),
+      /*bInheritHandles=*/TRUE, 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 21909fb1b3eba..e72ef1da4b620 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -958,9 +958,15 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
                   IOHandler::Type::ProcessIO),
         m_process(process),
         m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false),
-        m_write_file(conpty_input), m_interrupt_event(INVALID_HANDLE_VALUE) {}
-
-  ~IOHandlerProcessSTDIOWindows() override = default;
+        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);
+  }
 
   void SetIsRunning(bool running) {
     std::lock_guard<std::mutex> guard(m_mutex);
@@ -977,6 +983,14 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
   /// \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;
@@ -1010,6 +1024,9 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
 
     DWORD consoleMode;
     bool isConsole = GetConsoleMode(hStdin, &consoleMode) != 0;
+    DWORD oldConsoleMode = consoleMode;
+    SetConsoleMode(hStdin,
+                   consoleMode & ~ENABLE_LINE_INPUT & ~ENABLE_ECHO_INPUT);
 
     while (true) {
       {
@@ -1025,8 +1042,10 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
       case WAIT_OBJECT_0: {
         if (isConsole) {
           auto hasInputOrErr = ConsoleHasTextInput(hStdin);
-          if (hasInputOrErr.takeError())
+          if (!hasInputOrErr) {
+            llvm::consumeError(hasInputOrErr.takeError());
             goto exit_loop;
+          }
 
           // If no text input is ready, go back to waiting.
           if (!*hasInputOrErr)
@@ -1059,6 +1078,8 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
 
   exit_loop:;
     SetIsRunning(false);
+    SetIsDone(true);
+    SetConsoleMode(hStdin, oldConsoleMode);
   }
 
   void Cancel() override {

>From ad5d8e4214d6d584ee87e7f9382e53eab4190ea0 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Thu, 22 Jan 2026 16:33:01 +0000
Subject: [PATCH 10/15] drain conpty escape sequences

---
 .../include/lldb/Host/windows/PseudoConsole.h | 10 ++++
 lldb/source/Host/windows/PseudoConsole.cpp    | 50 +++++++++++++++++++
 2 files changed, 60 insertions(+)

diff --git a/lldb/include/lldb/Host/windows/PseudoConsole.h 
b/lldb/include/lldb/Host/windows/PseudoConsole.h
index 039783985c025..7a9829e23a097 100644
--- a/lldb/include/lldb/Host/windows/PseudoConsole.h
+++ b/lldb/include/lldb/Host/windows/PseudoConsole.h
@@ -61,6 +61,16 @@ class PseudoConsole {
   ///     invalid.
   HANDLE GetSTDINHandle() const { return m_conpty_input; };
 
+  /// Drains initialization sequences from the ConPTY output pipe.
+  ///
+  /// When a process first attaches to a ConPTY, Windows emits VT100/ANSI 
escape
+  /// sequences (ESC[2J for clear screen, ESC[H for cursor home and more) as
+  /// part of the PseudoConsole initialization. To prevent these sequences from
+  /// appearing in the debugger output (and flushing lldb's shell for instance)
+  /// we launch a short-lived dummy process that triggers the initialization,
+  /// then drain all output before launching the actual debuggee.
+  llvm::Error DrainInitSequences();
+
 protected:
   HANDLE m_conpty_handle = ((HANDLE)(long long)-1);
   HANDLE m_conpty_output = ((HANDLE)(long long)-1);
diff --git a/lldb/source/Host/windows/PseudoConsole.cpp 
b/lldb/source/Host/windows/PseudoConsole.cpp
index b3314fd58bbb8..b10e702a915a2 100644
--- a/lldb/source/Host/windows/PseudoConsole.cpp
+++ b/lldb/source/Host/windows/PseudoConsole.cpp
@@ -11,6 +11,7 @@
 #include <mutex>
 
 #include "lldb/Host/windows/PipeWindows.h"
+#include "lldb/Host/windows/ProcessLauncherWindows.h"
 #include "lldb/Host/windows/windows.h"
 #include "lldb/Utility/LLDBLog.h"
 
@@ -118,6 +119,9 @@ llvm::Error PseudoConsole::OpenPseudoConsole() {
   m_conpty_output = hOutputRead;
   m_conpty_input = hInputWrite;
 
+  if (auto err = DrainInitSequences())
+    return err;
+
   return llvm::Error::success();
 }
 
@@ -133,3 +137,49 @@ void PseudoConsole::Close() {
   m_conpty_input = INVALID_HANDLE_VALUE;
   m_conpty_output = INVALID_HANDLE_VALUE;
 }
+
+llvm::Error PseudoConsole::DrainInitSequences() {
+  STARTUPINFOEXW startupinfoex = {};
+  startupinfoex.StartupInfo.cb = sizeof(STARTUPINFOEXW);
+  startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+  auto attributelist_or_err = ProcThreadAttributeList::Create(startupinfoex);
+  if (!attributelist_or_err)
+    return llvm::errorCodeToError(attributelist_or_err.getError());
+
+  PROCESS_INFORMATION pi = {};
+  wchar_t cmdline[] = L"echo foo";
+
+  if (!CreateProcessW(/*lpApplicationName=*/NULL, cmdline,
+                      /*lpProcessAttributes=*/NULL, 
/*lpThreadAttributes=*/NULL,
+                      /*bInheritHandles=*/TRUE,
+                      /*dwCreationFlags=*/EXTENDED_STARTUPINFO_PRESENT |
+                          CREATE_UNICODE_ENVIRONMENT,
+                      /*lpEnvironment=*/NULL, /*lpCurrentDirectory=*/NULL,
+                      /*lpStartupInfo=*/
+                      reinterpret_cast<STARTUPINFOW *>(&startupinfoex),
+                      /*lpProcessInformation=*/&pi))
+    return llvm::errorCodeToError(
+        std::error_code(GetLastError(), std::system_category()));
+
+  char buf[4096];
+  OVERLAPPED ov = {};
+  ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+  DWORD read;
+  ReadFile(m_conpty_output, buf, sizeof(buf), &read, &ov);
+
+  WaitForSingleObject(pi.hProcess, INFINITE);
+
+  if (GetOverlappedResult(m_conpty_output, &ov, &read, FALSE) && read > 0) {
+    ResetEvent(ov.hEvent);
+    ReadFile(m_conpty_output, buf, sizeof(buf), &read, &ov);
+  }
+
+  CancelIo(m_conpty_output);
+  CloseHandle(ov.hEvent);
+  CloseHandle(pi.hProcess);
+  CloseHandle(pi.hThread);
+
+  return llvm::Error::success();
+}
\ No newline at end of file

>From 37b6bc3603ade31d299522623f6568293c63bf84 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Thu, 22 Jan 2026 16:33:22 +0000
Subject: [PATCH 11/15] fix tests

---
 lldb/test/Shell/Settings/TestFrameFormatColor.test   | 2 +-
 lldb/test/Shell/Settings/TestFrameFormatNoColor.test | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

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}}

>From 01a9e36dd61a6cbbe275268eab587b39e5fbba19 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Thu, 22 Jan 2026 16:37:44 +0000
Subject: [PATCH 12/15] add comment

---
 lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index e72ef1da4b620..7a9aea38876b6 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -1024,6 +1024,10 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
 
     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);

>From ad5794a0e6257f83dd63981db214bd52c04bf584 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Thu, 22 Jan 2026 17:54:35 +0000
Subject: [PATCH 13/15] setup pseudoconsole

---
 .../lldb/Host/windows/ProcessLauncherWindows.h  |  9 ++++++++-
 .../Host/windows/ProcessLauncherWindows.cpp     | 17 +++++++++++++----
 lldb/source/Host/windows/PseudoConsole.cpp      |  5 ++++-
 3 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h 
b/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h
index b835885dd47c6..7f99a46447966 100644
--- a/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h
+++ b/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h
@@ -11,7 +11,7 @@
 
 #include "lldb/Host/ProcessLauncher.h"
 #include "lldb/Host/windows/windows.h"
-#include "llvm/Support/ErrorOr.h"
+#include "llvm/Support/Error.h"
 
 namespace lldb_private {
 
@@ -38,6 +38,13 @@ class ProcThreadAttributeList {
   static llvm::ErrorOr<ProcThreadAttributeList>
   Create(STARTUPINFOEXW &startupinfoex);
 
+  /// Setup the PseudoConsole handle in the underlying
+  /// LPPROC_THREAD_ATTRIBUTE_LIST.
+  ///
+  /// \param hPC
+  ///     The handle to the PseudoConsole.
+  llvm::Error SetupPseudoConsole(HPCON hPC);
+
   ~ProcThreadAttributeList() {
     if (lpAttributeList) {
       DeleteProcThreadAttributeList(lpAttributeList);
diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp 
b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
index a8b27e6a30528..907f0fc86bd89 100644
--- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp
+++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
@@ -106,6 +106,15 @@ ProcThreadAttributeList::Create(STARTUPINFOEXW 
&startupinfoex) {
   return ProcThreadAttributeList(startupinfoex.lpAttributeList);
 }
 
+llvm::Error ProcThreadAttributeList::SetupPseudoConsole(HPCON hPC) {
+  BOOL ok = UpdateProcThreadAttribute(lpAttributeList, 0,
+                                      PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, hPC,
+                                      sizeof(hPC), NULL, NULL);
+  if (!ok)
+    return llvm::errorCodeToError(llvm::mapWindowsError(GetLastError()));
+  return llvm::Error::success();
+}
+
 HostProcess
 ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
                                       Status &error) {
@@ -136,15 +145,15 @@ ProcessLauncherWindows::LaunchProcess(const 
ProcessLaunchInfo &launch_info,
     error = attributelist_or_err.getError();
     return HostProcess();
   }
+  ProcThreadAttributeList attributelist = std::move(*attributelist_or_err);
+
   llvm::scope_exit delete_attributelist(
       [&] { DeleteProcThreadAttributeList(startupinfoex.lpAttributeList); });
 
   std::vector<HANDLE> inherited_handles;
   if (use_pty) {
-    if (!UpdateProcThreadAttribute(startupinfoex.lpAttributeList, 0,
-                                   PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, hPC,
-                                   sizeof(hPC), NULL, NULL)) {
-      error = Status(::GetLastError(), eErrorTypeWin32);
+    if (auto err = attributelist.SetupPseudoConsole(hPC)) {
+      error = Status::FromError(std::move(err));
       return HostProcess();
     }
   } else {
diff --git a/lldb/source/Host/windows/PseudoConsole.cpp 
b/lldb/source/Host/windows/PseudoConsole.cpp
index b10e702a915a2..516015f7eab10 100644
--- a/lldb/source/Host/windows/PseudoConsole.cpp
+++ b/lldb/source/Host/windows/PseudoConsole.cpp
@@ -146,9 +146,12 @@ llvm::Error PseudoConsole::DrainInitSequences() {
   auto attributelist_or_err = ProcThreadAttributeList::Create(startupinfoex);
   if (!attributelist_or_err)
     return llvm::errorCodeToError(attributelist_or_err.getError());
+  ProcThreadAttributeList attributelist = std::move(*attributelist_or_err);
+  if (auto error = attributelist.SetupPseudoConsole(m_conpty_handle))
+    return error;
 
   PROCESS_INFORMATION pi = {};
-  wchar_t cmdline[] = L"echo foo";
+  wchar_t cmdline[] = L"cmd.exe /c 'echo foo && exit'";
 
   if (!CreateProcessW(/*lpApplicationName=*/NULL, cmdline,
                       /*lpProcessAttributes=*/NULL, 
/*lpThreadAttributes=*/NULL,

>From fcb68b5222471a4518b7fac946fdaab0180d1e00 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Thu, 22 Jan 2026 19:43:10 +0000
Subject: [PATCH 14/15] use COMSPEC

---
 lldb/source/Host/windows/PseudoConsole.cpp | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/lldb/source/Host/windows/PseudoConsole.cpp 
b/lldb/source/Host/windows/PseudoConsole.cpp
index 516015f7eab10..793e39114e53b 100644
--- a/lldb/source/Host/windows/PseudoConsole.cpp
+++ b/lldb/source/Host/windows/PseudoConsole.cpp
@@ -119,8 +119,10 @@ llvm::Error PseudoConsole::OpenPseudoConsole() {
   m_conpty_output = hOutputRead;
   m_conpty_input = hInputWrite;
 
-  if (auto err = DrainInitSequences())
-    return err;
+  if (auto error = DrainInitSequences()) {
+    Log *log = GetLog(LLDBLog::Host);
+    LLDB_LOG(log, "error: {0}", error);
+  }
 
   return llvm::Error::success();
 }
@@ -151,9 +153,19 @@ llvm::Error PseudoConsole::DrainInitSequences() {
     return error;
 
   PROCESS_INFORMATION pi = {};
-  wchar_t cmdline[] = L"cmd.exe /c 'echo foo && exit'";
 
-  if (!CreateProcessW(/*lpApplicationName=*/NULL, cmdline,
+  wchar_t comspec[MAX_PATH];
+  DWORD comspecLen = GetEnvironmentVariableW(L"COMSPEC", comspec, MAX_PATH);
+  if (comspecLen == 0 || comspecLen >= MAX_PATH)
+    return llvm::createStringError(
+        std::error_code(GetLastError(), std::system_category()),
+        "Failed to get the 'COMSPEC' environment variable");
+
+  std::wstring cmdline_str = std::wstring(comspec) + L" /c 'echo foo && exit'";
+  std::vector<wchar_t> cmdline(cmdline_str.begin(), cmdline_str.end());
+  cmdline.push_back(L'\0');
+
+  if (!CreateProcessW(/*lpApplicationName=*/comspec, cmdline.data(),
                       /*lpProcessAttributes=*/NULL, 
/*lpThreadAttributes=*/NULL,
                       /*bInheritHandles=*/TRUE,
                       /*dwCreationFlags=*/EXTENDED_STARTUPINFO_PRESENT |
@@ -185,4 +197,4 @@ llvm::Error PseudoConsole::DrainInitSequences() {
   CloseHandle(pi.hThread);
 
   return llvm::Error::success();
-}
\ No newline at end of file
+}

>From db68e85c82ecfe525d3764f2a9746bba66339f08 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Fri, 23 Jan 2026 12:27:01 +0000
Subject: [PATCH 15/15] switch to using LLDB_LOG_ERROR

---
 lldb/source/Host/windows/PseudoConsole.cpp                    | 3 ++-
 lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp | 4 +++-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/lldb/source/Host/windows/PseudoConsole.cpp 
b/lldb/source/Host/windows/PseudoConsole.cpp
index 793e39114e53b..413c8a3328445 100644
--- a/lldb/source/Host/windows/PseudoConsole.cpp
+++ b/lldb/source/Host/windows/PseudoConsole.cpp
@@ -121,7 +121,8 @@ llvm::Error PseudoConsole::OpenPseudoConsole() {
 
   if (auto error = DrainInitSequences()) {
     Log *log = GetLog(LLDBLog::Host);
-    LLDB_LOG(log, "error: {0}", error);
+    LLDB_LOG_ERROR(log, std::move(error),
+                   "failed to finalize ConPTY's setup: {0}");
   }
 
   return llvm::Error::success();
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 7a9aea38876b6..373729c952071 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -1047,7 +1047,9 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
         if (isConsole) {
           auto hasInputOrErr = ConsoleHasTextInput(hStdin);
           if (!hasInputOrErr) {
-            llvm::consumeError(hasInputOrErr.takeError());
+            Log *log = GetLog(WindowsLog::Process);
+            LLDB_LOG_ERROR(log, hasInputOrErr.takeError(),
+                           "failed to process debuggee's IO: {0}");
             goto exit_loop;
           }
 

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

Reply via email to