https://github.com/charles-zablit updated 
https://github.com/llvm/llvm-project/pull/201124

>From 7c7765b5d7bdb1a46ce40e7f3c488eee4e807dad Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Tue, 2 Jun 2026 16:10:27 +0100
Subject: [PATCH 1/3] [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 73989f18173c9..137b9eee93308 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.
@@ -686,4 +698,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 d2c37d202f242..2706564b622c0 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 a84e965b04bee..bf02f53ae84be 100644
--- 
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ 
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -286,13 +286,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
   }
 
   {
@@ -1219,6 +1214,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 5a088f8de2380..c8f0d0fc4f57e 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 fdd8617aea99ee7e0ad3b1328b5eded9271f8d86 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Tue, 2 Jun 2026 16:15:38 +0100
Subject: [PATCH 2/3] [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 bf02f53ae84be..788b5c4f002d6 100644
--- 
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ 
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -1219,11 +1219,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() {
@@ -2033,6 +2048,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 c8f0d0fc4f57e..30750d8e87098 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 e0f1acb84a66c42dec003dea5d9d855801be1b34 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Thu, 4 Jun 2026 15:29:33 +0100
Subject: [PATCH 3/3] 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 137b9eee93308..b5ef800a191bc 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") {

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to