LLDB mainline
PseudoConsole.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10
11#include <cstdio>
12#include <mutex>
13
16
17#include "llvm/Support/Errc.h"
18
19using namespace lldb_private;
20
21using CreatePseudoConsole_t = HRESULT(WINAPI *)(COORD size, HANDLE hInput,
22 HANDLE hOutput, DWORD dwFlags,
23 HPCON *phPC);
24
25using ClosePseudoConsole_t = VOID(WINAPI *)(HPCON hPC);
26
27static constexpr DWORD PSEUDOCONSOLE_INHERIT_CURSOR = 0x1;
28
29struct Kernel32 {
31 hModule = LoadLibraryW(L"kernel32.dll");
32 if (!hModule) {
33 llvm::Error err = llvm::errorCodeToError(
34 std::error_code(GetLastError(), std::system_category()));
35 LLDB_LOG_ERROR(GetLog(LLDBLog::Host), std::move(err),
36 "Could not load kernel32: {0}");
37 return;
38 }
40 (CreatePseudoConsole_t)GetProcAddress(hModule, "CreatePseudoConsole");
42 (ClosePseudoConsole_t)GetProcAddress(hModule, "ClosePseudoConsole");
44 }
45
46 HRESULT CreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput,
47 DWORD dwFlags, HPCON *phPC) {
48 assert(CreatePseudoConsole_ && "CreatePseudoConsole is not available!");
49 return CreatePseudoConsole_(size, hInput, hOutput, dwFlags, phPC);
50 }
51
53 assert(ClosePseudoConsole_ && "ClosePseudoConsole is not available!");
54 return ClosePseudoConsole_(hPC);
55 }
56
57 bool IsConPTYAvailable() { return isAvailable; }
58
59private:
60 HMODULE hModule = nullptr;
63 bool isAvailable = false;
64};
65
67
69 HANDLE &out_write,
70 bool inheritable) {
71 wchar_t pipe_name[MAX_PATH];
72 swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p",
73 GetCurrentProcessId(), this);
74 out_read =
75 CreateNamedPipeW(pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
76 PIPE_TYPE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, nullptr);
77 if (out_read == INVALID_HANDLE_VALUE)
78 return llvm::errorCodeToError(
79 std::error_code(GetLastError(), std::system_category()));
80 SECURITY_ATTRIBUTES write_sa = {sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
81 out_write = CreateFileW(pipe_name, GENERIC_WRITE, 0,
82 inheritable ? &write_sa : nullptr, OPEN_EXISTING,
83 FILE_ATTRIBUTE_NORMAL, nullptr);
84 if (out_write == INVALID_HANDLE_VALUE) {
85 CloseHandle(out_read);
86 out_read = INVALID_HANDLE_VALUE;
87 return llvm::errorCodeToError(
88 std::error_code(GetLastError(), std::system_category()));
89 }
90
91 return llvm::Error::success();
92}
93
95
97 Reset();
98
99 if (!kernel32.IsConPTYAvailable())
100 return llvm::make_error<llvm::StringError>("ConPTY is not available",
101 llvm::errc::io_error);
102 // A 4096 bytes buffer should be large enough for the majority of console
103 // burst outputs.
104 wchar_t pipe_name[MAX_PATH];
105 swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p",
106 GetCurrentProcessId(), this);
107 HANDLE hOutputRead = INVALID_HANDLE_VALUE;
108 HANDLE hOutputWrite = INVALID_HANDLE_VALUE;
109 if (auto err = CreateOverlappedPipePair(hOutputRead, hOutputWrite, false))
110 return err;
111
112 HANDLE hInputRead = INVALID_HANDLE_VALUE;
113 HANDLE hInputWrite = INVALID_HANDLE_VALUE;
114 if (!CreatePipe(&hInputRead, &hInputWrite, nullptr, 0)) {
115 CloseHandle(hOutputRead);
116 CloseHandle(hOutputWrite);
117 return llvm::errorCodeToError(
118 std::error_code(GetLastError(), std::system_category()));
119 }
120
121 COORD consoleSize{80, 25};
122 // Cursor position within the visible window, 1-indexed for VT sequences.
123 // Defaults to the last row so ConPTY won't scroll back over existing output
124 // if we can't query the real console.
125 int cursorRow = consoleSize.Y;
126 int cursorCol = 1;
127 CONSOLE_SCREEN_BUFFER_INFO csbi;
128 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
129 consoleSize = {
130 static_cast<SHORT>(csbi.srWindow.Right - csbi.srWindow.Left + 1),
131 static_cast<SHORT>(csbi.srWindow.Bottom - csbi.srWindow.Top + 1)};
132 cursorRow = csbi.dwCursorPosition.Y - csbi.srWindow.Top + 1;
133 cursorCol = csbi.dwCursorPosition.X + 1;
134 }
135 HPCON hPC = INVALID_HANDLE_VALUE;
136 HRESULT hr =
137 kernel32.CreatePseudoConsole(consoleSize, hInputRead, hOutputWrite,
139 CloseHandle(hInputRead);
140 CloseHandle(hOutputWrite);
141
142 if (FAILED(hr)) {
143 CloseHandle(hInputWrite);
144 CloseHandle(hOutputRead);
145 return llvm::make_error<llvm::StringError>(
146 "Failed to create Windows ConPTY pseudo terminal",
147 llvm::errc::io_error);
148 }
149
150 m_conpty_handle = hPC;
151 m_conpty_output = hOutputRead;
152 m_conpty_input = hInputWrite;
154
155 // PSEUDOCONSOLE_INHERIT_CURSOR causes ConPTY to emit ESC[6n on the output
156 // pipe to query the current cursor position before it finishes initializing.
157 // Write the cursor position response to the input pipe so ConPTY can read it
158 // and initialize without clearing the screen or overwriting LLDB's prompt.
159 {
160 llvm::SmallString<32> response =
161 llvm::formatv("\x1b[{0};{1}R", cursorRow, cursorCol).sstr<32>();
162 DWORD nwritten = 0;
163 WriteFile(m_conpty_input, response.data(), response.size(), &nwritten,
164 nullptr);
165 }
166
167 return llvm::Error::success();
168}
169
171 if (m_mode == Mode::Pipe)
172 return m_conpty_input != INVALID_HANDLE_VALUE &&
173 m_conpty_output != INVALID_HANDLE_VALUE;
174 return m_conpty_handle != INVALID_HANDLE_VALUE &&
175 m_conpty_input != INVALID_HANDLE_VALUE &&
176 m_conpty_output != INVALID_HANDLE_VALUE;
177}
178
180 SetStopping(true);
181 std::unique_lock<std::mutex> guard(m_mutex);
182 if (m_conpty_handle != INVALID_HANDLE_VALUE)
183 kernel32.ClosePseudoConsole(m_conpty_handle);
184 m_conpty_handle = INVALID_HANDLE_VALUE;
185 SetStopping(false);
186 m_cv.notify_all();
187}
188
190 if (m_conpty_input != INVALID_HANDLE_VALUE)
191 CloseHandle(m_conpty_input);
192 if (m_conpty_output != INVALID_HANDLE_VALUE)
193 CloseHandle(m_conpty_output);
194
195 m_conpty_input = INVALID_HANDLE_VALUE;
196 m_conpty_output = INVALID_HANDLE_VALUE;
197}
198
200 if (m_pipe_child_stdin != INVALID_HANDLE_VALUE)
201 CloseHandle(m_pipe_child_stdin);
202 if (m_pipe_child_stdout != INVALID_HANDLE_VALUE)
203 CloseHandle(m_pipe_child_stdout);
204
205 m_pipe_child_stdin = INVALID_HANDLE_VALUE;
206 m_pipe_child_stdout = INVALID_HANDLE_VALUE;
207}
208
215
217 Reset();
218
219 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
220 HANDLE hStdinRead = INVALID_HANDLE_VALUE;
221 HANDLE hStdinWrite = INVALID_HANDLE_VALUE;
222 if (!CreatePipe(&hStdinRead, &hStdinWrite, &sa, 0))
223 return llvm::errorCodeToError(
224 std::error_code(GetLastError(), std::system_category()));
225 // Parent write end must not be inherited by the child.
226 SetHandleInformation(hStdinWrite, HANDLE_FLAG_INHERIT, 0);
227
228 HANDLE hStdoutRead = INVALID_HANDLE_VALUE;
229 HANDLE hStdoutWrite = INVALID_HANDLE_VALUE;
230 if (auto err = CreateOverlappedPipePair(hStdoutRead, hStdoutWrite, true)) {
231 CloseHandle(hStdinRead);
232 CloseHandle(hStdinWrite);
233 return err;
234 }
235
236 m_conpty_input = hStdinWrite;
237 m_conpty_output = hStdoutRead;
238 m_pipe_child_stdin = hStdinRead;
239 m_pipe_child_stdout = hStdoutWrite;
241 return llvm::Error::success();
242}
#define LLDB_LOG_ERROR(log, error,...)
Definition Log.h:394
HRESULT(WINAPI *)(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON *phPC) CreatePseudoConsole_t
VOID(WINAPI *)(HPCON hPC) ClosePseudoConsole_t
static Kernel32 kernel32
static constexpr DWORD PSEUDOCONSOLE_INHERIT_CURSOR
void * HPCON
void * HANDLE
llvm::Error OpenPseudoConsole()
Creates and opens a new ConPTY instance with a default console size of 80x25.
llvm::Error CreateOverlappedPipePair(HANDLE &out_read, HANDLE &out_write, bool inheritable)
Creates a named pipe pair for overlapped I/O.
std::condition_variable m_cv
llvm::Error OpenAnonymousPipes()
Creates a pair of anonymous pipes to use for stdio instead of a ConPTY.
void SetStopping(bool value)
Sets the stopping flag to value, signalling to threads waiting on the ConPTY that they should stop.
void Close()
Closes the ConPTY and invalidates its handle, without closing the STDIN and STDOUT pipes.
void Reset()
Closes any open ConPTY/pipe handles and resets internal state to a freshly-constructed PseudoConsole.
void ClosePseudoConsolePipes()
Closes the STDIN and STDOUT pipe handles and invalidates them.
bool IsConnected() const
Returns whether the ConPTY and its pipes are currently open and valid.
void CloseAnonymousPipes()
Closes the child-side pipe handles (stdin read end and stdout/stderr write end) that were passed to C...
A class that represents a running process on the host machine.
Log * GetLog(Cat mask)
Retrieve the Log object for the channel associated with the given log enum.
Definition Log.h:327
VOID ClosePseudoConsole(HPCON hPC)
CreatePseudoConsole_t CreatePseudoConsole_
HMODULE hModule
bool IsConPTYAvailable()
HRESULT CreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON *phPC)
ClosePseudoConsole_t ClosePseudoConsole_