Author: Charles Zablit Date: 2026-06-05T17:10:25+01:00 New Revision: c4f2f2535438cbdf23d9f84689ea0f518ffa4a95
URL: https://github.com/llvm/llvm-project/commit/c4f2f2535438cbdf23d9f84689ea0f518ffa4a95 DIFF: https://github.com/llvm/llvm-project/commit/c4f2f2535438cbdf23d9f84689ea0f518ffa4a95.diff LOG: [NFC][lldb][Windows] extract IOHandlerProcessSTDIOWindows (#201651) Co-authored-by: Nerixyz <[email protected]> Added: lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.cpp lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.h Modified: lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h Removed: ################################################################################ diff --git a/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt b/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt index 9854b79fbb8d6..bd076d5d64df1 100644 --- a/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt +++ b/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt @@ -1,5 +1,6 @@ add_lldb_library(lldbPluginProcessWindowsCommon PLUGIN + IOHandlerProcessSTDIOWindows.cpp DebuggerThread.cpp LocalDebugDelegate.cpp NativeProcessWindows.cpp diff --git a/lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.cpp new file mode 100644 index 0000000000000..2b9ab9ce0812b --- /dev/null +++ b/lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.cpp @@ -0,0 +1,172 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "IOHandlerProcessSTDIOWindows.h" + +#include "lldb/Host/windows/windows.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/State.h" + +using namespace lldb_private; + +IOHandlerProcessSTDIOWindows::IOHandlerProcessSTDIOWindows(Process *process) + : IOHandler(process->GetTarget().GetDebugger(), IOHandler::Type::ProcessIO), + m_process(process), + m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false), + m_interrupt_event( + CreateEvent(/*lpEventAttributes=*/nullptr, /*bManualReset=*/FALSE, + /*bInitialState=*/FALSE, /*lpName=*/nullptr)) {} + +IOHandlerProcessSTDIOWindows::~IOHandlerProcessSTDIOWindows() { + if (m_interrupt_event != INVALID_HANDLE_VALUE) + ::CloseHandle(m_interrupt_event); +} + +void IOHandlerProcessSTDIOWindows::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> +IOHandlerProcessSTDIOWindows::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, nullptr, 0, nullptr, &bytesAvailable, nullptr)) { + 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 IOHandlerProcessSTDIOWindows::Run() { + if (!m_read_file.IsValid()) { + SetIsDone(true); + return; + } + + SetIsDone(false); + SetIsRunning(true); + + 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) { + { + std::lock_guard<std::mutex> guard(m_mutex); + if (GetIsDone()) + goto exit_loop; + } + + DWORD result = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE); + switch (result) { + case WAIT_FAILED: + goto exit_loop; + case WAIT_OBJECT_0: { + if (isConsole) { + auto hasInputOrErr = ConsoleHasTextInput(hStdin); + if (!hasInputOrErr) { + Log *log = GetLog(LLDBLog::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) + goto exit_loop; + + Status err; + m_process->PutSTDIN(&ch, 1, err); + if (err.Fail()) + goto exit_loop; + break; + } + case WAIT_OBJECT_0 + 1: { + ControlOp op = m_pending_op.exchange(eControlOpNone); + if (op == eControlOpQuit) + goto exit_loop; + if (op == eControlOpInterrupt && + StateIsRunningState(m_process->GetState())) + m_process->SendAsyncInterrupt(); + break; + } + default: + goto exit_loop; + } + } + +exit_loop:; + SetIsRunning(false); + SetIsDone(true); + SetConsoleMode(hStdin, oldConsoleMode); +} + +void IOHandlerProcessSTDIOWindows::Cancel() { + std::lock_guard<std::mutex> guard(m_mutex); + SetIsDone(true); + if (m_is_running) { + m_pending_op.store(eControlOpQuit); + ::SetEvent(m_interrupt_event); + } +} + +bool IOHandlerProcessSTDIOWindows::Interrupt() { + if (m_active) { + m_pending_op.store(eControlOpInterrupt); + ::SetEvent(m_interrupt_event); + return true; + } + if (StateIsRunningState(m_process->GetState())) { + m_process->SendAsyncInterrupt(); + return true; + } + return false; +} diff --git a/lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.h b/lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.h new file mode 100644 index 0000000000000..79a414eeda12f --- /dev/null +++ b/lldb/source/Plugins/Process/Windows/Common/IOHandlerProcessSTDIOWindows.h @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LIBLLDB_PLUGINS_PROCESS_WINDOWS_COMMON_IO_HANDLER_PROCESS_STDIO_WINDOWS_H_ +#define LIBLLDB_PLUGINS_PROCESS_WINDOWS_COMMON_IO_HANDLER_PROCESS_STDIO_WINDOWS_H_ + +#include "lldb/Core/IOHandler.h" +#include "lldb/Host/File.h" +#include "lldb/Target/Process.h" + +using HANDLE = void *; + +using namespace lldb_private; + +class IOHandlerProcessSTDIOWindows : public IOHandler { +public: + IOHandlerProcessSTDIOWindows(Process *process); + + ~IOHandlerProcessSTDIOWindows() override; + + 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. + /// + /// \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); + + void Run() override; + + void Cancel() override; + + bool Interrupt() override; + + void GotEOF() override {} + +private: + 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; + HANDLE m_interrupt_event = + reinterpret_cast<HANDLE>(static_cast<intptr_t>(-1)); + std::atomic<ControlOp> m_pending_op{eControlOpNone}; + std::mutex m_mutex; + bool m_is_running = false; +}; + +#endif // LIBLLDB_PLUGINS_PROCESS_WINDOWS_COMMON_IO_HANDLER_PROCESS_STDIO_WINDOWS_H_ diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp index 9a273463792ce..941c25e9ecb77 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp @@ -44,6 +44,7 @@ #include "DebuggerThread.h" #include "ExceptionRecord.h" #include "ForwardDecl.h" +#include "IOHandlerProcessSTDIOWindows.h" #include "LocalDebugDelegate.h" #include "ProcessWindowsLog.h" #include "TargetThreadWindows.h" @@ -1084,184 +1085,15 @@ Status ProcessWindows::DisableWatchpoint(WatchpointSP wp_sp, bool notify) { return error; } -class IOHandlerProcessSTDIOWindows : public IOHandler { -public: - IOHandlerProcessSTDIOWindows(Process *process, HANDLE conpty_input) - : IOHandler(process->GetTarget().GetDebugger(), - IOHandler::Type::ProcessIO), - m_process(process), - m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false), - m_write_file(conpty_input), - m_interrupt_event( - CreateEvent(/*lpEventAttributes=*/nullptr, /*bManualReset=*/FALSE, - /*bInitialState=*/FALSE, /*lpName=*/nullptr)) {} - - ~IOHandlerProcessSTDIOWindows() override { - if (m_interrupt_event != INVALID_HANDLE_VALUE) - ::CloseHandle(m_interrupt_event); +size_t ProcessWindows::PutSTDIN(const char *src, size_t src_len, + Status &error) { + if (!m_stdio_communication.IsConnected()) { + error = Status::FromErrorString("stdin not connected"); + return 0; } - - 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, nullptr, 0, nullptr, &bytesAvailable, nullptr)) { - 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) { - SetIsDone(true); - return; - } - - SetIsDone(false); - SetIsRunning(true); - - 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) { - { - std::lock_guard<std::mutex> guard(m_mutex); - if (GetIsDone()) - goto exit_loop; - } - - DWORD result = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE); - switch (result) { - 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) - goto exit_loop; - - DWORD written = 0; - if (!WriteFile(m_write_file, &ch, 1, &written, nullptr) || written != 1) - goto exit_loop; - break; - } - case WAIT_OBJECT_0 + 1: { - ControlOp op = m_pending_op.exchange(eControlOpNone); - if (op == eControlOpQuit) - goto exit_loop; - if (op == eControlOpInterrupt && - StateIsRunningState(m_process->GetState())) - m_process->SendAsyncInterrupt(); - break; - } - default: - goto exit_loop; - } - } - - 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) { - m_pending_op.store(eControlOpQuit); - ::SetEvent(m_interrupt_event); - } - } - - bool Interrupt() override { - if (m_active) { - m_pending_op.store(eControlOpInterrupt); - ::SetEvent(m_interrupt_event); - return true; - } - if (StateIsRunningState(m_process->GetState())) { - m_process->SendAsyncInterrupt(); - return true; - } - return false; - } - - void GotEOF() override {} - -private: - 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; -}; + ConnectionStatus status; + return m_stdio_communication.WriteAll(src, src_len, status, &error); +} void ProcessWindows::SetPseudoConsoleHandle() { if (m_pty == nullptr) @@ -1277,8 +1109,8 @@ void ProcessWindows::SetPseudoConsoleHandle() { { std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); if (!m_process_input_reader) - m_process_input_reader = std::make_shared<IOHandlerProcessSTDIOWindows>( - this, m_pty->GetSTDINHandle()); + m_process_input_reader = + std::make_shared<IOHandlerProcessSTDIOWindows>(this); } } } diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h index da35ae923dcee..677ade4971eed 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h @@ -113,6 +113,8 @@ class ProcessWindows : public Process, public ProcessDebugger { /// buffered in the ConPTY/pipe to the process's STDOUT cache. void DrainProcessStdout(); + size_t PutSTDIN(const char *src, size_t src_len, Status &error) override; + ProcessWindows(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp); Status DoGetMemoryRegionInfo(lldb::addr_t vm_addr, _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
