Add tests of screen buffer handles.
This commit is contained in:
parent
4d52166b8c
commit
3bf4703f6a
672
misc/ScreenBufferTest.cc
Executable file
672
misc/ScreenBufferTest.cc
Executable 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
152
misc/ScreenBufferTest2.cc
Normal 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;
|
||||
}
|
@ -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,
|
||||
|
57
misc/buffer-tests/Makefile
Normal file
57
misc/buffer-tests/Makefile
Normal 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
|
19
misc/buffer-tests/TestHandleInheritance.cc
Normal file
19
misc/buffer-tests/TestHandleInheritance.cc
Normal 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);
|
||||
}
|
32
misc/buffer-tests/Win7Bug_InheritHandles.cc
Normal file
32
misc/buffer-tests/Win7Bug_InheritHandles.cc
Normal 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);
|
||||
}
|
56
misc/buffer-tests/Win7Bug_RaceCondition.cc
Normal file
56
misc/buffer-tests/Win7Bug_RaceCondition.cc
Normal 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);
|
||||
}
|
0
misc/buffer-tests/build/.gitkeep
Normal file
0
misc/buffer-tests/build/.gitkeep
Normal file
31
misc/buffer-tests/harness/Event.cc
Normal file
31
misc/buffer-tests/harness/Event.cc
Normal 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");
|
||||
}
|
30
misc/buffer-tests/harness/Event.h
Normal file
30
misc/buffer-tests/harness/Event.h
Normal 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;
|
||||
};
|
37
misc/buffer-tests/harness/FixedSizeString.h
Normal file
37
misc/buffer-tests/harness/FixedSizeString.h
Normal 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];
|
||||
};
|
37
misc/buffer-tests/harness/HookAssert.cc
Normal file
37
misc/buffer-tests/harness/HookAssert.cc
Normal 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();
|
||||
}
|
25
misc/buffer-tests/harness/HookAssert.h
Normal file
25
misc/buffer-tests/harness/HookAssert.h
Normal 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);
|
42
misc/buffer-tests/harness/ShmemParcel.cc
Normal file
42
misc/buffer-tests/harness/ShmemParcel.cc
Normal 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);
|
||||
}
|
||||
}
|
61
misc/buffer-tests/harness/ShmemParcel.h
Normal file
61
misc/buffer-tests/harness/ShmemParcel.h
Normal 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;
|
||||
};
|
44
misc/buffer-tests/harness/Spawn.cc
Normal file
44
misc/buffer-tests/harness/Spawn.cc
Normal 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 ¶ms) {
|
||||
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;
|
||||
}
|
12
misc/buffer-tests/harness/Spawn.h
Normal file
12
misc/buffer-tests/harness/Spawn.h
Normal 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 ¶ms);
|
178
misc/buffer-tests/harness/TestCommon.cc
Normal file
178
misc/buffer-tests/harness/TestCommon.cc
Normal 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 ¶ms) {
|
||||
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();
|
||||
}
|
113
misc/buffer-tests/harness/TestCommon.h
Normal file
113
misc/buffer-tests/harness/TestCommon.h
Normal 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 ¶ms);
|
||||
~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;
|
||||
};
|
44
misc/buffer-tests/harness/UnicodeConversions.cc
Normal file
44
misc/buffer-tests/harness/UnicodeConversions.cc
Normal 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());
|
||||
}
|
6
misc/buffer-tests/harness/UnicodeConversions.h
Normal file
6
misc/buffer-tests/harness/UnicodeConversions.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
std::string narrowString(const std::wstring &input);
|
||||
std::wstring widenString(const std::string &input);
|
24
misc/buffer-tests/harness/Util.cc
Normal file
24
misc/buffer-tests/harness/Util.cc
Normal 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);
|
||||
}
|
9
misc/buffer-tests/harness/Util.h
Normal file
9
misc/buffer-tests/harness/Util.h
Normal 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);
|
250
misc/buffer-tests/harness/Worker.cc
Normal file
250
misc/buffer-tests/harness/Worker.cc
Normal 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;
|
||||
}
|
47
misc/buffer-tests/harness/WorkerApi.h
Normal file
47
misc/buffer-tests/harness/WorkerApi.h
Normal 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;
|
||||
};
|
Loading…
Reference in New Issue
Block a user