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
72
74 if (!kernel32.IsConPTYAvailable())
75 return llvm::make_error<llvm::StringError>("ConPTY is not available",
76 llvm::errc::io_error);
77
78 assert(m_conpty_handle == INVALID_HANDLE_VALUE &&
79 "ConPTY has already been opened");
80
81 HRESULT hr;
82 HANDLE hInputRead = INVALID_HANDLE_VALUE;
83 HANDLE hInputWrite = INVALID_HANDLE_VALUE;
84 HANDLE hOutputRead = INVALID_HANDLE_VALUE;
85 HANDLE hOutputWrite = INVALID_HANDLE_VALUE;
86
87 wchar_t pipe_name[MAX_PATH];
88 swprintf(pipe_name, MAX_PATH, L"\\\\.\\pipe\\conpty-lldb-%d-%p",
89 GetCurrentProcessId(), this);
90
91 // A 4096 bytes buffer should be large enough for the majority of console
92 // burst outputs.
93 hOutputRead =
94 CreateNamedPipeW(pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
95 PIPE_TYPE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, NULL);
96 hOutputWrite = CreateFileW(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
97 FILE_ATTRIBUTE_NORMAL, NULL);
98
99 if (!CreatePipe(&hInputRead, &hInputWrite, NULL, 0))
100 return llvm::errorCodeToError(
101 std::error_code(GetLastError(), std::system_category()));
102
103 COORD consoleSize{80, 25};
104 CONSOLE_SCREEN_BUFFER_INFO csbi;
105 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
106 consoleSize = {
107 static_cast<SHORT>(csbi.srWindow.Right - csbi.srWindow.Left + 1),
108 static_cast<SHORT>(csbi.srWindow.Bottom - csbi.srWindow.Top + 1)};
109 HPCON hPC = INVALID_HANDLE_VALUE;
110 hr = kernel32.CreatePseudoConsole(consoleSize, hInputRead, hOutputWrite, 0,
111 &hPC);
112 CloseHandle(hInputRead);
113 CloseHandle(hOutputWrite);
114
115 if (FAILED(hr)) {
116 CloseHandle(hInputWrite);
117 CloseHandle(hOutputRead);
118 return llvm::make_error<llvm::StringError>(
119 "Failed to create Windows ConPTY pseudo terminal",
120 llvm::errc::io_error);
121 }
122
123 DWORD mode = PIPE_NOWAIT;
124 SetNamedPipeHandleState(hOutputRead, &mode, NULL, NULL);
125
126 m_conpty_handle = hPC;
127 m_conpty_output = hOutputRead;
128 m_conpty_input = hInputWrite;
129
130 if (auto error = DrainInitSequences()) {
131 Log *log = GetLog(LLDBLog::Host);
132 LLDB_LOG_ERROR(log, std::move(error),
133 "failed to finalize ConPTY's setup: {0}");
134 }
135
136 return llvm::Error::success();
137}
138
140 return m_conpty_handle != INVALID_HANDLE_VALUE &&
141 m_conpty_input != INVALID_HANDLE_VALUE &&
142 m_conpty_output != INVALID_HANDLE_VALUE;
143}
144
146 SetStopping(true);
147 std::unique_lock<std::mutex> guard(m_mutex);
148 if (m_conpty_handle != INVALID_HANDLE_VALUE)
149 kernel32.ClosePseudoConsole(m_conpty_handle);
150 m_conpty_handle = INVALID_HANDLE_VALUE;
151 SetStopping(false);
152 m_cv.notify_all();
153}
154
156 if (m_conpty_input != INVALID_HANDLE_VALUE)
157 CloseHandle(m_conpty_input);
158 if (m_conpty_output != INVALID_HANDLE_VALUE)
159 CloseHandle(m_conpty_output);
160
161 m_conpty_input = INVALID_HANDLE_VALUE;
162 m_conpty_output = INVALID_HANDLE_VALUE;
163}
164
166 STARTUPINFOEXW startupinfoex = {};
167 startupinfoex.StartupInfo.cb = sizeof(STARTUPINFOEXW);
168 startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
169
170 auto attributelist_or_err = ProcThreadAttributeList::Create(startupinfoex);
171 if (!attributelist_or_err)
172 return llvm::errorCodeToError(attributelist_or_err.getError());
173 ProcThreadAttributeList attributelist = std::move(*attributelist_or_err);
174 if (auto error = attributelist.SetupPseudoConsole(m_conpty_handle))
175 return error;
176
177 PROCESS_INFORMATION pi = {};
178
179 wchar_t comspec[MAX_PATH];
180 DWORD comspecLen = GetEnvironmentVariableW(L"COMSPEC", comspec, MAX_PATH);
181 if (comspecLen == 0 || comspecLen >= MAX_PATH)
182 return llvm::createStringError(
183 std::error_code(GetLastError(), std::system_category()),
184 "Failed to get the 'COMSPEC' environment variable");
185
186 std::wstring cmdline_str = std::wstring(comspec) + L" /c 'echo foo && exit'";
187 std::vector<wchar_t> cmdline(cmdline_str.begin(), cmdline_str.end());
188 cmdline.push_back(L'\0');
189
190 if (!CreateProcessW(/*lpApplicationName=*/comspec, cmdline.data(),
191 /*lpProcessAttributes=*/NULL, /*lpThreadAttributes=*/NULL,
192 /*bInheritHandles=*/TRUE,
193 /*dwCreationFlags=*/EXTENDED_STARTUPINFO_PRESENT |
194 CREATE_UNICODE_ENVIRONMENT,
195 /*lpEnvironment=*/NULL, /*lpCurrentDirectory=*/NULL,
196 /*lpStartupInfo=*/
197 reinterpret_cast<STARTUPINFOW *>(&startupinfoex),
198 /*lpProcessInformation=*/&pi))
199 return llvm::errorCodeToError(
200 std::error_code(GetLastError(), std::system_category()));
201
202 char buf[4096];
203 OVERLAPPED ov = {};
204 ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
205
206 DWORD read;
207 ReadFile(m_conpty_output, buf, sizeof(buf), &read, &ov);
208
209 WaitForSingleObject(pi.hProcess, INFINITE);
210
211 if (GetOverlappedResult(m_conpty_output, &ov, &read, FALSE) && read > 0) {
212 ResetEvent(ov.hEvent);
213 ReadFile(m_conpty_output, buf, sizeof(buf), &read, &ov);
214 }
215
216 CancelIo(m_conpty_output);
217 CloseHandle(ov.hEvent);
218 CloseHandle(pi.hProcess);
219 CloseHandle(pi.hThread);
220
221 return llvm::Error::success();
222}
static llvm::raw_ostream & error(Stream &strm)
#define LLDB_LOG_ERROR(log, error,...)
Definition Log.h:392
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.
void ClosePipes()
Closes the STDIN and STDOUT pipe handles and invalidates them.
std::condition_variable m_cv
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.
bool IsConnected() const
Returns whether the ConPTY and its pipes are currently open and valid.
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_