Add tests of screen buffer handles.

This commit is contained in:
Ryan Prichard 2015-10-19 23:32:08 -05:00
parent 4d52166b8c
commit 3bf4703f6a
25 changed files with 1989 additions and 5 deletions

672
misc/ScreenBufferTest.cc Executable file
View File

@ -0,0 +1,672 @@
//
// Windows versions tested
//
// Vista Enterprise SP2 32-bit
// - ver reports [Version 6.0.6002]
// - kernel32.dll product/file versions are 6.0.6002.19381
//
// Windows 7 Ultimate SP1 32-bit
// - ver reports [Version 6.1.7601]
// - conhost.exe product/file versions are 6.1.7601.18847
// - kernel32.dll product/file versions are 6.1.7601.18847
//
// Windows Server 2008 R2 Datacenter SP1 64-bit
// - ver reports [Version 6.1.7601]
// - conhost.exe product/file versions are 6.1.7601.23153
// - kernel32.dll product/file versions are 6.1.7601.23153
//
// Windows 8 Enterprise 32-bit
// - ver reports [Version 6.2.9200]
// - conhost.exe product/file versions are 6.2.9200.16578
// - kernel32.dll product/file versions are 6.2.9200.16859
//
//
// Specific version details on working Server 2008 R2:
//
// dwMajorVersion = 6
// dwMinorVersion = 1
// dwBuildNumber = 7601
// dwPlatformId = 2
// szCSDVersion = Service Pack 1
// wServicePackMajor = 1
// wServicePackMinor = 0
// wSuiteMask = 0x190
// wProductType = 0x3
//
// Specific version details on broken Win7:
//
// dwMajorVersion = 6
// dwMinorVersion = 1
// dwBuildNumber = 7601
// dwPlatformId = 2
// szCSDVersion = Service Pack 1
// wServicePackMajor = 1
// wServicePackMinor = 0
// wSuiteMask = 0x100
// wProductType = 0x1
//
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include "TestUtil.cc"
#include "../shared/DebugClient.cc"
const char *g_prefix = "";
static void dumpHandles() {
trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x",
g_prefix,
(long long)GetStdHandle(STD_INPUT_HANDLE),
(long long)GetStdHandle(STD_OUTPUT_HANDLE),
(long long)GetStdHandle(STD_ERROR_HANDLE));
}
static const char *successOrFail(BOOL ret) {
return ret ? "ok" : "FAILED";
}
static void startChildInSameConsole(const wchar_t *args, BOOL
bInheritHandles=FALSE) {
wchar_t program[1024];
wchar_t cmdline[1024];
GetModuleFileNameW(NULL, program, 1024);
swprintf(cmdline, L"\"%ls\" %ls", program, args);
STARTUPINFOW sui;
PROCESS_INFORMATION pi;
memset(&sui, 0, sizeof(sui));
memset(&pi, 0, sizeof(pi));
sui.cb = sizeof(sui);
CreateProcessW(program, cmdline,
NULL, NULL,
/*bInheritHandles=*/bInheritHandles,
/*dwCreationFlags=*/0,
NULL, NULL,
&sui, &pi);
}
static void closeHandle(HANDLE h) {
trace("%sClosing handle 0x%I64x...", g_prefix, (long long)h);
trace("%sClosing handle 0x%I64x... %s", g_prefix, (long long)h, successOrFail(CloseHandle(h)));
}
static HANDLE createBuffer() {
// If sa isn't provided, the handle defaults to not-inheritable.
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
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, (long long)conout);
return conout;
}
static HANDLE openConout() {
// If sa isn't provided, the handle defaults to not-inheritable.
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
trace("%sOpening CONOUT...", g_prefix);
HANDLE conout = CreateFileW(L"CONOUT$",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa,
OPEN_EXISTING, 0, NULL);
trace("%sOpening CONOUT... 0x%I64x", g_prefix, (long long)conout);
return conout;
}
static void setConsoleActiveScreenBuffer(HANDLE conout) {
trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called...",
g_prefix, (long long)conout);
trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called... %s",
g_prefix, (long long)conout,
successOrFail(SetConsoleActiveScreenBuffer(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, (long long)conout, msg);
DWORD actual = 0;
BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL);
trace("%sWriting to 0x%I64x: '%s'... %s",
g_prefix, (long long)conout, msg,
successOrFail(ret && actual == strlen(writeData)));
}
static void writeTest(const char *msg) {
writeTest(GetStdHandle(STD_OUTPUT_HANDLE), msg);
}
///////////////////////////////////////////////////////////////////////////////
// TEST 1 -- create new buffer, activate it, and close the handle. The console
// automatically switches the screen buffer back to the original.
//
// This test passes everywhere.
//
static void test1(int argc, char *argv[]) {
if (!strcmp(argv[1], "1")) {
startChildProcess(L"1:child");
return;
}
HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
writeTest(origBuffer, "<-- origBuffer -->");
HANDLE newBuffer = createBuffer();
writeTest(newBuffer, "<-- newBuffer -->");
setConsoleActiveScreenBuffer(newBuffer);
Sleep(2000);
writeTest(origBuffer, "TEST PASSED!");
// Closing the handle w/o switching the active screen buffer automatically
// switches the console back to the original buffer.
closeHandle(newBuffer);
while (true) {
Sleep(1000);
}
}
///////////////////////////////////////////////////////////////////////////////
// TEST 2 -- Test program that creates and activates newBuffer, starts a child
// process, then closes its newBuffer handle. newBuffer remains activated,
// because the child keeps it active. (Also see TEST D.)
//
static void test2(int argc, char *argv[]) {
if (!strcmp(argv[1], "2")) {
startChildProcess(L"2:parent");
return;
}
if (!strcmp(argv[1], "2:parent")) {
g_prefix = "parent: ";
dumpHandles();
HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
writeTest(origBuffer, "<-- origBuffer -->");
HANDLE newBuffer = createBuffer();
writeTest(newBuffer, "<-- newBuffer -->");
setConsoleActiveScreenBuffer(newBuffer);
Sleep(1000);
writeTest(newBuffer, "bInheritHandles=FALSE:");
startChildInSameConsole(L"2:child", FALSE);
Sleep(1000);
writeTest(newBuffer, "bInheritHandles=TRUE:");
startChildInSameConsole(L"2:child", TRUE);
Sleep(1000);
trace("parent:----");
// Close the new buffer. The active screen buffer doesn't automatically
// switch back to origBuffer, because the child process has a handle open
// to the original buffer.
closeHandle(newBuffer);
Sleep(600 * 1000);
return;
}
if (!strcmp(argv[1], "2:child")) {
g_prefix = "child: ";
dumpHandles();
// The child's output isn't visible, because it's still writing to
// origBuffer.
trace("child:----");
writeTest("writing to STDOUT");
// Handle inheritability is curious. The console handles this program
// creates are inheritable, but CreateProcess is called with both
// bInheritHandles=TRUE and bInheritHandles=FALSE.
//
// Vista and Windows 7: bInheritHandles has no effect. The child and
// parent processes have the same STDIN/STDOUT/STDERR handles:
// 0x3, 0x7, and 0xB. The parent has a 0xF handle for newBuffer.
// The child can only write to 0x7, 0xB, and 0xF. Only the writes to
// 0xF are visible (i.e. they touch newBuffer).
//
// Windows 8 or Windows 10 (legacy or non-legacy): the lowest 2 bits of
// the HANDLE to WriteConsole seem to be ignored. The new process'
// console handles always refer to the buffer that was active when they
// started, but the values of the handles depend upon bInheritHandles.
// With bInheritHandles=TRUE, the child has the same
// STDIN/STDOUT/STDERR/newBuffer handles as the parent, and the three
// output handles all work, though their output is all visible. With
// bInheritHandles=FALSE, the child has different STDIN/STDOUT/STDERR
// handles, and only the new STDOUT/STDERR handles work.
//
for (unsigned int i = 0x1; i <= 0xB0; ++i) {
char msg[256];
sprintf(msg, "Write to handle 0x%x", i);
HANDLE h = reinterpret_cast<HANDLE>(i);
writeTest(h, msg);
}
Sleep(600 * 1000);
return;
}
}
///////////////////////////////////////////////////////////////////////////////
// TEST A -- demonstrate an apparent Windows bug with screen buffers
//
// Steps:
// - The parent starts a child process.
// - The child process creates and activates newBuffer
// - The parent opens CONOUT$ and writes to it.
// - The parent closes CONOUT$.
// - At this point, broken Windows reactivates origBuffer.
// - The child writes to newBuffer again.
// - The child activates origBuffer again, then closes newBuffer.
//
// Test passes if the message "TEST PASSED!" is visible.
// Test commonly fails if conhost.exe crashes.
//
// Results:
// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
// - Windows 8 Enterprise 32-bit: PASS
// - Windows 10 64-bit (legacy and non-legacy): PASS
//
static void testA_parentWork() {
// Open an extra CONOUT$ handle so that the HANDLE values in parent and
// child don't collide. I think it's OK if they collide, but since we're
// trying to track down a Windows bug, it's best to avoid unnecessary
// complication.
HANDLE dummy = openConout();
Sleep(3000);
// Step 2: Open CONOUT$ in the parent. This opens the active buffer, which
// was just created in the child. It's handle 0x13. Write to it.
HANDLE newBuffer = openConout();
writeTest(newBuffer, "step2: writing to newBuffer");
Sleep(3000);
// Step 3: Close handle 0x13. With Windows 7, the console switches back to
// origBuffer, and (unless I'm missing something) it shouldn't.
closeHandle(newBuffer);
}
static void testA_childWork() {
HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
//
// Step 1: Create the new screen buffer in the child process and make it
// active. (Typically, it's handle 0x0F.)
//
HANDLE newBuffer = createBuffer();
setConsoleActiveScreenBuffer(newBuffer);
writeTest(newBuffer, "<-- newBuffer -->");
Sleep(9000);
trace("child:----");
// Step 4: write to the newBuffer again.
writeTest(newBuffer, "TEST PASSED!");
//
// Step 5: Switch back to the original screen buffer and close the new
// buffer. The switch call succeeds, but the CloseHandle call freezes for
// several seconds, because conhost.exe crashes.
//
Sleep(3000);
setConsoleActiveScreenBuffer(origBuffer);
writeTest(origBuffer, "writing to origBuffer");
closeHandle(newBuffer);
// The console HWND is NULL.
trace("child: console HWND=0x%I64x", (long long)GetConsoleWindow());
// At this point, the console window has closed, but the parent/child
// processes are still running. Calling AllocConsole would fail, but
// calling FreeConsole followed by AllocConsole would both succeed, and a
// new console would appear.
}
static void testA(int argc, char *argv[]) {
if (!strcmp(argv[1], "A")) {
startChildProcess(L"A:parent");
return;
}
if (!strcmp(argv[1], "A:parent")) {
g_prefix = "parent: ";
trace("parent:----");
dumpHandles();
writeTest("<-- origBuffer -->");
startChildInSameConsole(L"A:child");
testA_parentWork();
Sleep(120000);
return;
}
if (!strcmp(argv[1], "A:child")) {
g_prefix = "child: ";
dumpHandles();
testA_childWork();
Sleep(120000);
return;
}
}
///////////////////////////////////////////////////////////////////////////////
// TEST B -- invert TEST A -- also crashes conhost on Windows 7
//
// Test passes if the message "TEST PASSED!" is visible.
// Test commonly fails if conhost.exe crashes.
//
// Results:
// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
// - Windows 8 Enterprise 32-bit: PASS
// - Windows 10 64-bit (legacy and non-legacy): PASS
//
static void testB(int argc, char *argv[]) {
if (!strcmp(argv[1], "B")) {
startChildProcess(L"B:parent");
return;
}
if (!strcmp(argv[1], "B:parent")) {
g_prefix = "parent: ";
startChildInSameConsole(L"B:child");
writeTest("<-- origBuffer -->");
HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
//
// Step 1: Create the new buffer and make it active.
//
trace("%s----", g_prefix);
HANDLE newBuffer = createBuffer();
setConsoleActiveScreenBuffer(newBuffer);
writeTest(newBuffer, "<-- newBuffer -->");
//
// Step 4: Attempt to write again to the new buffer.
//
Sleep(9000);
trace("%s----", g_prefix);
writeTest(newBuffer, "TEST PASSED!");
//
// Step 5: Switch back to the original buffer.
//
Sleep(3000);
trace("%s----", g_prefix);
setConsoleActiveScreenBuffer(origBuffer);
closeHandle(newBuffer);
writeTest(origBuffer, "writing to the initial buffer");
Sleep(60000);
return;
}
if (!strcmp(argv[1], "B:child")) {
g_prefix = "child: ";
Sleep(3000);
trace("%s----", g_prefix);
//
// Step 2: Open the newly active buffer and write to it.
//
HANDLE newBuffer = openConout();
writeTest(newBuffer, "writing to newBuffer");
//
// Step 3: Close the newly active buffer.
//
Sleep(3000);
closeHandle(newBuffer);
Sleep(60000);
return;
}
}
///////////////////////////////////////////////////////////////////////////////
// TEST C -- Interleaving open/close of console handles also seems to break on
// Windows 7.
//
// Test:
// - child creates and activates newBuf1
// - parent opens newBuf1
// - child creates and activates newBuf2
// - parent opens newBuf2, then closes newBuf1
// - child switches back to newBuf1
// * At this point, the console starts malfunctioning.
// - parent and child close newBuf2
// - child closes newBuf1
//
// Test passes if the message "TEST PASSED!" is visible.
// Test commonly fails if conhost.exe crashes.
//
// Results:
// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
// - Windows 8 Enterprise 32-bit: PASS
// - Windows 10 64-bit (legacy and non-legacy): PASS
//
static void testC(int argc, char *argv[]) {
if (!strcmp(argv[1], "C")) {
startChildProcess(L"C:parent");
return;
}
if (!strcmp(argv[1], "C:parent")) {
startChildInSameConsole(L"C:child");
writeTest("<-- origBuffer -->");
g_prefix = "parent: ";
// At time=4, open newBuffer1.
Sleep(4000);
trace("%s---- t=4", g_prefix);
const HANDLE newBuffer1 = openConout();
// At time=8, open newBuffer2, and close newBuffer1.
Sleep(4000);
trace("%s---- t=8", g_prefix);
const HANDLE newBuffer2 = openConout();
closeHandle(newBuffer1);
// At time=25, cleanup of newBuffer2.
Sleep(17000);
trace("%s---- t=25", g_prefix);
closeHandle(newBuffer2);
Sleep(240000);
return;
}
if (!strcmp(argv[1], "C:child")) {
g_prefix = "child: ";
// At time=2, create newBuffer1 and activate it.
Sleep(2000);
trace("%s---- t=2", g_prefix);
const HANDLE newBuffer1 = createBuffer();
setConsoleActiveScreenBuffer(newBuffer1);
writeTest(newBuffer1, "<-- newBuffer1 -->");
// At time=6, create newBuffer2 and activate it.
Sleep(4000);
trace("%s---- t=6", g_prefix);
const HANDLE newBuffer2 = createBuffer();
setConsoleActiveScreenBuffer(newBuffer2);
writeTest(newBuffer2, "<-- newBuffer2 -->");
// At time=10, attempt to switch back to newBuffer1. The parent process
// has opened and closed its handle to newBuffer1, so does it still exist?
Sleep(4000);
trace("%s---- t=10", g_prefix);
setConsoleActiveScreenBuffer(newBuffer1);
writeTest(newBuffer1, "write to newBuffer1: TEST PASSED!");
// At time=25, cleanup of newBuffer2.
Sleep(15000);
trace("%s---- t=25", g_prefix);
closeHandle(newBuffer2);
// At time=35, cleanup of newBuffer1. The console should switch to the
// initial buffer again.
Sleep(10000);
trace("%s---- t=35", g_prefix);
closeHandle(newBuffer1);
Sleep(240000);
return;
}
}
///////////////////////////////////////////////////////////////////////////////
// TEST D -- parent creates a new buffer, child launches, writes,
// closes it output handle, then parent writes again. (Also see TEST 2.)
//
// On success, this will appear:
//
// parent: <-- newBuffer -->
// child: writing to newBuffer
// parent: TEST PASSED!
//
// If this appears, it indicates that the child's closing its output handle did
// not destroy newBuffer.
//
// Results:
// - Windows 7 Ultimate SP1 32-bit: PASS
// - Windows 8 Enterprise 32-bit: PASS
// - Windows 10 64-bit (legacy and non-legacy): PASS
//
static void testD(int argc, char *argv[]) {
if (!strcmp(argv[1], "D")) {
startChildProcess(L"D:parent");
return;
}
if (!strcmp(argv[1], "D:parent")) {
g_prefix = "parent: ";
HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
writeTest(origBuffer, "<-- origBuffer -->");
HANDLE newBuffer = createBuffer();
writeTest(newBuffer, "<-- newBuffer -->");
setConsoleActiveScreenBuffer(newBuffer);
// At t=2, start a child process, explicitly forcing it to use
// newBuffer for its standard handles. These calls are apparently
// redundant on Windows 8 and up.
Sleep(2000);
trace("parent:----");
trace("parent: starting child process");
SetStdHandle(STD_OUTPUT_HANDLE, newBuffer);
SetStdHandle(STD_ERROR_HANDLE, newBuffer);
startChildInSameConsole(L"D:child");
SetStdHandle(STD_OUTPUT_HANDLE, origBuffer);
SetStdHandle(STD_ERROR_HANDLE, origBuffer);
// At t=6, write again to newBuffer.
Sleep(4000);
trace("parent:----");
writeTest(newBuffer, "TEST PASSED!");
// At t=8, close the newBuffer. In earlier versions of windows
// (including Server 2008 R2), the console then switches back to
// origBuffer. As of Windows 8, it doesn't, because somehow the child
// process is keeping the console on newBuffer, even though the child
// process closed its STDIN/STDOUT/STDERR handles. Killing the child
// process by hand after the test finishes *does* force the console
// back to origBuffer.
Sleep(2000);
closeHandle(newBuffer);
Sleep(120000);
return;
}
if (!strcmp(argv[1], "D:child")) {
g_prefix = "child: ";
// At t=2, the child starts.
trace("child:----");
dumpHandles();
writeTest("writing to newBuffer");
// At t=4, the child explicitly closes its handle.
Sleep(2000);
trace("child:----");
if (GetStdHandle(STD_ERROR_HANDLE) != GetStdHandle(STD_OUTPUT_HANDLE)) {
closeHandle(GetStdHandle(STD_ERROR_HANDLE));
}
closeHandle(GetStdHandle(STD_OUTPUT_HANDLE));
closeHandle(GetStdHandle(STD_INPUT_HANDLE));
Sleep(120000);
return;
}
}
int main(int argc, char *argv[]) {
if (argc == 1) {
printf("USAGE: %s testnum\n", argv[0]);
return 0;
}
if (argv[1][0] == '1') {
test1(argc, argv);
} else if (argv[1][0] == '2') {
test2(argc, argv);
} else if (argv[1][0] == 'A') {
testA(argc, argv);
} else if (argv[1][0] == 'B') {
testB(argc, argv);
} else if (argv[1][0] == 'C') {
testC(argc, argv);
} else if (argv[1][0] == 'D') {
testD(argc, argv);
}
return 0;
}

152
misc/ScreenBufferTest2.cc Normal file
View File

@ -0,0 +1,152 @@
#include <windows.h>
#include "TestUtil.cc"
#include "../shared/DebugClient.cc"
const char *g_prefix = "";
static void dumpHandles() {
trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x",
g_prefix,
(long long)GetStdHandle(STD_INPUT_HANDLE),
(long long)GetStdHandle(STD_OUTPUT_HANDLE),
(long long)GetStdHandle(STD_ERROR_HANDLE));
}
static HANDLE createBuffer() {
// If sa isn't provided, the handle defaults to not-inheritable.
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
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, (long long)conout);
return conout;
}
static const char *successOrFail(BOOL ret) {
return ret ? "ok" : "FAILED";
}
static void setConsoleActiveScreenBuffer(HANDLE conout) {
trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called...",
g_prefix, (long long)conout);
trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called... %s",
g_prefix, (long long)conout,
successOrFail(SetConsoleActiveScreenBuffer(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, (long long)conout, msg);
DWORD actual = 0;
BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL);
trace("%sWriting to 0x%I64x: '%s'... %s",
g_prefix, (long long)conout, msg,
successOrFail(ret && actual == strlen(writeData)));
}
static HANDLE startChildInSameConsole(const wchar_t *args, BOOL
bInheritHandles=FALSE) {
wchar_t program[1024];
wchar_t cmdline[1024];
GetModuleFileNameW(NULL, program, 1024);
swprintf(cmdline, L"\"%ls\" %ls", program, args);
STARTUPINFOW sui;
PROCESS_INFORMATION pi;
memset(&sui, 0, sizeof(sui));
memset(&pi, 0, sizeof(pi));
sui.cb = sizeof(sui);
CreateProcessW(program, cmdline,
NULL, NULL,
/*bInheritHandles=*/bInheritHandles,
/*dwCreationFlags=*/0,
NULL, NULL,
&sui, &pi);
return pi.hProcess;
}
static HANDLE dup(HANDLE h, HANDLE targetProcess) {
HANDLE h2 = INVALID_HANDLE_VALUE;
BOOL ret = DuplicateHandle(
GetCurrentProcess(), h,
targetProcess, &h2,
0, TRUE, DUPLICATE_SAME_ACCESS);
trace("dup(0x%I64x) to process 0x%I64x... %s, 0x%I64x",
(long long)h,
(long long)targetProcess,
successOrFail(ret),
(long long)h2);
return h2;
}
int main(int argc, char *argv[]) {
if (argc == 1) {
startChildProcess(L"parent");
return 0;
}
if (!strcmp(argv[1], "parent")) {
g_prefix = "parent: ";
dumpHandles();
HANDLE hChild = startChildInSameConsole(L"child");
// Windows 10.
HANDLE orig1 = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE new1 = createBuffer();
Sleep(2000);
setConsoleActiveScreenBuffer(new1);
// Handle duplication results to child process in same console:
// - Windows XP: fails
// - Windows 7 Ultimate SP1 32-bit: fails
// - Windows Server 2008 R2 Datacenter SP1 64-bit: fails
// - Windows 8 Enterprise 32-bit: succeeds
// - Windows 10: succeeds
HANDLE orig2 = dup(orig1, GetCurrentProcess());
HANDLE new2 = dup(new1, GetCurrentProcess());
dup(orig1, hChild);
dup(new1, hChild);
// The writes to orig1/orig2 are invisible. The writes to new1/new2
// are visible.
writeTest(orig1, "write to orig1");
writeTest(orig2, "write to orig2");
writeTest(new1, "write to new1");
writeTest(new2, "write to new2");
Sleep(120000);
return 0;
}
if (!strcmp(argv[1], "child")) {
g_prefix = "child: ";
dumpHandles();
Sleep(4000);
for (unsigned int i = 0x1; i <= 0xB0; ++i) {
char msg[256];
sprintf(msg, "Write to handle 0x%x", i);
HANDLE h = reinterpret_cast<HANDLE>(i);
writeTest(h, msg);
}
Sleep(120000);
return 0;
}
return 0;
}

View File

@ -114,25 +114,31 @@ static void repeatChar(int count, char ch) {
// it helps to call `setlocale(LC_ALL, "")`, but the Japanese symbols are
// ultimately converted to `?` symbols, even though MS Gothic is able to
// display its own name, and the current code page is 932 (Shift-JIS).
static void cvprintf(const wchar_t *fmt, va_list ap) {
static void cvfprintf(HANDLE conout, const wchar_t *fmt, va_list ap) {
wchar_t buffer[256];
vswprintf(buffer, 256 - 1, fmt, ap);
buffer[255] = L'\0';
DWORD actual = 0;
if (!WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
buffer, wcslen(buffer), &actual, NULL)) {
if (!WriteConsoleW(conout, buffer, wcslen(buffer), &actual, NULL)) {
wprintf(L"WriteConsoleW call failed!\n");
}
}
static void cfprintf(HANDLE conout, const wchar_t *fmt, ...) {
va_list ap;
va_start(ap, fmt);
cvfprintf(conout, fmt, ap);
va_end(ap);
}
static void cprintf(const wchar_t *fmt, ...) {
va_list ap;
va_start(ap, fmt);
cvprintf(fmt, ap);
cvfprintf(GetStdHandle(STD_OUTPUT_HANDLE), fmt, ap);
va_end(ap);
}
std::string narrowString(const std::wstring &input)
static std::string narrowString(const std::wstring &input)
{
int mblen = WideCharToMultiByte(
CP_UTF8, 0,

View File

@ -0,0 +1,57 @@
include ../../config-mingw.mk
CFLAGS += -MMD -Wall -Iharness -I../../shared
CXXFLAGS += -MMD -Wall -std=c++11 -Iharness -I../../shared
LDFLAGS += -static -static-libgcc -static-libstdc++
# Use gmake -n to see the command-lines gmake would run.
build/%.o : %.c
@echo Compiling $<
@$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
build/%.o : %.cc
@echo Compiling $<
@$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $<
build/%.o : ../../shared/%.cc
@echo Compiling $<
@$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $<
build/%.o : harness/%.cc
@echo Compiling $<
@$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $<
.PRECIOUS : build/%.o
COMMON_OBJECTS = \
build/DebugClient.o \
build/Event.o \
build/HookAssert.o \
build/ShmemParcel.o \
build/Spawn.o \
build/UnicodeConversions.o \
build/Util.o
WORKER_OBJECTS = build/Worker.o
TEST_OBJECTS = build/TestCommon.o
all : \
build/Win7Bug_InheritHandles.exe \
build/Win7Bug_RaceCondition.exe \
build/TestHandleInheritance.exe \
build/Worker.exe
Worker.exe : $(WORKER_OBJECTS) $(COMMON_OBJECTS)
@echo Linking $@
@$(CXX) -o $@ $^ $(LDFLAGS)
%.exe : %.o $(TEST_OBJECTS) $(COMMON_OBJECTS)
@echo Linking $@
@$(CXX) -o $@ $^ $(LDFLAGS)
.PHONY : clean
clean:
rm -f build/*.exe build/*.o build/*.d
-include build/*.d

View File

@ -0,0 +1,19 @@
#include <TestCommon.h>
int main() {
SpawnParams sp;
sp.bInheritHandles = FALSE;
Worker p;
p.getStdout().write("<-- origBuffer -->");
auto b1 = p.newBuffer(FALSE);
auto b2 = p.newBuffer(TRUE);
auto b3 = b1.dup(FALSE);
auto b5 = b2.dup(FALSE);
auto b4 = b1.dup(TRUE);
auto b6 = b2.dup(TRUE);
p.dumpScreenBuffers();
Sleep(300000);
}

View File

@ -0,0 +1,32 @@
#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.dumpScreenBuffers(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.dumpScreenBuffers(TRUE);
auto pb = p.openConout();
cb.close();
// Demonstrate that pb is an invalid handle.
pb.close();
Sleep(300000);
}

View File

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

View File

View File

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

View File

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

View File

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

View File

@ -0,0 +1,37 @@
// Copyright (c) 2011-2012 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
#include "HookAssert.h"
#include <stdlib.h>
#include <DebugClient.h>
// Calling the standard assert() function does not work in the agent because
// the error message would be printed to the console, and the only way the
// user can see the console is via a working agent! This custom assert
// function instead sends the message to the DebugServer.
void assertFail(const char *file, int line, const char *cond)
{
trace("Assertion failed: %s, file %s, line %d",
cond, file, line);
abort();
}

View File

@ -0,0 +1,25 @@
#pragma once
// Copyright (c) 2011-2012 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
#define ASSERT(x) do { if (!(x)) { ::assertFail(__FILE__, __LINE__, #x); } } while(0)
void assertFail(const char *file, int line, const char *cond);

View File

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

View File

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

View File

@ -0,0 +1,44 @@
#include "Spawn.h"
#include <string.h>
#include "HookAssert.h"
#include "UnicodeConversions.h"
#include "Util.h"
#include <DebugClient.h>
namespace {
static std::vector<wchar_t> wstrToWVector(const std::wstring &str) {
std::vector<wchar_t> ret;
ret.resize(str.size() + 1);
wmemcpy(ret.data(), str.c_str(), str.size() + 1);
return ret;
}
} // anonymous namespace
HANDLE spawn(const std::string &workerName, const SpawnParams &params) {
auto workerPath = pathDirName(getModuleFileName(NULL)) + "\\Worker.exe";
const std::wstring workerPathWStr = widenString(workerPath);
const std::string cmdLine = "\"" + workerPath + "\" " + workerName;
auto cmdLineWVec = wstrToWVector(widenString(cmdLine));
STARTUPINFOW sui;
memset(&sui, 0, sizeof(sui));
sui.cb = sizeof(sui);
PROCESS_INFORMATION pi;
memset(&pi, 0, sizeof(pi));
BOOL ret = CreateProcessW(workerPathWStr.c_str(), cmdLineWVec.data(),
NULL, NULL,
/*bInheritHandles=*/params.bInheritHandles,
/*dwCreationFlags=*/params.dwCreationFlags,
NULL, NULL,
&sui, &pi);
ASSERT(ret && "CreateProcessW failed");
CloseHandle(pi.hThread);
return pi.hProcess;
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <windows.h>
#include <string>
struct SpawnParams {
BOOL bInheritHandles = FALSE;
DWORD dwCreationFlags = 0;
};
HANDLE spawn(const std::string &workerName, const SpawnParams &params);

View File

@ -0,0 +1,178 @@
#include "TestCommon.h"
#include <assert.h>
#include <string>
#include <DebugClient.h>
Handle Handle::dup(HANDLE h, Worker &target, BOOL bInheritHandle) {
HANDLE targetHandle;
BOOL success = DuplicateHandle(
GetCurrentProcess(),
h,
target.m_process,
&targetHandle,
0, bInheritHandle, DUPLICATE_SAME_ACCESS);
ASSERT(success && "DuplicateHandle failed");
return Handle(targetHandle, target);
}
void Handle::activate() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetActiveBuffer);
}
void Handle::write(const std::string &msg) {
worker().cmd().handle = m_value;
worker().cmd().writeText = msg;
worker().rpc(Command::WriteText);
}
void Handle::close() {
worker().cmd().handle = m_value;
worker().rpc(Command::Close);
}
Handle &Handle::setStdin() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetStdin);
return *this;
}
Handle &Handle::setStdout() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetStdout);
return *this;
}
Handle &Handle::setStderr() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetStderr);
return *this;
}
Handle Handle::dup(Worker &target, BOOL bInheritHandle) {
HANDLE targetProcessFromSource;
if (&target == &worker()) {
targetProcessFromSource = GetCurrentProcess();
} else {
// Allow the source worker to see the target worker.
targetProcessFromSource = INVALID_HANDLE_VALUE;
BOOL ret = DuplicateHandle(
GetCurrentProcess(),
target.m_process,
worker().m_process,
&targetProcessFromSource,
0, FALSE, DUPLICATE_SAME_ACCESS);
ASSERT(ret && "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);
if (&target != &worker()) {
// Cleanup targetProcessFromSource.
worker().cmd().handle = targetProcessFromSource;
worker().rpc(Command::Close);
ASSERT(worker().cmd().success);
}
return Handle(worker().cmd().handle, target);
}
CONSOLE_SCREEN_BUFFER_INFO Handle::screenBufferInfo() {
worker().cmd().handle = m_value;
worker().rpc(Command::GetConsoleScreenBufferInfo);
ASSERT(worker().cmd().success);
return worker().cmd().consoleScreenBufferInfo;
}
static std::string timeString() {
FILETIME fileTime;
GetSystemTimeAsFileTime(&fileTime);
auto ret = ((uint64_t)fileTime.dwHighDateTime << 32) |
fileTime.dwLowDateTime;
return std::to_string(ret);
}
static std::string newWorkerName() {
static int workerCounter = 0;
static auto initialTimeString = timeString();
return std::string("WinptyBufferTests-") +
std::to_string(static_cast<int>(GetCurrentProcessId())) + "-" +
initialTimeString + "-" +
std::to_string(++workerCounter);
}
Worker::Worker(const std::string &name) :
m_name(name),
m_parcel(name + "-shmem", ShmemParcel::CreateNew),
m_startEvent(name + "-start"),
m_finishEvent(name + "-finish")
{
m_finishEvent.set();
}
Worker::Worker(SpawnParams params) : Worker(newWorkerName()) {
params.dwCreationFlags |= CREATE_NEW_CONSOLE;
m_process = spawn(m_name, params);
}
Worker Worker::child(const SpawnParams &params) {
Worker ret(newWorkerName());
cmd().spawnName = ret.m_name;
cmd().spawnParams = params;
rpc(Command::SpawnChild);
BOOL dupSuccess = DuplicateHandle(
m_process,
cmd().handle,
GetCurrentProcess(),
&ret.m_process,
0, FALSE, DUPLICATE_SAME_ACCESS);
ASSERT(dupSuccess && "DuplicateHandle failed");
rpc(Command::Close);
return ret;
}
Worker::~Worker() {
if (!m_moved) {
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);
}
}
CONSOLE_SELECTION_INFO Worker::selectionInfo() {
rpc(Command::GetConsoleSelectionInfo);
ASSERT(cmd().success);
return cmd().consoleSelectionInfo;
}
void Worker::dumpScreenBuffers(BOOL writeToEach) {
cmd().writeToEach = writeToEach;
rpc(Command::DumpScreenBuffers);
}
void Worker::rpc(Command::Kind kind) {
rpcImpl(kind);
m_finishEvent.wait();
}
void Worker::rpcAsync(Command::Kind kind) {
rpcImpl(kind);
}
void Worker::rpcImpl(Command::Kind kind) {
m_finishEvent.wait();
m_finishEvent.reset();
cmd().kind = kind;
m_startEvent.set();
}

View File

@ -0,0 +1,113 @@
#pragma once
#include <windows.h>
#include <string>
#include "Event.h"
#include "ShmemParcel.h"
#include "Spawn.h"
#include "WorkerApi.h"
#include <DebugClient.h>
class Worker;
class Handle {
friend class Worker;
private:
Handle(HANDLE value, Worker &worker) : m_value(value), m_worker(&worker) {}
public:
static Handle invent(HANDLE h, Worker &worker) { return Handle(h, worker); }
static Handle invent(DWORD h, Worker &worker) { return Handle((HANDLE)h, worker); }
void activate();
void write(const std::string &msg);
void close();
Handle &setStdin();
Handle &setStdout();
Handle &setStderr();
Handle dup(Worker &target, BOOL bInheritHandle=FALSE);
Handle dup(BOOL bInheritHandle=FALSE) { return dup(worker(), bInheritHandle); }
static Handle dup(HANDLE h, Worker &target, BOOL bInheritHandle=FALSE);
CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo();
HANDLE value() { return m_value; }
Worker &worker() { return *m_worker; }
private:
HANDLE m_value;
Worker *m_worker;
};
class Worker {
friend class Handle;
private:
Worker(const std::string &name);
public:
Worker() : Worker(SpawnParams {}) {}
Worker(SpawnParams params);
Worker child() { return child(SpawnParams {}); }
Worker child(const SpawnParams &params);
~Worker();
// allow moving
Worker(Worker &&other) :
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_moved = true;
}
Worker &operator=(Worker &&other) {
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_moved = true;
return *this;
}
// Commands
Handle getStdin() { rpc(Command::GetStdin); return Handle(cmd().handle, *this); }
Handle getStdout() { rpc(Command::GetStdout); return Handle(cmd().handle, *this); }
Handle getStderr() { rpc(Command::GetStderr); return Handle(cmd().handle, *this); }
void detach() { rpc(Command::FreeConsole); }
void attach(Worker &worker) { cmd().dword = GetProcessId(worker.m_process); rpc(Command::AttachConsole); }
void alloc() { rpc(Command::AllocConsole); }
void dumpHandles() { rpc(Command::DumpHandles); }
int system(const std::string &arg) { cmd().systemText = arg; rpc(Command::System); return cmd().dword; }
HWND consoleWindow() { rpc(Command::GetConsoleWindow); return cmd().hwnd; }
CONSOLE_SELECTION_INFO selectionInfo();
void dumpScreenBuffers(BOOL writeToEach=FALSE);
Handle openConout(BOOL bInheritHandle=FALSE) {
cmd().bInheritHandle = bInheritHandle;
rpc(Command::OpenConOut);
return Handle(cmd().handle, *this);
}
Handle newBuffer(BOOL bInheritHandle=FALSE) {
cmd().bInheritHandle = bInheritHandle;
rpc(Command::NewBuffer);
return Handle(cmd().handle, *this);
}
private:
Command &cmd() { return m_parcel.value(); }
void rpc(Command::Kind kind);
void rpcAsync(Command::Kind kind);
void rpcImpl(Command::Kind kind);
private:
bool m_moved = false;
std::string m_name;
ShmemParcelTyped<Command> m_parcel;
Event m_startEvent;
Event m_finishEvent;
HANDLE m_process = NULL;
};

View File

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

View File

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

View File

@ -0,0 +1,24 @@
#include "Util.h"
#include "HookAssert.h"
#include "UnicodeConversions.h"
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);
}

View File

@ -0,0 +1,9 @@
#pragma once
#include <windows.h>
#include <string>
#include <vector>
std::string pathDirName(const std::string &path);
std::string getModuleFileName(HMODULE module);

View File

@ -0,0 +1,250 @@
#include <windows.h>
#include <stdint.h>
#include <stdio.h>
#include <vector>
#include "Event.h"
#include "ShmemParcel.h"
#include "Spawn.h"
#include "WorkerApi.h"
#include <DebugClient.h>
static const char *g_prefix = "";
static const char *successOrFail(BOOL ret) {
return ret ? "ok" : "FAILED";
}
static HANDLE openConout(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 CONOUT...", g_prefix);
HANDLE conout = CreateFileW(L"CONOUT$",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa,
OPEN_EXISTING, 0, NULL);
trace("%sOpening CONOUT... 0x%I64x", g_prefix, (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 dumpHandles() {
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> scanForScreenBuffers() {
std::vector<HANDLE> ret;
OSVERSIONINFO verinfo = {0};
verinfo.dwOSVersionInfoSize = sizeof(verinfo);
BOOL success = GetVersionEx(&verinfo);
ASSERT(success && "GetVersionEx failed");
uint64_t version =
((uint64_t)verinfo.dwMajorVersion << 32) | verinfo.dwMinorVersion;
CONSOLE_SCREEN_BUFFER_INFO info;
if (version >= 0x600000002) {
// As of Windows 8, console screen buffers are real kernel handles.
for (unsigned int h = 0x4; h <= 0x1000; h += 4) {
if (GetConsoleScreenBufferInfo((HANDLE)h, &info)) {
ret.push_back((HANDLE)h);
}
}
} else {
for (unsigned int h = 0x1; h <= 0xfff; h += 1) {
if (GetConsoleScreenBufferInfo((HANDLE)h, &info)) {
ret.push_back((HANDLE)h);
}
}
}
return ret;
}
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();
dumpHandles();
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::DumpHandles:
dumpHandles();
break;
case Command::DumpScreenBuffers: {
std::string dumpLine = "";
for (HANDLE h : scanForScreenBuffers()) {
char buf[32];
const char *inherit = "";
DWORD flags;
if (GetHandleInformation(h, &flags)) {
inherit = (flags & HANDLE_FLAG_INHERIT) ? "(I)" : "(N)";
}
sprintf(buf, "0x%I64x%s", (int64_t)h, inherit);
dumpLine += std::string(" ") + buf;
if (cmd.writeToEach) {
char msg[256];
sprintf(msg, "%d: Writing to 0x%I64x",
(int)GetCurrentProcessId(), (int64_t)h);
writeTest((HANDLE)h, msg);
}
}
trace("Valid screen buffers:%s", dumpLine.c_str());
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::GetConsoleScreenBufferInfo:
memset(&cmd.consoleScreenBufferInfo, 0, sizeof(cmd.consoleScreenBufferInfo));
cmd.success = GetConsoleScreenBufferInfo(
cmd.handle, &cmd.consoleScreenBufferInfo);
break;
case Command::GetConsoleSelectionInfo:
memset(&cmd.consoleSelectionInfo, 0, sizeof(cmd.consoleSelectionInfo));
cmd.success = GetConsoleSelectionInfo(&cmd.consoleSelectionInfo);
break;
case Command::GetConsoleWindow:
cmd.hwnd = GetConsoleWindow();
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::NewBuffer:
cmd.handle = createBuffer(cmd.bInheritHandle);
break;
case Command::OpenConOut:
cmd.handle = openConout(cmd.bInheritHandle);
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.spawnName.str(), cmd.spawnParams);
trace("Spawning child... pid %u",
(unsigned int)GetProcessId(cmd.handle));
break;
case Command::System:
cmd.dword = system(cmd.systemText.c_str());
break;
case Command::WriteText:
writeTest(cmd.handle, cmd.writeText.c_str());
break;
}
finishEvent.set();
}
return 0;
}

View File

@ -0,0 +1,47 @@
#pragma once
#include "FixedSizeString.h"
#include "Spawn.h"
struct Command {
enum Kind {
AllocConsole,
AttachConsole,
Close,
DumpHandles,
DumpScreenBuffers,
Duplicate,
Exit,
FreeConsole,
GetConsoleScreenBufferInfo,
GetConsoleSelectionInfo,
GetConsoleWindow,
GetStdin,
GetStderr,
GetStdout,
NewBuffer,
OpenConOut,
SetStdin,
SetStderr,
SetStdout,
SetActiveBuffer,
SpawnChild,
System,
WriteText,
};
Kind kind;
HANDLE handle;
HANDLE targetProcess;
FixedSizeString<128> spawnName;
SpawnParams spawnParams;
FixedSizeString<1024> writeText;
FixedSizeString<1024> systemText;
DWORD dword;
BOOL success;
BOOL bInheritHandle;
BOOL writeToEach;
CONSOLE_SCREEN_BUFFER_INFO consoleScreenBufferInfo;
CONSOLE_SELECTION_INFO consoleSelectionInfo;
HWND hwnd;
};