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
