[Lldb-commits] [lldb] [llvm] [lldb][windows] add Windows Virtual Console support (PR #168729)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Wed Dec 17 08:23:52 PST 2025
https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/168729
>From e092d04c9de6bedc4e5a8551d5f555a662af3bb6 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 01/12] [lldb][windows] add Windows Virtual Console support
---
lldb/include/lldb/Host/ProcessLaunchInfo.h | 4 +-
lldb/include/lldb/Host/PseudoTerminal.h | 26 ++-
.../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, 371 insertions(+), 51 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 245deec997d80..20a417a4fd28b 100644
--- a/lldb/include/lldb/Host/PseudoTerminal.h
+++ b/lldb/include/lldb/Host/PseudoTerminal.h
@@ -82,6 +82,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 +106,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 +117,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 +148,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 e983c527a174a..0879a13fcacb7 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 608003d2b951563c69ad58dd8db5d9c151a32e6c 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 02/12] 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 7e01dabba31331bd4f36cb451393de2c2a00c4a7 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 03/12] 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 0879a13fcacb7..c97109c8d0f13 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 c4cb8a313825d34e6834ac21aea03f459aaf2ea6 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 04/12] 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 c97109c8d0f13..f958fd965349e 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 97ba07f68dbdbf31efe42b60d4b7b1aa71916581 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 05/12] dynamically load CreatePseudoConsole
---
lldb/include/lldb/Host/ProcessLaunchInfo.h | 14 ++++
lldb/include/lldb/Host/PseudoTerminal.h | 26 +-----
.../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, 204 insertions(+), 112 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 20a417a4fd28b..245deec997d80 100644
--- a/lldb/include/lldb/Host/PseudoTerminal.h
+++ b/lldb/include/lldb/Host/PseudoTerminal.h
@@ -82,16 +82,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
@@ -106,8 +96,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
@@ -117,17 +105,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.
///
@@ -148,7 +126,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 f958fd965349e..ae79171337726 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 44fc2004e990f5978a48d66df44c325c12ac3324 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 06/12] 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;
}
>From 57c59420cd6f2392b33933e2d20fed42d5a89909 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Tue, 16 Dec 2025 15:00:52 +0000
Subject: [PATCH 07/12] fix variable declaration
---
lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
index 3764610c5d05b..f106c01601e29 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
@@ -496,7 +496,6 @@ 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,
@@ -526,9 +525,11 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info,
#ifdef _WIN32
if (error.Success())
process_sp->SetPseudoConsoleHandle(launch_info.GetPTYSP());
- else
+ else {
+ Log *log = GetLog(LLDBLog::Platform);
LLDB_LOGF(log, "Platform::%s LaunchProcess() failed: %s", __FUNCTION__,
error.AsCString());
+ }
#endif
return process_sp;
>From 241d47210c4657693a7e7c4e8fb7662778710321 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Tue, 16 Dec 2025 17:50:27 +0000
Subject: [PATCH 08/12] address comments
---
lldb/source/Host/windows/PseudoConsole.cpp | 2 +-
lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lldb/source/Host/windows/PseudoConsole.cpp b/lldb/source/Host/windows/PseudoConsole.cpp
index 342d0e02c9420..b509525a1135a 100644
--- a/lldb/source/Host/windows/PseudoConsole.cpp
+++ b/lldb/source/Host/windows/PseudoConsole.cpp
@@ -100,7 +100,7 @@ llvm::Error PseudoConsole::OpenPseudoConsole() {
return llvm::errorCodeToError(
std::error_code(GetLastError(), std::system_category()));
- COORD consoleSize{256, 25};
+ COORD consoleSize{80, 25};
HPCON hPC = INVALID_HANDLE_VALUE;
hr = ConPTY::Create()(consoleSize, hInputRead, hOutputWrite, 0, &hPC);
CloseHandle(hInputRead);
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;
}
>From c5c95da54557ea45b4e624d79d92bc252172e451 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Tue, 16 Dec 2025 18:43:11 +0000
Subject: [PATCH 09/12] refactor class
---
.../Process/Windows/Common/ProcessWindows.cpp | 34 +++----------------
1 file changed, 4 insertions(+), 30 deletions(-)
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 2dffe48ad465c..3a1020a11a749 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -1027,17 +1027,6 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
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()) {
@@ -1048,28 +1037,13 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
}
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;
- }
+ }
+ if (StateIsRunningState(m_process->GetState())) {
+ m_process->SendAsyncInterrupt();
+ return true;
}
return false;
}
>From f2c1f8039c2c3dd61f261c6bdf45cb011ec1dda1 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 17 Dec 2025 15:33:09 +0000
Subject: [PATCH 10/12] address comments
---
lldb/include/lldb/Host/ProcessLaunchInfo.h | 22 ++++++++-----------
.../include/lldb/Host/windows/PseudoConsole.h | 20 +++++++++--------
lldb/source/Host/common/ProcessLaunchInfo.cpp | 16 ++------------
lldb/source/Host/windows/PseudoConsole.cpp | 2 +-
.../Process/Windows/Common/ProcessWindows.cpp | 16 +++++++++-----
5 files changed, 34 insertions(+), 42 deletions(-)
diff --git a/lldb/include/lldb/Host/ProcessLaunchInfo.h b/lldb/include/lldb/Host/ProcessLaunchInfo.h
index 55ebcee8ca705..1bddface440e7 100644
--- a/lldb/include/lldb/Host/ProcessLaunchInfo.h
+++ b/lldb/include/lldb/Host/ProcessLaunchInfo.h
@@ -27,6 +27,12 @@
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.
@@ -122,15 +128,9 @@ 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; }
+ PTY &GetPTY() const { return *m_pty; }
- std::shared_ptr<PseudoTerminal> GetPTYSP() const { return m_pty; }
-#endif
+ std::shared_ptr<PTY> GetPTYSP() const { return m_pty; }
void SetLaunchEventData(const char *data) { m_event_data.assign(data); }
@@ -148,11 +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
-#ifdef _WIN32
- std::shared_ptr<PseudoConsole> m_pty;
-#else
- std::shared_ptr<PseudoTerminal> m_pty;
-#endif
+ 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
index fdca79ff27ce0..c3bf817cf831d 100644
--- a/lldb/include/lldb/Host/windows/PseudoConsole.h
+++ b/lldb/include/lldb/Host/windows/PseudoConsole.h
@@ -6,13 +6,15 @@
//
//===----------------------------------------------------------------------===//
-#ifndef liblldb_Host_Windows_PseudoConsole_H_
-#define liblldb_Host_Windows_PseudoConsole_H_
+#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 {
@@ -31,7 +33,7 @@ class PseudoConsole {
/// \return
/// The ConPTY HPCON handle, or INVALID_HANDLE_VALUE if it is currently
/// invalid.
- void *GetPseudoTerminalHandle() { return m_conpty_handle; };
+ HPCON GetPseudoTerminalHandle() { return m_conpty_handle; };
/// The STDOUT read HANDLE accessor.
///
@@ -40,7 +42,7 @@ class PseudoConsole {
/// \return
/// The STDOUT read HANDLE, or INVALID_HANDLE_VALUE if it is currently
/// invalid.
- void *GetSTDOUTHandle() const { return m_conpty_output; };
+ HANDLE GetSTDOUTHandle() const { return m_conpty_output; };
/// The STDIN write HANDLE accessor.
///
@@ -49,13 +51,13 @@ class PseudoConsole {
/// \return
/// The STDIN write HANDLE, or INVALID_HANDLE_VALUE if it is currently
/// invalid.
- void *GetSTDINHandle() const { return m_conpty_input; };
+ HANDLE 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);
+ 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_
+#endif // LIBLLDB_HOST_WINDOWS_PSEUDOCONSOLE_H_
diff --git a/lldb/source/Host/common/ProcessLaunchInfo.cpp b/lldb/source/Host/common/ProcessLaunchInfo.cpp
index 256bf713b7372..e82cc11187fe5 100644
--- a/lldb/source/Host/common/ProcessLaunchInfo.cpp
+++ b/lldb/source/Host/common/ProcessLaunchInfo.cpp
@@ -33,13 +33,7 @@ using namespace lldb_private;
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<PseudoConsole>();
-#else
- m_pty = std::make_shared<PseudoTerminal>();
-#endif
-}
+ m_file_actions(), m_pty(new PTY), m_monitor_callback(nullptr) {}
ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec,
const FileSpec &stdout_file_spec,
@@ -47,13 +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() {
-#ifdef _WIN32
- m_pty = std::make_shared<PseudoConsole>();
-#else
- m_pty = std::make_shared<PseudoTerminal>();
-#endif
-
+ m_file_actions(), m_pty(new PTY) {
if (stdin_file_spec) {
FileAction file_action;
const bool read = true;
diff --git a/lldb/source/Host/windows/PseudoConsole.cpp b/lldb/source/Host/windows/PseudoConsole.cpp
index b509525a1135a..9a12a9fa435f4 100644
--- a/lldb/source/Host/windows/PseudoConsole.cpp
+++ b/lldb/source/Host/windows/PseudoConsole.cpp
@@ -132,4 +132,4 @@ void PseudoConsole::Close() {
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/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 3a1020a11a749..127dd0f59e9ae 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -977,7 +977,7 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
SetIsDone(false);
SetIsRunning(true);
- HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
+ HANDLE hStdin = (HANDLE)_get_osfhandle(m_read_file.GetDescriptor());
HANDLE hInterrupt = (HANDLE)_get_osfhandle(m_pipe.GetReadFileDescriptor());
HANDLE waitHandles[2] = {hStdin, hInterrupt};
@@ -1009,9 +1009,10 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
if (!ReadFile(hInterrupt, &ch, 1, &read, nullptr) || read != 1)
goto exit_loop;
- if (ch == 'q')
+ if (ch == eControlOpQuit)
goto exit_loop;
- if (ch == 'i' && StateIsRunningState(m_process->GetState()))
+ if (ch == eControlOpInterrupt &&
+ StateIsRunningState(m_process->GetState()))
m_process->SendAsyncInterrupt();
break;
}
@@ -1028,7 +1029,7 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
std::lock_guard<std::mutex> guard(m_mutex);
SetIsDone(true);
if (m_is_running) {
- char ch = 'q'; // Send 'q' for quit
+ 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}");
@@ -1038,7 +1039,7 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
bool Interrupt() override {
if (m_active) {
- char ch = 'i'; // Send 'i' for interrupt
+ char ch = eControlOpInterrupt;
return !errorToBool(m_pipe.Write(&ch, 1).takeError());
}
if (StateIsRunningState(m_process->GetState())) {
@@ -1059,6 +1060,11 @@ class IOHandlerProcessSTDIOWindows : public IOHandler {
Pipe m_pipe;
std::mutex m_mutex;
bool m_is_running = false;
+
+ enum ControlOp : char {
+ eControlOpQuit = 'q',
+ eControlOpInterrupt = 'i',
+ };
};
void ProcessWindows::SetPseudoConsoleHandle(
>From 13c300553f81e7df8bdddc80d04b42bfd6f85e5c Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 17 Dec 2025 16:02:11 +0000
Subject: [PATCH 11/12] refactor dynamic loading of CreatePseudoConsole
---
lldb/source/Host/windows/PseudoConsole.cpp | 68 ++++++++--------------
1 file changed, 23 insertions(+), 45 deletions(-)
diff --git a/lldb/source/Host/windows/PseudoConsole.cpp b/lldb/source/Host/windows/PseudoConsole.cpp
index 9a12a9fa435f4..312faded0da5d 100644
--- a/lldb/source/Host/windows/PseudoConsole.cpp
+++ b/lldb/source/Host/windows/PseudoConsole.cpp
@@ -21,61 +21,38 @@ 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() {
- std::lock_guard<std::mutex> guard(m_initialized_mutex);
-
- if (!m_initialized) {
- m_initialized = true;
-
- HMODULE hMod = LoadLibraryW(L"kernel32.dll");
- if (!hMod) {
- return false;
- }
-
- pCreate =
- (CreatePseudoConsole_t)GetProcAddress(hMod, "CreatePseudoConsole");
- pClose = (ClosePseudoConsole_t)GetProcAddress(hMod, "ClosePseudoConsole");
-
- m_success = (pCreate && pClose);
- }
-
- return m_success;
+struct Kernel32 {
+ Kernel32() {
+ hModule = LoadLibraryW(L"kernel32.dll");
+ CreatePseudoConsole_ =
+ (CreatePseudoConsole_t)GetProcAddress(hModule, "CreatePseudoConsole");
+ ClosePseudoConsole_ =
+ (ClosePseudoConsole_t)GetProcAddress(hModule, "ClosePseudoConsole");
+ isAvailable = (CreatePseudoConsole_ && ClosePseudoConsole_);
}
- static bool IsAvailable() { return Initialize(); }
-
- static CreatePseudoConsole_t Create() {
- Initialize();
- return pCreate;
+ HRESULT CreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput,
+ DWORD dwFlags, HPCON *phPC) {
+ return CreatePseudoConsole_(size, hInput, hOutput, dwFlags, phPC);
}
- static ClosePseudoConsole_t Close() {
- Initialize();
- return pClose;
- }
+ VOID ClosePseudoConsole(HPCON hPC) { return ClosePseudoConsole_(hPC); }
+
+ bool IsAvailable() { return isAvailable; }
private:
- static CreatePseudoConsole_t pCreate;
- static ClosePseudoConsole_t pClose;
- static std::mutex m_initialized_mutex;
- static bool m_initialized;
- static bool m_success;
+ HMODULE hModule;
+ CreatePseudoConsole_t CreatePseudoConsole_;
+ ClosePseudoConsole_t ClosePseudoConsole_;
+ bool isAvailable;
};
-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;
+static Kernel32 kernel32;
llvm::Error PseudoConsole::OpenPseudoConsole() {
- if (!ConPTY::IsAvailable())
+ if (!kernel32.IsAvailable())
return llvm::make_error<llvm::StringError>("ConPTY is not available",
llvm::errc::io_error);
HRESULT hr;
@@ -102,7 +79,8 @@ llvm::Error PseudoConsole::OpenPseudoConsole() {
COORD consoleSize{80, 25};
HPCON hPC = INVALID_HANDLE_VALUE;
- hr = ConPTY::Create()(consoleSize, hInputRead, hOutputWrite, 0, &hPC);
+ hr = kernel32.CreatePseudoConsole(consoleSize, hInputRead, hOutputWrite, 0,
+ &hPC);
CloseHandle(hInputRead);
CloseHandle(hOutputWrite);
@@ -126,7 +104,7 @@ llvm::Error PseudoConsole::OpenPseudoConsole() {
void PseudoConsole::Close() {
if (m_conpty_handle != INVALID_HANDLE_VALUE)
- ConPTY::Close()(m_conpty_handle);
+ kernel32.ClosePseudoConsole(m_conpty_handle);
CloseHandle(m_conpty_input);
CloseHandle(m_conpty_output);
m_conpty_handle = INVALID_HANDLE_VALUE;
>From e67b3726799da8cf09dc60a5389943c34b14ae9e Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 17 Dec 2025 16:23:28 +0000
Subject: [PATCH 12/12] add version check on Windows tests
---
.../Python/lldbsuite/test/decorators.py | 27 +++++++++++++++++--
.../tools/lldb-dap/launch/TestDAP_launch.py | 8 ++++++
2 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py
index 150f5bbd3868b..e78beb576b19f 100644
--- a/lldb/packages/Python/lldbsuite/test/decorators.py
+++ b/lldb/packages/Python/lldbsuite/test/decorators.py
@@ -781,9 +781,32 @@ 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/test/API/tools/lldb-dap/launch/TestDAP_launch.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
index b0e124303a3b0..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,6 +16,7 @@
class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase):
+ @skipIfWindows(major=10, build=1809)
def test_default(self):
"""
Tests the default launch of a simple program. No arguments,
@@ -75,6 +76,7 @@ def test_failing_console(self):
r"unexpected value, expected 'internalConsole\', 'integratedTerminal\' or 'externalTerminal\' at arguments.console",
)
+ @skipIfWindows(major=10, build=1809)
def test_termination(self):
"""
Tests the correct termination of lldb-dap upon a 'disconnect'
@@ -208,6 +210,7 @@ def test_disableSTDIO(self):
self.assertEqual(output, "", "expect no program output")
@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):
"""
@@ -230,6 +233,7 @@ def test_shellExpandArguments_enabled(self):
quote_path, line, 'verify "%s" expanded to "%s"' % (glob, program)
)
+ @skipIfWindows(major=10, build=1809)
def test_shellExpandArguments_disabled(self):
"""
Tests the default launch of a simple program with shell expansion
@@ -251,6 +255,7 @@ def test_shellExpandArguments_disabled(self):
quote_path, line, 'verify "%s" stayed to "%s"' % (glob, glob)
)
+ @skipIfWindows(major=10, build=1809)
def test_args(self):
"""
Tests launch of a simple program with arguments
@@ -275,6 +280,7 @@ def test_args(self):
'arg[%i] "%s" not in "%s"' % (i + 1, quoted_arg, lines[i]),
)
+ @skipIfWindows(major=10, build=1809)
def test_environment_with_object(self):
"""
Tests launch of a simple program with environment variables
@@ -551,6 +557,7 @@ def test_terminate_commands(self):
output = self.collect_console(pattern=terminateCommands[0])
self.verify_commands("terminateCommands", output, terminateCommands)
+ @skipIfWindows(major=10, build=1809)
def test_version(self):
"""
Tests that "initialize" response contains the "version" string the same
@@ -633,6 +640,7 @@ def test_stdio_redirection(self):
)
@skipIfAsan
+ @skipIfWindows(major=10, build=1809)
@skipIf(oslist=["linux"], archs=no_match(["x86_64"]))
@skipIfBuildType(["debug"])
def test_stdio_redirection_and_console(self):
More information about the lldb-commits
mailing list