#unhide (This post can be viewed by guests also)
NTVDM Fullscren Issue #2
========================
Today I noticed another problem with the NTVDM Fullscreen handling in
Windows 7. I can't call it a bug, as it's by design, but it's an annoyance
anyway. When running DBASE for DOS, I noticed that switching between
windowed mode and Fullscreen in some cases took 5 seconds to react which is
very annoying and gives the impression that the NTVDM got stuck.
This issue is not with all DOS-applications, but it can be reproduced with
DBASE.
This time, it's not CONHOST which is at fault, but it's NTVDM itself.
When attaching WinDbg to the failed process, the following Stacktrace can be
seen (we already know this one from the CONHOST bug..):
Code:
kd> !process 0 0 ntvdm.exe
PROCESS 863ef2a8 SessionId: 1 Cid: 06f4 Peb: 7ffdf000 ParentCid: 0f04
DirBase: 3f4c7580 ObjectTable: 9498ccd0 HandleCount: 158.
Image: ntvdm.exe
kd> .process /r /p 863ef2a8
Implicit process is now 863ef2a8
.cache forcedecodeuser done
Loading User Symbols
........................................
kd> !process 863ef2a8
PROCESS 863ef2a8 SessionId: 1 Cid: 06f4 Peb: 7ffdf000 ParentCid: 0f04
DirBase: 3f4c7580 ObjectTable: 9498ccd0 HandleCount: 158.
Image: ntvdm.exe
VadRoot 86200e58 Vads 110 Clone 0 Private 462. Modified 375. Locked 0.
DeviceMap 97da9648
Token 973c1040
ElapsedTime 00:06:20.128
UserTime 00:00:00.000
KernelTime 00:00:00.000
QuotaPoolUsage[PagedPool] 0
QuotaPoolUsage[NonPagedPool] 0
Working Set Sizes (now,min,max) (1672, 50, 345) (6688KB, 200KB, 1380KB)
PeakWorkingSetSize 1699
VirtualSize 77 Mb
PeakVirtualSize 78 Mb
PageFaultCount 2880
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 1898
...
THREAD 851e2040 Cid 06f4.0818 Teb: 7ffdc000 Win32Thread: 00000000 WAIT: (UserRequest) UserMode Non-Alertable
860a7ff0 NotificationEvent
8637c6e8 NotificationEvent
Not impersonating
DeviceMap 97da9648
Owning Process 863ef2a8 Image: ntvdm.exe
Attached Process N/A Image: N/A
Wait Start TickCount 139029 Ticks: 109 (0:00:00:01.703)
Context Switch Count 3186
UserTime 00:00:00.000
KernelTime 00:00:00.171
Win32 Start Address ntvdm!HeartBeatThread (0x0e2c9a56)
Stack Init 93e3ffd0 Current 93e3f748 Base 93e40000 Limit 93e3d000 Call 0
Priority 15 BasePriority 15 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
ChildEBP RetAddr
93e3f760 82899e2d nt!KiSwapContext+0x26 (FPO: [Uses EBP] [0,0,4])
93e3f798 82898c87 nt!KiSwapThread+0x266
93e3f7c0 82894a64 nt!KiCommitThreadWait+0x1df
93e3f93c 82a46b0c nt!KeWaitForMultipleObjects+0x535
93e3fbc8 82a46879 nt!ObpWaitForMultipleObjects+0x262
93e3fd18 82859896 nt!NtWaitForMultipleObjects+0xcd
93e3fd18 77ac70f4 nt!KiSystemServicePostCall (FPO: [0,3] TrapFrame @ 93e3fd34)
012cfa5c 77ac6a44 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
012cfa60 75d56a8e ntdll!ZwWaitForMultipleObjects+0xc (FPO: [5,0,0])
012cfafc 7650be2e KERNELBASE!WaitForMultipleObjectsEx+0x100 (FPO: [SEH])
012cfb44 7650be9c kernel32!WaitForMultipleObjectsExImplementation+0xe0 (FPO: [5,8,4])
012cfb60 0e2d96b7 kernel32!WaitForMultipleObjects+0x18 (FPO: [4,0,0])
012cfbac 0e2c95ca ntvdm!DoHandShake+0x5c (FPO: [SEH])
012cfbc8 0e2c9916 ntvdm!DelayHeartBeat+0x53 (FPO: [1,2,4])
012cfbe4 0e2c9ad0 ntvdm!Win32_host_timer+0x19 (FPO: [0,2,0])
012cfc18 7650ee1c ntvdm!HeartBeatThread+0x7a (FPO: [SEH])
012cfc24 77ae37eb kernel32!BaseThreadInitThunk+0xe (FPO: [1,0,0])
012cfc64 77ae37be ntdll!__RtlUserThreadStart+0x70 (FPO: [SEH])
012cfc7c 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [2,2,0]) So it's stuck at the first WaitForMultipleObjects call in ntvdm!DoHandShake:
Code:
ntvdm!DoHandShake:
0e4c965b 6a14 push 14h
0e4c965d 68f8e04f0e push offset ntvdm!mouse_cursor_mode_change+0x4d3 (0e4fe0f8)
0e4c9662 e8cdc6fcff call ntvdm!_SEH_prolog4 (0e495d34)
0e4c9667 33db xor ebx,ebx
0e4c9669 895de4 mov dword ptr [ebp-1Ch],ebx
0e4c966c a1c0d1530e mov eax,dword ptr [ntvdm!hErrorHardwareEvent (0e53d1c0)]
0e4c9671 8945dc mov dword ptr [ebp-24h],eax
0e4c9674 a1a4d4530e mov eax,dword ptr [ntvdm!hMainThreadSuspended (0e53d4a4)]
0e4c9679 8945e0 mov dword ptr [ebp-20h],eax
0e4c967c ff35b4d4530e push dword ptr [ntvdm!hResume (0e53d4b4)]
0e4c9682 ff15b011490e call dword ptr [ntvdm!_imp__ResetEvent (0e4911b0)]
0e4c9688 ff35acd4530e push dword ptr [ntvdm!hSuspend (0e53d4ac)]
0e4c968e 8b3d7411490e mov edi,dword ptr [ntvdm!_imp__SetEvent (0e491174)]
0e4c9694 ffd7 call edi
0e4c9696 c605c42d510e01 mov byte ptr [ntvdm!HandshakeInProgress (0e512dc4)],1
0e4c969d 891dc8d1530e mov dword ptr [ntvdm!HandshakeStage (0e53d1c8)],ebx
0e4c96a3 68f4010000 push 1F4h
0e4c96a8 53 push ebx
0e4c96a9 8d45dc lea eax,[ebp-24h]
0e4c96ac 50 push eax
0e4c96ad 6a02 push 2
0e4c96af 8b35cc11490e mov esi,dword ptr [ntvdm!_imp__WaitForMultipleObjects (0e4911cc)]
0e4c96b5 ffd6 call esi
0e4c96b7 3d02010000 cmp eax,102h
0e4c96bc 7547 jne ntvdm!DoHandShake+0xaa (0e4c9705)
0e4c96be b814070000 mov eax,714h
0e4c96c3 f0810800000001 lock or dword ptr [eax],1000000h
0e4c96ca ff352ce0530e push dword ptr [ntvdm!CurrentMonitorTeb (0e53e02c)]
0e4c96d0 e81fa70000 call ntvdm!ThreadLookUp (0e4d3df4)
0e4c96d5 3bc3 cmp eax,ebx
0e4c96d7 7408 je ntvdm!DoHandShake+0x86 (0e4c96e1)
0e4c96d9 50 push eax
0e4c96da 6a01 push 1
0e4c96dc e877d10200 call ntvdm!NtVdmControl (0e4f6858)
0e4c96e1 bbc0270900 mov ebx,927C0h
0e4c96e6 53 push ebx
0e4c96e7 6a00 push 0
0e4c96e9 8d45dc lea eax,[ebp-24h]
0e4c96ec 50 push eax
0e4c96ed 6a02 push 2
0e4c96ef ffd6 call esi
0e4c96f1 83f801 cmp eax,1
0e4c96f4 7427 je ntvdm!DoHandShake+0xc2 (0e4c971d)
0e4c96f6 c705c8d1530e14000000 mov dword ptr [ntvdm!HandshakeStage (0e53d1c8)],14h
0e4c9700 e9d4000000 jmp ntvdm!DoHandShake+0x17e (0e4c97d9)
0e4c9705 3bc3 cmp eax,ebx
0e4c9707 750f jne ntvdm!DoHandShake+0xbd (0e4c9718)
0e4c9709 c705c8d1530e0a000000 mov dword ptr [ntvdm!HandshakeStage (0e53d1c8)],0Ah
0e4c9713 e9c1000000 jmp ntvdm!DoHandShake+0x17e (0e4c97d9)
...
As can be seen, there are 2 handles being waited on:
ntvdm!hErrorHardwareEvent and ntvdm!hMainThreadSuspended with a timeout of
5000ms (1F4h). Bingo!
Now obviously normally the hMainThreadSuspended needs to get satisfied, we're
not keen on a handshake error. If the wait times out (return value is
WAIT_TIMEOUT=102h) then a Wakeup-call to the Monitor in the kernel, which is
responsible for the V86 execution of the program is being sent via NtVdmControl
and then there is another Wait for 60 seconds (927C0h) this time.
This gives us a clue where to search for where the necessary condition is being
set:
Code:
ntvdm!cpu_simulate:
0e4d3148 8bff mov edi,edi
0e4d314a 55 push ebp
0e4d314b 8bec mov ebp,esp
0e4d314d 51 push ecx
0e4d314e 64a118000000 mov eax,dword ptr fs:[00000018h]
0e4d3154 53 push ebx
0e4d3155 a32ce0530e mov dword ptr [ntvdm!CurrentMonitorTeb (0e53e02c)],eax
0e4d315a 64a118000000 mov eax,dword ptr fs:[00000018h]
0e4d3160 56 push esi
0e4d3161 8bb0180f0000 mov esi,dword ptr [eax+0F18h]
0e4d3167 33db xor ebx,ebx
0e4d3169 57 push edi
0e4d316a bf00020000 mov edi,200h
0e4d316f 3bf3 cmp esi,ebx
0e4d3171 0f846a010000 je ntvdm!cpu_simulate+0x199 (0e4d32e1)
0e4d3177 8d8674060000 lea eax,[esi+674h]
0e4d317d c60001 mov byte ptr [eax],1
0e4d3180 c786d802000007000100 mov dword ptr [esi+2D8h],10007h
0e4d318a 3818 cmp byte ptr [eax],bl
0e4d318c 0f8448010000 je ntvdm!cpu_simulate+0x192 (0e4d32da)
0e4d3192 f6051407000003 test byte ptr ds:[714h],3
0e4d3199 7405 je ntvdm!cpu_simulate+0x58 (0e4d31a0)
0e4d319b e87b090000 call ntvdm!DispatchInterrupts (0e4d3b1b)
0e4d31a0 e8aef6ffff call ntvdm!getMSW (0e4d2853)
0e4d31a5 a801 test al,1
0e4d31a7 7456 je ntvdm!cpu_simulate+0xb7 (0e4d31ff)
0e4d31a9 391d54df500e cmp dword ptr [ntvdm!VDMForWOW (0e50df54)],ebx
0e4d31af 7524 jne ntvdm!cpu_simulate+0x8d (0e4d31d5)
0e4d31b1 e843f6ffff call ntvdm!getIF (0e4d27f9)
0e4d31b6 85c0 test eax,eax
0e4d31b8 751b jne ntvdm!cpu_simulate+0x8d (0e4d31d5)
0e4d31ba 81ff00020000 cmp edi,200h
0e4d31c0 7513 jne ntvdm!cpu_simulate+0x8d (0e4d31d5)
0e4d31c2 8d45fc lea eax,[ebp-4]
0e4d31c5 50 push eax
0e4d31c6 6a0d push 0Dh
0e4d31c8 c745fc03000000 mov dword ptr [ebp-4],3
0e4d31cf ff159814490e call dword ptr [ntvdm!_imp__NtVdmControl (0e491498)]
0e4d31d5 81a698030000fffffdff and dword ptr [esi+398h],0FFFDFFFFh
0e4d31df 391dc42d510e cmp dword ptr [ntvdm!HandshakeInProgress (0e512dc4)],ebx ; BINGO #1!
0e4d31e5 740b je ntvdm!cpu_simulate+0xaa (0e4d31f2)
0e4d31e7 ff35a4d4530e push dword ptr [ntvdm!hMainThreadSuspended (0e53d4a4)] ; Our wait condition
0e4d31ed e804ffffff call ntvdm!CheckScreenSwitchRequest (0e4d30f6) ; Aha, what's that?
0e4d31f2 881d3865500e mov byte ptr [ntvdm!MainThreadInMonitor (0e506538)],bl
0e4d31f8 e84f160000 call ntvdm!FastEnterPm (0e4d484c)
0e4d31fd eb2b jmp ntvdm!cpu_simulate+0xe2 (0e4d322a)
0e4d31ff 818e9803000000000200 or dword ptr [esi+398h],20000h
0e4d3209 391dc42d510e cmp dword ptr [ntvdm!HandshakeInProgress (0e512dc4)],ebx
...
So there are multiple calls to ntvdm!CheckScreenSwitchRequest in cpu_simulate
of the NTVDM, which is the trap handler for V86 execution.
Code:
ntvdm!CheckScreenSwitchRequest:
0e4d30f6 8bff mov edi,edi
0e4d30f8 55 push ebp
0e4d30f9 8bec mov ebp,esp
0e4d30fb 56 push esi
0e4d30fc 8b357811490e mov esi,dword ptr [ntvdm!_imp__WaitForSingleObject (0e491178)]
0e4d3102 57 push edi
0e4d3103 6a00 push 0
0e4d3105 ff35acd4530e push dword ptr [ntvdm!hSuspend (0e53d4ac)]
0e4d310b ffd6 call esi
0e4d310d 8b3db011490e mov edi,dword ptr [ntvdm!_imp__ResetEvent (0e4911b0)]
0e4d3113 eb1f jmp ntvdm!CheckScreenSwitchRequest+0x3e (0e4d3134)
0e4d3115 ff157411490e call dword ptr [ntvdm!_imp__SetEvent (0e491174)]
0e4d311b 6aff push 0FFFFFFFFh
0e4d311d ff35b4d4530e push dword ptr [ntvdm!hResume (0e53d4b4)]
0e4d3123 ffd6 call esi
0e4d3125 ff7508 push dword ptr [ebp+8]
0e4d3128 ffd7 call edi
0e4d312a 6a00 push 0
0e4d312c ff35acd4530e push dword ptr [ntvdm!hSuspend (0e53d4ac)]
0e4d3132 ffd6 call esi
0e4d3134 ff7508 push dword ptr [ebp+8]
0e4d3137 85c0 test eax,eax
0e4d3139 74da je ntvdm!CheckScreenSwitchRequest+0x1f (0e4d3115)
0e4d313b ffd7 call edi
0e4d313d 5f pop edi
0e4d313e 5e pop esi
0e4d313f 5d pop ebp
0e4d3140 c20400 ret 4
So we finally get the idea what is going on here:
NTVDM is assuming that the hosted DOS application generates some traps that
cause it to return to the cpu_simulate thread which in turn checks for
HandshakeInProgress in multiple places. If the is is set, it suspends
execution of the DOS application and the signals success by setting the
hMainThreadSuspended event which in turn releases the Wait-condition in
DoHandshake and let it continue its screen switch routine. After that the main
thread can be resumed and DOS-application is happy to continue its execution.
But if the application like DBASE isn't doing any traps, the Wait-call runs
into its 5 second timeout and this causes it to finally wake up the V86
MONITOR in kernel via NtVdmControl syscall and then finally the event gets
trapped and everything continues as expected.
Now why didn't this occur i.e. in the Windows XP version of NTVDM?
When looking at the DoHandshake() code in there, it just does a normal
SuspendThread(MainThread); in there and everything is working flawlessly.
Why M$ decided to change this behaviour is unknown to me, I guess only their
architects can tell us.
So this time, it's not a bug, but I think the timeout of 5 seconds is a bad
choice and a bit too high and therefore causes annoyance to the user.
So let's change it to another value, i.e. 500ms which looks fair considered
the fact that there is a wakeup-call to the Monitor in case of timeout anyway:
Search for
Code:
68 88 13 00 00 53 8D 45 DC 50
in NTVDM and replace it with i.e.:
for 500ms.
When patching ntvdm in Windows\System32 directory, ensure that you have
permissions to do so, so doing
Code:
takeown /f ntvdm.exe
icacls ntvdm.exe /grant [your username]:F
before.
Of course, you can also use a little patcher that I wrote for your convenience:
Just run ntvdm_pat.exe to patch your Windows 7 NTVDM to 500ms timeout.
If you want to use a different timeout value, please pass the desired value to
it on the command line, i.e. if you want to use 200ms timeout, run:
This should fix another annoying behaviour of Windows 7 NTVDM for people who
for whatever reason cannot stick with Windows XP (i.e. because of a lack of
drivers).
Direct download:
http://www.waldbauer.com/tmp/dl.php?download=ntvdmpatch