UNPKG

21 kBtext/x-cView Raw
1//
2// Windows versions tested
3//
4// Vista Enterprise SP2 32-bit
5// - ver reports [Version 6.0.6002]
6// - kernel32.dll product/file versions are 6.0.6002.19381
7//
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
12//
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
17//
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
22//
23
24//
25// Specific version details on working Server 2008 R2:
26//
27// dwMajorVersion = 6
28// dwMinorVersion = 1
29// dwBuildNumber = 7601
30// dwPlatformId = 2
31// szCSDVersion = Service Pack 1
32// wServicePackMajor = 1
33// wServicePackMinor = 0
34// wSuiteMask = 0x190
35// wProductType = 0x3
36//
37// Specific version details on broken Win7:
38//
39// dwMajorVersion = 6
40// dwMinorVersion = 1
41// dwBuildNumber = 7601
42// dwPlatformId = 2
43// szCSDVersion = Service Pack 1
44// wServicePackMajor = 1
45// wServicePackMinor = 0
46// wSuiteMask = 0x100
47// wProductType = 0x1
48//
49
50#include <windows.h>
51#include <stdio.h>
52#include <string.h>
53
54#include "TestUtil.cc"
55
56const char *g_prefix = "";
57
58static void dumpHandles() {
59 trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x",
60 g_prefix,
61 (long long)GetStdHandle(STD_INPUT_HANDLE),
62 (long long)GetStdHandle(STD_OUTPUT_HANDLE),
63 (long long)GetStdHandle(STD_ERROR_HANDLE));
64}
65
66static const char *successOrFail(BOOL ret) {
67 return ret ? "ok" : "FAILED";
68}
69
70static 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);
76
77 STARTUPINFOW sui;
78 PROCESS_INFORMATION pi;
79 memset(&sui, 0, sizeof(sui));
80 memset(&pi, 0, sizeof(pi));
81 sui.cb = sizeof(sui);
82
83 CreateProcessW(program, cmdline,
84 NULL, NULL,
85 /*bInheritHandles=*/bInheritHandles,
86 /*dwCreationFlags=*/0,
87 NULL, NULL,
88 &sui, &pi);
89}
90
91static 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)));
94}
95
96static HANDLE createBuffer() {
97
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;
102
103 trace("%sCreating a new buffer...", g_prefix);
104 HANDLE conout = CreateConsoleScreenBuffer(
105 GENERIC_READ | GENERIC_WRITE,
106 FILE_SHARE_READ | FILE_SHARE_WRITE,
107 &sa,
108 CONSOLE_TEXTMODE_BUFFER, NULL);
109
110 trace("%sCreating a new buffer... 0x%I64x", g_prefix, (long long)conout);
111 return conout;
112}
113
114static HANDLE openConout() {
115
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;
120
121 trace("%sOpening CONOUT...", g_prefix);
122 HANDLE conout = CreateFileW(L"CONOUT$",
123 GENERIC_READ | GENERIC_WRITE,
124 FILE_SHARE_READ | FILE_SHARE_WRITE,
125 &sa,
126 OPEN_EXISTING, 0, NULL);
127 trace("%sOpening CONOUT... 0x%I64x", g_prefix, (long long)conout);
128 return conout;
129}
130
131static 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)));
137}
138
139static void writeTest(HANDLE conout, const char *msg) {
140 char writeData[256];
141 sprintf(writeData, "%s%s\n", g_prefix, msg);
142
143 trace("%sWriting to 0x%I64x: '%s'...",
144 g_prefix, (long long)conout, msg);
145 DWORD actual = 0;
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)));
150}
151
152static void writeTest(const char *msg) {
153 writeTest(GetStdHandle(STD_OUTPUT_HANDLE), msg);
154}
155
156
157
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.
161//
162// This test passes everywhere.
163//
164
165static void test1(int argc, char *argv[]) {
166 if (!strcmp(argv[1], "1")) {
167 startChildProcess(L"1:child");
168 return;
169 }
170
171 HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
172 writeTest(origBuffer, "<-- origBuffer -->");
173
174 HANDLE newBuffer = createBuffer();
175 writeTest(newBuffer, "<-- newBuffer -->");
176 setConsoleActiveScreenBuffer(newBuffer);
177 Sleep(2000);
178
179 writeTest(origBuffer, "TEST PASSED!");
180
181 // Closing the handle w/o switching the active screen buffer automatically
182 // switches the console back to the original buffer.
183 closeHandle(newBuffer);
184
185 while (true) {
186 Sleep(1000);
187 }
188}
189
190
191
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.)
196//
197
198static void test2(int argc, char *argv[]) {
199 if (!strcmp(argv[1], "2")) {
200 startChildProcess(L"2:parent");
201 return;
202 }
203
204 if (!strcmp(argv[1], "2:parent")) {
205 g_prefix = "parent: ";
206 dumpHandles();
207 HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
208 writeTest(origBuffer, "<-- origBuffer -->");
209
210 HANDLE newBuffer = createBuffer();
211 writeTest(newBuffer, "<-- newBuffer -->");
212 setConsoleActiveScreenBuffer(newBuffer);
213
214 Sleep(1000);
215 writeTest(newBuffer, "bInheritHandles=FALSE:");
216 startChildInSameConsole(L"2:child", FALSE);
217 Sleep(1000);
218 writeTest(newBuffer, "bInheritHandles=TRUE:");
219 startChildInSameConsole(L"2:child", TRUE);
220
221 Sleep(1000);
222 trace("parent:----");
223
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);
228
229 Sleep(600 * 1000);
230 return;
231 }
232
233 if (!strcmp(argv[1], "2:child")) {
234 g_prefix = "child: ";
235 dumpHandles();
236 // The child's output isn't visible, because it's still writing to
237 // origBuffer.
238 trace("child:----");
239 writeTest("writing to STDOUT");
240
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.
244 //
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).
250 //
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.
260 //
261 for (unsigned int i = 0x1; i <= 0xB0; ++i) {
262 char msg[256];
263 sprintf(msg, "Write to handle 0x%x", i);
264 HANDLE h = reinterpret_cast<HANDLE>(i);
265 writeTest(h, msg);
266 }
267
268 Sleep(600 * 1000);
269 return;
270 }
271}
272
273
274
275///////////////////////////////////////////////////////////////////////////////
276// TEST A -- demonstrate an apparent Windows bug with screen buffers
277//
278// Steps:
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.
286//
287// Test passes if the message "TEST PASSED!" is visible.
288// Test commonly fails if conhost.exe crashes.
289//
290// Results:
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
295//
296
297static 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
301 // complication.
302 HANDLE dummy = openConout();
303
304 Sleep(3000);
305
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.
308
309 HANDLE newBuffer = openConout();
310 writeTest(newBuffer, "step2: writing to newBuffer");
311
312 Sleep(3000);
313
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.
316
317 closeHandle(newBuffer);
318}
319
320static void testA_childWork() {
321 HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
322
323 //
324 // Step 1: Create the new screen buffer in the child process and make it
325 // active. (Typically, it's handle 0x0F.)
326 //
327
328 HANDLE newBuffer = createBuffer();
329
330 setConsoleActiveScreenBuffer(newBuffer);
331 writeTest(newBuffer, "<-- newBuffer -->");
332
333 Sleep(9000);
334 trace("child:----");
335
336 // Step 4: write to the newBuffer again.
337 writeTest(newBuffer, "TEST PASSED!");
338
339 //
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.
343 //
344 Sleep(3000);
345
346 setConsoleActiveScreenBuffer(origBuffer);
347 writeTest(origBuffer, "writing to origBuffer");
348
349 closeHandle(newBuffer);
350
351 // The console HWND is NULL.
352 trace("child: console HWND=0x%I64x", (long long)GetConsoleWindow());
353
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.
358}
359
360static void testA(int argc, char *argv[]) {
361
362 if (!strcmp(argv[1], "A")) {
363 startChildProcess(L"A:parent");
364 return;
365 }
366
367 if (!strcmp(argv[1], "A:parent")) {
368 g_prefix = "parent: ";
369 trace("parent:----");
370 dumpHandles();
371 writeTest("<-- origBuffer -->");
372 startChildInSameConsole(L"A:child");
373 testA_parentWork();
374 Sleep(120000);
375 return;
376 }
377
378 if (!strcmp(argv[1], "A:child")) {
379 g_prefix = "child: ";
380 dumpHandles();
381 testA_childWork();
382 Sleep(120000);
383 return;
384 }
385}
386
387
388
389///////////////////////////////////////////////////////////////////////////////
390// TEST B -- invert TEST A -- also crashes conhost on Windows 7
391//
392// Test passes if the message "TEST PASSED!" is visible.
393// Test commonly fails if conhost.exe crashes.
394//
395// Results:
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
400//
401
402static void testB(int argc, char *argv[]) {
403 if (!strcmp(argv[1], "B")) {
404 startChildProcess(L"B:parent");
405 return;
406 }
407
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);
413
414 //
415 // Step 1: Create the new buffer and make it active.
416 //
417 trace("%s----", g_prefix);
418 HANDLE newBuffer = createBuffer();
419 setConsoleActiveScreenBuffer(newBuffer);
420 writeTest(newBuffer, "<-- newBuffer -->");
421
422 //
423 // Step 4: Attempt to write again to the new buffer.
424 //
425 Sleep(9000);
426 trace("%s----", g_prefix);
427 writeTest(newBuffer, "TEST PASSED!");
428
429 //
430 // Step 5: Switch back to the original buffer.
431 //
432 Sleep(3000);
433 trace("%s----", g_prefix);
434 setConsoleActiveScreenBuffer(origBuffer);
435 closeHandle(newBuffer);
436 writeTest(origBuffer, "writing to the initial buffer");
437
438 Sleep(60000);
439 return;
440 }
441
442 if (!strcmp(argv[1], "B:child")) {
443 g_prefix = "child: ";
444 Sleep(3000);
445 trace("%s----", g_prefix);
446
447 //
448 // Step 2: Open the newly active buffer and write to it.
449 //
450 HANDLE newBuffer = openConout();
451 writeTest(newBuffer, "writing to newBuffer");
452
453 //
454 // Step 3: Close the newly active buffer.
455 //
456 Sleep(3000);
457 closeHandle(newBuffer);
458
459 Sleep(60000);
460 return;
461 }
462}
463
464
465
466///////////////////////////////////////////////////////////////////////////////
467// TEST C -- Interleaving open/close of console handles also seems to break on
468// Windows 7.
469//
470// Test:
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
479//
480// Test passes if the message "TEST PASSED!" is visible.
481// Test commonly fails if conhost.exe crashes.
482//
483// Results:
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
488//
489
490static void testC(int argc, char *argv[]) {
491 if (!strcmp(argv[1], "C")) {
492 startChildProcess(L"C:parent");
493 return;
494 }
495
496 if (!strcmp(argv[1], "C:parent")) {
497 startChildInSameConsole(L"C:child");
498 writeTest("<-- origBuffer -->");
499 g_prefix = "parent: ";
500
501 // At time=4, open newBuffer1.
502 Sleep(4000);
503 trace("%s---- t=4", g_prefix);
504 const HANDLE newBuffer1 = openConout();
505
506 // At time=8, open newBuffer2, and close newBuffer1.
507 Sleep(4000);
508 trace("%s---- t=8", g_prefix);
509 const HANDLE newBuffer2 = openConout();
510 closeHandle(newBuffer1);
511
512 // At time=25, cleanup of newBuffer2.
513 Sleep(17000);
514 trace("%s---- t=25", g_prefix);
515 closeHandle(newBuffer2);
516
517 Sleep(240000);
518 return;
519 }
520
521 if (!strcmp(argv[1], "C:child")) {
522 g_prefix = "child: ";
523
524 // At time=2, create newBuffer1 and activate it.
525 Sleep(2000);
526 trace("%s---- t=2", g_prefix);
527 const HANDLE newBuffer1 = createBuffer();
528 setConsoleActiveScreenBuffer(newBuffer1);
529 writeTest(newBuffer1, "<-- newBuffer1 -->");
530
531 // At time=6, create newBuffer2 and activate it.
532 Sleep(4000);
533 trace("%s---- t=6", g_prefix);
534 const HANDLE newBuffer2 = createBuffer();
535 setConsoleActiveScreenBuffer(newBuffer2);
536 writeTest(newBuffer2, "<-- newBuffer2 -->");
537
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?
540 Sleep(4000);
541 trace("%s---- t=10", g_prefix);
542 setConsoleActiveScreenBuffer(newBuffer1);
543 writeTest(newBuffer1, "write to newBuffer1: TEST PASSED!");
544
545 // At time=25, cleanup of newBuffer2.
546 Sleep(15000);
547 trace("%s---- t=25", g_prefix);
548 closeHandle(newBuffer2);
549
550 // At time=35, cleanup of newBuffer1. The console should switch to the
551 // initial buffer again.
552 Sleep(10000);
553 trace("%s---- t=35", g_prefix);
554 closeHandle(newBuffer1);
555
556 Sleep(240000);
557 return;
558 }
559}
560
561
562
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.)
566//
567// On success, this will appear:
568//
569// parent: <-- newBuffer -->
570// child: writing to newBuffer
571// parent: TEST PASSED!
572//
573// If this appears, it indicates that the child's closing its output handle did
574// not destroy newBuffer.
575//
576// Results:
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
580//
581
582static void testD(int argc, char *argv[]) {
583 if (!strcmp(argv[1], "D")) {
584 startChildProcess(L"D:parent");
585 return;
586 }
587
588 if (!strcmp(argv[1], "D:parent")) {
589 g_prefix = "parent: ";
590 HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
591 writeTest(origBuffer, "<-- origBuffer -->");
592
593 HANDLE newBuffer = createBuffer();
594 writeTest(newBuffer, "<-- newBuffer -->");
595 setConsoleActiveScreenBuffer(newBuffer);
596
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.
600 Sleep(2000);
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);
608
609 // At t=6, write again to newBuffer.
610 Sleep(4000);
611 trace("parent:----");
612 writeTest(newBuffer, "TEST PASSED!");
613
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.
621 Sleep(2000);
622 closeHandle(newBuffer);
623
624 Sleep(120000);
625 return;
626 }
627
628 if (!strcmp(argv[1], "D:child")) {
629 g_prefix = "child: ";
630 // At t=2, the child starts.
631 trace("child:----");
632 dumpHandles();
633 writeTest("writing to newBuffer");
634
635 // At t=4, the child explicitly closes its handle.
636 Sleep(2000);
637 trace("child:----");
638 if (GetStdHandle(STD_ERROR_HANDLE) != GetStdHandle(STD_OUTPUT_HANDLE)) {
639 closeHandle(GetStdHandle(STD_ERROR_HANDLE));
640 }
641 closeHandle(GetStdHandle(STD_OUTPUT_HANDLE));
642 closeHandle(GetStdHandle(STD_INPUT_HANDLE));
643
644 Sleep(120000);
645 return;
646 }
647}
648
649
650
651int main(int argc, char *argv[]) {
652 if (argc == 1) {
653 printf("USAGE: %s testnum\n", argv[0]);
654 return 0;
655 }
656
657 if (argv[1][0] == '1') {
658 test1(argc, argv);
659 } else if (argv[1][0] == '2') {
660 test2(argc, argv);
661 } else if (argv[1][0] == 'A') {
662 testA(argc, argv);
663 } else if (argv[1][0] == 'B') {
664 testB(argc, argv);
665 } else if (argv[1][0] == 'C') {
666 testC(argc, argv);
667 } else if (argv[1][0] == 'D') {
668 testD(argc, argv);
669 }
670 return 0;
671}