[Lldb-commits] [lldb] [lldb][windows] add STDIN and STDOUT forwarding support (PR #175812)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Thu Jan 22 08:33:42 PST 2026
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 <c_zablit at apple.com>
Date: Tue, 13 Jan 2026 18:54:43 +0000
Subject: [PATCH 01/11] [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 <c_zablit at apple.com>
Date: Mon, 19 Jan 2026 13:50:09 +0000
Subject: [PATCH 02/11] 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 <c_zablit at apple.com>
Date: Mon, 19 Jan 2026 13:57:50 +0000
Subject: [PATCH 03/11] 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 <c_zablit at apple.com>
Date: Mon, 19 Jan 2026 15:50:09 +0000
Subject: [PATCH 04/11] 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 <c_zablit at apple.com>
Date: Mon, 19 Jan 2026 17:32:34 +0000
Subject: [PATCH 05/11] 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 <c_zablit at apple.com>
Date: Tue, 20 Jan 2026 11:15:27 +0000
Subject: [PATCH 06/11] 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 <c_zablit at apple.com>
Date: Tue, 20 Jan 2026 14:33:32 +0000
Subject: [PATCH 07/11] 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 <c_zablit at apple.com>
Date: Tue, 20 Jan 2026 14:49:09 +0000
Subject: [PATCH 08/11] 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 <c_zablit at apple.com>
Date: Wed, 21 Jan 2026 15:49:16 +0000
Subject: [PATCH 09/11] 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 <c_zablit at apple.com>
Date: Thu, 22 Jan 2026 16:33:01 +0000
Subject: [PATCH 10/11] 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 <c_zablit at apple.com>
Date: Thu, 22 Jan 2026 16:33:22 +0000
Subject: [PATCH 11/11] 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}}
More information about the lldb-commits
mailing list