Summary
The WHP backend runs the guest without a populated IDT, so any CPU exception the emulator does not explicitly intercept gets vectored by the hardware through the null IDT — reading IDT_base(0) + vector*16 and faulting. Today only #DB/#BP/#UD/#PF are intercepted (WHvPartitionPropertyCodeExceptionExitBitmap). Any other exception → spurious #PF at a tiny address → crash that looks like a guest null-deref but isn't.
Concrete bug this surfaced from
Intermittent in-game crash in iw4x/MW2 (WHP) reported as Null-pointer call to 0x130:
0x130 = vector 19 (#XM, SIMD FP exception) × 16 = IDT[19]. That is the whole tell.
- Trigger: guest MXCSR is corrupted to
0x20 (all exception masks cleared; Windows user-mode default is 0x1F80 = masked). An SSE op (CVTTSD2SI [ESP] at iw4x.exe:0x6b5255, DIVSS at iw4x.dll:0x7ee9b) on a NaN/out-of-range value then raises #XM, which isn't intercepted → null-IDT fault at 0x130.
- What made it confusing: the "faulting instruction" is a benign FP op that touches no memory; the error code is supervisor (descriptor/IDT reads are supervisor); RIP and all segment bases are correct. It survives with preemption disabled (so it's not a scheduler race), just rarer.
Current workaround (applied)
- Add
WHvX64ExceptionTypeSimdFloatingPointFault (#XM) and WHvX64ExceptionTypeFloatingPointErrorFault (#MF) to the exception-exit bitmap.
- In
handle_exception, re-mask MXCSR (| 0x1F80 & ~0x3F) + x87 CW (| 0x3F) and re-run the instruction so it completes (NaN/inf), matching Windows masked-FP behavior.
Validated: [FP-EXC] #1 type=19 mxcsr 0x20->0x1f80 readback=0x1f80, no crash, in-game survival went from ~60-90s to 325s+ before hitting an unrelated failure.
This stops the crash but is a workaround, because:
- It treats the symptom (re-masks) instead of the cause — whatever clears the MXCSR mask bits (most likely the WoW64 32-bit FP-state transition; a sibling of the fresh-thread x87 TagWord seeding bug).
- It forcibly overrides guest MXCSR (not transparent if a program legitimately unmasks FP exceptions).
- It only covers the FP vectors.
#DE (vector 0 → IDT[0]=0x0), #GP (13 → 0xD0), #AC, #BR, #OF, … are still un-intercepted and would crash identically.
Proper fix
- Find and fix the MXCSR mask corruption — trace MXCSR across the WoW64 32↔64 boundary / FP-state save-restore so
#XM never fires (the Windows-correct state is 0x1F80).
- Generalize exception handling — intercept all user-reachable exception vectors and faithfully deliver each to the guest as the correct
STATUS_* SEH exception (decoding MXCSR / error code), exactly as the Windows kernel does, instead of depending on the (absent) guest IDT.
Repro
EMULATOR_WHP=1 analyzer -vc -ss "<...>/iw4x.exe" -console -stdout
Load a map and play; previously crashed within ~1-5 min with Null-pointer call to 0x130.
Summary
The WHP backend runs the guest without a populated IDT, so any CPU exception the emulator does not explicitly intercept gets vectored by the hardware through the null IDT — reading
IDT_base(0) + vector*16and faulting. Today only#DB/#BP/#UD/#PFare intercepted (WHvPartitionPropertyCodeExceptionExitBitmap). Any other exception → spurious#PFat a tiny address → crash that looks like a guest null-deref but isn't.Concrete bug this surfaced from
Intermittent in-game crash in iw4x/MW2 (WHP) reported as
Null-pointer call to 0x130:0x130= vector 19 (#XM, SIMD FP exception) × 16 =IDT[19]. That is the whole tell.0x20(all exception masks cleared; Windows user-mode default is0x1F80= masked). An SSE op (CVTTSD2SI [ESP]atiw4x.exe:0x6b5255,DIVSSatiw4x.dll:0x7ee9b) on a NaN/out-of-range value then raises#XM, which isn't intercepted → null-IDT fault at0x130.Current workaround (applied)
WHvX64ExceptionTypeSimdFloatingPointFault(#XM) andWHvX64ExceptionTypeFloatingPointErrorFault(#MF) to the exception-exit bitmap.handle_exception, re-mask MXCSR (| 0x1F80 & ~0x3F) + x87 CW (| 0x3F) and re-run the instruction so it completes (NaN/inf), matching Windows masked-FP behavior.Validated:
[FP-EXC] #1 type=19 mxcsr 0x20->0x1f80 readback=0x1f80, no crash, in-game survival went from ~60-90s to 325s+ before hitting an unrelated failure.This stops the crash but is a workaround, because:
#DE(vector 0 →IDT[0]=0x0),#GP(13 →0xD0),#AC,#BR,#OF, … are still un-intercepted and would crash identically.Proper fix
#XMnever fires (the Windows-correct state is0x1F80).STATUS_*SEH exception (decoding MXCSR / error code), exactly as the Windows kernel does, instead of depending on the (absent) guest IDT.Repro
Load a map and play; previously crashed within ~1-5 min with
Null-pointer call to 0x130.