This commit is contained in:
Leonard Hecker 2024-08-22 15:40:42 +02:00
parent 56cfb77c6d
commit 830aaab130
8 changed files with 216 additions and 198 deletions

View File

@ -1,17 +1,16 @@
#include "pch.h"
#include "conhost.h"
#include <conmsgl1.h>
#include <winternl.h>
#include <wil/win32_helpers.h>
#include "arena.h"
static unique_nthandle conhostCreateHandle(HANDLE parent, const wchar_t* typeName, bool inherit, bool synchronous)
template<size_t N>
static constexpr UNICODE_STRING constantUnicodeString(const wchar_t (&s)[N])
{
UNICODE_STRING name;
RtlInitUnicodeString(&name, typeName);
return { N * 2 - 2, N * 2, const_cast<wchar_t*>(&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<wchar_t>(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);

View File

@ -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;
}

View File

@ -7,8 +7,11 @@
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <conmsgl1.h>
#include <winternl.h>
#include <wil/result.h>
#include <wil/resource.h>
#include <wil/win32_helpers.h>
#include <array>
#include <algorithm>

View File

@ -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);
}

View File

@ -15,8 +15,10 @@
<ClCompile Include="../precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="../../server/DeviceHandle.cpp" />
<ClCompile Include="../../server/WinNTControl.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\precomp.h" />
<ClInclude Include="..\winconpty.h" />
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>
@ -59,4 +61,4 @@
</PackagingOutputs>
</ItemGroup>
</Target>
</Project>
</Project>

View File

@ -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 <algorithm> 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 <windows.h>
#undef WIN32_NO_STATUS
#include <Windows.h>
#include <winternl.h>
#pragma warning(push)
#pragma warning(disable : 4430) // Must disable 4430 "default int" warning for C++ because ntstatus.h is inflexible SDK definition.
#include <ntstatus.h>
#pragma warning(pop)
#include <strsafe.h>
#include "../host/conddkrefs.h"
// This includes support libraries from the CRT, STL, WIL, and GSL
#include "LibraryIncludes.h"
#include <winconp.h>
#include <wil/win32_helpers.h>

View File

@ -6,29 +6,123 @@
#include "winconpty.h"
#ifdef __INSIDE_WINDOWS
#include <strsafe.h>
#include <consoleinternal.h>
// You need kernelbasestaging.h to be able to use wil in libraries consumed by kernelbase.dll
#include <kernelbasestaging.h>
#define RESOURCE_SUPPRESS_STL
#define WIL_SUPPORT_BITOPERATION_PASCAL_NAMES
#include <wil/resource.h>
#else
#include "device.h"
#include <filesystem>
#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<decltype(&::NtClose), ::NtClose>;
static wil::unique_process_heap_string allocString(size_t count)
{
const auto addr = static_cast<wchar_t*>(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<wil::unique_process_heap_string>(systemDirectory);
return wil::str_concat_failfast<wil::unique_process_heap_string>(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<std::wstring>(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<size_t N>
static constexpr UNICODE_STRING constantUnicodeString(const wchar_t (&s)[N])
{
return { N * 2 - 2, N * 2, const_cast<wchar_t*>(&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<uintptr_t>(signalPipeConhostSide.get()),
std::bit_cast<uintptr_t>(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<uintptr_t>(signalPipeConhostSide.get()),
std::bit_cast<uintptr_t>(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<PPROC_THREAD_ATTRIBUTE_LIST>(&attrListBuffer[0]);
// I have to use a HeapAlloc here because kernelbase can't link new[] or delete[]
auto attrList = static_cast<PPROC_THREAD_ATTRIBUTE_LIST>(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;
}

View File

@ -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