2 // Windows versions tested
4 // Vista Enterprise SP2 32-bit
5 // - ver reports [Version 6.0.6002]
6 // - kernel32.dll product/file versions are 6.0.6002.19381
8 // Windows 7 Ultimate SP1 32-bit
9 // - ver reports [Version 6.1.7601]
10 // - conhost.exe product/file versions are 6.1.7601.18847
11 // - kernel32.dll product/file versions are 6.1.7601.18847
13 // Windows Server 2008 R2 Datacenter SP1 64-bit
14 // - ver reports [Version 6.1.7601]
15 // - conhost.exe product/file versions are 6.1.7601.23153
16 // - kernel32.dll product/file versions are 6.1.7601.23153
18 // Windows 8 Enterprise 32-bit
19 // - ver reports [Version 6.2.9200]
20 // - conhost.exe product/file versions are 6.2.9200.16578
21 // - kernel32.dll product/file versions are 6.2.9200.16859
25 // Specific version details on working Server 2008 R2:
29 // dwBuildNumber = 7601
31 // szCSDVersion = Service Pack 1
32 // wServicePackMajor = 1
33 // wServicePackMinor = 0
37 // Specific version details on broken Win7:
41 // dwBuildNumber = 7601
43 // szCSDVersion = Service Pack 1
44 // wServicePackMajor = 1
45 // wServicePackMinor = 0
54 #include "TestUtil.cc"
56 const char *g_prefix = "";
58 static void dumpHandles() {
59 trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x",
61 (long long)GetStdHandle(STD_INPUT_HANDLE),
62 (long long)GetStdHandle(STD_OUTPUT_HANDLE),
63 (long long)GetStdHandle(STD_ERROR_HANDLE));
66 static const char *successOrFail(BOOL ret) {
67 return ret ? "ok" : "FAILED";
70 static void startChildInSameConsole(const wchar_t *args, BOOL
71 bInheritHandles=FALSE) {
72 wchar_t program[1024];
73 wchar_t cmdline[1024];
74 GetModuleFileNameW(NULL, program, 1024);
75 swprintf(cmdline, L"\"%ls\" %ls", program, args);
78 PROCESS_INFORMATION pi;
79 memset(&sui, 0, sizeof(sui));
80 memset(&pi, 0, sizeof(pi));
83 CreateProcessW(program, cmdline,
85 /*bInheritHandles=*/bInheritHandles,
86 /*dwCreationFlags=*/0,
91 static void closeHandle(HANDLE h) {
92 trace("%sClosing handle 0x%I64x...", g_prefix, (long long)h);
93 trace("%sClosing handle 0x%I64x... %s", g_prefix, (long long)h, successOrFail(CloseHandle(h)));
96 static HANDLE createBuffer() {
98 // If sa isn't provided, the handle defaults to not-inheritable.
99 SECURITY_ATTRIBUTES sa = {0};
100 sa.nLength = sizeof(sa);
101 sa.bInheritHandle = TRUE;
103 trace("%sCreating a new buffer...", g_prefix);
104 HANDLE conout = CreateConsoleScreenBuffer(
105 GENERIC_READ | GENERIC_WRITE,
106 FILE_SHARE_READ | FILE_SHARE_WRITE,
108 CONSOLE_TEXTMODE_BUFFER, NULL);
110 trace("%sCreating a new buffer... 0x%I64x", g_prefix, (long long)conout);
114 static HANDLE openConout() {
116 // If sa isn't provided, the handle defaults to not-inheritable.
117 SECURITY_ATTRIBUTES sa = {0};
118 sa.nLength = sizeof(sa);
119 sa.bInheritHandle = TRUE;
121 trace("%sOpening CONOUT...", g_prefix);
122 HANDLE conout = CreateFileW(L"CONOUT$",
123 GENERIC_READ | GENERIC_WRITE,
124 FILE_SHARE_READ | FILE_SHARE_WRITE,
126 OPEN_EXISTING, 0, NULL);
127 trace("%sOpening CONOUT... 0x%I64x", g_prefix, (long long)conout);
131 static void setConsoleActiveScreenBuffer(HANDLE conout) {
132 trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called...",
133 g_prefix, (long long)conout);
134 trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called... %s",
135 g_prefix, (long long)conout,
136 successOrFail(SetConsoleActiveScreenBuffer(conout)));
139 static void writeTest(HANDLE conout, const char *msg) {
141 sprintf(writeData, "%s%s\n", g_prefix, msg);
143 trace("%sWriting to 0x%I64x: '%s'...",
144 g_prefix, (long long)conout, msg);
146 BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL);
147 trace("%sWriting to 0x%I64x: '%s'... %s",
148 g_prefix, (long long)conout, msg,
149 successOrFail(ret && actual == strlen(writeData)));
152 static void writeTest(const char *msg) {
153 writeTest(GetStdHandle(STD_OUTPUT_HANDLE), msg);
158 ///////////////////////////////////////////////////////////////////////////////
159 // TEST 1 -- create new buffer, activate it, and close the handle. The console
160 // automatically switches the screen buffer back to the original.
162 // This test passes everywhere.
165 static void test1(int argc, char *argv[]) {
166 if (!strcmp(argv[1], "1")) {
167 startChildProcess(L"1:child");
171 HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
172 writeTest(origBuffer, "<-- origBuffer -->");
174 HANDLE newBuffer = createBuffer();
175 writeTest(newBuffer, "<-- newBuffer -->");
176 setConsoleActiveScreenBuffer(newBuffer);
179 writeTest(origBuffer, "TEST PASSED!");
181 // Closing the handle w/o switching the active screen buffer automatically
182 // switches the console back to the original buffer.
183 closeHandle(newBuffer);
192 ///////////////////////////////////////////////////////////////////////////////
193 // TEST 2 -- Test program that creates and activates newBuffer, starts a child
194 // process, then closes its newBuffer handle. newBuffer remains activated,
195 // because the child keeps it active. (Also see TEST D.)
198 static void test2(int argc, char *argv[]) {
199 if (!strcmp(argv[1], "2")) {
200 startChildProcess(L"2:parent");
204 if (!strcmp(argv[1], "2:parent")) {
205 g_prefix = "parent: ";
207 HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
208 writeTest(origBuffer, "<-- origBuffer -->");
210 HANDLE newBuffer = createBuffer();
211 writeTest(newBuffer, "<-- newBuffer -->");
212 setConsoleActiveScreenBuffer(newBuffer);
215 writeTest(newBuffer, "bInheritHandles=FALSE:");
216 startChildInSameConsole(L"2:child", FALSE);
218 writeTest(newBuffer, "bInheritHandles=TRUE:");
219 startChildInSameConsole(L"2:child", TRUE);
222 trace("parent:----");
224 // Close the new buffer. The active screen buffer doesn't automatically
225 // switch back to origBuffer, because the child process has a handle open
226 // to the original buffer.
227 closeHandle(newBuffer);
233 if (!strcmp(argv[1], "2:child")) {
234 g_prefix = "child: ";
236 // The child's output isn't visible, because it's still writing to
239 writeTest("writing to STDOUT");
241 // Handle inheritability is curious. The console handles this program
242 // creates are inheritable, but CreateProcess is called with both
243 // bInheritHandles=TRUE and bInheritHandles=FALSE.
245 // Vista and Windows 7: bInheritHandles has no effect. The child and
246 // parent processes have the same STDIN/STDOUT/STDERR handles:
247 // 0x3, 0x7, and 0xB. The parent has a 0xF handle for newBuffer.
248 // The child can only write to 0x7, 0xB, and 0xF. Only the writes to
249 // 0xF are visible (i.e. they touch newBuffer).
251 // Windows 8 or Windows 10 (legacy or non-legacy): the lowest 2 bits of
252 // the HANDLE to WriteConsole seem to be ignored. The new process'
253 // console handles always refer to the buffer that was active when they
254 // started, but the values of the handles depend upon bInheritHandles.
255 // With bInheritHandles=TRUE, the child has the same
256 // STDIN/STDOUT/STDERR/newBuffer handles as the parent, and the three
257 // output handles all work, though their output is all visible. With
258 // bInheritHandles=FALSE, the child has different STDIN/STDOUT/STDERR
259 // handles, and only the new STDOUT/STDERR handles work.
261 for (unsigned int i = 0x1; i <= 0xB0; ++i) {
263 sprintf(msg, "Write to handle 0x%x", i);
264 HANDLE h = reinterpret_cast<HANDLE>(i);
275 ///////////////////////////////////////////////////////////////////////////////
276 // TEST A -- demonstrate an apparent Windows bug with screen buffers
279 // - The parent starts a child process.
280 // - The child process creates and activates newBuffer
281 // - The parent opens CONOUT$ and writes to it.
282 // - The parent closes CONOUT$.
283 // - At this point, broken Windows reactivates origBuffer.
284 // - The child writes to newBuffer again.
285 // - The child activates origBuffer again, then closes newBuffer.
287 // Test passes if the message "TEST PASSED!" is visible.
288 // Test commonly fails if conhost.exe crashes.
291 // - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
292 // - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
293 // - Windows 8 Enterprise 32-bit: PASS
294 // - Windows 10 64-bit (legacy and non-legacy): PASS
297 static void testA_parentWork() {
298 // Open an extra CONOUT$ handle so that the HANDLE values in parent and
299 // child don't collide. I think it's OK if they collide, but since we're
300 // trying to track down a Windows bug, it's best to avoid unnecessary
302 HANDLE dummy = openConout();
306 // Step 2: Open CONOUT$ in the parent. This opens the active buffer, which
307 // was just created in the child. It's handle 0x13. Write to it.
309 HANDLE newBuffer = openConout();
310 writeTest(newBuffer, "step2: writing to newBuffer");
314 // Step 3: Close handle 0x13. With Windows 7, the console switches back to
315 // origBuffer, and (unless I'm missing something) it shouldn't.
317 closeHandle(newBuffer);
320 static void testA_childWork() {
321 HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
324 // Step 1: Create the new screen buffer in the child process and make it
325 // active. (Typically, it's handle 0x0F.)
328 HANDLE newBuffer = createBuffer();
330 setConsoleActiveScreenBuffer(newBuffer);
331 writeTest(newBuffer, "<-- newBuffer -->");
336 // Step 4: write to the newBuffer again.
337 writeTest(newBuffer, "TEST PASSED!");
340 // Step 5: Switch back to the original screen buffer and close the new
341 // buffer. The switch call succeeds, but the CloseHandle call freezes for
342 // several seconds, because conhost.exe crashes.
346 setConsoleActiveScreenBuffer(origBuffer);
347 writeTest(origBuffer, "writing to origBuffer");
349 closeHandle(newBuffer);
351 // The console HWND is NULL.
352 trace("child: console HWND=0x%I64x", (long long)GetConsoleWindow());
354 // At this point, the console window has closed, but the parent/child
355 // processes are still running. Calling AllocConsole would fail, but
356 // calling FreeConsole followed by AllocConsole would both succeed, and a
357 // new console would appear.
360 static void testA(int argc, char *argv[]) {
362 if (!strcmp(argv[1], "A")) {
363 startChildProcess(L"A:parent");
367 if (!strcmp(argv[1], "A:parent")) {
368 g_prefix = "parent: ";
369 trace("parent:----");
371 writeTest("<-- origBuffer -->");
372 startChildInSameConsole(L"A:child");
378 if (!strcmp(argv[1], "A:child")) {
379 g_prefix = "child: ";
389 ///////////////////////////////////////////////////////////////////////////////
390 // TEST B -- invert TEST A -- also crashes conhost on Windows 7
392 // Test passes if the message "TEST PASSED!" is visible.
393 // Test commonly fails if conhost.exe crashes.
396 // - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
397 // - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
398 // - Windows 8 Enterprise 32-bit: PASS
399 // - Windows 10 64-bit (legacy and non-legacy): PASS
402 static void testB(int argc, char *argv[]) {
403 if (!strcmp(argv[1], "B")) {
404 startChildProcess(L"B:parent");
408 if (!strcmp(argv[1], "B:parent")) {
409 g_prefix = "parent: ";
410 startChildInSameConsole(L"B:child");
411 writeTest("<-- origBuffer -->");
412 HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
415 // Step 1: Create the new buffer and make it active.
417 trace("%s----", g_prefix);
418 HANDLE newBuffer = createBuffer();
419 setConsoleActiveScreenBuffer(newBuffer);
420 writeTest(newBuffer, "<-- newBuffer -->");
423 // Step 4: Attempt to write again to the new buffer.
426 trace("%s----", g_prefix);
427 writeTest(newBuffer, "TEST PASSED!");
430 // Step 5: Switch back to the original buffer.
433 trace("%s----", g_prefix);
434 setConsoleActiveScreenBuffer(origBuffer);
435 closeHandle(newBuffer);
436 writeTest(origBuffer, "writing to the initial buffer");
442 if (!strcmp(argv[1], "B:child")) {
443 g_prefix = "child: ";
445 trace("%s----", g_prefix);
448 // Step 2: Open the newly active buffer and write to it.
450 HANDLE newBuffer = openConout();
451 writeTest(newBuffer, "writing to newBuffer");
454 // Step 3: Close the newly active buffer.
457 closeHandle(newBuffer);
466 ///////////////////////////////////////////////////////////////////////////////
467 // TEST C -- Interleaving open/close of console handles also seems to break on
471 // - child creates and activates newBuf1
472 // - parent opens newBuf1
473 // - child creates and activates newBuf2
474 // - parent opens newBuf2, then closes newBuf1
475 // - child switches back to newBuf1
476 // * At this point, the console starts malfunctioning.
477 // - parent and child close newBuf2
478 // - child closes newBuf1
480 // Test passes if the message "TEST PASSED!" is visible.
481 // Test commonly fails if conhost.exe crashes.
484 // - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
485 // - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
486 // - Windows 8 Enterprise 32-bit: PASS
487 // - Windows 10 64-bit (legacy and non-legacy): PASS
490 static void testC(int argc, char *argv[]) {
491 if (!strcmp(argv[1], "C")) {
492 startChildProcess(L"C:parent");
496 if (!strcmp(argv[1], "C:parent")) {
497 startChildInSameConsole(L"C:child");
498 writeTest("<-- origBuffer -->");
499 g_prefix = "parent: ";
501 // At time=4, open newBuffer1.
503 trace("%s---- t=4", g_prefix);
504 const HANDLE newBuffer1 = openConout();
506 // At time=8, open newBuffer2, and close newBuffer1.
508 trace("%s---- t=8", g_prefix);
509 const HANDLE newBuffer2 = openConout();
510 closeHandle(newBuffer1);
512 // At time=25, cleanup of newBuffer2.
514 trace("%s---- t=25", g_prefix);
515 closeHandle(newBuffer2);
521 if (!strcmp(argv[1], "C:child")) {
522 g_prefix = "child: ";
524 // At time=2, create newBuffer1 and activate it.
526 trace("%s---- t=2", g_prefix);
527 const HANDLE newBuffer1 = createBuffer();
528 setConsoleActiveScreenBuffer(newBuffer1);
529 writeTest(newBuffer1, "<-- newBuffer1 -->");
531 // At time=6, create newBuffer2 and activate it.
533 trace("%s---- t=6", g_prefix);
534 const HANDLE newBuffer2 = createBuffer();
535 setConsoleActiveScreenBuffer(newBuffer2);
536 writeTest(newBuffer2, "<-- newBuffer2 -->");
538 // At time=10, attempt to switch back to newBuffer1. The parent process
539 // has opened and closed its handle to newBuffer1, so does it still exist?
541 trace("%s---- t=10", g_prefix);
542 setConsoleActiveScreenBuffer(newBuffer1);
543 writeTest(newBuffer1, "write to newBuffer1: TEST PASSED!");
545 // At time=25, cleanup of newBuffer2.
547 trace("%s---- t=25", g_prefix);
548 closeHandle(newBuffer2);
550 // At time=35, cleanup of newBuffer1. The console should switch to the
551 // initial buffer again.
553 trace("%s---- t=35", g_prefix);
554 closeHandle(newBuffer1);
563 ///////////////////////////////////////////////////////////////////////////////
564 // TEST D -- parent creates a new buffer, child launches, writes,
565 // closes it output handle, then parent writes again. (Also see TEST 2.)
567 // On success, this will appear:
569 // parent: <-- newBuffer -->
570 // child: writing to newBuffer
571 // parent: TEST PASSED!
573 // If this appears, it indicates that the child's closing its output handle did
574 // not destroy newBuffer.
577 // - Windows 7 Ultimate SP1 32-bit: PASS
578 // - Windows 8 Enterprise 32-bit: PASS
579 // - Windows 10 64-bit (legacy and non-legacy): PASS
582 static void testD(int argc, char *argv[]) {
583 if (!strcmp(argv[1], "D")) {
584 startChildProcess(L"D:parent");
588 if (!strcmp(argv[1], "D:parent")) {
589 g_prefix = "parent: ";
590 HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
591 writeTest(origBuffer, "<-- origBuffer -->");
593 HANDLE newBuffer = createBuffer();
594 writeTest(newBuffer, "<-- newBuffer -->");
595 setConsoleActiveScreenBuffer(newBuffer);
597 // At t=2, start a child process, explicitly forcing it to use
598 // newBuffer for its standard handles. These calls are apparently
599 // redundant on Windows 8 and up.
601 trace("parent:----");
602 trace("parent: starting child process");
603 SetStdHandle(STD_OUTPUT_HANDLE, newBuffer);
604 SetStdHandle(STD_ERROR_HANDLE, newBuffer);
605 startChildInSameConsole(L"D:child");
606 SetStdHandle(STD_OUTPUT_HANDLE, origBuffer);
607 SetStdHandle(STD_ERROR_HANDLE, origBuffer);
609 // At t=6, write again to newBuffer.
611 trace("parent:----");
612 writeTest(newBuffer, "TEST PASSED!");
614 // At t=8, close the newBuffer. In earlier versions of windows
615 // (including Server 2008 R2), the console then switches back to
616 // origBuffer. As of Windows 8, it doesn't, because somehow the child
617 // process is keeping the console on newBuffer, even though the child
618 // process closed its STDIN/STDOUT/STDERR handles. Killing the child
619 // process by hand after the test finishes *does* force the console
620 // back to origBuffer.
622 closeHandle(newBuffer);
628 if (!strcmp(argv[1], "D:child")) {
629 g_prefix = "child: ";
630 // At t=2, the child starts.
633 writeTest("writing to newBuffer");
635 // At t=4, the child explicitly closes its handle.
638 if (GetStdHandle(STD_ERROR_HANDLE) != GetStdHandle(STD_OUTPUT_HANDLE)) {
639 closeHandle(GetStdHandle(STD_ERROR_HANDLE));
641 closeHandle(GetStdHandle(STD_OUTPUT_HANDLE));
642 closeHandle(GetStdHandle(STD_INPUT_HANDLE));
651 int main(int argc, char *argv[]) {
653 printf("USAGE: %s testnum\n", argv[0]);
657 if (argv[1][0] == '1') {
659 } else if (argv[1][0] == '2') {
661 } else if (argv[1][0] == 'A') {
663 } else if (argv[1][0] == 'B') {
665 } else if (argv[1][0] == 'C') {
667 } else if (argv[1][0] == 'D') {