| //===-- 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/windows/PseudoConsole.h" | |
| #include "lldb/Host/windows/WindowsFileAction.h" | |
| #include "lldb/Host/windows/windows.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/Support/ConvertUTF.h" | |
| #include "llvm/Support/Program.h" | |
| #include "llvm/Support/Windows/WindowsSupport.h" | |
| #include "llvm/Support/WindowsError.h" | |
| #include <string> | |
| #include <vector> | |
| 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<wchar_t> CreateEnvironmentBufferW(const Environment &env) { | |
| std::vector<std::wstring> 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<wchar_t> 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; | |
| } | |
| namespace lldb_private { | |
| llvm::ErrorOr<std::wstring> | |
| GetFlattenedWindowsCommandStringW(const Args &args) { | |
| if (args.empty()) | |
| return L""; | |
| std::vector<llvm::StringRef> args_ref; | |
| for (auto &entry : args.entries()) | |
| args_ref.push_back(entry.ref()); | |
| return llvm::sys::flattenWindowsCommandLine(args_ref); | |
| } | |
| llvm::ErrorOr<std::wstring> | |
| GetFlattenedWindowsCommandStringW(llvm::ArrayRef<const char *> args) { | |
| if (args.empty()) | |
| return L""; | |
| std::vector<llvm::StringRef> args_ref(args.begin(), args.end()); | |
| return llvm::sys::flattenWindowsCommandLine(args_ref); | |
| } | |
| } // namespace lldb_private | |
| llvm::ErrorOr<ProcThreadAttributeList> | |
| ProcThreadAttributeList::Create(STARTUPINFOEXW &startupinfoex) { | |
| SIZE_T attributelist_size = 0; | |
| InitializeProcThreadAttributeList(/*lpAttributeList=*/nullptr, | |
| /*dwAttributeCount=*/1, /*dwFlags=*/0, | |
| &attributelist_size); | |
| startupinfoex.lpAttributeList = | |
| static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(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), nullptr, nullptr); | |
| 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; | |
| PseudoConsole::Mode pty_mode = launch_info.ShouldUsePTY() | |
| ? launch_info.GetPTY().GetMode() | |
| : PseudoConsole::Mode::None; | |
| 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<HANDLE> inherited_handles; | |
| switch (pty_mode) { | |
| case PseudoConsole::Mode::ConPTY: { | |
| HPCON hPC = launch_info.GetPTY().GetPseudoTerminalHandle(); | |
| if (auto err = attributelist.SetupPseudoConsole(hPC)) { | |
| error = Status::FromError(std::move(err)); | |
| return HostProcess(); | |
| } | |
| break; | |
| } | |
| case PseudoConsole::Mode::Pipe: { | |
| PseudoConsole &pty = launch_info.GetPTY(); | |
| startupinfoex.StartupInfo.hStdInput = pty.GetChildStdinHandle(); | |
| startupinfoex.StartupInfo.hStdOutput = pty.GetChildStdoutHandle(); | |
| startupinfoex.StartupInfo.hStdError = pty.GetChildStdoutHandle(); | |
| inherited_handles = {pty.GetChildStdinHandle(), pty.GetChildStdoutHandle()}; | |
| if (!UpdateProcThreadAttribute( | |
| startupinfoex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, | |
| inherited_handles.data(), inherited_handles.size() * sizeof(HANDLE), | |
| nullptr, nullptr)) { | |
| error = Status(::GetLastError(), eErrorTypeWin32); | |
| return HostProcess(); | |
| } | |
| break; | |
| } | |
| case PseudoConsole::Mode::None: { | |
| auto inherited_handles_or_err = | |
| GetInheritedHandles(startupinfoex, &launch_info, 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); | |
| break; | |
| } | |
| } | |
| 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_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT; | |
| const bool stdio_redirected = launch_info.IsFDRedirected(STDIN_FILENO) && | |
| launch_info.IsFDRedirected(STDOUT_FILENO) && | |
| launch_info.IsFDRedirected(STDERR_FILENO); | |
| if (stdio_redirected) | |
| flags |= CREATE_NO_WINDOW; | |
| else if (!launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO) && | |
| pty_mode == PseudoConsole::Mode::None) | |
| flags |= CREATE_NEW_CONSOLE; | |
| if (launch_info.GetFlags().Test(eLaunchFlagDebug)) | |
| flags |= DEBUG_ONLY_THIS_PROCESS; | |
| std::vector<wchar_t> 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]; | |
| llvm::SmallVector<wchar_t, MAX_PATH> wexecutable; | |
| if (std::error_code ec = llvm::sys::windows::widenPath( | |
| launch_info.GetExecutableFile().GetPath(), wexecutable)) { | |
| error = Status(ec); | |
| return HostProcess(); | |
| } | |
| std::wstring wworkingDirectory; | |
| llvm::ConvertUTF8toWide(launch_info.GetWorkingDirectory().GetPath(), | |
| wworkingDirectory); | |
| PROCESS_INFORMATION pi = {}; | |
| BOOL result = ::CreateProcessW( | |
| wexecutable.data(), pwcommandLine, nullptr, nullptr, | |
| /*bInheritHandles=*/!inherited_handles.empty() || | |
| pty_mode != PseudoConsole::Mode::None, | |
| flags, environment.data(), | |
| wworkingDirectory.size() == 0 ? nullptr : wworkingDirectory.c_str(), | |
| reinterpret_cast<STARTUPINFOW *>(&startupinfoex), &pi); | |
| if (!result) { | |
| // Call GetLastError before we make any other system calls. | |
| // Note that error 50 ("The request is not supported") will occur if you | |
| // try debug a 64-bit inferior from a 32-bit LLDB. | |
| error = Status(::GetLastError(), eErrorTypeWin32); | |
| return HostProcess(); | |
| } | |
| // Do not call CloseHandle on pi.hProcess, since we want to pass that back | |
| // through the HostProcess. | |
| ::CloseHandle(pi.hThread); | |
| if (pty_mode == PseudoConsole::Mode::Pipe) | |
| launch_info.GetPTY().CloseAnonymousPipes(); | |
| return HostProcess(pi.hProcess); | |
| } | |
| llvm::ErrorOr<std::vector<HANDLE>> ProcessLauncherWindows::GetInheritedHandles( | |
| STARTUPINFOEXW &startupinfoex, const ProcessLaunchInfo *launch_info, | |
| HANDLE stdout_handle, HANDLE stderr_handle, HANDLE stdin_handle) { | |
| std::vector<HANDLE> inherited_handles; | |
| startupinfoex.StartupInfo.hStdInput = | |
| stdin_handle ? stdin_handle : GetStdHandle(STD_INPUT_HANDLE); | |
| startupinfoex.StartupInfo.hStdOutput = | |
| stdout_handle ? stdout_handle : GetStdHandle(STD_OUTPUT_HANDLE); | |
| // eFileActionDuplicate stores the source fd in m_fd and the destination in | |
| // m_arg. GetFileActionForFD searches by m_fd (source), so a | |
| // AppendDuplicateFileAction(STDOUT, STDERR) won't be found when looking up | |
| // STDERR. Scan for duplicate actions that target stderr explicitly. | |
| HANDLE effective_stderr = stderr_handle; | |
| if (!effective_stderr && launch_info) { | |
| for (size_t i = 0; i < launch_info->GetNumFileActions(); ++i) { | |
| const FileAction *act = launch_info->GetFileActionAtIndex(i); | |
| if (act->GetAction() == FileAction::eFileActionDuplicate && | |
| act->GetActionArgument() == STDERR_FILENO) { | |
| effective_stderr = startupinfoex.StartupInfo.hStdOutput; | |
| break; | |
| } | |
| } | |
| } | |
| startupinfoex.StartupInfo.hStdError = | |
| effective_stderr ? effective_stderr : GetStdHandle(STD_ERROR_HANDLE); | |
| // PROC_THREAD_ATTRIBUTE_HANDLE_LIST requires unique entries. | |
| auto push_if_new = [&](HANDLE h) { | |
| if (h && std::find(inherited_handles.begin(), inherited_handles.end(), h) == | |
| inherited_handles.end()) | |
| inherited_handles.push_back(h); | |
| }; | |
| push_if_new(startupinfoex.StartupInfo.hStdError); | |
| push_if_new(startupinfoex.StartupInfo.hStdInput); | |
| push_if_new(startupinfoex.StartupInfo.hStdOutput); | |
| if (launch_info) { | |
| for (size_t i = 0; i < launch_info->GetNumFileActions(); ++i) { | |
| const WindowsFileAction *act = static_cast<const WindowsFileAction *>( | |
| launch_info->GetFileActionAtIndex(i)); | |
| if (std::find(inherited_handles.begin(), inherited_handles.end(), | |
| act->GetHandle()) != inherited_handles.end()) | |
| continue; | |
| if (act->GetAction() != FileAction::eFileActionDuplicate) | |
| continue; | |
| if (act->GetActionArgument() != -1 && | |
| act->GetFD() == act->GetActionArgument()) | |
| inherited_handles.push_back(act->GetHandle()); | |
| else if (act->GetActionArgumentHandle() != INVALID_HANDLE_VALUE && | |
| act->GetHandle() == act->GetActionArgumentHandle()) | |
| inherited_handles.push_back(act->GetHandle()); | |
| } | |
| } | |
| 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 = nullptr; | |
| for (size_t i = 0; i < launch_info.GetNumFileActions(); ++i) { | |
| const FileAction *candidate = launch_info.GetFileActionAtIndex(i); | |
| if (candidate->GetFD() == fd && | |
| candidate->GetAction() != FileAction::eFileActionDuplicate) | |
| action = candidate; | |
| } | |
| if (action == nullptr) | |
| return nullptr; | |
| const std::string path = action->GetFileSpec().GetPath(); | |
| return GetStdioHandle(path, fd); | |
| } | |
| HANDLE ProcessLauncherWindows::GetStdioHandle(const llvm::StringRef path, | |
| int fd) { | |
| if (path.empty()) | |
| return nullptr; | |
| 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; | |
| switch (fd) { | |
| case STDIN_FILENO: | |
| access = GENERIC_READ; | |
| create = OPEN_EXISTING; | |
| flags = FILE_ATTRIBUTE_READONLY; | |
| break; | |
| case STDERR_FILENO: | |
| flags = FILE_FLAG_WRITE_THROUGH; | |
| [[fallthrough]]; | |
| case STDOUT_FILENO: | |
| access = GENERIC_WRITE; | |
| create = CREATE_ALWAYS; | |
| break; | |
| default: | |
| break; | |
| } | |
| std::wstring wpath; | |
| llvm::ConvertUTF8toWide(path, wpath); | |
| HANDLE result = ::CreateFileW(wpath.c_str(), access, share, &secattr, create, | |
| flags, nullptr); | |
| return (result == INVALID_HANDLE_VALUE) ? nullptr : result; | |
| } |