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 <mutex>
12
17
18#include "llvm/Support/Errc.h"
19#include "llvm/Support/Errno.h"
20
21using namespace lldb_private;
22
23typedef HRESULT(WINAPI *CreatePseudoConsole_t)(COORD size, HANDLE hInput,
24 HANDLE hOutput, DWORD dwFlags,
25 HPCON *phPC);
26
27typedef VOID(WINAPI *ClosePseudoConsole_t)(HPCON hPC);
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 DWORD mode = PIPE_NOWAIT;
92 SetNamedPipeHandleState(out_read, &mode, NULL, NULL);
93 return llvm::Error::success();
94}
95
101
103 assert(m_mode == Mode::None &&
104 "Attempted to open a PseudoConsole in a different mode than None");
105
106 if (!kernel32.IsConPTYAvailable())
107 return llvm::make_error<llvm::StringError>("ConPTY is not available",
108 llvm::errc::io_error);
109
110 assert(m_conpty_handle == INVALID_HANDLE_VALUE &&
111 "ConPTY has already been opened");
112
113 // A 4096 bytes buffer should be large enough for the majority of console
114 // burst outputs.
115 wchar_t pipe_name[MAX_PATH];
116 swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p",
117 GetCurrentProcessId(), this);
118 HANDLE hOutputRead = INVALID_HANDLE_VALUE;
119 HANDLE hOutputWrite = INVALID_HANDLE_VALUE;
120 if (auto err = CreateOverlappedPipePair(hOutputRead, hOutputWrite, false))
121 return err;
122
123 HANDLE hInputRead = INVALID_HANDLE_VALUE;
124 HANDLE hInputWrite = INVALID_HANDLE_VALUE;
125 if (!CreatePipe(&hInputRead, &hInputWrite, NULL, 0)) {
126 CloseHandle(hOutputRead);
127 CloseHandle(hOutputWrite);
128 return llvm::errorCodeToError(
129 std::error_code(GetLastError(), std::system_category()));
130 }
131
132 COORD consoleSize{80, 25};
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 HPCON hPC = INVALID_HANDLE_VALUE;
139 HRESULT hr = kernel32.CreatePseudoConsole(consoleSize, hInputRead,
140 hOutputWrite, 0, &hPC);
141 CloseHandle(hInputRead);
142 CloseHandle(hOutputWrite);
143
144 if (FAILED(hr)) {
145 CloseHandle(hInputWrite);
146 CloseHandle(hOutputRead);
147 return llvm::make_error<llvm::StringError>(
148 "Failed to create Windows ConPTY pseudo terminal",
149 llvm::errc::io_error);
150 }
151
152 m_conpty_handle = hPC;
153 m_conpty_output = hOutputRead;
154 m_conpty_input = hInputWrite;
156
157 if (auto error = DrainInitSequences()) {
158 Log *log = GetLog(LLDBLog::Host);
159 LLDB_LOG_ERROR(log, std::move(error),
160 "failed to finalize ConPTY's setup: {0}");
161 }
162
163 return llvm::Error::success();
164}
165
167 if (m_mode == Mode::Pipe)
168 return m_conpty_input != INVALID_HANDLE_VALUE &&
169 m_conpty_output != INVALID_HANDLE_VALUE;
170 return m_conpty_handle != INVALID_HANDLE_VALUE &&
171 m_conpty_input != INVALID_HANDLE_VALUE &&
172 m_conpty_output != INVALID_HANDLE_VALUE;
173}
174
176 SetStopping(true);
177 std::unique_lock<std::mutex> guard(m_mutex);
178 if (m_conpty_handle != INVALID_HANDLE_VALUE)
179 kernel32.ClosePseudoConsole(m_conpty_handle);
180 m_conpty_handle = INVALID_HANDLE_VALUE;
181 SetStopping(false);
182 m_cv.notify_all();
183}
184
186 if (m_conpty_input != INVALID_HANDLE_VALUE)
187 CloseHandle(m_conpty_input);
188 if (m_conpty_output != INVALID_HANDLE_VALUE)
189 CloseHandle(m_conpty_output);
190
191 m_conpty_input = INVALID_HANDLE_VALUE;
192 m_conpty_output = INVALID_HANDLE_VALUE;
193}
194
196 if (m_pipe_child_stdin != INVALID_HANDLE_VALUE)
197 CloseHandle(m_pipe_child_stdin);
198 if (m_pipe_child_stdout != INVALID_HANDLE_VALUE)
199 CloseHandle(m_pipe_child_stdout);
200
201 m_pipe_child_stdin = INVALID_HANDLE_VALUE;
202 m_pipe_child_stdout = INVALID_HANDLE_VALUE;
203}
204
206 assert(m_mode == Mode::None &&
207 "Attempted to open a AnonymousPipes in a different mode than None");
208
209 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
210 HANDLE hStdinRead = INVALID_HANDLE_VALUE;
211 HANDLE hStdinWrite = INVALID_HANDLE_VALUE;
212 if (!CreatePipe(&hStdinRead, &hStdinWrite, &sa, 0))
213 return llvm::errorCodeToError(
214 std::error_code(GetLastError(), std::system_category()));
215 // Parent write end must not be inherited by the child.
216 SetHandleInformation(hStdinWrite, HANDLE_FLAG_INHERIT, 0);
217
218 HANDLE hStdoutRead = INVALID_HANDLE_VALUE;
219 HANDLE hStdoutWrite = INVALID_HANDLE_VALUE;
220 if (auto err = CreateOverlappedPipePair(hStdoutRead, hStdoutWrite, true)) {
221 CloseHandle(hStdinRead);
222 CloseHandle(hStdinWrite);
223 return err;
224 }
225
226 m_conpty_input = hStdinWrite;
227 m_conpty_output = hStdoutRead;
228 m_pipe_child_stdin = hStdinRead;
229 m_pipe_child_stdout = hStdoutWrite;
231 return llvm::Error::success();
232}
233
235 STARTUPINFOEXW startupinfoex = {};
236 startupinfoex.StartupInfo.cb = sizeof(STARTUPINFOEXW);
237 startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
238
239 auto attributelist_or_err = ProcThreadAttributeList::Create(startupinfoex);
240 if (!attributelist_or_err)
241 return llvm::errorCodeToError(attributelist_or_err.getError());
242 ProcThreadAttributeList attributelist = std::move(*attributelist_or_err);
243 if (auto error = attributelist.SetupPseudoConsole(m_conpty_handle))
244 return error;
245
246 PROCESS_INFORMATION pi = {};
247
248 wchar_t comspec[MAX_PATH];
249 DWORD comspecLen = GetEnvironmentVariableW(L"COMSPEC", comspec, MAX_PATH);
250 if (comspecLen == 0 || comspecLen >= MAX_PATH)
251 return llvm::createStringError(
252 std::error_code(GetLastError(), std::system_category()),
253 "Failed to get the 'COMSPEC' environment variable");
254
255 std::wstring cmdline_str = std::wstring(comspec) + L" /c 'echo foo && exit'";
256 std::vector<wchar_t> cmdline(cmdline_str.begin(), cmdline_str.end());
257 cmdline.push_back(L'\0');
258
259 if (!CreateProcessW(/*lpApplicationName=*/comspec, cmdline.data(),
260 /*lpProcessAttributes=*/NULL, /*lpThreadAttributes=*/NULL,
261 /*bInheritHandles=*/TRUE,
262 /*dwCreationFlags=*/EXTENDED_STARTUPINFO_PRESENT |
263 CREATE_UNICODE_ENVIRONMENT,
264 /*lpEnvironment=*/NULL, /*lpCurrentDirectory=*/NULL,
265 /*lpStartupInfo=*/
266 reinterpret_cast<STARTUPINFOW *>(&startupinfoex),
267 /*lpProcessInformation=*/&pi))
268 return llvm::errorCodeToError(
269 std::error_code(GetLastError(), std::system_category()));
270
271 char buf[4096];
272 OVERLAPPED ov = {};
273 ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
274
275 DWORD read;
276 ReadFile(m_conpty_output, buf, sizeof(buf), &read, &ov);
277
278 WaitForSingleObject(pi.hProcess, INFINITE);
279
280 if (GetOverlappedResult(m_conpty_output, &ov, &read, FALSE) && read > 0) {
281 ResetEvent(ov.hEvent);
282 ReadFile(m_conpty_output, buf, sizeof(buf), &read, &ov);
283 }
284
285 CancelIo(m_conpty_output);
286 CloseHandle(ov.hEvent);
287 CloseHandle(pi.hProcess);
288 CloseHandle(pi.hThread);
289
290 return llvm::Error::success();
291}
static llvm::raw_ostream & error(Stream &strm)
#define LLDB_LOG_ERROR(log, error,...)
Definition Log.h:399
HRESULT(WINAPI * CreatePseudoConsole_t)(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON *phPC)
VOID(WINAPI * ClosePseudoConsole_t)(HPCON hPC)
static Kernel32 kernel32
void * HPCON
void * HANDLE
This class manages the lifetime of a PROC_THREAD_ATTRIBUTE_LIST, which is used with STARTUPINFOEX.
static llvm::ErrorOr< ProcThreadAttributeList > Create(STARTUPINFOEXW &startupinfoex)
Allocate memory for the attribute list, initialize it, and sets the lpAttributeList member of STARTUP...
llvm::Error SetupPseudoConsole(HPCON hPC)
Setup the PseudoConsole handle in the underlying LPPROC_THREAD_ATTRIBUTE_LIST.
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.
llvm::Error DrainInitSequences()
Drains initialization sequences from the ConPTY output pipe.
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:332
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_