https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/201638
>From 35d4db93b51d6eeb4c1f0c8c6c5bb27afc6adba4 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 4 Jun 2026 18:09:30 +0100 Subject: [PATCH 1/3] [lldb][Windows] add stdin support to lldb-server --- .../lldb/Host/common/NativeProcessProtocol.h | 6 +- .../Host/windows/ConnectionConPTYWindows.cpp | 27 ++- .../Plugins/Process/Utility/CMakeLists.txt | 1 + .../Utility/IOHandlerProcessSTDIOWindows.cpp | 172 ++++++++++++++++ .../Utility/IOHandlerProcessSTDIOWindows.h | 63 ++++++ .../Windows/Common/NativeProcessWindows.cpp | 15 ++ .../Windows/Common/NativeProcessWindows.h | 7 + .../Process/Windows/Common/ProcessWindows.cpp | 190 +----------------- .../Process/Windows/Common/ProcessWindows.h | 2 + .../GDBRemoteCommunicationServerLLGS.cpp | 9 +- .../Process/gdb-remote/ProcessGDBRemote.cpp | 16 +- 11 files changed, 325 insertions(+), 183 deletions(-) create mode 100644 lldb/source/Plugins/Process/Utility/IOHandlerProcessSTDIOWindows.cpp create mode 100644 lldb/source/Plugins/Process/Utility/IOHandlerProcessSTDIOWindows.h diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h index 4e4804623b1d8..435185a38f3f9 100644 --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -247,8 +247,12 @@ class NativeProcessProtocol { // Access to inferior stdio virtual int GetTerminalFileDescriptor() { return m_terminal_fd; } - // Stop id interface + /// Write up to \p len bytes from \p buf to the inferior's stdin. + virtual size_t WriteStdin(const void *buf, size_t len, Status &error) { + return 0; + } + // Stop id interface uint32_t GetStopID() const; // Callbacks for low-level process state changes diff --git a/lldb/source/Host/windows/ConnectionConPTYWindows.cpp b/lldb/source/Host/windows/ConnectionConPTYWindows.cpp index e9d026fc7dc9f..f7e90421653e3 100644 --- a/lldb/source/Host/windows/ConnectionConPTYWindows.cpp +++ b/lldb/source/Host/windows/ConnectionConPTYWindows.cpp @@ -61,5 +61,30 @@ size_t ConnectionConPTY::Read(void *dst, size_t dst_len, size_t ConnectionConPTY::Write(const void *src, size_t src_len, lldb::ConnectionStatus &status, Status *error_ptr) { - llvm_unreachable("not implemented"); + if (!m_pty || !m_pty->IsConnected()) { + status = eConnectionStatusNoConnection; + if (error_ptr) + *error_ptr = Status::FromErrorString("ConPTY not connected"); + return 0; + } + HANDLE stdin_handle = m_pty->GetSTDINHandle(); + if (stdin_handle == INVALID_HANDLE_VALUE || stdin_handle == nullptr) { + status = eConnectionStatusNoConnection; + if (error_ptr) + *error_ptr = Status::FromErrorString("ConPTY STDIN handle is invalid"); + return 0; + } + DWORD written = 0; + if (!::WriteFile(stdin_handle, src, static_cast<DWORD>(src_len), &written, + nullptr)) { + DWORD err = ::GetLastError(); + status = (err == ERROR_BROKEN_PIPE || err == ERROR_NO_DATA) + ? eConnectionStatusEndOfFile + : eConnectionStatusError; + if (error_ptr) + *error_ptr = Status(err, lldb::eErrorTypeWin32); + return written; + } + status = eConnectionStatusSuccess; + return written; } diff --git a/lldb/source/Plugins/Process/Utility/CMakeLists.txt b/lldb/source/Plugins/Process/Utility/CMakeLists.txt index 3652a0cfb530f..4206b398d982f 100644 --- a/lldb/source/Plugins/Process/Utility/CMakeLists.txt +++ b/lldb/source/Plugins/Process/Utility/CMakeLists.txt @@ -4,6 +4,7 @@ set_property(DIRECTORY PROPERTY LLDB_PLUGIN_KIND ProcessUtility) add_lldb_library(lldbPluginProcessUtility AuxVector.cpp FreeBSDSignals.cpp + IOHandlerProcessSTDIOWindows.cpp GDBRemoteSignals.cpp HistoryThread.cpp HistoryUnwind.cpp diff --git a/lldb/source/Plugins/Process/Utility/IOHandlerProcessSTDIOWindows.cpp b/lldb/source/Plugins/Process/Utility/IOHandlerProcessSTDIOWindows.cpp new file mode 100644 index 0000000000000..f8f5435fdbe07 --- /dev/null +++ b/lldb/source/Plugins/Process/Utility/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; +} \ No newline at end of file diff --git a/lldb/source/Plugins/Process/Utility/IOHandlerProcessSTDIOWindows.h b/lldb/source/Plugins/Process/Utility/IOHandlerProcessSTDIOWindows.h new file mode 100644 index 0000000000000..fcbd587b40143 --- /dev/null +++ b/lldb/source/Plugins/Process/Utility/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" + +typedef void *HANDLE; + +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/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp index b235ab281bad6..bf447c9ea81f5 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp @@ -749,4 +749,19 @@ void NativeProcessWindows::STDIOReadThreadBytesReceived(void *baton, self->m_delegate.NewProcessOutput( self, llvm::StringRef(static_cast<const char *>(src), src_len)); } + +size_t NativeProcessWindows::WriteStdin(const void *buf, size_t len, + Status &error) { + if (!m_stdio_communication.HasConnection()) { + error = Status::FromErrorString( + "no ConPTY connection on this NativeProcessWindows"); + return 0; + } + ConnectionStatus status; + size_t written = m_stdio_communication.Write(buf, len, status, &error); + if (status != eConnectionStatusSuccess && error.Success()) + error = Status::FromErrorStringWithFormatv( + "ConPTY stdin write returned status {0}", static_cast<int>(status)); + return written; +} } // namespace lldb_private diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h index 95b85754ebdeb..cac3920d82aa9 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h @@ -107,6 +107,13 @@ class NativeProcessWindows : public NativeProcessProtocol, bool HasPendingLibraryEvents() override; + /// Forward bytes from the gdb-remote `I` packet into the inferior's + /// ConPTY-backed stdin via `m_stdio_communication.Write` → + /// `ConnectionConPTY::Write` → `WriteFile` on the parent-side STDIN + /// HANDLE. Returns the number of bytes written (0 if the PTY is + /// disconnected or write fails). + size_t WriteStdin(const void *buf, size_t len, Status &error) override; + // ProcessDebugger Overrides void OnExitProcess(uint32_t exit_code) override; void OnDebuggerConnected(lldb::addr_t image_base) override; diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp index 9a273463792ce..83d9e784f75a0 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp @@ -45,6 +45,7 @@ #include "ExceptionRecord.h" #include "ForwardDecl.h" #include "LocalDebugDelegate.h" +#include "Plugins/Process/Utility/IOHandlerProcessSTDIOWindows.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..38c47a95e6cbf 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); + ProcessWindows(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp); Status DoGetMemoryRegionInfo(lldb::addr_t vm_addr, diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp index d56026e011564..077144f94227d 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -2569,12 +2569,19 @@ GDBRemoteCommunicationServerLLGS::Handle_I(StringExtractorGDBRemote &packet) { // write directly to stdin *this might block if stdin buffer is full* // TODO: enqueue this block in circular buffer and send window size to // remote host - ConnectionStatus status; Status error; + +#if defined(_WIN32) + // On Windows the inferior's stdio is owned by NativeProcessWindows. + if (m_current_process->WriteStdin(tmp, read, error) != read || error.Fail()) + return SendErrorResponse(0x15); +#else + ConnectionStatus status; m_stdio_communication.WriteAll(tmp, read, status, &error); if (error.Fail()) { return SendErrorResponse(0x15); } +#endif } return SendOKResponse(); diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index f6eaf5851338b..c6573499ff3d6 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -95,6 +95,10 @@ #include "llvm/Support/Threading.h" #include "llvm/Support/raw_ostream.h" +#ifdef _WIN32 +#include "Plugins/Process/Utility/IOHandlerProcessSTDIOWindows.h" +#endif + #if defined(__APPLE__) #define DEBUGSERVER_BASENAME "debugserver" #elif defined(_WIN32) @@ -873,8 +877,18 @@ Status ProcessGDBRemote::DoLaunch(lldb_private::Module *exe_module, SetPrivateState(SetThreadStopInfo(response)); if (!disable_stdio) { - if (pty.GetPrimaryFileDescriptor() != PseudoTerminal::invalid_fd) + if (pty.GetPrimaryFileDescriptor() != PseudoTerminal::invalid_fd) { SetSTDIOFileDescriptor(pty.ReleasePrimaryFileDescriptor()); + } +#ifdef _WIN32 + else if (m_stdin_forward) { + // No client-side PTY FD on Windows. + 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); + } +#endif } } } else { >From 24cdb004703724e62e4960937eca0bd4c7e3dda8 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 4 Jun 2026 18:10:57 +0100 Subject: [PATCH 2/3] [lldb][gdb-remote] Plumb interrupt_timeout through SendStdinNotification --- .../Process/gdb-remote/GDBRemoteCommunicationClient.cpp | 7 ++++--- .../Process/gdb-remote/GDBRemoteCommunicationClient.h | 9 ++++++++- .../Plugins/Process/gdb-remote/ProcessGDBRemote.cpp | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp index 8df7936786b04..3a69a1d21f906 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -1442,13 +1442,14 @@ bool GDBRemoteCommunicationClient::GetHostInfo(bool force) { return m_qHostInfo_is_valid == eLazyBoolYes; } -int GDBRemoteCommunicationClient::SendStdinNotification(const char *data, - size_t data_len) { +int GDBRemoteCommunicationClient::SendStdinNotification( + const char *data, size_t data_len, std::chrono::seconds interrupt_timeout) { StreamString packet; packet.PutCString("I"); packet.PutBytesAsRawHex8(data, data_len); StringExtractorGDBRemote response; - if (SendPacketAndWaitForResponse(packet.GetString(), response) == + if (SendPacketAndWaitForResponse(packet.GetString(), response, + interrupt_timeout) == PacketResult::Success) { return 0; } diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h index 79ca0bcd3ed22..384a7da4d06e4 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -127,10 +127,17 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase { /// \param[in] data_len /// The number of bytes available at \a data. /// + /// \param[in] interrupt_timeout + /// If the inferior is running, how long to wait for a `\x03` BREAK + /// to interrupt it before giving up. Pass zero (the default) only + /// when the caller knows the inferior is stopped. + /// /// \return /// Zero if the attach was successful, or an error indicating /// an error code. - int SendStdinNotification(const char *data, size_t data_len); + int SendStdinNotification( + const char *data, size_t data_len, + std::chrono::seconds interrupt_timeout = std::chrono::seconds(0)); /// Sets the path to use for stdin/out/err for a process /// that will be launched with the 'A' packet. diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index c6573499ff3d6..0319c3606362a 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -3352,7 +3352,7 @@ size_t ProcessGDBRemote::PutSTDIN(const char *src, size_t src_len, ConnectionStatus status; m_stdio_communication.WriteAll(src, src_len, status, nullptr); } else if (m_stdin_forward) { - m_gdb_comm.SendStdinNotification(src, src_len); + m_gdb_comm.SendStdinNotification(src, src_len, GetInterruptTimeout()); } return 0; } >From 540a72bc597eca7827c48522e8e67cec3b0b46a5 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 4 Jun 2026 18:12:21 +0100 Subject: [PATCH 3/3] [lldb][Windows] Surface DebugBreakProcess Halt() as a SIGSTOP signal stop --- .../Windows/Common/NativeProcessWindows.cpp | 36 +++++++++++++++++-- .../Windows/Common/NativeProcessWindows.h | 3 ++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp index bf447c9ea81f5..f0dc855457ac9 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp @@ -173,8 +173,13 @@ NativeProcessWindows::GetThreadByID(lldb::tid_t thread_id) { Status NativeProcessWindows::Halt() { bool caused_stop = false; StateType state = GetState(); - if (state != eStateStopped) - return HaltProcess(caused_stop); + if (state != eStateStopped) { + m_pending_halt = true; + Status err = HaltProcess(caused_stop); + if (err.Fail() || !caused_stop) + m_pending_halt = false; + return err; + } return Status(); } @@ -581,6 +586,33 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) { // Any remaining STATUS_BREAKPOINT is a breakpoint instruction in the // program's own code (e.g. `__debugbreak()` or `__builtin_debugtrap()`). // Stop the debugger and let the user decide what to do. + if (m_pending_halt) { + LLDB_LOG(log, + "DebugBreakProcess injection treated as Halt SIGSTOP for tid " + "{0:x}", + thread_id); + m_pending_halt = false; + ThreadStopInfo signal_info; + signal_info.reason = StopReason::eStopReasonSignal; + signal_info.signo = 19; // SIGSTOP on POSIX + + // Halt all threads at the kernel level. + for (uint32_t i = 0; i < m_threads.size(); ++i) { + auto t = static_cast<NativeThreadWindows *>(m_threads[i].get()); + if (t->DoStop().Fail()) + exit(1); + } + if (!m_threads.empty()) { + auto first = static_cast<NativeThreadWindows *>(m_threads[0].get()); + first->SetStopReason(signal_info, "interrupt"); + } + SetCurrentThreadID(thread_id); + if (NativeThreadWindows *injected = GetThreadByID(thread_id)) + injected->SetStopReason(signal_info, "interrupt"); + SetState(eStateStopped, true); + return ExceptionResult::BreakInDebugger; + } + std::string desc = formatv("Exception {0:x8} encountered at address {1:x8}", record.GetExceptionCode(), exception_addr) .str(); diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h index cac3920d82aa9..d1c57fc01e06b 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h @@ -167,6 +167,9 @@ class NativeProcessWindows : public NativeProcessProtocol, /// launch / attach. bool m_initial_stop_seen = false; + /// Set when Halt() / Interrupt() schedules a DebugBreakProcess injection. + bool m_pending_halt = false; + /// PseudoConsole for the lldb-server stdio-forwarding path. std::shared_ptr<PseudoConsole> m_pty; _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
