[Lldb-commits] [lldb] 7fe5953 - [lldb][windows] add Windows Virtual Console support (#168729)
via lldb-commits
lldb-commits at lists.llvm.org
Thu Dec 18 02:29:43 PST 2025
Author: Charles Zablit
Date: 2025-12-18T10:29:38Z
New Revision: 7fe5953a44bbce16c0fcc5b52e47905c1719b7b3
URL: https://github.com/llvm/llvm-project/commit/7fe5953a44bbce16c0fcc5b52e47905c1719b7b3
DIFF: https://github.com/llvm/llvm-project/commit/7fe5953a44bbce16c0fcc5b52e47905c1719b7b3.diff
LOG: [lldb][windows] add Windows Virtual Console support (#168729)
Added:
lldb/include/lldb/Host/windows/PseudoConsole.h
lldb/source/Host/windows/PseudoConsole.cpp
Modified:
lldb/include/lldb/Host/ProcessLaunchInfo.h
lldb/include/lldb/Target/Process.h
lldb/packages/Python/lldbsuite/test/decorators.py
lldb/source/Host/CMakeLists.txt
lldb/source/Host/common/ProcessLaunchInfo.cpp
lldb/source/Host/windows/ProcessLauncherWindows.cpp
lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp
lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
lldb/source/Target/Platform.cpp
lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn
Removed:
################################################################################
diff --git a/lldb/include/lldb/Host/ProcessLaunchInfo.h b/lldb/include/lldb/Host/ProcessLaunchInfo.h
index 25762bc65295d..1bddface440e7 100644
--- a/lldb/include/lldb/Host/ProcessLaunchInfo.h
+++ b/lldb/include/lldb/Host/ProcessLaunchInfo.h
@@ -17,12 +17,22 @@
#include "lldb/Host/FileAction.h"
#include "lldb/Host/Host.h"
+#ifdef _WIN32
+#include "lldb/Host/windows/PseudoConsole.h"
+#else
#include "lldb/Host/PseudoTerminal.h"
+#endif
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/ProcessInfo.h"
namespace lldb_private {
+#if defined(_WIN32)
+using PTY = PseudoConsole;
+#else
+using PTY = PseudoTerminal;
+#endif
+
// ProcessLaunchInfo
//
// Describes any information that is required to launch a process.
@@ -118,7 +128,9 @@ class ProcessLaunchInfo : public ProcessInfo {
bool MonitorProcess() const;
- PseudoTerminal &GetPTY() { return *m_pty; }
+ PTY &GetPTY() const { return *m_pty; }
+
+ std::shared_ptr<PTY> GetPTYSP() const { return m_pty; }
void SetLaunchEventData(const char *data) { m_event_data.assign(data); }
@@ -136,7 +148,7 @@ class ProcessLaunchInfo : public ProcessInfo {
FileSpec m_shell;
Flags m_flags; // Bitwise OR of bits from lldb::LaunchFlags
std::vector<FileAction> m_file_actions; // File actions for any other files
- std::shared_ptr<PseudoTerminal> m_pty;
+ std::shared_ptr<PTY> m_pty;
uint32_t m_resume_count = 0; // How many times do we resume after launching
Host::MonitorChildProcessCallback m_monitor_callback;
std::string m_event_data; // A string passed to the plugin launch, having no
diff --git a/lldb/include/lldb/Host/windows/PseudoConsole.h b/lldb/include/lldb/Host/windows/PseudoConsole.h
new file mode 100644
index 0000000000000..c3bf817cf831d
--- /dev/null
+++ b/lldb/include/lldb/Host/windows/PseudoConsole.h
@@ -0,0 +1,63 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LIBLLDB_HOST_WINDOWS_PSEUDOCONSOLE_H_
+#define LIBLLDB_HOST_WINDOWS_PSEUDOCONSOLE_H_
+
+#include "llvm/Support/Error.h"
+#include <string>
+
+#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x20016
+typedef void *HANDLE;
+typedef void *HPCON;
+
+namespace lldb_private {
+
+class PseudoConsole {
+
+public:
+ llvm::Error OpenPseudoConsole();
+
+ /// Close the ConPTY, its read/write handles and invalidate them.
+ void Close();
+
+ /// The ConPTY HPCON handle accessor.
+ ///
+ /// This object retains ownership of the HPCON when this accessor is used.
+ ///
+ /// \return
+ /// The ConPTY HPCON handle, or INVALID_HANDLE_VALUE if it is currently
+ /// invalid.
+ HPCON GetPseudoTerminalHandle() { return m_conpty_handle; };
+
+ /// The STDOUT read HANDLE accessor.
+ ///
+ /// This object retains ownership of the HANDLE when this accessor is used.
+ ///
+ /// \return
+ /// The STDOUT read HANDLE, or INVALID_HANDLE_VALUE if it is currently
+ /// invalid.
+ HANDLE GetSTDOUTHandle() const { return m_conpty_output; };
+
+ /// The STDIN write HANDLE accessor.
+ ///
+ /// This object retains ownership of the HANDLE when this accessor is used.
+ ///
+ /// \return
+ /// The STDIN write HANDLE, or INVALID_HANDLE_VALUE if it is currently
+ /// invalid.
+ HANDLE GetSTDINHandle() const { return m_conpty_input; };
+
+protected:
+ HANDLE m_conpty_handle = ((HANDLE)(long long)-1);
+ HANDLE m_conpty_output = ((HANDLE)(long long)-1);
+ HANDLE m_conpty_input = ((HANDLE)(long long)-1);
+};
+}; // namespace lldb_private
+
+#endif // LIBLLDB_HOST_WINDOWS_PSEUDOCONSOLE_H_
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 8e6c16cbfe0fc..4dd8559addbd5 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -2534,6 +2534,30 @@ void PruneThreadPlans();
void CalculateExecutionContext(ExecutionContext &exe_ctx) override;
+#ifdef _WIN32
+ /// Associates a ConPTY read and write HANDLEs with the process' STDIO
+ /// handling and configures an asynchronous reading of that ConPTY's stdout
+ /// HANDLE.
+ ///
+ /// This method installs a ConnectionGenericFile for the passed ConPTY and
+ /// starts a dedicated read thread. If the read thread starts successfully,
+ /// the method also ensures that an IOHandlerProcessSTDIOWindows is created to
+ /// manage user input to the process.
+ ///
+ /// When data is successfully read from the ConPTY, it is stored in
+ /// m_stdout_data. There is no
diff erentiation between stdout and stderr.
+ ///
+ /// \param[in] pty
+ /// The ConPTY to use for process STDIO communication. It's
+ /// assumed to be valid.
+ ///
+ /// \see lldb_private::Process::STDIOReadThreadBytesReceived()
+ /// \see lldb_private::IOHandlerProcessSTDIOWindows
+ /// \see lldb_private::PseudoConsole
+ virtual void
+ SetPseudoConsoleHandle(const std::shared_ptr<PseudoConsole> &pty) {};
+#endif
+
/// Associates a file descriptor with the process' STDIO handling
/// and configures an asynchronous reading of that descriptor.
///
diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py
index 150f5bbd3868b..6f388cb090f41 100644
--- a/lldb/packages/Python/lldbsuite/test/decorators.py
+++ b/lldb/packages/Python/lldbsuite/test/decorators.py
@@ -781,9 +781,35 @@ def skipIfLinux(func):
return skipIfPlatform(["linux"])(func)
-def skipIfWindows(func):
+def skipIfWindows(func=None, major=None, build=None):
"""Decorate the item to skip tests that should be skipped on Windows."""
- return skipIfPlatform(["windows"])(func)
+
+ def decorator(func):
+ if major is None and build is None:
+ return skipIfPlatform(["windows"])(func)
+ else:
+ import platform
+ import sys
+
+ def version_check():
+ check_major = 0 if major is None else major
+ check_build = 0 if build is None else build
+ if platform.system() != "Windows":
+ return False
+ win_version = sys.getwindowsversion()
+ return (
+ win_version.major >= check_major
+ and win_version.build >= check_build
+ )
+
+ return unittest.skipIf(
+ version_check(),
+ f"Test is skipped on Windows major={major} build={build}",
+ )(func)
+
+ if func is not None:
+ return decorator(func)
+ return decorator
def skipIfWindowsAndNonEnglish(func):
diff --git a/lldb/source/Host/CMakeLists.txt b/lldb/source/Host/CMakeLists.txt
index 3184d3a1ead0d..8ad485fa40285 100644
--- a/lldb/source/Host/CMakeLists.txt
+++ b/lldb/source/Host/CMakeLists.txt
@@ -76,6 +76,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows")
windows/MainLoopWindows.cpp
windows/PipeWindows.cpp
windows/ProcessLauncherWindows.cpp
+ windows/PseudoConsole.cpp
windows/ProcessRunLock.cpp
)
else()
diff --git a/lldb/source/Host/common/ProcessLaunchInfo.cpp b/lldb/source/Host/common/ProcessLaunchInfo.cpp
index 49159cca9c57c..e82cc11187fe5 100644
--- a/lldb/source/Host/common/ProcessLaunchInfo.cpp
+++ b/lldb/source/Host/common/ProcessLaunchInfo.cpp
@@ -20,7 +20,9 @@
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/FileSystem.h"
-#if !defined(_WIN32)
+#ifdef _WIN32
+#include "lldb/Host/windows/PseudoConsole.h"
+#else
#include <climits>
#endif
@@ -31,8 +33,7 @@ using namespace lldb_private;
ProcessLaunchInfo::ProcessLaunchInfo()
: ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(0),
- m_file_actions(), m_pty(new PseudoTerminal), m_monitor_callback(nullptr) {
-}
+ m_file_actions(), m_pty(new PTY), m_monitor_callback(nullptr) {}
ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec,
const FileSpec &stdout_file_spec,
@@ -40,7 +41,7 @@ ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec,
const FileSpec &working_directory,
uint32_t launch_flags)
: ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(launch_flags),
- m_file_actions(), m_pty(new PseudoTerminal) {
+ m_file_actions(), m_pty(new PTY) {
if (stdin_file_spec) {
FileAction file_action;
const bool read = true;
@@ -208,13 +209,12 @@ llvm::Error ProcessLaunchInfo::SetUpPtyRedirection() {
LLDB_LOG(log, "Generating a pty to use for stdin/out/err");
- int open_flags = O_RDWR | O_NOCTTY;
-#if !defined(_WIN32)
- // We really shouldn't be specifying platform specific flags that are
- // intended for a system call in generic code. But this will have to
- // do for now.
- open_flags |= O_CLOEXEC;
-#endif
+#ifdef _WIN32
+ if (llvm::Error Err = m_pty->OpenPseudoConsole())
+ return Err;
+ return llvm::Error::success();
+#else
+ int open_flags = O_RDWR | O_NOCTTY | O_CLOEXEC;
if (llvm::Error Err = m_pty->OpenFirstAvailablePrimary(open_flags))
return Err;
@@ -229,6 +229,7 @@ llvm::Error ProcessLaunchInfo::SetUpPtyRedirection() {
if (stderr_free)
AppendOpenFileAction(STDERR_FILENO, secondary_file_spec, false, true);
return llvm::Error::success();
+#endif
}
bool ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell(
diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
index e983c527a174a..ae79171337726 100644
--- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp
+++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
@@ -9,6 +9,8 @@
#include "lldb/Host/windows/ProcessLauncherWindows.h"
#include "lldb/Host/HostProcess.h"
#include "lldb/Host/ProcessLaunchInfo.h"
+#include "lldb/Host/windows/PseudoConsole.h"
+#include "lldb/Host/windows/windows.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallVector.h"
@@ -87,9 +89,13 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
error.Clear();
STARTUPINFOEXW startupinfoex = {};
- startupinfoex.StartupInfo.cb = sizeof(startupinfoex);
+ startupinfoex.StartupInfo.cb = sizeof(STARTUPINFOEXW);
startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+ HPCON hPC = launch_info.GetPTY().GetPseudoTerminalHandle();
+ bool use_pty =
+ hPC != INVALID_HANDLE_VALUE && launch_info.GetNumFileActions() == 0;
+
HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO);
HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO);
HANDLE stderr_handle = GetStdioHandle(launch_info, STDERR_FILENO);
@@ -120,13 +126,23 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
auto delete_attributelist = llvm::make_scope_exit(
[&] { DeleteProcThreadAttributeList(startupinfoex.lpAttributeList); });
- auto inherited_handles_or_err = GetInheritedHandles(
- launch_info, startupinfoex, stdout_handle, stderr_handle, stdin_handle);
- if (!inherited_handles_or_err) {
- error = Status(inherited_handles_or_err.getError());
- return HostProcess();
+ std::vector<HANDLE> inherited_handles;
+ if (use_pty) {
+ if (!UpdateProcThreadAttribute(startupinfoex.lpAttributeList, 0,
+ PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, hPC,
+ sizeof(hPC), NULL, NULL)) {
+ error = Status(::GetLastError(), eErrorTypeWin32);
+ return HostProcess();
+ }
+ } else {
+ auto inherited_handles_or_err = GetInheritedHandles(
+ launch_info, startupinfoex, stdout_handle, stderr_handle, stdin_handle);
+ if (!inherited_handles_or_err) {
+ error = Status(inherited_handles_or_err.getError());
+ return HostProcess();
+ }
+ inherited_handles = std::move(*inherited_handles_or_err);
}
- std::vector<HANDLE> inherited_handles = *inherited_handles_or_err;
const char *hide_console_var =
getenv("LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE");
@@ -141,7 +157,7 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
if (launch_info.GetFlags().Test(eLaunchFlagDebug))
flags |= DEBUG_ONLY_THIS_PROCESS;
- if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO))
+ if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO) || use_pty)
flags &= ~CREATE_NEW_CONSOLE;
std::vector<wchar_t> environment =
diff --git a/lldb/source/Host/windows/PseudoConsole.cpp b/lldb/source/Host/windows/PseudoConsole.cpp
new file mode 100644
index 0000000000000..9204488930a3e
--- /dev/null
+++ b/lldb/source/Host/windows/PseudoConsole.cpp
@@ -0,0 +1,126 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/windows/PseudoConsole.h"
+
+#include <mutex>
+
+#include "lldb/Host/windows/PipeWindows.h"
+#include "lldb/Host/windows/windows.h"
+#include "lldb/Utility/LLDBLog.h"
+
+#include "llvm/Support/Errc.h"
+#include "llvm/Support/Errno.h"
+
+using namespace lldb_private;
+
+typedef HRESULT(WINAPI *CreatePseudoConsole_t)(COORD size, HANDLE hInput,
+ HANDLE hOutput, DWORD dwFlags,
+ HPCON *phPC);
+
+typedef VOID(WINAPI *ClosePseudoConsole_t)(HPCON hPC);
+
+struct Kernel32 {
+ Kernel32() {
+ hModule = LoadLibraryW(L"kernel32.dll");
+ if (!hModule) {
+ llvm::Error err = llvm::errorCodeToError(
+ std::error_code(GetLastError(), std::system_category()));
+ LLDB_LOG_ERROR(GetLog(LLDBLog::Host), std::move(err),
+ "Could not load kernel32: {0}");
+ return;
+ }
+ CreatePseudoConsole_ =
+ (CreatePseudoConsole_t)GetProcAddress(hModule, "CreatePseudoConsole");
+ ClosePseudoConsole_ =
+ (ClosePseudoConsole_t)GetProcAddress(hModule, "ClosePseudoConsole");
+ isAvailable = (CreatePseudoConsole_ && ClosePseudoConsole_);
+ }
+
+ HRESULT CreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput,
+ DWORD dwFlags, HPCON *phPC) {
+ assert(CreatePseudoConsole_ && "CreatePseudoConsole is not available!");
+ return CreatePseudoConsole_(size, hInput, hOutput, dwFlags, phPC);
+ }
+
+ VOID ClosePseudoConsole(HPCON hPC) {
+ assert(ClosePseudoConsole_ && "ClosePseudoConsole is not available!");
+ return ClosePseudoConsole_(hPC);
+ }
+
+ bool IsConPTYAvailable() { return isAvailable; }
+
+private:
+ HMODULE hModule;
+ CreatePseudoConsole_t CreatePseudoConsole_;
+ ClosePseudoConsole_t ClosePseudoConsole_;
+ bool isAvailable;
+};
+
+static Kernel32 kernel32;
+
+llvm::Error PseudoConsole::OpenPseudoConsole() {
+ if (!kernel32.IsConPTYAvailable())
+ return llvm::make_error<llvm::StringError>("ConPTY is not available",
+ llvm::errc::io_error);
+ HRESULT hr;
+ HANDLE hInputRead = INVALID_HANDLE_VALUE;
+ HANDLE hInputWrite = INVALID_HANDLE_VALUE;
+ HANDLE hOutputRead = INVALID_HANDLE_VALUE;
+ HANDLE hOutputWrite = INVALID_HANDLE_VALUE;
+
+ wchar_t pipe_name[MAX_PATH];
+ swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p",
+ GetCurrentProcessId(), this);
+
+ // A 4096 bytes buffer should be large enough for the majority of console
+ // burst outputs.
+ hOutputRead =
+ CreateNamedPipeW(pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
+ PIPE_TYPE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, NULL);
+ hOutputWrite = CreateFileW(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (!CreatePipe(&hInputRead, &hInputWrite, NULL, 0))
+ return llvm::errorCodeToError(
+ std::error_code(GetLastError(), std::system_category()));
+
+ COORD consoleSize{80, 25};
+ HPCON hPC = INVALID_HANDLE_VALUE;
+ hr = kernel32.CreatePseudoConsole(consoleSize, hInputRead, hOutputWrite, 0,
+ &hPC);
+ CloseHandle(hInputRead);
+ CloseHandle(hOutputWrite);
+
+ if (FAILED(hr)) {
+ CloseHandle(hInputWrite);
+ CloseHandle(hOutputRead);
+ return llvm::make_error<llvm::StringError>(
+ "Failed to create Windows ConPTY pseudo terminal",
+ llvm::errc::io_error);
+ }
+
+ DWORD mode = PIPE_NOWAIT;
+ SetNamedPipeHandleState(hOutputRead, &mode, NULL, NULL);
+
+ m_conpty_handle = hPC;
+ m_conpty_output = hOutputRead;
+ m_conpty_input = hInputWrite;
+
+ return llvm::Error::success();
+}
+
+void PseudoConsole::Close() {
+ if (m_conpty_handle != INVALID_HANDLE_VALUE)
+ kernel32.ClosePseudoConsole(m_conpty_handle);
+ CloseHandle(m_conpty_input);
+ CloseHandle(m_conpty_output);
+ m_conpty_handle = INVALID_HANDLE_VALUE;
+ m_conpty_input = INVALID_HANDLE_VALUE;
+ m_conpty_output = INVALID_HANDLE_VALUE;
+}
diff --git a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
index befc28b09d185..427b2ce4c21fe 100644
--- a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
+++ b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
@@ -488,12 +488,14 @@ lldb::ProcessSP PlatformPOSIX::DebugProcess(ProcessLaunchInfo &launch_info,
if (error.Success()) {
// Hook up process PTY if we have one (which we should for local debugging
// with llgs).
+#ifndef _WIN32 // TODO: Implement on Windows
int pty_fd = launch_info.GetPTY().ReleasePrimaryFileDescriptor();
if (pty_fd != PseudoTerminal::invalid_fd) {
process_sp->SetSTDIOFileDescriptor(pty_fd);
LLDB_LOG(log, "hooked up STDIO pty to process");
} else
LLDB_LOG(log, "not using process STDIO pty");
+#endif
} else {
LLDB_LOG(log, "{0}", error);
// FIXME figure out appropriate cleanup here. Do we delete the process?
diff --git a/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp b/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
index c182d3d862269..1054892496732 100644
--- a/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
+++ b/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
@@ -235,10 +235,12 @@ lldb::ProcessSP PlatformQemuUser::DebugProcess(ProcessLaunchInfo &launch_info,
if (error.Fail())
return nullptr;
+#ifndef _WIN32 // TODO: Implement on Windows
if (launch_info.GetPTY().GetPrimaryFileDescriptor() !=
PseudoTerminal::invalid_fd)
process_sp->SetSTDIOFileDescriptor(
launch_info.GetPTY().ReleasePrimaryFileDescriptor());
+#endif
return process_sp;
}
diff --git a/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp b/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp
index f77ac7abbb678..75fa815131c76 100644
--- a/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp
+++ b/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp
@@ -203,11 +203,11 @@ lldb::ProcessSP PlatformWasm::DebugProcess(ProcessLaunchInfo &launch_info,
return nullptr;
}
-
+#ifndef _WIN32
if (launch_info.GetPTY().GetPrimaryFileDescriptor() !=
PseudoTerminal::invalid_fd)
process_sp->SetSTDIOFileDescriptor(
launch_info.GetPTY().ReleasePrimaryFileDescriptor());
-
+#endif
return process_sp;
}
diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
index c0c26cc5f1954..f106c01601e29 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
@@ -519,8 +519,18 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info,
// We need to launch and attach to the process.
launch_info.GetFlags().Set(eLaunchFlagDebug);
- if (process_sp)
- error = process_sp->Launch(launch_info);
+ if (!process_sp)
+ return nullptr;
+ error = process_sp->Launch(launch_info);
+#ifdef _WIN32
+ if (error.Success())
+ process_sp->SetPseudoConsoleHandle(launch_info.GetPTYSP());
+ else {
+ Log *log = GetLog(LLDBLog::Platform);
+ LLDB_LOGF(log, "Platform::%s LaunchProcess() failed: %s", __FUNCTION__,
+ error.AsCString());
+ }
+#endif
return process_sp;
}
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 79dd46ba319d6..e73b5749edde0 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -15,6 +15,7 @@
#include "lldb/Host/HostNativeProcessBase.h"
#include "lldb/Host/HostProcess.h"
#include "lldb/Host/ProcessLaunchInfo.h"
+#include "lldb/Host/PseudoTerminal.h"
#include "lldb/Host/windows/AutoHandle.h"
#include "lldb/Host/windows/HostThreadWindows.h"
#include "lldb/Host/windows/ProcessLauncherWindows.h"
@@ -47,9 +48,10 @@ namespace lldb_private {
NativeProcessWindows::NativeProcessWindows(ProcessLaunchInfo &launch_info,
NativeDelegate &delegate,
llvm::Error &E)
- : NativeProcessProtocol(LLDB_INVALID_PROCESS_ID,
- launch_info.GetPTY().ReleasePrimaryFileDescriptor(),
- delegate),
+ : NativeProcessProtocol(
+ LLDB_INVALID_PROCESS_ID,
+ PseudoTerminal::invalid_fd, // TODO: Implement on Windows
+ delegate),
ProcessDebugger(), m_arch(launch_info.GetArchitecture()) {
ErrorAsOutParameter EOut(&E);
DebugDelegateSP delegate_sp(new NativeDebugDelegate(*this));
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 4cc39f928ee1e..127dd0f59e9ae 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -13,6 +13,7 @@
#include <psapi.h>
#include "lldb/Breakpoint/Watchpoint.h"
+#include "lldb/Core/IOHandler.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/PluginManager.h"
@@ -21,12 +22,17 @@
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/HostNativeProcessBase.h"
#include "lldb/Host/HostProcess.h"
+#include "lldb/Host/Pipe.h"
+#include "lldb/Host/PseudoTerminal.h"
+#include "lldb/Host/windows/ConnectionGenericFileWindows.h"
#include "lldb/Host/windows/HostThreadWindows.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Target/DynamicLoader.h"
#include "lldb/Target/MemoryRegionInfo.h"
#include "lldb/Target/StopInfo.h"
#include "lldb/Target/Target.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
#include "lldb/Utility/State.h"
#include "llvm/Support/ConvertUTF.h"
@@ -123,22 +129,6 @@ ProcessWindows::ProcessWindows(lldb::TargetSP target_sp,
ProcessWindows::~ProcessWindows() {}
-size_t ProcessWindows::GetSTDOUT(char *buf, size_t buf_size, Status &error) {
- error = Status::FromErrorString("GetSTDOUT unsupported on Windows");
- return 0;
-}
-
-size_t ProcessWindows::GetSTDERR(char *buf, size_t buf_size, Status &error) {
- error = Status::FromErrorString("GetSTDERR unsupported on Windows");
- return 0;
-}
-
-size_t ProcessWindows::PutSTDIN(const char *buf, size_t buf_size,
- Status &error) {
- error = Status::FromErrorString("PutSTDIN unsupported on Windows");
- return 0;
-}
-
Status ProcessWindows::EnableBreakpointSite(BreakpointSite *bp_site) {
if (bp_site->HardwareRequired())
return Status::FromErrorString("Hardware breakpoints are not supported.");
@@ -661,6 +651,7 @@ void ProcessWindows::OnExitProcess(uint32_t exit_code) {
LLDB_LOG(log, "Process {0} exited with code {1}", GetID(), exit_code);
TargetSP target = CalculateTarget();
+ target->GetProcessLaunchInfo().GetPTY().Close();
if (target) {
ModuleSP executable_module = target->GetExecutableModule();
ModuleList unloaded_modules;
@@ -956,4 +947,142 @@ Status ProcessWindows::DisableWatchpoint(WatchpointSP wp_sp, bool notify) {
return error;
}
+
+class IOHandlerProcessSTDIOWindows : public IOHandler {
+public:
+ IOHandlerProcessSTDIOWindows(Process *process, HANDLE conpty_input)
+ : IOHandler(process->GetTarget().GetDebugger(),
+ IOHandler::Type::ProcessIO),
+ m_process(process),
+ m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false),
+ m_write_file(conpty_input) {
+ m_pipe.CreateNew();
+ }
+
+ ~IOHandlerProcessSTDIOWindows() override = default;
+
+ void SetIsRunning(bool running) {
+ std::lock_guard<std::mutex> guard(m_mutex);
+ SetIsDone(!running);
+ m_is_running = running;
+ }
+
+ void Run() override {
+ if (!m_read_file.IsValid() || m_write_file == INVALID_HANDLE_VALUE ||
+ !m_pipe.CanRead() || !m_pipe.CanWrite()) {
+ SetIsDone(true);
+ return;
+ }
+
+ SetIsDone(false);
+ SetIsRunning(true);
+
+ HANDLE hStdin = (HANDLE)_get_osfhandle(m_read_file.GetDescriptor());
+ HANDLE hInterrupt = (HANDLE)_get_osfhandle(m_pipe.GetReadFileDescriptor());
+ HANDLE waitHandles[2] = {hStdin, hInterrupt};
+
+ while (true) {
+ {
+ std::lock_guard<std::mutex> guard(m_mutex);
+ if (GetIsDone())
+ goto exit_loop;
+ }
+
+ DWORD result = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
+ switch (result) {
+ case WAIT_FAILED:
+ goto exit_loop;
+ case WAIT_OBJECT_0: {
+ char ch = 0;
+ DWORD read = 0;
+ if (!ReadFile(hStdin, &ch, 1, &read, nullptr) || read != 1)
+ goto exit_loop;
+
+ DWORD written = 0;
+ if (!WriteFile(m_write_file, &ch, 1, &written, nullptr) || written != 1)
+ goto exit_loop;
+ break;
+ }
+ case WAIT_OBJECT_0 + 1: {
+ char ch = 0;
+ DWORD read = 0;
+ if (!ReadFile(hInterrupt, &ch, 1, &read, nullptr) || read != 1)
+ goto exit_loop;
+
+ if (ch == eControlOpQuit)
+ goto exit_loop;
+ if (ch == eControlOpInterrupt &&
+ StateIsRunningState(m_process->GetState()))
+ m_process->SendAsyncInterrupt();
+ break;
+ }
+ default:
+ goto exit_loop;
+ }
+ }
+
+ exit_loop:;
+ SetIsRunning(false);
+ }
+
+ void Cancel() override {
+ std::lock_guard<std::mutex> guard(m_mutex);
+ SetIsDone(true);
+ if (m_is_running) {
+ char ch = eControlOpQuit;
+ if (llvm::Error err = m_pipe.Write(&ch, 1).takeError()) {
+ LLDB_LOG_ERROR(GetLog(LLDBLog::Process), std::move(err),
+ "Pipe write failed: {0}");
+ }
+ }
+ }
+
+ bool Interrupt() override {
+ if (m_active) {
+ char ch = eControlOpInterrupt;
+ return !errorToBool(m_pipe.Write(&ch, 1).takeError());
+ }
+ if (StateIsRunningState(m_process->GetState())) {
+ m_process->SendAsyncInterrupt();
+ return true;
+ }
+ return false;
+ }
+
+ void GotEOF() override {}
+
+private:
+ Process *m_process;
+ NativeFile m_read_file; // Read from this file (usually actual STDIN for LLDB
+ HANDLE m_write_file =
+ INVALID_HANDLE_VALUE; // Write to this file (usually the primary pty for
+ // getting io to debuggee)
+ Pipe m_pipe;
+ std::mutex m_mutex;
+ bool m_is_running = false;
+
+ enum ControlOp : char {
+ eControlOpQuit = 'q',
+ eControlOpInterrupt = 'i',
+ };
+};
+
+void ProcessWindows::SetPseudoConsoleHandle(
+ const std::shared_ptr<PseudoConsole> &pty) {
+ m_stdio_communication.SetConnection(
+ std::make_unique<ConnectionGenericFile>(pty->GetSTDOUTHandle(), false));
+ if (m_stdio_communication.IsConnected()) {
+ m_stdio_communication.SetReadThreadBytesReceivedCallback(
+ STDIOReadThreadBytesReceived, this);
+ m_stdio_communication.StartReadThread();
+
+ // Now read thread is set up, set up input reader.
+ {
+ std::lock_guard<std::mutex> guard(m_process_input_reader_mutex);
+ if (!m_process_input_reader)
+ m_process_input_reader = std::make_shared<IOHandlerProcessSTDIOWindows>(
+ this, pty->GetSTDINHandle());
+ }
+ }
+}
} // namespace lldb_private
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
index 97284b7cd1436..33e4de6b85932 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
@@ -9,6 +9,7 @@
#ifndef liblldb_Plugins_Process_Windows_Common_ProcessWindows_H_
#define liblldb_Plugins_Process_Windows_Common_ProcessWindows_H_
+#include "lldb/Host/windows/PseudoConsole.h"
#include "lldb/Target/Process.h"
#include "lldb/Utility/Status.h"
#include "lldb/lldb-forward.h"
@@ -38,10 +39,6 @@ class ProcessWindows : public Process, public ProcessDebugger {
~ProcessWindows();
- size_t GetSTDOUT(char *buf, size_t buf_size, Status &error) override;
- size_t GetSTDERR(char *buf, size_t buf_size, Status &error) override;
- size_t PutSTDIN(const char *buf, size_t buf_size, Status &error) override;
-
llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
Status EnableBreakpointSite(BreakpointSite *bp_site) override;
@@ -101,6 +98,9 @@ class ProcessWindows : public Process, public ProcessDebugger {
Status DisableWatchpoint(lldb::WatchpointSP wp_sp,
bool notify = true) override;
+ void
+ SetPseudoConsoleHandle(const std::shared_ptr<PseudoConsole> &pty) override;
+
protected:
ProcessWindows(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp);
diff --git a/lldb/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp
index 5b0930cf26b77..67a5857c12aa0 100644
--- a/lldb/source/Target/Platform.cpp
+++ b/lldb/source/Target/Platform.cpp
@@ -1054,10 +1054,12 @@ lldb::ProcessSP Platform::DebugProcess(ProcessLaunchInfo &launch_info,
// been used where the secondary side was given as the file to open for
// stdin/out/err after we have already opened the primary so we can
// read/write stdin/out/err.
+#ifndef _WIN32
int pty_fd = launch_info.GetPTY().ReleasePrimaryFileDescriptor();
if (pty_fd != PseudoTerminal::invalid_fd) {
process_sp->SetSTDIOFileDescriptor(pty_fd);
}
+#endif
} else {
LLDB_LOGF(log, "Platform::%s Attach() failed: %s", __FUNCTION__,
error.AsCString());
diff --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
index ca881f1d817c5..2fdf1bb42ca09 100644
--- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
+++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
@@ -16,7 +16,7 @@
class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase):
- @skipIfWindows
+ @skipIfWindows(major=10, build=1809)
def test_default(self):
"""
Tests the default launch of a simple program. No arguments,
@@ -76,7 +76,7 @@ def test_failing_console(self):
r"unexpected value, expected 'internalConsole\', 'integratedTerminal\' or 'externalTerminal\' at arguments.console",
)
- @skipIfWindows
+ @skipIfWindows(major=10, build=1809)
def test_termination(self):
"""
Tests the correct termination of lldb-dap upon a 'disconnect'
@@ -209,8 +209,8 @@ def test_disableSTDIO(self):
output = self.get_stdout()
self.assertEqual(output, "", "expect no program output")
- @skipIfWindows
@skipIfLinux # shell argument expansion doesn't seem to work on Linux
+ @skipIfWindows(major=10, build=1809)
@expectedFailureAll(oslist=["freebsd", "netbsd"], bugnumber="llvm.org/pr48349")
def test_shellExpandArguments_enabled(self):
"""
@@ -233,7 +233,7 @@ def test_shellExpandArguments_enabled(self):
quote_path, line, 'verify "%s" expanded to "%s"' % (glob, program)
)
- @skipIfWindows
+ @skipIfWindows(major=10, build=1809)
def test_shellExpandArguments_disabled(self):
"""
Tests the default launch of a simple program with shell expansion
@@ -255,7 +255,7 @@ def test_shellExpandArguments_disabled(self):
quote_path, line, 'verify "%s" stayed to "%s"' % (glob, glob)
)
- @skipIfWindows
+ @skipIfWindows(major=10, build=1809)
def test_args(self):
"""
Tests launch of a simple program with arguments
@@ -280,7 +280,7 @@ def test_args(self):
'arg[%i] "%s" not in "%s"' % (i + 1, quoted_arg, lines[i]),
)
- @skipIfWindows
+ @skipIfWindows(major=10, build=1809)
def test_environment_with_object(self):
"""
Tests launch of a simple program with environment variables
@@ -557,7 +557,7 @@ def test_terminate_commands(self):
output = self.collect_console(pattern=terminateCommands[0])
self.verify_commands("terminateCommands", output, terminateCommands)
- @skipIfWindows
+ @skipIfWindows(major=10, build=1809)
def test_version(self):
"""
Tests that "initialize" response contains the "version" string the same
@@ -640,7 +640,7 @@ def test_stdio_redirection(self):
)
@skipIfAsan
- @skipIfWindows
+ @skipIfWindows(major=10, build=1809)
@skipIf(oslist=["linux"], archs=no_match(["x86_64"]))
@skipIfBuildType(["debug"])
def test_stdio_redirection_and_console(self):
diff --git a/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn b/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn
index af4533285d3e9..f200a637ae99a 100644
--- a/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn
+++ b/llvm/utils/gn/secondary/lldb/source/Host/BUILD.gn
@@ -75,6 +75,7 @@ static_library("Host") {
"windows/MainLoopWindows.cpp",
"windows/PipeWindows.cpp",
"windows/ProcessLauncherWindows.cpp",
+ "windows/PseudoConsole.cpp",
"windows/ProcessRunLock.cpp",
]
} else {
More information about the lldb-commits
mailing list