diff --git a/src/loader/x64InstructionEmulator.cpp b/src/loader/x64InstructionEmulator.cpp index fd762be4b..6067b818c 100644 --- a/src/loader/x64InstructionEmulator.cpp +++ b/src/loader/x64InstructionEmulator.cpp @@ -546,7 +546,11 @@ static bool TryEmulateSse4a(Context& context) { if ((rip[offset] & 0xf0u) == 0x40u) { rex = rip[offset++]; } - if (rip[offset] != 0x0f || rip[offset + 1] != 0x78) { + if (rip[offset] != 0x0f) { + return false; + } + const bool register_extract = prefix == 0x66 && rip[offset + 1] == 0x79; + if (rip[offset + 1] != 0x78 && !register_extract) { return false; } @@ -555,14 +559,12 @@ static bool TryEmulateSse4a(Context& context) { return false; } - const uint8_t reg = ((modrm >> 3u) & 0x07u) | ((rex & 0x04u) << 1u); - const uint8_t rm = (modrm & 0x07u) | ((rex & 0x01u) << 3u); - const uint8_t length = rip[offset + 3]; - const uint8_t index = rip[offset + 4]; + const uint8_t reg = ((modrm >> 3u) & 0x07u) | ((rex & 0x04u) << 1u); + const uint8_t rm = (modrm & 0x07u) | ((rex & 0x01u) << 3u); - // AMD SSE4a immediate-form EXTRQ/INSERTQ. + // Immediate EXTRQ encodes its destination in r/m; the two-register form uses reg. uint8_t dest_index = reg; - if (prefix == 0x66) { + if (prefix == 0x66 && !register_extract) { dest_index = rm; } auto* dest_xmm = context.Xmm(dest_index); @@ -574,6 +576,17 @@ static bool TryEmulateSse4a(Context& context) { uint64_t source = 0; std::memcpy(dest, dest_xmm, sizeof(dest)); std::memcpy(&source, src_xmm, sizeof(source)); + uint8_t length = 0; + uint8_t index = 0; + size_t instruction_length = offset + 3; + if (register_extract) { + length = static_cast(source); + index = static_cast(source >> 8u); + } else { + length = rip[offset + 3]; + index = rip[offset + 4]; + instruction_length += 2; + } if (prefix == 0x66) { dest[0] = ExtractBitField(dest[0], length, index); dest[1] = 0; @@ -581,7 +594,7 @@ static bool TryEmulateSse4a(Context& context) { dest[0] = InsertBitField(dest[0], source, length, index); } std::memcpy(dest_xmm, dest, sizeof(dest)); - context.Advance(offset + 5); + context.Advance(instruction_length); return true; } diff --git a/tests/VirtualMemoryAllocationTests.cpp b/tests/VirtualMemoryAllocationTests.cpp index 663691317..23074ba06 100644 --- a/tests/VirtualMemoryAllocationTests.cpp +++ b/tests/VirtualMemoryAllocationTests.cpp @@ -2795,6 +2795,27 @@ void TestPackedReciprocalSquareRoot() { emulate({0x66, 0x41, 0x0f, 0x78, 0xc0, 8, 4}, 7); // extrq xmm8, 8, 4 Check(test, fpstate._xmm[8].element[0] == 0x23 && fpstate._xmm[8].element[2] == 0, "SSE4a extraction lost its extended register or upper-half semantics"); + fpstate._xmm[1].element[0] = 0x1234; + fpstate._xmm[8].element[0] = 0xffffc4c8; + emulate({0x66, 0x41, 0x0f, 0x79, 0xc8}, 5); // observed fault: extrq xmm1, xmm8 + Check(test, fpstate._xmm[1].element[0] == 0x23 && fpstate._xmm[8].element[0] == 0xffffc4c8, + "register EXTRQ lost its source controls, ignored bits, or separate destination"); + fpstate._xmm[11].element[0] = 0x89abcdef; + fpstate._xmm[11].element[1] = 0x01234567; + fpstate._xmm[0].element[0] = 0x2010; + emulate({0x66, 0x44, 0x0f, 0x79, 0xd8}, 5); // extrq xmm11, xmm0 + Check(test, fpstate._xmm[11].element[0] == 0x4567 && fpstate._xmm[11].element[1] == 0, + "register EXTRQ lost its extended destination or 64-bit extraction"); + fpstate._xmm[8].element[0] = 0x89abcdef; + fpstate._xmm[8].element[1] = 0x01234567; + fpstate._xmm[9].element[0] = 0; + emulate({0x66, 0x45, 0x0f, 0x79, 0xc1}, 5); // extrq xmm8, xmm9 + Check(test, fpstate._xmm[8].element[0] == 0x89abcdef && fpstate._xmm[8].element[1] == 0x01234567, + "register EXTRQ did not interpret zero length as 64 bits"); + fpstate._xmm[3].element[0] = 0xab0408; + emulate({0x66, 0x0f, 0x79, 0xdb}, 4); // extrq xmm3, xmm3 + Check(test, fpstate._xmm[3].element[0] == 0x40, + "register EXTRQ overwrote aliased controls before reading them"); fpstate._xmm[8].element[0] = 0x1111; fpstate._xmm[8].element[2] = 0xdeadbeef; fpstate._xmm[9].element[0] = 0xab;