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
96llvm::Error PseudoConsole::OpenPseudoConsole(uint16_t req_cols,
97 uint16_t req_rows) {
98 Reset();
99
100 if (!kernel32.IsConPTYAvailable())
101 return llvm::make_error<llvm::StringError>("ConPTY is not available",
102 llvm::errc::io_error);
103 // A 4096 bytes buffer should be large enough for the majority of console
104 // burst outputs.
105 wchar_t pipe_name[MAX_PATH];
106 swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p",
107 GetCurrentProcessId(), this);
108 HANDLE hOutputRead = INVALID_HANDLE_VALUE;
109 HANDLE hOutputWrite = INVALID_HANDLE_VALUE;
110 if (auto err = CreateOverlappedPipePair(hOutputRead, hOutputWrite, false))
111 return err;
112
113 HANDLE hInputRead = INVALID_HANDLE_VALUE;
114 HANDLE hInputWrite = INVALID_HANDLE_VALUE;
115 if (!CreatePipe(&hInputRead, &hInputWrite, nullptr, 0)) {
116 CloseHandle(hOutputRead);
117 CloseHandle(hOutputWrite);
118 return llvm::errorCodeToError(
119 std::error_code(GetLastError(), std::system_category()));
120 }
121
122 COORD consoleSize{80, 25};
123 // Cursor position within the visible window, 1-indexed for VT sequences.
124 // Defaults to the last row so ConPTY won't scroll back over existing output
125 // if we can't query the real console.
126 int cursorRow = consoleSize.Y;
127 int cursorCol = 1;
128 if (req_cols != 0 && req_rows != 0) {
129 consoleSize = {static_cast<SHORT>(req_cols), static_cast<SHORT>(req_rows)};
130 cursorRow = consoleSize.Y;
131 cursorCol = 1;
132 } else {
133 CONSOLE_SCREEN_BUFFER_INFO csbi;
134 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
135 consoleSize = {
136 static_cast<SHORT>(csbi.srWindow.Right - csbi.srWindow.Left + 1),
137 static_cast<SHORT>(csbi.srWindow.Bottom - csbi.srWindow.Top + 1)};
138 cursorRow = csbi.dwCursorPosition.Y - csbi.srWindow.Top + 1;
139 cursorCol = csbi.dwCursorPosition.X + 1;
140 }
141 }
142 HPCON hPC = INVALID_HANDLE_VALUE;
143 HRESULT hr =
144 kernel32.CreatePseudoConsole(consoleSize, hInputRead, hOutputWrite,
146 CloseHandle(hInputRead);
147 CloseHandle(hOutputWrite);
148
149 if (FAILED(hr)) {
150 CloseHandle(hInputWrite);
151 CloseHandle(hOutputRead);
152 return llvm::make_error<llvm::StringError>(
153 "Failed to create Windows ConPTY pseudo terminal",
154 llvm::errc::io_error);
155 }
156
157 m_conpty_handle = hPC;
158 m_conpty_output = hOutputRead;
159 m_conpty_input = hInputWrite;
161
162 // PSEUDOCONSOLE_INHERIT_CURSOR causes ConPTY to emit ESC[6n on the output
163 // pipe to query the current cursor position before it finishes initializing.
164 // Write the cursor position response to the input pipe so ConPTY can read it
165 // and initialize without clearing the screen or overwriting LLDB's prompt.
166 {
167 llvm::SmallString<32> response =
168 llvm::formatv("\x1b[{0};{1}R", cursorRow, cursorCol).sstr<32>();
169 DWORD nwritten = 0;
170 WriteFile(m_conpty_input, response.data(), response.size(), &nwritten,
171 nullptr);
172 }
173
174 return llvm::Error::success();
175}
176
178 if (m_mode == Mode::Pipe)
179 return m_conpty_input != INVALID_HANDLE_VALUE &&
180 m_conpty_output != INVALID_HANDLE_VALUE;
181 return m_conpty_handle != INVALID_HANDLE_VALUE &&
182 m_conpty_input != INVALID_HANDLE_VALUE &&
183 m_conpty_output != INVALID_HANDLE_VALUE;
184}
185
187 SetStopping(true);
188 std::unique_lock<std::mutex> guard(m_mutex);
189 if (m_conpty_handle != INVALID_HANDLE_VALUE)
190 kernel32.ClosePseudoConsole(m_conpty_handle);
191 if (m_mode == Mode::Pipe && m_conpty_output != INVALID_HANDLE_VALUE)
192 CancelIoEx(m_conpty_output, nullptr);
193 m_conpty_handle = INVALID_HANDLE_VALUE;
194 SetStopping(false);
195 m_cv.notify_all();
196}
197
199 if (m_conpty_input != INVALID_HANDLE_VALUE)
200 CloseHandle(m_conpty_input);
201 if (m_conpty_output != INVALID_HANDLE_VALUE)
202 CloseHandle(m_conpty_output);
203
204 m_conpty_input = INVALID_HANDLE_VALUE;
205 m_conpty_output = INVALID_HANDLE_VALUE;
206}
207
209 if (m_pipe_child_stdin != INVALID_HANDLE_VALUE)
210 CloseHandle(m_pipe_child_stdin);
211 if (m_pipe_child_stdout != INVALID_HANDLE_VALUE)
212 CloseHandle(m_pipe_child_stdout);
213
214 m_pipe_child_stdin = INVALID_HANDLE_VALUE;
215 m_pipe_child_stdout = INVALID_HANDLE_VALUE;
216}
217
224
226 Reset();
227
228 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
229 HANDLE hStdinRead = INVALID_HANDLE_VALUE;
230 HANDLE hStdinWrite = INVALID_HANDLE_VALUE;
231 if (!CreatePipe(&hStdinRead, &hStdinWrite, &sa, 0))
232 return llvm::errorCodeToError(
233 std::error_code(GetLastError(), std::system_category()));
234 // Parent write end must not be inherited by the child.
235 SetHandleInformation(hStdinWrite, HANDLE_FLAG_INHERIT, 0);
236
237 HANDLE hStdoutRead = INVALID_HANDLE_VALUE;
238 HANDLE hStdoutWrite = INVALID_HANDLE_VALUE;
239 if (auto err = CreateOverlappedPipePair(hStdoutRead, hStdoutWrite, true)) {
240 CloseHandle(hStdinRead);
241 CloseHandle(hStdinWrite);
242 return err;
243 }
244
245 m_conpty_input = hStdinWrite;
246 m_conpty_output = hStdoutRead;
247 m_pipe_child_stdin = hStdinRead;
248 m_pipe_child_stdout = hStdoutWrite;
250 return llvm::Error::success();
251}
#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 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 OpenPseudoConsole(uint16_t req_cols=0, uint16_t req_rows=0)
Creates and opens a new ConPTY instance with a default console size of 80x25.
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_