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 1/2] [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}} >From 504d9196f1aff2fccd14c866f307a4bf03cff1e4 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Wed, 11 Feb 2026 17:08:46 +0100 Subject: [PATCH 2/2] remove test change --- 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 f30dafadf5919..970d7238e7512 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: {{\[[0-9;]+m}} +# CHECK: diff --git a/lldb/test/Shell/Settings/TestFrameFormatNoColor.test b/lldb/test/Shell/Settings/TestFrameFormatNoColor.test index 37906311c4f69..2bcdb8e82bd9d 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: {{\[[0-9;]+m}} +# CHECK-NOT: _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
