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
21typedef HRESULT(WINAPI *CreatePseudoConsole_t)(COORD size, HANDLE hInput,
22 HANDLE hOutput, DWORD dwFlags,
23 HPCON *phPC);
24
25typedef VOID(WINAPI *ClosePseudoConsole_t)(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;
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, NULL);
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), NULL, TRUE};
81 out_write =
82 CreateFileW(pipe_name, GENERIC_WRITE, 0, inheritable ? &write_sa : NULL,
83 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
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
99
101 assert(m_mode == Mode::None &&
102 "Attempted to open a PseudoConsole in a different mode than None");
103
104 if (!kernel32.IsConPTYAvailable())
105 return llvm::make_error<llvm::StringError>("ConPTY is not available",
106 llvm::errc::io_error);
107
108 assert(m_conpty_handle == INVALID_HANDLE_VALUE &&
109 "ConPTY has already been opened");
110
111 // A 4096 bytes buffer should be large enough for the majority of console
112 // burst outputs.
113 wchar_t pipe_name[MAX_PATH];
114 swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p",
115 GetCurrentProcessId(), this);
116 HANDLE hOutputRead = INVALID_HANDLE_VALUE;
117 HANDLE hOutputWrite = INVALID_HANDLE_VALUE;
118 if (auto err = CreateOverlappedPipePair(hOutputRead, hOutputWrite, false))
119 return err;
120
121 HANDLE hInputRead = INVALID_HANDLE_VALUE;
122 HANDLE hInputWrite = INVALID_HANDLE_VALUE;
123 if (!CreatePipe(&hInputRead, &hInputWrite, NULL, 0)) {
124 CloseHandle(hOutputRead);
125 CloseHandle(hOutputWrite);
126 return llvm::errorCodeToError(
127 std::error_code(GetLastError(), std::system_category()));
128 }
129
130 COORD consoleSize{80, 25};
131 // Cursor position within the visible window, 1-indexed for VT sequences.
132 // Defaults to the last row so ConPTY won't scroll back over existing output
133 // if we can't query the real console.
134 int cursorRow = consoleSize.Y;
135 int cursorCol = 1;
136 CONSOLE_SCREEN_BUFFER_INFO csbi;
137 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
138 consoleSize = {
139 static_cast<SHORT>(csbi.srWindow.Right - csbi.srWindow.Left + 1),
140 static_cast<SHORT>(csbi.srWindow.Bottom - csbi.srWindow.Top + 1)};
141 cursorRow = csbi.dwCursorPosition.Y - csbi.srWindow.Top + 1;
142 cursorCol = csbi.dwCursorPosition.X + 1;
143 }
144 HPCON hPC = INVALID_HANDLE_VALUE;
145 HRESULT hr =
146 kernel32.CreatePseudoConsole(consoleSize, hInputRead, hOutputWrite,
148 CloseHandle(hInputRead);
149 CloseHandle(hOutputWrite);
150
151 if (FAILED(hr)) {
152 CloseHandle(hInputWrite);
153 CloseHandle(hOutputRead);
154 return llvm::make_error<llvm::StringError>(
155 "Failed to create Windows ConPTY pseudo terminal",
156 llvm::errc::io_error);
157 }
158
159 m_conpty_handle = hPC;
160 m_conpty_output = hOutputRead;
161 m_conpty_input = hInputWrite;
163
164 // PSEUDOCONSOLE_INHERIT_CURSOR causes ConPTY to emit ESC[6n on the output
165 // pipe to query the current cursor position before it finishes initializing.
166 // Write the cursor position response to the input pipe so ConPTY can read it
167 // and initialize without clearing the screen or overwriting LLDB's prompt.
168 {
169 llvm::SmallString<32> response =
170 llvm::formatv("\x1b[{0};{1}R", cursorRow, cursorCol).sstr<32>();
171 DWORD nwritten = 0;
172 WriteFile(m_conpty_input, response.data(), response.size(), &nwritten,
173 NULL);
174 }
175
176 return llvm::Error::success();
177}
178
180 if (m_mode == Mode::Pipe)
181 return m_conpty_input != INVALID_HANDLE_VALUE &&
182 m_conpty_output != INVALID_HANDLE_VALUE;
183 return m_conpty_handle != INVALID_HANDLE_VALUE &&
184 m_conpty_input != INVALID_HANDLE_VALUE &&
185 m_conpty_output != INVALID_HANDLE_VALUE;
186}
187
189 SetStopping(true);
190 std::unique_lock<std::mutex> guard(m_mutex);
191 if (m_conpty_handle != INVALID_HANDLE_VALUE)
192 kernel32.ClosePseudoConsole(m_conpty_handle);
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
219 assert(m_mode == Mode::None &&
220 "Attempted to open a AnonymousPipes in a different mode than None");
221
222 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
223 HANDLE hStdinRead = INVALID_HANDLE_VALUE;
224 HANDLE hStdinWrite = INVALID_HANDLE_VALUE;
225 if (!CreatePipe(&hStdinRead, &hStdinWrite, &sa, 0))
226 return llvm::errorCodeToError(
227 std::error_code(GetLastError(), std::system_category()));
228 // Parent write end must not be inherited by the child.
229 SetHandleInformation(hStdinWrite, HANDLE_FLAG_INHERIT, 0);
230
231 HANDLE hStdoutRead = INVALID_HANDLE_VALUE;
232 HANDLE hStdoutWrite = INVALID_HANDLE_VALUE;
233 if (auto err = CreateOverlappedPipePair(hStdoutRead, hStdoutWrite, true)) {
234 CloseHandle(hStdinRead);
235 CloseHandle(hStdinWrite);
236 return err;
237 }
238
239 m_conpty_input = hStdinWrite;
240 m_conpty_output = hStdoutRead;
241 m_pipe_child_stdin = hStdinRead;
242 m_pipe_child_stdout = hStdoutWrite;
244 return llvm::Error::success();
245}
#define LLDB_LOG_ERROR(log, error,...)
Definition Log.h:394
HRESULT(WINAPI * CreatePseudoConsole_t)(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON *phPC)
VOID(WINAPI * ClosePseudoConsole_t)(HPCON hPC)
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 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_