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:
parent
e235706503
commit
a4ba2420e7
1
misc/buffer-tests/.gitignore
vendored
1
misc/buffer-tests/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/build
|
@ -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.
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
};
|
@ -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");
|
||||
}
|
@ -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;
|
||||
};
|
@ -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];
|
||||
};
|
@ -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;
|
||||
}
|
@ -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);
|
@ -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();
|
||||
}
|
@ -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, ®ion, 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, ®ion, 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));
|
||||
}
|
@ -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);
|
@ -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();
|
||||
}
|
@ -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;
|
||||
};
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
};
|
@ -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 ¶ms,
|
||||
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;
|
||||
}
|
@ -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 ¶ms,
|
||||
SpawnFailure &error);
|
@ -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;
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
@ -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());
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
std::string narrowString(const std::wstring &input);
|
||||
std::wstring widenString(const std::string &input);
|
@ -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);
|
||||
}
|
@ -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);
|
@ -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;
|
||||
}
|
@ -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>
|
@ -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>
|
@ -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
|
@ -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.
|
Loading…
Reference in New Issue
Block a user