Split out misc/{buffer-tests,console-handles.md} to a new repo

See https://github.com/rprichard/win32-console-docs
This commit is contained in:
Ryan Prichard 2016-02-24 20:44:58 -06:00
parent e235706503
commit a4ba2420e7
46 changed files with 0 additions and 5239 deletions

View File

@ -1 +0,0 @@
/build

View File

@ -1,101 +0,0 @@
#include <TestCommon.h>
REGISTER(Test_CreateProcess_ModeCombos, always);
static void Test_CreateProcess_ModeCombos() {
// It is often unclear how (or whether) various combinations of
// CreateProcess parameters work when combined. Try to test the ambiguous
// combinations.
SpawnFailure failure;
{
// CREATE_NEW_CONSOLE | DETACHED_PROCESS ==> call fails
Worker p;
auto c = p.tryChild({ false, CREATE_NEW_CONSOLE | DETACHED_PROCESS }, &failure);
CHECK(!c.valid());
CHECK_EQ(failure.kind, SpawnFailure::CreateProcess);
CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER);
}
{
// CREATE_NO_WINDOW | CREATE_NEW_CONSOLE ==> CREATE_NEW_CONSOLE dominates
Worker p;
auto c = p.tryChild({ false, CREATE_NO_WINDOW | CREATE_NEW_CONSOLE }, &failure);
CHECK(c.valid());
CHECK(c.consoleWindow() != nullptr);
CHECK(IsWindowVisible(c.consoleWindow()));
}
{
// CREATE_NO_WINDOW | DETACHED_PROCESS ==> DETACHED_PROCESS dominates
Worker p;
auto c = p.tryChild({ false, CREATE_NO_WINDOW | DETACHED_PROCESS }, &failure);
CHECK(c.valid());
CHECK_EQ(c.newBuffer().value(), INVALID_HANDLE_VALUE);
}
}
REGISTER(Test_CreateProcess_STARTUPINFOEX, isAtLeastVista);
static void Test_CreateProcess_STARTUPINFOEX() {
// STARTUPINFOEX tests.
Worker p;
SpawnFailure failure;
auto pipe1 = newPipe(p, true);
auto ph1 = std::get<0>(pipe1);
auto ph2 = std::get<1>(pipe1);
auto testSetup = [&](SpawnParams sp, size_t cb, HANDLE inherit) {
sp.sui.cb = cb;
sp.inheritCount = 1;
sp.inheritList = { inherit };
return p.tryChild(sp, &failure);
};
{
// The STARTUPINFOEX parameter is ignored if
// EXTENDED_STARTUPINFO_PRESENT isn't present.
auto c = testSetup({true}, sizeof(STARTUPINFOEXW), ph1.value());
CHECK(c.valid());
auto ch2 = Handle::invent(ph2.value(), c);
// i.e. ph2 was inherited, because ch2 identifies the same thing.
CHECK(compareObjectHandles(ph2, ch2));
}
{
// If EXTENDED_STARTUPINFO_PRESENT is specified, but the cb value
// is wrong, the API call fails.
auto c = testSetup({true, EXTENDED_STARTUPINFO_PRESENT},
sizeof(STARTUPINFOW), ph1.value());
CHECK(!c.valid());
CHECK_EQ(failure.kind, SpawnFailure::CreateProcess);
CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER);
}
}
REGISTER(Test_CreateNoWindow_HiddenVsNothing, always);
static void Test_CreateNoWindow_HiddenVsNothing() {
Worker p;
auto c = p.child({ false, CREATE_NO_WINDOW });
if (isAtLeastWin7()) {
// As of Windows 7, GetConsoleWindow returns NULL.
CHECK(c.consoleWindow() == nullptr);
} else {
// On earlier operating systems, GetConsoleWindow returns a handle
// to an invisible window.
CHECK(c.consoleWindow() != nullptr);
CHECK(!IsWindowVisible(c.consoleWindow()));
}
}
// MSDN's CreateProcess page currently has this note in it:
//
// Important The caller is responsible for ensuring that the standard
// handle fields in STARTUPINFO contain valid handle values. These fields
// are copied unchanged to the child process without validation, even when
// the dwFlags member specifies STARTF_USESTDHANDLES. Incorrect values can
// cause the child process to misbehave or crash. Use the Application
// Verifier runtime verification tool to detect invalid handles.
//
// XXX: The word "even" here sticks out. Verify that the standard handle
// fields in STARTUPINFO are ignored when STARTF_USESTDHANDLES is not
// specified.

View File

@ -1,66 +0,0 @@
#include <TestCommon.h>
// Test CreateProcess called with dwCreationFlags containing DETACHED_PROCESS.
// This macro will output nicer line information than a function if it fails.
#define CHECK_NULL(proc) \
do { \
CHECK(handleInts(stdHandles(proc)) == \
(std::vector<uint64_t> {0,0,0})); \
} while(0)
REGISTER(Test_CreateProcess_Detached, always);
static void Test_CreateProcess_Detached() {
{
Worker p;
auto c1 = p.child({ true, DETACHED_PROCESS });
CHECK_NULL(c1);
auto c2 = p.child({ false, DETACHED_PROCESS });
CHECK_NULL(c2);
}
{
Worker p;
auto c = p.child({ true, DETACHED_PROCESS, {
p.getStdin(),
p.getStdout(),
p.getStderr(),
}});
CHECK(handleValues(stdHandles(c)) == handleValues(stdHandles(p)));
}
{
Worker p;
auto c = p.child({ false, DETACHED_PROCESS, {
p.getStdin(),
p.getStdout(),
p.getStderr(),
}});
if (isTraditionalConio()) {
CHECK(handleValues(stdHandles(c)) == handleValues(stdHandles(p)));
} else{
CHECK_NULL(c);
}
}
{
Worker p({ false, DETACHED_PROCESS });
auto pipe = newPipe(p, true);
std::get<0>(pipe).setStdin();
std::get<1>(pipe).setStdout().setStderr();
{
auto c1 = p.child({ true, DETACHED_PROCESS });
CHECK_NULL(c1);
auto c2 = p.child({ false, DETACHED_PROCESS });
CHECK_NULL(c2);
}
{
// The worker p2 was started with STARTF_USESTDHANDLES and with
// standard handles referring to a pipe. Nevertheless, its
// children's standard handles are NULL.
auto p2 = p.child({ true, DETACHED_PROCESS, stdHandles(p) });
auto c1 = p2.child({ true, DETACHED_PROCESS });
CHECK_NULL(c1);
auto c2 = p2.child({ false, DETACHED_PROCESS });
CHECK_NULL(c2);
}
}
}

View File

@ -1,212 +0,0 @@
#include <TestCommon.h>
// If CreateProcess is called with these parameters:
// - bInheritHandles=FALSE
// - STARTF_USESTDHANDLES is not specified
// - the "CreationConsoleMode" is Inherit (see console-handles.md)
// then Windows duplicates each of STDIN/STDOUT/STDERR to the child.
//
// There are variations between OS releases, especially with regards to
// how console handles work.
// This handle duplication seems to be broken in WOW64 mode. It affects
// at least:
// - Windows 7 SP1
// For some reason, the problem apparently only affects the client operating
// system, not the server OS.
//
// Export this function to the other duplicate tests.
bool brokenDuplicationInWow64() {
return isWin7() && isWorkstation() && isWow64();
}
namespace {
static bool handlesAreNull(Worker &p) {
return handleInts(stdHandles(p)) == std::vector<uint64_t> {0, 0, 0};
}
static std::string testMessage(bool isNull) {
return isNull ? "BUG(dup->NULL)" : "OK(dup)";
}
template <typename T>
void Test_CreateProcess_Duplicate_Impl(T makeChild) {
printTestName(__FUNCTION__);
{
// An inheritable pipe is still inherited.
Worker p;
auto pipe = newPipe(p, true);
auto wh = std::get<1>(pipe).setStdin().setStdout().setStderr();
CHECK(wh.inheritable());
auto c = makeChild(p, { false });
const auto expect = testMessage(brokenDuplicationInWow64());
const auto actual = testMessage(handlesAreNull(c));
std::cout << __FUNCTION__ << ": expect: " << expect << std::endl;
std::cout << __FUNCTION__ << ": actual: " << actual << std::endl;
CHECK_EQ(actual, expect);
if (c.getStdout().value() != nullptr) {
{
ObjectSnap snap;
CHECK(snap.eq({ c.getStdin(), c.getStdout(), c.getStderr(), wh }));
}
for (auto h : stdHandles(c)) {
CHECK(h.tryFlags());
if (!h.tryFlags()) {
continue;
}
auto inheritMessage = [](bool inheritable) {
return inheritable
? "OK(inherit)"
: "BAD(dup->non-inheritable)";
};
const std::string expect = inheritMessage(isAtLeastVista());
const std::string actual = inheritMessage(h.inheritable());
if (expect == actual && isAtLeastVista()) {
continue; // We'll just stay silent in this case.
}
std::cout << __FUNCTION__ << ": expect: " << expect << std::endl;
std::cout << __FUNCTION__ << ": actual: " << actual << std::endl;
CHECK_EQ(actual, expect);
}
}
}
{
// A non-inheritable pipe is still inherited.
Worker p;
auto pipe = newPipe(p, false);
auto wh = std::get<1>(pipe).setStdin().setStdout().setStderr();
auto c = makeChild(p, { false });
const auto expect = testMessage(brokenDuplicationInWow64());
const auto actual = testMessage(handlesAreNull(c));
std::cout << __FUNCTION__ << ": expect: " << expect << std::endl;
std::cout << __FUNCTION__ << ": actual: " << actual << std::endl;
CHECK_EQ(actual, expect);
if (c.getStdout().value() != nullptr) {
{
ObjectSnap snap;
CHECK(snap.eq({ c.getStdin(), c.getStdout(), c.getStderr(), wh }));
}
// CreateProcess makes separate handles for stdin/stdout/stderr,
// even though the parent has the same handle for each of them.
CHECK(c.getStdin().value() != c.getStdout().value());
CHECK(c.getStdout().value() != c.getStderr().value());
CHECK(c.getStdin().value() != c.getStderr().value());
for (auto h : stdHandles(c)) {
CHECK(h.tryFlags() && !h.inheritable());
}
// Calling FreeConsole in the child does not free the duplicated
// handles.
c.detach();
{
ObjectSnap snap;
CHECK(snap.eq({ c.getStdin(), c.getStdout(), c.getStderr(), wh }));
}
}
}
{
// Bogus values are transformed into zero.
Worker p;
Handle::invent(0x10000ull, p).setStdin().setStdout();
Handle::invent(0x0ull, p).setStderr();
auto c = makeChild(p, { false });
CHECK(handleInts(stdHandles(c)) == (std::vector<uint64_t> {0,0,0}));
}
if (isAtLeastWin8()) {
// On Windows 8 and up, if a standard handle we duplicate just happens
// to be a console handle, that isn't sufficient reason for FreeConsole
// to close it.
Worker p;
auto c = makeChild(p, { false });
auto ph = stdHandles(p);
auto ch = stdHandles(c);
auto check = [&]() {
ObjectSnap snap;
for (int i = 0; i < 3; ++i) {
CHECK(snap.eq(ph[i], ch[i]));
CHECK(ph[i].tryFlags() && ch[i].tryFlags());
CHECK_EQ(ph[i].tryFlags() && ph[i].inheritable(),
ch[i].tryFlags() && ch[i].inheritable());
}
};
check();
c.detach();
check();
}
{
// Traditional console-like values are passed through as-is,
// up to 0x0FFFFFFFull.
Worker p;
Handle::invent(0x0FFFFFFFull, p).setStdin();
Handle::invent(0x10000003ull, p).setStdout();
Handle::invent(0x00000003ull, p).setStderr();
auto c = makeChild(p, { false });
if (isAtLeastWin8()) {
// These values are invalid on Windows 8 and turned into NULL.
CHECK(handleInts(stdHandles(c)) ==
(std::vector<uint64_t> { 0, 0, 0 }));
} else {
CHECK(handleInts(stdHandles(c)) ==
(std::vector<uint64_t> { 0x0FFFFFFFull, 0, 3 }));
}
}
{
// Test setting STDIN/STDOUT/STDERR to non-inheritable console handles.
//
// Handle duplication does not apply to traditional console handles,
// and a console handle is inherited if and only if it is inheritable.
//
// On new releases, this will Just Work.
//
Worker p;
p.getStdout().setFirstChar('A');
p.openConin(false).setStdin();
p.newBuffer(false, 'B').setStdout().setStderr();
auto c = makeChild(p, { false });
if (!isAtLeastWin8()) {
CHECK(handleValues(stdHandles(p)) == handleValues(stdHandles(c)));
CHECK(!c.getStdin().tryFlags());
CHECK(!c.getStdout().tryFlags());
CHECK(!c.getStderr().tryFlags());
} else {
// In Win8, a console handle works like all other handles.
CHECK_EQ(c.getStdout().firstChar(), 'B');
ObjectSnap snap;
CHECK(snap.eq({ p.getStdout(), p.getStderr(),
c.getStdout(), c.getStderr() }));
CHECK(!c.getStdout().inheritable());
CHECK(!c.getStderr().inheritable());
}
}
}
} // anonymous namespace
REGISTER(Test_CreateProcess_Duplicate, always);
static void Test_CreateProcess_Duplicate() {
Test_CreateProcess_Duplicate_Impl([](Worker &p, SpawnParams sp) {
return p.child(sp);
});
if (isModernConio()) {
// With modern console I/O, calling CreateProcess with these
// parameters also duplicates standard handles:
// - bInheritHandles=TRUE
// - STARTF_USESTDHANDLES not specified
// - an inherit list (PROC_THREAD_ATTRIBUTE_HANDLE_LIST) is specified
Test_CreateProcess_Duplicate_Impl([](Worker &p, SpawnParams sp) {
return childWithDummyInheritList(p, sp, false);
});
Test_CreateProcess_Duplicate_Impl([](Worker &p, SpawnParams sp) {
return childWithDummyInheritList(p, sp, true);
});
}
}

View File

@ -1,81 +0,0 @@
#include <TestCommon.h>
// With CreateProcess's default handle duplication behavior, the
// GetCurrentProcess() psuedo-handle (i.e. INVALID_HANDLE_VALUE) is translated
// to a real handle value for the child process. It is a handle to the parent
// process. Naturally, this was unintended behavior, and as of Windows 8.1,
// the handle is instead translated to NULL. On some older operating systems,
// the WOW64 mode also translates it to NULL.
const std::string bugParentProc = "BUG(parent-proc)";
const std::string okInvalid = "OK(INVALID)";
const std::string okNull = "OK(NULL)";
static std::string determineChildStdout(Worker &c, Worker &p) {
if (c.getStdout().value() == nullptr) {
return okNull;
} else if (c.getStdout().value() == INVALID_HANDLE_VALUE) {
return okInvalid;
} else {
auto handleToPInP = Handle::dup(p.processHandle(), p);
CHECK(compareObjectHandles(c.getStdout(), handleToPInP));
return bugParentProc;
}
}
REGISTER(Test_CreateProcess_Duplicate_PseudoHandleBug, always);
static void Test_CreateProcess_Duplicate_PseudoHandleBug() {
Worker p;
Handle::invent(GetCurrentProcess(), p).setStdout();
auto c = p.child({ false });
const std::string expect =
(isAtLeastWin8_1() || (isAtLeastVista() && isWow64()))
? okNull
: bugParentProc;
const std::string actual = determineChildStdout(c, p);
trace("%s: actual: %s", __FUNCTION__, actual.c_str());
std::cout << __FUNCTION__ << ": expect: " << expect << std::endl;
std::cout << __FUNCTION__ << ": actual: " << actual << std::endl;
CHECK_EQ(actual, expect);
}
REGISTER(Test_CreateProcess_Duplicate_PseudoHandleBug_IL, isAtLeastVista);
static void Test_CreateProcess_Duplicate_PseudoHandleBug_IL() {
// As above, but use an inherit list. With an inherit list, standard
// handles are duplicated, but only with Windows 8 and up.
for (int useDummyPipe = 0; useDummyPipe <= 1; ++useDummyPipe) {
Worker p;
Handle::invent(INVALID_HANDLE_VALUE, p).setStdout();
auto c = childWithDummyInheritList(p, {}, useDummyPipe != 0);
// Figure out what we expect to see.
std::string expect;
if (isAtLeastWin8_1()) {
// Windows 8.1 turns INVALID_HANDLE_VALUE into NULL.
expect = okNull;
} else if (isAtLeastWin8()) {
// Windows 8 tries to duplicate the handle. WOW64 seems to be
// OK, though.
if (isWow64()) {
expect = okNull;
} else {
expect = bugParentProc;
}
} else {
// Prior to Windows 8, duplication doesn't occur in this case, so
// the bug isn't relevant. We run the test anyway, but it's less
// interesting.
expect = okInvalid;
}
const std::string actual = determineChildStdout(c, p);
trace("%s: actual: %s", __FUNCTION__, actual.c_str());
std::cout << __FUNCTION__ << ": expect: " << expect << std::endl;
std::cout << __FUNCTION__ << ": actual: " << actual << std::endl;
CHECK_EQ(actual, expect);
}
}

View File

@ -1,47 +0,0 @@
#include <TestCommon.h>
// Windows XP bug: default inheritance doesn't work with the read end
// of a pipe, even if it's inheritable. It works with the write end.
bool brokenDuplicationInWow64();
REGISTER(Test_CreateProcess_Duplicate_XPPipeBug, always);
static void Test_CreateProcess_Duplicate_XPPipeBug() {
auto check = [](Worker &proc, Handle correct, bool expectNull) {
CHECK_EQ((proc.getStdin().value() == nullptr), expectNull);
CHECK_EQ((proc.getStdout().value() == nullptr), expectNull);
CHECK_EQ((proc.getStderr().value() == nullptr), expectNull);
if (proc.getStdout().value() != nullptr) {
ObjectSnap snap;
CHECK(snap.eq({
proc.getStdin(), proc.getStdout(), proc.getStderr(), correct
}));
}
};
Worker p;
auto pipe = newPipe(p, false);
auto rh = std::get<0>(pipe).setStdin().setStdout().setStderr();
auto c1 = p.child({ false });
check(c1, rh, !isAtLeastVista() || brokenDuplicationInWow64());
// Marking the handle itself inheritable makes no difference.
rh.setInheritable(true);
auto c2 = p.child({ false });
check(c2, rh, !isAtLeastVista() || brokenDuplicationInWow64());
// If we enter bInheritHandles=TRUE mode, it works.
auto c3 = p.child({ true });
check(c3, rh, false);
// Using STARTF_USESTDHANDLES works too.
Handle::invent(nullptr, p).setStdin().setStdout().setStderr();
auto c4 = p.child({ true, 0, { rh, rh, rh }});
check(c4, rh, false);
// Also test the write end of the pipe.
auto wh = std::get<1>(pipe).setStdin().setStdout().setStderr();
auto c5 = p.child({ false });
check(c5, wh, brokenDuplicationInWow64());
}

View File

@ -1,53 +0,0 @@
#include <TestCommon.h>
//
// Test CreateProcess when called with these parameters:
// - STARTF_USESTDHANDLES is not specified
// - bInheritHandles=TRUE
// - CreationConsoleMode=Inherit
//
REGISTER(Test_CreateProcess_InheritAllHandles, always);
static void Test_CreateProcess_InheritAllHandles() {
auto &hv = handleValues;
{
// Simple case: the standard handles are left as-is.
Worker p;
auto pipe = newPipe(p, true);
std::get<0>(pipe).setStdin();
std::get<1>(pipe).setStdout().setStderr();
auto c = p.child({ true });
CHECK(hv(stdHandles(c)) == hv(stdHandles(p)));
}
{
// We can pass arbitrary values through.
Worker p;
Handle::invent(0x0ull, p).setStdin();
Handle::invent(0x10000ull, p).setStdout();
Handle::invent(INVALID_HANDLE_VALUE, p).setStderr();
auto c = p.child({ true });
CHECK(hv(stdHandles(c)) == hv(stdHandles(p)));
}
{
// Passing through a non-inheritable handle produces an invalid child
// handle.
Worker p;
p.openConin(false).setStdin();
p.openConout(false).setStdout().setStderr();
auto c = p.child({ true });
CHECK(hv(stdHandles(c)) == hv(stdHandles(p)));
if (isTraditionalConio()) {
CHECK(!c.getStdin().tryFlags());
CHECK(!c.getStdout().tryFlags());
CHECK(!c.getStderr().tryFlags());
} else {
ObjectSnap snap;
CHECK(!snap.eq(p.getStdin(), c.getStdin()));
CHECK(!snap.eq(p.getStdout(), c.getStdout()));
CHECK(!snap.eq(p.getStderr(), c.getStderr()));
}
}
}

View File

@ -1,388 +0,0 @@
#include <TestCommon.h>
//
// Test CreateProcess, using PROC_THREAD_ATTRIBUTE_HANDLE_LIST to restrict the
// inherited handles.
//
// Ordinarily, standard handles are copied as-is.
//
// On Windows 8 and later, if a PROC_THREAD_ATTRIBUTE_HANDLE_LIST list is used,
// then the standard handles are duplicated instead.
//
REGISTER(Test_CreateProcess_InheritList, isAtLeastVista);
static void Test_CreateProcess_InheritList() {
// Specifically test inherit lists.
SpawnFailure failure;
auto testSetup = [&](Worker &proc,
SpawnParams sp,
std::initializer_list<HANDLE> inheritList) {
sp.dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
sp.sui.cb = sizeof(STARTUPINFOEXW);
sp.inheritCount = inheritList.size();
std::copy(inheritList.begin(), inheritList.end(),
sp.inheritList.begin());
return proc.tryChild(sp, &failure);
};
Worker p;
auto pipe1 = newPipe(p, true);
auto ph1 = std::get<0>(pipe1);
auto ph2 = std::get<1>(pipe1);
auto pipe2 = newPipe(p, true);
auto ph3 = std::get<0>(pipe2);
auto ph4 = std::get<1>(pipe2);
auto phNI = ph1.dup(false);
// Add an extra console handle so we can verify that a child's console
// handles didn't revert to the original default, but were inherited.
p.openConout(true);
auto testSetupStdHandles = [&](SpawnParams sp) {
const auto in = sp.sui.hStdInput;
const auto out = sp.sui.hStdOutput;
const auto err = sp.sui.hStdError;
sp.dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
sp.sui.cb = sizeof(STARTUPINFOEXW);
// This test case isn't interested in what
// PROC_THREAD_ATTRIBUTE_HANDLE_LIST does when there are duplicate
// handles in its list.
ASSERT(in != out && out != err && in != err);
sp.inheritCount = 3;
sp.inheritList = { in, out, err };
return p.tryChild(sp, &failure);
};
auto ch1 = [&](RemoteWorker &c) { return Handle::invent(ph1.value(), c); };
auto ch2 = [&](RemoteWorker &c) { return Handle::invent(ph2.value(), c); };
auto ch3 = [&](RemoteWorker &c) { return Handle::invent(ph3.value(), c); };
auto ch4 = [&](RemoteWorker &c) { return Handle::invent(ph4.value(), c); };
{
// Use PROC_THREAD_ATTRIBUTE_HANDLE_LIST correctly.
auto c = testSetup(p, {true}, {ph1.value()});
CHECK(c.valid());
// i.e. ph1 was inherited, because ch1 identifies the same thing.
// ph2 was not inherited, because it wasn't listed.
ObjectSnap snap;
CHECK(snap.eq(ph1, ch1(c)));
CHECK(!snap.eq(ph2, ch2(c)));
if (!isAtLeastWin8()) {
// The traditional console handles were all inherited, but they're
// also the standard handles, so maybe that's an exception. We'll
// test more aggressively below.
CHECK(handleValues(c.scanForConsoleHandles()) ==
handleValues(p.scanForConsoleHandles()));
}
}
{
// UpdateProcThreadAttribute fails if the buffer size is zero.
auto c = testSetup(p, {true}, {});
CHECK(!c.valid());
CHECK_EQ(failure.kind, SpawnFailure::UpdateProcThreadAttribute);
CHECK_EQ(failure.errCode, (DWORD)ERROR_BAD_LENGTH);
}
{
// Attempting to inherit the GetCurrentProcess pseudo-handle also
// fails. (The MSDN docs point out that using GetCurrentProcess here
// will fail.)
auto c = testSetup(p, {true}, {GetCurrentProcess()});
CHECK(!c.valid());
CHECK_EQ(failure.kind, SpawnFailure::CreateProcess);
CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER);
}
{
// CreateProcess fails if the inherit list has a non-inheritable handle
// in it. (STARTF_USESTDHANDLES not set.)
auto c1 = testSetup(p, {true}, {phNI.value()});
CHECK(!c1.valid());
CHECK_EQ(failure.kind, SpawnFailure::CreateProcess);
CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER);
}
{
// CreateProcess fails if the inherit list has a non-inheritable handle
// in it. (STARTF_USESTDHANDLES set.)
auto c = testSetup(p, {true, 0, {phNI, phNI, phNI}}, {phNI.value()});
CHECK(!c.valid());
CHECK_EQ(failure.kind, SpawnFailure::CreateProcess);
CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER);
}
{
// If bInheritHandles=FALSE and PROC_THREAD_ATTRIBUTE_HANDLE_LIST are
// combined, the API call fails. (STARTF_USESTDHANDLES not set.)
auto c = testSetup(p, {false}, {ph1.value()});
CHECK(!c.valid());
CHECK_EQ(failure.kind, SpawnFailure::CreateProcess);
CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER);
}
{
// If bInheritHandles=FALSE and PROC_THREAD_ATTRIBUTE_HANDLE_LIST are
// combined, the API call fails. (STARTF_USESTDHANDLES set.)
auto c = testSetupStdHandles({false, 0, {ph1, ph2, ph4}});
CHECK(!c.valid());
CHECK_EQ(failure.kind, SpawnFailure::CreateProcess);
CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER);
}
if (!isAtLeastWin8()) {
// Attempt to restrict inheritance to just one of the three open
// traditional console handles.
auto c = testSetupStdHandles({true, 0, {ph1, ph2, p.getStderr()}});
if (isWin7()) {
// On Windows 7, the CreateProcess call fails with a strange
// error.
CHECK(!c.valid());
CHECK_EQ(failure.kind, SpawnFailure::CreateProcess);
CHECK_EQ(failure.errCode, (DWORD)ERROR_NO_SYSTEM_RESOURCES);
} else {
// On Vista, the CreateProcess call succeeds, but handle
// inheritance is broken. All of the console handles are
// inherited, not just the error screen buffer that was listed.
// None of the pipe handles were inherited, even though two were
// listed.
c.dumpConsoleHandles();
CHECK(handleValues(c.scanForConsoleHandles()) ==
handleValues(p.scanForConsoleHandles()));
{
ObjectSnap snap;
CHECK(!snap.eq(ph1, ch1(c)));
CHECK(!snap.eq(ph2, ch2(c)));
CHECK(!snap.eq(ph3, ch3(c)));
CHECK(!snap.eq(ph4, ch4(c)));
}
}
}
if (!isAtLeastWin8()) {
// Make a final valiant effort to find a
// PROC_THREAD_ATTRIBUTE_HANDLE_LIST and console handle interaction.
// We'll set all the standard handles to pipes. Nevertheless, all
// console handles are inherited.
auto c = testSetupStdHandles({true, 0, {ph1, ph2, ph4}});
CHECK(c.valid());
CHECK(handleValues(c.scanForConsoleHandles()) ==
handleValues(p.scanForConsoleHandles()));
}
//
// What does it mean if the inherit list has a NULL handle in it?
//
{
// CreateProcess apparently succeeds if the inherit list has a single
// NULL in it. Inheritable handles unrelated to standard handles are
// not inherited.
auto c = testSetup(p, {true}, {NULL});
CHECK(c.valid());
// None of the inheritable handles were inherited.
ObjectSnap snap;
CHECK(!snap.eq(ph1, ch1(c)));
CHECK(!snap.eq(ph2, ch2(c)));
}
{
// {NULL, a handle} ==> nothing is inherited.
auto c = testSetup(p, {true}, {NULL, ph2.value()});
CHECK(c.valid());
ObjectSnap snap;
CHECK(!snap.eq(ph1, ch1(c)));
CHECK(!snap.eq(ph2, ch2(c)));
}
{
// {a handle, NULL} ==> nothing is inherited. (Apparently a NULL
// anywhere in the list means "inherit nothing"? The attribute is not
// ignored.)
auto c = testSetup(p, {true}, {ph1.value(), NULL});
CHECK(c.valid());
ObjectSnap snap;
CHECK(!snap.eq(ph1, ch1(c)));
CHECK(!snap.eq(ph2, ch2(c)));
}
{
// bInheritHandles=FALSE still fails.
auto c = testSetup(p, {false}, {NULL});
CHECK(!c.valid());
CHECK_EQ(failure.kind, SpawnFailure::CreateProcess);
CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER);
}
{
// Test whether inheritList={NULL} has an unexpected effect on the
// standard handles. Everything seems consistent.
auto q = testSetup(p, {true}, {ph1.value(), ph2.value()});
ch1(q).setStdin();
ch2(q).setStdout().setStderr();
auto c = testSetup(q, {true}, {NULL});
ObjectSnap snap;
if (isAtLeastWin8()) {
// In Windows 8, standard handles are duplicated if an inherit
// list is specified.
CHECK(snap.eq({c.getStdin(), q.getStdin(), ch1(q)}));
CHECK(snap.eq({c.getStdout(), q.getStdout(), ch2(q)}));
CHECK(snap.eq({c.getStderr(), q.getStderr(), ch2(q)}));
CHECK(c.getStdout().value() != c.getStderr().value());
CHECK(c.getStdin().tryFlags() && c.getStdin().inheritable());
CHECK(c.getStdout().tryFlags() && c.getStdout().inheritable());
CHECK(c.getStderr().tryFlags() && c.getStderr().inheritable());
} else {
// The standard handles were not successfully inherited.
CHECK(handleValues(stdHandles(c)) == handleValues(stdHandles(q)));
CHECK(!snap.eq(ch1(c), ch1(q)));
CHECK(!snap.eq(ch2(c), ch2(q)));
}
}
}
REGISTER(Test_CreateProcess_InheritList_StdHandles, isAtLeastVista);
static void Test_CreateProcess_InheritList_StdHandles() {
// List one of the standard handles in the inherit list, and see what
// happens to the standard list.
auto check = [](Worker &p, RemoteHandle rh, RemoteHandle wh) {
ASSERT(!rh.isTraditionalConsole());
ASSERT(!wh.isTraditionalConsole());
{
// Test bInheritHandles=TRUE, STARTF_USESTDHANDLES, and the
// PROC_THREAD_ATTRIBUTE_HANDLE_LIST attribute. Verify that the
// standard handles are set to handles whose inheritability was
// suppressed.
SpawnParams sp { true, EXTENDED_STARTUPINFO_PRESENT, {rh, wh, wh} };
sp.sui.cb = sizeof(STARTUPINFOEXW);
sp.inheritCount = 1;
sp.inheritList = { wh.value() };
auto c = p.child(sp);
ObjectSnap snap;
CHECK(handleValues(stdHandles(c)) ==
handleValues(std::vector<RemoteHandle> {rh, wh, wh}));
CHECK(!snap.eq(rh, c.getStdin()));
CHECK(snap.eq(wh, c.getStdout()));
CHECK(snap.eq(wh, c.getStderr()));
}
{
// Same as above, but use a single NULL in the inherit list. Now
// none of the handles are inherited, but the standard values are
// unchanged.
SpawnParams sp { true, EXTENDED_STARTUPINFO_PRESENT, {rh, wh, wh} };
sp.sui.cb = sizeof(STARTUPINFOEXW);
sp.inheritCount = 1;
sp.inheritList = { NULL };
auto c = p.child(sp);
ObjectSnap snap;
CHECK(handleValues(stdHandles(c)) ==
handleValues(std::vector<RemoteHandle> {rh, wh, wh}));
CHECK(!snap.eq(rh, c.getStdin()));
CHECK(!snap.eq(wh, c.getStdout()));
CHECK(!snap.eq(wh, c.getStderr()));
}
if (!isAtLeastWin8()) {
// Same as above, but avoid STARTF_USESTDHANDLES this time. The
// behavior changed with Windows 8, which now appears to duplicate
// handles in this case.
rh.setStdin();
wh.setStdout().setStderr();
SpawnParams sp { true, EXTENDED_STARTUPINFO_PRESENT };
sp.sui.cb = sizeof(STARTUPINFOEXW);
sp.inheritCount = 1;
sp.inheritList = { wh.value() };
auto c = p.child(sp);
ObjectSnap snap;
CHECK(handleValues(stdHandles(p)) == handleValues(stdHandles(c)));
CHECK(!snap.eq(p.getStdin(), c.getStdin()));
CHECK(snap.eq(p.getStdout(), c.getStdout()));
}
};
{
Worker p;
auto pipe = newPipe(p, true);
check(p, std::get<0>(pipe), std::get<1>(pipe));
}
if (isModernConio()) {
Worker p;
check(p, p.openConin(true), p.openConout(true));
}
}
REGISTER(Test_CreateProcess_InheritList_ModernDuplication, isAtLeastVista);
static void Test_CreateProcess_InheritList_ModernDuplication() {
auto &hv = handleValues;
for (int useDummyPipe = 0; useDummyPipe <= 1; ++useDummyPipe) {
// Once we've specified an inherit list, non-inheritable standard
// handles are duplicated.
Worker p;
auto pipe = newPipe(p);
auto rh = std::get<0>(pipe).setStdin();
auto wh = std::get<1>(pipe).setStdout().setStderr();
auto c = childWithDummyInheritList(p, {}, useDummyPipe != 0);
if (isModernConio()) {
ObjectSnap snap;
CHECK(snap.eq(rh, c.getStdin()));
CHECK(snap.eq(wh, c.getStdout()));
CHECK(snap.eq(wh, c.getStderr()));
CHECK(c.getStdout().value() != c.getStderr().value());
for (auto h : stdHandles(c)) {
CHECK(!h.inheritable());
}
} else {
CHECK(hv(stdHandles(c)) == hv(stdHandles(p)));
CHECK(!c.getStdin().tryFlags());
CHECK(!c.getStdout().tryFlags());
CHECK(!c.getStderr().tryFlags());
}
}
for (int useDummyPipe = 0; useDummyPipe <= 1; ++useDummyPipe) {
// Invalid handles are translated to 0x0. (For full details, see the
// "duplicate" CreateProcess tests.)
Worker p;
Handle::invent(0x0ull, p).setStdin();
Handle::invent(0xdeadbeefull, p).setStdout();
auto c = childWithDummyInheritList(p, {}, useDummyPipe != 0);
if (isModernConio()) {
CHECK(c.getStdin().uvalue() == 0ull);
CHECK(c.getStdout().uvalue() == 0ull);
} else {
CHECK(c.getStdin().uvalue() == 0ull);
CHECK(c.getStdout().value() ==
Handle::invent(0xdeadbeefull, c).value());
}
}
}
REGISTER(Test_CreateProcess_Duplicate_StdHandles, isModernConio);
static void Test_CreateProcess_Duplicate_StdHandles() {
// The default Unbound console handles should be inheritable, so with
// bInheritHandles=TRUE and standard handles listed in the inherit list,
// the child process should have six console handles, all usable.
Worker p;
SpawnParams sp { true, EXTENDED_STARTUPINFO_PRESENT };
sp.sui.cb = sizeof(STARTUPINFOEXW);
sp.inheritCount = 3;
sp.inheritList = {
p.getStdin().value(),
p.getStdout().value(),
p.getStderr().value(),
};
auto c = p.child(sp);
std::vector<uint64_t> expected;
extendVector(expected, handleInts(stdHandles(p)));
extendVector(expected, handleInts(stdHandles(c)));
std::sort(expected.begin(), expected.end());
auto correct = handleInts(c.scanForConsoleHandles());
std::sort(correct.begin(), correct.end());
p.dumpConsoleHandles();
c.dumpConsoleHandles();
CHECK(expected == correct);
}

View File

@ -1,53 +0,0 @@
#include <TestCommon.h>
//
// Test CreateProcess when called with these parameters:
// - STARTF_USESTDHANDLES is not specified
// - bInheritHandles=FALSE or bInheritHandles=TRUE
// - CreationConsoleMode=NewConsole
//
REGISTER(Test_CreateProcess_NewConsole, always);
static void Test_CreateProcess_NewConsole() {
auto check = [](Worker &p, bool inheritHandles) {
auto c = p.child({ inheritHandles, Worker::defaultCreationFlags() });
if (isTraditionalConio()) {
checkInitConsoleHandleSet(c);
CHECK(handleInts(stdHandles(c)) ==
(std::vector<uint64_t> {0x3, 0x7, 0xb}));
} else {
checkModernConsoleHandleInit(c, true, true, true);
}
return c;
};
{
Worker p;
check(p, true);
check(p, false);
}
{
Worker p;
p.openConin(false).setStdin();
p.newBuffer(false).setStdout().dup(true).setStderr();
check(p, true);
check(p, false);
}
if (isModernConio()) {
// The default Unbound console handles should be inheritable, so with
// bInheritHandles=TRUE, the child process should have six console
// handles, all usable.
Worker p;
auto c = check(p, true);
std::vector<uint64_t> expected;
extendVector(expected, handleInts(stdHandles(p)));
extendVector(expected, handleInts(stdHandles(c)));
std::sort(expected.begin(), expected.end());
auto correct = handleInts(c.scanForConsoleHandles());
std::sort(correct.begin(), correct.end());
CHECK(expected == correct);
}
}

View File

@ -1,190 +0,0 @@
#include <TestCommon.h>
//
// Test CreateProcess when called with these parameters:
// - STARTF_USESTDHANDLES is specified
// - bInheritHandles=FALSE or bInheritHandles=TRUE
// - CreationConsoleMode=NewConsole or CreationConsoleMode=Inherit
//
// Verify:
// - The resulting traditional ConsoleHandleSet is correct.
// - Windows 8 creates Unbound handles when appropriate.
// - Standard handles are set correctly.
//
// Before Windows 8, the child process has the standard handles specified
// in STARTUPINFO, without exception. Starting with Windows 8, the STARTUPINFO
// handles are ignored with bInheritHandles=FALSE, and even with
// bInheritHandles=TRUE, a NULL hStd{Input,Output,Error} field is translated to
// a new open handle if a new console is being created.
template <typename T>
void checkVariousInputs(T check) {
{
// Specify the original std values. With CreationConsoleMode==Inherit
// and bInheritHandles=FALSE, this code used to work (i.e. produce
// valid standard handles in the child). As of Windows 8, the standard
// handles are now NULL instead.
Worker p;
check(p, stdHandles(p));
}
{
Worker p;
check(p, {
p.getStdin().dup(),
p.getStdout().dup(),
p.getStderr().dup(),
});
}
{
Worker p;
check(p, {
p.getStdin().dup(true),
p.getStdout().dup(true),
p.getStderr().dup(true),
});
}
{
Worker p;
check(p, {
p.openConin(),
p.openConout(),
p.openConout(),
});
}
{
Worker p;
check(p, {
p.openConin(true),
p.openConout(true),
p.openConout(true),
});
}
{
// Invalid handles.
Worker p;
check(p, {
Handle::invent(nullptr, p),
Handle::invent(0x10000ull, p),
Handle::invent(0xdeadbeecull, p),
});
check(p, {
Handle::invent(INVALID_HANDLE_VALUE, p),
Handle::invent(nullptr, p),
Handle::invent(nullptr, p),
});
check(p, {
Handle::invent(nullptr, p),
Handle::invent(nullptr, p),
Handle::invent(nullptr, p),
});
}
{
// Try a non-inheritable pipe.
Worker p;
auto pipe = newPipe(p, false);
check(p, {
std::get<0>(pipe),
std::get<1>(pipe),
std::get<1>(pipe),
});
}
{
// Try an inheritable pipe.
Worker p;
auto pipe = newPipe(p, true);
check(p, {
std::get<0>(pipe),
std::get<1>(pipe),
std::get<1>(pipe),
});
}
}
REGISTER(Test_CreateProcess_UseStdHandles, always);
static void Test_CreateProcess_UseStdHandles() {
checkVariousInputs([](Worker &p, std::vector<Handle> newHandles) {
ASSERT(newHandles.size() == 3);
auto check = [&](Worker &c, bool inheritHandles, bool newConsole) {
trace("Test_CreateProcess_UseStdHandles: "
"inheritHandles=%d newConsole=%d",
inheritHandles, newConsole);
auto childHandles = stdHandles(c);
if (isTraditionalConio()) {
CHECK(handleValues(stdHandles(c)) == handleValues(newHandles));
if (newConsole) {
checkInitConsoleHandleSet(c);
} else {
checkInitConsoleHandleSet(c, p);
}
// The child handles have the same values as the parent.
// Verify that the child standard handles point to the right
// kernel objects.
ObjectSnap snap;
for (int i = 0; i < 3; ++i) {
if (newHandles[i].value() == nullptr ||
newHandles[i].value() == INVALID_HANDLE_VALUE) {
// Nothing to check.
} else if (newHandles[i].isTraditionalConsole()) {
// Everything interesting was already checked in
// checkInitConsoleHandleSet.
} else if (newHandles[i].tryFlags()) {
// A handle is not inherited simply because it is
// listed in STARTUPINFO. The new child standard
// handle is valid iff:
// - the parent handle was valid, AND
// - the parent handle was inheritable, AND
// - bInheritHandles is TRUE
//
// The logic below is not obviously true for all
// possible handle values, but it will work for all
// values we test for. (i.e. There could be some
// handle H to object O that isn't inherited, but by
// sheer conincidence, the child gets a handle H that
// also refers to O. (e.g. Windows internal objects.)
// This test case works because we know that Windows
// won't create a reference to our test objects.)
CHECK(snap.eq(newHandles[i], childHandles[i]) ==
(inheritHandles && newHandles[i].inheritable()));
}
}
} else {
ObjectSnap snap;
bool consoleOpened[3] = {false, false, false};
for (int i = 0; i < 3; ++i) {
if (inheritHandles && newHandles[i].value() != nullptr) {
// The parent's standard handle is used, without
// validation or duplication. It is not inherited
// simply because it is listed in STARTUPINFO.
CHECK(childHandles[i].value() ==
newHandles[i].value());
if (newHandles[i].value() == INVALID_HANDLE_VALUE) {
// The test below does not work on the current
// process pseudo-handle (aka
// INVALID_HANDLE_VALUE).
} else if (newHandles[i].tryFlags()) {
CHECK(snap.eq(newHandles[i], childHandles[i]) ==
newHandles[i].inheritable());
}
} else if (newConsole) {
consoleOpened[i] = true;
} else {
CHECK(childHandles[i].value() == nullptr);
}
}
checkModernConsoleHandleInit(c,
consoleOpened[0],
consoleOpened[1],
consoleOpened[2]);
}
};
for (int inheritInt = 0; inheritInt <= 1; ++inheritInt) {
const bool inherit = inheritInt != 0;
auto c1 = p.child({inherit, 0, newHandles});
check(c1, inherit, false);
auto c2 = p.child(
{inherit, Worker::defaultCreationFlags(), newHandles});
check(c2, inherit, true);
}
});
}

View File

@ -1,223 +0,0 @@
#include <TestCommon.h>
REGISTER(Test_CompareObjectHandles, always);
static void Test_CompareObjectHandles() {
// Verify that compareObjectHandles and ObjectSnap are working.
Worker p;
Worker other;
auto pipe1 = newPipe(p, true);
auto ph1 = std::get<0>(pipe1);
auto ph2 = std::get<1>(pipe1);
auto ph1dup = ph1.dup();
auto ph1other = ph1.dup(other);
ObjectSnap snap;
CHECK(!compareObjectHandles(ph1, ph2));
CHECK(compareObjectHandles(ph1, ph1dup));
CHECK(compareObjectHandles(ph1, ph1other));
CHECK(!snap.eq(ph1, ph2));
CHECK(snap.eq(ph1, ph1dup));
CHECK(snap.eq(ph1, ph1other));
CHECK(snap.eq({ ph1, ph1other, ph1dup }));
CHECK(!snap.eq({ ph2, ph1, ph1other, ph1dup }));
CHECK(!snap.eq({ ph1, ph2, ph1other, ph1dup }));
CHECK(!snap.eq({ ph1, ph1other, ph2, ph1dup }));
CHECK(!snap.eq({ ph1, ph1other, ph1dup, ph2 }));
}
REGISTER(Test_IntrinsicInheritFlags, always);
static void Test_IntrinsicInheritFlags() {
// Console handles have an inherit flag, just as kernel handles do.
//
// In Windows 7, there is a bug where DuplicateHandle(h, FALSE) makes the
// new handle inheritable if the old handle was inheritable.
Worker p;
auto n = p.newBuffer(FALSE);
auto y = p.newBuffer(TRUE);
auto nn = n.dup(FALSE);
auto yn = y.dup(FALSE);
auto ny = n.dup(TRUE);
auto yy = y.dup(TRUE);
p.dumpConsoleHandles();
CHECK(n.inheritable() == false);
CHECK(nn.inheritable() == false);
CHECK(yn.inheritable() == isWin7());
CHECK(y.inheritable() == true);
CHECK(ny.inheritable() == true);
CHECK(yy.inheritable() == true);
for (auto &h : (Handle[]){ n, y, nn, ny, yn, yy }) {
const bool v = h.inheritable();
if (isWin7()) {
// In Windows 7, the console handle inherit flags could not be
// changed.
CHECK(h.trySetInheritable(v) == false);
CHECK(h.trySetInheritable(!v) == false);
CHECK(h.inheritable() == v);
} else {
// With older and newer operating systems, the inheritability can
// be changed. (In newer operating systems, i.e. Windows 8 and up,
// the console handles are just normal kernel handles.)
CHECK(h.trySetInheritable(!v) == true);
CHECK(h.inheritable() == !v);
}
}
p.dumpConsoleHandles();
// For sanity's sake, check that DuplicateHandle(h, FALSE) does the right
// thing with an inheritable pipe handle, even on Windows 7.
auto pipeY = std::get<0>(newPipe(p, TRUE));
auto pipeN = pipeY.dup(FALSE);
CHECK(pipeY.inheritable() == true);
CHECK(pipeN.inheritable() == false);
}
REGISTER(Test_Input_Vs_Output, always);
static void Test_Input_Vs_Output() {
// Ensure that APIs meant for the other kind of handle fail.
Worker p;
CHECK(!p.getStdin().tryScreenBufferInfo());
CHECK(!p.getStdout().tryNumberOfConsoleInputEvents());
}
REGISTER(Test_Detach_Does_Not_Change_Standard_Handles, always);
static void Test_Detach_Does_Not_Change_Standard_Handles() {
// Detaching the current console does not affect the standard handles.
auto check = [](Worker &p) {
auto handles1 = handleValues(stdHandles(p));
p.detach();
auto handles2 = handleValues(stdHandles(p));
CHECK(handles1 == handles2);
};
// Simplest form of the test.
{
Worker p1;
check(p1);
}
// Also do a test with duplicated handles, just in case detaching resets
// the handles to their defaults.
{
Worker p2;
p2.getStdin().dup(TRUE).setStdin();
p2.getStdout().dup(TRUE).setStdout();
p2.getStderr().dup(TRUE).setStderr();
check(p2);
}
// Do another test with STARTF_USESTDHANDLES, just in case detaching resets
// to the hStd{Input,Output,Error} values.
{
Worker p3;
auto pipe = newPipe(p3, true);
auto rh = std::get<0>(pipe);
auto wh = std::get<1>(pipe);
auto p3c = p3.child({true, 0, {rh, wh, wh.dup(true)}});
check(p3c);
}
}
REGISTER(Test_Activate_Does_Not_Change_Standard_Handles, always);
static void Test_Activate_Does_Not_Change_Standard_Handles() {
// SetConsoleActiveScreenBuffer does not change the standard handles.
// MSDN documents this fact on "Console Handles"[1]
//
// "Note that changing the active screen buffer does not affect the
// handle returned by GetStdHandle. Similarly, using SetStdHandle to
// change the STDOUT handle does not affect the active screen buffer."
//
// [1] https://msdn.microsoft.com/en-us/library/windows/desktop/ms682075.aspx
Worker p;
auto handles1 = handleValues(stdHandles(p));
p.newBuffer(TRUE).activate();
auto handles2 = handleValues(stdHandles(p));
CHECK(handles1 == handles2);
}
REGISTER(Test_Active_ScreenBuffer_Order, always);
static void Test_Active_ScreenBuffer_Order() {
// SetActiveConsoleScreenBuffer does not increase a refcount on the
// screen buffer. Instead, when the active screen buffer's refcount hits
// zero, Windows activates the most-recently-activated buffer.
auto firstChar = [](Worker &p) {
auto h = p.openConout();
auto ret = h.firstChar();
h.close();
return ret;
};
{
// Simplest test
Worker p;
p.getStdout().setFirstChar('a');
auto h = p.newBuffer(false, 'b').activate();
h.close();
CHECK_EQ(firstChar(p), 'a');
}
{
// a -> b -> c -> b -> a
Worker p;
p.getStdout().setFirstChar('a');
auto b = p.newBuffer(false, 'b').activate();
auto c = p.newBuffer(false, 'c').activate();
c.close();
CHECK_EQ(firstChar(p), 'b');
b.close();
CHECK_EQ(firstChar(p), 'a');
}
{
// a -> b -> c -> b -> c -> a
Worker p;
p.getStdout().setFirstChar('a');
auto b = p.newBuffer(false, 'b').activate();
auto c = p.newBuffer(false, 'c').activate();
b.activate();
b.close();
CHECK_EQ(firstChar(p), 'c');
c.close();
CHECK_EQ(firstChar(p), 'a');
}
}
REGISTER(Test_GetStdHandle_SetStdHandle, always);
static void Test_GetStdHandle_SetStdHandle() {
// A commenter on the Old New Thing blog suggested that
// GetStdHandle/SetStdHandle could have internally used CloseHandle and/or
// DuplicateHandle, which would have changed the resource management
// obligations of the callers to those APIs. In fact, the APIs are just
// simple wrappers around global variables. Try to write tests for this
// fact.
//
// http://blogs.msdn.com/b/oldnewthing/archive/2013/03/07/10399690.aspx#10400489
auto &hv = handleValues;
{
// Set values and read them back. We get the same handles.
Worker p;
auto pipe = newPipe(p);
auto rh = std::get<0>(pipe);
auto wh1 = std::get<1>(pipe);
auto wh2 = std::get<1>(pipe).dup();
setStdHandles({ rh, wh1, wh2 });
CHECK(hv(stdHandles(p)) == hv({ rh, wh1, wh2}));
// Call again, and we still get the same handles.
CHECK(hv(stdHandles(p)) == hv({ rh, wh1, wh2}));
}
{
Worker p;
p.getStdout().setFirstChar('a');
p.newBuffer(false, 'b').activate().setStdout().dup().setStderr();
std::get<1>(newPipe(p)).setStdout().dup().setStderr();
// SetStdHandle doesn't close its previous handle when it's given a new
// handle. Therefore, the two handles given to SetStdHandle for STDOUT
// and STDERR are still open, and the new screen buffer is still
// active.
CHECK_EQ(p.openConout().firstChar(), 'b');
}
}

View File

@ -1,240 +0,0 @@
#include <TestCommon.h>
REGISTER(Test_AttachConsole_AllocConsole_StdHandles, isModernConio);
static void Test_AttachConsole_AllocConsole_StdHandles() {
// Verify that AttachConsole does the right thing w.r.t. console handle
// sets and standard handles.
auto check = [](bool newConsole, bool useStdHandles, int nullIndex) {
trace("checking: newConsole=%d useStdHandles=%d nullIndex=%d",
newConsole, useStdHandles, nullIndex);
Worker p;
SpawnParams sp = useStdHandles
? SpawnParams { true, 0, stdHandles(p) }
: SpawnParams { false, 0 };
auto c = p.child(sp);
auto pipe = newPipe(c, true);
std::get<0>(pipe).setStdin();
std::get<1>(pipe).setStdout().setStdout();
if (nullIndex == 0) {
Handle::invent(nullptr, c).setStdin();
} else if (nullIndex == 1) {
Handle::invent(nullptr, c).setStdout();
} else if (nullIndex == 2) {
Handle::invent(nullptr, c).setStderr();
}
auto origStdHandles = stdHandles(c);
c.detach();
CHECK(handleValues(stdHandles(c)) == handleValues(origStdHandles));
if (newConsole) {
c.alloc();
} else {
Worker other;
c.attach(other);
}
if (useStdHandles) {
auto curHandles = stdHandles(c);
for (int i = 0; i < 3; ++i) {
if (i != nullIndex) {
CHECK(curHandles[i].value() == origStdHandles[i].value());
}
}
checkModernConsoleHandleInit(c,
nullIndex == 0,
nullIndex == 1,
nullIndex == 2);
} else {
checkModernConsoleHandleInit(c, true, true, true);
}
};
for (int i = -1; i < 3; ++i) {
check(false, false, i);
check(false, true, i);
check(true, false, i);
check(true, true, i);
}
}
REGISTER(Test_Unbound_vs_Bound, isModernConio);
static void Test_Unbound_vs_Bound() {
{
// An Unbound output handle refers to the initial buffer.
Worker p;
auto ob = p.getStdout().setFirstChar('O');
p.newBuffer(true, 'N').activate().setStdout().setStderr();
CHECK_EQ(ob.firstChar(), 'O');
// The handle can come from another process.
Worker p2;
CHECK_EQ(p2.getStdout().dup(p).firstChar(), 'O');
// CONOUT$ will use the new buffer, though.
CHECK_EQ(p.openConout().firstChar(), 'N');
}
{
// A Bound handle from another process does not work.
Worker wa;
Worker wb;
wa.getStdout().setFirstChar('a');
wb.getStdout().setFirstChar('b');
auto a_b = wb.openConout().dup(wa);
auto a_c = wb.newBuffer(false, 'c').dup(wa);
CHECK(a_b.tryFlags());
CHECK(a_c.tryFlags());
CHECK(!a_b.tryScreenBufferInfo());
CHECK(!a_c.tryScreenBufferInfo());
// We can *make* them work, though, if we reattach p to p2's console.
wa.detach();
CHECK(a_b.tryFlags() && a_c.tryFlags());
wa.attach(wb);
CHECK(a_b.tryScreenBufferInfo() && a_b.firstChar() == 'b');
CHECK(a_c.tryScreenBufferInfo() && a_c.firstChar() == 'c');
}
}
REGISTER(Test_Console_Without_Processes, isModernConio);
static void Test_Console_Without_Processes() {
auto waitForTitle = [](HWND hwnd, const std::string &title) {
for (int i = 0; i < 100 && (windowText(hwnd) != title); ++i) {
Sleep(20);
}
};
auto waitForNotTitle = [](HWND hwnd, const std::string &title) {
for (int i = 0; i < 100 && (windowText(hwnd) == title); ++i) {
Sleep(20);
}
};
{
// It is possible to have a console with no attached process. Verify
// that the console window has the expected title even after its only
// process detaches. The window dies once the duplicated Bound handle
// is closed.
Worker p({ false, CREATE_NEW_CONSOLE });
auto bound = p.openConout();
auto hwnd = p.consoleWindow();
auto title = makeTempName(__FUNCTION__);
p.setTitle(title);
waitForTitle(hwnd, title);
p.detach();
Sleep(200);
CHECK_EQ(windowText(hwnd), title);
bound.close();
waitForNotTitle(hwnd, title);
CHECK(windowText(hwnd) != title);
}
}
REGISTER(Test_Implicit_Buffer_Reference, isModernConio);
static void Test_Implicit_Buffer_Reference() {
// Test that a process attached to a console holds an implicit reference
// to the screen buffer that was active at attachment.
auto activeFirstChar = [](Worker &proc) {
auto b = proc.openConout();
auto ret = b.firstChar();
b.close();
return ret;
};
{
Worker p;
Worker p2({ false, DETACHED_PROCESS });
p.getStdout().setFirstChar('A');
auto b = p.newBuffer(false, 'B').activate();
auto pipe = newPipe(p, true);
// Spawn a child process that has no console handles open.
SpawnParams sp({ true, EXTENDED_STARTUPINFO_PRESENT, {
std::get<0>(pipe),
std::get<1>(pipe),
std::get<1>(pipe),
}});
sp.sui.cb = sizeof(STARTUPINFOEXW);
sp.inheritCount = 2;
sp.inheritList = {
std::get<0>(pipe).value(),
std::get<1>(pipe).value(),
};
auto c = p.child(sp);
CHECK_EQ(c.scanForConsoleHandles().size(), 0u);
// Now close the only open handle to the B buffer. The active
// buffer remains A, because the child implicitly references B.
b.close();
CHECK_EQ(activeFirstChar(p), 'B');
c.detach();
// Once the child detaches, B is freed, and A activates.
CHECK_EQ(activeFirstChar(p), 'A');
}
}
REGISTER(Test_FreeConsole_Closes_Handles, isModernConio);
static void Test_FreeConsole_Closes_Handles() {
auto check = [](Worker &proc, bool ineq, bool outeq, bool erreq) {
auto dupin = proc.getStdin().dup();
auto dupout = proc.getStdout().dup();
auto duperr = proc.getStderr().dup();
proc.detach();
ObjectSnap snap;
CHECK_EQ(snap.eq(proc.getStdin(), dupin), ineq);
CHECK_EQ(snap.eq(proc.getStdout(), dupout), outeq);
CHECK_EQ(snap.eq(proc.getStderr(), duperr), erreq);
dupin.close();
dupout.close();
duperr.close();
};
{
// The child opened three console handles, so FreeConsole closes all of
// them.
Worker p;
check(p, false, false, false);
}
{
// The child inherited the handles, so FreeConsole closes none of them.
Worker p;
auto c = p.child({ true });
check(c, true, true, true);
}
{
// Duplicated console handles: still none of them are closed.
Worker p;
auto c = p.child({ false });
check(c, true, true, true);
}
{
// FreeConsole doesn't close the current stdhandles; it closes the
// handles it opened at attach-time.
Worker p;
p.openConout().setStderr();
check(p, false, false, true);
}
{
// With UseStdHandles, handles aren't closed.
Worker p;
auto c = p.child({ true, 0, stdHandles(p) });
check(c, true, true, true);
}
{
// Using StdHandles, AllocConsole sometimes only opens a few handles.
// Only the handles it opens are closed.
Worker p({ false, DETACHED_PROCESS });
auto pipe = newPipe(p, true);
auto c = p.child({ true, DETACHED_PROCESS, {
std::get<0>(pipe),
std::get<1>(pipe),
std::get<1>(pipe),
}});
Handle::invent(0ull, c).setStderr();
c.alloc();
CHECK(c.getStdin().value() == std::get<0>(pipe).value());
CHECK(c.getStdout().value() == std::get<1>(pipe).value());
CHECK(c.getStderr().tryScreenBufferInfo());
check(c, true, true, false);
}
}

View File

@ -1,307 +0,0 @@
#include <TestCommon.h>
REGISTER(Test_HandleDuplication, isTraditionalConio);
static void Test_HandleDuplication() {
// A traditional console handle cannot be duplicated to another process,
// and it must be duplicated using the GetConsoleProcess() pseudo-value.
// (This tests targetProcess != psuedo-value, but it doesn't test
// sourceProcess != pseudo-value. Not worth the trouble.)
Worker p, other;
p.getStdout().setFirstChar('x');
CHECK_EQ(p.getStdout().dup().firstChar(), 'x');
CHECK_EQ(p.getStdout().dup(p).value(), INVALID_HANDLE_VALUE);
CHECK_EQ(p.getStdout().dup(other).value(), INVALID_HANDLE_VALUE);
}
REGISTER(Test_NewConsole_Resets_ConsoleHandleSet, isTraditionalConio);
static void Test_NewConsole_Resets_ConsoleHandleSet() {
// Test that creating a new console properly resets everything.
Worker p;
// Open some handles to demonstrate the "clean slate" outcome.
auto orig = stdHandles(p);
p.getStdin().dup(true).setStdin();
p.newBuffer(true).setStderr().dup(true).setStdout().activate();
for (auto &h : orig) {
h.close();
}
auto checkClean = [](Worker &proc) {
proc.dumpConsoleHandles();
CHECK_EQ(proc.getStdin().uvalue(), 0x3u);
CHECK_EQ(proc.getStdout().uvalue(), 0x7u);
CHECK_EQ(proc.getStderr().uvalue(), 0xbu);
auto handles = proc.scanForConsoleHandles();
CHECK(handleValues(handles) == (std::vector<HANDLE> {
proc.getStdin().value(),
proc.getStdout().value(),
proc.getStderr().value(),
}));
CHECK(allInheritable(handles));
};
// A child with a new console is reset to a blank slate.
for (int inherit = 0; inherit <= 1; ++inherit) {
auto c1 = p.child({ inherit != 0, CREATE_NEW_CONSOLE });
checkClean(c1);
auto c2 = p.child({ inherit != 0, CREATE_NO_WINDOW });
checkClean(c2);
// Starting a child from a DETACHED_PROCESS also produces a clean
// configuration.
Worker detachedParent({ false, DETACHED_PROCESS });
auto pipe = newPipe(detachedParent, true);
std::get<0>(pipe).setStdin();
std::get<1>(pipe).setStdout().dup(true).setStdout();
Worker c3 = detachedParent.child({ inherit != 0, 0 });
checkClean(c3);
}
// Similarly, detaching and allocating a new console resets the
// ConsoleHandleSet.
p.detach();
p.alloc();
checkClean(p);
}
REGISTER(Test_CreateProcess_DetachedProcess, isTraditionalConio);
static void Test_CreateProcess_DetachedProcess() {
// A child with DETACHED_PROCESS has no console, and its standard handles
// are set to 0 by default.
Worker p;
p.getStdin().dup(TRUE).setStdin();
p.getStdout().dup(TRUE).setStdout();
p.getStderr().dup(TRUE).setStderr();
auto c = p.child({ true, DETACHED_PROCESS });
CHECK(c.getStdin().uvalue() == 0);
CHECK(c.getStdout().uvalue() == 0);
CHECK(c.getStderr().uvalue() == 0);
CHECK(c.scanForConsoleHandles().empty());
CHECK(c.consoleWindow() == NULL);
// XXX: What do GetConsoleCP and GetConsoleOutputCP do when no console is attached?
// Verify that we have a blank slate even with an implicit console
// creation.
auto c2 = c.child({ true });
auto c2h = c2.scanForConsoleHandles();
CHECK(handleValues(c2h) == (std::vector<HANDLE> {
c2.getStdin().value(),
c2.getStdout().value(),
c2.getStderr().value(),
}));
}
REGISTER(Test_Creation_bInheritHandles_Flag, isTraditionalConio);
static void Test_Creation_bInheritHandles_Flag() {
// The bInheritHandles flags to CreateProcess has no effect on console
// handles.
Worker p;
for (auto &h : (Handle[]){
p.getStdin(),
p.getStdout(),
p.getStderr(),
p.newBuffer(false),
p.newBuffer(true),
}) {
h.dup(false);
h.dup(true);
}
auto cY = p.child({ true });
auto cN = p.child({ false });
auto &hv = handleValues;
CHECK(hv(cY.scanForConsoleHandles()) == hv(inheritableHandles(p.scanForConsoleHandles())));
CHECK(hv(cN.scanForConsoleHandles()) == hv(inheritableHandles(p.scanForConsoleHandles())));
}
REGISTER(Test_HandleAllocationOrder, isTraditionalConio);
static void Test_HandleAllocationOrder() {
// When a new handle is created, it always assumes the lowest unused value.
Worker p;
auto h3 = p.getStdin();
auto h7 = p.getStdout();
auto hb = p.getStderr();
auto hf = h7.dup(true);
auto h13 = h3.dup(true);
auto h17 = hb.dup(true);
CHECK(h3.uvalue() == 0x3);
CHECK(h7.uvalue() == 0x7);
CHECK(hb.uvalue() == 0xb);
CHECK(hf.uvalue() == 0xf);
CHECK(h13.uvalue() == 0x13);
CHECK(h17.uvalue() == 0x17);
hf.close();
h13.close();
h7.close();
h7 = h3.dup(true);
hf = h3.dup(true);
h13 = h3.dup(true);
auto h1b = h3.dup(true);
CHECK(h7.uvalue() == 0x7);
CHECK(hf.uvalue() == 0xf);
CHECK(h13.uvalue() == 0x13);
CHECK(h1b.uvalue() == 0x1b);
}
REGISTER(Test_InheritNothing, isTraditionalConio);
static void Test_InheritNothing() {
// It's possible for the standard handles to be non-inheritable.
//
// Avoid calling DuplicateHandle(h, FALSE), because it produces inheritable
// console handles on Windows 7.
Worker p;
auto conin = p.openConin();
auto conout = p.openConout();
p.getStdin().close();
p.getStdout().close();
p.getStderr().close();
conin.setStdin();
conout.setStdout().dup().setStderr();
p.dumpConsoleHandles();
auto c = p.child({ true });
// The child has no open console handles.
CHECK(c.scanForConsoleHandles().empty());
c.dumpConsoleHandles();
// The standard handle values are inherited, even though they're invalid.
CHECK(c.getStdin().value() == p.getStdin().value());
CHECK(c.getStdout().value() == p.getStdout().value());
CHECK(c.getStderr().value() == p.getStderr().value());
// Verify a console is attached.
CHECK(c.openConin().value() != INVALID_HANDLE_VALUE);
CHECK(c.openConout().value() != INVALID_HANDLE_VALUE);
CHECK(c.newBuffer().value() != INVALID_HANDLE_VALUE);
}
REGISTER(Test_AttachConsole_And_CreateProcess_Inheritance, isTraditionalConio);
static void Test_AttachConsole_And_CreateProcess_Inheritance() {
Worker p;
Worker unrelated({ false, DETACHED_PROCESS });
auto conin = p.getStdin().dup(true);
auto conout1 = p.getStdout().dup(true);
auto conout2 = p.getStderr().dup(true);
p.openConout(false); // an extra handle for checkInitConsoleHandleSet testing
p.openConout(true); // an extra handle for checkInitConsoleHandleSet testing
p.getStdin().close();
p.getStdout().close();
p.getStderr().close();
conin.setStdin();
conout1.setStdout();
conout2.setStderr();
auto c = p.child({ true });
auto c2 = c.child({ true });
c2.detach();
c2.attach(c);
unrelated.attach(p);
// The first child will have the same standard handles as the parent.
CHECK(c.getStdin().value() == p.getStdin().value());
CHECK(c.getStdout().value() == p.getStdout().value());
CHECK(c.getStderr().value() == p.getStderr().value());
// AttachConsole sets the handles to (0x3, 0x7, 0xb) regardless of handle
// validity. In this case, c2 initially had non-default handles, and it
// attached to a process that has and also initially had non-default
// handles. Nevertheless, the new standard handles are the defaults.
for (auto proc : {&c2, &unrelated}) {
CHECK(proc->getStdin().uvalue() == 0x3);
CHECK(proc->getStdout().uvalue() == 0x7);
CHECK(proc->getStderr().uvalue() == 0xb);
}
// The set of inheritable console handles in these processes exactly match
// that of the parent.
checkInitConsoleHandleSet(c, p);
checkInitConsoleHandleSet(c2, p);
checkInitConsoleHandleSet(unrelated, p);
}
REGISTER(Test_Detach_Implicitly_Closes_Handles, isTraditionalConio);
static void Test_Detach_Implicitly_Closes_Handles() {
// After detaching, calling GetHandleInformation fails on previous console
// handles.
Worker p;
Handle orig[] = {
p.getStdin(),
p.getStdout(),
p.getStderr(),
p.getStdin().dup(TRUE),
p.getStdout().dup(TRUE),
p.getStderr().dup(TRUE),
p.openConin(TRUE),
p.openConout(TRUE),
};
p.detach();
for (auto h : orig) {
CHECK(!h.tryFlags());
}
}
REGISTER(Test_AttachConsole_AllocConsole_StdHandles, isTraditionalConio);
static void Test_AttachConsole_AllocConsole_StdHandles() {
// Verify that AttachConsole does the right thing w.r.t. console handle
// sets and standard handles.
auto check = [](bool newConsole, bool useStdHandles) {
trace("checking: newConsole=%d useStdHandles=%d",
newConsole, useStdHandles);
Worker p;
SpawnParams sp = useStdHandles
? SpawnParams { true, 0, stdHandles(p) }
: SpawnParams { false, 0 };
p.openConout(false); // 0x0f
p.openConout(true); // 0x13
auto c = p.child(sp);
auto pipe = newPipe(c, true);
std::get<0>(pipe).setStdin();
std::get<1>(pipe).setStdout().setStdout();
auto origStdHandles = stdHandles(c);
c.detach();
CHECK(handleValues(stdHandles(c)) == handleValues(origStdHandles));
if (newConsole) {
c.alloc();
checkInitConsoleHandleSet(c);
} else {
Worker other;
auto out = other.newBuffer(true, 'N'); // 0x0f
other.openConin(false); // 0x13
auto in = other.openConin(true); // 0x17
out.activate(); // activate new buffer
other.getStdin().close(); // close 0x03
other.getStdout().close(); // close 0x07
other.getStderr().close(); // close 0x0b
in.setStdin(); // 0x17
out.setStdout().dup(true).setStderr(); // 0x0f and 0x1b
c.attach(other);
checkInitConsoleHandleSet(c, other);
}
if (useStdHandles) {
CHECK(handleValues(stdHandles(c)) == handleValues(origStdHandles));
} else {
CHECK(handleInts(stdHandles(c)) ==
(std::vector<uint64_t> { 0x3, 0x7, 0xb }));
}
};
check(false, false);
check(false, true);
check(true, false);
check(true, true);
}

View File

@ -1,117 +0,0 @@
#include <TestCommon.h>
// Test for the Windows 7 win7_conout_crash bug.
//
// See console-handle.md, #win7_conout_crash, for theory. Basically, if a
// process does not have a handle for a screen buffer, and it opens and closes
// CONOUT$, then the buffer is destroyed, even though another process is still
// using it. Closing the *other* handles crashes conhost.exe.
//
// The bug affects Windows 7 SP1, but does not affect
// Windows Server 2008 R2 SP1, the server version of the OS.
//
REGISTER(Win7_RefCount_Bug, always);
static void Win7_RefCount_Bug() {
{
// Simplest demonstration:
//
// We will have two screen buffers in this test, O and N. The parent opens
// CONOUT$ to access N, but when it closes its handle, N is freed,
// restoring O as the active buffer.
//
Worker p;
p.getStdout().setFirstChar('O');
auto c = p.child();
c.newBuffer(false, 'N').activate();
auto conout = p.openConout();
CHECK_EQ(conout.firstChar(), 'N');
conout.close();
// At this point, Win7 is broken. Test for it and hope we don't crash.
conout = p.openConout();
if (isWin7() && isWorkstation()) {
CHECK_EQ(conout.firstChar(), 'O');
} else {
CHECK_EQ(conout.firstChar(), 'N');
}
}
{
// We can still "close" the handle by first importing it to another
// process, then detaching that process from its console.
Worker p;
Worker assistant({ false, DETACHED_PROCESS });
p.getStdout().setFirstChar('O');
auto c = p.child();
c.newBuffer(false, 'N').activate();
// Do the read a few times for good measure.
for (int i = 0; i < 5; ++i) {
auto conout = p.openConout(true); // Must be inheritable!
CHECK_EQ(conout.firstChar(), 'N');
assistant.attach(p); // The attach imports the CONOUT$ handle
conout.close();
assistant.detach(); // Exiting would also work.
}
}
{
// If the child detaches, the screen buffer is still allocated. This
// demonstrates that the CONOUT$ handle *did* increment a refcount on
// the buffer.
Worker p;
p.getStdout().setFirstChar('O');
Worker c = p.child();
c.newBuffer(false, 'N').activate();
auto conout = p.openConout();
c.detach(); // The child must exit/detach *without* closing the handle.
CHECK_EQ(conout.firstChar(), 'N');
auto conout2 = p.openConout();
CHECK_EQ(conout2.firstChar(), 'N');
// It is now safe to close the handles. There is no other "console
// object" referencing the screen buffer.
conout.close();
conout2.close();
}
{
// If there are multiple console objects, closing any of them frees
// the screen buffer.
Worker p;
auto c1 = p.child();
auto c2 = p.child();
p.getStdout().setFirstChar('O');
p.newBuffer(false, 'N').activate();
auto ch1 = c1.openConout();
auto ch2 = c2.openConout();
CHECK_EQ(ch1.firstChar(), 'N');
CHECK_EQ(ch2.firstChar(), 'N');
ch1.close();
// At this point, Win7 is broken. Test for it and hope we don't crash.
auto testHandle = c1.openConout();
if (isWin7() && isWorkstation()) {
CHECK_EQ(testHandle.firstChar(), 'O');
} else {
CHECK_EQ(testHandle.firstChar(), 'N');
}
}
if (isTraditionalConio()) {
// Two processes can share a console object; in that case, CloseHandle
// does not immediately fail.
for (int i = 0; i < 2; ++i) {
Worker p1;
Worker p2 = p1.child();
Worker p3({false, DETACHED_PROCESS});
p1.getStdout().setFirstChar('O');
Worker observer = p1.child();
p1.newBuffer(false, 'N').activate();
auto objref1 = p2.openConout(true);
p3.attach(p2);
auto objref2 = Handle::invent(objref1.value(), p3);
if (i == 0) {
objref1.close();
} else {
objref2.close();
}
CHECK_EQ(observer.openConout().firstChar(), 'N');
}
}
}

View File

@ -1,33 +0,0 @@
#include <TestCommon.h>
int main() {
for (DWORD flags : {CREATE_NEW_CONSOLE, CREATE_NO_WINDOW}) {
if (flags == CREATE_NEW_CONSOLE) {
printTestName("Using CREATE_NEW_CONSOLE as default creation mode");
} else {
printTestName("Using CREATE_NO_WINDOW as default creation mode");
}
Worker::setDefaultCreationFlags(flags);
for (auto &test : registeredTests()) {
std::string name;
bool (*cond)();
void (*func)();
std::tie(name, cond, func) = test;
if (cond()) {
printTestName(name);
func();
}
}
}
std::cout << std::endl;
const auto failures = failedTests();
if (failures.empty()) {
std::cout << "All tests passed!" << std::endl;
} else {
std::cout << "Failed tests:" << std::endl;
for (auto name : failures) {
std::cout << " " << name << std::endl;
}
}
return 0;
}

View File

@ -1,112 +0,0 @@
include ../../config.mk
CXX = $(MINGW_CXX)
# Pass -Wno-format to disable format checking because gcc complains about
# %I64x. I can't use %lld because it's broken on MinGW-on-WinXP, though it
# works on later OSs. %I64x works everywhere with MinGW, at runtime. I
# need to find a better way to do this int-to-string conversion.
CXXFLAGS += \
-MMD \
-Wall \
-Wno-format \
-Iharness -I../../src/shared \
-std=c++11 \
-DUNICODE \
-D_UNICODE \
-D_WIN32_WINNT=0x0600
LDFLAGS += -static -static-libgcc -static-libstdc++
# To disable PCH, just comment out these two lines.
PCHFLAGS = -include build/obj/pch.h
PCHDEPS = build/obj/pch.h.gch
# Use gmake -n to see the command-lines gmake would run.
COMMON_OBJECTS = \
build/obj/harness/Event.o \
build/obj/harness/NtHandleQuery.o \
build/obj/harness/ShmemParcel.o \
build/obj/harness/Spawn.o \
build/obj/harness/UnicodeConversions.o \
build/obj/harness/Util.o \
build/obj/shared/DebugClient.o \
build/obj/shared/WinptyAssert.o \
build/obj/shared/winpty_wcsnlen.o
WORKER_OBJECTS = \
build/obj/harness/WorkerProgram.o
TEST_OBJECTS = \
build/obj/harness/RemoteHandle.o \
build/obj/harness/RemoteWorker.o \
build/obj/harness/TestUtil.o \
HANDLETESTS_OBJECTS = \
build/obj/HandleTests/CreateProcess.o \
build/obj/HandleTests/CreateProcess_Detached.o \
build/obj/HandleTests/CreateProcess_Duplicate.o \
build/obj/HandleTests/CreateProcess_Duplicate_PseudoHandleBug.o \
build/obj/HandleTests/CreateProcess_Duplicate_XPPipeBug.o \
build/obj/HandleTests/CreateProcess_InheritAllHandles.o \
build/obj/HandleTests/CreateProcess_InheritList.o \
build/obj/HandleTests/CreateProcess_NewConsole.o \
build/obj/HandleTests/CreateProcess_UseStdHandles.o \
build/obj/HandleTests/MiscTests.o \
build/obj/HandleTests/Modern.o \
build/obj/HandleTests/Traditional.o \
build/obj/HandleTests/Win7_Conout_Crash.o \
build/obj/HandleTests/main.o
include tests.mk
.PHONY : all
all : \
$(TESTS) \
build/Worker.exe \
build/HandleTests.exe \
build/HandleTests.exe.manifest
.PHONY : clean
clean:
rm -fr build
build/obj/pch.h.gch : harness/pch.h
@echo Compiling PCH $<
@mkdir -p $$(dirname $@)
@$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $<
build/obj/%.o : %.cc $(PCHDEPS)
@echo Compiling $<
@mkdir -p $$(dirname $@)
@$(CXX) $(PCHFLAGS) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $<
build/obj/shared/%.o : ../../src/shared/%.cc $(PCHDEPS)
@echo Compiling $<
@mkdir -p $$(dirname $@)
@$(CXX) $(PCHFLAGS) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $<
.PRECIOUS : build/obj/%.o
.PRECIOUS : build/obj/shared/%.o
build/Worker.exe : $(WORKER_OBJECTS) $(COMMON_OBJECTS)
@echo Linking $@
@$(CXX) -o $@ $^ $(LDFLAGS)
build/HandleTests.exe : $(HANDLETESTS_OBJECTS) $(TEST_OBJECTS) $(COMMON_OBJECTS)
@echo Linking $@
@$(CXX) -o $@ $^ $(LDFLAGS)
build/HandleTests.exe.manifest : manifest.xml
@Echo Copying $< to $@
@mkdir -p $$(dirname $@)
@cp $< $@
build/%.exe : build/obj/%.o $(TEST_OBJECTS) $(COMMON_OBJECTS)
@echo Linking $@
@$(CXX) -o $@ $^ $(LDFLAGS)
-include $(COMMON_OBJECTS:.o=.d)
-include $(WORKER_OBJECTS:.o=.d)
-include $(TEST_OBJECTS:.o=.d)
-include $(HANDLETESTS_OBJECTS:.o=.d)
-include build/obj/*.d

View File

@ -1,119 +0,0 @@
// Test GetConsoleTitleW.
//
// Each of these OS sets implements different semantics for the system call:
// * Windows XP
// * Vista and Windows 7
// * Windows 8 and up (at least to Windows 10)
//
#include <TestCommon.h>
static void checkBuf(const std::array<wchar_t, 1024> &actual,
const std::array<wchar_t, 1024> &expected,
const char *filename,
int line) {
if (actual != expected) {
for (size_t i = 0; i < actual.size(); ++i) {
if (actual[i] != expected[i]) {
std::cout << filename << ":" << line << ": "
<< "char mismatch: [" << i << "]: "
<< actual[i] << " != " << expected[i]
<< " ('" << static_cast<char>(actual[i]) << "' != '"
<< static_cast<char>(expected[i]) << "')"
<< std::endl;
}
}
}
}
#define CHECK_BUF(actual, ...) (checkBuf((actual), __VA_ARGS__, __FILE__, __LINE__))
int main() {
Worker w;
std::array<wchar_t, 1024> readBuf;
const std::wstring kNul = std::wstring(L"", 1);
const std::array<wchar_t, 1024> kJunk = {
'1', '2', '3', '4', '5', '6', '7', '8',
'9', '0', 'A', 'B', 'C', 'D', 'E', 'F',
};
for (auto inputStr : {
std::wstring(L""),
std::wstring(L"a"),
std::wstring(L"ab"),
std::wstring(L"abc"),
std::wstring(L"abcd"),
std::wstring(L"abcde"),
}) {
for (size_t readLen = 0; readLen < 12; ++readLen) {
std::cout << "Testing \"" << narrowString(inputStr) << "\", "
<< "reading " << readLen << " chars" << std::endl;
// Set the title and read it back.
w.setTitle(narrowString(inputStr));
readBuf = kJunk;
const DWORD retVal = w.titleInternal(readBuf, readLen);
if (readLen == 0) {
// When passing a buffer size 0, the API returns 0 and leaves
// the buffer untouched. Every OS version does the same thing.
CHECK_EQ(retVal, 0u);
CHECK_BUF(readBuf, kJunk);
continue;
}
std::wstring expectedWrite;
if (isAtLeastWin8()) {
expectedWrite = inputStr.substr(0, readLen - 1) + kNul;
// The call returns the untruncated length.
CHECK_EQ(retVal, inputStr.size());
}
else if (isAtLeastVista()) {
// Vista and Windows 7 have a bug where the title is instead
// truncated to half the correct number of characters. (i.e.
// The `readlen` is seemingly interpreted as a byte count
// rather than a character count.) The bug isn't present on XP
// or Windows 8.
if (readLen == 1) {
// There is not even room for a NUL terminator, so it's
// just left off. The call still succeeds, though.
expectedWrite = std::wstring();
} else {
expectedWrite =
inputStr.substr(0, (readLen / 2) - 1) + kNul;
}
// The call returns the untruncated length.
CHECK_EQ(retVal, inputStr.size());
}
else {
// Unlike later OSs, XP returns a truncated title length.
// Moreover, whenever it would return 0, either because:
// * the title is blank, and/or
// * the read length is 1
// then XP does not NUL-terminate the buffer.
const size_t truncatedLen = std::min(inputStr.size(), readLen - 1);
if (truncatedLen == 0) {
expectedWrite = std::wstring();
} else {
expectedWrite = inputStr.substr(0, truncatedLen) + kNul;
}
CHECK_EQ(retVal, truncatedLen);
}
// I will assume that remaining characters have undefined values,
// but I suspect they're actually unchanged. On the other hand,
// the API must never modify the bytes beyond `readLen`.
auto expected = kJunk;
std::copy(&readBuf[0], &readBuf[readLen], expected.begin());
std::copy(expectedWrite.begin(), expectedWrite.end(), expected.begin());
CHECK_BUF(readBuf, expected);
}
}
return 0;
}

View File

@ -1,32 +0,0 @@
#include <TestCommon.h>
int main() {
trace("----------------------------------");
Worker p;
p.getStdout().write("<-- origBuffer -->");
auto c = p.child();
auto cb = c.newBuffer(FALSE);
cb.activate();
cb.write("<-- cb -->");
c.dumpConsoleHandles(TRUE);
// Proposed fix: the agent somehow decides it should attach to this
// particular child process. Does that fix the problem?
//
// No, because the child's new buffer was not marked inheritable. If it
// were inheritable, then the parent would "inherit" the handle during
// attach, and both processes would use the same refcount for
// `CloseHandle`.
p.detach();
p.attach(c);
p.dumpConsoleHandles(TRUE);
auto pb = p.openConout();
cb.close();
// Demonstrate that pb is an invalid handle.
pb.close();
Sleep(300000);
}

View File

@ -1,56 +0,0 @@
#include <TestCommon.h>
const int SC_CONSOLE_MARK = 0xFFF2;
const int SC_CONSOLE_SELECT_ALL = 0xFFF5;
int main() {
SpawnParams sp;
sp.bInheritHandles = TRUE;
trace("----------------------------------");
Worker p;
p.getStdout().write("<-- origBuffer -->");
auto c = p.child();
auto cb = c.newBuffer();
cb.activate();
cb.write("<-- cb -->");
// This is what the winpty-agent would want to do:
// - It tries to "freeze" the console with "Select All", which blocks
// WriteConsole but little else. Closing a screen buffer is not
// blocked.
// - Then, winpty wants to get the buffer info, then read screen content.
// - If the child process closes its special screen buffer during the
// scraping, then on Windows 7, conhost can start reading freed memory
// and crash. In this test case, `info2` is frequently garbage.
// Somehow winpty-agent needs to avoid this situation, but options seem
// scarce:
// - The Windows 7 bug only happens with `CloseHandle` AFAICT. If a
// buffer handle goes away implicitly from `FreeConsole` or process
// exit, then the buffer is reference counted properly. If app
// developers avoid closing their buffer handle, winpty can work.
// - Be really careful about when to scrape. Pay close attention to
// the kinds of WinEvents a full-screen app generates just before it
// exits, and try to fast-path everything such that no scraping is
// necessary.
// - Start interfering with the user processes attached to the console.
// - e.g. inject a DLL inside the processes and open CONOUT$, or
// override APIs, etc.
// - Attach to the right console process before opening CONOUT$. If
// that console's buffer handle is inheritable, then opening CONOUT$
// will then produce a safe handle.
// - Accept a certain amount of unreliability.
SendMessage(p.consoleWindow(), WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0);
auto scrape = p.openConout();
auto info1 = scrape.screenBufferInfo();
cb.close();
Sleep(200); // Helps the test fail more often.
auto info2 = scrape.screenBufferInfo();
SendMessage(p.consoleWindow(), WM_CHAR, 27, 0x00010001);
trace("%d %d %d %d", info1.srWindow.Left, info1.srWindow.Top, info1.srWindow.Right, info1.srWindow.Bottom);
trace("%d %d %d %d", info2.srWindow.Left, info2.srWindow.Top, info2.srWindow.Right, info2.srWindow.Bottom);
Sleep(300000);
}

View File

@ -1,94 +0,0 @@
#pragma once
#include "FixedSizeString.h"
#include "Spawn.h"
#include <array>
#include <cstdint>
struct Command {
enum Kind {
AllocConsole,
AttachConsole,
Close,
CloseQuietly,
DumpConsoleHandles,
DumpStandardHandles,
Duplicate,
Exit,
FreeConsole,
GetConsoleProcessList,
GetConsoleScreenBufferInfo,
GetConsoleSelectionInfo,
GetConsoleTitle,
GetConsoleWindow,
GetHandleInformation,
GetNumberOfConsoleInputEvents,
GetStdin,
GetStderr,
GetStdout,
Hello,
LookupKernelObject,
NewBuffer,
OpenConin,
OpenConout,
ReadConsoleOutput,
ScanForConsoleHandles,
SetConsoleTitle,
SetHandleInformation,
SetStdin,
SetStderr,
SetStdout,
SetActiveBuffer,
SpawnChild,
System,
WriteConsoleOutput,
WriteText,
};
// These fields must appear first so that the LookupKernelObject RPC will
// work. This RPC occurs from 32-bit test programs to a 64-bit worker.
// In that case, most of this struct's fields do not have the same
// addresses or sizes.
Kind kind;
struct {
uint32_t pid;
uint32_t handle[2];
uint32_t kernelObject[2];
} lookupKernelObject;
HANDLE handle;
HANDLE targetProcess;
DWORD dword;
BOOL success;
BOOL bInheritHandle;
BOOL writeToEach;
HWND hwnd;
union {
CONSOLE_SCREEN_BUFFER_INFO consoleScreenBufferInfo;
CONSOLE_SELECTION_INFO consoleSelectionInfo;
struct {
FixedSizeString<128> spawnName;
SpawnParams spawnParams;
SpawnFailure spawnFailure;
} spawn;
FixedSizeString<1024> writeText;
FixedSizeString<1024> systemText;
std::array<wchar_t, 1024> consoleTitle;
std::array<DWORD, 1024> processList;
struct {
DWORD mask;
DWORD flags;
} setFlags;
struct {
int count;
std::array<HANDLE, 1024> table;
} scanForConsoleHandles;
struct {
std::array<CHAR_INFO, 1024> buffer;
COORD bufferSize;
COORD bufferCoord;
SMALL_RECT ioRegion;
} consoleIo;
} u;
};

View File

@ -1,31 +0,0 @@
#include "Event.h"
#include "UnicodeConversions.h"
#include <WinptyAssert.h>
Event::Event(const std::string &name) {
// Create manual-reset, not signaled initially.
m_handle = CreateEventW(NULL, TRUE, FALSE, widenString(name).c_str());
ASSERT(m_handle != NULL);
}
Event::~Event() {
if (m_handle != NULL) {
CloseHandle(m_handle);
}
}
void Event::set() {
BOOL ret = SetEvent(m_handle);
ASSERT(ret && "SetEvent failed");
}
void Event::reset() {
BOOL ret = ResetEvent(m_handle);
ASSERT(ret && "ResetEvent failed");
}
void Event::wait() {
DWORD ret = WaitForSingleObject(m_handle, INFINITE);
ASSERT(ret == WAIT_OBJECT_0 && "WaitForSingleObject failed");
}

View File

@ -1,30 +0,0 @@
#pragma once
#include <windows.h>
#include <string>
#include <utility>
class Event {
public:
explicit Event(const std::string &name);
~Event();
void set();
void reset();
void wait();
// no copying
Event(const Event &other) = delete;
Event &operator=(const Event &other) = delete;
// moving is okay
Event(Event &&other) { *this = std::move(other); }
Event &operator=(Event &&other) {
m_handle = other.m_handle;
other.m_handle = NULL;
return *this;
}
private:
HANDLE m_handle;
};

View File

@ -1,37 +0,0 @@
#pragma once
#include <cstdlib>
#include <cstring>
#include <string>
#include <WinptyAssert.h>
template <size_t N>
struct FixedSizeString {
public:
std::string str() const {
ASSERT(strnlen(data, N) < N);
return std::string(data);
}
const char *c_str() const {
ASSERT(strnlen(data, N) < N);
return data;
}
FixedSizeString &operator=(const char *from) {
ASSERT(strlen(from) < N);
strcpy(data, from);
return *this;
}
FixedSizeString &operator=(const std::string &from) {
ASSERT(from.size() < N);
ASSERT(from.size() == strlen(from.c_str()));
strcpy(data, from.c_str());
return *this;
}
private:
char data[N];
};

View File

@ -1,74 +0,0 @@
#include "NtHandleQuery.h"
#include <DebugClient.h>
#include <OsModule.h>
// internal definitions copied from mingw-w64's winternl.h and ntstatus.h.
#define STATUS_SUCCESS ((NTSTATUS)0x00000000)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004)
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemProcessorInformation = 1,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemHandleInformation = 16,
SystemPagefileInformation = 18,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45
} SYSTEM_INFORMATION_CLASS;
typedef NTSTATUS NTAPI NtQuerySystemInformation_Type(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength);
std::vector<SYSTEM_HANDLE_ENTRY> queryNtHandles() {
OsModule ntdll(L"ntdll.dll");
auto funcPtr = ntdll.proc("NtQuerySystemInformation");
ASSERT(funcPtr != NULL && "NtQuerySystemInformation API is missing");
auto func = reinterpret_cast<NtQuerySystemInformation_Type*>(funcPtr);
static std::vector<char> buf(1024);
while (true) {
ULONG returnLength = 0;
auto ret = func(
SystemHandleInformation,
buf.data(),
buf.size(),
&returnLength);
if (ret == STATUS_INFO_LENGTH_MISMATCH) {
buf.resize(buf.size() * 2);
continue;
} else if (ret == STATUS_SUCCESS) {
break;
} else {
trace("Could not query NT handles, status was 0x%x",
static_cast<unsigned>(ret));
return {};
}
}
auto &info = *reinterpret_cast<SYSTEM_HANDLE_INFORMATION*>(buf.data());
std::vector<SYSTEM_HANDLE_ENTRY> ret(info.Count);
std::copy(info.Handle, info.Handle + info.Count, ret.begin());
return ret;
}
// Get the ObjectPointer (underlying NT object) for the NT handle.
void *ntHandlePointer(const std::vector<SYSTEM_HANDLE_ENTRY> &table,
DWORD pid, HANDLE h) {
HANDLE ret = nullptr;
for (auto &entry : table) {
if (entry.OwnerPid == pid &&
entry.HandleValue == reinterpret_cast<uint64_t>(h)) {
ASSERT(ret == nullptr);
ret = entry.ObjectPointer;
}
}
return ret;
}

View File

@ -1,23 +0,0 @@
#pragma once
#include <windows.h>
#include <vector>
typedef struct _SYSTEM_HANDLE_ENTRY {
ULONG OwnerPid;
BYTE ObjectType;
BYTE HandleFlags;
USHORT HandleValue;
PVOID ObjectPointer;
ULONG AccessMask;
} SYSTEM_HANDLE_ENTRY, *PSYSTEM_HANDLE_ENTRY;
typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG Count;
SYSTEM_HANDLE_ENTRY Handle[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
std::vector<SYSTEM_HANDLE_ENTRY> queryNtHandles();
void *ntHandlePointer(const std::vector<SYSTEM_HANDLE_ENTRY> &table,
DWORD pid, HANDLE h);

View File

@ -1,47 +0,0 @@
#pragma once
#include <windows.h>
#include <tuple>
#include <WinptyAssert.h>
inline std::tuple<int, int> osversion() {
OSVERSIONINFOW info = { sizeof(info) };
ASSERT(GetVersionExW(&info));
return std::make_tuple(info.dwMajorVersion, info.dwMinorVersion);
}
inline bool isWorkstation() {
OSVERSIONINFOEXW info = { sizeof(info) };
ASSERT(GetVersionExW(reinterpret_cast<OSVERSIONINFO*>(&info)));
return info.wProductType == VER_NT_WORKSTATION;
}
inline bool isWin7() {
return osversion() == std::make_tuple(6, 1);
}
inline bool isAtLeastVista() {
return osversion() >= std::make_tuple(6, 0);
}
inline bool isAtLeastWin7() {
return osversion() >= std::make_tuple(6, 1);
}
inline bool isAtLeastWin8() {
return osversion() >= std::make_tuple(6, 2);
}
inline bool isAtLeastWin8_1() {
return osversion() >= std::make_tuple(6, 3);
}
inline bool isTraditionalConio() {
return !isAtLeastWin8();
}
inline bool isModernConio() {
return isAtLeastWin8();
}

View File

@ -1,226 +0,0 @@
#include "RemoteHandle.h"
#include <string>
#include <vector>
#include "RemoteWorker.h"
#include <DebugClient.h>
#include <WinptyAssert.h>
RemoteHandle RemoteHandle::dup(HANDLE h, RemoteWorker &target,
BOOL bInheritHandle) {
HANDLE targetHandle;
BOOL success = DuplicateHandle(
GetCurrentProcess(),
h,
target.m_process,
&targetHandle,
0, bInheritHandle, DUPLICATE_SAME_ACCESS);
ASSERT(success && "DuplicateHandle failed");
return RemoteHandle(targetHandle, target);
}
RemoteHandle &RemoteHandle::activate() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetActiveBuffer);
return *this;
}
void RemoteHandle::write(const std::string &msg) {
worker().cmd().handle = m_value;
worker().cmd().u.writeText = msg;
worker().rpc(Command::WriteText);
}
void RemoteHandle::close() {
worker().cmd().handle = m_value;
worker().rpc(Command::Close);
}
RemoteHandle &RemoteHandle::setStdin() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetStdin);
return *this;
}
RemoteHandle &RemoteHandle::setStdout() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetStdout);
return *this;
}
RemoteHandle &RemoteHandle::setStderr() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetStderr);
return *this;
}
RemoteHandle RemoteHandle::dupImpl(RemoteWorker *target, BOOL bInheritHandle) {
HANDLE targetProcessFromSource;
if (target == nullptr) {
targetProcessFromSource = GetCurrentProcess();
} else {
// Allow the source worker to see the target worker.
targetProcessFromSource = INVALID_HANDLE_VALUE;
BOOL success = DuplicateHandle(
GetCurrentProcess(),
target->m_process,
worker().m_process,
&targetProcessFromSource,
0, FALSE, DUPLICATE_SAME_ACCESS);
ASSERT(success && "Process handle duplication failed");
}
// Do the user-level duplication in the source process.
worker().cmd().handle = m_value;
worker().cmd().targetProcess = targetProcessFromSource;
worker().cmd().bInheritHandle = bInheritHandle;
worker().rpc(Command::Duplicate);
HANDLE retHandle = worker().cmd().handle;
if (target != nullptr) {
// Cleanup targetProcessFromSource.
worker().cmd().handle = targetProcessFromSource;
worker().rpc(Command::CloseQuietly);
ASSERT(worker().cmd().success &&
"Error closing remote process handle");
}
return RemoteHandle(retHandle, target != nullptr ? *target : worker());
}
CONSOLE_SCREEN_BUFFER_INFO RemoteHandle::screenBufferInfo() {
CONSOLE_SCREEN_BUFFER_INFO ret;
bool success = tryScreenBufferInfo(&ret);
ASSERT(success && "GetConsoleScreenBufferInfo failed");
return ret;
}
bool RemoteHandle::tryScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO *info) {
worker().cmd().handle = m_value;
worker().rpc(Command::GetConsoleScreenBufferInfo);
if (worker().cmd().success && info != nullptr) {
*info = worker().cmd().u.consoleScreenBufferInfo;
}
return worker().cmd().success;
}
DWORD RemoteHandle::flags() {
DWORD ret;
bool success = tryFlags(&ret);
ASSERT(success && "GetHandleInformation failed");
return ret;
}
bool RemoteHandle::tryFlags(DWORD *flags) {
worker().cmd().handle = m_value;
worker().rpc(Command::GetHandleInformation);
if (worker().cmd().success && flags != nullptr) {
*flags = worker().cmd().dword;
}
return worker().cmd().success;
}
void RemoteHandle::setFlags(DWORD mask, DWORD flags) {
bool success = trySetFlags(mask, flags);
ASSERT(success && "SetHandleInformation failed");
}
bool RemoteHandle::trySetFlags(DWORD mask, DWORD flags) {
worker().cmd().handle = m_value;
worker().cmd().u.setFlags.mask = mask;
worker().cmd().u.setFlags.flags = flags;
worker().rpc(Command::SetHandleInformation);
return worker().cmd().success;
}
wchar_t RemoteHandle::firstChar() {
// The "first char" is useful for identifying which output buffer a handle
// refers to.
worker().cmd().handle = m_value;
const SMALL_RECT region = {};
auto &io = worker().cmd().u.consoleIo;
io.bufferSize = { 1, 1 };
io.bufferCoord = {};
io.ioRegion = region;
worker().rpc(Command::ReadConsoleOutput);
ASSERT(worker().cmd().success);
ASSERT(!memcmp(&io.ioRegion, &region, sizeof(region)));
return io.buffer[0].Char.UnicodeChar;
}
RemoteHandle &RemoteHandle::setFirstChar(wchar_t ch) {
// The "first char" is useful for identifying which output buffer a handle
// refers to.
worker().cmd().handle = m_value;
const SMALL_RECT region = {};
auto &io = worker().cmd().u.consoleIo;
io.buffer[0].Char.UnicodeChar = ch;
io.buffer[0].Attributes = 7;
io.bufferSize = { 1, 1 };
io.bufferCoord = {};
io.ioRegion = region;
worker().rpc(Command::WriteConsoleOutput);
ASSERT(worker().cmd().success);
ASSERT(!memcmp(&io.ioRegion, &region, sizeof(region)));
return *this;
}
bool RemoteHandle::tryNumberOfConsoleInputEvents(DWORD *ret) {
worker().cmd().handle = m_value;
worker().rpc(Command::GetNumberOfConsoleInputEvents);
if (worker().cmd().success && ret != nullptr) {
*ret = worker().cmd().dword;
}
return worker().cmd().success;
}
std::vector<RemoteHandle> inheritableHandles(
const std::vector<RemoteHandle> &vec) {
std::vector<RemoteHandle> ret;
for (auto h : vec) {
if (h.inheritable()) {
ret.push_back(h);
}
}
return ret;
}
std::vector<uint64_t> handleInts(const std::vector<RemoteHandle> &vec) {
std::vector<uint64_t> ret;
for (auto h : vec) {
ret.push_back(reinterpret_cast<uint64_t>(h.value()));
}
return ret;
}
std::vector<HANDLE> handleValues(const std::vector<RemoteHandle> &vec) {
std::vector<HANDLE> ret;
for (auto h : vec) {
ret.push_back(h.value());
}
return ret;
}
// It would make more sense to use a std::tuple here, but it's inconvenient.
std::vector<RemoteHandle> stdHandles(RemoteWorker &worker) {
return {
worker.getStdin(),
worker.getStdout(),
worker.getStderr(),
};
}
// It would make more sense to use a std::tuple here, but it's inconvenient.
void setStdHandles(std::vector<RemoteHandle> handles) {
ASSERT(handles.size() == 3);
handles[0].setStdin();
handles[1].setStdout();
handles[2].setStderr();
}
bool allInheritable(const std::vector<RemoteHandle> &vec) {
return handleValues(vec) == handleValues(inheritableHandles(vec));
}

View File

@ -1,82 +0,0 @@
#pragma once
#include <windows.h>
#include <cstdint>
#include <string>
#include <vector>
#include <WinptyAssert.h>
class RemoteWorker;
class RemoteHandle {
friend class RemoteWorker;
private:
RemoteHandle(HANDLE value, RemoteWorker &worker) :
m_value(value), m_worker(&worker)
{
}
public:
static RemoteHandle invent(HANDLE h, RemoteWorker &worker) {
return RemoteHandle(h, worker);
}
static RemoteHandle invent(uint64_t h, RemoteWorker &worker) {
return RemoteHandle(reinterpret_cast<HANDLE>(h), worker);
}
RemoteHandle &activate();
void write(const std::string &msg);
void close();
RemoteHandle &setStdin();
RemoteHandle &setStdout();
RemoteHandle &setStderr();
private:
RemoteHandle dupImpl(RemoteWorker *target, BOOL bInheritHandle);
public:
RemoteHandle dup(RemoteWorker &target, BOOL bInheritHandle=FALSE) {
return dupImpl(&target, bInheritHandle);
}
RemoteHandle dup(BOOL bInheritHandle=FALSE) {
return dupImpl(nullptr, bInheritHandle);
}
static RemoteHandle dup(HANDLE h, RemoteWorker &target,
BOOL bInheritHandle=FALSE);
CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo();
bool tryScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO *info=nullptr);
DWORD flags();
bool tryFlags(DWORD *flags=nullptr);
void setFlags(DWORD mask, DWORD flags);
bool trySetFlags(DWORD mask, DWORD flags);
bool inheritable() {
return flags() & HANDLE_FLAG_INHERIT;
}
void setInheritable(bool inheritable) {
auto success = trySetInheritable(inheritable);
ASSERT(success && "setInheritable failed");
}
bool trySetInheritable(bool inheritable) {
return trySetFlags(HANDLE_FLAG_INHERIT,
inheritable ? HANDLE_FLAG_INHERIT : 0);
}
wchar_t firstChar();
RemoteHandle &setFirstChar(wchar_t ch);
bool tryNumberOfConsoleInputEvents(DWORD *ret=nullptr);
HANDLE value() const { return m_value; }
uint64_t uvalue() const { return reinterpret_cast<uint64_t>(m_value); }
bool isTraditionalConsole() const { return (uvalue() & 3) == 3; }
RemoteWorker &worker() const { return *m_worker; }
private:
HANDLE m_value;
RemoteWorker *m_worker;
};
std::vector<RemoteHandle> inheritableHandles(
const std::vector<RemoteHandle> &vec);
std::vector<uint64_t> handleInts(const std::vector<RemoteHandle> &vec);
std::vector<HANDLE> handleValues(const std::vector<RemoteHandle> &vec);
std::vector<RemoteHandle> stdHandles(RemoteWorker &worker);
void setStdHandles(std::vector<RemoteHandle> handles);
bool allInheritable(const std::vector<RemoteHandle> &vec);

View File

@ -1,156 +0,0 @@
#include "RemoteWorker.h"
#include <string>
#include "UnicodeConversions.h"
#include "Util.h"
#include <DebugClient.h>
#include <WinptyAssert.h>
DWORD RemoteWorker::dwDefaultCreationFlags = CREATE_NEW_CONSOLE;
RemoteWorker::RemoteWorker(decltype(DoNotSpawn)) :
m_name(makeTempName("WinptyBufferTests")),
m_parcel(m_name + "-shmem", ShmemParcel::CreateNew),
m_startEvent(m_name + "-start"),
m_finishEvent(m_name + "-finish")
{
m_finishEvent.set();
}
RemoteWorker::RemoteWorker(SpawnParams params) : RemoteWorker(DoNotSpawn) {
SpawnFailure dummy;
m_process = spawn(m_name, params, dummy);
ASSERT(m_process != nullptr && "Could not create RemoteWorker");
m_valid = true;
// Perform an RPC just to ensure that the worker process is ready, and
// the console exists, before returning.
rpc(Command::Hello);
}
RemoteWorker RemoteWorker::child(SpawnParams params) {
auto ret = tryChild(params);
ASSERT(ret.valid() && "Could not spawn child worker");
return ret;
}
RemoteWorker RemoteWorker::tryChild(SpawnParams params, SpawnFailure *failure) {
RemoteWorker ret(DoNotSpawn);
cmd().u.spawn.spawnName = ret.m_name;
cmd().u.spawn.spawnParams = params;
rpc(Command::SpawnChild);
if (cmd().handle == nullptr) {
if (failure != nullptr) {
*failure = cmd().u.spawn.spawnFailure;
}
} else {
BOOL dupSuccess = DuplicateHandle(
m_process,
cmd().handle,
GetCurrentProcess(),
&ret.m_process,
0, FALSE, DUPLICATE_SAME_ACCESS);
ASSERT(dupSuccess && "RemoteWorker::child: DuplicateHandle failed");
rpc(Command::CloseQuietly);
ASSERT(cmd().success && "RemoteWorker::child: CloseHandle failed");
ret.m_valid = true;
// Perform an RPC just to ensure that the worker process is ready, and
// the console exists, before returning.
ret.rpc(Command::Hello);
}
return ret;
}
void RemoteWorker::exit() {
cmd().dword = 0;
rpcAsync(Command::Exit);
DWORD result = WaitForSingleObject(m_process, INFINITE);
ASSERT(result == WAIT_OBJECT_0 &&
"WaitForSingleObject failed while killing worker");
CloseHandle(m_process);
m_valid = false;
}
CONSOLE_SELECTION_INFO RemoteWorker::selectionInfo() {
rpc(Command::GetConsoleSelectionInfo);
ASSERT(cmd().success);
return cmd().u.consoleSelectionInfo;
}
void RemoteWorker::dumpConsoleHandles(BOOL writeToEach) {
cmd().writeToEach = writeToEach;
rpc(Command::DumpConsoleHandles);
}
std::vector<RemoteHandle> RemoteWorker::scanForConsoleHandles() {
rpc(Command::ScanForConsoleHandles);
auto &rpcTable = cmd().u.scanForConsoleHandles;
std::vector<RemoteHandle> ret;
for (int i = 0; i < rpcTable.count; ++i) {
ret.push_back(RemoteHandle(rpcTable.table[i], *this));
}
return ret;
}
bool RemoteWorker::setTitleInternal(const std::wstring &wstr) {
ASSERT(wstr.size() < cmd().u.consoleTitle.size());
ASSERT(wstr.size() == wcslen(wstr.c_str()));
wcscpy(cmd().u.consoleTitle.data(), wstr.c_str());
rpc(Command::SetConsoleTitle);
return cmd().success;
}
std::string RemoteWorker::title() {
std::array<wchar_t, 1024> buf;
DWORD ret = titleInternal(buf, buf.size());
ret = std::min<DWORD>(ret, buf.size() - 1);
buf[std::min<size_t>(buf.size() - 1, ret)] = L'\0';
return narrowString(std::wstring(buf.data()));
}
// This API is more low-level than typical, because GetConsoleTitleW is buggy
// in older versions of Windows, and this method is used to test the bugs.
DWORD RemoteWorker::titleInternal(std::array<wchar_t, 1024> &buf, DWORD bufSize) {
cmd().dword = bufSize;
cmd().u.consoleTitle = buf;
rpc(Command::GetConsoleTitle);
buf = cmd().u.consoleTitle;
return cmd().dword;
}
std::vector<DWORD> RemoteWorker::consoleProcessList() {
rpc(Command::GetConsoleProcessList);
DWORD count = cmd().dword;
ASSERT(count <= cmd().u.processList.size());
return std::vector<DWORD>(
&cmd().u.processList[0],
&cmd().u.processList[count]);
}
uint64_t RemoteWorker::lookupKernelObject(DWORD pid, HANDLE h) {
const uint64_t h64 = reinterpret_cast<uint64_t>(h);
cmd().lookupKernelObject.pid = pid;
memcpy(&cmd().lookupKernelObject.handle, &h64, sizeof(h64));
rpc(Command::LookupKernelObject);
uint64_t ret;
memcpy(&ret, &cmd().lookupKernelObject.kernelObject, sizeof(ret));
return ret;
}
void RemoteWorker::rpc(Command::Kind kind) {
rpcImpl(kind);
m_finishEvent.wait();
}
void RemoteWorker::rpcAsync(Command::Kind kind) {
rpcImpl(kind);
}
void RemoteWorker::rpcImpl(Command::Kind kind) {
ASSERT(m_valid && "Cannot perform an RPC on an invalid RemoteWorker");
m_finishEvent.wait();
m_finishEvent.reset();
cmd().kind = kind;
m_startEvent.set();
}

View File

@ -1,138 +0,0 @@
#pragma once
#include <windows.h>
#include <string>
#include <vector>
#include "Command.h"
#include "Event.h"
#include "RemoteHandle.h"
#include "ShmemParcel.h"
#include "Spawn.h"
#include "UnicodeConversions.h"
class RemoteWorker {
friend class RemoteHandle;
friend uint64_t wow64LookupKernelObject(DWORD pid, HANDLE handle);
static DWORD dwDefaultCreationFlags;
public:
static void setDefaultCreationFlags(DWORD flags) {
dwDefaultCreationFlags = flags;
}
static DWORD defaultCreationFlags() {
return dwDefaultCreationFlags;
}
public:
struct {} static constexpr DoNotSpawn = {};
explicit RemoteWorker(decltype(DoNotSpawn));
explicit RemoteWorker() : RemoteWorker({false, dwDefaultCreationFlags}) {}
explicit RemoteWorker(SpawnParams params);
RemoteWorker child(SpawnParams params={});
RemoteWorker tryChild(SpawnParams params={}, SpawnFailure *failure=nullptr);
~RemoteWorker() { cleanup(); }
bool valid() { return m_valid; }
void exit();
private:
void cleanup() { if (m_valid) { exit(); } }
public:
// basic worker info
HANDLE processHandle() { return m_process; }
DWORD pid() { return GetProcessId(m_process); }
// allow moving
RemoteWorker(RemoteWorker &&other) :
m_valid(std::move(other.m_valid)),
m_name(std::move(other.m_name)),
m_parcel(std::move(other.m_parcel)),
m_startEvent(std::move(other.m_startEvent)),
m_finishEvent(std::move(other.m_finishEvent)),
m_process(std::move(other.m_process))
{
other.m_valid = false;
other.m_process = nullptr;
}
RemoteWorker &operator=(RemoteWorker &&other) {
cleanup();
m_valid = std::move(other.m_valid);
m_name = std::move(other.m_name);
m_parcel = std::move(other.m_parcel);
m_startEvent = std::move(other.m_startEvent);
m_finishEvent = std::move(other.m_finishEvent);
m_process = std::move(other.m_process);
other.m_valid = false;
other.m_process = nullptr;
return *this;
}
// Commands
RemoteHandle getStdin() { rpc(Command::GetStdin); return RemoteHandle(cmd().handle, *this); }
RemoteHandle getStdout() { rpc(Command::GetStdout); return RemoteHandle(cmd().handle, *this); }
RemoteHandle getStderr() { rpc(Command::GetStderr); return RemoteHandle(cmd().handle, *this); }
bool detach() { rpc(Command::FreeConsole); return cmd().success; }
bool attach(RemoteWorker &worker) { cmd().dword = GetProcessId(worker.m_process); rpc(Command::AttachConsole); return cmd().success; }
bool alloc() { rpc(Command::AllocConsole); return cmd().success; }
void dumpStandardHandles() { rpc(Command::DumpStandardHandles); }
int system(const std::string &arg) { cmd().u.systemText = arg; rpc(Command::System); return cmd().dword; }
HWND consoleWindow() { rpc(Command::GetConsoleWindow); return cmd().hwnd; }
CONSOLE_SELECTION_INFO selectionInfo();
void dumpConsoleHandles(BOOL writeToEach=FALSE);
std::vector<RemoteHandle> scanForConsoleHandles();
void setTitle(const std::string &str) { auto b = setTitleInternal(widenString(str)); ASSERT(b && "setTitle failed"); }
bool setTitleInternal(const std::wstring &str);
std::string title();
DWORD titleInternal(std::array<wchar_t, 1024> &buf, DWORD bufSize);
std::vector<DWORD> consoleProcessList();
RemoteHandle openConin(BOOL bInheritHandle=FALSE) {
cmd().bInheritHandle = bInheritHandle;
rpc(Command::OpenConin);
return RemoteHandle(cmd().handle, *this);
}
RemoteHandle openConout(BOOL bInheritHandle=FALSE) {
cmd().bInheritHandle = bInheritHandle;
rpc(Command::OpenConout);
return RemoteHandle(cmd().handle, *this);
}
RemoteHandle newBuffer(BOOL bInheritHandle=FALSE, wchar_t firstChar=L'\0') {
cmd().bInheritHandle = bInheritHandle;
rpc(Command::NewBuffer);
auto h = RemoteHandle(cmd().handle, *this);
if (firstChar != L'\0') {
h.setFirstChar(firstChar);
}
return h;
}
private:
uint64_t lookupKernelObject(DWORD pid, HANDLE h);
private:
Command &cmd() { return m_parcel.value()[0]; }
void rpc(Command::Kind kind);
void rpcAsync(Command::Kind kind);
void rpcImpl(Command::Kind kind);
private:
bool m_valid = false;
std::string m_name;
// HACK: Use Command[2] instead of Command. To accommodate WOW64, we need
// to have a 32-bit test program communicate with a 64-bit worker to query
// kernel handles. The sizes of the parcels will not match, but it's
// mostly OK as long as the creation size is larger than the open size, and
// the 32-bit program creates the parcel. In principle, a better fix might
// be to use parcels of different sizes or make the Command struct's size
// independent of architecture, but those changes are hard.
ShmemParcelTyped<Command[2]> m_parcel;
Event m_startEvent;
Event m_finishEvent;
HANDLE m_process = NULL;
};

View File

@ -1,42 +0,0 @@
#include "ShmemParcel.h"
#include "UnicodeConversions.h"
#include <DebugClient.h>
#include <WinptyAssert.h>
ShmemParcel::ShmemParcel(
const std::string &name,
CreationDisposition disposition,
size_t size)
{
if (disposition == CreateNew) {
SetLastError(0);
m_hfile = CreateFileMappingW(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
size,
widenString(name).c_str());
ASSERT(m_hfile != NULL && GetLastError() == 0 &&
"Failed to create shared memory");
} else if (disposition == OpenExisting) {
m_hfile = OpenFileMappingW(
FILE_MAP_ALL_ACCESS,
FALSE,
widenString(name).c_str());
ASSERT(m_hfile != NULL && "Failed to open shared memory");
} else {
ASSERT(false && "Invalid disposition value");
}
m_view = MapViewOfFile(m_hfile, FILE_MAP_ALL_ACCESS, 0, 0, size);
ASSERT(m_view != NULL && "Failed to map view of shared memory to create it");
}
ShmemParcel::~ShmemParcel()
{
if (m_hfile != NULL) {
UnmapViewOfFile(m_view);
CloseHandle(m_hfile);
}
}

View File

@ -1,61 +0,0 @@
#pragma once
#include <windows.h>
#include <cstdlib>
#include <string>
#include <utility>
class ShmemParcel {
public:
enum CreationDisposition {
CreateNew,
OpenExisting,
};
public:
ShmemParcel(
const std::string &name,
CreationDisposition disposition,
size_t size);
~ShmemParcel();
// no copying
ShmemParcel(const ShmemParcel &other) = delete;
ShmemParcel &operator=(const ShmemParcel &other) = delete;
// moving is okay
ShmemParcel(ShmemParcel &&other) {
*this = std::move(other);
}
ShmemParcel &operator=(ShmemParcel &&other) {
m_hfile = other.m_hfile;
m_view = other.m_view;
other.m_hfile = NULL;
other.m_view = NULL;
return *this;
}
void *view() { return m_view; }
private:
HANDLE m_hfile;
void *m_view;
};
template <typename T>
class ShmemParcelTyped {
public:
ShmemParcelTyped(
const std::string &name,
ShmemParcel::CreationDisposition disposition) :
m_parcel(name, disposition, sizeof(T))
{
}
T &value() { return *static_cast<T*>(m_parcel.view()); }
private:
ShmemParcel m_parcel;
};

View File

@ -1,131 +0,0 @@
#include "Spawn.h"
#include <string.h>
#include "UnicodeConversions.h"
#include "Util.h"
#include <DebugClient.h>
#include <OsModule.h>
#include <WinptyAssert.h>
namespace {
static std::vector<wchar_t> wstrToWVector(const std::wstring &str) {
std::vector<wchar_t> ret;
ret.resize(str.size() + 1);
wmemcpy(ret.data(), str.c_str(), str.size() + 1);
return ret;
}
} // anonymous namespace
HANDLE spawn(const std::string &workerName,
const SpawnParams &params,
SpawnFailure &error) {
auto exeBaseName = (isWow64() && params.nativeWorkerBitness)
? "Worker64.exe" // always 64-bit binary, used to escape WOW64 environ
: "Worker.exe"; // 32 or 64-bit binary, same arch as test program
auto workerPath =
pathDirName(getModuleFileName(NULL)) + "\\" + exeBaseName;
const std::wstring workerPathWStr = widenString(workerPath);
const std::string cmdLine = "\"" + workerPath + "\" " + workerName;
auto cmdLineWVec = wstrToWVector(widenString(cmdLine));
STARTUPINFOEXW suix = { params.sui };
ASSERT(suix.StartupInfo.cb == sizeof(STARTUPINFOW) ||
suix.StartupInfo.cb == sizeof(STARTUPINFOEXW));
std::unique_ptr<char[]> attrListBuffer;
auto inheritList = params.inheritList;
OsModule kernel32(L"kernel32.dll");
#define DECL_API_FUNC(name) decltype(name) *p##name = nullptr;
DECL_API_FUNC(InitializeProcThreadAttributeList);
DECL_API_FUNC(UpdateProcThreadAttribute);
DECL_API_FUNC(DeleteProcThreadAttributeList);
#undef DECL_API_FUNC
struct AttrList {
decltype(DeleteProcThreadAttributeList) *cleanup = nullptr;
LPPROC_THREAD_ATTRIBUTE_LIST v = nullptr;
~AttrList() {
if (v != nullptr) {
ASSERT(cleanup != nullptr);
cleanup(v);
}
}
} attrList;
if (params.inheritCount != SpawnParams::NoInheritList) {
// Add PROC_THREAD_ATTRIBUTE_HANDLE_LIST to the STARTUPINFOEX. Use
// dynamic binding, because this code must run on XP, which does not
// have this functionality.
ASSERT(params.inheritCount < params.inheritList.size());
#define GET_API_FUNC(name) p##name = reinterpret_cast<decltype(name)*>(kernel32.proc(#name));
GET_API_FUNC(InitializeProcThreadAttributeList);
GET_API_FUNC(UpdateProcThreadAttribute);
GET_API_FUNC(DeleteProcThreadAttributeList);
#undef GET_API_FUNC
if (pInitializeProcThreadAttributeList == nullptr ||
pUpdateProcThreadAttribute == nullptr ||
pDeleteProcThreadAttributeList == nullptr) {
trace("Error: skipping PROC_THREAD_ATTRIBUTE_HANDLE_LIST "
"due to missing APIs");
} else {
SIZE_T bufferSize = 0;
auto success = pInitializeProcThreadAttributeList(
NULL, 1, 0, &bufferSize);
if (!success && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
// The InitializeProcThreadAttributeList API "fails" with
// ERROR_INSUFFICIENT_BUFFER.
success = TRUE;
}
ASSERT(success &&
"First InitializeProcThreadAttributeList call failed");
attrListBuffer = std::unique_ptr<char[]>(new char[bufferSize]);
attrList.cleanup = pDeleteProcThreadAttributeList;
suix.lpAttributeList = attrList.v =
reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(
attrListBuffer.get());
success = pInitializeProcThreadAttributeList(
suix.lpAttributeList, 1, 0, &bufferSize);
ASSERT(success &&
"Second InitializeProcThreadAttributeList call failed");
success = pUpdateProcThreadAttribute(
suix.lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
inheritList.data(),
params.inheritCount * sizeof(HANDLE),
nullptr, nullptr);
if (!success) {
error.kind = SpawnFailure::UpdateProcThreadAttribute;
error.errCode = GetLastError();
trace("UpdateProcThreadAttribute failed: %s",
errorString(error.errCode).c_str());
return nullptr;
}
}
}
PROCESS_INFORMATION pi;
memset(&pi, 0, sizeof(pi));
auto success = CreateProcessW(workerPathWStr.c_str(), cmdLineWVec.data(),
NULL, NULL,
/*bInheritHandles=*/params.bInheritHandles,
/*dwCreationFlags=*/params.dwCreationFlags,
NULL, NULL,
&suix.StartupInfo, &pi);
if (!success) {
error.kind = SpawnFailure::CreateProcess;
error.errCode = GetLastError();
trace("CreateProcessW failed: %s",
errorString(error.errCode).c_str());
return nullptr;
}
error.kind = SpawnFailure::Success;
error.errCode = 0;
CloseHandle(pi.hThread);
return pi.hProcess;
}

View File

@ -1,45 +0,0 @@
#pragma once
#include <windows.h>
#include <string>
#include "RemoteHandle.h"
struct SpawnParams {
BOOL bInheritHandles = FALSE;
DWORD dwCreationFlags = 0;
STARTUPINFOW sui = { sizeof(STARTUPINFOW), 0 };
static const size_t NoInheritList = static_cast<size_t>(~0);
size_t inheritCount = NoInheritList;
std::array<HANDLE, 1024> inheritList = {};
bool nativeWorkerBitness = false;
SpawnParams(bool bInheritHandles=false, DWORD dwCreationFlags=0) :
bInheritHandles(bInheritHandles),
dwCreationFlags(dwCreationFlags)
{
}
SpawnParams(bool bInheritHandles, DWORD dwCreationFlags,
std::vector<RemoteHandle> stdHandles) :
bInheritHandles(bInheritHandles),
dwCreationFlags(dwCreationFlags)
{
ASSERT(stdHandles.size() == 3);
sui.dwFlags |= STARTF_USESTDHANDLES;
sui.hStdInput = stdHandles[0].value();
sui.hStdOutput = stdHandles[1].value();
sui.hStdError = stdHandles[2].value();
}
};
struct SpawnFailure {
enum Kind { Success, CreateProcess, UpdateProcThreadAttribute };
Kind kind = Success;
DWORD errCode = 0;
};
HANDLE spawn(const std::string &workerName,
const SpawnParams &params,
SpawnFailure &error);

View File

@ -1,31 +0,0 @@
#pragma once
#include <algorithm>
#include <array>
#include <cassert>
#include <cstdio>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <cwchar>
#include <iostream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "NtHandleQuery.h"
#include "OsVersion.h"
#include "RemoteHandle.h"
#include "RemoteWorker.h"
#include "TestUtil.h"
#include "UnicodeConversions.h"
#include "Util.h"
#include <DebugClient.h>
#include <WinptyAssert.h>
#include <winpty_wcsnlen.h>
using Handle = RemoteHandle;
using Worker = RemoteWorker;

View File

@ -1,327 +0,0 @@
#include "TestUtil.h"
#include <array>
#include <iostream>
#include <string>
#include <unordered_set>
#include <vector>
#include "OsVersion.h"
#include "NtHandleQuery.h"
#include "RemoteHandle.h"
#include "RemoteWorker.h"
#include "UnicodeConversions.h"
#include "Util.h"
#include <DebugClient.h>
#include <OsModule.h>
#include <WinptyAssert.h>
static RegistrationTable *g_testFunctions;
static std::unordered_set<std::string> g_testFailures;
void printTestName(const std::string &name) {
trace("----------------------------------------------------------");
trace("%s", name.c_str());
printf("%s\n", name.c_str());
fflush(stdout);
}
void recordFailure(const std::string &name) {
g_testFailures.insert(name);
}
std::vector<std::string> failedTests() {
std::vector<std::string> ret(g_testFailures.begin(), g_testFailures.end());
std::sort(ret.begin(), ret.end());
return ret;
}
void registerTest(const std::string &name, bool (&cond)(), void (&func)()) {
if (g_testFunctions == nullptr) {
g_testFunctions = new RegistrationTable {};
}
for (auto &entry : *g_testFunctions) {
// I think the compiler catches duplicates already, but just in case.
ASSERT(&cond != std::get<1>(entry) || &func != std::get<2>(entry));
}
g_testFunctions->push_back(std::make_tuple(name, &cond, &func));
}
RegistrationTable registeredTests() {
return *g_testFunctions;
}
static bool hasBuiltinCompareObjectHandles() {
static auto kernelbase = LoadLibraryW(L"KernelBase.dll");
if (kernelbase != nullptr) {
static auto proc = GetProcAddress(kernelbase, "CompareObjectHandles");
if (proc != nullptr) {
return true;
}
}
return false;
}
static bool needsWow64HandleLookup() {
// The Worker.exe and the test programs must always be the same bitness.
// However, in WOW64 mode, prior to Windows 7 64-bit, the WOW64 version of
// NtQuerySystemInformation returned almost no handle information. Even
// in Windows 7, the pointers are truncated to 32-bits, so for maximum
// reliability, use the RPC technique there too. Windows 10 has a proper
// API.
static bool value = isWow64();
return value;
}
static RemoteWorker makeLookupWorker() {
SpawnParams sp(false, DETACHED_PROCESS);
sp.nativeWorkerBitness = true;
return RemoteWorker(sp);
}
uint64_t wow64LookupKernelObject(DWORD pid, HANDLE handle) {
static auto lookupWorker = makeLookupWorker();
return lookupWorker.lookupKernelObject(pid, handle);
}
static bool builtinCompareObjectHandles(RemoteHandle h1, RemoteHandle h2) {
static OsModule kernelbase(L"KernelBase.dll");
static auto comp =
reinterpret_cast<BOOL(WINAPI*)(HANDLE,HANDLE)>(
kernelbase.proc("CompareObjectHandles"));
ASSERT(comp != nullptr);
HANDLE h1local = nullptr;
HANDLE h2local = nullptr;
bool dup1 = DuplicateHandle(
h1.worker().processHandle(),
h1.value(),
GetCurrentProcess(),
&h1local,
0, false, DUPLICATE_SAME_ACCESS);
bool dup2 = DuplicateHandle(
h2.worker().processHandle(),
h2.value(),
GetCurrentProcess(),
&h2local,
0, false, DUPLICATE_SAME_ACCESS);
bool ret = dup1 && dup2 && comp(h1local, h2local);
if (dup1) {
CloseHandle(h1local);
}
if (dup2) {
CloseHandle(h2local);
}
return ret;
}
bool compareObjectHandles(RemoteHandle h1, RemoteHandle h2) {
ObjectSnap snap;
return snap.eq(h1, h2);
}
ObjectSnap::ObjectSnap() {
if (!hasBuiltinCompareObjectHandles() && !needsWow64HandleLookup()) {
m_table = queryNtHandles();
m_hasTable = true;
}
}
uint64_t ObjectSnap::object(RemoteHandle h) {
if (needsWow64HandleLookup()) {
return wow64LookupKernelObject(h.worker().pid(), h.value());
}
if (!m_hasTable) {
m_table = queryNtHandles();
}
return reinterpret_cast<uint64_t>(ntHandlePointer(
m_table, h.worker().pid(), h.value()));
}
bool ObjectSnap::eq(std::initializer_list<RemoteHandle> handles) {
if (handles.size() < 2) {
return true;
}
if (hasBuiltinCompareObjectHandles()) {
for (auto i = handles.begin() + 1; i < handles.end(); ++i) {
if (!builtinCompareObjectHandles(*handles.begin(), *i)) {
return false;
}
}
} else {
auto first = object(*handles.begin());
for (auto i = handles.begin() + 1; i < handles.end(); ++i) {
if (first != object(*i)) {
return false;
}
}
}
return true;
}
std::tuple<RemoteHandle, RemoteHandle> newPipe(
RemoteWorker &w, BOOL inheritable) {
HANDLE readPipe, writePipe;
auto ret = CreatePipe(&readPipe, &writePipe, NULL, 0);
ASSERT(ret && "CreatePipe failed");
auto p1 = RemoteHandle::dup(readPipe, w, inheritable);
auto p2 = RemoteHandle::dup(writePipe, w, inheritable);
trace("Opened pipe in pid %u: rh=0x%I64x wh=0x%I64x",
w.pid(), p1.uvalue(), p2.uvalue());
CloseHandle(readPipe);
CloseHandle(writePipe);
return std::make_tuple(p1, p2);
}
std::string windowText(HWND hwnd) {
std::array<wchar_t, 256> buf;
DWORD ret = GetWindowTextW(hwnd, buf.data(), buf.size());
ASSERT(ret >= 0 && ret <= buf.size() - 1);
buf[ret] = L'\0';
return narrowString(std::wstring(buf.data()));
}
// Verify that the process' open console handle set is as expected from
// attaching to a new console.
// * The set of console handles is exactly (0x3, 0x7, 0xb).
// * The console handles are inheritable.
void checkInitConsoleHandleSet(RemoteWorker &proc) {
CHECK(isTraditionalConio() && "checkInitConsoleHandleSet is not valid "
"with modern conio");
auto actualHandles = proc.scanForConsoleHandles();
auto correctHandles = std::vector<uint64_t> { 0x3, 0x7, 0xb };
if (handleInts(actualHandles) == correctHandles &&
allInheritable(actualHandles)) {
return;
}
proc.dumpConsoleHandles();
CHECK(false && "checkInitConsoleHandleSet failed");
}
// Verify that the child's open console handle set is as expected from having
// just attached to or spawned from a source worker.
// * The set of child handles should exactly match the set of inheritable
// source handles.
// * Every open child handle should be inheritable.
void checkInitConsoleHandleSet(RemoteWorker &child, RemoteWorker &source) {
ASSERT(isTraditionalConio() && "checkInitConsoleHandleSet is not valid "
"with modern conio");
auto cvec = child.scanForConsoleHandles();
auto cvecInherit = inheritableHandles(cvec);
auto svecInherit = inheritableHandles(source.scanForConsoleHandles());
auto hv = &handleValues;
if (hv(cvecInherit) == hv(svecInherit) && allInheritable(cvec)) {
return;
}
source.dumpConsoleHandles();
child.dumpConsoleHandles();
CHECK(false && "checkInitConsoleHandleSet failed");
}
// Returns true if the handle is a "usable console handle":
// * The handle must be open.
// * It must be a console handle.
// * The process must have an attached console.
// * With modern conio, the handle must be "unbound" or bound to the
// currently attached console.
bool isUsableConsoleHandle(RemoteHandle h) {
// XXX: It would be more efficient/elegant to use GetConsoleMode instead.
return h.tryNumberOfConsoleInputEvents() || h.tryScreenBufferInfo();
}
bool isUsableConsoleInputHandle(RemoteHandle h) {
return h.tryNumberOfConsoleInputEvents();
}
bool isUsableConsoleOutputHandle(RemoteHandle h) {
return h.tryScreenBufferInfo();
}
bool isUnboundConsoleObject(RemoteHandle h) {
// XXX: Consider what happens here with NULL, INVALID_HANDLE_OBJECT, junk,
// etc. I *think* it should work.
ASSERT(isModernConio() && "isUnboundConsoleObject is not valid with "
"traditional conio");
static RemoteWorker other{ SpawnParams {false, CREATE_NO_WINDOW} };
auto dup = h.dup(other);
bool ret = isUsableConsoleHandle(dup);
dup.close();
return ret;
}
// Verify that an optional subset of the STDIN/STDOUT/STDERR standard
// handles are new handles referring to new Unbound console objects.
void checkModernConsoleHandleInit(RemoteWorker &proc,
bool in, bool out, bool err) {
// List all the usable console handles that weren't just opened.
std::vector<RemoteHandle> preExistingHandles;
for (auto h : proc.scanForConsoleHandles()) {
if ((in && h.value() == proc.getStdin().value()) ||
(out && h.value() == proc.getStdout().value()) ||
(err && h.value() == proc.getStderr().value())) {
continue;
}
preExistingHandles.push_back(h);
}
ObjectSnap snap;
auto checkNonReuse = [&](RemoteHandle h) {
// The Unbound console objects that were just opened should not be
// inherited from anywhere else -- they should be brand new objects.
for (auto other : preExistingHandles) {
CHECK(!snap.eq(h, other));
}
};
if (in) {
CHECK(isUsableConsoleInputHandle(proc.getStdin()));
CHECK(isUnboundConsoleObject(proc.getStdin()));
checkNonReuse(proc.getStdin());
}
if (out) {
CHECK(isUsableConsoleOutputHandle(proc.getStdout()));
CHECK(isUnboundConsoleObject(proc.getStdout()));
checkNonReuse(proc.getStdout());
}
if (err) {
CHECK(isUsableConsoleOutputHandle(proc.getStderr()));
CHECK(isUnboundConsoleObject(proc.getStderr()));
checkNonReuse(proc.getStderr());
}
if (out && err) {
ObjectSnap snap;
CHECK(proc.getStdout().value() != proc.getStderr().value());
CHECK(snap.eq(proc.getStdout(), proc.getStderr()));
}
}
// Wrapper around RemoteWorker::child that does the bare minimum to use an
// inherit list.
//
// If `dummyPipeInInheritList` is true, it also creates an inheritable pipe,
// closes one end, and specifies the other end in an inherit list. It closes
// the final pipe end in the parent and child before returning.
//
// This function is useful for testing the modern bInheritHandles=TRUE handle
// duplication functionality.
//
RemoteWorker childWithDummyInheritList(RemoteWorker &p, SpawnParams sp,
bool dummyPipeInInheritList) {
sp.bInheritHandles = true;
sp.dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
sp.sui.cb = sizeof(STARTUPINFOEXW);
sp.inheritCount = 1;
if (dummyPipeInInheritList) {
auto pipe = newPipe(p, true);
std::get<0>(pipe).close();
auto dummy = std::get<1>(pipe);
sp.inheritList = { dummy.value() };
auto c = p.child(sp);
RemoteHandle::invent(dummy.value(), c).close();
dummy.close();
return c;
} else {
sp.inheritList = { NULL };
return p.child(sp);
}
}

View File

@ -1,90 +0,0 @@
#pragma once
#include <windows.h>
#include <iostream>
#include <string>
#include <tuple>
#include <vector>
#include "NtHandleQuery.h"
#include "RemoteHandle.h"
#include "Spawn.h"
class RemoteWorker;
#define CHECK(cond) \
do { \
if (!(cond)) { \
recordFailure(__FUNCTION__); \
trace("%s:%d: ERROR: check failed: " #cond, __FILE__, __LINE__); \
std::cout << __FILE__ << ":" << __LINE__ \
<< (": ERROR: check failed: " #cond) \
<< std::endl; \
} \
} while(0)
#define CHECK_EQ(actual, expected) \
do { \
auto a = (actual); \
auto e = (expected); \
if (a != e) { \
recordFailure(__FUNCTION__); \
trace("%s:%d: ERROR: check failed " \
"(" #actual " != " #expected ")", __FILE__, __LINE__); \
std::cout << __FILE__ << ":" << __LINE__ \
<< ": ERROR: check failed " \
<< ("(" #actual " != " #expected "): ") \
<< a << " != " << e \
<< std::endl; \
} \
} while(0)
#define REGISTER(name, cond) \
static void name(); \
int g_register_ ## cond ## _ ## name = (registerTest(#name, cond, name), 0)
template <typename T>
static void extendVector(std::vector<T> &base, const std::vector<T> &to_add) {
base.insert(base.end(), to_add.begin(), to_add.end());
}
// Test registration
void printTestName(const std::string &name);
void recordFailure(const std::string &name);
std::vector<std::string> failedTests();
void registerTest(const std::string &name, bool(&cond)(), void(&func)());
using RegistrationTable = std::vector<std::tuple<std::string, bool(*)(), void(*)()>>;
RegistrationTable registeredTests();
inline bool always() { return true; }
bool compareObjectHandles(RemoteHandle h1, RemoteHandle h2);
// NT kernel handle->object snapshot
class ObjectSnap {
public:
ObjectSnap();
uint64_t object(RemoteHandle h);
bool eq(std::initializer_list<RemoteHandle> handles);
bool eq(RemoteHandle h1, RemoteHandle h2) { return eq({h1, h2}); }
private:
bool m_hasTable = false;
std::vector<SYSTEM_HANDLE_ENTRY> m_table;
};
// Misc
std::tuple<RemoteHandle, RemoteHandle> newPipe(
RemoteWorker &w, BOOL inheritable=FALSE);
std::string windowText(HWND hwnd);
// "domain-specific" routines: perhaps these belong outside the harness?
void checkInitConsoleHandleSet(RemoteWorker &child);
void checkInitConsoleHandleSet(RemoteWorker &child, RemoteWorker &source);
bool isUsableConsoleHandle(RemoteHandle h);
bool isUsableConsoleInputHandle(RemoteHandle h);
bool isUsableConsoleOutputHandle(RemoteHandle h);
bool isUnboundConsoleObject(RemoteHandle h);
void checkModernConsoleHandleInit(RemoteWorker &proc,
bool in, bool out, bool err);
RemoteWorker childWithDummyInheritList(RemoteWorker &p, SpawnParams sp,
bool dummyPipeInInheritList);

View File

@ -1,44 +0,0 @@
#include "UnicodeConversions.h"
#include <windows.h>
#include <vector>
#include <WinptyAssert.h>
std::string narrowString(const std::wstring &input)
{
int mblen = WideCharToMultiByte(
CP_UTF8, 0,
input.data(), input.size(),
NULL, 0, NULL, NULL);
if (mblen <= 0) {
return std::string();
}
std::vector<char> tmp(mblen);
int mblen2 = WideCharToMultiByte(
CP_UTF8, 0,
input.data(), input.size(),
tmp.data(), tmp.size(),
NULL, NULL);
ASSERT(mblen2 == mblen);
return std::string(tmp.data(), tmp.size());
}
std::wstring widenString(const std::string &input)
{
int widelen = MultiByteToWideChar(
CP_UTF8, 0,
input.data(), input.size(),
NULL, 0);
if (widelen <= 0) {
return std::wstring();
}
std::vector<wchar_t> tmp(widelen);
int widelen2 = MultiByteToWideChar(
CP_UTF8, 0,
input.data(), input.size(),
tmp.data(), tmp.size());
ASSERT(widelen2 == widelen);
return std::wstring(tmp.data(), tmp.size());
}

View File

@ -1,6 +0,0 @@
#pragma once
#include <string>
std::string narrowString(const std::wstring &input);
std::wstring widenString(const std::string &input);

View File

@ -1,126 +0,0 @@
#include "Util.h"
#include <windows.h>
#include <cstdint>
#include <sstream>
#include <string>
#include "UnicodeConversions.h"
#include <OsModule.h>
#include <WinptyAssert.h>
namespace {
static std::string timeString() {
FILETIME fileTime;
GetSystemTimeAsFileTime(&fileTime);
auto ret = ((uint64_t)fileTime.dwHighDateTime << 32) |
fileTime.dwLowDateTime;
return std::to_string(ret);
}
} // anonymous namespace
std::string pathDirName(const std::string &path)
{
std::string::size_type pos = path.find_last_of("\\/");
if (pos == std::string::npos) {
return std::string();
} else {
return path.substr(0, pos);
}
}
// Wrapper for GetModuleFileNameW. Returns a UTF-8 string. Aborts on error.
std::string getModuleFileName(HMODULE module)
{
const DWORD size = 4096;
wchar_t filename[size];
DWORD actual = GetModuleFileNameW(module, filename, size);
ASSERT(actual > 0 && actual < size);
return narrowString(filename);
}
// Convert GetLastError()'s error code to a presentable message such as:
//
// <87:The parameter is incorrect.>
//
std::string errorString(DWORD errCode) {
// MSDN has this note about "Windows 10":
//
// Windows 10:
//
// LocalFree is not in the modern SDK, so it cannot be used to free
// the result buffer. Instead, use HeapFree (GetProcessHeap(),
// allocatedMessage). In this case, this is the same as calling
// LocalFree on memory.
//
// Important: LocalAlloc() has different options: LMEM_FIXED, and
// LMEM_MOVABLE. FormatMessage() uses LMEM_FIXED, so HeapFree can be
// used. If LMEM_MOVABLE is used, HeapFree cannot be used.
//
// My interpretation of this note is:
// * "Windows 10" really just means, "the latest MS SDK", which supports
// Windows 10, as well as older releases.
// * In every NT kernel ever, HeapFree is perfectly fine to use with
// LocalAlloc LMEM_FIXED allocations.
// * In every NT kernel ever, the FormatMessage buffer can be freed with
// HeapFree.
// The note is clumsy, though. Without clarity, I can't safely use
// HeapFree, but apparently LocalFree calls stop compiling in the newest
// SDK.
//
// Instead, I'll use a fixed-size buffer.
std::stringstream ss;
ss << "<" << errCode << ":";
std::vector<wchar_t> msgBuf(1024);
DWORD ret = FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
errCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
msgBuf.data(),
msgBuf.size(),
nullptr);
if (ret == 0) {
ss << "FormatMessageW failed:";
ss << GetLastError();
} else {
msgBuf[msgBuf.size() - 1] = L'\0';
std::string msg = narrowString(std::wstring(msgBuf.data()));
if (msg.size() >= 2 && msg.substr(msg.size() - 2) == "\r\n") {
msg.resize(msg.size() - 2);
}
ss << msg;
}
ss << ">";
return ss.str();
}
bool isWow64() {
static bool valueInitialized = false;
static bool value = false;
if (!valueInitialized) {
OsModule kernel32(L"kernel32.dll");
auto proc = reinterpret_cast<decltype(IsWow64Process)*>(
kernel32.proc("IsWow64Process"));
BOOL isWow64 = FALSE;
BOOL ret = proc(GetCurrentProcess(), &isWow64);
value = ret && isWow64;
valueInitialized = true;
}
return value;
}
std::string makeTempName(const std::string &baseName) {
static int workerCounter = 0;
static auto initialTimeString = timeString();
return baseName + "-" +
std::to_string(static_cast<int>(GetCurrentProcessId())) + "-" +
initialTimeString + "-" +
std::to_string(++workerCounter);
}

View File

@ -1,12 +0,0 @@
#pragma once
#include <windows.h>
#include <string>
#include <vector>
std::string pathDirName(const std::string &path);
std::string getModuleFileName(HMODULE module);
std::string errorString(DWORD errCode);
bool isWow64();
std::string makeTempName(const std::string &baseName);

View File

@ -1,355 +0,0 @@
#include <windows.h>
#include <stdint.h>
#include <stdio.h>
#include <algorithm>
#include <vector>
#include "Command.h"
#include "Event.h"
#include "NtHandleQuery.h"
#include "OsVersion.h"
#include "ShmemParcel.h"
#include "Spawn.h"
#include <DebugClient.h>
static const char *g_prefix = "";
static const char *successOrFail(BOOL ret) {
return ret ? "ok" : "FAILED";
}
static HANDLE openConHandle(const wchar_t *name, BOOL bInheritHandle) {
// If sa isn't provided, the handle defaults to not-inheritable.
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(sa);
sa.bInheritHandle = bInheritHandle;
trace("%sOpening %ls...", g_prefix, name);
HANDLE conout = CreateFileW(name,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa,
OPEN_EXISTING, 0, NULL);
trace("%sOpening %ls... 0x%I64x", g_prefix, name, (int64_t)conout);
return conout;
}
static HANDLE createBuffer(BOOL bInheritHandle) {
// If sa isn't provided, the handle defaults to not-inheritable.
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(sa);
sa.bInheritHandle = bInheritHandle;
trace("%sCreating a new buffer...", g_prefix);
HANDLE conout = CreateConsoleScreenBuffer(
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa,
CONSOLE_TEXTMODE_BUFFER, NULL);
trace("%sCreating a new buffer... 0x%I64x", g_prefix, (int64_t)conout);
return conout;
}
static void writeTest(HANDLE conout, const char *msg) {
char writeData[256];
sprintf(writeData, "%s%s\n", g_prefix, msg);
trace("%sWriting to 0x%I64x: '%s'...",
g_prefix, (int64_t)conout, msg);
DWORD actual = 0;
BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL);
trace("%sWriting to 0x%I64x: '%s'... %s",
g_prefix, (int64_t)conout, msg,
successOrFail(ret && actual == strlen(writeData)));
}
static void setConsoleActiveScreenBuffer(HANDLE conout) {
trace("SetConsoleActiveScreenBuffer(0x%I64x) called...",
(int64_t)conout);
trace("SetConsoleActiveScreenBuffer(0x%I64x) called... %s",
(int64_t)conout,
successOrFail(SetConsoleActiveScreenBuffer(conout)));
}
static void dumpStandardHandles() {
trace("stdin=0x%I64x stdout=0x%I64x stderr=0x%I64x",
(int64_t)GetStdHandle(STD_INPUT_HANDLE),
(int64_t)GetStdHandle(STD_OUTPUT_HANDLE),
(int64_t)GetStdHandle(STD_ERROR_HANDLE));
}
static std::vector<HANDLE> scanForConsoleHandles() {
std::vector<HANDLE> ret;
if (isModernConio()) {
// As of Windows 8, console handles are real kernel handles.
for (unsigned int i = 0x4; i <= 0x1000; i += 4) {
HANDLE h = reinterpret_cast<HANDLE>(i);
DWORD mode;
if (GetConsoleMode(h, &mode)) {
ret.push_back(h);
}
}
} else {
for (unsigned int i = 0x3; i < 0x3 + 100 * 4; i += 4) {
HANDLE h = reinterpret_cast<HANDLE>(i);
DWORD mode;
if (GetConsoleMode(h, &mode)) {
ret.push_back(h);
}
}
}
return ret;
}
static void dumpConsoleHandles(bool writeToEach) {
std::string dumpLine = "";
for (HANDLE h : scanForConsoleHandles()) {
char buf[32];
sprintf(buf, "0x%I64x", (int64_t)h);
dumpLine += buf;
dumpLine.push_back('(');
CONSOLE_SCREEN_BUFFER_INFO info;
bool is_output = false;
DWORD count;
if (GetNumberOfConsoleInputEvents(h, &count)) {
dumpLine.push_back('I');
}
if (GetConsoleScreenBufferInfo(h, &info)) {
is_output = true;
dumpLine.push_back('O');
CHAR_INFO charInfo;
SMALL_RECT readRegion = {};
if (ReadConsoleOutputW(h, &charInfo, {1,1}, {0,0}, &readRegion)) {
wchar_t ch = charInfo.Char.UnicodeChar;
if (ch != L' ') {
dumpLine.push_back((char)ch);
}
}
}
{
DWORD flags = 0;
if (GetHandleInformation(h, &flags)) {
dumpLine.push_back((flags & HANDLE_FLAG_INHERIT) ? '^' : '_');
}
}
dumpLine += ") ";
if (writeToEach && is_output) {
char msg[256];
sprintf(msg, "%d: Writing to 0x%I64x",
(int)GetCurrentProcessId(), (int64_t)h);
writeTest(h, msg);
}
}
trace("Valid console handles:%s", dumpLine.c_str());
}
template <typename T>
void handleConsoleIoCommand(Command &cmd, T func) {
const auto sz = cmd.u.consoleIo.bufferSize;
ASSERT(static_cast<size_t>(sz.X) * sz.Y <= cmd.u.consoleIo.buffer.size());
cmd.success = func(cmd.handle, cmd.u.consoleIo.buffer.data(),
cmd.u.consoleIo.bufferSize, cmd.u.consoleIo.bufferCoord,
&cmd.u.consoleIo.ioRegion);
}
int main(int argc, char *argv[]) {
std::string workerName = argv[1];
ShmemParcelTyped<Command> parcel(workerName + "-shmem", ShmemParcel::OpenExisting);
Event startEvent(workerName + "-start");
Event finishEvent(workerName + "-finish");
Command &cmd = parcel.value();
dumpStandardHandles();
while (true) {
startEvent.wait();
startEvent.reset();
switch (cmd.kind) {
case Command::AllocConsole:
trace("Calling AllocConsole...");
cmd.success = AllocConsole();
trace("Calling AllocConsole... %s",
successOrFail(cmd.success));
break;
case Command::AttachConsole:
trace("Calling AttachConsole(%u)...",
(unsigned int)cmd.dword);
cmd.success = AttachConsole(cmd.dword);
trace("Calling AttachConsole(%u)... %s",
(unsigned int)cmd.dword, successOrFail(cmd.success));
break;
case Command::Close:
trace("closing 0x%I64x...",
(int64_t)cmd.handle);
cmd.success = CloseHandle(cmd.handle);
trace("closing 0x%I64x... %s",
(int64_t)cmd.handle, successOrFail(cmd.success));
break;
case Command::CloseQuietly:
cmd.success = CloseHandle(cmd.handle);
break;
case Command::DumpStandardHandles:
dumpStandardHandles();
break;
case Command::DumpConsoleHandles:
dumpConsoleHandles(cmd.writeToEach);
break;
case Command::Duplicate: {
HANDLE sourceHandle = cmd.handle;
cmd.success = DuplicateHandle(
GetCurrentProcess(),
sourceHandle,
cmd.targetProcess,
&cmd.handle,
0, cmd.bInheritHandle, DUPLICATE_SAME_ACCESS);
if (!cmd.success) {
cmd.handle = INVALID_HANDLE_VALUE;
}
trace("dup 0x%I64x to pid %u... %s, 0x%I64x",
(int64_t)sourceHandle,
(unsigned int)GetProcessId(cmd.targetProcess),
successOrFail(cmd.success),
(int64_t)cmd.handle);
break;
}
case Command::Exit:
trace("exiting");
ExitProcess(cmd.dword);
break;
case Command::FreeConsole:
trace("Calling FreeConsole...");
cmd.success = FreeConsole();
trace("Calling FreeConsole... %s", successOrFail(cmd.success));
break;
case Command::GetConsoleProcessList:
cmd.dword = GetConsoleProcessList(cmd.u.processList.data(),
cmd.u.processList.size());
break;
case Command::GetConsoleScreenBufferInfo:
cmd.u.consoleScreenBufferInfo = {};
cmd.success = GetConsoleScreenBufferInfo(
cmd.handle, &cmd.u.consoleScreenBufferInfo);
break;
case Command::GetConsoleSelectionInfo:
cmd.u.consoleSelectionInfo = {};
cmd.success = GetConsoleSelectionInfo(&cmd.u.consoleSelectionInfo);
break;
case Command::GetConsoleTitle:
// GetConsoleTitle is buggy, so make the worker API for it very
// explicit so we can test its bugginess.
ASSERT(cmd.dword <= cmd.u.consoleTitle.size());
cmd.dword = GetConsoleTitleW(cmd.u.consoleTitle.data(), cmd.dword);
break;
case Command::GetConsoleWindow:
cmd.hwnd = GetConsoleWindow();
break;
case Command::GetHandleInformation:
cmd.success = GetHandleInformation(cmd.handle, &cmd.dword);
break;
case Command::GetNumberOfConsoleInputEvents:
cmd.success = GetNumberOfConsoleInputEvents(cmd.handle, &cmd.dword);
break;
case Command::GetStdin:
cmd.handle = GetStdHandle(STD_INPUT_HANDLE);
break;
case Command::GetStderr:
cmd.handle = GetStdHandle(STD_ERROR_HANDLE);
break;
case Command::GetStdout:
cmd.handle = GetStdHandle(STD_OUTPUT_HANDLE);
break;
case Command::Hello:
// NOOP for Worker startup synchronization.
break;
case Command::LookupKernelObject: {
uint64_t h64;
memcpy(&h64, &cmd.lookupKernelObject.handle, sizeof(h64));
auto handles = queryNtHandles();
uint64_t result =
reinterpret_cast<uint64_t>(
ntHandlePointer(
handles, cmd.lookupKernelObject.pid,
reinterpret_cast<HANDLE>(h64)));
memcpy(&cmd.lookupKernelObject.kernelObject,
&result, sizeof(result));
trace("LOOKUP: p%d: 0x%I64x => 0x%I64x",
(int)cmd.lookupKernelObject.pid,
h64,
result);
break;
}
case Command::NewBuffer:
cmd.handle = createBuffer(cmd.bInheritHandle);
break;
case Command::OpenConin:
cmd.handle = openConHandle(L"CONIN$", cmd.bInheritHandle);
break;
case Command::OpenConout:
cmd.handle = openConHandle(L"CONOUT$", cmd.bInheritHandle);
break;
case Command::ReadConsoleOutput:
handleConsoleIoCommand(cmd, ReadConsoleOutputW);
break;
case Command::ScanForConsoleHandles: {
auto ret = scanForConsoleHandles();
ASSERT(ret.size() <= cmd.u.scanForConsoleHandles.table.size());
cmd.u.scanForConsoleHandles.count = ret.size();
std::copy(ret.begin(), ret.end(),
cmd.u.scanForConsoleHandles.table.begin());
break;
}
case Command::SetConsoleTitle: {
auto nul = std::find(cmd.u.consoleTitle.begin(),
cmd.u.consoleTitle.end(), L'\0');
ASSERT(nul != cmd.u.consoleTitle.end());
cmd.success = SetConsoleTitleW(cmd.u.consoleTitle.data());
break;
}
case Command::SetHandleInformation:
cmd.success = SetHandleInformation(
cmd.handle, cmd.u.setFlags.mask, cmd.u.setFlags.flags);
break;
case Command::SetStdin:
SetStdHandle(STD_INPUT_HANDLE, cmd.handle);
trace("setting stdin to 0x%I64x", (int64_t)cmd.handle);
break;
case Command::SetStderr:
SetStdHandle(STD_ERROR_HANDLE, cmd.handle);
trace("setting stderr to 0x%I64x", (int64_t)cmd.handle);
break;
case Command::SetStdout:
SetStdHandle(STD_OUTPUT_HANDLE, cmd.handle);
trace("setting stdout to 0x%I64x", (int64_t)cmd.handle);
break;
case Command::SetActiveBuffer:
setConsoleActiveScreenBuffer(cmd.handle);
break;
case Command::SpawnChild:
trace("Spawning child...");
cmd.handle = spawn(cmd.u.spawn.spawnName.str(),
cmd.u.spawn.spawnParams,
cmd.u.spawn.spawnFailure);
if (cmd.handle != nullptr) {
trace("Spawning child... pid %u",
(unsigned int)GetProcessId(cmd.handle));
}
break;
case Command::System:
cmd.dword = system(cmd.u.systemText.c_str());
break;
case Command::WriteConsoleOutput:
handleConsoleIoCommand(cmd, WriteConsoleOutputW);
break;
case Command::WriteText:
writeTest(cmd.handle, cmd.u.writeText.c_str());
break;
}
finishEvent.set();
}
return 0;
}

View File

@ -1,22 +0,0 @@
// Precompiled Headers.
//
// Unfortunate evil that speeds up compile times. In principle, compilation
// should still work if PCH is turned off.
//
#include <windows.h>
#include <algorithm>
#include <array>
#include <cassert>
#include <cstdio>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <cwchar>
#include <iostream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

View File

@ -1,7 +0,0 @@
TESTS += \
build/Test_GetConsoleTitleW.exe \
build/Win7Bug_InheritHandles.exe \
build/Win7Bug_RaceCondition.exe
# To add tests that aren't checked-in, create an mk file of this name:
-include local_tests.mk

View File

@ -1,547 +0,0 @@
Console Handles and Standard Handles
====================================
This document attempts to explain how console handles and standard handles
work, and how they interact with process creation and console attachment and
detachment. It is based on experiments that I ran against various versions of
Windows from Windows XP to Windows 10.
The information here is verified by the test suite in the `misc/buffer-tests`
directory. It should be taken with a grain of salt. I don't have access
to many operating systems. There may be important things I didn't think to
test. Some of the behavior is surprising, so it's hard to be sure I have
fully identified the behavior.
Feel free to report errors or omissions. An easy thing to do is to run the
accompanying test suite and report errors. The [test suite](#test-suite) is
designed to expect bugs on the appropriate Windows releases.
Table of Contents
-----------------
* [Common semantics](#common-semantics)
* [Traditional semantics](#traditional-semantics)
* [Console handles and handle sets (traditional)](#console-handles-and-handle-sets-traditional)
* [CreateProcess (traditional)](#createprocess-traditional)
* [AllocConsole, AttachConsole (traditional)](#allocconsole-attachconsole-traditional)
* [FreeConsole (traditional)](#freeconsole-traditional)
* [Modern semantics](#modern-semantics)
* [Console handles (modern)](#console-handles-modern)
* [CreateProcess (modern)](#createprocess-modern)
* [AllocConsole, AttachConsole (modern)](#allocconsole-attachconsole-modern)
* [Implicit screen buffer refcount](#implicit-screen-buffer-refcount)
* [FreeConsole (modern)](#freeconsole-modern)
* [Interesting properties](#interesting-properties)
* [Other notes](#other-notes)
* [SetActiveConsoleScreenBuffer](#setactiveconsolescreenbuffer)
* [CREATE_NO_WINDOW process creation flag](#create_no_window-process-creation-flag)
* [PROC_THREAD_ATTRIBUTE_HANDLE_LIST](#proc_thread_attribute_handle_list)
* [Bugs](#bugs)
* [Windows XP does not duplicate a pipe's read handle [xppipe]](#windows-xp-does-not-duplicate-a-pipes-read-handle-xppipe)
* [Windows XP duplication inheritability [xpinh]](#windows-xp-duplication-inheritability-xpinh)
* [CreateProcess duplicates `INVALID_HANDLE_VALUE` until Windows 8.1 [dupproc]](#createprocess-duplicates-invalid_handle_value-until-windows-81-dupproc)
* [CreateProcess duplication broken w/WOW64 [wow64dup]](#createprocess-duplication-broken-wwow64-wow64dup)
* [Windows Vista BSOD](#windows-vista-bsod)
* [Windows 7 inheritability [win7inh]](#windows-7-inheritability-win7inh)
* [Windows 7 conhost.exe crash with `CONOUT$` [win7_conout_crash]](#windows-7-conhostexe-crash-with-conout-win7_conout_crash)
* [Test suite](#test-suite)
* [Footnotes](#footnotes)
Common semantics
----------------
There are three flags to `CreateProcess` that affect what console a new console
process is attached to:
- `CREATE_NEW_CONSOLE`
- `CREATE_NO_WINDOW`
- `DETACHED_PROCESS`
These flags are interpreted to produce what I will call the *CreationConsoleMode*.
`CREATE_NO_WINDOW` is ignored if combined with either other flag, and the
combination of `CREATE_NEW_CONSOLE` and `DETACHED_PROCESS` is an error:
| Criteria | Resulting *CreationConsoleMode* |
| ------------------------------------------- | ------------------------------------- |
| None of the flags (parent has a console) | *Inherit* |
| None of the flags (parent has no console) | *NewConsole* |
| `CREATE_NEW_CONSOLE` | *NewConsole* |
| `CREATE_NEW_CONSOLE | CREATE_NO_WINDOW` | *NewConsole* |
| `CREATE_NO_WINDOW` | *NewConsoleNoWindow* |
| `DETACHED_PROCESS` | *Detach* |
| `DETACHED_PROCESS | CREATE_NO_WINDOW` | *Detach* |
| `CREATE_NEW_CONSOLE | DETACHED_PROCESS` | none - the `CreateProcess` call fails |
| All three flags | none - the `CreateProcess` call fails |
Windows' behavior depends on the *CreationConsoleMode*:
* *NewConsole* or *NewConsoleNoWindow*: Windows attaches the new process to
a new console. *NewConsoleNoWindow* is special--it creates an invisible
console. (Prior to Windows 7, `GetConsoleWindow` returned a handle to an
invisible window. Starting with Windows 7, `GetConsoleWindow` returns
`NULL`.)
* *Inherit*: The child attaches to its parent's console.
* *Detach*: The child has no attached console, even if its parent had one.
I have not tested whether or how these flags affect non-console programs (i.e.
programs whose PE header subsystem is `WINDOWS` rather than `CONSOLE`).
There is one other `CreateProcess` flag that plays an important role in
understanding console handles -- `STARTF_USESTDHANDLES`. This flag influences
whether the `AllocConsole` and `AttachConsole` APIs change the
"standard handles" (`STDIN/STDOUT/STDERR`) during the lifetime of the
new process, as well as the new process' initial standard handles, of course.
The standard handles are accessed with `GetStdHandle`
and `SetStdHandle`, which [are effectively wrappers around a global
`HANDLE[3]` variable](http://blogs.msdn.com/b/oldnewthing/archive/2013/03/07/10399690.aspx)
-- these APIs do not use `DuplicateHandle` or `CloseHandle`
internally, and [while NT kernels objects are reference counted, `HANDLE`s
are not](http://blogs.msdn.com/b/oldnewthing/archive/2007/08/29/4620336.aspx).
The `FreeConsole` API detaches a process from its console, but it never alters
the standard handles.
(Note that by "standard handles", I am strictly referring to `HANDLE` values
and not `int` file descriptors or `FILE*` file streams provided by the C
language. C and C++ standard I/O is implemented on top of Windows `HANDLE`s.)
Traditional semantics
---------------------
### Console handles and handle sets (traditional)
In releases prior to Windows 8, console handles are not true NT handles.
Instead, the values are always multiples of four minus one (i.e. 0x3, 0x7,
0xb, 0xf, ...), and the functions in `kernel32.dll` detect the special handles
and perform LPCs to `csrss.exe` and/or `conhost.exe`.
A new console's initial console handles are always inheritable, but
non-inheritable handles can also be created. The inheritability can
be changed, except on Windows 7 (see [[win7inh]](#win7inh)).
Traditional console handles cannot be duplicated to other processes. If such
a handle is used with `DuplicateHandle`, the source and target process handles
must be the `GetCurrentProcess()` pseudo-handle, not a real handle to the
current process.
Whenever a process creates a new console (either during startup or when it
calls `AllocConsole`), Windows replaces that process' set of open
console handles (its *ConsoleHandleSet*) with three inheritable handles
(0x3, 0x7, 0xb). Whenever a process attaches to an existing console (either
during startup or when it calls `AttachConsole`), Windows completely replaces
that process' *ConsoleHandleSet* with the set of inheritable open handles
from the originating process. These "imported" handles are also inheritable.
### CreateProcess (traditional)
The manner in which Windows sets standard handles is influenced by two flags:
- Whether `STARTF_USESTDHANDLES` was set in `STARTUPINFO` when the process
started (*UseStdHandles*)
- Whether the `CreateProcess` parameter, `bInheritHandles`, was `TRUE`
(*InheritHandles*)
From Window XP up until Windows 8, `CreateProcess` sets standard handles using
the first matching rule:
1. If *UseStdHandles*, then the child uses the `STARTUPINFO` fields. Windows
makes no attempt to validate the handles, nor will it treat a
non-inheritable handle as inheritable simply because it is listed in
`STARTUPINFO`.
2. If *ConsoleCreationMode* is *NewConsole* or *NewConsoleNoWindow*, then
Windows sets the handles to (0x3, 0x7, 0xb).
3. If *ConsoleCreationMode* is *Detach*, then Windows sets the handles to
(`NULL`, `NULL`, `NULL`).
4. If *InheritHandles*, then the parent's standard handles are copied as-is
to the child, without exception.
5. Windows duplicates each
of the parent's non-console standard handles into the child. Any
standard handle that looks like a traditional console handle, up to
0x0FFFFFFF, is copied as-is, whether or not the handle is open.
<sup>[[1]](#foot_dup_noninherit_con)</sup>
If Windows fails to duplicate a handle for any reason (e.g. because
it is `NULL` or not open), then the child's new handle is `NULL`.
The child handles have the same inheritability as the parent handles.
These handles are not closed by `FreeConsole`.
(Bugs:
[[xppipe]](#xppipe)
[[xpinh]](#xpinh)
[[dupproc]](#dupproc)
[[wow64dup]](#wow64dup))
The `bInheritHandles` parameter to `CreateProcess` does not affect whether
console handles are inherited. Console handles are inherited if and only if
they are marked inheritable. The `PROC_THREAD_ATTRIBUTE_HANDLE_LIST`
attribute added in Vista does not restrict console handle inheritance, and
erratic behavior may result from specifying a traditional console handle in
`PROC_THREAD_ATTRIBUTE_HANDLE_LIST`'s `HANDLE` list. (See the
`Test_CreateProcess_STARTUPINFOEX` test in `misc/buffer-tests`.)
### AllocConsole, AttachConsole (traditional)
`AllocConsole` and `AttachConsole` set the standard handles as follows:
- If *UseStdHandles*, then Windows does not modify the standard handles.
- If !*UseStdHandles*, then Windows changes the standard handles to
(0x3, 0x7, 0xb), even if those handles are not open.
### FreeConsole (traditional)
After calling `FreeConsole`, no console APIs work, and all previous console
handles are apparently closed -- even `GetHandleInformation` fails on the
handles. `FreeConsole` has no effect on the `STDIN/STDOUT/STDERR` values.
Modern semantics
----------------
### Console handles (modern)
Starting with Windows 8, console handles are true NT kernel handles that
reference NT kernel objects.
If a process is attached to a console, then it will have two handles open
to `\Device\ConDrv` that Windows uses internally. These handles are never
observable by the user program. (To view them, use `handle.exe` from
sysinternals, i.e. `handle.exe -a -p <pid>`.) A process with no attached
console never has these two handles open.
Ordinary I/O console handles are also associated with `\Device\ConDrv`. The
underlying console objects can be classified in two ways:
- *Input* vs *Output*
- *Bound* vs *Unbound*
A *Bound* *Input* object is tied to a particular console, and a *Bound*
*Output* object is tied to a particular console screen buffer. These
objects are usable only if the process is attached to the correct
console. *Bound* objects are created through these methods only:
- `CreateConsoleScreenBuffer`
- opening `CONIN$` or `CONOUT$`
Most console objects are *Unbound*, which are created during console
initialization. For any given console API call, an *Unbound* *Input* object
refers to the currently attached console's input queue, and an *Unbound*
*Output* object refers to the screen buffer that was active during the calling
process' console initialization. These objects are usable as long as the
calling process has any console attached.
Unlike traditional console handles, modern console handles **can** be
duplicated to other processes.
### CreateProcess (modern)
Whenever a process is attached to a console (during startup, `AttachConsole`,
or `AllocConsole`), Windows will sometimes create new *Unbound* console
objects and assign them to one or more standard handles. If it assigns
to both `STDOUT` and `STDERR`, it reuses the same new *Unbound*
*Output* object for both.
As with previous releases, standard handle determination is affected by the
*UseStdHandles* and *InheritHandles* flags.
Each of the child's standard handles is set using the first match:
1. If *InheritHandles*, *UseStdHandles*, and the relevant `STARTUPINFO`
field is non-`NULL`, then Windows uses the `STARTUPINFO` field. As with
previous releases, Windows makes no effort to validate the handle, nor
will it treat a non-inheritable handle as inheritable simply because it
is listed in `STARTUPINFO`. <sup>[[2]](#foot_explicit_stdhnd_con)</sup>
2. If *CreationConsoleMode* is *NewConsole* or *NewConsoleNoWindow*, then
Windows opens a handle to a new *Unbound* console object. This handle will
be closed if `FreeConsole` is later called. (N.B.: Windows reuses the
same *Unbound* output object if it creates handles for both `STDOUT` and
`STDERR`. The handles themselves are still different, though.)
3. If *ConsoleCreationMode* is *Detach*, then Windows sets the handles to
(`NULL`, `NULL`, `NULL`).
4. If *UseStdHandles*, the child's standard handle becomes `NULL`.
5. If *InheritHandles*, and there is no `PROC_THREAD_ATTRIBUTE_HANDLE_LIST`
specified, then the parent's standard handle is copied as-is.
6. The parent's standard handle is duplicated. As with previous releases, if
the handle cannot be duplicated, then the child's handle becomes `NULL`.
The child handle has the same inheritability as the parent handle.
`FreeConsole` does *not* close this handle, even if it happens to be a
console handle (which is not unlikely).
(Bugs: [[dupproc]](#dupproc))
### AllocConsole, AttachConsole (modern)
`AllocConsole` and `AttachConsole` set the standard handles as follows:
- If *UseStdHandles*, then Windows opens a console handle for each standard
handle that is currently `NULL`.
- If !*UseStdHandles*, then Windows opens three new console handles.
### Implicit screen buffer refcount
When a process' console state is initialized (at startup, `AllocConsole`
or `AttachConsole`), Windows increments a refcount on the console's
currently active screen buffer, which decrements only when the process
detaches from the console. All *Unbound* *Output* console objects reference
this screen buffer.
### FreeConsole (modern)
As in previous Windows releases, `FreeConsole` in Windows 8 does not change
the `STDIN/STDOUT/STDERR` values. If Windows opened new console handles for
`STDIN/STDOUT/STDERR` when it initialized the process' console state, then
`FreeConsole` will close those handles. Otherwise, `FreeConsole` will only
close the two internal handles.
### Interesting properties
* `FreeConsole` can close a non-console handle. This happens if:
1. Windows had opened handles during console initialization.
2. The program closes its standard handles and opens new non-console
handles with the same values.
3. The program calls `FreeConsole`.
(Perhaps programs are not expected to close their standard handles.)
* Console handles--*Bound* or *Unbound*--can be duplicated to other
processes. The duplicated handles are sometimes usable, especially
if *Unbound*. The same *Unbound* *Output* object can be open in two
different processes and refer to different screen buffers in the same
console or in different consoles.
* Even without duplicating console handles, it is possible to have open
console handles that are not usable, even with a console attached.
* Dangling *Bound* handles are not allowed, so it is possible to have
consoles with no attached processes. The console cannot be directly
modified (or attached to), but its visible content can be changed by
closing *Bound* *Output* handles to activate other screen buffers.
* A program that repeatedly reinvoked itself with `CREATE_NEW_CONSOLE` and
`bInheritHandles=TRUE` would accumulate console handles. Each child
would inherit all of the previous child's console handles, then allocate
three more for itself. All of the handles would be usable (if the
program kept track of them somehow).
Other notes
-----------
### SetActiveConsoleScreenBuffer
Screen buffers are referenced counted. Changing the active screen buffer
with `SetActiveConsoleScreenBuffer` does not increment a refcount on the
buffer. If the active buffer's refcount hits zero, then Windows chooses
another buffer and activates it.
### `CREATE_NO_WINDOW` process creation flag
The documentation for `CREATE_NO_WINDOW` is confusing:
> The process is a console application that is being run without a
> console window. Therefore, the console handle for the application is
> not set.
>
> This flag is ignored if the application is not a console application,
> or if it is used with either `CREATE_NEW_CONSOLE` or `DETACHED_PROCESS`.
Here's what's evident from examining the OS behavior:
* Specifying both `CREATE_NEW_CONSOLE` and `DETACHED_PROCESS` causes the
`CreateProcess` call to fail.
* If `CREATE_NO_WINDOW` is specified together with `CREATE_NEW_CONSOLE` or
`DETACHED_PROCESS`, it is quietly ignored, just as documented.
* Otherwise, `CreateProcess` behaves the same way with `CREATE_NO_WINDOW` as
it does with `CREATE_NEW_CONSOLE`, except that the new console either has
a hidden window (before Windows 7) or has no window at all (Windows 7
and later). These situations can be distinguished using the
`GetConsoleWindow` and `IsWindowVisible` calls. `GetConsoleWindow` returns
`NULL` starting with Windows 7.
### `PROC_THREAD_ATTRIBUTE_HANDLE_LIST`
The `PROC_THREAD_ATTRIBUTE_HANDLE_LIST` list cannot be empty; the
`UpdateProcThreadAttribute` call fails if `cbSize` is `0`. However, a list
containing a `NULL` is apparently OK and equivalent to an empty list.
Curiously, if the inherit list has both a non-`NULL` handle and a `NULL`
handle, the list is still treated as empty (i.e. the non-`NULL` handle is
not inherited).
Starting with Windows 8, `CreateProcess` duplicates the parent's handles into
the child when `PROC_THREAD_ATTRIBUTE_HANDLE_LIST` and these other parameters
are specified:
- *InheritHandles* is true
- *UseStdHandles* is false
- *CreationConsoleMode* is *Inherit*
Bugs
----
### <a name="xppipe">Windows XP does not duplicate a pipe's read handle [xppipe]</a>
On Windows XP, `CreateProcess` fails to duplicate a handle in this situation:
- `bInheritHandles` is `FALSE`.
- `STARTF_USESTDHANDLES` is not specified in `STARTUPINFO.dwFlags`.
- One of the `STDIN/STDOUT/STDERR` handles is set to the read end of an
anonymous pipe.
In this situation, Windows XP will set the child process's standard handle to
`NULL`. The write end of the pipe works fine. Passing a `bInheritHandles`
of `TRUE` (and an inheritable pipe handle) works fine. Using
`STARTF_USESTDHANDLES` also works. See `Test_CreateProcess_Duplicate_XPPipeBug`
in `misc/buffer-tests` for a test case.
### <a name="xpinh">Windows XP duplication inheritability [xpinh]</a>
When `CreateProcess` in XP duplicates an inheritable handle, the duplicated
handle is non-inheritable. In Vista and later, the new handle is also
inheritable.
### <a name="dupproc">`CreateProcess` duplicates `INVALID_HANDLE_VALUE` until Windows 8.1 [dupproc]</a>
From Windows XP to Windows 8, when `CreateProcess` duplicates parent standard
handles into the child, it duplicates `INVALID_HANDLE_VALUE` (aka the
`GetCurrentProcess()` pseudo-handle) to a true handle to the parent process.
This bug was fixed in Windows 8.1.
On some older operating systems, the WOW64 mode also translates
`INVALID_HANDLE_VALUE` to `NULL`.
### <a name="wow64dup">CreateProcess duplication broken w/WOW64 [wow64dup]</a>
On some versions of 64-bit Windows, when a 32-bit program invokes another
32-bit program, `CreateProcess`'s handle duplication does not occur.
Traditional console handles are passed through, but other handles are converted
to `NULL`. The problem does not occur when 64-bit programs invoke 64-bit
programs. (I have not tested 32-bit to 64-bit or vice versa.)
The problem affects at least:
- Windows 7 SP1
### Windows Vista BSOD
It is easy to cause a BSOD on Vista and Server 2008 by (1) closing all handles
to the last screen buffer, then (2) creating a new screen buffer:
#include <windows.h>
int main() {
FreeConsole();
AllocConsole();
CloseHandle((HANDLE)0x7);
CloseHandle((HANDLE)0xb);
CreateConsoleScreenBuffer(
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CONSOLE_TEXTMODE_BUFFER,
NULL);
return 0;
}
### <a name="win7inh">Windows 7 inheritability [win7inh]</a>
* Calling `DuplicateHandle(bInheritHandle=FALSE)` on an inheritable console
handle produces an inheritable handle, but it should be non-inheritable.
Previous and later Windows releases work as expected, as does Windows 7 with
a non-console handle.
* Calling `SetHandleInformation(dwMask=HANDLE_FLAG_INHERIT)` fails on console
handles, so the inheritability of an existing console handle cannot be
changed.
### <a name="win7_conout_crash">Windows 7 `conhost.exe` crash with `CONOUT$` [win7_conout_crash]</a>
There is a bug in Windows 7 involving `CONOUT$` and `CloseHandle` that can
easily crash `conhost.exe` and/or activate the wrong screen buffer. The
bug is triggered when a process without a handle to the active screen buffer
opens `CONOUT$` and then closes it using `CloseHandle`.
Here's what *seems* to be going on:
Each process may have at most one "console object" referencing
a particular buffer. A single console object can be shared between multiple
processes, and whenever console handles are imported (`CreateProcess` and
`AttachConsole`), the objects are reused.
If a process opens `CONOUT$`, however, and does not already have a reference
to the active screen buffer, then Windows creates a new console object. The
bug in Windows 7 is this: if a process calls `CloseHandle` on the last handle
for a console object, then the screen buffer is freed, even if there are other
handles/objects still referencing it. At that point, the console might display
the wrong screen buffer, but using the other handles to the buffer can return
garbage and/or crash `conhost.exe`. Closing a dangling handle is especially
likely to trigger a crash.
Rather than using `CloseHandle`, letting Windows automatically clean up a
console handle via `DetachConsole` or exiting somehow avoids the problem.
The bug affects Windows 7 SP1, but does not affect
Windows Server 2008 R2 SP1, the server version of the OS.
See `misc/buffer-tests/HandleTests/Win7_Conout_Crash.cc`.
Test suite
----------
To run the `misc/buffer-tests` test suite, follow the instructions for
building winpty. Then, enter the `misc/buffer-tests` directory, run `make`,
and then run `build/HandleTests.exe`.
For a WOW64 run:
* Build the 64-bit `Worker.exe`.
* Rename it to `Worker64.exe` and save it somewhere.
* Build the 32-bit binaries.
* Copy `Worker64.exe` to the `build` directory alongside `Worker.exe`.
Footnotes
---------
<a name="foot_dup_noninherit_con">1</a>: From the previous discussion,
it follows that if a standard handle is a non-inheritable console handle,
then the child's standard handle will be invalid:
- Traditional console standard handles are copied as-is to the child.
- The child has the same *ConsoleHandleSet* as the parent, excluding
non-inheritable handles.
It's an interesting edge case, though, so I test for it specifically. As of
Windows 8, the non-inheritable console handle would be successfully duplicated.
<a name="foot_explicit_stdhnd_con">2</a>: Suppose a console program invokes
`CreateProcess` with these parameters:
- `bInheritHandles` is `FALSE`.
- `STARTF_USESTDHANDLES` is set.
- `STARTUPINFO` refers to inheritable console handles (e.g. the default
standard handles)
Prior to Windows 8, the child would have received valid standard handles. As
of Windows 8, the child's standard handles will be `NULL` instead.