Compare commits

...

6 Commits

Author SHA1 Message Date
Dustin L. Howett
eb818f9872 Update for project system changes 2023-10-20 14:53:52 -05:00
Dustin L. Howett
ebf1256463 Merge remote-tracking branch 'origin/main' into dev/duhowett/chop 2023-10-20 14:42:40 -05:00
Dustin L. Howett
d4e756c956 Migrate spelling-0.0.21 changes from main 2021-04-14 15:50:35 -07:00
Dustin L. Howett
e97a49068e Migrate spelling-0.0.19 changes from main 2021-04-14 15:50:35 -07:00
Michael Niksa
e5c61155a0 Let VS update solution config matrix for Chop.EXE 2021-04-14 15:50:35 -07:00
Dustin Howett
2b6ad8dd1c Add "CHOP", the "Console Hosting Process"
This tool takes a conhost .exe (>= RS4) or .dll (< RS4) and attempts to
start a process attached to it. Its primary use is in
compatibility-testing older versions of conhost without starting up a
whole VM.
2021-04-14 15:47:57 -07:00
5 changed files with 410 additions and 0 deletions

View File

@@ -400,6 +400,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests_Control", "src\ca
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Chop.EXE", "src\chop\chop.vcxproj", "{88B6EE17-0FC8-4FA5-8243-92479EE5C446}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsTerminal.UIA.Tests", "src\cascadia\WindowsTerminal_UIATests\WindowsTerminal.UIA.Tests.csproj", "{F19DACD5-0C6E-40DC-B6E4-767A3200542C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utils", "Utils", "{61901E80-E97D-4D61-A9BB-E8F2FDA8B40C}"
@@ -2578,6 +2580,32 @@ Global
{C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|x64.Build.0 = Release|x64
{C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|x86.ActiveCfg = Release|Win32
{C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|x86.Build.0 = Release|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.AuditMode|Any CPU.ActiveCfg = Release|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.AuditMode|ARM.ActiveCfg = AuditMode|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.AuditMode|ARM64.ActiveCfg = Release|ARM64
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.AuditMode|x64.ActiveCfg = Release|x64
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.AuditMode|x86.ActiveCfg = Release|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Debug|Any CPU.ActiveCfg = Debug|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Debug|ARM.ActiveCfg = Debug|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Debug|ARM64.ActiveCfg = Debug|ARM64
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Debug|ARM64.Build.0 = Debug|ARM64
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Debug|x64.ActiveCfg = Debug|x64
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Debug|x64.Build.0 = Debug|x64
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Debug|x86.ActiveCfg = Debug|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Debug|x86.Build.0 = Debug|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Fuzzing|x64.ActiveCfg = Fuzzing|x64
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Release|Any CPU.ActiveCfg = Release|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Release|ARM.ActiveCfg = Release|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Release|ARM64.ActiveCfg = Release|ARM64
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Release|ARM64.Build.0 = Release|ARM64
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Release|x64.ActiveCfg = Release|x64
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Release|x64.Build.0 = Release|x64
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Release|x86.ActiveCfg = Release|Win32
{88B6EE17-0FC8-4FA5-8243-92479EE5C446}.Release|x86.Build.0 = Release|Win32
{F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|Any CPU.ActiveCfg = Debug|Win32
{F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|ARM.ActiveCfg = Debug|Win32
{F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|ARM64.ActiveCfg = Debug|ARM64
@@ -2902,6 +2930,7 @@ Global
{9921CA0A-320C-4460-8623-3A3196E7F4CB} = {59840756-302F-44DF-AA47-441A9D673202}
{05D9052F-D78F-478F-968A-2DE38A6DB996} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
{C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
{88B6EE17-0FC8-4FA5-8243-92479EE5C446} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
{F19DACD5-0C6E-40DC-B6E4-767A3200542C} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
{61901E80-E97D-4D61-A9BB-E8F2FDA8B40C} = {59840756-302F-44DF-AA47-441A9D673202}
{8222900C-8B6C-452A-91AC-BE95DB04B95F} = {05500DEF-2294-41E3-AF9A-24E580B82836}

39
src/chop/chop.vcxproj Normal file
View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{88b6ee17-0fc8-4fa5-8243-92479ee5c446}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>Chop</RootNamespace>
<ProjectName>Chop.EXE</ProjectName>
<TargetName>chop</TargetName>
<ConfigurationType>Application</ConfigurationType>
</PropertyGroup>
<Import Project="$(SolutionDir)common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.props" />
<ItemGroup>
<ClInclude Include="precomp.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<ClCompile Include="exemain.cpp" />
<ClCompile Include="../server/DeviceHandle.cpp" />
<ClCompile Include="../server/WinNTControl.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\types\lib\types.vcxproj">
<Project>{18d09a24-8240-42d6-8cb6-236eee820263}</Project>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<EmbedManifest>
</EmbedManifest>
<GenerateManifest>true</GenerateManifest>
</PropertyGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
</Project>

293
src/chop/exemain.cpp Normal file
View File

@@ -0,0 +1,293 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "../server/DeviceHandle.h"
#include "../server/winbasep.h"
__declspec(dllexport) BOOL WINAPI EnableChildWindowDpiMessage(HWND, BOOL)
{
return FALSE;
}
[[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);
}
[[nodiscard]] static HRESULT StartConsoleHostEXE(PCWSTR conhostPath, HANDLE serverHandle)
{
STARTUPINFOEX StartupInformation = { 0 };
StartupInformation.StartupInfo.cb = sizeof(STARTUPINFOEX);
StartupInformation.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
StartupInformation.StartupInfo.hStdInput = nullptr;
StartupInformation.StartupInfo.hStdOutput = nullptr;
StartupInformation.StartupInfo.hStdError = nullptr;
SIZE_T AttributeListSize;
InitializeProcThreadAttributeList(NULL,
1,
0,
&AttributeListSize);
// Alloc space
wistd::unique_ptr<BYTE[]> AttributeList = wil::make_unique_nothrow<BYTE[]>(AttributeListSize);
RETURN_IF_NULL_ALLOC(AttributeList);
StartupInformation.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(AttributeList.get());
// Call second time to actually initialize space.
RETURN_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(StartupInformation.lpAttributeList,
1, // This represents the length of the list. We will call UpdateProcThreadAttribute twice so this is 2.
0,
&AttributeListSize));
// Set cleanup data for ProcThreadAttributeList when successful.
auto CleanupProcThreadAttribute = wil::scope_exit([&] {
DeleteProcThreadAttributeList(StartupInformation.lpAttributeList);
});
// UpdateProcThreadAttributes wants this as a bare array of handles and doesn't like our smart structures,
// so set it up for its use.
HANDLE HandleList[1];
HandleList[0] = serverHandle;
RETURN_IF_WIN32_BOOL_FALSE(UpdateProcThreadAttribute(StartupInformation.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
&HandleList[0],
sizeof HandleList,
NULL,
NULL));
if (!conhostPath || wcslen(conhostPath) == 0)
{
conhostPath = L"\\\\?\\%WINDIR%\\System32\\conhost.exe";
}
// Expand any environment variables present in the command line string.
std::wstring cmdLine = wil::ExpandEnvironmentStringsW<std::wstring>(conhostPath);
std::wstring expandedConhostPath{ cmdLine };
cmdLine += wil::str_printf<std::wstring>(L" 0x%x", reinterpret_cast<uintptr_t>(HandleList[0]));
// Call create process
wil::unique_process_information ProcessInformation;
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(expandedConhostPath.data(),
cmdLine.data(),
NULL,
NULL,
TRUE,
EXTENDED_STARTUPINFO_PRESENT,
NULL,
NULL,
&StartupInformation.StartupInfo,
ProcessInformation.addressof()));
return S_OK;
}
[[nodiscard]] static HRESULT StartConsoleHostDLL(PCWSTR conhostPath, HANDLE serverHandle)
{
typedef NTSTATUS (*PFNCONSOLECREATEIOTHREAD)(__in HANDLE Server);
wil::unique_hmodule conhost{ LoadLibrary(conhostPath) };
RETURN_LAST_ERROR_IF_NULL(conhost);
PFNCONSOLECREATEIOTHREAD pfnConsoleCreateIoThread = (PFNCONSOLECREATEIOTHREAD)GetProcAddress(conhost.get(), "ConsoleCreateIoThread");
RETURN_HR_IF_NULL(E_UNEXPECTED, pfnConsoleCreateIoThread);
RETURN_IF_NTSTATUS_FAILED(pfnConsoleCreateIoThread(serverHandle));
conhost.release(); // keep the module loaded
return S_OK;
}
[[nodiscard]] static HRESULT StartConsoleHost(PCWSTR conhostPath, HANDLE serverHandle, bool& keepRunning)
{
if (!conhostPath || conhostPath[0] == L'\0')
{
return StartConsoleHostEXE(conhostPath, serverHandle);
}
std::filesystem::path p{ conhostPath };
auto ext{ p.extension() };
if (ext == L".dll" || ext == L".DLL")
{
keepRunning = true;
return StartConsoleHostDLL(conhostPath, serverHandle);
}
return StartConsoleHostEXE(conhostPath, serverHandle);
}
[[nodiscard]] static HRESULT
StartConsoleForCmdLine(PCWSTR conhostPath, PCWSTR pwszCmdLine)
{
bool keepRunning{ false };
// Create a scope because we're going to exit thread if everything goes well.
// This scope will ensure all C++ objects and smart pointers get a chance to destruct before ExitThread is called.
{
// Create the server and reference handles and create the console object.
wil::unique_handle ServerHandle;
RETURN_IF_NTSTATUS_FAILED(CreateServerHandle(ServerHandle.addressof(), TRUE));
wil::unique_handle ReferenceHandle;
RETURN_IF_NTSTATUS_FAILED(CreateClientHandle(ReferenceHandle.addressof(),
ServerHandle.get(),
L"\\Reference",
FALSE));
RETURN_IF_FAILED(StartConsoleHost(conhostPath, ServerHandle.get(), keepRunning));
//if (!keepRunning)
{
Sleep(500); // let the host quiesce
}
auto hServer = ServerHandle.release();
// Now that the console object was created, we're in a state that lets us
// create the default io objects.
wil::unique_handle ClientHandle[3];
// Input
RETURN_IF_NTSTATUS_FAILED(CreateClientHandle(ClientHandle[0].addressof(),
hServer,
L"\\Input",
TRUE));
// Output
RETURN_IF_NTSTATUS_FAILED(CreateClientHandle(ClientHandle[1].addressof(),
hServer,
L"\\Output",
TRUE));
// Error is a copy of Output
RETURN_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(),
ClientHandle[1].get(),
GetCurrentProcess(),
ClientHandle[2].addressof(),
0,
TRUE,
DUPLICATE_SAME_ACCESS));
// Create the child process. We will temporarily overwrite the values in the
// PEB to force them to be inherited.
STARTUPINFOEX StartupInformation = { 0 };
StartupInformation.StartupInfo.cb = sizeof(STARTUPINFOEX);
StartupInformation.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
StartupInformation.StartupInfo.hStdInput = ClientHandle[0].get();
StartupInformation.StartupInfo.hStdOutput = ClientHandle[1].get();
StartupInformation.StartupInfo.hStdError = ClientHandle[2].get();
// Get the parent startup info for this process. It might contain LNK data we need to pass to the child.
{
STARTUPINFO HostStartupInfo = { 0 };
HostStartupInfo.cb = sizeof(STARTUPINFO);
GetStartupInfoW(&HostStartupInfo);
// Pass the title we were started with down to our child process.
// Conhost itself absolutely doesn't care about this value, but the
// child might.
StartupInformation.StartupInfo.lpTitle = HostStartupInfo.lpTitle;
// If we were started with Title is Link Name, then pass the flag
// down to the child. (the link name was already passed down above)
if (WI_IsFlagSet(HostStartupInfo.dwFlags, STARTF_TITLEISLINKNAME))
{
StartupInformation.StartupInfo.dwFlags |= STARTF_TITLEISLINKNAME;
}
}
// Create the extended attributes list that will pass the console server information into the child process.
// Call first time to find size
SIZE_T AttributeListSize;
InitializeProcThreadAttributeList(NULL,
2,
0,
&AttributeListSize);
// Alloc space
wistd::unique_ptr<BYTE[]> AttributeList = wil::make_unique_nothrow<BYTE[]>(AttributeListSize);
RETURN_IF_NULL_ALLOC(AttributeList);
StartupInformation.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(AttributeList.get());
// Call second time to actually initialize space.
RETURN_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(StartupInformation.lpAttributeList,
2, // This represents the length of the list. We will call UpdateProcThreadAttribute twice so this is 2.
0,
&AttributeListSize));
// Set cleanup data for ProcThreadAttributeList when successful.
auto CleanupProcThreadAttribute = wil::scope_exit([&] {
DeleteProcThreadAttributeList(StartupInformation.lpAttributeList);
});
RETURN_IF_WIN32_BOOL_FALSE(UpdateProcThreadAttribute(StartupInformation.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_CONSOLE_REFERENCE,
ReferenceHandle.addressof(),
sizeof(HANDLE),
NULL,
NULL));
// UpdateProcThreadAttributes wants this as a bare array of handles and doesn't like our smart structures,
// so set it up for its use.
HANDLE HandleList[3];
HandleList[0] = StartupInformation.StartupInfo.hStdInput;
HandleList[1] = StartupInformation.StartupInfo.hStdOutput;
HandleList[2] = StartupInformation.StartupInfo.hStdError;
RETURN_IF_WIN32_BOOL_FALSE(UpdateProcThreadAttribute(StartupInformation.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
&HandleList[0],
sizeof HandleList,
NULL,
NULL));
// We have to copy the command line string we're given because CreateProcessW has to be called with mutable data.
if (!pwszCmdLine || wcslen(pwszCmdLine) == 0)
{
// If they didn't give us one, just launch cmd.exe.
pwszCmdLine = L"%WINDIR%\\system32\\cmd.exe";
}
// Expand any environment variables present in the command line string.
std::wstring cmdLine = wil::ExpandEnvironmentStringsW<std::wstring>(pwszCmdLine);
// Call create process
wil::unique_process_information ProcessInformation;
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(NULL,
cmdLine.data(),
NULL,
NULL,
TRUE,
EXTENDED_STARTUPINFO_PRESENT,
NULL,
NULL,
&StartupInformation.StartupInfo,
ProcessInformation.addressof()));
}
// Exit the process since we've done our jobs
if (!keepRunning)
{
ExitProcess(S_OK);
}
return S_OK;
}
int CALLBACK wWinMain(
_In_ HINSTANCE /*hInstance*/,
_In_ HINSTANCE /*hPrevInstance*/,
_In_ PWSTR /*pwszCmdLine*/,
_In_ int /*nCmdShow*/)
{
int argc{ 0 };
auto commandline{ GetCommandLineW() };
auto argv{ CommandLineToArgvW(commandline, &argc) };
PCWSTR conhost = argc >= 2 ? argv[1] : nullptr;
PCWSTR cmdLine = argc >= 3 ? argv[2] : nullptr;
THROW_IF_FAILED(StartConsoleForCmdLine(conhost, cmdLine));
SetProcessShutdownParameters(0, 0);
ExitThread(S_OK);
}

4
src/chop/precomp.cpp Normal file
View File

@@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"

45
src/chop/precomp.h Normal file
View File

@@ -0,0 +1,45 @@
/*++
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).
--*/
#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 NOMINMAX
#define WIN32_NO_STATUS
#include <windows.h>
#undef WIN32_NO_STATUS
#define INLINE_NTSTATUS_FROM_WIN32 1 // Must use inline NTSTATUS or it will call the wrapped function twice.
#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)
// This includes support libraries from the CRT, STL, WIL, and GSL
#include "LibraryIncludes.h"
#include "../host/conddkrefs.h"
#include <winuser.h>
#include <winconp.h>
#include <ntcon.h>
#include <windowsx.h>
#include <dde.h>
// TODO: MSFT 9355094 Find a better way of doing this. http://osgvsowi/9355094
[[nodiscard]] inline NTSTATUS NTSTATUS_FROM_HRESULT(HRESULT hr)
{
return NTSTATUS_FROM_WIN32(HRESULT_CODE(hr));
}