[lldb] [llvm] [lldb][windows] add Windows Virtual Console support (PR #168729)

Charles Zablit via llvm-commits llvm-commits at lists.llvm.org
Thu Dec 11 07:21:55 PST 2025


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

>From 2101873001ffd00cb6117e757b222fe197512f08 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 26 Nov 2025 18:25:23 +0000
Subject: [PATCH 1/6] [lldb][windows] add Windows Virtual Console support

---
 lldb/include/lldb/Host/ProcessLaunchInfo.h    |   4 +-
 lldb/include/lldb/Host/PseudoTerminal.h       |  42 +++-
 .../lldb/Host/windows/PseudoTerminalWindows.h |  39 ++++
 lldb/include/lldb/Host/windows/windows.h      |   4 +-
 lldb/include/lldb/Target/Process.h            |   9 +
 lldb/source/Host/CMakeLists.txt               |   1 +
 lldb/source/Host/common/ProcessLaunchInfo.cpp |  19 +-
 lldb/source/Host/common/PseudoTerminal.cpp    |   4 +-
 .../Host/windows/ProcessLauncherWindows.cpp   |  38 ++--
 .../Host/windows/PseudoTerminalWindows.cpp    |  69 +++++++
 .../Platform/Windows/PlatformWindows.cpp      |  11 +-
 .../Process/Windows/Common/ProcessWindows.cpp | 181 ++++++++++++++++--
 .../Process/Windows/Common/ProcessWindows.h   |   8 +-
 .../tools/lldb-dap/launch/TestDAP_launch.py   |   8 -
 .../gn/secondary/lldb/source/Host/BUILD.gn    |   1 +
 15 files changed, 380 insertions(+), 58 deletions(-)
 create mode 100644 lldb/include/lldb/Host/windows/PseudoTerminalWindows.h
 create mode 100644 lldb/source/Host/windows/PseudoTerminalWindows.cpp

diff --git a/lldb/include/lldb/Host/ProcessLaunchInfo.h b/lldb/include/lldb/Host/ProcessLaunchInfo.h
index 25762bc65295d..e0528c5b80539 100644
--- a/lldb/include/lldb/Host/ProcessLaunchInfo.h
+++ b/lldb/include/lldb/Host/ProcessLaunchInfo.h
@@ -118,7 +118,9 @@ class ProcessLaunchInfo : public ProcessInfo {
 
   bool MonitorProcess() const;
 
-  PseudoTerminal &GetPTY() { return *m_pty; }
+  PseudoTerminal &GetPTY() const { return *m_pty; }
+
+  std::shared_ptr<PseudoTerminal> GetPTYSP() const { return m_pty; }
 
   void SetLaunchEventData(const char *data) { m_event_data.assign(data); }
 
diff --git a/lldb/include/lldb/Host/PseudoTerminal.h b/lldb/include/lldb/Host/PseudoTerminal.h
index bd1e2f56241b2..c8443c71fbc62 100644
--- a/lldb/include/lldb/Host/PseudoTerminal.h
+++ b/lldb/include/lldb/Host/PseudoTerminal.h
@@ -35,11 +35,14 @@ class PseudoTerminal {
 
   /// Destructor
   ///
-  /// The destructor will close the primary and secondary file descriptors if
-  /// they are valid and ownership has not been released using one of: @li
-  /// PseudoTerminal::ReleasePrimaryFileDescriptor() @li
-  /// PseudoTerminal::ReleaseSaveFileDescriptor()
-  ~PseudoTerminal();
+  /// The destructor will close the primary and secondary file
+  /// descriptor/HANDLEs if they are valid and ownership has not been released
+  /// using PseudoTerminal::Close().
+  virtual ~PseudoTerminal();
+
+  /// Close all the file descriptors or Handles of the PseudoTerminal if they
+  /// are valid.
+  virtual void Close();
 
   /// Close the primary file descriptor if it is valid.
   void ClosePrimaryFileDescriptor();
@@ -59,8 +62,7 @@ class PseudoTerminal {
   ///
   /// This class will close the file descriptors for the primary/secondary when
   /// the destructor is called. The file handles can be released using either:
-  /// @li PseudoTerminal::ReleasePrimaryFileDescriptor() @li
-  /// PseudoTerminal::ReleaseSaveFileDescriptor()
+  /// @li PseudoTerminal::ReleasePrimaryFileDescriptor()
   ///
   /// \return
   ///     \b Parent process: a child process ID that is greater
@@ -82,6 +84,16 @@ class PseudoTerminal {
   /// \see PseudoTerminal::ReleasePrimaryFileDescriptor()
   int GetPrimaryFileDescriptor() const;
 
+  /// The primary HANDLE accessor.
+  ///
+  /// This object retains ownership of the primary HANDLE when this
+  /// accessor is used.
+  ///
+  /// \return
+  ///     The primary HANDLE, or INVALID_HANDLE_VALUE if the primary HANDLE is
+  ///     not currently valid.
+  virtual void *GetPrimaryHandle() const { return ((void *)(long long)-1); };
+
   /// The secondary file descriptor accessor.
   ///
   /// This object retains ownership of the secondary file descriptor when this
@@ -96,6 +108,8 @@ class PseudoTerminal {
   /// \see PseudoTerminal::ReleaseSecondaryFileDescriptor()
   int GetSecondaryFileDescriptor() const;
 
+  virtual void *GetSecondaryHandle() const { return ((void *)(long long)-1); };
+
   /// Get the name of the secondary pseudo terminal.
   ///
   /// A primary pseudo terminal should already be valid prior to
@@ -105,7 +119,17 @@ class PseudoTerminal {
   ///     The name of the secondary pseudo terminal.
   ///
   /// \see PseudoTerminal::OpenFirstAvailablePrimary()
-  std::string GetSecondaryName() const;
+  virtual std::string GetSecondaryName() const;
+
+  /// The underlying Windows Pseudo Terminal HANDLE's accessor.
+  ///
+  /// This object retains ownership of the ConPTY's HANDLE when this
+  /// accessor is used.
+  ///
+  /// \return
+  ///     The primary HANDLE, or INVALID_HANDLE_VALUE if the primary HANDLE is
+  ///     not currently valid.
+  virtual void *GetPseudoTerminalHandle() { return ((void *)(long long)-1); };
 
   /// Open the first available pseudo terminal.
   ///
@@ -126,7 +150,7 @@ class PseudoTerminal {
   ///
   /// \see PseudoTerminal::GetPrimaryFileDescriptor() @see
   /// PseudoTerminal::ReleasePrimaryFileDescriptor()
-  llvm::Error OpenFirstAvailablePrimary(int oflag);
+  virtual llvm::Error OpenFirstAvailablePrimary(int oflag);
 
   /// Open the secondary for the current primary pseudo terminal.
   ///
diff --git a/lldb/include/lldb/Host/windows/PseudoTerminalWindows.h b/lldb/include/lldb/Host/windows/PseudoTerminalWindows.h
new file mode 100644
index 0000000000000..55868665b582b
--- /dev/null
+++ b/lldb/include/lldb/Host/windows/PseudoTerminalWindows.h
@@ -0,0 +1,39 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_Host_Windows_PseudoTerminalWindows_H_
+#define liblldb_Host_Windows_PseudoTerminalWindows_H_
+
+#include "lldb/Host/PseudoTerminal.h"
+#include "lldb/Host/windows/windows.h"
+
+namespace lldb_private {
+
+class PseudoTerminalWindows : public PseudoTerminal {
+
+public:
+  void Close() override;
+
+  HPCON GetPseudoTerminalHandle() override { return m_conpty_handle; };
+
+  HANDLE GetPrimaryHandle() const override { return m_conpty_output; };
+
+  HANDLE GetSecondaryHandle() const override { return m_conpty_input; };
+
+  std::string GetSecondaryName() const override { return ""; };
+
+  llvm::Error OpenFirstAvailablePrimary(int oflag) override;
+
+protected:
+  HANDLE m_conpty_handle = INVALID_HANDLE_VALUE;
+  HANDLE m_conpty_output = INVALID_HANDLE_VALUE;
+  HANDLE m_conpty_input = INVALID_HANDLE_VALUE;
+};
+}; // namespace lldb_private
+
+#endif // liblldb_Host_Windows_PseudoTerminalWindows_H_
diff --git a/lldb/include/lldb/Host/windows/windows.h b/lldb/include/lldb/Host/windows/windows.h
index d53d4b9967268..bb6695a112c7d 100644
--- a/lldb/include/lldb/Host/windows/windows.h
+++ b/lldb/include/lldb/Host/windows/windows.h
@@ -9,9 +9,9 @@
 #ifndef LLDB_lldb_windows_h_
 #define LLDB_lldb_windows_h_
 
-#define NTDDI_VERSION NTDDI_VISTA
+#define NTDDI_VERSION NTDDI_WIN10_RS5
 #undef _WIN32_WINNT // undef a previous definition to avoid warning
-#define _WIN32_WINNT _WIN32_WINNT_VISTA
+#define _WIN32_WINNT _WIN32_WINNT_WIN10
 #define WIN32_LEAN_AND_MEAN
 #define NOGDI
 #undef NOMINMAX // undef a previous definition to avoid warning
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 8e6c16cbfe0fc..d2fab11879681 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -2558,6 +2558,15 @@ void PruneThreadPlans();
   /// \see lldb_private::ConnectionFileDescriptor
   void SetSTDIOFileDescriptor(int file_descriptor);
 
+  /// Windows equivalent of Process::SetSTDIOFileDescriptor, with a
+  /// PseudoTerminalWindows instead of a file descriptor.
+  ///
+  /// \param pty
+  ///     The PseudoTerminal to use for process STDIO communication. It is not
+  ///     managed by the created read thread.
+  virtual void
+  SetPseudoTerminalHandle(const std::shared_ptr<PseudoTerminal> &pty) {};
+
   // Add a permanent region of memory that should never be read or written to.
   // This can be used to ensure that memory reads or writes to certain areas of
   // memory never end up being sent to the DoReadMemory or DoWriteMemory
diff --git a/lldb/source/Host/CMakeLists.txt b/lldb/source/Host/CMakeLists.txt
index 3184d3a1ead0d..d2fd566e9a711 100644
--- a/lldb/source/Host/CMakeLists.txt
+++ b/lldb/source/Host/CMakeLists.txt
@@ -76,6 +76,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows")
     windows/MainLoopWindows.cpp
     windows/PipeWindows.cpp
     windows/ProcessLauncherWindows.cpp
+    windows/PseudoTerminalWindows.cpp
     windows/ProcessRunLock.cpp
     )
 else()
diff --git a/lldb/source/Host/common/ProcessLaunchInfo.cpp b/lldb/source/Host/common/ProcessLaunchInfo.cpp
index 49159cca9c57c..c3beef7031f18 100644
--- a/lldb/source/Host/common/ProcessLaunchInfo.cpp
+++ b/lldb/source/Host/common/ProcessLaunchInfo.cpp
@@ -20,7 +20,9 @@
 #include "llvm/Support/ConvertUTF.h"
 #include "llvm/Support/FileSystem.h"
 
-#if !defined(_WIN32)
+#ifdef _WIN32
+#include "lldb/Host/windows/PseudoTerminalWindows.h"
+#else
 #include <climits>
 #endif
 
@@ -31,7 +33,12 @@ using namespace lldb_private;
 
 ProcessLaunchInfo::ProcessLaunchInfo()
     : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(0),
-      m_file_actions(), m_pty(new PseudoTerminal), m_monitor_callback(nullptr) {
+      m_file_actions(), m_monitor_callback(nullptr) {
+#ifdef _WIN32
+  m_pty = std::make_shared<PseudoTerminalWindows>();
+#else
+  m_pty = std::make_shared<PseudoTerminal>();
+#endif
 }
 
 ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec,
@@ -40,7 +47,13 @@ ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec,
                                      const FileSpec &working_directory,
                                      uint32_t launch_flags)
     : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(launch_flags),
-      m_file_actions(), m_pty(new PseudoTerminal) {
+      m_file_actions() {
+#ifdef _WIN32
+  m_pty = std::make_shared<PseudoTerminalWindows>();
+#else
+  m_pty = std::make_shared<PseudoTerminal>();
+#endif
+
   if (stdin_file_spec) {
     FileAction file_action;
     const bool read = true;
diff --git a/lldb/source/Host/common/PseudoTerminal.cpp b/lldb/source/Host/common/PseudoTerminal.cpp
index 53e91aff212a4..6c548701857c3 100644
--- a/lldb/source/Host/common/PseudoTerminal.cpp
+++ b/lldb/source/Host/common/PseudoTerminal.cpp
@@ -38,7 +38,9 @@ PseudoTerminal::PseudoTerminal() = default;
 // are valid and ownership has not been released using the
 // ReleasePrimaryFileDescriptor() or the ReleaseSaveFileDescriptor() member
 // functions.
-PseudoTerminal::~PseudoTerminal() {
+PseudoTerminal::~PseudoTerminal() { Close(); }
+
+void PseudoTerminal::Close() {
   ClosePrimaryFileDescriptor();
   CloseSecondaryFileDescriptor();
 }
diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
index ac5383f20f58e..704c0ff8e6ae5 100644
--- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp
+++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
@@ -87,9 +87,13 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
   error.Clear();
 
   STARTUPINFOEXW startupinfoex = {};
-  startupinfoex.StartupInfo.cb = sizeof(startupinfoex);
+  startupinfoex.StartupInfo.cb = sizeof(STARTUPINFOEXW);
   startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
 
+  HPCON hPC = launch_info.GetPTY().GetPseudoTerminalHandle();
+  bool use_pty =
+      hPC != INVALID_HANDLE_VALUE && launch_info.GetNumFileActions() == 0;
+
   HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO);
   HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO);
   HANDLE stderr_handle = GetStdioHandle(launch_info, STDERR_FILENO);
@@ -107,10 +111,10 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
                                     /*dwAttributeCount=*/1, /*dwFlags=*/0,
                                     &attributelist_size);
 
-  startupinfoex.lpAttributeList =
-      static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attributelist_size));
-  auto free_attributelist =
-      llvm::make_scope_exit([&] { free(startupinfoex.lpAttributeList); });
+  startupinfoex.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
+      GetProcessHeap(), 0, attributelist_size);
+  auto free_attributelist = llvm::make_scope_exit(
+      [&] { HeapFree(GetProcessHeap(), 0, startupinfoex.lpAttributeList); });
   if (!InitializeProcThreadAttributeList(startupinfoex.lpAttributeList,
                                          /*dwAttributeCount=*/1, /*dwFlags=*/0,
                                          &attributelist_size)) {
@@ -120,13 +124,23 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
   auto delete_attributelist = llvm::make_scope_exit(
       [&] { DeleteProcThreadAttributeList(startupinfoex.lpAttributeList); });
 
-  auto inherited_handles_or_err = GetInheritedHandles(
-      launch_info, startupinfoex, stdout_handle, stderr_handle, stdin_handle);
-  if (!inherited_handles_or_err) {
-    error = Status(inherited_handles_or_err.getError());
-    return HostProcess();
+  std::vector<HANDLE> inherited_handles;
+  if (use_pty) {
+    if (!UpdateProcThreadAttribute(startupinfoex.lpAttributeList, 0,
+                                   PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, hPC,
+                                   sizeof(hPC), NULL, NULL)) {
+      error = Status(::GetLastError(), eErrorTypeWin32);
+      return HostProcess();
+    }
+  } else {
+    auto inherited_handles_or_err = GetInheritedHandles(
+        launch_info, startupinfoex, stdout_handle, stderr_handle, stdin_handle);
+    if (!inherited_handles_or_err) {
+      error = Status(inherited_handles_or_err.getError());
+      return HostProcess();
+    }
+    inherited_handles = *inherited_handles_or_err;
   }
-  std::vector<HANDLE> inherited_handles = *inherited_handles_or_err;
 
   const char *hide_console_var =
       getenv("LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE");
@@ -141,7 +155,7 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
   if (launch_info.GetFlags().Test(eLaunchFlagDebug))
     flags |= DEBUG_ONLY_THIS_PROCESS;
 
-  if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO))
+  if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO) || use_pty)
     flags &= ~CREATE_NEW_CONSOLE;
 
   std::vector<wchar_t> environment =
diff --git a/lldb/source/Host/windows/PseudoTerminalWindows.cpp b/lldb/source/Host/windows/PseudoTerminalWindows.cpp
new file mode 100644
index 0000000000000..626df2f254644
--- /dev/null
+++ b/lldb/source/Host/windows/PseudoTerminalWindows.cpp
@@ -0,0 +1,69 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "lldb/Host/windows/PseudoTerminalWindows.h"
+
+#include "llvm/Support/Errc.h"
+#include "llvm/Support/Errno.h"
+
+using namespace lldb_private;
+
+void PseudoTerminalWindows::Close() {
+  if (m_conpty_handle != INVALID_HANDLE_VALUE)
+    ClosePseudoConsole(m_conpty_handle);
+  CloseHandle(m_conpty_input);
+  CloseHandle(m_conpty_output);
+  m_conpty_handle = INVALID_HANDLE_VALUE;
+  m_conpty_input = INVALID_HANDLE_VALUE;
+  m_conpty_output = INVALID_HANDLE_VALUE;
+}
+
+llvm::Error PseudoTerminalWindows::OpenFirstAvailablePrimary(int oflag) {
+  HRESULT hr;
+  HANDLE hInputRead = INVALID_HANDLE_VALUE;
+  HANDLE hInputWrite = INVALID_HANDLE_VALUE;
+  HANDLE hOutputRead = INVALID_HANDLE_VALUE;
+  HANDLE hOutputWrite = INVALID_HANDLE_VALUE;
+
+  wchar_t pipe_name[MAX_PATH];
+  swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-%d-%p",
+           GetCurrentProcessId(), this);
+
+  hOutputRead =
+      CreateNamedPipeW(pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
+                       PIPE_TYPE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, NULL);
+  hOutputWrite = CreateFileW(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+                             FILE_ATTRIBUTE_NORMAL, NULL);
+
+  if (!CreatePipe(&hInputRead, &hInputWrite, NULL, 0))
+    return llvm::errorCodeToError(
+        std::error_code(GetLastError(), std::system_category()));
+
+  COORD consoleSize{256, 25};
+  HPCON hPC = INVALID_HANDLE_VALUE;
+  hr = CreatePseudoConsole(consoleSize, hInputRead, hOutputWrite, 0, &hPC);
+  CloseHandle(hInputRead);
+  CloseHandle(hOutputWrite);
+
+  if (FAILED(hr)) {
+    CloseHandle(hInputWrite);
+    CloseHandle(hOutputRead);
+    return llvm::make_error<llvm::StringError>(
+        "Failed to create Windows ConPTY pseudo terminal",
+        llvm::errc::io_error);
+  }
+
+  DWORD mode = PIPE_NOWAIT;
+  SetNamedPipeHandleState(hOutputRead, &mode, NULL, NULL);
+
+  m_conpty_handle = hPC;
+  m_conpty_output = hOutputRead;
+  m_conpty_input = hInputWrite;
+
+  return llvm::Error::success();
+}
diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
index c0c26cc5f1954..da52bd59bdc4c 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
@@ -496,6 +496,7 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info,
   // plugin, and PlatformWindows::DebugProcess is just a pass-through to get to
   // the process plugin.
 
+  Log *log = GetLog(LLDBLog::Platform);
   if (IsRemote()) {
     if (m_remote_platform_sp)
       return m_remote_platform_sp->DebugProcess(launch_info, debugger, target,
@@ -519,8 +520,14 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info,
 
   // We need to launch and attach to the process.
   launch_info.GetFlags().Set(eLaunchFlagDebug);
-  if (process_sp)
-    error = process_sp->Launch(launch_info);
+  if (!process_sp)
+    return process_sp;
+  error = process_sp->Launch(launch_info);
+  if (error.Success())
+    process_sp->SetPseudoTerminalHandle(launch_info.GetPTYSP());
+  else
+    LLDB_LOGF(log, "Platform::%s LaunchProcess() failed: %s", __FUNCTION__,
+              error.AsCString());
 
   return process_sp;
 }
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 4cc39f928ee1e..17961c72e56a8 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -13,6 +13,7 @@
 #include <psapi.h>
 
 #include "lldb/Breakpoint/Watchpoint.h"
+#include "lldb/Core/IOHandler.h"
 #include "lldb/Core/Module.h"
 #include "lldb/Core/ModuleSpec.h"
 #include "lldb/Core/PluginManager.h"
@@ -21,12 +22,17 @@
 #include "lldb/Host/HostInfo.h"
 #include "lldb/Host/HostNativeProcessBase.h"
 #include "lldb/Host/HostProcess.h"
+#include "lldb/Host/Pipe.h"
+#include "lldb/Host/PseudoTerminal.h"
+#include "lldb/Host/windows/ConnectionGenericFileWindows.h"
 #include "lldb/Host/windows/HostThreadWindows.h"
 #include "lldb/Symbol/ObjectFile.h"
 #include "lldb/Target/DynamicLoader.h"
 #include "lldb/Target/MemoryRegionInfo.h"
 #include "lldb/Target/StopInfo.h"
 #include "lldb/Target/Target.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
 #include "lldb/Utility/State.h"
 
 #include "llvm/Support/ConvertUTF.h"
@@ -123,22 +129,6 @@ ProcessWindows::ProcessWindows(lldb::TargetSP target_sp,
 
 ProcessWindows::~ProcessWindows() {}
 
-size_t ProcessWindows::GetSTDOUT(char *buf, size_t buf_size, Status &error) {
-  error = Status::FromErrorString("GetSTDOUT unsupported on Windows");
-  return 0;
-}
-
-size_t ProcessWindows::GetSTDERR(char *buf, size_t buf_size, Status &error) {
-  error = Status::FromErrorString("GetSTDERR unsupported on Windows");
-  return 0;
-}
-
-size_t ProcessWindows::PutSTDIN(const char *buf, size_t buf_size,
-                                Status &error) {
-  error = Status::FromErrorString("PutSTDIN unsupported on Windows");
-  return 0;
-}
-
 Status ProcessWindows::EnableBreakpointSite(BreakpointSite *bp_site) {
   if (bp_site->HardwareRequired())
     return Status::FromErrorString("Hardware breakpoints are not supported.");
@@ -661,6 +651,7 @@ void ProcessWindows::OnExitProcess(uint32_t exit_code) {
   LLDB_LOG(log, "Process {0} exited with code {1}", GetID(), exit_code);
 
   TargetSP target = CalculateTarget();
+  target->GetProcessLaunchInfo().GetPTY().Close();
   if (target) {
     ModuleSP executable_module = target->GetExecutableModule();
     ModuleList unloaded_modules;
@@ -956,4 +947,162 @@ 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_pipe.CreateNew();
+  }
+
+  ~IOHandlerProcessSTDIOWindows() override = default;
+
+  void SetIsRunning(bool running) {
+    std::lock_guard<std::mutex> guard(m_mutex);
+    SetIsDone(!running);
+    m_is_running = running;
+  }
+
+  void Run() override {
+    if (!m_read_file.IsValid() || m_write_file == INVALID_HANDLE_VALUE ||
+        !m_pipe.CanRead() || !m_pipe.CanWrite()) {
+      SetIsDone(true);
+      return;
+    }
+
+    SetIsDone(false);
+    SetIsRunning(true);
+
+    HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
+    HANDLE hInterrupt = (HANDLE)_get_osfhandle(m_pipe.GetReadFileDescriptor());
+    HANDLE waitHandles[2] = {hStdin, hInterrupt};
+
+    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: {
+        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: {
+        char ch = 0;
+        DWORD read = 0;
+        if (!ReadFile(hInterrupt, &ch, 1, &read, nullptr) || read != 1)
+          goto exit_loop;
+
+        if (ch == 'q')
+          goto exit_loop;
+        if (ch == 'i' && StateIsRunningState(m_process->GetState()))
+          m_process->SendAsyncInterrupt();
+        break;
+      }
+      default:
+        goto exit_loop;
+      }
+    }
+
+  exit_loop:;
+    SetIsRunning(false);
+  }
+
+  void Cancel() override {
+    std::lock_guard<std::mutex> guard(m_mutex);
+    SetIsDone(true);
+    // Only write to our pipe to cancel if we are in
+    // IOHandlerProcessSTDIO::Run(). We can end up with a python command that
+    // is being run from the command interpreter:
+    //
+    // (lldb) step_process_thousands_of_times
+    //
+    // In this case the command interpreter will be in the middle of handling
+    // the command and if the process pushes and pops the IOHandler thousands
+    // of times, we can end up writing to m_pipe without ever consuming the
+    // bytes from the pipe in IOHandlerProcessSTDIO::Run() and end up
+    // deadlocking when the pipe gets fed up and blocks until data is consumed.
+    if (m_is_running) {
+      char ch = 'q'; // Send 'q' for quit
+      if (llvm::Error err = m_pipe.Write(&ch, 1).takeError()) {
+        LLDB_LOG_ERROR(GetLog(LLDBLog::Process), std::move(err),
+                       "Pipe write failed: {0}");
+      }
+    }
+  }
+
+  bool Interrupt() override {
+    // Do only things that are safe to do in an interrupt context (like in a
+    // SIGINT handler), like write 1 byte to a file descriptor. This will
+    // interrupt the IOHandlerProcessSTDIO::Run() and we can look at the byte
+    // that was written to the pipe and then call
+    // m_process->SendAsyncInterrupt() from a much safer location in code.
+    if (m_active) {
+      char ch = 'i'; // Send 'i' for interrupt
+      return !errorToBool(m_pipe.Write(&ch, 1).takeError());
+    } else {
+      // This IOHandler might be pushed on the stack, but not being run
+      // currently so do the right thing if we aren't actively watching for
+      // STDIN by sending the interrupt to the process. Otherwise the write to
+      // the pipe above would do nothing. This can happen when the command
+      // interpreter is running and gets a "expression ...". It will be on the
+      // IOHandler thread and sending the input is complete to the delegate
+      // which will cause the expression to run, which will push the process IO
+      // handler, but not run it.
+
+      if (StateIsRunningState(m_process->GetState())) {
+        m_process->SendAsyncInterrupt();
+        return true;
+      }
+    }
+    return false;
+  }
+
+  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;
+};
+
+void ProcessWindows::SetPseudoTerminalHandle(
+    const std::shared_ptr<PseudoTerminal> &pty) {
+  m_stdio_communication.SetConnection(
+      std::make_unique<ConnectionGenericFile>(pty->GetPrimaryHandle(), false));
+  if (m_stdio_communication.IsConnected()) {
+    m_stdio_communication.SetReadThreadBytesReceivedCallback(
+        STDIOReadThreadBytesReceived, this);
+    m_stdio_communication.StartReadThread();
+
+    // Now read thread is set up, set up input reader.
+    {
+      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, pty->GetSecondaryHandle());
+    }
+  }
+}
 } // namespace lldb_private
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
index 97284b7cd1436..49596e49a9f82 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
@@ -9,6 +9,7 @@
 #ifndef liblldb_Plugins_Process_Windows_Common_ProcessWindows_H_
 #define liblldb_Plugins_Process_Windows_Common_ProcessWindows_H_
 
+#include "lldb/Host/windows/PseudoTerminalWindows.h"
 #include "lldb/Target/Process.h"
 #include "lldb/Utility/Status.h"
 #include "lldb/lldb-forward.h"
@@ -38,10 +39,6 @@ class ProcessWindows : public Process, public ProcessDebugger {
 
   ~ProcessWindows();
 
-  size_t GetSTDOUT(char *buf, size_t buf_size, Status &error) override;
-  size_t GetSTDERR(char *buf, size_t buf_size, Status &error) override;
-  size_t PutSTDIN(const char *buf, size_t buf_size, Status &error) override;
-
   llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
 
   Status EnableBreakpointSite(BreakpointSite *bp_site) override;
@@ -101,6 +98,9 @@ class ProcessWindows : public Process, public ProcessDebugger {
   Status DisableWatchpoint(lldb::WatchpointSP wp_sp,
                            bool notify = true) override;
 
+  void
+  SetPseudoTerminalHandle(const std::shared_ptr<PseudoTerminal> &pty) override;
+
 protected:
   ProcessWindows(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp);
 
diff --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
index ca881f1d817c5..b0e124303a3b0 100644
--- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
+++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
@@ -16,7 +16,6 @@
 
 
 class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase):
-    @skipIfWindows
     def test_default(self):
         """
         Tests the default launch of a simple program. No arguments,
@@ -76,7 +75,6 @@ def test_failing_console(self):
             r"unexpected value, expected 'internalConsole\', 'integratedTerminal\' or 'externalTerminal\' at arguments.console",
         )
 
-    @skipIfWindows
     def test_termination(self):
         """
         Tests the correct termination of lldb-dap upon a 'disconnect'
@@ -209,7 +207,6 @@ def test_disableSTDIO(self):
         output = self.get_stdout()
         self.assertEqual(output, "", "expect no program output")
 
-    @skipIfWindows
     @skipIfLinux  # shell argument expansion doesn't seem to work on Linux
     @expectedFailureAll(oslist=["freebsd", "netbsd"], bugnumber="llvm.org/pr48349")
     def test_shellExpandArguments_enabled(self):
@@ -233,7 +230,6 @@ def test_shellExpandArguments_enabled(self):
                     quote_path, line, 'verify "%s" expanded to "%s"' % (glob, program)
                 )
 
-    @skipIfWindows
     def test_shellExpandArguments_disabled(self):
         """
         Tests the default launch of a simple program with shell expansion
@@ -255,7 +251,6 @@ def test_shellExpandArguments_disabled(self):
                     quote_path, line, 'verify "%s" stayed to "%s"' % (glob, glob)
                 )
 
-    @skipIfWindows
     def test_args(self):
         """
         Tests launch of a simple program with arguments
@@ -280,7 +275,6 @@ def test_args(self):
                 'arg[%i] "%s" not in "%s"' % (i + 1, quoted_arg, lines[i]),
             )
 
-    @skipIfWindows
     def test_environment_with_object(self):
         """
         Tests launch of a simple program with environment variables
@@ -557,7 +551,6 @@ def test_terminate_commands(self):
         output = self.collect_console(pattern=terminateCommands[0])
         self.verify_commands("terminateCommands", output, terminateCommands)
 
-    @skipIfWindows
     def test_version(self):
         """
         Tests that "initialize" response contains the "version" string the same
@@ -640,7 +633,6 @@ def test_stdio_redirection(self):
             )
 
     @skipIfAsan
-    @skipIfWindows
     @skipIf(oslist=["linux"], archs=no_match(["x86_64"]))
     @skipIfBuildType(["debug"])
     def test_stdio_redirection_and_console(self):
diff --git a/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn b/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn
index af4533285d3e9..e91b5ae5c9d24 100644
--- a/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn
+++ b/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn
@@ -75,6 +75,7 @@ static_library("Host") {
       "windows/MainLoopWindows.cpp",
       "windows/PipeWindows.cpp",
       "windows/ProcessLauncherWindows.cpp",
+      "windows/PseudoTerminalWindows.cpp",
       "windows/ProcessRunLock.cpp",
     ]
   } else {

>From afaaa73eeba87e03db879d2e43e9bab6caa9eb06 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Tue, 2 Dec 2025 15:42:10 +0000
Subject: [PATCH 2/6] fixup! [lldb][windows] add Windows Virtual Console
 support

---
 lldb/source/Host/windows/PseudoTerminalWindows.cpp       | 2 +-
 lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lldb/source/Host/windows/PseudoTerminalWindows.cpp b/lldb/source/Host/windows/PseudoTerminalWindows.cpp
index 626df2f254644..a3b69aa89b065 100644
--- a/lldb/source/Host/windows/PseudoTerminalWindows.cpp
+++ b/lldb/source/Host/windows/PseudoTerminalWindows.cpp
@@ -31,7 +31,7 @@ llvm::Error PseudoTerminalWindows::OpenFirstAvailablePrimary(int oflag) {
   HANDLE hOutputWrite = INVALID_HANDLE_VALUE;
 
   wchar_t pipe_name[MAX_PATH];
-  swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-%d-%p",
+  swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p",
            GetCurrentProcessId(), this);
 
   hOutputRead =
diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
index da52bd59bdc4c..a65324e1c1434 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
@@ -521,7 +521,7 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info,
   // We need to launch and attach to the process.
   launch_info.GetFlags().Set(eLaunchFlagDebug);
   if (!process_sp)
-    return process_sp;
+    return nullptr;
   error = process_sp->Launch(launch_info);
   if (error.Success())
     process_sp->SetPseudoTerminalHandle(launch_info.GetPTYSP());

>From ad84a21abb28bcd6e9ff44e91c5c84a5c19aecec Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Tue, 2 Dec 2025 15:46:19 +0000
Subject: [PATCH 3/6] fixup! [lldb][windows] add Windows Virtual Console
 support

---
 lldb/source/Host/windows/ProcessLauncherWindows.cpp | 8 ++++----
 lldb/source/Host/windows/PseudoTerminalWindows.cpp  | 2 ++
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
index 704c0ff8e6ae5..aab85a589194d 100644
--- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp
+++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
@@ -111,10 +111,10 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
                                     /*dwAttributeCount=*/1, /*dwFlags=*/0,
                                     &attributelist_size);
 
-  startupinfoex.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
-      GetProcessHeap(), 0, attributelist_size);
-  auto free_attributelist = llvm::make_scope_exit(
-      [&] { HeapFree(GetProcessHeap(), 0, startupinfoex.lpAttributeList); });
+  startupinfoex.lpAttributeList =
+      static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attributelist_size));
+  auto free_attributelist =
+      llvm::make_scope_exit([&] { free(startupinfoex.lpAttributeList); });
   if (!InitializeProcThreadAttributeList(startupinfoex.lpAttributeList,
                                          /*dwAttributeCount=*/1, /*dwFlags=*/0,
                                          &attributelist_size)) {
diff --git a/lldb/source/Host/windows/PseudoTerminalWindows.cpp b/lldb/source/Host/windows/PseudoTerminalWindows.cpp
index a3b69aa89b065..079dc472bc114 100644
--- a/lldb/source/Host/windows/PseudoTerminalWindows.cpp
+++ b/lldb/source/Host/windows/PseudoTerminalWindows.cpp
@@ -34,6 +34,8 @@ llvm::Error PseudoTerminalWindows::OpenFirstAvailablePrimary(int oflag) {
   swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p",
            GetCurrentProcessId(), this);
 
+  // A 4096 bytes buffer should be large enough for the majority of console
+  // burst outputs.
   hOutputRead =
       CreateNamedPipeW(pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
                        PIPE_TYPE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, NULL);

>From d107524b61912ee04b206ce9e8a587f8c39e24e3 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 3 Dec 2025 15:40:37 +0000
Subject: [PATCH 4/6] refactor inherited_handles creation

---
 lldb/source/Host/windows/ProcessLauncherWindows.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
index aab85a589194d..983afa94c6b07 100644
--- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp
+++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
@@ -9,6 +9,7 @@
 #include "lldb/Host/windows/ProcessLauncherWindows.h"
 #include "lldb/Host/HostProcess.h"
 #include "lldb/Host/ProcessLaunchInfo.h"
+#include "lldb/Host/windows/windows.h"
 
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/SmallVector.h"

>From d42afba1f5140eb2157a3e054347a2f6298361cb Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Fri, 5 Dec 2025 14:16:03 +0000
Subject: [PATCH 5/6] dynamically load CreatePseudoConsole

---
 lldb/include/lldb/Host/ProcessLaunchInfo.h    | 14 ++++
 lldb/include/lldb/Host/PseudoTerminal.h       | 32 +-------
 .../include/lldb/Host/windows/PseudoConsole.h | 61 ++++++++++++++
 .../lldb/Host/windows/PseudoTerminalWindows.h | 39 ---------
 lldb/include/lldb/Host/windows/windows.h      |  4 +-
 lldb/include/lldb/Target/Process.h            | 33 +++++---
 lldb/source/Host/CMakeLists.txt               |  2 +-
 lldb/source/Host/common/ProcessLaunchInfo.cpp | 20 ++---
 lldb/source/Host/common/PseudoTerminal.cpp    |  4 +-
 .../Host/windows/ProcessLauncherWindows.cpp   |  3 +-
 ...oTerminalWindows.cpp => PseudoConsole.cpp} | 80 ++++++++++++++++---
 .../Plugins/Platform/POSIX/PlatformPOSIX.cpp  |  2 +
 .../Platform/QemuUser/PlatformQemuUser.cpp    |  2 +
 .../Platform/Windows/PlatformWindows.cpp      |  2 +-
 .../Windows/Common/NativeProcessWindows.cpp   |  8 +-
 .../Process/Windows/Common/ProcessWindows.cpp |  8 +-
 .../Process/Windows/Common/ProcessWindows.h   |  4 +-
 lldb/source/Target/Platform.cpp               |  2 +
 .../gn/secondary/lldb/source/Host/BUILD.gn    |  2 +-
 19 files changed, 205 insertions(+), 117 deletions(-)
 create mode 100644 lldb/include/lldb/Host/windows/PseudoConsole.h
 delete mode 100644 lldb/include/lldb/Host/windows/PseudoTerminalWindows.h
 rename lldb/source/Host/windows/{PseudoTerminalWindows.cpp => PseudoConsole.cpp} (55%)

diff --git a/lldb/include/lldb/Host/ProcessLaunchInfo.h b/lldb/include/lldb/Host/ProcessLaunchInfo.h
index e0528c5b80539..55ebcee8ca705 100644
--- a/lldb/include/lldb/Host/ProcessLaunchInfo.h
+++ b/lldb/include/lldb/Host/ProcessLaunchInfo.h
@@ -17,7 +17,11 @@
 
 #include "lldb/Host/FileAction.h"
 #include "lldb/Host/Host.h"
+#ifdef _WIN32
+#include "lldb/Host/windows/PseudoConsole.h"
+#else
 #include "lldb/Host/PseudoTerminal.h"
+#endif
 #include "lldb/Utility/FileSpec.h"
 #include "lldb/Utility/ProcessInfo.h"
 
@@ -118,9 +122,15 @@ class ProcessLaunchInfo : public ProcessInfo {
 
   bool MonitorProcess() const;
 
+#ifdef _WIN32
+  PseudoConsole &GetPTY() const { return *m_pty; }
+
+  std::shared_ptr<PseudoConsole> GetPTYSP() const { return m_pty; }
+#else
   PseudoTerminal &GetPTY() const { return *m_pty; }
 
   std::shared_ptr<PseudoTerminal> GetPTYSP() const { return m_pty; }
+#endif
 
   void SetLaunchEventData(const char *data) { m_event_data.assign(data); }
 
@@ -138,7 +148,11 @@ class ProcessLaunchInfo : public ProcessInfo {
   FileSpec m_shell;
   Flags m_flags; // Bitwise OR of bits from lldb::LaunchFlags
   std::vector<FileAction> m_file_actions; // File actions for any other files
+#ifdef _WIN32
+  std::shared_ptr<PseudoConsole> m_pty;
+#else
   std::shared_ptr<PseudoTerminal> m_pty;
+#endif
   uint32_t m_resume_count = 0; // How many times do we resume after launching
   Host::MonitorChildProcessCallback m_monitor_callback;
   std::string m_event_data; // A string passed to the plugin launch, having no
diff --git a/lldb/include/lldb/Host/PseudoTerminal.h b/lldb/include/lldb/Host/PseudoTerminal.h
index c8443c71fbc62..deb016c846c77 100644
--- a/lldb/include/lldb/Host/PseudoTerminal.h
+++ b/lldb/include/lldb/Host/PseudoTerminal.h
@@ -38,11 +38,7 @@ class PseudoTerminal {
   /// The destructor will close the primary and secondary file
   /// descriptor/HANDLEs if they are valid and ownership has not been released
   /// using PseudoTerminal::Close().
-  virtual ~PseudoTerminal();
-
-  /// Close all the file descriptors or Handles of the PseudoTerminal if they
-  /// are valid.
-  virtual void Close();
+  ~PseudoTerminal();
 
   /// Close the primary file descriptor if it is valid.
   void ClosePrimaryFileDescriptor();
@@ -84,16 +80,6 @@ class PseudoTerminal {
   /// \see PseudoTerminal::ReleasePrimaryFileDescriptor()
   int GetPrimaryFileDescriptor() const;
 
-  /// The primary HANDLE accessor.
-  ///
-  /// This object retains ownership of the primary HANDLE when this
-  /// accessor is used.
-  ///
-  /// \return
-  ///     The primary HANDLE, or INVALID_HANDLE_VALUE if the primary HANDLE is
-  ///     not currently valid.
-  virtual void *GetPrimaryHandle() const { return ((void *)(long long)-1); };
-
   /// The secondary file descriptor accessor.
   ///
   /// This object retains ownership of the secondary file descriptor when this
@@ -108,8 +94,6 @@ class PseudoTerminal {
   /// \see PseudoTerminal::ReleaseSecondaryFileDescriptor()
   int GetSecondaryFileDescriptor() const;
 
-  virtual void *GetSecondaryHandle() const { return ((void *)(long long)-1); };
-
   /// Get the name of the secondary pseudo terminal.
   ///
   /// A primary pseudo terminal should already be valid prior to
@@ -119,17 +103,7 @@ class PseudoTerminal {
   ///     The name of the secondary pseudo terminal.
   ///
   /// \see PseudoTerminal::OpenFirstAvailablePrimary()
-  virtual std::string GetSecondaryName() const;
-
-  /// The underlying Windows Pseudo Terminal HANDLE's accessor.
-  ///
-  /// This object retains ownership of the ConPTY's HANDLE when this
-  /// accessor is used.
-  ///
-  /// \return
-  ///     The primary HANDLE, or INVALID_HANDLE_VALUE if the primary HANDLE is
-  ///     not currently valid.
-  virtual void *GetPseudoTerminalHandle() { return ((void *)(long long)-1); };
+  std::string GetSecondaryName() const;
 
   /// Open the first available pseudo terminal.
   ///
@@ -150,7 +124,7 @@ class PseudoTerminal {
   ///
   /// \see PseudoTerminal::GetPrimaryFileDescriptor() @see
   /// PseudoTerminal::ReleasePrimaryFileDescriptor()
-  virtual llvm::Error OpenFirstAvailablePrimary(int oflag);
+  llvm::Error OpenFirstAvailablePrimary(int oflag);
 
   /// Open the secondary for the current primary pseudo terminal.
   ///
diff --git a/lldb/include/lldb/Host/windows/PseudoConsole.h b/lldb/include/lldb/Host/windows/PseudoConsole.h
new file mode 100644
index 0000000000000..fdca79ff27ce0
--- /dev/null
+++ b/lldb/include/lldb/Host/windows/PseudoConsole.h
@@ -0,0 +1,61 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_Host_Windows_PseudoConsole_H_
+#define liblldb_Host_Windows_PseudoConsole_H_
+
+#include "llvm/Support/Error.h"
+#include <string>
+
+#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x20016
+
+namespace lldb_private {
+
+class PseudoConsole {
+
+public:
+  llvm::Error OpenPseudoConsole();
+
+  /// Close the ConPTY, its read/write handles and invalidate them.
+  void Close();
+
+  /// The ConPTY HPCON handle accessor.
+  ///
+  /// This object retains ownership of the HPCON when this accessor is used.
+  ///
+  /// \return
+  ///     The ConPTY HPCON handle, or INVALID_HANDLE_VALUE if it is currently
+  ///     invalid.
+  void *GetPseudoTerminalHandle() { return m_conpty_handle; };
+
+  /// The STDOUT read HANDLE accessor.
+  ///
+  /// This object retains ownership of the HANDLE when this accessor is used.
+  ///
+  /// \return
+  ///     The STDOUT read HANDLE, or INVALID_HANDLE_VALUE if it is currently
+  ///     invalid.
+  void *GetSTDOUTHandle() const { return m_conpty_output; };
+
+  /// The STDIN write HANDLE accessor.
+  ///
+  /// This object retains ownership of the HANDLE when this accessor is used.
+  ///
+  /// \return
+  ///     The STDIN write HANDLE, or INVALID_HANDLE_VALUE if it is currently
+  ///     invalid.
+  void *GetSTDINHandle() const { return m_conpty_input; };
+
+protected:
+  void *m_conpty_handle = ((void *)(long long)-1);
+  void *m_conpty_output = ((void *)(long long)-1);
+  void *m_conpty_input = ((void *)(long long)-1);
+};
+}; // namespace lldb_private
+
+#endif // liblldb_Host_Windows_PseudoConsole_H_
diff --git a/lldb/include/lldb/Host/windows/PseudoTerminalWindows.h b/lldb/include/lldb/Host/windows/PseudoTerminalWindows.h
deleted file mode 100644
index 55868665b582b..0000000000000
--- a/lldb/include/lldb/Host/windows/PseudoTerminalWindows.h
+++ /dev/null
@@ -1,39 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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_Host_Windows_PseudoTerminalWindows_H_
-#define liblldb_Host_Windows_PseudoTerminalWindows_H_
-
-#include "lldb/Host/PseudoTerminal.h"
-#include "lldb/Host/windows/windows.h"
-
-namespace lldb_private {
-
-class PseudoTerminalWindows : public PseudoTerminal {
-
-public:
-  void Close() override;
-
-  HPCON GetPseudoTerminalHandle() override { return m_conpty_handle; };
-
-  HANDLE GetPrimaryHandle() const override { return m_conpty_output; };
-
-  HANDLE GetSecondaryHandle() const override { return m_conpty_input; };
-
-  std::string GetSecondaryName() const override { return ""; };
-
-  llvm::Error OpenFirstAvailablePrimary(int oflag) override;
-
-protected:
-  HANDLE m_conpty_handle = INVALID_HANDLE_VALUE;
-  HANDLE m_conpty_output = INVALID_HANDLE_VALUE;
-  HANDLE m_conpty_input = INVALID_HANDLE_VALUE;
-};
-}; // namespace lldb_private
-
-#endif // liblldb_Host_Windows_PseudoTerminalWindows_H_
diff --git a/lldb/include/lldb/Host/windows/windows.h b/lldb/include/lldb/Host/windows/windows.h
index bb6695a112c7d..d53d4b9967268 100644
--- a/lldb/include/lldb/Host/windows/windows.h
+++ b/lldb/include/lldb/Host/windows/windows.h
@@ -9,9 +9,9 @@
 #ifndef LLDB_lldb_windows_h_
 #define LLDB_lldb_windows_h_
 
-#define NTDDI_VERSION NTDDI_WIN10_RS5
+#define NTDDI_VERSION NTDDI_VISTA
 #undef _WIN32_WINNT // undef a previous definition to avoid warning
-#define _WIN32_WINNT _WIN32_WINNT_WIN10
+#define _WIN32_WINNT _WIN32_WINNT_VISTA
 #define WIN32_LEAN_AND_MEAN
 #define NOGDI
 #undef NOMINMAX // undef a previous definition to avoid warning
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index d2fab11879681..4dd8559addbd5 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -2534,6 +2534,30 @@ void PruneThreadPlans();
 
   void CalculateExecutionContext(ExecutionContext &exe_ctx) override;
 
+#ifdef _WIN32
+  /// Associates a ConPTY read and write HANDLEs with the process' STDIO
+  /// handling and configures an asynchronous reading of that ConPTY's stdout
+  /// HANDLE.
+  ///
+  /// This method installs a ConnectionGenericFile for the passed ConPTY and
+  /// starts a dedicated read thread. If the read thread starts successfully,
+  /// the method also ensures that an IOHandlerProcessSTDIOWindows is created to
+  /// manage user input to the process.
+  ///
+  /// When data is successfully read from the ConPTY, it is stored in
+  /// m_stdout_data. There is no differentiation between stdout and stderr.
+  ///
+  /// \param[in] pty
+  ///     The ConPTY to use for process STDIO communication. It's
+  ///     assumed to be valid.
+  ///
+  /// \see lldb_private::Process::STDIOReadThreadBytesReceived()
+  /// \see lldb_private::IOHandlerProcessSTDIOWindows
+  /// \see lldb_private::PseudoConsole
+  virtual void
+  SetPseudoConsoleHandle(const std::shared_ptr<PseudoConsole> &pty) {};
+#endif
+
   /// Associates a file descriptor with the process' STDIO handling
   /// and configures an asynchronous reading of that descriptor.
   ///
@@ -2558,15 +2582,6 @@ void PruneThreadPlans();
   /// \see lldb_private::ConnectionFileDescriptor
   void SetSTDIOFileDescriptor(int file_descriptor);
 
-  /// Windows equivalent of Process::SetSTDIOFileDescriptor, with a
-  /// PseudoTerminalWindows instead of a file descriptor.
-  ///
-  /// \param pty
-  ///     The PseudoTerminal to use for process STDIO communication. It is not
-  ///     managed by the created read thread.
-  virtual void
-  SetPseudoTerminalHandle(const std::shared_ptr<PseudoTerminal> &pty) {};
-
   // Add a permanent region of memory that should never be read or written to.
   // This can be used to ensure that memory reads or writes to certain areas of
   // memory never end up being sent to the DoReadMemory or DoWriteMemory
diff --git a/lldb/source/Host/CMakeLists.txt b/lldb/source/Host/CMakeLists.txt
index d2fd566e9a711..8ad485fa40285 100644
--- a/lldb/source/Host/CMakeLists.txt
+++ b/lldb/source/Host/CMakeLists.txt
@@ -76,7 +76,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows")
     windows/MainLoopWindows.cpp
     windows/PipeWindows.cpp
     windows/ProcessLauncherWindows.cpp
-    windows/PseudoTerminalWindows.cpp
+    windows/PseudoConsole.cpp
     windows/ProcessRunLock.cpp
     )
 else()
diff --git a/lldb/source/Host/common/ProcessLaunchInfo.cpp b/lldb/source/Host/common/ProcessLaunchInfo.cpp
index c3beef7031f18..256bf713b7372 100644
--- a/lldb/source/Host/common/ProcessLaunchInfo.cpp
+++ b/lldb/source/Host/common/ProcessLaunchInfo.cpp
@@ -21,7 +21,7 @@
 #include "llvm/Support/FileSystem.h"
 
 #ifdef _WIN32
-#include "lldb/Host/windows/PseudoTerminalWindows.h"
+#include "lldb/Host/windows/PseudoConsole.h"
 #else
 #include <climits>
 #endif
@@ -35,7 +35,7 @@ ProcessLaunchInfo::ProcessLaunchInfo()
     : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(0),
       m_file_actions(), m_monitor_callback(nullptr) {
 #ifdef _WIN32
-  m_pty = std::make_shared<PseudoTerminalWindows>();
+  m_pty = std::make_shared<PseudoConsole>();
 #else
   m_pty = std::make_shared<PseudoTerminal>();
 #endif
@@ -49,7 +49,7 @@ ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec,
     : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(launch_flags),
       m_file_actions() {
 #ifdef _WIN32
-  m_pty = std::make_shared<PseudoTerminalWindows>();
+  m_pty = std::make_shared<PseudoConsole>();
 #else
   m_pty = std::make_shared<PseudoTerminal>();
 #endif
@@ -221,13 +221,12 @@ llvm::Error ProcessLaunchInfo::SetUpPtyRedirection() {
 
   LLDB_LOG(log, "Generating a pty to use for stdin/out/err");
 
-  int open_flags = O_RDWR | O_NOCTTY;
-#if !defined(_WIN32)
-  // We really shouldn't be specifying platform specific flags that are
-  // intended for a system call in generic code.  But this will have to
-  // do for now.
-  open_flags |= O_CLOEXEC;
-#endif
+#ifdef _WIN32
+  if (llvm::Error Err = m_pty->OpenPseudoConsole())
+    return Err;
+  return llvm::Error::success();
+#else
+  int open_flags = O_RDWR | O_NOCTTY | O_CLOEXEC;
   if (llvm::Error Err = m_pty->OpenFirstAvailablePrimary(open_flags))
     return Err;
 
@@ -242,6 +241,7 @@ llvm::Error ProcessLaunchInfo::SetUpPtyRedirection() {
   if (stderr_free)
     AppendOpenFileAction(STDERR_FILENO, secondary_file_spec, false, true);
   return llvm::Error::success();
+#endif
 }
 
 bool ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell(
diff --git a/lldb/source/Host/common/PseudoTerminal.cpp b/lldb/source/Host/common/PseudoTerminal.cpp
index 6c548701857c3..53e91aff212a4 100644
--- a/lldb/source/Host/common/PseudoTerminal.cpp
+++ b/lldb/source/Host/common/PseudoTerminal.cpp
@@ -38,9 +38,7 @@ PseudoTerminal::PseudoTerminal() = default;
 // are valid and ownership has not been released using the
 // ReleasePrimaryFileDescriptor() or the ReleaseSaveFileDescriptor() member
 // functions.
-PseudoTerminal::~PseudoTerminal() { Close(); }
-
-void PseudoTerminal::Close() {
+PseudoTerminal::~PseudoTerminal() {
   ClosePrimaryFileDescriptor();
   CloseSecondaryFileDescriptor();
 }
diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
index 983afa94c6b07..001a8a16f3720 100644
--- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp
+++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
@@ -9,6 +9,7 @@
 #include "lldb/Host/windows/ProcessLauncherWindows.h"
 #include "lldb/Host/HostProcess.h"
 #include "lldb/Host/ProcessLaunchInfo.h"
+#include "lldb/Host/windows/PseudoConsole.h"
 #include "lldb/Host/windows/windows.h"
 
 #include "llvm/ADT/ScopeExit.h"
@@ -140,7 +141,7 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
       error = Status(inherited_handles_or_err.getError());
       return HostProcess();
     }
-    inherited_handles = *inherited_handles_or_err;
+    inherited_handles = std::move(*inherited_handles_or_err);
   }
 
   const char *hide_console_var =
diff --git a/lldb/source/Host/windows/PseudoTerminalWindows.cpp b/lldb/source/Host/windows/PseudoConsole.cpp
similarity index 55%
rename from lldb/source/Host/windows/PseudoTerminalWindows.cpp
rename to lldb/source/Host/windows/PseudoConsole.cpp
index 079dc472bc114..e882faac25150 100644
--- a/lldb/source/Host/windows/PseudoTerminalWindows.cpp
+++ b/lldb/source/Host/windows/PseudoConsole.cpp
@@ -6,24 +6,70 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "lldb/Host/windows/PseudoTerminalWindows.h"
+#include "lldb/Host/windows/PseudoConsole.h"
+#include "lldb/Host/windows/windows.h"
 
 #include "llvm/Support/Errc.h"
 #include "llvm/Support/Errno.h"
 
 using namespace lldb_private;
 
-void PseudoTerminalWindows::Close() {
-  if (m_conpty_handle != INVALID_HANDLE_VALUE)
-    ClosePseudoConsole(m_conpty_handle);
-  CloseHandle(m_conpty_input);
-  CloseHandle(m_conpty_output);
-  m_conpty_handle = INVALID_HANDLE_VALUE;
-  m_conpty_input = INVALID_HANDLE_VALUE;
-  m_conpty_output = INVALID_HANDLE_VALUE;
-}
+typedef HRESULT(WINAPI *CreatePseudoConsole_t)(COORD size, HANDLE hInput,
+                                               HANDLE hOutput, DWORD dwFlags,
+                                               HPCON *phPC);
+
+typedef HRESULT(WINAPI *ResizePseudoConsole_t)(HPCON hPC, COORD size);
+
+typedef VOID(WINAPI *ClosePseudoConsole_t)(HPCON hPC);
+
+class ConPTY {
+public:
+  static bool Initialize() {
+    static bool initialized = false;
+    static bool success = false;
+
+    if (!initialized) {
+      initialized = true;
+
+      HMODULE hMod = LoadLibraryW(L"kernel32.dll");
+      if (!hMod) {
+        return false;
+      }
+
+      pCreate =
+          (CreatePseudoConsole_t)GetProcAddress(hMod, "CreatePseudoConsole");
+      pClose = (ClosePseudoConsole_t)GetProcAddress(hMod, "ClosePseudoConsole");
+
+      success = (pCreate && pClose);
+    }
+
+    return success;
+  }
+
+  static bool IsAvailable() { return Initialize(); }
+
+  static CreatePseudoConsole_t Create() {
+    Initialize();
+    return pCreate;
+  }
+
+  static ClosePseudoConsole_t Close() {
+    Initialize();
+    return pClose;
+  }
+
+private:
+  static CreatePseudoConsole_t pCreate;
+  static ClosePseudoConsole_t pClose;
+};
+
+CreatePseudoConsole_t ConPTY::pCreate = nullptr;
+ClosePseudoConsole_t ConPTY::pClose = nullptr;
 
-llvm::Error PseudoTerminalWindows::OpenFirstAvailablePrimary(int oflag) {
+llvm::Error PseudoConsole::OpenPseudoConsole() {
+  if (!ConPTY::IsAvailable())
+    return llvm::make_error<llvm::StringError>("ConPTY is not available",
+                                               llvm::errc::io_error);
   HRESULT hr;
   HANDLE hInputRead = INVALID_HANDLE_VALUE;
   HANDLE hInputWrite = INVALID_HANDLE_VALUE;
@@ -48,7 +94,7 @@ llvm::Error PseudoTerminalWindows::OpenFirstAvailablePrimary(int oflag) {
 
   COORD consoleSize{256, 25};
   HPCON hPC = INVALID_HANDLE_VALUE;
-  hr = CreatePseudoConsole(consoleSize, hInputRead, hOutputWrite, 0, &hPC);
+  hr = ConPTY::Create()(consoleSize, hInputRead, hOutputWrite, 0, &hPC);
   CloseHandle(hInputRead);
   CloseHandle(hOutputWrite);
 
@@ -69,3 +115,13 @@ llvm::Error PseudoTerminalWindows::OpenFirstAvailablePrimary(int oflag) {
 
   return llvm::Error::success();
 }
+
+void PseudoConsole::Close() {
+  if (m_conpty_handle != INVALID_HANDLE_VALUE)
+    ConPTY::Close()(m_conpty_handle);
+  CloseHandle(m_conpty_input);
+  CloseHandle(m_conpty_output);
+  m_conpty_handle = INVALID_HANDLE_VALUE;
+  m_conpty_input = INVALID_HANDLE_VALUE;
+  m_conpty_output = INVALID_HANDLE_VALUE;
+}
\ No newline at end of file
diff --git a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
index befc28b09d185..427b2ce4c21fe 100644
--- a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
+++ b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
@@ -488,12 +488,14 @@ lldb::ProcessSP PlatformPOSIX::DebugProcess(ProcessLaunchInfo &launch_info,
   if (error.Success()) {
     // Hook up process PTY if we have one (which we should for local debugging
     // with llgs).
+#ifndef _WIN32 // TODO: Implement on Windows
     int pty_fd = launch_info.GetPTY().ReleasePrimaryFileDescriptor();
     if (pty_fd != PseudoTerminal::invalid_fd) {
       process_sp->SetSTDIOFileDescriptor(pty_fd);
       LLDB_LOG(log, "hooked up STDIO pty to process");
     } else
       LLDB_LOG(log, "not using process STDIO pty");
+#endif
   } else {
     LLDB_LOG(log, "{0}", error);
     // FIXME figure out appropriate cleanup here. Do we delete the process?
diff --git a/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp b/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
index c182d3d862269..1054892496732 100644
--- a/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
+++ b/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
@@ -235,10 +235,12 @@ lldb::ProcessSP PlatformQemuUser::DebugProcess(ProcessLaunchInfo &launch_info,
   if (error.Fail())
     return nullptr;
 
+#ifndef _WIN32 // TODO: Implement on Windows
   if (launch_info.GetPTY().GetPrimaryFileDescriptor() !=
       PseudoTerminal::invalid_fd)
     process_sp->SetSTDIOFileDescriptor(
         launch_info.GetPTY().ReleasePrimaryFileDescriptor());
+#endif
 
   return process_sp;
 }
diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
index a65324e1c1434..42884c893c6ab 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
@@ -524,7 +524,7 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info,
     return nullptr;
   error = process_sp->Launch(launch_info);
   if (error.Success())
-    process_sp->SetPseudoTerminalHandle(launch_info.GetPTYSP());
+    process_sp->SetPseudoConsoleHandle(launch_info.GetPTYSP());
   else
     LLDB_LOGF(log, "Platform::%s LaunchProcess() failed: %s", __FUNCTION__,
               error.AsCString());
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 79dd46ba319d6..e73b5749edde0 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -15,6 +15,7 @@
 #include "lldb/Host/HostNativeProcessBase.h"
 #include "lldb/Host/HostProcess.h"
 #include "lldb/Host/ProcessLaunchInfo.h"
+#include "lldb/Host/PseudoTerminal.h"
 #include "lldb/Host/windows/AutoHandle.h"
 #include "lldb/Host/windows/HostThreadWindows.h"
 #include "lldb/Host/windows/ProcessLauncherWindows.h"
@@ -47,9 +48,10 @@ namespace lldb_private {
 NativeProcessWindows::NativeProcessWindows(ProcessLaunchInfo &launch_info,
                                            NativeDelegate &delegate,
                                            llvm::Error &E)
-    : NativeProcessProtocol(LLDB_INVALID_PROCESS_ID,
-                            launch_info.GetPTY().ReleasePrimaryFileDescriptor(),
-                            delegate),
+    : NativeProcessProtocol(
+          LLDB_INVALID_PROCESS_ID,
+          PseudoTerminal::invalid_fd, // TODO: Implement on Windows
+          delegate),
       ProcessDebugger(), m_arch(launch_info.GetArchitecture()) {
   ErrorAsOutParameter EOut(&E);
   DebugDelegateSP delegate_sp(new NativeDebugDelegate(*this));
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 17961c72e56a8..2dffe48ad465c 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -1087,10 +1087,10 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
   bool m_is_running = false;
 };
 
-void ProcessWindows::SetPseudoTerminalHandle(
-    const std::shared_ptr<PseudoTerminal> &pty) {
+void ProcessWindows::SetPseudoConsoleHandle(
+    const std::shared_ptr<PseudoConsole> &pty) {
   m_stdio_communication.SetConnection(
-      std::make_unique<ConnectionGenericFile>(pty->GetPrimaryHandle(), false));
+      std::make_unique<ConnectionGenericFile>(pty->GetSTDOUTHandle(), false));
   if (m_stdio_communication.IsConnected()) {
     m_stdio_communication.SetReadThreadBytesReceivedCallback(
         STDIOReadThreadBytesReceived, this);
@@ -1101,7 +1101,7 @@ void ProcessWindows::SetPseudoTerminalHandle(
       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, pty->GetSecondaryHandle());
+            this, pty->GetSTDINHandle());
     }
   }
 }
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
index 49596e49a9f82..33e4de6b85932 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
@@ -9,7 +9,7 @@
 #ifndef liblldb_Plugins_Process_Windows_Common_ProcessWindows_H_
 #define liblldb_Plugins_Process_Windows_Common_ProcessWindows_H_
 
-#include "lldb/Host/windows/PseudoTerminalWindows.h"
+#include "lldb/Host/windows/PseudoConsole.h"
 #include "lldb/Target/Process.h"
 #include "lldb/Utility/Status.h"
 #include "lldb/lldb-forward.h"
@@ -99,7 +99,7 @@ class ProcessWindows : public Process, public ProcessDebugger {
                            bool notify = true) override;
 
   void
-  SetPseudoTerminalHandle(const std::shared_ptr<PseudoTerminal> &pty) override;
+  SetPseudoConsoleHandle(const std::shared_ptr<PseudoConsole> &pty) override;
 
 protected:
   ProcessWindows(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp);
diff --git a/lldb/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp
index 5b0930cf26b77..67a5857c12aa0 100644
--- a/lldb/source/Target/Platform.cpp
+++ b/lldb/source/Target/Platform.cpp
@@ -1054,10 +1054,12 @@ lldb::ProcessSP Platform::DebugProcess(ProcessLaunchInfo &launch_info,
         // been used where the secondary side was given as the file to open for
         // stdin/out/err after we have already opened the primary so we can
         // read/write stdin/out/err.
+#ifndef _WIN32
         int pty_fd = launch_info.GetPTY().ReleasePrimaryFileDescriptor();
         if (pty_fd != PseudoTerminal::invalid_fd) {
           process_sp->SetSTDIOFileDescriptor(pty_fd);
         }
+#endif
       } else {
         LLDB_LOGF(log, "Platform::%s Attach() failed: %s", __FUNCTION__,
                   error.AsCString());
diff --git a/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn b/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn
index e91b5ae5c9d24..f200a637ae99a 100644
--- a/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn
+++ b/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn
@@ -75,7 +75,7 @@ static_library("Host") {
       "windows/MainLoopWindows.cpp",
       "windows/PipeWindows.cpp",
       "windows/ProcessLauncherWindows.cpp",
-      "windows/PseudoTerminalWindows.cpp",
+      "windows/PseudoConsole.cpp",
       "windows/ProcessRunLock.cpp",
     ]
   } else {

>From 1a659e12cf86f3afdf010f91664d1c306565e302 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Thu, 11 Dec 2025 15:21:34 +0000
Subject: [PATCH 6/6] make ConPTY thread safe

---
 lldb/source/Host/windows/PseudoConsole.cpp    | 20 +++++++++++++------
 .../Platform/Windows/PlatformWindows.cpp      |  2 ++
 2 files changed, 16 insertions(+), 6 deletions(-)

diff --git a/lldb/source/Host/windows/PseudoConsole.cpp b/lldb/source/Host/windows/PseudoConsole.cpp
index e882faac25150..342d0e02c9420 100644
--- a/lldb/source/Host/windows/PseudoConsole.cpp
+++ b/lldb/source/Host/windows/PseudoConsole.cpp
@@ -7,6 +7,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "lldb/Host/windows/PseudoConsole.h"
+
+#include <mutex>
+
 #include "lldb/Host/windows/windows.h"
 
 #include "llvm/Support/Errc.h"
@@ -25,11 +28,10 @@ typedef VOID(WINAPI *ClosePseudoConsole_t)(HPCON hPC);
 class ConPTY {
 public:
   static bool Initialize() {
-    static bool initialized = false;
-    static bool success = false;
+    std::lock_guard<std::mutex> guard(m_initialized_mutex);
 
-    if (!initialized) {
-      initialized = true;
+    if (!m_initialized) {
+      m_initialized = true;
 
       HMODULE hMod = LoadLibraryW(L"kernel32.dll");
       if (!hMod) {
@@ -40,10 +42,10 @@ class ConPTY {
           (CreatePseudoConsole_t)GetProcAddress(hMod, "CreatePseudoConsole");
       pClose = (ClosePseudoConsole_t)GetProcAddress(hMod, "ClosePseudoConsole");
 
-      success = (pCreate && pClose);
+      m_success = (pCreate && pClose);
     }
 
-    return success;
+    return m_success;
   }
 
   static bool IsAvailable() { return Initialize(); }
@@ -61,10 +63,16 @@ class ConPTY {
 private:
   static CreatePseudoConsole_t pCreate;
   static ClosePseudoConsole_t pClose;
+  static std::mutex m_initialized_mutex;
+  static bool m_initialized;
+  static bool m_success;
 };
 
 CreatePseudoConsole_t ConPTY::pCreate = nullptr;
 ClosePseudoConsole_t ConPTY::pClose = nullptr;
+std::mutex ConPTY::m_initialized_mutex{};
+bool ConPTY::m_initialized = false;
+bool ConPTY::m_success = false;
 
 llvm::Error PseudoConsole::OpenPseudoConsole() {
   if (!ConPTY::IsAvailable())
diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
index 42884c893c6ab..3764610c5d05b 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
@@ -523,11 +523,13 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info,
   if (!process_sp)
     return nullptr;
   error = process_sp->Launch(launch_info);
+#ifdef _WIN32
   if (error.Success())
     process_sp->SetPseudoConsoleHandle(launch_info.GetPTYSP());
   else
     LLDB_LOGF(log, "Platform::%s LaunchProcess() failed: %s", __FUNCTION__,
               error.AsCString());
+#endif
 
   return process_sp;
 }



More information about the llvm-commits mailing list