From ce3af2bd7a19ef9dfe4fc62a9b435f88199d242e Mon Sep 17 00:00:00 2001 From: Yang Liu Date: Tue, 12 May 2026 16:13:02 +0800 Subject: [PATCH] [INTERP][DYNAREC] Fixed XSAVE/XRSTOR opcodes (#3849) * [INTERP][DYNAREC] Fixed XSAVE/XRSTOR opcodes * Added a unitest --- CMakeLists.txt | 5 + src/dynarec/arm64/dynarec_arm64_0f.c | 4 +- src/dynarec/la64/dynarec_la64_0f.c | 4 +- src/dynarec/rv64/dynarec_rv64_0f.c | 4 +- src/emu/x64run0f.c | 4 +- tests/misc/xsave_xrstor | Bin 0 -> 11888 bytes tests/misc/xsave_xrstor.c | 160 +++++++++++++++++++++++++++ tests/misc/xsave_xrstor.txt | 2 + 8 files changed, 175 insertions(+), 8 deletions(-) create mode 100755 tests/misc/xsave_xrstor create mode 100644 tests/misc/xsave_xrstor.c create mode 100644 tests/misc/xsave_xrstor.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index f4fc24959..3080ff07b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1887,6 +1887,11 @@ add_test(lock ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} endif() if(NOT ANDROID) +add_test(NAME xsave_xrstor COMMAND ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/misc/xsave_xrstor -D TEST_OUTPUT=tmpfile-xsave_xrstor.txt + -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/misc/xsave_xrstor.txt + -P ${CMAKE_SOURCE_DIR}/runTest.cmake) + file(GLOB extension_tests "${CMAKE_SOURCE_DIR}/tests/extensions/*.c") foreach(file ${extension_tests}) get_filename_component(testname "${file}" NAME_WE) diff --git a/src/dynarec/arm64/dynarec_arm64_0f.c b/src/dynarec/arm64/dynarec_arm64_0f.c index 3dd3152ba..d32a702bb 100644 --- a/src/dynarec/arm64/dynarec_arm64_0f.c +++ b/src/dynarec/arm64/dynarec_arm64_0f.c @@ -2075,7 +2075,7 @@ uintptr_t dynarec64_0F(dynarec_arm_t* dyn, uintptr_t addr, uintptr_t ip, int nin BARRIER(BARRIER_FLOAT); addr = geted(dyn, addr, ninst, nextop, &ed, x1, &fixedaddress, NULL, 0, 0, rex, NULL, 0, 0); if(ed!=x1) {MOVx_REG(x1, ed);} - MOV32w(x2, rex.w?0:1); + MOV32w(x2, rex.is32bits); CALL(const_fpu_xsave, -1); break; case 5: @@ -2084,7 +2084,7 @@ uintptr_t dynarec64_0F(dynarec_arm_t* dyn, uintptr_t addr, uintptr_t ip, int nin BARRIER(BARRIER_FLOAT); addr = geted(dyn, addr, ninst, nextop, &ed, x1, &fixedaddress, NULL, 0, 0, rex, NULL, 0, 0); if(ed!=x1) {MOVx_REG(x1, ed);} - MOV32w(x2, rex.w?0:1); + MOV32w(x2, rex.is32bits); CALL(const_fpu_xrstor, -1); break; case 7: diff --git a/src/dynarec/la64/dynarec_la64_0f.c b/src/dynarec/la64/dynarec_la64_0f.c index 7a16d45b3..7b7f76e89 100644 --- a/src/dynarec/la64/dynarec_la64_0f.c +++ b/src/dynarec/la64/dynarec_la64_0f.c @@ -1709,7 +1709,7 @@ uintptr_t dynarec64_0F(dynarec_la64_t* dyn, uintptr_t addr, uintptr_t ip, int ni MESSAGE(LOG_DUMP, "Need Optimization\n"); BARRIER(BARRIER_FLOAT); addr = geted(dyn, addr, ninst, nextop, &ed, x1, x2, &fixedaddress, rex, NULL, 0, 0); - MOV32w(x2, rex.w ? 0 : 1); + MOV32w(x2, rex.is32bits); CALL(const_fpu_xsave, -1, ed, x2); break; case 5: @@ -1717,7 +1717,7 @@ uintptr_t dynarec64_0F(dynarec_la64_t* dyn, uintptr_t addr, uintptr_t ip, int ni MESSAGE(LOG_DUMP, "Need Optimization\n"); BARRIER(BARRIER_FLOAT); addr = geted(dyn, addr, ninst, nextop, &ed, x1, x2, &fixedaddress, rex, NULL, 0, 0); - MOV32w(x2, rex.w ? 0 : 1); + MOV32w(x2, rex.is32bits); CALL(const_fpu_xrstor, -1, ed, x2); break; case 7: diff --git a/src/dynarec/rv64/dynarec_rv64_0f.c b/src/dynarec/rv64/dynarec_rv64_0f.c index 37c8ce8e7..ff683efe5 100644 --- a/src/dynarec/rv64/dynarec_rv64_0f.c +++ b/src/dynarec/rv64/dynarec_rv64_0f.c @@ -2095,7 +2095,7 @@ uintptr_t dynarec64_0F(dynarec_rv64_t* dyn, uintptr_t addr, uintptr_t ip, int ni MESSAGE(LOG_DUMP, "Need Optimization\n"); fpu_purgecache(dyn, ninst, 0, x1, x2, x3); addr = geted(dyn, addr, ninst, nextop, &ed, x1, x2, &fixedaddress, rex, NULL, 0, 0); - MOV32w(x2, rex.w ? 0 : 1); + MOV32w(x2, rex.is32bits); CALL(const_fpu_xsave, -1, ed, x2); break; case 5: @@ -2103,7 +2103,7 @@ uintptr_t dynarec64_0F(dynarec_rv64_t* dyn, uintptr_t addr, uintptr_t ip, int ni MESSAGE(LOG_DUMP, "Need Optimization\n"); fpu_purgecache(dyn, ninst, 0, x1, x2, x3); addr = geted(dyn, addr, ninst, nextop, &ed, x1, x2, &fixedaddress, rex, NULL, 0, 0); - MOV32w(x2, rex.w ? 0 : 1); + MOV32w(x2, rex.is32bits); CALL(const_fpu_xrstor, -1, ed, x2); break; case 7: diff --git a/src/emu/x64run0f.c b/src/emu/x64run0f.c index 499e9a348..79ca8d890 100644 --- a/src/emu/x64run0f.c +++ b/src/emu/x64run0f.c @@ -1403,12 +1403,12 @@ uintptr_t Run0F(x64emu_t *emu, rex_t rex, uintptr_t addr, int *step) #ifdef TEST_INTERPRETER emu->sw.f.F87_TOP = emu->top&7; #else - fpu_xsave(emu, ED, rex.w?0:1); + fpu_xsave(emu, ED, rex.is32bits); #endif break; case 5: /* XRSTOR Ed */ _GETED(0); - fpu_xrstor(emu, ED, rex.w?0:1); + fpu_xrstor(emu, ED, rex.is32bits); break; case 7: /* CLFLUSH Ed */ _GETED(0); diff --git a/tests/misc/xsave_xrstor b/tests/misc/xsave_xrstor new file mode 100755 index 0000000000000000000000000000000000000000..b6b76d436e240721993d9e816a7e0fd236a8ed4d GIT binary patch literal 11888 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 rbW2NpxvtXRz0$hK_2mYBkOLsC)Tbp3w +#include +#include +#include + +typedef struct xmm128_s { + uint64_t q[2]; +} xmm128_t; + +static const xmm128_t expected_state[8] = { + {{0x8881828384858687ULL, 0x1811121314151617ULL}}, + {{0x9992939495969798ULL, 0x2922232425262728ULL}}, + {{0xAAA3A4A5A6A7A8A9ULL, 0x3A33343536373839ULL}}, + {{0xBBB4B5B6B7B8B9BAULL, 0x4B4445464748494AULL}}, + {{0xCCC5C6C7C8C9CACBULL, 0x5C55565758595A5BULL}}, + {{0xDDD6D7D8D9DADBDCULL, 0x6D666768696A6B6CULL}}, + {{0xEEE7E8E9EAEBECEDULL, 0x7E7778797A7B7C7DULL}}, + {{0xFFF8F9FAFBFCFDFEULL, 0x8F88898A8B8C8D8EULL}}, +}; + +static const xmm128_t poisoned_state[8] = { + {{0xF0F1F2F3F4F5F6F7ULL, 0x0706050403020100ULL}}, + {{0xE0E1E2E3E4E5E6E7ULL, 0x1716151413121110ULL}}, + {{0xD0D1D2D3D4D5D6D7ULL, 0x2726252423222120ULL}}, + {{0xC0C1C2C3C4C5C6C7ULL, 0x3736353433323130ULL}}, + {{0xB0B1B2B3B4B5B6B7ULL, 0x4746454443424140ULL}}, + {{0xA0A1A2A3A4A5A6A7ULL, 0x5756555453525150ULL}}, + {{0x9091929394959697ULL, 0x6766656463626160ULL}}, + {{0x8081828384858687ULL, 0x7776757473727170ULL}}, +}; + +static inline uint64_t read_xcr0(void) +{ + uint32_t eax, edx; + __asm__ volatile("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0)); + return ((uint64_t)edx << 32) | eax; +} + +static inline void xsave_plain(void* area, uint64_t mask) +{ + uint32_t eax = (uint32_t)mask; + uint32_t edx = (uint32_t)(mask >> 32); + __asm__ volatile("xsave (%0)" : : "r"(area), "a"(eax), "d"(edx) : "memory"); +} + +static inline void xrstor_plain(const void* area, uint64_t mask) +{ + uint32_t eax = (uint32_t)mask; + uint32_t edx = (uint32_t)(mask >> 32); + __asm__ volatile("xrstor (%0)" : : "r"(area), "a"(eax), "d"(edx) : "memory"); +} + +static inline void xsave64_rexw(void* area, uint64_t mask) +{ + uint32_t eax = (uint32_t)mask; + uint32_t edx = (uint32_t)(mask >> 32); + __asm__ volatile( + "mov %0, %%rdi\n\t" + ".byte 0x48, 0x0f, 0xae, 0x27" + : + : "r"(area), "a"(eax), "d"(edx) + : "rdi", "memory"); +} + +static inline void xrstor64_rexw(const void* area, uint64_t mask) +{ + uint32_t eax = (uint32_t)mask; + uint32_t edx = (uint32_t)(mask >> 32); + __asm__ volatile( + "mov %0, %%rdi\n\t" + ".byte 0x48, 0x0f, 0xae, 0x2f" + : + : "r"(area), "a"(eax), "d"(edx) + : "rdi", "memory"); +} + +static inline void load_xmm8_15(const xmm128_t* values) +{ + __asm__ volatile( + "movdqu 0(%0), %%xmm8\n\t" + "movdqu 16(%0), %%xmm9\n\t" + "movdqu 32(%0), %%xmm10\n\t" + "movdqu 48(%0), %%xmm11\n\t" + "movdqu 64(%0), %%xmm12\n\t" + "movdqu 80(%0), %%xmm13\n\t" + "movdqu 96(%0), %%xmm14\n\t" + "movdqu 112(%0), %%xmm15\n\t" + : + : "r"(values) + : "memory", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15"); +} + +static inline void store_xmm8_15(xmm128_t* values) +{ + __asm__ volatile( + "movdqu %%xmm8, 0(%0)\n\t" + "movdqu %%xmm9, 16(%0)\n\t" + "movdqu %%xmm10, 32(%0)\n\t" + "movdqu %%xmm11, 48(%0)\n\t" + "movdqu %%xmm12, 64(%0)\n\t" + "movdqu %%xmm13, 80(%0)\n\t" + "movdqu %%xmm14, 96(%0)\n\t" + "movdqu %%xmm15, 112(%0)\n\t" + : + : "r"(values) + : "memory"); +} + +static void dump_reg(const char* name, const xmm128_t* value) +{ + printf(" %s = 0x%016" PRIx64 "%016" PRIx64 "\n", name, value->q[1], value->q[0]); +} + +static int compare_xmm8_15(const char* name) +{ + static const char* reg_names[8] = { + "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", + }; + xmm128_t got[8]; + int failed = 0; + + store_xmm8_15(got); + for (int i = 0; i < 8; ++i) { + if (memcmp(&got[i], &expected_state[i], sizeof(got[i])) != 0) { + if (!failed) + printf("%s: FAIL\n", name); + failed = 1; + dump_reg(reg_names[i], &got[i]); + } + } + if (!failed) + printf("%s: OK\n", name); + return failed; +} + +static int run_roundtrip(const char* name, void (*xsave_fn)(void*, uint64_t), void (*xrstor_fn)(const void*, uint64_t)) +{ + struct __attribute__((aligned(64))) { + unsigned char bytes[1024]; + } area; + + // XRSTOR validates reserved bytes in the XSAVE image, so start from a + // zeroed buffer instead of poison-filled memory. + memset(&area, 0, sizeof(area)); + load_xmm8_15(expected_state); + xsave_fn(&area, 0x3); + load_xmm8_15(poisoned_state); + xrstor_fn(&area, 0x3); + return compare_xmm8_15(name); +} + +int main(void) +{ + int failed = 0; + + failed |= run_roundtrip("XSAVE/XRSTOR restores XMM8..XMM15", xsave_plain, xrstor_plain); + failed |= run_roundtrip("XSAVE64/XRSTOR64 restores XMM8..XMM15", xsave64_rexw, xrstor64_rexw); + + return failed; +} diff --git a/tests/misc/xsave_xrstor.txt b/tests/misc/xsave_xrstor.txt new file mode 100644 index 000000000..e9c4dbfde --- /dev/null +++ b/tests/misc/xsave_xrstor.txt @@ -0,0 +1,2 @@ +XSAVE/XRSTOR restores XMM8..XMM15: OK +XSAVE64/XRSTOR64 restores XMM8..XMM15: OK