| //===----------------------------------------------------------------------===// |
| // |
| // 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 <cstdio> |
| #include <mutex> |
| |
| #include "lldb/Host/windows/windows.h" |
| #include "lldb/Utility/LLDBLog.h" |
| |
| #include "llvm/Support/Errc.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); |
| |
| static constexpr DWORD PSEUDOCONSOLE_INHERIT_CURSOR = 0x1; |
| |
| 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::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())); |
| } |
| |
| return llvm::Error::success(); |
| } |
| |
| PseudoConsole::~PseudoConsole() { |
| Close(); |
| 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<llvm::StringError>("ConPTY is not available", |
| llvm::errc::io_error); |
| |
| assert(m_conpty_handle == INVALID_HANDLE_VALUE && |
| "ConPTY has already been opened"); |
| |
| // 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; |
| |
| 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}; |
| // Cursor position within the visible window, 1-indexed for VT sequences. |
| // Defaults to the last row so ConPTY won't scroll back over existing output |
| // if we can't query the real console. |
| int cursorRow = consoleSize.Y; |
| int cursorCol = 1; |
| CONSOLE_SCREEN_BUFFER_INFO csbi; |
| if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { |
| consoleSize = { |
| static_cast<SHORT>(csbi.srWindow.Right - csbi.srWindow.Left + 1), |
| static_cast<SHORT>(csbi.srWindow.Bottom - csbi.srWindow.Top + 1)}; |
| cursorRow = csbi.dwCursorPosition.Y - csbi.srWindow.Top + 1; |
| cursorCol = csbi.dwCursorPosition.X + 1; |
| } |
| HPCON hPC = INVALID_HANDLE_VALUE; |
| HRESULT hr = |
| kernel32.CreatePseudoConsole(consoleSize, hInputRead, hOutputWrite, |
| PSEUDOCONSOLE_INHERIT_CURSOR, &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); |
| } |
| |
| m_conpty_handle = hPC; |
| m_conpty_output = hOutputRead; |
| m_conpty_input = hInputWrite; |
| m_mode = Mode::ConPTY; |
| |
| // PSEUDOCONSOLE_INHERIT_CURSOR causes ConPTY to emit ESC[6n on the output |
| // pipe to query the current cursor position before it finishes initializing. |
| // Write the cursor position response to the input pipe so ConPTY can read it |
| // and initialize without clearing the screen or overwriting LLDB's prompt. |
| { |
| llvm::SmallString<32> response = |
| llvm::formatv("\x1b[{0};{1}R", cursorRow, cursorCol).sstr<32>(); |
| DWORD nwritten = 0; |
| WriteFile(m_conpty_input, response.data(), response.size(), &nwritten, |
| NULL); |
| } |
| |
| return llvm::Error::success(); |
| } |
| |
| 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; |
| } |
| |
| void PseudoConsole::Close() { |
| SetStopping(true); |
| std::unique_lock<std::mutex> guard(m_mutex); |
| if (m_conpty_handle != INVALID_HANDLE_VALUE) |
| kernel32.ClosePseudoConsole(m_conpty_handle); |
| m_conpty_handle = INVALID_HANDLE_VALUE; |
| SetStopping(false); |
| m_cv.notify_all(); |
| } |
| |
| void PseudoConsole::ClosePseudoConsolePipes() { |
| if (m_conpty_input != INVALID_HANDLE_VALUE) |
| CloseHandle(m_conpty_input); |
| if (m_conpty_output != INVALID_HANDLE_VALUE) |
| CloseHandle(m_conpty_output); |
| |
| m_conpty_input = INVALID_HANDLE_VALUE; |
| 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(); |
| } |