https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/201124
>From 17858ef458e52bc3795a4867342f7a6ce9ffe731 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Tue, 2 Jun 2026 16:10:27 +0100 Subject: [PATCH 1/4] [lldb][Windows] Forward debuggee stdout through lldb-server via ConPTY --- .../lldb/Host/common/NativeProcessProtocol.h | 5 ++ .../Windows/Common/NativeProcessWindows.cpp | 55 ++++++++++++++++++- .../Windows/Common/NativeProcessWindows.h | 21 +++++++ .../GDBRemoteCommunicationServerLLGS.cpp | 17 ++++-- .../GDBRemoteCommunicationServerLLGS.h | 5 ++ 5 files changed, 96 insertions(+), 7 deletions(-) diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h index f3c0f16502fab..4e4804623b1d8 100644 --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -266,6 +266,11 @@ class NativeProcessProtocol { virtual void NewSubprocess(NativeProcessProtocol *parent_process, std::unique_ptr<NativeProcessProtocol> child_process) = 0; + + /// Called by the platform when the inferior writes to stdout/stderr + /// through a redirected pseudoconsole that the platform owns. + virtual void NewProcessOutput(NativeProcessProtocol *process, + llvm::StringRef data) {} }; virtual Status GetLoadedModuleFileSpec(const char *module_path, diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp index 60cd4106d9db4..886515d86328b 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp @@ -17,11 +17,14 @@ #include "lldb/Host/ProcessLaunchInfo.h" #include "lldb/Host/PseudoTerminal.h" #include "lldb/Host/windows/AutoHandle.h" +#include "lldb/Host/windows/ConnectionConPTYWindows.h" #include "lldb/Host/windows/HostThreadWindows.h" #include "lldb/Host/windows/ProcessLauncherWindows.h" +#include "lldb/Host/windows/PseudoConsole.h" #include "lldb/Target/MemoryRegionInfo.h" #include "lldb/Target/Process.h" #include "lldb/Utility/State.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" @@ -52,7 +55,8 @@ NativeProcessWindows::NativeProcessWindows(ProcessLaunchInfo &launch_info, LLDB_INVALID_PROCESS_ID, PseudoTerminal::invalid_fd, // TODO: Implement on Windows delegate), - ProcessDebugger(), m_arch(launch_info.GetArchitecture()) { + ProcessDebugger(), m_arch(launch_info.GetArchitecture()), + m_stdio_communication("lldb.NativeProcessWindows.stdio") { ErrorAsOutParameter EOut(&E); DebugDelegateSP delegate_sp(new NativeDebugDelegate(*this)); E = LaunchProcess(launch_info, delegate_sp).ToError(); @@ -60,12 +64,16 @@ NativeProcessWindows::NativeProcessWindows(ProcessLaunchInfo &launch_info, return; SetID(GetDebuggedProcessId()); + + m_pty = launch_info.TakePTY(); + StartStdioForwarding(); } NativeProcessWindows::NativeProcessWindows(lldb::pid_t pid, int terminal_fd, NativeDelegate &delegate, llvm::Error &E) - : NativeProcessProtocol(pid, terminal_fd, delegate), ProcessDebugger() { + : NativeProcessProtocol(pid, terminal_fd, delegate), ProcessDebugger(), + m_stdio_communication("lldb.NativeProcessWindows.stdio") { ErrorAsOutParameter EOut(&E); DebugDelegateSP delegate_sp(new NativeDebugDelegate(*this)); ProcessAttachInfo attach_info; @@ -431,6 +439,10 @@ void NativeProcessWindows::OnExitProcess(uint32_t exit_code) { Log *log = GetLog(WindowsLog::Process); LLDB_LOG(log, "Process {0} exited with code {1}", GetID(), exit_code); + // Closing the ConPTY signals EOF on the parent-side STDOUT pipe so the + // read thread can exit. Tear it down before the inferior is reaped. + StopStdioForwarding(); + ProcessDebugger::OnExitProcess(exit_code); // No signal involved. It is just an exit event. @@ -698,4 +710,43 @@ NativeProcessWindows::Manager::Attach( return std::move(E); return std::move(process_up); } + +NativeProcessWindows::~NativeProcessWindows() { StopStdioForwarding(); } + +void NativeProcessWindows::StartStdioForwarding() { + if (!m_pty || !m_pty->IsConnected()) + return; + + m_stdio_communication.SetConnection( + std::make_unique<ConnectionConPTY>(m_pty)); + if (!m_stdio_communication.IsConnected()) + return; + m_stdio_communication.SetReadThreadBytesReceivedCallback( + &NativeProcessWindows::STDIOReadThreadBytesReceived, this); + m_stdio_communication.StartReadThread(); +} + +void NativeProcessWindows::StopStdioForwarding() { + if (!m_stdio_communication.HasConnection()) + return; + + if (m_pty) + m_pty->Close(); + + if (m_stdio_communication.ReadThreadIsRunning()) + m_stdio_communication.JoinReadThread(); + + if (m_stdio_communication.HasConnection()) + m_stdio_communication.Disconnect(); +} + +void NativeProcessWindows::STDIOReadThreadBytesReceived(void *baton, + const void *src, + size_t src_len) { + auto *self = static_cast<NativeProcessWindows *>(baton); + if (src_len == 0) + return; + self->m_delegate.NewProcessOutput( + self, llvm::StringRef(static_cast<const char *>(src), src_len)); +} } // namespace lldb_private diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h index a15b51c7c66ab..95b85754ebdeb 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h @@ -9,6 +9,7 @@ #ifndef liblldb_NativeProcessWindows_h_ #define liblldb_NativeProcessWindows_h_ +#include "lldb/Core/ThreadedCommunication.h" #include "lldb/Host/common/NativeProcessProtocol.h" #include "lldb/lldb-forward.h" @@ -21,6 +22,7 @@ class HostProcess; class NativeProcessWindows; class NativeThreadWindows; class NativeDebugDelegate; +class PseudoConsole; using NativeDebugDelegateSP = std::shared_ptr<NativeDebugDelegate>; @@ -47,6 +49,8 @@ class NativeProcessWindows : public NativeProcessProtocol, } }; + ~NativeProcessWindows() override; + Status Resume(const ResumeActionList &resume_actions) override; Status Halt() override; @@ -155,6 +159,23 @@ class NativeProcessWindows : public NativeProcessProtocol, /// Whether we've seen the loader breakpoint that fires once per process at /// launch / attach. bool m_initial_stop_seen = false; + + /// PseudoConsole for the lldb-server stdio-forwarding path. + std::shared_ptr<PseudoConsole> m_pty; + + /// Wraps a ConnectionConPTY around the PTY's parent-side STDOUT HANDLE. + ThreadedCommunication m_stdio_communication; + + /// Bridge between m_stdio_communication's read thread and + /// NativeDelegate::NewProcessOutput. + static void STDIOReadThreadBytesReceived(void *baton, const void *src, + size_t src_len); + + /// Wire up m_stdio_communication on m_pty's STDOUT HANDLE. + void StartStdioForwarding(); + + /// Tear down the read thread and disconnect m_stdio_communication. + void StopStdioForwarding(); }; //------------------------------------------------------------------ diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp index ec80a3e960949..11fdf33f6b3d7 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -291,13 +291,8 @@ Status GDBRemoteCommunicationServerLLGS::LaunchProcess() { m_process_launch_info.GetFlags().Set(eLaunchFlagDebug); if (should_forward_stdio) { - // Temporarily relax the following for Windows until we can take advantage - // of the recently added pty support. This doesn't really affect the use of - // lldb-server on Windows. -#if !defined(_WIN32) if (llvm::Error Err = m_process_launch_info.SetUpPtyRedirection()) return Status::FromError(std::move(Err)); -#endif } { @@ -1224,6 +1219,18 @@ void GDBRemoteCommunicationServerLLGS::NewSubprocess( DebuggedProcess{std::move(child_process), DebuggedProcess::Flag{}}); } +void GDBRemoteCommunicationServerLLGS::NewProcessOutput(NativeProcessProtocol *, + llvm::StringRef data) { + if (data.empty()) + return; + + std::string owned(data); + m_mainloop.AddPendingCallback( + [this, owned = std::move(owned)](MainLoopBase &) { + SendONotification(owned.data(), owned.size()); + }); +} + void GDBRemoteCommunicationServerLLGS::DataAvailableCallback() { Log *log = GetLog(GDBRLog::Comm); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h index d582d2d77b639..0de4897a7f3cf 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -88,6 +88,11 @@ class GDBRemoteCommunicationServerLLGS NewSubprocess(NativeProcessProtocol *parent_process, std::unique_ptr<NativeProcessProtocol> child_process) override; + /// Forward a chunk of inferior stdout/stderr produced by the platform's + /// own reader. This is used on Windows. + void NewProcessOutput(NativeProcessProtocol *process, + llvm::StringRef data) override; + Status InitializeConnection(std::unique_ptr<Connection> connection); GDBRemoteCommunication::PacketResult >From 7b1d0977bb0bd66f58ae96a25252b764020e0e1f Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Tue, 2 Jun 2026 16:15:38 +0100 Subject: [PATCH 2/4] [lldb][gdb-remote] Don't emit \$O while the inferior is stopped --- .../GDBRemoteCommunicationServerLLGS.cpp | 33 ++++++++++++++++--- .../GDBRemoteCommunicationServerLLGS.h | 7 ++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp index 11fdf33f6b3d7..d56026e011564 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -1224,11 +1224,26 @@ void GDBRemoteCommunicationServerLLGS::NewProcessOutput(NativeProcessProtocol *, if (data.empty()) return; - std::string owned(data); + { + std::lock_guard<std::mutex> lock(m_pending_output_mutex); + m_pending_output_buffer.append(data.begin(), data.end()); + } m_mainloop.AddPendingCallback( - [this, owned = std::move(owned)](MainLoopBase &) { - SendONotification(owned.data(), owned.size()); - }); + [this](MainLoopBase &) { FlushPendingProcessOutput(); }); +} + +void GDBRemoteCommunicationServerLLGS::FlushPendingProcessOutput() { + if (!m_current_process || !StateIsRunningState(m_current_process->GetState())) + return; + + std::string out; + { + std::lock_guard<std::mutex> lock(m_pending_output_mutex); + if (m_pending_output_buffer.empty()) + return; + out.swap(m_pending_output_buffer); + } + SendONotification(out.data(), out.size()); } void GDBRemoteCommunicationServerLLGS::DataAvailableCallback() { @@ -2038,6 +2053,16 @@ GDBRemoteCommunicationServerLLGS::SendStopReasonForState( bool force_synchronous) { Log *log = GetLog(LLDBLog::Process); + { + std::string out; + { + std::lock_guard<std::mutex> lock(m_pending_output_mutex); + out.swap(m_pending_output_buffer); + } + if (!out.empty()) + SendONotification(out.data(), out.size()); + } + if (m_disabling_non_stop) { // Check if we are waiting for any more processes to stop. If we are, // do not send the OK response yet. diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h index 0de4897a7f3cf..e5b4c9ec0bed0 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -93,6 +93,10 @@ class GDBRemoteCommunicationServerLLGS void NewProcessOutput(NativeProcessProtocol *process, llvm::StringRef data) override; + /// Drain m_pending_output_buffer and emit a `$O` packet if the debuggee + /// is currently in a running state. No-op otherwise. + void FlushPendingProcessOutput(); + Status InitializeConnection(std::unique_ptr<Connection> connection); GDBRemoteCommunication::PacketResult @@ -125,6 +129,9 @@ class GDBRemoteCommunicationServerLLGS Communication m_stdio_communication; MainLoop::ReadHandleUP m_stdio_handle_up; + std::string m_pending_output_buffer; + std::mutex m_pending_output_mutex; + llvm::StringMap<std::unique_ptr<llvm::MemoryBuffer>> m_xfer_buffer_map; std::mutex m_saved_registers_mutex; std::unordered_map<uint32_t, lldb::DataBufferSP> m_saved_registers_map; >From 6db3d18a580eebc5898568eb4c8d9435dae95941 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 4 Jun 2026 15:29:33 +0100 Subject: [PATCH 3/4] remove todo --- .../Plugins/Process/Windows/Common/NativeProcessWindows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp index 886515d86328b..12a0302ace886 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp @@ -53,7 +53,7 @@ NativeProcessWindows::NativeProcessWindows(ProcessLaunchInfo &launch_info, llvm::Error &E) : NativeProcessProtocol( LLDB_INVALID_PROCESS_ID, - PseudoTerminal::invalid_fd, // TODO: Implement on Windows + PseudoTerminal::invalid_fd, // NativeProcessWindows owns the ConPTY. delegate), ProcessDebugger(), m_arch(launch_info.GetArchitecture()), m_stdio_communication("lldb.NativeProcessWindows.stdio") { >From 022f7409ae481479d41333a947c419330eea0f04 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Fri, 5 Jun 2026 15:13:53 +0100 Subject: [PATCH 4/4] Update NativeProcessWindows.cpp --- .../Plugins/Process/Windows/Common/NativeProcessWindows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp index 12a0302ace886..b235ab281bad6 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp @@ -440,7 +440,7 @@ void NativeProcessWindows::OnExitProcess(uint32_t exit_code) { LLDB_LOG(log, "Process {0} exited with code {1}", GetID(), exit_code); // Closing the ConPTY signals EOF on the parent-side STDOUT pipe so the - // read thread can exit. Tear it down before the inferior is reaped. + // read thread can exit. Tear it down before the debuggee is destroyed. StopStdioForwarding(); ProcessDebugger::OnExitProcess(exit_code); _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
