Skip to content

Instantly share code, notes, and snippets.

@asmichi
Created March 30, 2024 14:56
Show Gist options
  • Save asmichi/a37622e5cb7117e2870df68f750cb69b to your computer and use it in GitHub Desktop.
Save asmichi/a37622e5cb7117e2870df68f750cb69b to your computer and use it in GitHub Desktop.
Pseudo Console: Application error in a hosted child when the parent is killed before the child gets the time to initialize
#define WIN32_LEAN_AND_MEAN
#define NOMIMNAX
#include <Windows.h>
#include <processthreadsapi.h>
// If this parent process is killed before the hosted process gets the time to initialize (especially when the system is busy),
// the "Application Error" dialog will pop up with error code 0xc0000142 (STATUS_DLL_INIT_FAILED)
// as noted in the Note:
//
// https://learn.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#creating-the-hosted-process
// > Note
// >
// > Closing the pseudoconsole session while the hosted process is still starting up
// > and connecting can result in an error dialog being shown by the client application.
// > The same error dialog is shown if the hosted process is given an invalid pseudoconsole
// > handle for startup. To the hosted process initialization code, the two circumstances
// > are identical. The pop-up dialog from the hosted client application on failure will read
// > 0xc0000142 with a localized message detailing failure to initialize.
//
// See the TerminateProcess call below just after CreateProcessW.
HRESULT SetUpPseudoConsole(COORD size);
HRESULT PrepareStartupInformation(HPCON hpc, STARTUPINFOEX* psi);
int wmain()
{
#if 0
// To mitigate the application error dialog,
// kill our child process when this process is killed
// by assigning the child process to a job object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.
//
// Hopefully our child process will be killed before accessing the closed pseudoconsole.
//
// For simplicity, have the child process inherit this job object
// instead of adding PROC_THREAD_ATTRIBUTE_JOB_LIST.
HANDLE hJob = CreateJobObjectW(NULL, NULL);
if (hJob == NULL)
{
return HRESULT_FROM_WIN32(GetLastError());
}
JOBOBJECT_EXTENDED_LIMIT_INFORMATION eli{};
eli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &eli, sizeof(eli)))
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (!AssignProcessToJobObject(hJob, GetCurrentProcess()))
{
return HRESULT_FROM_WIN32(GetLastError());
}
#endif
COORD size = { 80, 25 };
SetUpPseudoConsole(size);
}
HRESULT SetUpPseudoConsole(COORD size)
{
//
// Copied from https://learn.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#creating-the-pseudoconsole
//
HRESULT hr = S_OK;
// Create communication channels
// - Close these after CreateProcess of child application with pseudoconsole object.
HANDLE inputReadSide, outputWriteSide;
// - Hold onto these and use them for communication with the child through the pseudoconsole.
HANDLE outputReadSide, inputWriteSide;
if (!CreatePipe(&inputReadSide, &inputWriteSide, NULL, 0))
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (!CreatePipe(&outputReadSide, &outputWriteSide, NULL, 0))
{
return HRESULT_FROM_WIN32(GetLastError());
}
HPCON hPC;
hr = CreatePseudoConsole(size, inputReadSide, outputWriteSide, 0, &hPC);
if (FAILED(hr))
{
return hr;
}
//
// Copied from https://learn.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#creating-the-hosted-process
//
PCWSTR childApplication = L"C:\\windows\\system32\\cmd.exe";
// Create mutable text string for CreateProcessW command line string.
const size_t charsRequired = wcslen(childApplication) + 1; // +1 null terminator
PWSTR cmdLineMutable = (PWSTR)HeapAlloc(GetProcessHeap(), 0, sizeof(wchar_t) * charsRequired);
if (!cmdLineMutable)
{
return E_OUTOFMEMORY;
}
wcscpy_s(cmdLineMutable, charsRequired, childApplication);
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
STARTUPINFOEX siEx;
hr = PrepareStartupInformation(hPC, &siEx);
if (FAILED(hr))
{
return hr;
}
// Call CreateProcess
if (!CreateProcessW(NULL,
cmdLineMutable,
NULL,
NULL,
FALSE,
EXTENDED_STARTUPINFO_PRESENT,
NULL,
NULL,
&siEx.StartupInfo,
&pi))
{
HeapFree(GetProcessHeap(), 0, cmdLineMutable);
return HRESULT_FROM_WIN32(GetLastError());
}
// ...
//
// Simulate Ctrl+Break, the [x] button of a console (suppose the host is a console app),
// TerminateProcess from another process.
TerminateProcess(GetCurrentProcess(), 1);
return S_OK;
}
//
// Copied from https://learn.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#preparing-for-creation-of-the-child-process
//
HRESULT PrepareStartupInformation(HPCON hpc, STARTUPINFOEX* psi)
{
// Prepare Startup Information structure
STARTUPINFOEX si;
ZeroMemory(&si, sizeof(si));
si.StartupInfo.cb = sizeof(STARTUPINFOEX);
// Discover the size required for the list
size_t bytesRequired;
InitializeProcThreadAttributeList(NULL, 1, 0, &bytesRequired);
// Allocate memory to represent the list
si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, bytesRequired);
if (!si.lpAttributeList)
{
return E_OUTOFMEMORY;
}
// Initialize the list memory location
if (!InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &bytesRequired))
{
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
return HRESULT_FROM_WIN32(GetLastError());
}
// Set the pseudoconsole information into the list
if (!UpdateProcThreadAttribute(si.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
hpc,
sizeof(hpc),
NULL,
NULL))
{
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
return HRESULT_FROM_WIN32(GetLastError());
}
*psi = si;
return S_OK;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment