diff --git a/lldb/include/lldb/Host/ProcessLaunchInfo.h b/lldb/include/lldb/Host/ProcessLaunchInfo.h index 7801662b244ad..365d1952d173f 100644 --- a/lldb/include/lldb/Host/ProcessLaunchInfo.h +++ b/lldb/include/lldb/Host/ProcessLaunchInfo.h @@ -64,6 +64,14 @@ class ProcessLaunchInfo : public ProcessInfo { // but stderr doesn't, then only stderr will be redirected to a pty.) llvm::Error SetUpPtyRedirection(); +#ifdef _WIN32 + // Redirect stdin/stdout/stderr to anonymous pipes instead of a ConPTY. + // Used when terminal emulation is not needed (e.g. lldb-dap internalConsole). + llvm::Error SetUpPipeRedirection(); +#endif + + bool HasPTY() const { return m_pty != nullptr; } + size_t GetNumFileActions() const { return m_file_actions.size(); } const FileAction *GetFileActionAtIndex(size_t idx) const; @@ -138,7 +146,7 @@ class ProcessLaunchInfo : public ProcessInfo { #ifdef _WIN32 if (!m_pty) return false; - return GetPTY().GetPseudoTerminalHandle() != ((HANDLE)(long long)-1) && + return GetPTY().GetMode() != PseudoConsole::Mode::None && GetNumFileActions() == 0; #else return true; diff --git a/lldb/include/lldb/Host/windows/PseudoConsole.h b/lldb/include/lldb/Host/windows/PseudoConsole.h index a1967fc85188c..f57fc524a72d4 100644 --- a/lldb/include/lldb/Host/windows/PseudoConsole.h +++ b/lldb/include/lldb/Host/windows/PseudoConsole.h @@ -22,6 +22,8 @@ namespace lldb_private { class PseudoConsole { public: + enum class Mode { ConPTY, Pipe, None }; + PseudoConsole() = default; ~PseudoConsole(); @@ -30,6 +32,13 @@ class PseudoConsole { PseudoConsole &operator=(const PseudoConsole &) = delete; PseudoConsole &operator=(PseudoConsole &&) = delete; + /// Creates a named pipe pair for overlapped I/O. The read end is set to + /// non-blocking (PIPE_NOWAIT). + /// On failure any handles that were successfully opened are closed and an + /// error is returned. + llvm::Error CreateOverlappedPipePair(HANDLE &out_read, HANDLE &out_write, + bool inheritable); + /// Creates and opens a new ConPTY instance with a default console size of /// 80x25. Also sets up the associated STDIN/STDOUT pipes and drains any /// initialization sequences emitted by Windows. @@ -40,13 +49,24 @@ class PseudoConsole { /// otherwise. llvm::Error OpenPseudoConsole(); + /// Creates a pair of anonymous pipes to use for stdio instead of a ConPTY. + /// + /// \return + /// An llvm::Error if the pipes could not be created. + llvm::Error OpenAnonymousPipes(); + /// Closes the ConPTY and invalidates its handle, without closing the STDIN /// and STDOUT pipes. Closing the ConPTY signals EOF to any process currently /// attached to it. void Close(); - /// Closes the STDIN and STDOUT pipe handles and invalidates them - void ClosePipes(); + /// Closes the STDIN and STDOUT pipe handles and invalidates them. + void ClosePseudoConsolePipes(); + + /// Closes the child-side pipe handles (stdin read end and stdout/stderr write + /// end) that were passed to CreateProcessW. Must be called after a successful + /// CreateProcessW to avoid keeping the pipes alive indefinitely. + void CloseAnonymousPipes(); /// Returns whether the ConPTY and its pipes are currently open and valid. bool IsConnected() const; @@ -78,6 +98,14 @@ class PseudoConsole { /// invalid. HANDLE GetSTDINHandle() const { return m_conpty_input; }; + /// The child-side stdin read HANDLE (pipe mode only). + HANDLE GetChildStdinHandle() const { return m_pipe_child_stdin; }; + + /// The child-side stdout/stderr write HANDLE (pipe mode only). + HANDLE GetChildStdoutHandle() const { return m_pipe_child_stdout; }; + + Mode GetMode() const { return m_mode; }; + /// Drains initialization sequences from the ConPTY output pipe. /// /// When a process first attaches to a ConPTY, Windows emits VT100/ANSI escape @@ -112,6 +140,10 @@ class PseudoConsole { HANDLE m_conpty_handle = ((HANDLE)(long long)-1); HANDLE m_conpty_output = ((HANDLE)(long long)-1); HANDLE m_conpty_input = ((HANDLE)(long long)-1); + // Pipe mode: child-side handles passed to CreateProcessW, closed after launch + HANDLE m_pipe_child_stdin = ((HANDLE)(long long)-1); + HANDLE m_pipe_child_stdout = ((HANDLE)(long long)-1); + Mode m_mode = Mode::None; std::mutex m_mutex{}; std::condition_variable m_cv{}; std::atomic m_stopping = false; diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index d2600d0a6ce44..11b7808bb1b61 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -132,6 +132,10 @@ FLAGS_ENUM(LaunchFlags){ ///< permissions but instead inherit them from its parent. eLaunchFlagMemoryTagging = (1u << 13), ///< Launch process with memory tagging explicitly enabled. + eLaunchFlagUsePipes = + (1u << 14), ///< Use anonymous pipes for stdio instead of a ConPTY on + ///< Windows. Useful when terminal emulation is not needed + ///< (e.g. lldb-dap internalConsole mode). }; /// Thread Run Modes. diff --git a/lldb/source/Host/common/ProcessLaunchInfo.cpp b/lldb/source/Host/common/ProcessLaunchInfo.cpp index 2f67a417996ac..9f87e6a783adc 100644 --- a/lldb/source/Host/common/ProcessLaunchInfo.cpp +++ b/lldb/source/Host/common/ProcessLaunchInfo.cpp @@ -242,6 +242,14 @@ llvm::Error ProcessLaunchInfo::SetUpPtyRedirection() { #endif } +#ifdef _WIN32 +llvm::Error ProcessLaunchInfo::SetUpPipeRedirection() { + if (!m_pty) + m_pty = std::make_shared(); + return m_pty->OpenAnonymousPipes(); +} +#endif + bool ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell( Status &error, bool will_debug, bool first_arg_is_full_shell_command, uint32_t num_resumes) { diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp index fb091eb75d9b8..0d2e14264ede2 100644 --- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp +++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp @@ -130,7 +130,9 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, startupinfoex.StartupInfo.cb = sizeof(STARTUPINFOEXW); startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; - bool use_pty = launch_info.ShouldUsePTY(); + PseudoConsole::Mode pty_mode = launch_info.ShouldUsePTY() + ? launch_info.GetPTY().GetMode() + : PseudoConsole::Mode::None; HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO); HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO); @@ -152,13 +154,31 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, ProcThreadAttributeList attributelist = std::move(*attributelist_or_err); std::vector inherited_handles; - if (use_pty) { + switch (pty_mode) { + case PseudoConsole::Mode::ConPTY: { HPCON hPC = launch_info.GetPTY().GetPseudoTerminalHandle(); if (auto err = attributelist.SetupPseudoConsole(hPC)) { error = Status::FromError(std::move(err)); return HostProcess(); } - } else { + break; + } + case PseudoConsole::Mode::Pipe: { + PseudoConsole &pty = launch_info.GetPTY(); + startupinfoex.StartupInfo.hStdInput = pty.GetChildStdinHandle(); + startupinfoex.StartupInfo.hStdOutput = pty.GetChildStdoutHandle(); + startupinfoex.StartupInfo.hStdError = pty.GetChildStdoutHandle(); + inherited_handles = {pty.GetChildStdinHandle(), pty.GetChildStdoutHandle()}; + if (!UpdateProcThreadAttribute( + startupinfoex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + inherited_handles.data(), inherited_handles.size() * sizeof(HANDLE), + nullptr, nullptr)) { + error = Status(::GetLastError(), eErrorTypeWin32); + return HostProcess(); + } + break; + } + case PseudoConsole::Mode::None: { auto inherited_handles_or_err = GetInheritedHandles(startupinfoex, &launch_info, stdout_handle, stderr_handle, stdin_handle); @@ -167,6 +187,8 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, return HostProcess(); } inherited_handles = std::move(*inherited_handles_or_err); + break; + } } const char *hide_console_var = @@ -182,7 +204,8 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, if (launch_info.GetFlags().Test(eLaunchFlagDebug)) flags |= DEBUG_ONLY_THIS_PROCESS; - if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO) || use_pty) + if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO) || + pty_mode != PseudoConsole::Mode::None) flags &= ~CREATE_NEW_CONSOLE; std::vector environment = @@ -210,8 +233,9 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, BOOL result = ::CreateProcessW( wexecutable.c_str(), pwcommandLine, NULL, NULL, - /*bInheritHandles=*/!inherited_handles.empty() || use_pty, flags, - environment.data(), + /*bInheritHandles=*/!inherited_handles.empty() || + pty_mode != PseudoConsole::Mode::None, + flags, environment.data(), wworkingDirectory.size() == 0 ? NULL : wworkingDirectory.c_str(), reinterpret_cast(&startupinfoex), &pi); @@ -226,6 +250,8 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, // Do not call CloseHandle on pi.hProcess, since we want to pass that back // through the HostProcess. ::CloseHandle(pi.hThread); + if (pty_mode == PseudoConsole::Mode::Pipe) + launch_info.GetPTY().CloseAnonymousPipes(); } if (!result) diff --git a/lldb/source/Host/windows/PseudoConsole.cpp b/lldb/source/Host/windows/PseudoConsole.cpp index b8b74091fe474..7c5818c03e37f 100644 --- a/lldb/source/Host/windows/PseudoConsole.cpp +++ b/lldb/source/Host/windows/PseudoConsole.cpp @@ -65,12 +65,44 @@ struct Kernel32 { static Kernel32 kernel32; +llvm::Error PseudoConsole::CreateOverlappedPipePair(HANDLE &out_read, + HANDLE &out_write, + bool inheritable) { + wchar_t pipe_name[MAX_PATH]; + swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p", + GetCurrentProcessId(), this); + out_read = + CreateNamedPipeW(pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, NULL); + if (out_read == INVALID_HANDLE_VALUE) + return llvm::errorCodeToError( + std::error_code(GetLastError(), std::system_category())); + SECURITY_ATTRIBUTES write_sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; + out_write = + CreateFileW(pipe_name, GENERIC_WRITE, 0, inheritable ? &write_sa : NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (out_write == INVALID_HANDLE_VALUE) { + CloseHandle(out_read); + out_read = INVALID_HANDLE_VALUE; + return llvm::errorCodeToError( + std::error_code(GetLastError(), std::system_category())); + } + + DWORD mode = PIPE_NOWAIT; + SetNamedPipeHandleState(out_read, &mode, NULL, NULL); + return llvm::Error::success(); +} + PseudoConsole::~PseudoConsole() { Close(); - ClosePipes(); + ClosePseudoConsolePipes(); + CloseAnonymousPipes(); } llvm::Error PseudoConsole::OpenPseudoConsole() { + assert(m_mode == Mode::None && + "Attempted to open a PseudoConsole in a different mode than None"); + if (!kernel32.IsConPTYAvailable()) return llvm::make_error("ConPTY is not available", llvm::errc::io_error); @@ -78,27 +110,24 @@ llvm::Error PseudoConsole::OpenPseudoConsole() { assert(m_conpty_handle == INVALID_HANDLE_VALUE && "ConPTY has already been opened"); - HRESULT hr; - HANDLE hInputRead = INVALID_HANDLE_VALUE; - HANDLE hInputWrite = INVALID_HANDLE_VALUE; - HANDLE hOutputRead = INVALID_HANDLE_VALUE; - HANDLE hOutputWrite = INVALID_HANDLE_VALUE; - + // A 4096 bytes buffer should be large enough for the majority of console + // burst outputs. wchar_t pipe_name[MAX_PATH]; swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p", GetCurrentProcessId(), this); + HANDLE hOutputRead = INVALID_HANDLE_VALUE; + HANDLE hOutputWrite = INVALID_HANDLE_VALUE; + if (auto err = CreateOverlappedPipePair(hOutputRead, hOutputWrite, false)) + return err; - // 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)) + HANDLE hInputRead = INVALID_HANDLE_VALUE; + HANDLE hInputWrite = INVALID_HANDLE_VALUE; + if (!CreatePipe(&hInputRead, &hInputWrite, NULL, 0)) { + CloseHandle(hOutputRead); + CloseHandle(hOutputWrite); return llvm::errorCodeToError( std::error_code(GetLastError(), std::system_category())); + } COORD consoleSize{80, 25}; CONSOLE_SCREEN_BUFFER_INFO csbi; @@ -107,8 +136,8 @@ llvm::Error PseudoConsole::OpenPseudoConsole() { static_cast(csbi.srWindow.Right - csbi.srWindow.Left + 1), static_cast(csbi.srWindow.Bottom - csbi.srWindow.Top + 1)}; HPCON hPC = INVALID_HANDLE_VALUE; - hr = kernel32.CreatePseudoConsole(consoleSize, hInputRead, hOutputWrite, 0, - &hPC); + HRESULT hr = kernel32.CreatePseudoConsole(consoleSize, hInputRead, + hOutputWrite, 0, &hPC); CloseHandle(hInputRead); CloseHandle(hOutputWrite); @@ -120,12 +149,10 @@ llvm::Error PseudoConsole::OpenPseudoConsole() { 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; + m_mode = Mode::ConPTY; if (auto error = DrainInitSequences()) { Log *log = GetLog(LLDBLog::Host); @@ -137,6 +164,9 @@ llvm::Error PseudoConsole::OpenPseudoConsole() { } bool PseudoConsole::IsConnected() const { + if (m_mode == Mode::Pipe) + return m_conpty_input != INVALID_HANDLE_VALUE && + m_conpty_output != INVALID_HANDLE_VALUE; return m_conpty_handle != INVALID_HANDLE_VALUE && m_conpty_input != INVALID_HANDLE_VALUE && m_conpty_output != INVALID_HANDLE_VALUE; @@ -152,7 +182,7 @@ void PseudoConsole::Close() { m_cv.notify_all(); } -void PseudoConsole::ClosePipes() { +void PseudoConsole::ClosePseudoConsolePipes() { if (m_conpty_input != INVALID_HANDLE_VALUE) CloseHandle(m_conpty_input); if (m_conpty_output != INVALID_HANDLE_VALUE) @@ -162,6 +192,45 @@ void PseudoConsole::ClosePipes() { m_conpty_output = INVALID_HANDLE_VALUE; } +void PseudoConsole::CloseAnonymousPipes() { + if (m_pipe_child_stdin != INVALID_HANDLE_VALUE) + CloseHandle(m_pipe_child_stdin); + if (m_pipe_child_stdout != INVALID_HANDLE_VALUE) + CloseHandle(m_pipe_child_stdout); + + m_pipe_child_stdin = INVALID_HANDLE_VALUE; + m_pipe_child_stdout = INVALID_HANDLE_VALUE; +} + +llvm::Error PseudoConsole::OpenAnonymousPipes() { + assert(m_mode == Mode::None && + "Attempted to open a AnonymousPipes in a different mode than None"); + + SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; + HANDLE hStdinRead = INVALID_HANDLE_VALUE; + HANDLE hStdinWrite = INVALID_HANDLE_VALUE; + if (!CreatePipe(&hStdinRead, &hStdinWrite, &sa, 0)) + return llvm::errorCodeToError( + std::error_code(GetLastError(), std::system_category())); + // Parent write end must not be inherited by the child. + SetHandleInformation(hStdinWrite, HANDLE_FLAG_INHERIT, 0); + + HANDLE hStdoutRead = INVALID_HANDLE_VALUE; + HANDLE hStdoutWrite = INVALID_HANDLE_VALUE; + if (auto err = CreateOverlappedPipePair(hStdoutRead, hStdoutWrite, true)) { + CloseHandle(hStdinRead); + CloseHandle(hStdinWrite); + return err; + } + + m_conpty_input = hStdinWrite; + m_conpty_output = hStdoutRead; + m_pipe_child_stdin = hStdinRead; + m_pipe_child_stdout = hStdoutWrite; + m_mode = Mode::Pipe; + return llvm::Error::success(); +} + llvm::Error PseudoConsole::DrainInitSequences() { STARTUPINFOEXW startupinfoex = {}; startupinfoex.StartupInfo.cb = sizeof(STARTUPINFOEXW); diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index 9c8124a15333b..939c848395730 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -3859,8 +3859,19 @@ void Target::FinalizeFileActions(ProcessLaunchInfo &info) { } if (default_to_use_pty) { - llvm::Error Err = info.SetUpPtyRedirection(); - LLDB_LOG_ERROR(log, std::move(Err), "SetUpPtyRedirection failed: {0}"); +#ifdef _WIN32 + if (info.GetFlags().Test(eLaunchFlagUsePipes)) { + llvm::Error Err = info.SetUpPipeRedirection(); + LLDB_LOG_ERROR(log, std::move(Err), + "SetUpPipeRedirection failed: {0}"); + } else { +#endif + llvm::Error Err = info.SetUpPtyRedirection(); + LLDB_LOG_ERROR(log, std::move(Err), + "SetUpPtyRedirection failed: {0}"); +#ifdef _WIN32 + } +#endif } } } diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index 5e8c2163c838f..f7aad21c0be41 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -223,6 +223,10 @@ llvm::Error BaseRequestHandler::LaunchProcess( SetLaunchFlag(flags, arguments.disableASLR, lldb::eLaunchFlagDisableASLR); flags = SetLaunchFlag(flags, arguments.disableSTDIO, lldb::eLaunchFlagDisableSTDIO); +#ifdef _WIN32 + flags = SetLaunchFlag(flags, arguments.console == protocol::eConsoleInternal, + lldb::eLaunchFlagUsePipes); +#endif launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug | lldb::eLaunchFlagStopAtEntry);