diff --git a/src/tools/ConsoleBench/conhost.cpp b/src/tools/ConsoleBench/conhost.cpp index 4db5352f99..d72fbd0e1a 100644 --- a/src/tools/ConsoleBench/conhost.cpp +++ b/src/tools/ConsoleBench/conhost.cpp @@ -1,17 +1,16 @@ #include "pch.h" #include "conhost.h" -#include -#include -#include - #include "arena.h" -static unique_nthandle conhostCreateHandle(HANDLE parent, const wchar_t* typeName, bool inherit, bool synchronous) +template +static constexpr UNICODE_STRING constantUnicodeString(const wchar_t (&s)[N]) { - UNICODE_STRING name; - RtlInitUnicodeString(&name, typeName); + return { N * 2 - 2, N * 2, const_cast(&s[0]) }; +} +static unique_nthandle conhostCreateHandle(HANDLE parent, UNICODE_STRING name, bool inherit, bool synchronous) +{ ULONG attrFlags = OBJ_CASE_INSENSITIVE; WI_SetFlagIf(attrFlags, OBJ_INHERIT, inherit); @@ -51,8 +50,8 @@ ConhostHandle spawn_conhost(mem::Arena& arena, const wchar_t* path) const auto isDLL = pathLen > 4 && wcscmp(&path[pathLen - 4], L".dll") == 0; const auto scratch = mem::get_scratch_arena(arena); - auto server = conhostCreateHandle(nullptr, L"\\Device\\ConDrv\\Server", true, false); - auto reference = conhostCreateHandle(server.get(), L"\\Reference", false, true); + auto server = conhostCreateHandle(nullptr, constantUnicodeString(L"\\Device\\ConDrv\\Server"), true, false); + auto reference = conhostCreateHandle(server.get(), constantUnicodeString(L"\\Reference"), false, true); { const auto selfPath = scratch.arena.push_uninitialized(64 * 1024); @@ -109,8 +108,7 @@ ConhostHandle spawn_conhost(mem::Arena& arena, const wchar_t* path) unique_nthandle connection; { - UNICODE_STRING name; - RtlInitUnicodeString(&name, L"\\Connect"); + UNICODE_STRING name = constantUnicodeString(L"\\Connect"); OBJECT_ATTRIBUTES attr; InitializeObjectAttributes(&attr, &name, OBJ_CASE_INSENSITIVE, reference.get(), nullptr); diff --git a/src/tools/ConsoleBench/main.cpp b/src/tools/ConsoleBench/main.cpp index 31b43e6b93..efb63e7982 100644 --- a/src/tools/ConsoleBench/main.cpp +++ b/src/tools/ConsoleBench/main.cpp @@ -478,7 +478,7 @@ try { if (argc < 2) { - mem::print_literal("Usage: %s [paths to conhost.exe]..."); + mem::print_literal("Usage: ConsoleBench [paths to conhost.exe]..."); return 1; } diff --git a/src/tools/ConsoleBench/pch.h b/src/tools/ConsoleBench/pch.h index ce01975430..f52537c3ae 100644 --- a/src/tools/ConsoleBench/pch.h +++ b/src/tools/ConsoleBench/pch.h @@ -7,8 +7,11 @@ #define WIN32_LEAN_AND_MEAN #include +#include +#include + #include -#include +#include #include #include diff --git a/src/winconpty/device.h b/src/winconpty/device.h deleted file mode 100644 index 61387ab7b3..0000000000 --- a/src/winconpty/device.h +++ /dev/null @@ -1,27 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- device.h - -Abstract: -- This header exists to reduce the differences in winconpty - from the in-box windows source. -- Relies on components from Server to reach into ntdll for NtOpenFile - to get at the NT namespace, which is required to open the console device. ---*/ - -#pragma once - -#include "../server/DeviceHandle.h" - -[[nodiscard]] static inline NTSTATUS CreateClientHandle(PHANDLE Handle, HANDLE ServerHandle, PCWSTR Name, BOOLEAN Inheritable) -{ - return DeviceHandle::CreateClientHandle(Handle, ServerHandle, Name, Inheritable); -} - -[[nodiscard]] static inline NTSTATUS CreateServerHandle(PHANDLE Handle, BOOLEAN Inheritable) -{ - return DeviceHandle::CreateServerHandle(Handle, Inheritable); -} diff --git a/src/winconpty/lib/winconptylib.vcxproj b/src/winconpty/lib/winconptylib.vcxproj index 8182dbfd6a..3642cd7543 100644 --- a/src/winconpty/lib/winconptylib.vcxproj +++ b/src/winconpty/lib/winconptylib.vcxproj @@ -15,8 +15,10 @@ Create - - + + + + @@ -59,4 +61,4 @@ - + \ No newline at end of file diff --git a/src/winconpty/precomp.h b/src/winconpty/precomp.h index c78377cc55..4a670691e6 100644 --- a/src/winconpty/precomp.h +++ b/src/winconpty/precomp.h @@ -1,41 +1,12 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- precomp.h - -Abstract: -- Contains external headers to include in the precompile phase of console build process. -- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building). ---*/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. #pragma once -// Ignore checked iterators warning from VC compiler. -#define _SCL_SECURE_NO_WARNINGS - -// Block minwindef.h min/max macros to prevent conflict +#define WIN32_LEAN_AND_MEAN #define NOMINMAX - -// Define and then undefine WIN32_NO_STATUS because windows.h has no guard to prevent it from double defing certain statuses -// when included with ntstatus.h -#define WIN32_NO_STATUS -#include -#undef WIN32_NO_STATUS +#include #include -#pragma warning(push) -#pragma warning(disable : 4430) // Must disable 4430 "default int" warning for C++ because ntstatus.h is inflexible SDK definition. -#include -#pragma warning(pop) - -#include - -#include "../host/conddkrefs.h" - -// This includes support libraries from the CRT, STL, WIL, and GSL -#include "LibraryIncludes.h" - -#include +#include diff --git a/src/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp index 60c6c442f2..497bc7a8bd 100644 --- a/src/winconpty/winconpty.cpp +++ b/src/winconpty/winconpty.cpp @@ -6,29 +6,123 @@ #include "winconpty.h" #ifdef __INSIDE_WINDOWS -#include -#include // You need kernelbasestaging.h to be able to use wil in libraries consumed by kernelbase.dll #include #define RESOURCE_SUPPRESS_STL #define WIL_SUPPORT_BITOPERATION_PASCAL_NAMES #include -#else -#include "device.h" -#include #endif // __INSIDE_WINDOWS #pragma warning(push) #pragma warning(disable : 4273) // inconsistent dll linkage (we are exporting things kernel32 also exports) -#pragma warning(disable : 26485) // array-to-pointer decay is virtually impossible to avoid when we can't use STL. + +using unique_nthandle = wil::unique_any_handle_null; + +static wil::unique_process_heap_string allocString(size_t count) +{ + const auto addr = static_cast(HeapAlloc(GetProcessHeap(), 0, count * sizeof(wchar_t))); + return wil::unique_process_heap_string{ addr }; +} // Function Description: // - Returns the path to conhost.exe as a process heap string. -static wil::unique_process_heap_string _InboxConsoleHostPath() +static wil::unique_process_heap_string getInboxConhostPath() { - wil::unique_process_heap_string systemDirectory; - wil::GetSystemDirectoryW(systemDirectory); - return wil::str_concat_failfast(L"\\\\?\\", systemDirectory, L"\\conhost.exe"); + auto path = allocString(MAX_PATH); + if (!path) + { + return {}; + } + + const auto cap = MAX_PATH - 12; + const auto len = GetSystemDirectoryW(path.get(), cap); + if (len >= cap) + { + return {}; + } + + memcpy(path.get() + len, L"\\conhost.exe", sizeof(L"\\conhost.exe")); + return path; +} + +static bool pathExists(const wchar_t* path) +{ + const auto attr = GetFileAttributesW(path); + return attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY); +} + +[[maybe_unused]] static wil::unique_process_heap_string getOssConhostPath() +{ + static constexpr DWORD longestSuffixLength = 22; // "arm64\OpenConsole.exe" + + const auto module = wil::GetModuleInstanceHandle(); + wil::unique_process_heap_string path; + DWORD basePathLength = MAX_PATH; + + for (;;) + { + path = allocString(basePathLength + longestSuffixLength); + if (!path) + { + return {}; + } + + const auto cap = basePathLength; + basePathLength = GetModuleFileNameW(module, path.get(), cap); + + // If the function fails, the return value is 0 (zero). + if (basePathLength == 0) + { + return {}; + } + + // If the function succeeds, the return value is the length of the string that is + // copied to the buffer, in characters, not including the terminating null character. + if (basePathLength < cap) + { + break; + } + + basePathLength *= 2; + } + + const auto basePathBeg = path.get(); + auto basePathEnd = basePathBeg + basePathLength; + for (; basePathEnd != basePathBeg && basePathEnd[-1] != L'\\'; --basePathEnd) + { + } + + memcpy(basePathEnd, L"OpenConsole.exe", sizeof(L"OpenConsole.exe")); + if (pathExists(path.get())) + { + return path; + } + + USHORT unusedImageFileMachine, nativeMachine; + if (IsWow64Process2(GetCurrentProcess(), &unusedImageFileMachine, &nativeMachine)) + { + // Despite being a machine type, the values IsWow64Process2 returns are *image* types + switch (nativeMachine) + { + case IMAGE_FILE_MACHINE_AMD64: + memcpy(basePathEnd, L"x64\\OpenConsole.exe", sizeof(L"x64\\OpenConsole.exe")); + break; + case IMAGE_FILE_MACHINE_ARM64: + memcpy(basePathEnd, L"arm64\\OpenConsole.exe", sizeof(L"arm64\\OpenConsole.exe")); + break; + case IMAGE_FILE_MACHINE_I386: + memcpy(basePathEnd, L"x86\\OpenConsole.exe", sizeof(L"x86\\OpenConsole.exe")); + break; + default: + break; + } + if (pathExists(path.get())) + { + return path; + } + } + + return {}; } // Function Description: @@ -36,54 +130,17 @@ static wil::unique_process_heap_string _InboxConsoleHostPath() // module is building with Windows and OpenConsole could be found. // Return Value: // - A pointer to permanent storage containing the path to the console host. -static wchar_t* _ConsoleHostPath() +static wchar_t* getConhostPath() { // Use the magic of magic statics to only calculate this once. - static auto consoleHostPath = []() { -#if defined(__INSIDE_WINDOWS) - return _InboxConsoleHostPath(); -#else - // Use the STL only if we're not building in Windows. - std::filesystem::path modulePath{ wil::GetModuleFileNameW(wil::GetModuleInstanceHandle()) }; - modulePath.replace_filename(L"OpenConsole.exe"); - if (!std::filesystem::exists(modulePath)) + static const auto consoleHostPath = []() { +#if !defined(__INSIDE_WINDOWS) + if (auto path = getOssConhostPath()) { - std::wstring_view architectureInfix{}; - USHORT unusedImageFileMachine{}, nativeMachine{}; - if (IsWow64Process2(GetCurrentProcess(), &unusedImageFileMachine, &nativeMachine)) - { - // Despite being a machine type, the values IsWow64Process2 returns are *image* types - switch (nativeMachine) - { - case IMAGE_FILE_MACHINE_AMD64: - architectureInfix = L"x64"; - break; - case IMAGE_FILE_MACHINE_ARM64: - architectureInfix = L"arm64"; - break; - case IMAGE_FILE_MACHINE_I386: - architectureInfix = L"x86"; - break; - default: - break; - } - } - if (architectureInfix.empty()) - { - // WHAT? - return _InboxConsoleHostPath(); - } - modulePath.replace_filename(architectureInfix); - modulePath.append(L"OpenConsole.exe"); + return path; } - if (!std::filesystem::exists(modulePath)) - { - // We tried the architecture infix version and failed, fall back to conhost. - return _InboxConsoleHostPath(); - } - const auto& modulePathAsString = modulePath.native(); - return wil::make_process_heap_string_nothrow(modulePathAsString.data(), modulePathAsString.size()); #endif // __INSIDE_WINDOWS + return getInboxConhostPath(); }(); return consoleHostPath.get(); } @@ -93,7 +150,39 @@ static bool _HandleIsValid(HANDLE h) noexcept return (h != INVALID_HANDLE_VALUE) && (h != nullptr); } -HRESULT _CreatePseudoConsole(const HANDLE hToken, +template +static constexpr UNICODE_STRING constantUnicodeString(const wchar_t (&s)[N]) +{ + return { N * 2 - 2, N * 2, const_cast(&s[0]) }; +} + +static NTSTATUS conhostCreateHandle(HANDLE* handle, HANDLE parent, UNICODE_STRING name, bool inherit, bool synchronous) +{ + ULONG attrFlags = OBJ_CASE_INSENSITIVE; + WI_SetFlagIf(attrFlags, OBJ_INHERIT, inherit); + + OBJECT_ATTRIBUTES attr; + InitializeObjectAttributes(&attr, &name, attrFlags, parent, nullptr); + + ULONG options = 0; + WI_SetFlagIf(options, FILE_SYNCHRONOUS_IO_NONALERT, synchronous); + + IO_STATUS_BLOCK ioStatus; + return NtCreateFile( + /* FileHandle */ handle, + /* DesiredAccess */ FILE_GENERIC_READ | FILE_GENERIC_WRITE, + /* ObjectAttributes */ &attr, + /* IoStatusBlock */ &ioStatus, + /* AllocationSize */ nullptr, + /* FileAttributes */ 0, + /* ShareAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + /* CreateDisposition */ FILE_CREATE, + /* CreateOptions */ options, + /* EaBuffer */ nullptr, + /* EaLength */ 0); +} + +HRESULT _CreatePseudoConsole(HANDLE hToken, const COORD size, const HANDLE hInput, const HANDLE hOutput, @@ -109,16 +198,16 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken, return E_INVALIDARG; } - wil::unique_handle serverHandle; - RETURN_IF_NTSTATUS_FAILED(CreateServerHandle(serverHandle.addressof(), TRUE)); + // CreateProcessAsUserW expects the token to be either valid or null. + if (hToken == INVALID_HANDLE_VALUE) + { + hToken = nullptr; + } - // The hPtyReference we create here is used when the PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE attribute is processed. - // This ensures that conhost's client processes inherit the correct (= our) console handle. - wil::unique_handle referenceHandle; - RETURN_IF_NTSTATUS_FAILED(CreateClientHandle(referenceHandle.addressof(), - serverHandle.get(), - L"\\Reference", - FALSE)); + unique_nthandle serverHandle; + unique_nthandle referenceHandle; + RETURN_IF_NTSTATUS_FAILED(conhostCreateHandle(serverHandle.addressof(), nullptr, constantUnicodeString(L"\\Device\\ConDrv\\Server"), true, false)); + RETURN_IF_NTSTATUS_FAILED(conhostCreateHandle(referenceHandle.addressof(), serverHandle.get(), constantUnicodeString(L"\\Reference"), false, true)); wil::unique_handle signalPipeConhostSide; wil::unique_handle signalPipeOurSide; @@ -134,7 +223,6 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken, // GH4061: Ensure that the path to executable in the format is escaped so C:\Program.exe cannot collide with C:\Program Files // This is plenty of space to hold the formatted string - wchar_t cmd[MAX_PATH]{}; const BOOL bInheritCursor = (dwFlags & PSEUDOCONSOLE_INHERIT_CURSOR) == PSEUDOCONSOLE_INHERIT_CURSOR; const wchar_t* textMeasurement; @@ -154,16 +242,23 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken, break; } - swprintf_s(cmd, - MAX_PATH, - L"\"%s\" --headless %s%s--width %hd --height %hd --signal 0x%tx --server 0x%tx", - _ConsoleHostPath(), - bInheritCursor ? L"--inheritcursor " : L"", - textMeasurement, - size.X, - size.Y, - std::bit_cast(signalPipeConhostSide.get()), - std::bit_cast(serverHandle.get())); + const auto conhostPath = getConhostPath(); + if (!conhostPath) + { + return E_OUTOFMEMORY; + } + + wil::unique_process_heap_string cmd; + RETURN_IF_FAILED(wil::str_printf_nothrow( + cmd, + L"\"%s\" --headless %s%s--width %hd --height %hd --signal 0x%tx --server 0x%tx", + conhostPath, + bInheritCursor ? L"--inheritcursor " : L"", + textMeasurement, + size.X, + size.Y, + std::bit_cast(signalPipeConhostSide.get()), + std::bit_cast(serverHandle.get()))); STARTUPINFOEXW siEx{ 0 }; siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW); @@ -180,23 +275,12 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken, inheritedHandles[2] = hOutput; inheritedHandles[3] = signalPipeConhostSide.get(); - // Get the size of the attribute list. We need one attribute, the handle list. - SIZE_T listSize = 0; - InitializeProcThreadAttributeList(nullptr, 1, 0, &listSize); + // Birds aren't real and DeleteProcThreadAttributeList isn't real either. + alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) char attrListBuffer[128]; + siEx.lpAttributeList = reinterpret_cast(&attrListBuffer[0]); - // I have to use a HeapAlloc here because kernelbase can't link new[] or delete[] - auto attrList = static_cast(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, listSize)); - RETURN_IF_NULL_ALLOC(attrList); - auto attrListDelete = wil::scope_exit([&]() noexcept { - HeapFree(GetProcessHeap(), 0, attrList); - }); - - siEx.lpAttributeList = attrList; + SIZE_T listSize = sizeof(attrListBuffer); RETURN_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &listSize)); - // Set cleanup data for ProcThreadAttributeList when successful. - auto cleanupProcThreadAttribute = wil::scope_exit([&]() noexcept { - DeleteProcThreadAttributeList(siEx.lpAttributeList); - }); RETURN_IF_WIN32_BOOL_FALSE(UpdateProcThreadAttribute(siEx.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, @@ -215,40 +299,26 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken, RtlWow64EnableFsRedirectionEx(RedirectionFlag, &RedirectionFlag); }); #endif - if (hToken == INVALID_HANDLE_VALUE || hToken == nullptr) - { - // Call create process - RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(_ConsoleHostPath(), - cmd, - nullptr, - nullptr, - TRUE, - EXTENDED_STARTUPINFO_PRESENT, - nullptr, - nullptr, - &siEx.StartupInfo, - pi.addressof())); - } - else - { - // Call create process - RETURN_IF_WIN32_BOOL_FALSE(CreateProcessAsUserW(hToken, - _ConsoleHostPath(), - cmd, - nullptr, - nullptr, - TRUE, - EXTENDED_STARTUPINFO_PRESENT, - nullptr, - nullptr, - &siEx.StartupInfo, - pi.addressof())); - } + + // Call create process + RETURN_IF_WIN32_BOOL_FALSE(CreateProcessAsUserW( + hToken, + conhostPath, + cmd.get(), + nullptr, + nullptr, + TRUE, + EXTENDED_STARTUPINFO_PRESENT, + nullptr, + nullptr, + &siEx.StartupInfo, + pi.addressof())); } pPty->hSignal = signalPipeOurSide.release(); pPty->hPtyReference = referenceHandle.release(); - pPty->hConPtyProcess = std::exchange(pi.hProcess, nullptr); + pPty->hConPtyProcess = pi.hProcess; + pi.hProcess = nullptr; return S_OK; } diff --git a/tools/OpenConsole.psm1 b/tools/OpenConsole.psm1 index bbcbc88993..896d7970e1 100644 --- a/tools/OpenConsole.psm1 +++ b/tools/OpenConsole.psm1 @@ -59,7 +59,7 @@ function Import-LocalModule #.SYNOPSIS # Grabs all environment variable set after vcvarsall.bat is called and pulls # them into the Powershell environment. -function Set-MsbuildDevEnvironment +function Set-MsBuildDevEnvironment { [CmdletBinding()] param( @@ -85,6 +85,7 @@ function Set-MsbuildDevEnvironment default { throw "Unknown architecture: $switch" } } + Write-Host $vsinfo $devShellModule = "$vspath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" Import-Module -Global -Name $devShellModule @@ -438,4 +439,4 @@ function Get-Format() & "$root\dep\nuget\nuget.exe" restore "$root\tools\packages.config" } -Export-ModuleMember -Function Set-MsbuildDevEnvironment,Invoke-OpenConsoleTests,Invoke-OpenConsoleBuild,Start-OpenConsole,Debug-OpenConsole,Invoke-CodeFormat,Invoke-XamlFormat,Test-XamlFormat,Get-Format +Export-ModuleMember -Function Set-MsBuildDevEnvironment,Invoke-OpenConsoleTests,Invoke-OpenConsoleBuild,Start-OpenConsole,Debug-OpenConsole,Invoke-CodeFormat,Invoke-XamlFormat,Test-XamlFormat,Get-Format