//===-- ProcessLauncherWindows.cpp ----------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "lldb/Host/windows/ProcessLauncherWindows.h" #include "lldb/Host/HostProcess.h" #include "lldb/Host/ProcessLaunchInfo.h" #include "lldb/Host/windows/PseudoConsole.h" #include "lldb/Host/windows/windows.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Program.h" #include "llvm/Support/WindowsError.h" #include #include using namespace lldb; using namespace lldb_private; /// Create a UTF-16 environment block to use with CreateProcessW. /// /// The buffer is a sequence of null-terminated UTF-16 strings, followed by an /// extra L'\0' (two bytes of 0). An empty environment must have one /// empty string, followed by an extra L'\0'. /// /// The keys are sorted to comply with the CreateProcess API calling convention. /// /// Ensure that the resulting buffer is used in conjunction with /// CreateProcessW and be sure that dwCreationFlags includes /// CREATE_UNICODE_ENVIRONMENT. /// /// \param env The Environment object to convert. /// \returns The sorted sequence of environment variables and their values, /// separated by null terminators. The vector is guaranteed to never be empty. static std::vector CreateEnvironmentBufferW(const Environment &env) { std::vector env_entries; for (const auto &KV : env) { std::wstring wentry; if (llvm::ConvertUTF8toWide(Environment::compose(KV), wentry)) env_entries.push_back(std::move(wentry)); } std::sort(env_entries.begin(), env_entries.end(), [](const std::wstring &a, const std::wstring &b) { return _wcsicmp(a.c_str(), b.c_str()) < 0; }); std::vector buffer; for (const auto &env_entry : env_entries) { buffer.insert(buffer.end(), env_entry.begin(), env_entry.end()); buffer.push_back(L'\0'); } if (buffer.empty()) buffer.push_back(L'\0'); // If there are no environment variables, we have // to ensure there are 4 zero bytes in the buffer. buffer.push_back(L'\0'); return buffer; } /// Flattens an Args object into a Windows command-line wide string. /// /// Returns an empty string if args is empty. /// /// \param args The Args object to flatten. /// \returns A wide string containing the flattened command line. static llvm::ErrorOr GetFlattenedWindowsCommandStringW(Args args) { if (args.empty()) return L""; std::vector args_ref; for (auto &entry : args.entries()) args_ref.push_back(entry.ref()); return llvm::sys::flattenWindowsCommandLine(args_ref); } llvm::ErrorOr ProcThreadAttributeList::Create(STARTUPINFOEXW &startupinfoex) { SIZE_T attributelist_size = 0; InitializeProcThreadAttributeList(/*lpAttributeList=*/nullptr, /*dwAttributeCount=*/1, /*dwFlags=*/0, &attributelist_size); startupinfoex.lpAttributeList = static_cast(malloc(attributelist_size)); if (!startupinfoex.lpAttributeList) return llvm::mapWindowsError(ERROR_OUTOFMEMORY); if (!InitializeProcThreadAttributeList(startupinfoex.lpAttributeList, /*dwAttributeCount=*/1, /*dwFlags=*/0, &attributelist_size)) { free(startupinfoex.lpAttributeList); return llvm::mapWindowsError(GetLastError()); } return ProcThreadAttributeList(startupinfoex.lpAttributeList); } llvm::Error ProcThreadAttributeList::SetupPseudoConsole(HPCON hPC) { BOOL ok = UpdateProcThreadAttribute(lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, hPC, sizeof(hPC), NULL, NULL); if (!ok) return llvm::errorCodeToError(llvm::mapWindowsError(GetLastError())); return llvm::Error::success(); } HostProcess ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, Status &error) { error.Clear(); STARTUPINFOEXW startupinfoex = {}; startupinfoex.StartupInfo.cb = sizeof(STARTUPINFOEXW); startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; HPCON hPC = launch_info.GetPTY().GetPseudoTerminalHandle(); bool use_pty = launch_info.ShouldUsePTY(); HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO); HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO); HANDLE stderr_handle = GetStdioHandle(launch_info, STDERR_FILENO); llvm::scope_exit close_handles([&] { if (stdin_handle) ::CloseHandle(stdin_handle); if (stdout_handle) ::CloseHandle(stdout_handle); if (stderr_handle) ::CloseHandle(stderr_handle); }); auto attributelist_or_err = ProcThreadAttributeList::Create(startupinfoex); if (!attributelist_or_err) { error = attributelist_or_err.getError(); return HostProcess(); } ProcThreadAttributeList attributelist = std::move(*attributelist_or_err); std::vector inherited_handles; if (use_pty) { if (auto err = attributelist.SetupPseudoConsole(hPC)) { error = Status::FromError(std::move(err)); return HostProcess(); } } else { auto inherited_handles_or_err = GetInheritedHandles( launch_info, startupinfoex, stdout_handle, stderr_handle, stdin_handle); if (!inherited_handles_or_err) { error = Status(inherited_handles_or_err.getError()); return HostProcess(); } inherited_handles = std::move(*inherited_handles_or_err); } const char *hide_console_var = getenv("LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE"); if (hide_console_var && llvm::StringRef(hide_console_var).equals_insensitive("true")) { startupinfoex.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW; startupinfoex.StartupInfo.wShowWindow = SW_HIDE; } DWORD flags = CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT; if (launch_info.GetFlags().Test(eLaunchFlagDebug)) flags |= DEBUG_ONLY_THIS_PROCESS; if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO) || use_pty) flags &= ~CREATE_NEW_CONSOLE; std::vector environment = CreateEnvironmentBufferW(launch_info.GetEnvironment()); auto wcommandLineOrErr = GetFlattenedWindowsCommandStringW(launch_info.GetArguments()); if (!wcommandLineOrErr) { error = Status(wcommandLineOrErr.getError()); return HostProcess(); } std::wstring wcommandLine = *wcommandLineOrErr; // If the command line is empty, it's best to pass a null pointer to tell // CreateProcessW to use the executable name as the command line. If the // command line is not empty, its contents may be modified by CreateProcessW. WCHAR *pwcommandLine = wcommandLine.empty() ? nullptr : &wcommandLine[0]; std::wstring wexecutable, wworkingDirectory; llvm::ConvertUTF8toWide(launch_info.GetExecutableFile().GetPath(), wexecutable); llvm::ConvertUTF8toWide(launch_info.GetWorkingDirectory().GetPath(), wworkingDirectory); PROCESS_INFORMATION pi = {}; BOOL result = ::CreateProcessW( wexecutable.c_str(), pwcommandLine, NULL, NULL, /*bInheritHandles=*/!inherited_handles.empty(), flags, environment.data(), wworkingDirectory.size() == 0 ? NULL : wworkingDirectory.c_str(), reinterpret_cast(&startupinfoex), &pi); if (!result) { // Call GetLastError before we make any other system calls. error = Status(::GetLastError(), eErrorTypeWin32); // Note that error 50 ("The request is not supported") will occur if you // try debug a 64-bit inferior from a 32-bit LLDB. } if (result) { // Do not call CloseHandle on pi.hProcess, since we want to pass that back // through the HostProcess. ::CloseHandle(pi.hThread); } if (!result) return HostProcess(); return HostProcess(pi.hProcess); } llvm::ErrorOr> ProcessLauncherWindows::GetInheritedHandles( const ProcessLaunchInfo &launch_info, STARTUPINFOEXW &startupinfoex, HANDLE stdout_handle, HANDLE stderr_handle, HANDLE stdin_handle) { std::vector inherited_handles; startupinfoex.StartupInfo.hStdError = stderr_handle ? stderr_handle : GetStdHandle(STD_ERROR_HANDLE); startupinfoex.StartupInfo.hStdInput = stdin_handle ? stdin_handle : GetStdHandle(STD_INPUT_HANDLE); startupinfoex.StartupInfo.hStdOutput = stdout_handle ? stdout_handle : GetStdHandle(STD_OUTPUT_HANDLE); if (startupinfoex.StartupInfo.hStdError) inherited_handles.push_back(startupinfoex.StartupInfo.hStdError); if (startupinfoex.StartupInfo.hStdInput) inherited_handles.push_back(startupinfoex.StartupInfo.hStdInput); if (startupinfoex.StartupInfo.hStdOutput) inherited_handles.push_back(startupinfoex.StartupInfo.hStdOutput); for (size_t i = 0; i < launch_info.GetNumFileActions(); ++i) { const FileAction *act = launch_info.GetFileActionAtIndex(i); if (act->GetAction() == FileAction::eFileActionDuplicate && act->GetFD() == act->GetActionArgument()) inherited_handles.push_back(reinterpret_cast(act->GetFD())); } if (inherited_handles.empty()) return inherited_handles; if (!UpdateProcThreadAttribute( startupinfoex.lpAttributeList, /*dwFlags=*/0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherited_handles.data(), inherited_handles.size() * sizeof(HANDLE), /*lpPreviousValue=*/nullptr, /*lpReturnSize=*/nullptr)) return llvm::mapWindowsError(::GetLastError()); return inherited_handles; } HANDLE ProcessLauncherWindows::GetStdioHandle(const ProcessLaunchInfo &launch_info, int fd) { const FileAction *action = launch_info.GetFileActionForFD(fd); if (action == nullptr) return NULL; SECURITY_ATTRIBUTES secattr = {}; secattr.nLength = sizeof(SECURITY_ATTRIBUTES); secattr.bInheritHandle = TRUE; DWORD access = 0; DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; DWORD create = 0; DWORD flags = 0; if (fd == STDIN_FILENO) { access = GENERIC_READ; create = OPEN_EXISTING; flags = FILE_ATTRIBUTE_READONLY; } if (fd == STDOUT_FILENO || fd == STDERR_FILENO) { access = GENERIC_WRITE; create = CREATE_ALWAYS; if (fd == STDERR_FILENO) flags = FILE_FLAG_WRITE_THROUGH; } const std::string path = action->GetFileSpec().GetPath(); std::wstring wpath; llvm::ConvertUTF8toWide(path, wpath); HANDLE result = ::CreateFileW(wpath.c_str(), access, share, &secattr, create, flags, NULL); return (result == INVALID_HANDLE_VALUE) ? NULL : result; }