https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/168729
>From 2101873001ffd00cb6117e757b222fe197512f08 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Wed, 26 Nov 2025 18:25:23 +0000 Subject: [PATCH 1/9] [lldb][windows] add Windows Virtual Console support --- lldb/include/lldb/Host/ProcessLaunchInfo.h | 4 +- lldb/include/lldb/Host/PseudoTerminal.h | 42 +++- .../lldb/Host/windows/PseudoTerminalWindows.h | 39 ++++ lldb/include/lldb/Host/windows/windows.h | 4 +- lldb/include/lldb/Target/Process.h | 9 + lldb/source/Host/CMakeLists.txt | 1 + lldb/source/Host/common/ProcessLaunchInfo.cpp | 19 +- lldb/source/Host/common/PseudoTerminal.cpp | 4 +- .../Host/windows/ProcessLauncherWindows.cpp | 38 ++-- .../Host/windows/PseudoTerminalWindows.cpp | 69 +++++++ .../Platform/Windows/PlatformWindows.cpp | 11 +- .../Process/Windows/Common/ProcessWindows.cpp | 181 ++++++++++++++++-- .../Process/Windows/Common/ProcessWindows.h | 8 +- .../tools/lldb-dap/launch/TestDAP_launch.py | 8 - .../gn/secondary/lldb/source/Host/BUILD.gn | 1 + 15 files changed, 380 insertions(+), 58 deletions(-) create mode 100644 lldb/include/lldb/Host/windows/PseudoTerminalWindows.h create mode 100644 lldb/source/Host/windows/PseudoTerminalWindows.cpp diff --git a/lldb/include/lldb/Host/ProcessLaunchInfo.h b/lldb/include/lldb/Host/ProcessLaunchInfo.h index 25762bc65295d..e0528c5b80539 100644 --- a/lldb/include/lldb/Host/ProcessLaunchInfo.h +++ b/lldb/include/lldb/Host/ProcessLaunchInfo.h @@ -118,7 +118,9 @@ class ProcessLaunchInfo : public ProcessInfo { bool MonitorProcess() const; - PseudoTerminal &GetPTY() { return *m_pty; } + PseudoTerminal &GetPTY() const { return *m_pty; } + + std::shared_ptr<PseudoTerminal> GetPTYSP() const { return m_pty; } void SetLaunchEventData(const char *data) { m_event_data.assign(data); } diff --git a/lldb/include/lldb/Host/PseudoTerminal.h b/lldb/include/lldb/Host/PseudoTerminal.h index bd1e2f56241b2..c8443c71fbc62 100644 --- a/lldb/include/lldb/Host/PseudoTerminal.h +++ b/lldb/include/lldb/Host/PseudoTerminal.h @@ -35,11 +35,14 @@ class PseudoTerminal { /// Destructor /// - /// The destructor will close the primary and secondary file descriptors if - /// they are valid and ownership has not been released using one of: @li - /// PseudoTerminal::ReleasePrimaryFileDescriptor() @li - /// PseudoTerminal::ReleaseSaveFileDescriptor() - ~PseudoTerminal(); + /// The destructor will close the primary and secondary file + /// descriptor/HANDLEs if they are valid and ownership has not been released + /// using PseudoTerminal::Close(). + virtual ~PseudoTerminal(); + + /// Close all the file descriptors or Handles of the PseudoTerminal if they + /// are valid. + virtual void Close(); /// Close the primary file descriptor if it is valid. void ClosePrimaryFileDescriptor(); @@ -59,8 +62,7 @@ class PseudoTerminal { /// /// This class will close the file descriptors for the primary/secondary when /// the destructor is called. The file handles can be released using either: - /// @li PseudoTerminal::ReleasePrimaryFileDescriptor() @li - /// PseudoTerminal::ReleaseSaveFileDescriptor() + /// @li PseudoTerminal::ReleasePrimaryFileDescriptor() /// /// \return /// \b Parent process: a child process ID that is greater @@ -82,6 +84,16 @@ class PseudoTerminal { /// \see PseudoTerminal::ReleasePrimaryFileDescriptor() int GetPrimaryFileDescriptor() const; + /// The primary HANDLE accessor. + /// + /// This object retains ownership of the primary HANDLE when this + /// accessor is used. + /// + /// \return + /// The primary HANDLE, or INVALID_HANDLE_VALUE if the primary HANDLE is + /// not currently valid. + virtual void *GetPrimaryHandle() const { return ((void *)(long long)-1); }; + /// The secondary file descriptor accessor. /// /// This object retains ownership of the secondary file descriptor when this @@ -96,6 +108,8 @@ class PseudoTerminal { /// \see PseudoTerminal::ReleaseSecondaryFileDescriptor() int GetSecondaryFileDescriptor() const; + virtual void *GetSecondaryHandle() const { return ((void *)(long long)-1); }; + /// Get the name of the secondary pseudo terminal. /// /// A primary pseudo terminal should already be valid prior to @@ -105,7 +119,17 @@ class PseudoTerminal { /// The name of the secondary pseudo terminal. /// /// \see PseudoTerminal::OpenFirstAvailablePrimary() - std::string GetSecondaryName() const; + virtual std::string GetSecondaryName() const; + + /// The underlying Windows Pseudo Terminal HANDLE's accessor. + /// + /// This object retains ownership of the ConPTY's HANDLE when this + /// accessor is used. + /// + /// \return + /// The primary HANDLE, or INVALID_HANDLE_VALUE if the primary HANDLE is + /// not currently valid. + virtual void *GetPseudoTerminalHandle() { return ((void *)(long long)-1); }; /// Open the first available pseudo terminal. /// @@ -126,7 +150,7 @@ class PseudoTerminal { /// /// \see PseudoTerminal::GetPrimaryFileDescriptor() @see /// PseudoTerminal::ReleasePrimaryFileDescriptor() - llvm::Error OpenFirstAvailablePrimary(int oflag); + virtual llvm::Error OpenFirstAvailablePrimary(int oflag); /// Open the secondary for the current primary pseudo terminal. /// diff --git a/lldb/include/lldb/Host/windows/PseudoTerminalWindows.h b/lldb/include/lldb/Host/windows/PseudoTerminalWindows.h new file mode 100644 index 0000000000000..55868665b582b --- /dev/null +++ b/lldb/include/lldb/Host/windows/PseudoTerminalWindows.h @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_Host_Windows_PseudoTerminalWindows_H_ +#define liblldb_Host_Windows_PseudoTerminalWindows_H_ + +#include "lldb/Host/PseudoTerminal.h" +#include "lldb/Host/windows/windows.h" + +namespace lldb_private { + +class PseudoTerminalWindows : public PseudoTerminal { + +public: + void Close() override; + + HPCON GetPseudoTerminalHandle() override { return m_conpty_handle; }; + + HANDLE GetPrimaryHandle() const override { return m_conpty_output; }; + + HANDLE GetSecondaryHandle() const override { return m_conpty_input; }; + + std::string GetSecondaryName() const override { return ""; }; + + llvm::Error OpenFirstAvailablePrimary(int oflag) override; + +protected: + HANDLE m_conpty_handle = INVALID_HANDLE_VALUE; + HANDLE m_conpty_output = INVALID_HANDLE_VALUE; + HANDLE m_conpty_input = INVALID_HANDLE_VALUE; +}; +}; // namespace lldb_private + +#endif // liblldb_Host_Windows_PseudoTerminalWindows_H_ diff --git a/lldb/include/lldb/Host/windows/windows.h b/lldb/include/lldb/Host/windows/windows.h index d53d4b9967268..bb6695a112c7d 100644 --- a/lldb/include/lldb/Host/windows/windows.h +++ b/lldb/include/lldb/Host/windows/windows.h @@ -9,9 +9,9 @@ #ifndef LLDB_lldb_windows_h_ #define LLDB_lldb_windows_h_ -#define NTDDI_VERSION NTDDI_VISTA +#define NTDDI_VERSION NTDDI_WIN10_RS5 #undef _WIN32_WINNT // undef a previous definition to avoid warning -#define _WIN32_WINNT _WIN32_WINNT_VISTA +#define _WIN32_WINNT _WIN32_WINNT_WIN10 #define WIN32_LEAN_AND_MEAN #define NOGDI #undef NOMINMAX // undef a previous definition to avoid warning diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index 8e6c16cbfe0fc..d2fab11879681 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -2558,6 +2558,15 @@ void PruneThreadPlans(); /// \see lldb_private::ConnectionFileDescriptor void SetSTDIOFileDescriptor(int file_descriptor); + /// Windows equivalent of Process::SetSTDIOFileDescriptor, with a + /// PseudoTerminalWindows instead of a file descriptor. + /// + /// \param pty + /// The PseudoTerminal to use for process STDIO communication. It is not + /// managed by the created read thread. + virtual void + SetPseudoTerminalHandle(const std::shared_ptr<PseudoTerminal> &pty) {}; + // Add a permanent region of memory that should never be read or written to. // This can be used to ensure that memory reads or writes to certain areas of // memory never end up being sent to the DoReadMemory or DoWriteMemory diff --git a/lldb/source/Host/CMakeLists.txt b/lldb/source/Host/CMakeLists.txt index 3184d3a1ead0d..d2fd566e9a711 100644 --- a/lldb/source/Host/CMakeLists.txt +++ b/lldb/source/Host/CMakeLists.txt @@ -76,6 +76,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows") windows/MainLoopWindows.cpp windows/PipeWindows.cpp windows/ProcessLauncherWindows.cpp + windows/PseudoTerminalWindows.cpp windows/ProcessRunLock.cpp ) else() diff --git a/lldb/source/Host/common/ProcessLaunchInfo.cpp b/lldb/source/Host/common/ProcessLaunchInfo.cpp index 49159cca9c57c..c3beef7031f18 100644 --- a/lldb/source/Host/common/ProcessLaunchInfo.cpp +++ b/lldb/source/Host/common/ProcessLaunchInfo.cpp @@ -20,7 +20,9 @@ #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/FileSystem.h" -#if !defined(_WIN32) +#ifdef _WIN32 +#include "lldb/Host/windows/PseudoTerminalWindows.h" +#else #include <climits> #endif @@ -31,7 +33,12 @@ using namespace lldb_private; ProcessLaunchInfo::ProcessLaunchInfo() : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(0), - m_file_actions(), m_pty(new PseudoTerminal), m_monitor_callback(nullptr) { + m_file_actions(), m_monitor_callback(nullptr) { +#ifdef _WIN32 + m_pty = std::make_shared<PseudoTerminalWindows>(); +#else + m_pty = std::make_shared<PseudoTerminal>(); +#endif } ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec, @@ -40,7 +47,13 @@ ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec, const FileSpec &working_directory, uint32_t launch_flags) : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(launch_flags), - m_file_actions(), m_pty(new PseudoTerminal) { + m_file_actions() { +#ifdef _WIN32 + m_pty = std::make_shared<PseudoTerminalWindows>(); +#else + m_pty = std::make_shared<PseudoTerminal>(); +#endif + if (stdin_file_spec) { FileAction file_action; const bool read = true; diff --git a/lldb/source/Host/common/PseudoTerminal.cpp b/lldb/source/Host/common/PseudoTerminal.cpp index 53e91aff212a4..6c548701857c3 100644 --- a/lldb/source/Host/common/PseudoTerminal.cpp +++ b/lldb/source/Host/common/PseudoTerminal.cpp @@ -38,7 +38,9 @@ PseudoTerminal::PseudoTerminal() = default; // are valid and ownership has not been released using the // ReleasePrimaryFileDescriptor() or the ReleaseSaveFileDescriptor() member // functions. -PseudoTerminal::~PseudoTerminal() { +PseudoTerminal::~PseudoTerminal() { Close(); } + +void PseudoTerminal::Close() { ClosePrimaryFileDescriptor(); CloseSecondaryFileDescriptor(); } diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp index ac5383f20f58e..704c0ff8e6ae5 100644 --- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp +++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp @@ -87,9 +87,13 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, error.Clear(); STARTUPINFOEXW startupinfoex = {}; - startupinfoex.StartupInfo.cb = sizeof(startupinfoex); + startupinfoex.StartupInfo.cb = sizeof(STARTUPINFOEXW); startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + HPCON hPC = launch_info.GetPTY().GetPseudoTerminalHandle(); + bool use_pty = + hPC != INVALID_HANDLE_VALUE && launch_info.GetNumFileActions() == 0; + HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO); HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO); HANDLE stderr_handle = GetStdioHandle(launch_info, STDERR_FILENO); @@ -107,10 +111,10 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, /*dwAttributeCount=*/1, /*dwFlags=*/0, &attributelist_size); - startupinfoex.lpAttributeList = - static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attributelist_size)); - auto free_attributelist = - llvm::make_scope_exit([&] { free(startupinfoex.lpAttributeList); }); + startupinfoex.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc( + GetProcessHeap(), 0, attributelist_size); + auto free_attributelist = llvm::make_scope_exit( + [&] { HeapFree(GetProcessHeap(), 0, startupinfoex.lpAttributeList); }); if (!InitializeProcThreadAttributeList(startupinfoex.lpAttributeList, /*dwAttributeCount=*/1, /*dwFlags=*/0, &attributelist_size)) { @@ -120,13 +124,23 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, auto delete_attributelist = llvm::make_scope_exit( [&] { DeleteProcThreadAttributeList(startupinfoex.lpAttributeList); }); - auto inherited_handles_or_err = GetInheritedHandles( - launch_info, startupinfoex, stdout_handle, stderr_handle, stdin_handle); - if (!inherited_handles_or_err) { - error = Status(inherited_handles_or_err.getError()); - return HostProcess(); + std::vector<HANDLE> inherited_handles; + if (use_pty) { + if (!UpdateProcThreadAttribute(startupinfoex.lpAttributeList, 0, + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, hPC, + sizeof(hPC), NULL, NULL)) { + error = Status(::GetLastError(), eErrorTypeWin32); + return HostProcess(); + } + } else { + auto inherited_handles_or_err = GetInheritedHandles( + launch_info, startupinfoex, stdout_handle, stderr_handle, stdin_handle); + if (!inherited_handles_or_err) { + error = Status(inherited_handles_or_err.getError()); + return HostProcess(); + } + inherited_handles = *inherited_handles_or_err; } - std::vector<HANDLE> inherited_handles = *inherited_handles_or_err; const char *hide_console_var = getenv("LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE"); @@ -141,7 +155,7 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, if (launch_info.GetFlags().Test(eLaunchFlagDebug)) flags |= DEBUG_ONLY_THIS_PROCESS; - if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO)) + if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO) || use_pty) flags &= ~CREATE_NEW_CONSOLE; std::vector<wchar_t> environment = diff --git a/lldb/source/Host/windows/PseudoTerminalWindows.cpp b/lldb/source/Host/windows/PseudoTerminalWindows.cpp new file mode 100644 index 0000000000000..626df2f254644 --- /dev/null +++ b/lldb/source/Host/windows/PseudoTerminalWindows.cpp @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/windows/PseudoTerminalWindows.h" + +#include "llvm/Support/Errc.h" +#include "llvm/Support/Errno.h" + +using namespace lldb_private; + +void PseudoTerminalWindows::Close() { + if (m_conpty_handle != INVALID_HANDLE_VALUE) + ClosePseudoConsole(m_conpty_handle); + CloseHandle(m_conpty_input); + CloseHandle(m_conpty_output); + m_conpty_handle = INVALID_HANDLE_VALUE; + m_conpty_input = INVALID_HANDLE_VALUE; + m_conpty_output = INVALID_HANDLE_VALUE; +} + +llvm::Error PseudoTerminalWindows::OpenFirstAvailablePrimary(int oflag) { + HRESULT hr; + HANDLE hInputRead = INVALID_HANDLE_VALUE; + HANDLE hInputWrite = INVALID_HANDLE_VALUE; + HANDLE hOutputRead = INVALID_HANDLE_VALUE; + HANDLE hOutputWrite = INVALID_HANDLE_VALUE; + + wchar_t pipe_name[MAX_PATH]; + swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-%d-%p", + GetCurrentProcessId(), this); + + hOutputRead = + CreateNamedPipeW(pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, NULL); + hOutputWrite = CreateFileW(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + + if (!CreatePipe(&hInputRead, &hInputWrite, NULL, 0)) + return llvm::errorCodeToError( + std::error_code(GetLastError(), std::system_category())); + + COORD consoleSize{256, 25}; + HPCON hPC = INVALID_HANDLE_VALUE; + hr = CreatePseudoConsole(consoleSize, hInputRead, hOutputWrite, 0, &hPC); + CloseHandle(hInputRead); + CloseHandle(hOutputWrite); + + if (FAILED(hr)) { + CloseHandle(hInputWrite); + CloseHandle(hOutputRead); + return llvm::make_error<llvm::StringError>( + "Failed to create Windows ConPTY pseudo terminal", + llvm::errc::io_error); + } + + DWORD mode = PIPE_NOWAIT; + SetNamedPipeHandleState(hOutputRead, &mode, NULL, NULL); + + m_conpty_handle = hPC; + m_conpty_output = hOutputRead; + m_conpty_input = hInputWrite; + + return llvm::Error::success(); +} diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp index c0c26cc5f1954..da52bd59bdc4c 100644 --- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp +++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp @@ -496,6 +496,7 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info, // plugin, and PlatformWindows::DebugProcess is just a pass-through to get to // the process plugin. + Log *log = GetLog(LLDBLog::Platform); if (IsRemote()) { if (m_remote_platform_sp) return m_remote_platform_sp->DebugProcess(launch_info, debugger, target, @@ -519,8 +520,14 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info, // We need to launch and attach to the process. launch_info.GetFlags().Set(eLaunchFlagDebug); - if (process_sp) - error = process_sp->Launch(launch_info); + if (!process_sp) + return process_sp; + error = process_sp->Launch(launch_info); + if (error.Success()) + process_sp->SetPseudoTerminalHandle(launch_info.GetPTYSP()); + else + LLDB_LOGF(log, "Platform::%s LaunchProcess() failed: %s", __FUNCTION__, + error.AsCString()); return process_sp; } diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp index 4cc39f928ee1e..17961c72e56a8 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp @@ -13,6 +13,7 @@ #include <psapi.h> #include "lldb/Breakpoint/Watchpoint.h" +#include "lldb/Core/IOHandler.h" #include "lldb/Core/Module.h" #include "lldb/Core/ModuleSpec.h" #include "lldb/Core/PluginManager.h" @@ -21,12 +22,17 @@ #include "lldb/Host/HostInfo.h" #include "lldb/Host/HostNativeProcessBase.h" #include "lldb/Host/HostProcess.h" +#include "lldb/Host/Pipe.h" +#include "lldb/Host/PseudoTerminal.h" +#include "lldb/Host/windows/ConnectionGenericFileWindows.h" #include "lldb/Host/windows/HostThreadWindows.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Target/DynamicLoader.h" #include "lldb/Target/MemoryRegionInfo.h" #include "lldb/Target/StopInfo.h" #include "lldb/Target/Target.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" #include "lldb/Utility/State.h" #include "llvm/Support/ConvertUTF.h" @@ -123,22 +129,6 @@ ProcessWindows::ProcessWindows(lldb::TargetSP target_sp, ProcessWindows::~ProcessWindows() {} -size_t ProcessWindows::GetSTDOUT(char *buf, size_t buf_size, Status &error) { - error = Status::FromErrorString("GetSTDOUT unsupported on Windows"); - return 0; -} - -size_t ProcessWindows::GetSTDERR(char *buf, size_t buf_size, Status &error) { - error = Status::FromErrorString("GetSTDERR unsupported on Windows"); - return 0; -} - -size_t ProcessWindows::PutSTDIN(const char *buf, size_t buf_size, - Status &error) { - error = Status::FromErrorString("PutSTDIN unsupported on Windows"); - return 0; -} - Status ProcessWindows::EnableBreakpointSite(BreakpointSite *bp_site) { if (bp_site->HardwareRequired()) return Status::FromErrorString("Hardware breakpoints are not supported."); @@ -661,6 +651,7 @@ void ProcessWindows::OnExitProcess(uint32_t exit_code) { LLDB_LOG(log, "Process {0} exited with code {1}", GetID(), exit_code); TargetSP target = CalculateTarget(); + target->GetProcessLaunchInfo().GetPTY().Close(); if (target) { ModuleSP executable_module = target->GetExecutableModule(); ModuleList unloaded_modules; @@ -956,4 +947,162 @@ Status ProcessWindows::DisableWatchpoint(WatchpointSP wp_sp, bool notify) { return error; } + +class IOHandlerProcessSTDIOWindows : public IOHandler { +public: + IOHandlerProcessSTDIOWindows(Process *process, HANDLE conpty_input) + : IOHandler(process->GetTarget().GetDebugger(), + IOHandler::Type::ProcessIO), + m_process(process), + m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false), + m_write_file(conpty_input) { + m_pipe.CreateNew(); + } + + ~IOHandlerProcessSTDIOWindows() override = default; + + void SetIsRunning(bool running) { + std::lock_guard<std::mutex> guard(m_mutex); + SetIsDone(!running); + m_is_running = running; + } + + void Run() override { + if (!m_read_file.IsValid() || m_write_file == INVALID_HANDLE_VALUE || + !m_pipe.CanRead() || !m_pipe.CanWrite()) { + SetIsDone(true); + return; + } + + SetIsDone(false); + SetIsRunning(true); + + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hInterrupt = (HANDLE)_get_osfhandle(m_pipe.GetReadFileDescriptor()); + HANDLE waitHandles[2] = {hStdin, hInterrupt}; + + while (true) { + { + std::lock_guard<std::mutex> guard(m_mutex); + if (GetIsDone()) + goto exit_loop; + } + + DWORD result = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE); + switch (result) { + case WAIT_FAILED: + goto exit_loop; + case WAIT_OBJECT_0: { + char ch = 0; + DWORD read = 0; + if (!ReadFile(hStdin, &ch, 1, &read, nullptr) || read != 1) + goto exit_loop; + + DWORD written = 0; + if (!WriteFile(m_write_file, &ch, 1, &written, nullptr) || written != 1) + goto exit_loop; + break; + } + case WAIT_OBJECT_0 + 1: { + char ch = 0; + DWORD read = 0; + if (!ReadFile(hInterrupt, &ch, 1, &read, nullptr) || read != 1) + goto exit_loop; + + if (ch == 'q') + goto exit_loop; + if (ch == 'i' && StateIsRunningState(m_process->GetState())) + m_process->SendAsyncInterrupt(); + break; + } + default: + goto exit_loop; + } + } + + exit_loop:; + SetIsRunning(false); + } + + void Cancel() override { + std::lock_guard<std::mutex> guard(m_mutex); + SetIsDone(true); + // Only write to our pipe to cancel if we are in + // IOHandlerProcessSTDIO::Run(). We can end up with a python command that + // is being run from the command interpreter: + // + // (lldb) step_process_thousands_of_times + // + // In this case the command interpreter will be in the middle of handling + // the command and if the process pushes and pops the IOHandler thousands + // of times, we can end up writing to m_pipe without ever consuming the + // bytes from the pipe in IOHandlerProcessSTDIO::Run() and end up + // deadlocking when the pipe gets fed up and blocks until data is consumed. + if (m_is_running) { + char ch = 'q'; // Send 'q' for quit + if (llvm::Error err = m_pipe.Write(&ch, 1).takeError()) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Process), std::move(err), + "Pipe write failed: {0}"); + } + } + } + + bool Interrupt() override { + // Do only things that are safe to do in an interrupt context (like in a + // SIGINT handler), like write 1 byte to a file descriptor. This will + // interrupt the IOHandlerProcessSTDIO::Run() and we can look at the byte + // that was written to the pipe and then call + // m_process->SendAsyncInterrupt() from a much safer location in code. + if (m_active) { + char ch = 'i'; // Send 'i' for interrupt + return !errorToBool(m_pipe.Write(&ch, 1).takeError()); + } else { + // This IOHandler might be pushed on the stack, but not being run + // currently so do the right thing if we aren't actively watching for + // STDIN by sending the interrupt to the process. Otherwise the write to + // the pipe above would do nothing. This can happen when the command + // interpreter is running and gets a "expression ...". It will be on the + // IOHandler thread and sending the input is complete to the delegate + // which will cause the expression to run, which will push the process IO + // handler, but not run it. + + if (StateIsRunningState(m_process->GetState())) { + m_process->SendAsyncInterrupt(); + return true; + } + } + return false; + } + + void GotEOF() override {} + +private: + Process *m_process; + NativeFile m_read_file; // Read from this file (usually actual STDIN for LLDB + HANDLE m_write_file = + INVALID_HANDLE_VALUE; // Write to this file (usually the primary pty for + // getting io to debuggee) + Pipe m_pipe; + std::mutex m_mutex; + bool m_is_running = false; +}; + +void ProcessWindows::SetPseudoTerminalHandle( + const std::shared_ptr<PseudoTerminal> &pty) { + m_stdio_communication.SetConnection( + std::make_unique<ConnectionGenericFile>(pty->GetPrimaryHandle(), false)); + if (m_stdio_communication.IsConnected()) { + m_stdio_communication.SetReadThreadBytesReceivedCallback( + STDIOReadThreadBytesReceived, this); + m_stdio_communication.StartReadThread(); + + // Now read thread is set up, set up input reader. + { + std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); + if (!m_process_input_reader) + m_process_input_reader = std::make_shared<IOHandlerProcessSTDIOWindows>( + this, pty->GetSecondaryHandle()); + } + } +} } // namespace lldb_private diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h index 97284b7cd1436..49596e49a9f82 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h @@ -9,6 +9,7 @@ #ifndef liblldb_Plugins_Process_Windows_Common_ProcessWindows_H_ #define liblldb_Plugins_Process_Windows_Common_ProcessWindows_H_ +#include "lldb/Host/windows/PseudoTerminalWindows.h" #include "lldb/Target/Process.h" #include "lldb/Utility/Status.h" #include "lldb/lldb-forward.h" @@ -38,10 +39,6 @@ class ProcessWindows : public Process, public ProcessDebugger { ~ProcessWindows(); - size_t GetSTDOUT(char *buf, size_t buf_size, Status &error) override; - size_t GetSTDERR(char *buf, size_t buf_size, Status &error) override; - size_t PutSTDIN(const char *buf, size_t buf_size, Status &error) override; - llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } Status EnableBreakpointSite(BreakpointSite *bp_site) override; @@ -101,6 +98,9 @@ class ProcessWindows : public Process, public ProcessDebugger { Status DisableWatchpoint(lldb::WatchpointSP wp_sp, bool notify = true) override; + void + SetPseudoTerminalHandle(const std::shared_ptr<PseudoTerminal> &pty) override; + protected: ProcessWindows(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp); diff --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py index ca881f1d817c5..b0e124303a3b0 100644 --- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py +++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py @@ -16,7 +16,6 @@ class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase): - @skipIfWindows def test_default(self): """ Tests the default launch of a simple program. No arguments, @@ -76,7 +75,6 @@ def test_failing_console(self): r"unexpected value, expected 'internalConsole\', 'integratedTerminal\' or 'externalTerminal\' at arguments.console", ) - @skipIfWindows def test_termination(self): """ Tests the correct termination of lldb-dap upon a 'disconnect' @@ -209,7 +207,6 @@ def test_disableSTDIO(self): output = self.get_stdout() self.assertEqual(output, "", "expect no program output") - @skipIfWindows @skipIfLinux # shell argument expansion doesn't seem to work on Linux @expectedFailureAll(oslist=["freebsd", "netbsd"], bugnumber="llvm.org/pr48349") def test_shellExpandArguments_enabled(self): @@ -233,7 +230,6 @@ def test_shellExpandArguments_enabled(self): quote_path, line, 'verify "%s" expanded to "%s"' % (glob, program) ) - @skipIfWindows def test_shellExpandArguments_disabled(self): """ Tests the default launch of a simple program with shell expansion @@ -255,7 +251,6 @@ def test_shellExpandArguments_disabled(self): quote_path, line, 'verify "%s" stayed to "%s"' % (glob, glob) ) - @skipIfWindows def test_args(self): """ Tests launch of a simple program with arguments @@ -280,7 +275,6 @@ def test_args(self): 'arg[%i] "%s" not in "%s"' % (i + 1, quoted_arg, lines[i]), ) - @skipIfWindows def test_environment_with_object(self): """ Tests launch of a simple program with environment variables @@ -557,7 +551,6 @@ def test_terminate_commands(self): output = self.collect_console(pattern=terminateCommands[0]) self.verify_commands("terminateCommands", output, terminateCommands) - @skipIfWindows def test_version(self): """ Tests that "initialize" response contains the "version" string the same @@ -640,7 +633,6 @@ def test_stdio_redirection(self): ) @skipIfAsan - @skipIfWindows @skipIf(oslist=["linux"], archs=no_match(["x86_64"])) @skipIfBuildType(["debug"]) def test_stdio_redirection_and_console(self): diff --git a/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn b/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn index af4533285d3e9..e91b5ae5c9d24 100644 --- a/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn +++ b/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn @@ -75,6 +75,7 @@ static_library("Host") { "windows/MainLoopWindows.cpp", "windows/PipeWindows.cpp", "windows/ProcessLauncherWindows.cpp", + "windows/PseudoTerminalWindows.cpp", "windows/ProcessRunLock.cpp", ] } else { >From afaaa73eeba87e03db879d2e43e9bab6caa9eb06 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Tue, 2 Dec 2025 15:42:10 +0000 Subject: [PATCH 2/9] fixup! [lldb][windows] add Windows Virtual Console support --- lldb/source/Host/windows/PseudoTerminalWindows.cpp | 2 +- lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lldb/source/Host/windows/PseudoTerminalWindows.cpp b/lldb/source/Host/windows/PseudoTerminalWindows.cpp index 626df2f254644..a3b69aa89b065 100644 --- a/lldb/source/Host/windows/PseudoTerminalWindows.cpp +++ b/lldb/source/Host/windows/PseudoTerminalWindows.cpp @@ -31,7 +31,7 @@ llvm::Error PseudoTerminalWindows::OpenFirstAvailablePrimary(int oflag) { HANDLE hOutputWrite = INVALID_HANDLE_VALUE; wchar_t pipe_name[MAX_PATH]; - swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-%d-%p", + swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p", GetCurrentProcessId(), this); hOutputRead = diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp index da52bd59bdc4c..a65324e1c1434 100644 --- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp +++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp @@ -521,7 +521,7 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info, // We need to launch and attach to the process. launch_info.GetFlags().Set(eLaunchFlagDebug); if (!process_sp) - return process_sp; + return nullptr; error = process_sp->Launch(launch_info); if (error.Success()) process_sp->SetPseudoTerminalHandle(launch_info.GetPTYSP()); >From ad84a21abb28bcd6e9ff44e91c5c84a5c19aecec Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Tue, 2 Dec 2025 15:46:19 +0000 Subject: [PATCH 3/9] fixup! [lldb][windows] add Windows Virtual Console support --- lldb/source/Host/windows/ProcessLauncherWindows.cpp | 8 ++++---- lldb/source/Host/windows/PseudoTerminalWindows.cpp | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp index 704c0ff8e6ae5..aab85a589194d 100644 --- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp +++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp @@ -111,10 +111,10 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, /*dwAttributeCount=*/1, /*dwFlags=*/0, &attributelist_size); - startupinfoex.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc( - GetProcessHeap(), 0, attributelist_size); - auto free_attributelist = llvm::make_scope_exit( - [&] { HeapFree(GetProcessHeap(), 0, startupinfoex.lpAttributeList); }); + startupinfoex.lpAttributeList = + static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attributelist_size)); + auto free_attributelist = + llvm::make_scope_exit([&] { free(startupinfoex.lpAttributeList); }); if (!InitializeProcThreadAttributeList(startupinfoex.lpAttributeList, /*dwAttributeCount=*/1, /*dwFlags=*/0, &attributelist_size)) { diff --git a/lldb/source/Host/windows/PseudoTerminalWindows.cpp b/lldb/source/Host/windows/PseudoTerminalWindows.cpp index a3b69aa89b065..079dc472bc114 100644 --- a/lldb/source/Host/windows/PseudoTerminalWindows.cpp +++ b/lldb/source/Host/windows/PseudoTerminalWindows.cpp @@ -34,6 +34,8 @@ llvm::Error PseudoTerminalWindows::OpenFirstAvailablePrimary(int oflag) { swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p", GetCurrentProcessId(), this); + // A 4096 bytes buffer should be large enough for the majority of console + // burst outputs. hOutputRead = CreateNamedPipeW(pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, NULL); >From d107524b61912ee04b206ce9e8a587f8c39e24e3 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Wed, 3 Dec 2025 15:40:37 +0000 Subject: [PATCH 4/9] refactor inherited_handles creation --- lldb/source/Host/windows/ProcessLauncherWindows.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp index aab85a589194d..983afa94c6b07 100644 --- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp +++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp @@ -9,6 +9,7 @@ #include "lldb/Host/windows/ProcessLauncherWindows.h" #include "lldb/Host/HostProcess.h" #include "lldb/Host/ProcessLaunchInfo.h" +#include "lldb/Host/windows/windows.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallVector.h" >From d42afba1f5140eb2157a3e054347a2f6298361cb Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Fri, 5 Dec 2025 14:16:03 +0000 Subject: [PATCH 5/9] dynamically load CreatePseudoConsole --- lldb/include/lldb/Host/ProcessLaunchInfo.h | 14 ++++ lldb/include/lldb/Host/PseudoTerminal.h | 32 +------- .../include/lldb/Host/windows/PseudoConsole.h | 61 ++++++++++++++ .../lldb/Host/windows/PseudoTerminalWindows.h | 39 --------- lldb/include/lldb/Host/windows/windows.h | 4 +- lldb/include/lldb/Target/Process.h | 33 +++++--- lldb/source/Host/CMakeLists.txt | 2 +- lldb/source/Host/common/ProcessLaunchInfo.cpp | 20 ++--- lldb/source/Host/common/PseudoTerminal.cpp | 4 +- .../Host/windows/ProcessLauncherWindows.cpp | 3 +- ...oTerminalWindows.cpp => PseudoConsole.cpp} | 80 ++++++++++++++++--- .../Plugins/Platform/POSIX/PlatformPOSIX.cpp | 2 + .../Platform/QemuUser/PlatformQemuUser.cpp | 2 + .../Platform/Windows/PlatformWindows.cpp | 2 +- .../Windows/Common/NativeProcessWindows.cpp | 8 +- .../Process/Windows/Common/ProcessWindows.cpp | 8 +- .../Process/Windows/Common/ProcessWindows.h | 4 +- lldb/source/Target/Platform.cpp | 2 + .../gn/secondary/lldb/source/Host/BUILD.gn | 2 +- 19 files changed, 205 insertions(+), 117 deletions(-) create mode 100644 lldb/include/lldb/Host/windows/PseudoConsole.h delete mode 100644 lldb/include/lldb/Host/windows/PseudoTerminalWindows.h rename lldb/source/Host/windows/{PseudoTerminalWindows.cpp => PseudoConsole.cpp} (55%) diff --git a/lldb/include/lldb/Host/ProcessLaunchInfo.h b/lldb/include/lldb/Host/ProcessLaunchInfo.h index e0528c5b80539..55ebcee8ca705 100644 --- a/lldb/include/lldb/Host/ProcessLaunchInfo.h +++ b/lldb/include/lldb/Host/ProcessLaunchInfo.h @@ -17,7 +17,11 @@ #include "lldb/Host/FileAction.h" #include "lldb/Host/Host.h" +#ifdef _WIN32 +#include "lldb/Host/windows/PseudoConsole.h" +#else #include "lldb/Host/PseudoTerminal.h" +#endif #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/ProcessInfo.h" @@ -118,9 +122,15 @@ class ProcessLaunchInfo : public ProcessInfo { bool MonitorProcess() const; +#ifdef _WIN32 + PseudoConsole &GetPTY() const { return *m_pty; } + + std::shared_ptr<PseudoConsole> GetPTYSP() const { return m_pty; } +#else PseudoTerminal &GetPTY() const { return *m_pty; } std::shared_ptr<PseudoTerminal> GetPTYSP() const { return m_pty; } +#endif void SetLaunchEventData(const char *data) { m_event_data.assign(data); } @@ -138,7 +148,11 @@ class ProcessLaunchInfo : public ProcessInfo { FileSpec m_shell; Flags m_flags; // Bitwise OR of bits from lldb::LaunchFlags std::vector<FileAction> m_file_actions; // File actions for any other files +#ifdef _WIN32 + std::shared_ptr<PseudoConsole> m_pty; +#else std::shared_ptr<PseudoTerminal> m_pty; +#endif uint32_t m_resume_count = 0; // How many times do we resume after launching Host::MonitorChildProcessCallback m_monitor_callback; std::string m_event_data; // A string passed to the plugin launch, having no diff --git a/lldb/include/lldb/Host/PseudoTerminal.h b/lldb/include/lldb/Host/PseudoTerminal.h index c8443c71fbc62..deb016c846c77 100644 --- a/lldb/include/lldb/Host/PseudoTerminal.h +++ b/lldb/include/lldb/Host/PseudoTerminal.h @@ -38,11 +38,7 @@ class PseudoTerminal { /// The destructor will close the primary and secondary file /// descriptor/HANDLEs if they are valid and ownership has not been released /// using PseudoTerminal::Close(). - virtual ~PseudoTerminal(); - - /// Close all the file descriptors or Handles of the PseudoTerminal if they - /// are valid. - virtual void Close(); + ~PseudoTerminal(); /// Close the primary file descriptor if it is valid. void ClosePrimaryFileDescriptor(); @@ -84,16 +80,6 @@ class PseudoTerminal { /// \see PseudoTerminal::ReleasePrimaryFileDescriptor() int GetPrimaryFileDescriptor() const; - /// The primary HANDLE accessor. - /// - /// This object retains ownership of the primary HANDLE when this - /// accessor is used. - /// - /// \return - /// The primary HANDLE, or INVALID_HANDLE_VALUE if the primary HANDLE is - /// not currently valid. - virtual void *GetPrimaryHandle() const { return ((void *)(long long)-1); }; - /// The secondary file descriptor accessor. /// /// This object retains ownership of the secondary file descriptor when this @@ -108,8 +94,6 @@ class PseudoTerminal { /// \see PseudoTerminal::ReleaseSecondaryFileDescriptor() int GetSecondaryFileDescriptor() const; - virtual void *GetSecondaryHandle() const { return ((void *)(long long)-1); }; - /// Get the name of the secondary pseudo terminal. /// /// A primary pseudo terminal should already be valid prior to @@ -119,17 +103,7 @@ class PseudoTerminal { /// The name of the secondary pseudo terminal. /// /// \see PseudoTerminal::OpenFirstAvailablePrimary() - virtual std::string GetSecondaryName() const; - - /// The underlying Windows Pseudo Terminal HANDLE's accessor. - /// - /// This object retains ownership of the ConPTY's HANDLE when this - /// accessor is used. - /// - /// \return - /// The primary HANDLE, or INVALID_HANDLE_VALUE if the primary HANDLE is - /// not currently valid. - virtual void *GetPseudoTerminalHandle() { return ((void *)(long long)-1); }; + std::string GetSecondaryName() const; /// Open the first available pseudo terminal. /// @@ -150,7 +124,7 @@ class PseudoTerminal { /// /// \see PseudoTerminal::GetPrimaryFileDescriptor() @see /// PseudoTerminal::ReleasePrimaryFileDescriptor() - virtual llvm::Error OpenFirstAvailablePrimary(int oflag); + llvm::Error OpenFirstAvailablePrimary(int oflag); /// Open the secondary for the current primary pseudo terminal. /// diff --git a/lldb/include/lldb/Host/windows/PseudoConsole.h b/lldb/include/lldb/Host/windows/PseudoConsole.h new file mode 100644 index 0000000000000..fdca79ff27ce0 --- /dev/null +++ b/lldb/include/lldb/Host/windows/PseudoConsole.h @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_Host_Windows_PseudoConsole_H_ +#define liblldb_Host_Windows_PseudoConsole_H_ + +#include "llvm/Support/Error.h" +#include <string> + +#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x20016 + +namespace lldb_private { + +class PseudoConsole { + +public: + llvm::Error OpenPseudoConsole(); + + /// Close the ConPTY, its read/write handles and invalidate them. + void Close(); + + /// The ConPTY HPCON handle accessor. + /// + /// This object retains ownership of the HPCON when this accessor is used. + /// + /// \return + /// The ConPTY HPCON handle, or INVALID_HANDLE_VALUE if it is currently + /// invalid. + void *GetPseudoTerminalHandle() { return m_conpty_handle; }; + + /// The STDOUT read HANDLE accessor. + /// + /// This object retains ownership of the HANDLE when this accessor is used. + /// + /// \return + /// The STDOUT read HANDLE, or INVALID_HANDLE_VALUE if it is currently + /// invalid. + void *GetSTDOUTHandle() const { return m_conpty_output; }; + + /// The STDIN write HANDLE accessor. + /// + /// This object retains ownership of the HANDLE when this accessor is used. + /// + /// \return + /// The STDIN write HANDLE, or INVALID_HANDLE_VALUE if it is currently + /// invalid. + void *GetSTDINHandle() const { return m_conpty_input; }; + +protected: + void *m_conpty_handle = ((void *)(long long)-1); + void *m_conpty_output = ((void *)(long long)-1); + void *m_conpty_input = ((void *)(long long)-1); +}; +}; // namespace lldb_private + +#endif // liblldb_Host_Windows_PseudoConsole_H_ diff --git a/lldb/include/lldb/Host/windows/PseudoTerminalWindows.h b/lldb/include/lldb/Host/windows/PseudoTerminalWindows.h deleted file mode 100644 index 55868665b582b..0000000000000 --- a/lldb/include/lldb/Host/windows/PseudoTerminalWindows.h +++ /dev/null @@ -1,39 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef liblldb_Host_Windows_PseudoTerminalWindows_H_ -#define liblldb_Host_Windows_PseudoTerminalWindows_H_ - -#include "lldb/Host/PseudoTerminal.h" -#include "lldb/Host/windows/windows.h" - -namespace lldb_private { - -class PseudoTerminalWindows : public PseudoTerminal { - -public: - void Close() override; - - HPCON GetPseudoTerminalHandle() override { return m_conpty_handle; }; - - HANDLE GetPrimaryHandle() const override { return m_conpty_output; }; - - HANDLE GetSecondaryHandle() const override { return m_conpty_input; }; - - std::string GetSecondaryName() const override { return ""; }; - - llvm::Error OpenFirstAvailablePrimary(int oflag) override; - -protected: - HANDLE m_conpty_handle = INVALID_HANDLE_VALUE; - HANDLE m_conpty_output = INVALID_HANDLE_VALUE; - HANDLE m_conpty_input = INVALID_HANDLE_VALUE; -}; -}; // namespace lldb_private - -#endif // liblldb_Host_Windows_PseudoTerminalWindows_H_ diff --git a/lldb/include/lldb/Host/windows/windows.h b/lldb/include/lldb/Host/windows/windows.h index bb6695a112c7d..d53d4b9967268 100644 --- a/lldb/include/lldb/Host/windows/windows.h +++ b/lldb/include/lldb/Host/windows/windows.h @@ -9,9 +9,9 @@ #ifndef LLDB_lldb_windows_h_ #define LLDB_lldb_windows_h_ -#define NTDDI_VERSION NTDDI_WIN10_RS5 +#define NTDDI_VERSION NTDDI_VISTA #undef _WIN32_WINNT // undef a previous definition to avoid warning -#define _WIN32_WINNT _WIN32_WINNT_WIN10 +#define _WIN32_WINNT _WIN32_WINNT_VISTA #define WIN32_LEAN_AND_MEAN #define NOGDI #undef NOMINMAX // undef a previous definition to avoid warning diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index d2fab11879681..4dd8559addbd5 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -2534,6 +2534,30 @@ void PruneThreadPlans(); void CalculateExecutionContext(ExecutionContext &exe_ctx) override; +#ifdef _WIN32 + /// Associates a ConPTY read and write HANDLEs with the process' STDIO + /// handling and configures an asynchronous reading of that ConPTY's stdout + /// HANDLE. + /// + /// This method installs a ConnectionGenericFile for the passed ConPTY and + /// starts a dedicated read thread. If the read thread starts successfully, + /// the method also ensures that an IOHandlerProcessSTDIOWindows is created to + /// manage user input to the process. + /// + /// When data is successfully read from the ConPTY, it is stored in + /// m_stdout_data. There is no differentiation between stdout and stderr. + /// + /// \param[in] pty + /// The ConPTY to use for process STDIO communication. It's + /// assumed to be valid. + /// + /// \see lldb_private::Process::STDIOReadThreadBytesReceived() + /// \see lldb_private::IOHandlerProcessSTDIOWindows + /// \see lldb_private::PseudoConsole + virtual void + SetPseudoConsoleHandle(const std::shared_ptr<PseudoConsole> &pty) {}; +#endif + /// Associates a file descriptor with the process' STDIO handling /// and configures an asynchronous reading of that descriptor. /// @@ -2558,15 +2582,6 @@ void PruneThreadPlans(); /// \see lldb_private::ConnectionFileDescriptor void SetSTDIOFileDescriptor(int file_descriptor); - /// Windows equivalent of Process::SetSTDIOFileDescriptor, with a - /// PseudoTerminalWindows instead of a file descriptor. - /// - /// \param pty - /// The PseudoTerminal to use for process STDIO communication. It is not - /// managed by the created read thread. - virtual void - SetPseudoTerminalHandle(const std::shared_ptr<PseudoTerminal> &pty) {}; - // Add a permanent region of memory that should never be read or written to. // This can be used to ensure that memory reads or writes to certain areas of // memory never end up being sent to the DoReadMemory or DoWriteMemory diff --git a/lldb/source/Host/CMakeLists.txt b/lldb/source/Host/CMakeLists.txt index d2fd566e9a711..8ad485fa40285 100644 --- a/lldb/source/Host/CMakeLists.txt +++ b/lldb/source/Host/CMakeLists.txt @@ -76,7 +76,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows") windows/MainLoopWindows.cpp windows/PipeWindows.cpp windows/ProcessLauncherWindows.cpp - windows/PseudoTerminalWindows.cpp + windows/PseudoConsole.cpp windows/ProcessRunLock.cpp ) else() diff --git a/lldb/source/Host/common/ProcessLaunchInfo.cpp b/lldb/source/Host/common/ProcessLaunchInfo.cpp index c3beef7031f18..256bf713b7372 100644 --- a/lldb/source/Host/common/ProcessLaunchInfo.cpp +++ b/lldb/source/Host/common/ProcessLaunchInfo.cpp @@ -21,7 +21,7 @@ #include "llvm/Support/FileSystem.h" #ifdef _WIN32 -#include "lldb/Host/windows/PseudoTerminalWindows.h" +#include "lldb/Host/windows/PseudoConsole.h" #else #include <climits> #endif @@ -35,7 +35,7 @@ ProcessLaunchInfo::ProcessLaunchInfo() : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(0), m_file_actions(), m_monitor_callback(nullptr) { #ifdef _WIN32 - m_pty = std::make_shared<PseudoTerminalWindows>(); + m_pty = std::make_shared<PseudoConsole>(); #else m_pty = std::make_shared<PseudoTerminal>(); #endif @@ -49,7 +49,7 @@ ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec, : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(launch_flags), m_file_actions() { #ifdef _WIN32 - m_pty = std::make_shared<PseudoTerminalWindows>(); + m_pty = std::make_shared<PseudoConsole>(); #else m_pty = std::make_shared<PseudoTerminal>(); #endif @@ -221,13 +221,12 @@ llvm::Error ProcessLaunchInfo::SetUpPtyRedirection() { LLDB_LOG(log, "Generating a pty to use for stdin/out/err"); - int open_flags = O_RDWR | O_NOCTTY; -#if !defined(_WIN32) - // We really shouldn't be specifying platform specific flags that are - // intended for a system call in generic code. But this will have to - // do for now. - open_flags |= O_CLOEXEC; -#endif +#ifdef _WIN32 + if (llvm::Error Err = m_pty->OpenPseudoConsole()) + return Err; + return llvm::Error::success(); +#else + int open_flags = O_RDWR | O_NOCTTY | O_CLOEXEC; if (llvm::Error Err = m_pty->OpenFirstAvailablePrimary(open_flags)) return Err; @@ -242,6 +241,7 @@ llvm::Error ProcessLaunchInfo::SetUpPtyRedirection() { if (stderr_free) AppendOpenFileAction(STDERR_FILENO, secondary_file_spec, false, true); return llvm::Error::success(); +#endif } bool ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell( diff --git a/lldb/source/Host/common/PseudoTerminal.cpp b/lldb/source/Host/common/PseudoTerminal.cpp index 6c548701857c3..53e91aff212a4 100644 --- a/lldb/source/Host/common/PseudoTerminal.cpp +++ b/lldb/source/Host/common/PseudoTerminal.cpp @@ -38,9 +38,7 @@ PseudoTerminal::PseudoTerminal() = default; // are valid and ownership has not been released using the // ReleasePrimaryFileDescriptor() or the ReleaseSaveFileDescriptor() member // functions. -PseudoTerminal::~PseudoTerminal() { Close(); } - -void PseudoTerminal::Close() { +PseudoTerminal::~PseudoTerminal() { ClosePrimaryFileDescriptor(); CloseSecondaryFileDescriptor(); } diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp index 983afa94c6b07..001a8a16f3720 100644 --- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp +++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp @@ -9,6 +9,7 @@ #include "lldb/Host/windows/ProcessLauncherWindows.h" #include "lldb/Host/HostProcess.h" #include "lldb/Host/ProcessLaunchInfo.h" +#include "lldb/Host/windows/PseudoConsole.h" #include "lldb/Host/windows/windows.h" #include "llvm/ADT/ScopeExit.h" @@ -140,7 +141,7 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, error = Status(inherited_handles_or_err.getError()); return HostProcess(); } - inherited_handles = *inherited_handles_or_err; + inherited_handles = std::move(*inherited_handles_or_err); } const char *hide_console_var = diff --git a/lldb/source/Host/windows/PseudoTerminalWindows.cpp b/lldb/source/Host/windows/PseudoConsole.cpp similarity index 55% rename from lldb/source/Host/windows/PseudoTerminalWindows.cpp rename to lldb/source/Host/windows/PseudoConsole.cpp index 079dc472bc114..e882faac25150 100644 --- a/lldb/source/Host/windows/PseudoTerminalWindows.cpp +++ b/lldb/source/Host/windows/PseudoConsole.cpp @@ -6,24 +6,70 @@ // //===----------------------------------------------------------------------===// -#include "lldb/Host/windows/PseudoTerminalWindows.h" +#include "lldb/Host/windows/PseudoConsole.h" +#include "lldb/Host/windows/windows.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Errno.h" using namespace lldb_private; -void PseudoTerminalWindows::Close() { - if (m_conpty_handle != INVALID_HANDLE_VALUE) - ClosePseudoConsole(m_conpty_handle); - CloseHandle(m_conpty_input); - CloseHandle(m_conpty_output); - m_conpty_handle = INVALID_HANDLE_VALUE; - m_conpty_input = INVALID_HANDLE_VALUE; - m_conpty_output = INVALID_HANDLE_VALUE; -} +typedef HRESULT(WINAPI *CreatePseudoConsole_t)(COORD size, HANDLE hInput, + HANDLE hOutput, DWORD dwFlags, + HPCON *phPC); + +typedef HRESULT(WINAPI *ResizePseudoConsole_t)(HPCON hPC, COORD size); + +typedef VOID(WINAPI *ClosePseudoConsole_t)(HPCON hPC); + +class ConPTY { +public: + static bool Initialize() { + static bool initialized = false; + static bool success = false; + + if (!initialized) { + initialized = true; + + HMODULE hMod = LoadLibraryW(L"kernel32.dll"); + if (!hMod) { + return false; + } + + pCreate = + (CreatePseudoConsole_t)GetProcAddress(hMod, "CreatePseudoConsole"); + pClose = (ClosePseudoConsole_t)GetProcAddress(hMod, "ClosePseudoConsole"); + + success = (pCreate && pClose); + } + + return success; + } + + static bool IsAvailable() { return Initialize(); } + + static CreatePseudoConsole_t Create() { + Initialize(); + return pCreate; + } + + static ClosePseudoConsole_t Close() { + Initialize(); + return pClose; + } + +private: + static CreatePseudoConsole_t pCreate; + static ClosePseudoConsole_t pClose; +}; + +CreatePseudoConsole_t ConPTY::pCreate = nullptr; +ClosePseudoConsole_t ConPTY::pClose = nullptr; -llvm::Error PseudoTerminalWindows::OpenFirstAvailablePrimary(int oflag) { +llvm::Error PseudoConsole::OpenPseudoConsole() { + if (!ConPTY::IsAvailable()) + return llvm::make_error<llvm::StringError>("ConPTY is not available", + llvm::errc::io_error); HRESULT hr; HANDLE hInputRead = INVALID_HANDLE_VALUE; HANDLE hInputWrite = INVALID_HANDLE_VALUE; @@ -48,7 +94,7 @@ llvm::Error PseudoTerminalWindows::OpenFirstAvailablePrimary(int oflag) { COORD consoleSize{256, 25}; HPCON hPC = INVALID_HANDLE_VALUE; - hr = CreatePseudoConsole(consoleSize, hInputRead, hOutputWrite, 0, &hPC); + hr = ConPTY::Create()(consoleSize, hInputRead, hOutputWrite, 0, &hPC); CloseHandle(hInputRead); CloseHandle(hOutputWrite); @@ -69,3 +115,13 @@ llvm::Error PseudoTerminalWindows::OpenFirstAvailablePrimary(int oflag) { return llvm::Error::success(); } + +void PseudoConsole::Close() { + if (m_conpty_handle != INVALID_HANDLE_VALUE) + ConPTY::Close()(m_conpty_handle); + CloseHandle(m_conpty_input); + CloseHandle(m_conpty_output); + m_conpty_handle = INVALID_HANDLE_VALUE; + m_conpty_input = INVALID_HANDLE_VALUE; + m_conpty_output = INVALID_HANDLE_VALUE; +} \ No newline at end of file diff --git a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp index befc28b09d185..427b2ce4c21fe 100644 --- a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp +++ b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp @@ -488,12 +488,14 @@ lldb::ProcessSP PlatformPOSIX::DebugProcess(ProcessLaunchInfo &launch_info, if (error.Success()) { // Hook up process PTY if we have one (which we should for local debugging // with llgs). +#ifndef _WIN32 // TODO: Implement on Windows int pty_fd = launch_info.GetPTY().ReleasePrimaryFileDescriptor(); if (pty_fd != PseudoTerminal::invalid_fd) { process_sp->SetSTDIOFileDescriptor(pty_fd); LLDB_LOG(log, "hooked up STDIO pty to process"); } else LLDB_LOG(log, "not using process STDIO pty"); +#endif } else { LLDB_LOG(log, "{0}", error); // FIXME figure out appropriate cleanup here. Do we delete the process? diff --git a/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp b/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp index c182d3d862269..1054892496732 100644 --- a/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp +++ b/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp @@ -235,10 +235,12 @@ lldb::ProcessSP PlatformQemuUser::DebugProcess(ProcessLaunchInfo &launch_info, if (error.Fail()) return nullptr; +#ifndef _WIN32 // TODO: Implement on Windows if (launch_info.GetPTY().GetPrimaryFileDescriptor() != PseudoTerminal::invalid_fd) process_sp->SetSTDIOFileDescriptor( launch_info.GetPTY().ReleasePrimaryFileDescriptor()); +#endif return process_sp; } diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp index a65324e1c1434..42884c893c6ab 100644 --- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp +++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp @@ -524,7 +524,7 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info, return nullptr; error = process_sp->Launch(launch_info); if (error.Success()) - process_sp->SetPseudoTerminalHandle(launch_info.GetPTYSP()); + process_sp->SetPseudoConsoleHandle(launch_info.GetPTYSP()); else LLDB_LOGF(log, "Platform::%s LaunchProcess() failed: %s", __FUNCTION__, error.AsCString()); diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp index 79dd46ba319d6..e73b5749edde0 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp @@ -15,6 +15,7 @@ #include "lldb/Host/HostNativeProcessBase.h" #include "lldb/Host/HostProcess.h" #include "lldb/Host/ProcessLaunchInfo.h" +#include "lldb/Host/PseudoTerminal.h" #include "lldb/Host/windows/AutoHandle.h" #include "lldb/Host/windows/HostThreadWindows.h" #include "lldb/Host/windows/ProcessLauncherWindows.h" @@ -47,9 +48,10 @@ namespace lldb_private { NativeProcessWindows::NativeProcessWindows(ProcessLaunchInfo &launch_info, NativeDelegate &delegate, llvm::Error &E) - : NativeProcessProtocol(LLDB_INVALID_PROCESS_ID, - launch_info.GetPTY().ReleasePrimaryFileDescriptor(), - delegate), + : NativeProcessProtocol( + LLDB_INVALID_PROCESS_ID, + PseudoTerminal::invalid_fd, // TODO: Implement on Windows + delegate), ProcessDebugger(), m_arch(launch_info.GetArchitecture()) { ErrorAsOutParameter EOut(&E); DebugDelegateSP delegate_sp(new NativeDebugDelegate(*this)); diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp index 17961c72e56a8..2dffe48ad465c 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp @@ -1087,10 +1087,10 @@ class IOHandlerProcessSTDIOWindows : public IOHandler { bool m_is_running = false; }; -void ProcessWindows::SetPseudoTerminalHandle( - const std::shared_ptr<PseudoTerminal> &pty) { +void ProcessWindows::SetPseudoConsoleHandle( + const std::shared_ptr<PseudoConsole> &pty) { m_stdio_communication.SetConnection( - std::make_unique<ConnectionGenericFile>(pty->GetPrimaryHandle(), false)); + std::make_unique<ConnectionGenericFile>(pty->GetSTDOUTHandle(), false)); if (m_stdio_communication.IsConnected()) { m_stdio_communication.SetReadThreadBytesReceivedCallback( STDIOReadThreadBytesReceived, this); @@ -1101,7 +1101,7 @@ void ProcessWindows::SetPseudoTerminalHandle( std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); if (!m_process_input_reader) m_process_input_reader = std::make_shared<IOHandlerProcessSTDIOWindows>( - this, pty->GetSecondaryHandle()); + this, pty->GetSTDINHandle()); } } } diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h index 49596e49a9f82..33e4de6b85932 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h @@ -9,7 +9,7 @@ #ifndef liblldb_Plugins_Process_Windows_Common_ProcessWindows_H_ #define liblldb_Plugins_Process_Windows_Common_ProcessWindows_H_ -#include "lldb/Host/windows/PseudoTerminalWindows.h" +#include "lldb/Host/windows/PseudoConsole.h" #include "lldb/Target/Process.h" #include "lldb/Utility/Status.h" #include "lldb/lldb-forward.h" @@ -99,7 +99,7 @@ class ProcessWindows : public Process, public ProcessDebugger { bool notify = true) override; void - SetPseudoTerminalHandle(const std::shared_ptr<PseudoTerminal> &pty) override; + SetPseudoConsoleHandle(const std::shared_ptr<PseudoConsole> &pty) override; protected: ProcessWindows(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp); diff --git a/lldb/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp index 5b0930cf26b77..67a5857c12aa0 100644 --- a/lldb/source/Target/Platform.cpp +++ b/lldb/source/Target/Platform.cpp @@ -1054,10 +1054,12 @@ lldb::ProcessSP Platform::DebugProcess(ProcessLaunchInfo &launch_info, // been used where the secondary side was given as the file to open for // stdin/out/err after we have already opened the primary so we can // read/write stdin/out/err. +#ifndef _WIN32 int pty_fd = launch_info.GetPTY().ReleasePrimaryFileDescriptor(); if (pty_fd != PseudoTerminal::invalid_fd) { process_sp->SetSTDIOFileDescriptor(pty_fd); } +#endif } else { LLDB_LOGF(log, "Platform::%s Attach() failed: %s", __FUNCTION__, error.AsCString()); diff --git a/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn b/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn index e91b5ae5c9d24..f200a637ae99a 100644 --- a/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn +++ b/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn @@ -75,7 +75,7 @@ static_library("Host") { "windows/MainLoopWindows.cpp", "windows/PipeWindows.cpp", "windows/ProcessLauncherWindows.cpp", - "windows/PseudoTerminalWindows.cpp", + "windows/PseudoConsole.cpp", "windows/ProcessRunLock.cpp", ] } else { >From 1a659e12cf86f3afdf010f91664d1c306565e302 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 11 Dec 2025 15:21:34 +0000 Subject: [PATCH 6/9] 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 ec769fba67334d37b0d1bddb66519edbf87d3f68 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Fri, 12 Dec 2025 18:37:12 +0000 Subject: [PATCH 7/9] fixup! make ConPTY thread safe --- lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp index 3764610c5d05b..d4943abc7d52e 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()); + error.AsCString()); + } #endif return process_sp; >From 2d36aec541012bb6ee4695661975ee763a139081 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Fri, 12 Dec 2025 18:41:48 +0000 Subject: [PATCH 8/9] fix formatting --- lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp index d4943abc7d52e..f106c01601e29 100644 --- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp +++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp @@ -528,8 +528,8 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info, else { Log *log = GetLog(LLDBLog::Platform); LLDB_LOGF(log, "Platform::%s LaunchProcess() failed: %s", __FUNCTION__, - error.AsCString()); - } + error.AsCString()); + } #endif return process_sp; >From 208f97605289f01658aa2bbf3092eace7f4e0e40 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Mon, 15 Dec 2025 15:29:31 +0000 Subject: [PATCH 9/9] revert documentation changes --- lldb/include/lldb/Host/PseudoTerminal.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lldb/include/lldb/Host/PseudoTerminal.h b/lldb/include/lldb/Host/PseudoTerminal.h index deb016c846c77..bd1e2f56241b2 100644 --- a/lldb/include/lldb/Host/PseudoTerminal.h +++ b/lldb/include/lldb/Host/PseudoTerminal.h @@ -35,9 +35,10 @@ class PseudoTerminal { /// Destructor /// - /// The destructor will close the primary and secondary file - /// descriptor/HANDLEs if they are valid and ownership has not been released - /// using PseudoTerminal::Close(). + /// The destructor will close the primary and secondary file descriptors if + /// they are valid and ownership has not been released using one of: @li + /// PseudoTerminal::ReleasePrimaryFileDescriptor() @li + /// PseudoTerminal::ReleaseSaveFileDescriptor() ~PseudoTerminal(); /// Close the primary file descriptor if it is valid. @@ -58,7 +59,8 @@ class PseudoTerminal { /// /// This class will close the file descriptors for the primary/secondary when /// the destructor is called. The file handles can be released using either: - /// @li PseudoTerminal::ReleasePrimaryFileDescriptor() + /// @li PseudoTerminal::ReleasePrimaryFileDescriptor() @li + /// PseudoTerminal::ReleaseSaveFileDescriptor() /// /// \return /// \b Parent process: a child process ID that is greater _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
