TCG's do_xsave_fpu() omitted writing the FOP field and the reserved
bytes (10..15) of each x87 register slot. Those parts of
the destination buffer retained their previous contents.

The reserved bytes of all eight slots are zeroed. TCG does not
yet track the actual FOP value, this patch stores a fixed zero instead.

Add a regression test in tests/tcg/x86_64/fxsave.c. It fills the
FXSAVE area with 0xcc, executes FNINIT and FXSAVE64, then verifies that
FOP and all reserved slot bytes are zero.

Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3522

Signed-off-by: Artemii Mashanov <[email protected]>
---
 target/i386/tcg/fpu_helper.c     |  3 ++
 tests/tcg/x86_64/Makefile.target |  1 +
 tests/tcg/x86_64/fxsave.c        | 66 ++++++++++++++++++++++++++++++++
 3 files changed, 70 insertions(+)
 create mode 100644 tests/tcg/x86_64/fxsave.c

diff --git a/target/i386/tcg/fpu_helper.c b/target/i386/tcg/fpu_helper.c
index b812125efa..9a2be488ef 100644
--- a/target/i386/tcg/fpu_helper.c
+++ b/target/i386/tcg/fpu_helper.c
@@ -2601,6 +2601,7 @@ static void do_xsave_fpu(X86Access *ac, target_ulong ptr)
     access_stw(ac, ptr + XO(legacy.fcw), env->fpuc);
     access_stw(ac, ptr + XO(legacy.fsw), fpus);
     access_stw(ac, ptr + XO(legacy.ftw), fptag ^ 0xff);
+    access_stw(ac, ptr + XO(legacy.fpop), 0);
 
     /* In 32-bit mode this is eip, sel, dp, sel.
        In 64-bit mode this is rip, rdp.
@@ -2613,6 +2614,8 @@ static void do_xsave_fpu(X86Access *ac, target_ulong ptr)
     for (i = 0; i < 8; i++) {
         floatx80 tmp = ST(i);
         do_fstt(ac, addr, tmp);
+        access_stw(ac, addr + 10, 0);
+        access_stl(ac, addr + 12, 0);
         addr += 16;
     }
 }
diff --git a/tests/tcg/x86_64/Makefile.target b/tests/tcg/x86_64/Makefile.target
index c48767fef8..0f8f48b1a5 100644
--- a/tests/tcg/x86_64/Makefile.target
+++ b/tests/tcg/x86_64/Makefile.target
@@ -20,6 +20,7 @@ X86_64_TESTS += test-1648
 X86_64_TESTS += test-2175
 X86_64_TESTS += cross-modifying-code
 X86_64_TESTS += fma
+X86_64_TESTS += fxsave
 TESTS=$(MULTIARCH_TESTS) $(X86_64_TESTS) test-x86_64
 else
 TESTS=$(MULTIARCH_TESTS)
diff --git a/tests/tcg/x86_64/fxsave.c b/tests/tcg/x86_64/fxsave.c
new file mode 100644
index 0000000000..d08a4e8a0e
--- /dev/null
+++ b/tests/tcg/x86_64/fxsave.c
@@ -0,0 +1,66 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See https://gitlab.com/qemu-project/qemu/-/issues/3522
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#define FXSAVE_FOP_OFFSET      6
+#define FXSAVE_X87_OFFSET      32
+#define FXSAVE_SLOT_SIZE       16
+#define FXSAVE_RESERVED_START  10
+#define FXSAVE_NUM_SLOTS       8
+
+struct fxsave_area {
+    uint8_t raw[512];
+} __attribute__((aligned(16)));
+
+_Static_assert(sizeof(struct fxsave_area) == 512,
+                "FXSAVE area must be exactly 512 bytes");
+
+static uint16_t u16_le(const uint8_t *p)
+{
+    uint16_t v;
+    memcpy(&v, p, sizeof(v));
+    return v;
+}
+
+int main(void)
+{
+    struct fxsave_area area;
+    uint16_t fop;
+
+    memset(&area, 0xcc, sizeof(area));
+
+    __asm__ volatile(
+        "fninit\n\t"
+        "fxsave64 %0"
+        : "+m" (area)
+        :
+        : "memory"
+    );
+
+    fop = u16_le(&area.raw[FXSAVE_FOP_OFFSET]);
+    if (fop != 0) {
+        fprintf(stderr, "FOP: expected 0, got 0x%04x\n", (unsigned)fop);
+        return 1;
+    }
+
+    for (int slot = 0; slot < FXSAVE_NUM_SLOTS; slot++) {
+        int base = FXSAVE_X87_OFFSET + slot * FXSAVE_SLOT_SIZE;
+        for (int b = FXSAVE_RESERVED_START; b < FXSAVE_SLOT_SIZE; b++) {
+            uint8_t val = area.raw[base + b];
+            if (val != 0) {
+                fprintf(stderr,
+                        "Slot %d byte %d (offset %d): expected 0x00, "
+                        "got 0x%02x\n",
+                        slot, b, base + b, (unsigned)val);
+                return 1;
+            }
+        }
+    }
+
+    return 0;
+}
-- 
2.54.0


Reply via email to