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
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 <ralerrdirsardx@gmail.com>
---
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
Hi,
Gentle ping on this patch from August 26. Could you please take a look when
you have time?
Patch archive
<https://www.mail-archive.com/qemu-devel@nongnu.org/msg1220122.html>
I'm happy to address any feedback.
Thanks
ср, 26 авг. 2026 г. в 20:16, Artemii Mashanov <ralerrdirsardx@gmail.com>:
> 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 <ralerrdirsardx@gmail.com>
> ---
> 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
>
>
© 2016 - 2026 Red Hat, Inc.