Author: Charles Zablit
Date: 2025-12-18T10:29:38Z
New Revision: 7fe5953a44bbce16c0fcc5b52e47905c1719b7b3

URL: 
https://github.com/llvm/llvm-project/commit/7fe5953a44bbce16c0fcc5b52e47905c1719b7b3
DIFF: 
https://github.com/llvm/llvm-project/commit/7fe5953a44bbce16c0fcc5b52e47905c1719b7b3.diff

LOG: [lldb][windows] add Windows Virtual Console support (#168729)

Added: 
    lldb/include/lldb/Host/windows/PseudoConsole.h
    lldb/source/Host/windows/PseudoConsole.cpp

Modified: 
    lldb/include/lldb/Host/ProcessLaunchInfo.h
    lldb/include/lldb/Target/Process.h
    lldb/packages/Python/lldbsuite/test/decorators.py
    lldb/source/Host/CMakeLists.txt
    lldb/source/Host/common/ProcessLaunchInfo.cpp
    lldb/source/Host/windows/ProcessLauncherWindows.cpp
    lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
    lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
    lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp
    lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
    lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
    lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
    lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
    lldb/source/Target/Platform.cpp
    lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
    llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Host/ProcessLaunchInfo.h 
b/lldb/include/lldb/Host/ProcessLaunchInfo.h
index 25762bc65295d..1bddface440e7 100644
--- a/lldb/include/lldb/Host/ProcessLaunchInfo.h
+++ b/lldb/include/lldb/Host/ProcessLaunchInfo.h
@@ -17,12 +17,22 @@
 
 #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"
 
 namespace lldb_private {
 
+#if defined(_WIN32)
+using PTY = PseudoConsole;
+#else
+using PTY = PseudoTerminal;
+#endif
+
 // ProcessLaunchInfo
 //
 // Describes any information that is required to launch a process.
@@ -118,7 +128,9 @@ class ProcessLaunchInfo : public ProcessInfo {
 
   bool MonitorProcess() const;
 
-  PseudoTerminal &GetPTY() { return *m_pty; }
+  PTY &GetPTY() const { return *m_pty; }
+
+  std::shared_ptr<PTY> GetPTYSP() const { return m_pty; }
 
   void SetLaunchEventData(const char *data) { m_event_data.assign(data); }
 
@@ -136,7 +148,7 @@ 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
-  std::shared_ptr<PseudoTerminal> m_pty;
+  std::shared_ptr<PTY> m_pty;
   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/windows/PseudoConsole.h 
b/lldb/include/lldb/Host/windows/PseudoConsole.h
new file mode 100644
index 0000000000000..c3bf817cf831d
--- /dev/null
+++ b/lldb/include/lldb/Host/windows/PseudoConsole.h
@@ -0,0 +1,63 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LIBLLDB_HOST_WINDOWS_PSEUDOCONSOLE_H_
+#define LIBLLDB_HOST_WINDOWS_PSEUDOCONSOLE_H_
+
+#include "llvm/Support/Error.h"
+#include <string>
+
+#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x20016
+typedef void *HANDLE;
+typedef void *HPCON;
+
+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.
+  HPCON 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.
+  HANDLE 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.
+  HANDLE GetSTDINHandle() const { return m_conpty_input; };
+
+protected:
+  HANDLE m_conpty_handle = ((HANDLE)(long long)-1);
+  HANDLE m_conpty_output = ((HANDLE)(long long)-1);
+  HANDLE m_conpty_input = ((HANDLE)(long long)-1);
+};
+}; // namespace lldb_private
+
+#endif // LIBLLDB_HOST_WINDOWS_PSEUDOCONSOLE_H_

diff  --git a/lldb/include/lldb/Target/Process.h 
b/lldb/include/lldb/Target/Process.h
index 8e6c16cbfe0fc..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 
diff erentiation 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.
   ///

diff  --git a/lldb/packages/Python/lldbsuite/test/decorators.py 
b/lldb/packages/Python/lldbsuite/test/decorators.py
index 150f5bbd3868b..6f388cb090f41 100644
--- a/lldb/packages/Python/lldbsuite/test/decorators.py
+++ b/lldb/packages/Python/lldbsuite/test/decorators.py
@@ -781,9 +781,35 @@ def skipIfLinux(func):
     return skipIfPlatform(["linux"])(func)
 
 
-def skipIfWindows(func):
+def skipIfWindows(func=None, major=None, build=None):
     """Decorate the item to skip tests that should be skipped on Windows."""
-    return skipIfPlatform(["windows"])(func)
+
+    def decorator(func):
+        if major is None and build is None:
+            return skipIfPlatform(["windows"])(func)
+        else:
+            import platform
+            import sys
+
+            def version_check():
+                check_major = 0 if major is None else major
+                check_build = 0 if build is None else build
+                if platform.system() != "Windows":
+                    return False
+                win_version = sys.getwindowsversion()
+                return (
+                    win_version.major >= check_major
+                    and win_version.build >= check_build
+                )
+
+            return unittest.skipIf(
+                version_check(),
+                f"Test is skipped on Windows major={major} build={build}",
+            )(func)
+
+    if func is not None:
+        return decorator(func)
+    return decorator
 
 
 def skipIfWindowsAndNonEnglish(func):

diff  --git a/lldb/source/Host/CMakeLists.txt b/lldb/source/Host/CMakeLists.txt
index 3184d3a1ead0d..8ad485fa40285 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/PseudoConsole.cpp
     windows/ProcessRunLock.cpp
     )
 else()

diff  --git a/lldb/source/Host/common/ProcessLaunchInfo.cpp 
b/lldb/source/Host/common/ProcessLaunchInfo.cpp
index 49159cca9c57c..e82cc11187fe5 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/PseudoConsole.h"
+#else
 #include <climits>
 #endif
 
@@ -31,8 +33,7 @@ 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_pty(new PTY), m_monitor_callback(nullptr) {}
 
 ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec,
                                      const FileSpec &stdout_file_spec,
@@ -40,7 +41,7 @@ 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(), m_pty(new PTY) {
   if (stdin_file_spec) {
     FileAction file_action;
     const bool read = true;
@@ -208,13 +209,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;
 
@@ -229,6 +229,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/windows/ProcessLauncherWindows.cpp 
b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
index e983c527a174a..ae79171337726 100644
--- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp
+++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
@@ -9,6 +9,8 @@
 #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"
 #include "llvm/ADT/SmallVector.h"
@@ -87,9 +89,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);
@@ -120,13 +126,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 = std::move(*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 +157,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/PseudoConsole.cpp 
b/lldb/source/Host/windows/PseudoConsole.cpp
new file mode 100644
index 0000000000000..9204488930a3e
--- /dev/null
+++ b/lldb/source/Host/windows/PseudoConsole.cpp
@@ -0,0 +1,126 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/PseudoConsole.h"
+
+#include <mutex>
+
+#include "lldb/Host/windows/PipeWindows.h"
+#include "lldb/Host/windows/windows.h"
+#include "lldb/Utility/LLDBLog.h"
+
+#include "llvm/Support/Errc.h"
+#include "llvm/Support/Errno.h"
+
+using namespace lldb_private;
+
+typedef HRESULT(WINAPI *CreatePseudoConsole_t)(COORD size, HANDLE hInput,
+                                               HANDLE hOutput, DWORD dwFlags,
+                                               HPCON *phPC);
+
+typedef VOID(WINAPI *ClosePseudoConsole_t)(HPCON hPC);
+
+struct Kernel32 {
+  Kernel32() {
+    hModule = LoadLibraryW(L"kernel32.dll");
+    if (!hModule) {
+      llvm::Error err = llvm::errorCodeToError(
+          std::error_code(GetLastError(), std::system_category()));
+      LLDB_LOG_ERROR(GetLog(LLDBLog::Host), std::move(err),
+                     "Could not load kernel32: {0}");
+      return;
+    }
+    CreatePseudoConsole_ =
+        (CreatePseudoConsole_t)GetProcAddress(hModule, "CreatePseudoConsole");
+    ClosePseudoConsole_ =
+        (ClosePseudoConsole_t)GetProcAddress(hModule, "ClosePseudoConsole");
+    isAvailable = (CreatePseudoConsole_ && ClosePseudoConsole_);
+  }
+
+  HRESULT CreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput,
+                              DWORD dwFlags, HPCON *phPC) {
+    assert(CreatePseudoConsole_ && "CreatePseudoConsole is not available!");
+    return CreatePseudoConsole_(size, hInput, hOutput, dwFlags, phPC);
+  }
+
+  VOID ClosePseudoConsole(HPCON hPC) {
+    assert(ClosePseudoConsole_ && "ClosePseudoConsole is not available!");
+    return ClosePseudoConsole_(hPC);
+  }
+
+  bool IsConPTYAvailable() { return isAvailable; }
+
+private:
+  HMODULE hModule;
+  CreatePseudoConsole_t CreatePseudoConsole_;
+  ClosePseudoConsole_t ClosePseudoConsole_;
+  bool isAvailable;
+};
+
+static Kernel32 kernel32;
+
+llvm::Error PseudoConsole::OpenPseudoConsole() {
+  if (!kernel32.IsConPTYAvailable())
+    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;
+  HANDLE hOutputRead = INVALID_HANDLE_VALUE;
+  HANDLE hOutputWrite = INVALID_HANDLE_VALUE;
+
+  wchar_t pipe_name[MAX_PATH];
+  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);
+  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{80, 25};
+  HPCON hPC = INVALID_HANDLE_VALUE;
+  hr = kernel32.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();
+}
+
+void PseudoConsole::Close() {
+  if (m_conpty_handle != INVALID_HANDLE_VALUE)
+    kernel32.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;
+}

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/WebAssembly/PlatformWasm.cpp 
b/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp
index f77ac7abbb678..75fa815131c76 100644
--- a/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp
+++ b/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp
@@ -203,11 +203,11 @@ lldb::ProcessSP 
PlatformWasm::DebugProcess(ProcessLaunchInfo &launch_info,
 
     return nullptr;
   }
-
+#ifndef _WIN32
   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 c0c26cc5f1954..f106c01601e29 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
@@ -519,8 +519,18 @@ 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 nullptr;
+  error = process_sp->Launch(launch_info);
+#ifdef _WIN32
+  if (error.Success())
+    process_sp->SetPseudoConsoleHandle(launch_info.GetPTYSP());
+  else {
+    Log *log = GetLog(LLDBLog::Platform);
+    LLDB_LOGF(log, "Platform::%s LaunchProcess() failed: %s", __FUNCTION__,
+              error.AsCString());
+  }
+#endif
 
   return process_sp;
 }

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 4cc39f928ee1e..127dd0f59e9ae 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,142 @@ 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 = (HANDLE)_get_osfhandle(m_read_file.GetDescriptor());
+    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 == eControlOpQuit)
+          goto exit_loop;
+        if (ch == eControlOpInterrupt &&
+            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);
+    if (m_is_running) {
+      char ch = eControlOpQuit;
+      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 {
+    if (m_active) {
+      char ch = eControlOpInterrupt;
+      return !errorToBool(m_pipe.Write(&ch, 1).takeError());
+    }
+    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;
+
+  enum ControlOp : char {
+    eControlOpQuit = 'q',
+    eControlOpInterrupt = 'i',
+  };
+};
+
+void ProcessWindows::SetPseudoConsoleHandle(
+    const std::shared_ptr<PseudoConsole> &pty) {
+  m_stdio_communication.SetConnection(
+      std::make_unique<ConnectionGenericFile>(pty->GetSTDOUTHandle(), 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->GetSTDINHandle());
+    }
+  }
+}
 } // 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..33e4de6b85932 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/PseudoConsole.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
+  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/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py 
b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
index ca881f1d817c5..2fdf1bb42ca09 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,7 @@
 
 
 class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase):
-    @skipIfWindows
+    @skipIfWindows(major=10, build=1809)
     def test_default(self):
         """
         Tests the default launch of a simple program. No arguments,
@@ -76,7 +76,7 @@ def test_failing_console(self):
             r"unexpected value, expected 'internalConsole\', 
'integratedTerminal\' or 'externalTerminal\' at arguments.console",
         )
 
-    @skipIfWindows
+    @skipIfWindows(major=10, build=1809)
     def test_termination(self):
         """
         Tests the correct termination of lldb-dap upon a 'disconnect'
@@ -209,8 +209,8 @@ 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
+    @skipIfWindows(major=10, build=1809)
     @expectedFailureAll(oslist=["freebsd", "netbsd"], 
bugnumber="llvm.org/pr48349")
     def test_shellExpandArguments_enabled(self):
         """
@@ -233,7 +233,7 @@ def test_shellExpandArguments_enabled(self):
                     quote_path, line, 'verify "%s" expanded to "%s"' % (glob, 
program)
                 )
 
-    @skipIfWindows
+    @skipIfWindows(major=10, build=1809)
     def test_shellExpandArguments_disabled(self):
         """
         Tests the default launch of a simple program with shell expansion
@@ -255,7 +255,7 @@ def test_shellExpandArguments_disabled(self):
                     quote_path, line, 'verify "%s" stayed to "%s"' % (glob, 
glob)
                 )
 
-    @skipIfWindows
+    @skipIfWindows(major=10, build=1809)
     def test_args(self):
         """
         Tests launch of a simple program with arguments
@@ -280,7 +280,7 @@ def test_args(self):
                 'arg[%i] "%s" not in "%s"' % (i + 1, quoted_arg, lines[i]),
             )
 
-    @skipIfWindows
+    @skipIfWindows(major=10, build=1809)
     def test_environment_with_object(self):
         """
         Tests launch of a simple program with environment variables
@@ -557,7 +557,7 @@ def test_terminate_commands(self):
         output = self.collect_console(pattern=terminateCommands[0])
         self.verify_commands("terminateCommands", output, terminateCommands)
 
-    @skipIfWindows
+    @skipIfWindows(major=10, build=1809)
     def test_version(self):
         """
         Tests that "initialize" response contains the "version" string the same
@@ -640,7 +640,7 @@ def test_stdio_redirection(self):
             )
 
     @skipIfAsan
-    @skipIfWindows
+    @skipIfWindows(major=10, build=1809)
     @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..f200a637ae99a 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/PseudoConsole.cpp",
       "windows/ProcessRunLock.cpp",
     ]
   } else {


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

Reply via email to