x86emul: suppress memory writes after faulting FPU insns FPU insns writing to memory must not touch memory if they latch #MF (to be delivered on the next waiting FPU insn). Note that inspecting FSW.ES needs to be avoided for all FNST* insns, as they don't raise exceptions themselves, but may instead be invoked with the bit already set. Signed-off-by: Jan Beulich --- While #MF and memory access faults are all listed in the same priority group, it is not entirely clear how FPU insns reading memory operate when an exception to be delivered doesn't depend on the memory operand (which would namely be FPU register stack overflows). It is therefore possible that memory reads would need to be suppressed in some situations, too. Of course this only matters for reads which have side effects. Otoh SNaN operand detection and stack overflow/underflow are listed as having the same priority, so the memory read may well be performed unconditionally. Furthermore I think we have another issue with writes: If the write faults, the FSW (or MXCSR, albeit there only for instructions we don't emulate yet) register may have been updated already, so we'd need to undo that update. For MXCSR that will be possible by saving the initial value and re-loading it in case ->write() fails (and iirc there's exactly one affected insn - VCVTPS2PH). There's no suitable way to load FSW, though - all existing mechanisms have further effects we don't really want (albeit arguably the side effects of going through a {F,}XSAVE/{F,}XRSTOR cycle could occur at any time as a scheduling side effect). --- a/xen/arch/x86/x86_emulate/x86_emulate.c +++ b/xen/arch/x86/x86_emulate/x86_emulate.c @@ -457,6 +457,9 @@ typedef union { #define EFLG_MBS (1<<1) #define EFLG_CF (1<<0) +/* Floating point status word definitions. */ +#define FSW_ES (1U << 7) + /* MXCSR bit definitions. */ #define MXCSR_MM (1U << 17) @@ -868,6 +871,15 @@ do { (_fic)->exn_raised); \ } while (0) +static inline bool fpu_check_write(void) +{ + uint16_t fsw; + + asm ( "fnstsw %0" : "=am" (fsw) ); + + return !(fsw & FSW_ES); +} + #define emulate_fpu_insn(_op) \ asm volatile ( \ "movb $2f-1f,%0 \n" \ @@ -3723,6 +3735,8 @@ x86_emulate( default: generate_exception(EXC_UD); } + if ( dst.type == OP_MEM && dst.bytes == 4 && !fpu_check_write() ) + dst.type = OP_NONE; } put_fpu(&fic); break; @@ -3835,7 +3849,8 @@ x86_emulate( case 7: /* fstp m80fp */ fail_if(!ops->write); emulate_fpu_insn_memdst("fstpt", *mmvalp); - if ( (rc = ops->write(ea.mem.seg, ea.mem.off, mmvalp, + if ( fpu_check_write() && + (rc = ops->write(ea.mem.seg, ea.mem.off, mmvalp, 10, ctxt)) != X86EMUL_OKAY ) goto done; dst.type = OP_NONE; @@ -3843,6 +3858,8 @@ x86_emulate( default: generate_exception(EXC_UD); } + if ( dst.type == OP_MEM && !fpu_check_write() ) + dst.type = OP_NONE; } put_fpu(&fic); break; @@ -3946,6 +3963,8 @@ x86_emulate( default: generate_exception(EXC_UD); } + if ( dst.type == OP_MEM && dst.bytes == 8 && !fpu_check_write() ) + dst.type = OP_NONE; } put_fpu(&fic); break; @@ -4063,7 +4082,8 @@ x86_emulate( case 6: /* fbstp packed bcd */ fail_if(!ops->write); emulate_fpu_insn_memdst("fbstp", *mmvalp); - if ( (rc = ops->write(ea.mem.seg, ea.mem.off, mmvalp, + if ( fpu_check_write() && + (rc = ops->write(ea.mem.seg, ea.mem.off, mmvalp, 10, ctxt)) != X86EMUL_OKAY ) goto done; dst.type = OP_NONE; @@ -4073,6 +4093,8 @@ x86_emulate( dst.bytes = 8; break; } + if ( dst.type == OP_MEM && !fpu_check_write() ) + dst.type = OP_NONE; } put_fpu(&fic); break;