mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 19:07:01 +02:00
35c6e8fff6
* loader: enable AMD CPU instruction patching on macOS The guest instruction patcher was compiled out on macOS and runtimeLinker forced emulate_amd off, so under Rosetta every SSE4a EXTRQ/INSERTQ took the SIGILL path (about 18 us each). Build the patcher on all hosts and let --amd-cpu enable it on macOS like on Windows and Linux. The host feature probe already reports no SSE4a under Rosetta, so EXTRQ becomes a native trampoline and the remaining fallbacks keep using the x64 instruction emulator through the host exception handler. * tests: macos_sse4a covers the guest instruction patcher on macOS Patch EXTRQ/INSERTQ blobs in an RWX area with a trampoline page behind it, with the host features of Rosetta (no SSE4a/RDPID/CLWB), and compare against the SIGILL emulator: EXTRQ must become a native trampoline without SIGILL, INSERTQ stays a trap handled by the emulator, and nothing is touched without emulate_amd. * loader: emulate the patcher's VRSQRTPS trap and MWAITX on macOS The AMD CPU patcher marks VRSQRTPS it cannot relocate by clearing a reserved VEX.vvvv bit, which raises #UD on every host including Rosetta. The macOS build left TryEmulateReciprocalSquareRoot and TryEmulateMonitorxMwaitx out, so the guest died with an unhandled SIGILL at the first such site. Both are plain SSE2/libc, so enable them on macOS. The Darwin context also gains ClearUpperYmm so the VEX.128 result zeroes YMM[255:128] when the thread has the AVX context. macos_sse4a covers the trapped VRSQRTPS and MWAITX. * tests: macos_sse4a runs a plain VEX.128 VRSQRTPS through the patcher The existing case started from an instruction that was already marked as a trap. The new cases feed the ordinary encoding to PatchGuestInstructions and compare the result with the emulated reference, both through a native trampoline and, when the instruction cannot be relocated, through the trap mark and the signal emulator. * loader: compact macOS instruction patcher coverage Share the emulator dispatch chain while keeping SHA emulation limited to non-Apple hosts and preserving handler order. Remove a duplicate VRSQRTPS signal test already covered by both native and trapped patcher cases, plus blank lines left by removed platform guards. --------- Co-authored-by: nmzik <Nmzik@mail.ru>
826 lines
24 KiB
C++
826 lines
24 KiB
C++
#include "loader/x64InstructionEmulator.h"
|
|
|
|
#include "common/common.h"
|
|
|
|
#include <Zydis/Zydis.h>
|
|
#include <bit>
|
|
#include <cstring>
|
|
#include <emmintrin.h>
|
|
#include <xmmintrin.h>
|
|
#include <cpuid.h>
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
#include <windows.h> // IWYU pragma: keep
|
|
#elif defined(__APPLE__)
|
|
#include <sched.h>
|
|
#include <sys/ucontext.h>
|
|
#else
|
|
#include <sched.h>
|
|
#include <ucontext.h>
|
|
#endif
|
|
|
|
namespace Loader::X64InstructionEmulator {
|
|
|
|
static uint64_t ExtractBitField(uint64_t value, uint32_t length, uint32_t index) {
|
|
length &= 0x3fu;
|
|
index &= 0x3fu;
|
|
|
|
if (length == 0) {
|
|
length = 64;
|
|
}
|
|
|
|
if (index >= 64) {
|
|
return 0;
|
|
}
|
|
|
|
auto available = 64u - index;
|
|
if (length > available) {
|
|
length = available;
|
|
}
|
|
|
|
const uint64_t mask = (length == 64 ? UINT64_MAX : ((uint64_t {1} << length) - 1u));
|
|
return (value >> index) & mask;
|
|
}
|
|
|
|
static uint64_t InsertBitField(uint64_t dst, uint64_t src, uint32_t length, uint32_t index) {
|
|
length &= 0x3fu;
|
|
index &= 0x3fu;
|
|
|
|
if (length == 0) {
|
|
length = 64;
|
|
}
|
|
|
|
if (index >= 64) {
|
|
return dst;
|
|
}
|
|
|
|
auto available = 64u - index;
|
|
if (length > available) {
|
|
length = available;
|
|
}
|
|
|
|
const uint64_t mask = (length == 64 ? UINT64_MAX : ((uint64_t {1} << length) - 1u));
|
|
const uint64_t shifted = (index == 0 ? mask : (mask << index));
|
|
const uint64_t src_shifted = (src & mask) << index;
|
|
|
|
return (dst & ~shifted) | src_shifted;
|
|
}
|
|
|
|
struct XmmWords {
|
|
uint32_t w[4];
|
|
};
|
|
|
|
static void Sha1Msg1(XmmWords& dest, const XmmWords& src2) {
|
|
const uint32_t w0 = dest.w[3];
|
|
const uint32_t w1 = dest.w[2];
|
|
const uint32_t w2 = dest.w[1];
|
|
const uint32_t w3 = dest.w[0];
|
|
const uint32_t w4 = src2.w[3];
|
|
const uint32_t w5 = src2.w[2];
|
|
dest.w[3] = w2 ^ w0;
|
|
dest.w[2] = w3 ^ w1;
|
|
dest.w[1] = w4 ^ w2;
|
|
dest.w[0] = w5 ^ w3;
|
|
}
|
|
|
|
static void Sha1Msg2(XmmWords& dest, const XmmWords& src2) {
|
|
const uint32_t w13 = src2.w[2];
|
|
const uint32_t w14 = src2.w[1];
|
|
const uint32_t w15 = src2.w[0];
|
|
const uint32_t w16 = std::rotl(dest.w[3] ^ w13, 1);
|
|
const uint32_t w17 = std::rotl(dest.w[2] ^ w14, 1);
|
|
const uint32_t w18 = std::rotl(dest.w[1] ^ w15, 1);
|
|
const uint32_t w19 = std::rotl(dest.w[0] ^ w16, 1);
|
|
dest.w[3] = w16;
|
|
dest.w[2] = w17;
|
|
dest.w[1] = w18;
|
|
dest.w[0] = w19;
|
|
}
|
|
|
|
static void Sha1Nexte(XmmWords& dest, const XmmWords& src2) {
|
|
const uint32_t tmp = std::rotl(dest.w[3], 30);
|
|
dest.w[3] = src2.w[3] + tmp;
|
|
dest.w[2] = src2.w[2];
|
|
dest.w[1] = src2.w[1];
|
|
dest.w[0] = src2.w[0];
|
|
}
|
|
|
|
static uint32_t Sha1RoundFunc(uint8_t group, uint32_t b, uint32_t c, uint32_t d) {
|
|
switch (group & 3u) {
|
|
case 0: return (b & c) ^ ((~b) & d);
|
|
case 1: return b ^ c ^ d;
|
|
case 2: return (b & c) ^ (b & d) ^ (c & d);
|
|
default: return b ^ c ^ d;
|
|
}
|
|
}
|
|
|
|
static uint32_t Sha1RoundConstant(uint8_t group) {
|
|
switch (group & 3u) {
|
|
case 0: return 0x5a827999u;
|
|
case 1: return 0x6ed9eba1u;
|
|
case 2: return 0x8f1bbcdcu;
|
|
default: return 0xca62c1d6u;
|
|
}
|
|
}
|
|
|
|
static void Sha1Rnds4(XmmWords& dest, const XmmWords& src2, uint8_t imm8) {
|
|
const uint8_t group = imm8 & 3u;
|
|
const uint32_t k = Sha1RoundConstant(group);
|
|
const uint32_t w[4] = {src2.w[3], src2.w[2], src2.w[1], src2.w[0]};
|
|
|
|
uint32_t a = dest.w[3];
|
|
uint32_t b = dest.w[2];
|
|
uint32_t c = dest.w[1];
|
|
uint32_t d = dest.w[0];
|
|
uint32_t e = 0;
|
|
|
|
for (unsigned int round = 0; round < 4u; round++) {
|
|
uint32_t term = Sha1RoundFunc(group, b, c, d) + std::rotl(a, 5) + w[round] + k;
|
|
if (round > 0u) {
|
|
term += e;
|
|
}
|
|
const uint32_t a1 = term;
|
|
e = d;
|
|
d = c;
|
|
c = std::rotl(b, 30);
|
|
b = a;
|
|
a = a1;
|
|
}
|
|
|
|
dest.w[3] = a;
|
|
dest.w[2] = b;
|
|
dest.w[1] = c;
|
|
dest.w[0] = d;
|
|
}
|
|
|
|
static uint32_t Sha256Sigma0(uint32_t x) {
|
|
return std::rotr(x, 7) ^ std::rotr(x, 18) ^ (x >> 3u);
|
|
}
|
|
|
|
static uint32_t Sha256Sigma1(uint32_t x) {
|
|
return std::rotr(x, 17) ^ std::rotr(x, 19) ^ (x >> 10u);
|
|
}
|
|
|
|
static uint32_t Sha256Sum0(uint32_t x) {
|
|
return std::rotr(x, 2) ^ std::rotr(x, 13) ^ std::rotr(x, 22);
|
|
}
|
|
|
|
static uint32_t Sha256Sum1(uint32_t x) {
|
|
return std::rotr(x, 6) ^ std::rotr(x, 11) ^ std::rotr(x, 25);
|
|
}
|
|
|
|
static uint32_t Sha256Ch(uint32_t e, uint32_t f, uint32_t g) {
|
|
return (e & f) ^ ((~e) & g);
|
|
}
|
|
|
|
static uint32_t Sha256Maj(uint32_t a, uint32_t b, uint32_t c) {
|
|
return (a & b) ^ (a & c) ^ (b & c);
|
|
}
|
|
|
|
static void Sha256Msg1(XmmWords& dest, const XmmWords& src2) {
|
|
const uint32_t w4 = src2.w[0];
|
|
const uint32_t w3 = dest.w[3];
|
|
const uint32_t w2 = dest.w[2];
|
|
const uint32_t w1 = dest.w[1];
|
|
const uint32_t w0 = dest.w[0];
|
|
dest.w[3] = w3 + Sha256Sigma0(w4);
|
|
dest.w[2] = w2 + Sha256Sigma0(w3);
|
|
dest.w[1] = w1 + Sha256Sigma0(w2);
|
|
dest.w[0] = w0 + Sha256Sigma0(w1);
|
|
}
|
|
|
|
static void Sha256Msg2(XmmWords& dest, const XmmWords& src2) {
|
|
const uint32_t w14 = src2.w[2];
|
|
const uint32_t w15 = src2.w[3];
|
|
const uint32_t w16 = dest.w[0] + Sha256Sigma1(w14);
|
|
const uint32_t w17 = dest.w[1] + Sha256Sigma1(w15);
|
|
const uint32_t w18 = dest.w[2] + Sha256Sigma1(w16);
|
|
const uint32_t w19 = dest.w[3] + Sha256Sigma1(w17);
|
|
dest.w[3] = w19;
|
|
dest.w[2] = w18;
|
|
dest.w[1] = w17;
|
|
dest.w[0] = w16;
|
|
}
|
|
|
|
static void Sha256Rnds2(XmmWords& dest, const XmmWords& src2, const XmmWords& xmm0) {
|
|
uint32_t a = src2.w[3];
|
|
uint32_t b = src2.w[2];
|
|
uint32_t c = dest.w[3];
|
|
uint32_t d = dest.w[2];
|
|
uint32_t e = src2.w[1];
|
|
uint32_t f = src2.w[0];
|
|
uint32_t g = dest.w[1];
|
|
uint32_t h = dest.w[0];
|
|
|
|
for (unsigned int round = 0; round < 2u; round++) {
|
|
const uint32_t wk = xmm0.w[round];
|
|
const uint32_t t1 = Sha256Ch(e, f, g) + Sha256Sum1(e) + wk + h;
|
|
const uint32_t t2 = Sha256Maj(a, b, c) + Sha256Sum0(a);
|
|
const uint32_t a1 = t1 + t2;
|
|
const uint32_t e1 = t1 + d;
|
|
const uint32_t b1 = a;
|
|
const uint32_t c1 = b;
|
|
const uint32_t d1 = c;
|
|
const uint32_t f1 = e;
|
|
const uint32_t g1 = f;
|
|
const uint32_t h1 = g;
|
|
a = a1;
|
|
b = b1;
|
|
c = c1;
|
|
d = d1;
|
|
e = e1;
|
|
f = f1;
|
|
g = g1;
|
|
h = h1;
|
|
}
|
|
|
|
dest.w[3] = a;
|
|
dest.w[2] = b;
|
|
dest.w[1] = e;
|
|
dest.w[0] = f;
|
|
}
|
|
|
|
struct ShaNiInsn {
|
|
uint8_t escape;
|
|
uint8_t opcode;
|
|
uint8_t imm8;
|
|
uint8_t rex;
|
|
size_t modrm_offset;
|
|
size_t length;
|
|
};
|
|
|
|
static bool DecodeShaNiInsn(const uint8_t* rip, ShaNiInsn& insn) {
|
|
size_t offset = 0;
|
|
uint8_t rex = 0;
|
|
if ((rip[0] & 0xf0u) == 0x40u) {
|
|
rex = rip[0];
|
|
offset = 1;
|
|
}
|
|
|
|
if (rip[offset] != 0x0f) {
|
|
return false;
|
|
}
|
|
|
|
if (rip[offset + 1] == 0x38) {
|
|
const uint8_t op = rip[offset + 2];
|
|
if (op != 0xc8 && op != 0xc9 && op != 0xca && op != 0xcb && op != 0xcc && op != 0xcd) {
|
|
return false;
|
|
}
|
|
insn.escape = 0x38;
|
|
insn.opcode = op;
|
|
insn.imm8 = 0;
|
|
insn.rex = rex;
|
|
insn.modrm_offset = offset + 3;
|
|
} else if (rip[offset + 1] == 0x3a && rip[offset + 2] == 0xcc) {
|
|
insn.escape = 0x3a;
|
|
insn.opcode = 0xcc;
|
|
insn.rex = rex;
|
|
insn.modrm_offset = offset + 3;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t modrm = rip[insn.modrm_offset];
|
|
const uint8_t mod = modrm >> 6u;
|
|
const uint8_t rm = modrm & 0x07u;
|
|
size_t end = insn.modrm_offset + 1;
|
|
|
|
if (mod != 3u) {
|
|
uint8_t sib_base = 0xffu;
|
|
if (rm == 4u) {
|
|
sib_base = rip[end] & 0x07u;
|
|
end++;
|
|
}
|
|
|
|
if (mod == 0u && (rm == 5u || (rm == 4u && sib_base == 5u))) {
|
|
end += 4;
|
|
} else if (mod == 1u) {
|
|
end++;
|
|
} else if (mod == 2u) {
|
|
end += 4;
|
|
}
|
|
}
|
|
|
|
if (insn.escape == 0x3a) {
|
|
insn.imm8 = rip[end];
|
|
end++;
|
|
}
|
|
|
|
insn.length = end;
|
|
return true;
|
|
}
|
|
|
|
static bool ShaNiModrmIsRegister(uint8_t modrm) {
|
|
return (modrm & 0xc0u) == 0xc0u;
|
|
}
|
|
|
|
static uint8_t ShaNiRegIndex(uint8_t modrm, uint8_t rex, bool reg_field) {
|
|
if (reg_field) {
|
|
return ((modrm >> 3u) & 0x07u) | ((rex & 0x04u) << 1u);
|
|
}
|
|
return (modrm & 0x07u) | ((rex & 0x01u) << 3u);
|
|
}
|
|
|
|
static bool ResolveShaNiMemoryAddress(const uint8_t* rip, const ShaNiInsn& insn,
|
|
const uint64_t (&gpr)[16], const void*& address) {
|
|
const uint8_t modrm = rip[insn.modrm_offset];
|
|
const uint8_t mod = modrm >> 6u;
|
|
const uint8_t rm = modrm & 0x07u;
|
|
if (mod == 3u) {
|
|
return false;
|
|
}
|
|
|
|
size_t offset = insn.modrm_offset + 1;
|
|
uint64_t result = 0;
|
|
|
|
if (rm == 4u) {
|
|
const uint8_t sib = rip[offset++];
|
|
const uint8_t scale = sib >> 6u;
|
|
const uint8_t index_low = (sib >> 3u) & 0x07u;
|
|
const uint8_t base_low = sib & 0x07u;
|
|
const bool has_index = index_low != 4u || (insn.rex & 0x02u) != 0;
|
|
const bool has_base = mod != 0u || base_low != 5u;
|
|
|
|
if (has_base) {
|
|
const uint8_t base = base_low | ((insn.rex & 0x01u) << 3u);
|
|
result += gpr[base];
|
|
}
|
|
if (has_index) {
|
|
const uint8_t index = index_low | ((insn.rex & 0x02u) << 2u);
|
|
result += gpr[index] << scale;
|
|
}
|
|
|
|
if (!has_base) {
|
|
int32_t displacement = 0;
|
|
std::memcpy(&displacement, rip + offset, sizeof(displacement));
|
|
result += static_cast<uint64_t>(static_cast<int64_t>(displacement));
|
|
offset += sizeof(displacement);
|
|
}
|
|
} else if (mod == 0u && rm == 5u) {
|
|
int32_t displacement = 0;
|
|
std::memcpy(&displacement, rip + offset, sizeof(displacement));
|
|
result = reinterpret_cast<uint64_t>(rip + insn.length) +
|
|
static_cast<uint64_t>(static_cast<int64_t>(displacement));
|
|
offset += sizeof(displacement);
|
|
} else {
|
|
const uint8_t base = rm | ((insn.rex & 0x01u) << 3u);
|
|
result = gpr[base];
|
|
}
|
|
|
|
if (mod == 1u) {
|
|
const auto displacement = static_cast<int8_t>(rip[offset]);
|
|
result += static_cast<uint64_t>(static_cast<int64_t>(displacement));
|
|
} else if (mod == 2u) {
|
|
int32_t displacement = 0;
|
|
std::memcpy(&displacement, rip + offset, sizeof(displacement));
|
|
result += static_cast<uint64_t>(static_cast<int64_t>(displacement));
|
|
}
|
|
|
|
address = reinterpret_cast<const void*>(result);
|
|
return true;
|
|
}
|
|
|
|
static bool ExecuteShaNiInsn(const ShaNiInsn& insn, const XmmWords& src2, const XmmWords& xmm0,
|
|
XmmWords& dest) {
|
|
if (insn.escape == 0x3a && insn.opcode == 0xcc) {
|
|
Sha1Rnds4(dest, src2, insn.imm8);
|
|
return true;
|
|
}
|
|
|
|
switch (insn.opcode) {
|
|
case 0xc8: Sha1Nexte(dest, src2); return true;
|
|
case 0xc9: Sha1Msg1(dest, src2); return true;
|
|
case 0xca: Sha1Msg2(dest, src2); return true;
|
|
case 0xcb: Sha256Rnds2(dest, src2, xmm0); return true;
|
|
case 0xcc: Sha256Msg1(dest, src2); return true;
|
|
case 0xcd: Sha256Msg2(dest, src2); return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
// Keep instruction semantics shared; only access to the saved host context differs.
|
|
struct Context {
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
PCONTEXT native;
|
|
|
|
[[nodiscard]] uint64_t Rip() const { return native->Rip; }
|
|
void Advance(size_t length) { native->Rip += length; }
|
|
[[nodiscard]] void* Xmm(uint8_t index) const { return &native->Xmm0 + index; }
|
|
|
|
[[nodiscard]] auto& Gpr(uint8_t index) const {
|
|
DWORD64* registers[] = {&native->Rax, &native->Rcx, &native->Rdx, &native->Rbx,
|
|
&native->Rsp, &native->Rbp, &native->Rsi, &native->Rdi,
|
|
&native->R8, &native->R9, &native->R10, &native->R11,
|
|
&native->R12, &native->R13, &native->R14, &native->R15};
|
|
return *registers[index];
|
|
}
|
|
|
|
void ClearUpperYmm(uint8_t index) const {
|
|
if ((native->ContextFlags & CONTEXT_XSTATE) != CONTEXT_XSTATE) {
|
|
return;
|
|
}
|
|
DWORD64 features = 0;
|
|
if (!GetXStateFeaturesMask(native, &features) || (features & XSTATE_MASK_AVX) == 0) {
|
|
return; // An absent AVX component restores zeroes.
|
|
}
|
|
DWORD size = 0;
|
|
auto* ymm = static_cast<M128A*>(LocateXStateFeature(native, XSTATE_AVX, &size));
|
|
if (ymm != nullptr && size >= (index + 1u) * sizeof(M128A)) {
|
|
ymm[index] = {};
|
|
}
|
|
}
|
|
#elif defined(__APPLE__)
|
|
ucontext_t* native;
|
|
|
|
[[nodiscard]] uint64_t Rip() const {
|
|
return static_cast<uint64_t>(native->uc_mcontext->__ss.__rip);
|
|
}
|
|
void Advance(size_t length) {
|
|
native->uc_mcontext->__ss.__rip += static_cast<uint64_t>(length);
|
|
}
|
|
[[nodiscard]] auto& Gpr(uint8_t index) const {
|
|
auto* state = &native->uc_mcontext->__ss;
|
|
uint64_t* registers[] = {&state->__rax, &state->__rcx, &state->__rdx, &state->__rbx,
|
|
&state->__rsp, &state->__rbp, &state->__rsi, &state->__rdi,
|
|
&state->__r8, &state->__r9, &state->__r10, &state->__r11,
|
|
&state->__r12, &state->__r13, &state->__r14, &state->__r15};
|
|
return *registers[index];
|
|
}
|
|
// Darwin names the XMM file __fpu_xmm0..__fpu_xmm15 instead of exposing an array.
|
|
[[nodiscard]] void* Xmm(uint8_t index) const {
|
|
auto* fs = &native->uc_mcontext->__fs;
|
|
switch (index) {
|
|
case 0: return &fs->__fpu_xmm0;
|
|
case 1: return &fs->__fpu_xmm1;
|
|
case 2: return &fs->__fpu_xmm2;
|
|
case 3: return &fs->__fpu_xmm3;
|
|
case 4: return &fs->__fpu_xmm4;
|
|
case 5: return &fs->__fpu_xmm5;
|
|
case 6: return &fs->__fpu_xmm6;
|
|
case 7: return &fs->__fpu_xmm7;
|
|
case 8: return &fs->__fpu_xmm8;
|
|
case 9: return &fs->__fpu_xmm9;
|
|
case 10: return &fs->__fpu_xmm10;
|
|
case 11: return &fs->__fpu_xmm11;
|
|
case 12: return &fs->__fpu_xmm12;
|
|
case 13: return &fs->__fpu_xmm13;
|
|
case 14: return &fs->__fpu_xmm14;
|
|
case 15: return &fs->__fpu_xmm15;
|
|
default: return nullptr;
|
|
}
|
|
}
|
|
// The kernel hands AVX threads the longer context that carries YMM[255:128]; a plain SSE
|
|
// context has no such state to clear.
|
|
void ClearUpperYmm(uint8_t index) const {
|
|
if (index >= 16 || native->uc_mcsize < sizeof(_STRUCT_MCONTEXT_AVX64)) {
|
|
return;
|
|
}
|
|
auto* avx = reinterpret_cast<_STRUCT_MCONTEXT_AVX64*>(native->uc_mcontext);
|
|
auto* ymmh = &avx->__fs.__fpu_ymmh0;
|
|
ymmh[index] = {};
|
|
}
|
|
#else
|
|
ucontext_t* native;
|
|
|
|
[[nodiscard]] uint64_t Rip() const {
|
|
return static_cast<uint64_t>(native->uc_mcontext.gregs[REG_RIP]);
|
|
}
|
|
void Advance(size_t length) {
|
|
native->uc_mcontext.gregs[REG_RIP] += static_cast<greg_t>(length);
|
|
}
|
|
[[nodiscard]] void* Xmm(uint8_t index) const {
|
|
if (native->uc_mcontext.fpregs == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return native->uc_mcontext.fpregs->_xmm[index].element;
|
|
}
|
|
|
|
[[nodiscard]] auto& Gpr(uint8_t index) const {
|
|
constexpr int registers[] = {REG_RAX, REG_RCX, REG_RDX, REG_RBX, REG_RSP, REG_RBP,
|
|
REG_RSI, REG_RDI, REG_R8, REG_R9, REG_R10, REG_R11,
|
|
REG_R12, REG_R13, REG_R14, REG_R15};
|
|
return native->uc_mcontext.gregs[registers[index]];
|
|
}
|
|
|
|
void ClearUpperYmm(uint8_t index) const {
|
|
// Linux signal frames use the standard XSAVE layout. An absent AVX component
|
|
// already restores the architectural initial value (all zeroes).
|
|
auto* state = reinterpret_cast<uint8_t*>(native->uc_mcontext.fpregs);
|
|
uint32_t magic = 0;
|
|
uint32_t size = 0;
|
|
uint64_t features = 0;
|
|
std::memcpy(&magic, state + 464, sizeof(magic));
|
|
if (magic != 0x46505853) {
|
|
return;
|
|
}
|
|
std::memcpy(&size, state + 480, sizeof(size));
|
|
if (size < 832) {
|
|
return;
|
|
}
|
|
std::memcpy(&features, state + 512, sizeof(features));
|
|
if ((features & 4) != 0) {
|
|
std::memset(state + 576 + index * 16, 0, 16);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void LoadGprs(uint64_t (&gpr)[16]) const {
|
|
for (uint8_t i = 0; i < 16; ++i) {
|
|
gpr[i] = static_cast<uint64_t>(Gpr(i));
|
|
}
|
|
}
|
|
};
|
|
|
|
#if !defined(__APPLE__)
|
|
|
|
static bool TryEmulateShaNi(Context& context) {
|
|
const auto* rip = reinterpret_cast<const uint8_t*>(context.Rip());
|
|
ShaNiInsn insn {};
|
|
if (!DecodeShaNiInsn(rip, insn)) {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t modrm_byte = rip[insn.modrm_offset];
|
|
const uint8_t dest_index = ShaNiRegIndex(modrm_byte, insn.rex, true);
|
|
auto* dest_xmm = context.Xmm(dest_index);
|
|
auto* xmm0 = context.Xmm(0);
|
|
if (dest_xmm == nullptr || xmm0 == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
XmmWords dest {};
|
|
XmmWords src2 {};
|
|
XmmWords xmm0_words {};
|
|
std::memcpy(&dest, dest_xmm, sizeof(dest));
|
|
std::memcpy(&xmm0_words, xmm0, sizeof(xmm0_words));
|
|
|
|
if (ShaNiModrmIsRegister(modrm_byte)) {
|
|
const uint8_t src_index = ShaNiRegIndex(modrm_byte, insn.rex, false);
|
|
auto* src_xmm = context.Xmm(src_index);
|
|
if (src_xmm == nullptr) {
|
|
return false;
|
|
}
|
|
std::memcpy(&src2, src_xmm, sizeof(src2));
|
|
} else {
|
|
uint64_t gpr[16] {};
|
|
const void* source = nullptr;
|
|
context.LoadGprs(gpr);
|
|
if (!ResolveShaNiMemoryAddress(rip, insn, gpr, source)) {
|
|
return false;
|
|
}
|
|
std::memcpy(&src2, source, sizeof(src2));
|
|
}
|
|
|
|
if (!ExecuteShaNiInsn(insn, src2, xmm0_words, dest)) {
|
|
return false;
|
|
}
|
|
|
|
std::memcpy(dest_xmm, &dest, sizeof(dest));
|
|
context.Advance(insn.length);
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
static bool TryEmulateSse4a(Context& context) {
|
|
const auto* rip = reinterpret_cast<const uint8_t*>(context.Rip());
|
|
const uint8_t prefix = rip[0];
|
|
if (prefix != 0x66 && prefix != 0xf2) {
|
|
return false;
|
|
}
|
|
|
|
size_t offset = 1;
|
|
uint8_t rex = 0;
|
|
if ((rip[offset] & 0xf0u) == 0x40u) {
|
|
rex = rip[offset++];
|
|
}
|
|
if (rip[offset] != 0x0f) {
|
|
return false;
|
|
}
|
|
const bool register_form = rip[offset + 1] == 0x79;
|
|
if (rip[offset + 1] != 0x78 && !register_form) {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t modrm = rip[offset + 2];
|
|
if ((modrm & 0xc0u) != 0xc0u) {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t reg = ((modrm >> 3u) & 0x07u) | ((rex & 0x04u) << 1u);
|
|
const uint8_t rm = (modrm & 0x07u) | ((rex & 0x01u) << 3u);
|
|
|
|
// Immediate EXTRQ encodes its destination in r/m; the two-register form uses reg.
|
|
uint8_t dest_index = reg;
|
|
if (prefix == 0x66 && !register_form) {
|
|
dest_index = rm;
|
|
}
|
|
auto* dest_xmm = context.Xmm(dest_index);
|
|
auto* src_xmm = context.Xmm(rm);
|
|
if (dest_xmm == nullptr || src_xmm == nullptr) {
|
|
return false;
|
|
}
|
|
uint64_t dest[2] {};
|
|
uint64_t source[2] {};
|
|
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_form) {
|
|
// EXTRQ controls occupy the low quadword; INSERTQ uses the upper quadword.
|
|
const uint64_t controls = source[prefix == 0x66 ? 0 : 1];
|
|
length = static_cast<uint8_t>(controls);
|
|
index = static_cast<uint8_t>(controls >> 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;
|
|
} else {
|
|
dest[0] = InsertBitField(dest[0], source[0], length, index);
|
|
}
|
|
std::memcpy(dest_xmm, dest, sizeof(dest));
|
|
context.Advance(instruction_length);
|
|
return true;
|
|
}
|
|
|
|
static bool ReadTscAux(uint32_t& auxiliary) {
|
|
uint32_t eax = 0;
|
|
uint32_t ebx = 0;
|
|
uint32_t ecx = 0;
|
|
uint32_t edx = 0;
|
|
if (!__get_cpuid(0x80000001u, &eax, &ebx, &ecx, &edx) || (edx & (1u << 27u)) == 0) {
|
|
return false;
|
|
}
|
|
asm volatile("rdtscp" : "=a"(eax), "=d"(edx), "=c"(auxiliary));
|
|
return true;
|
|
}
|
|
|
|
static void FlushCacheLine(uint64_t address, ZydisRegister segment) {
|
|
// Preserve FS/GS addressing rather than treating a segment-relative offset as a pointer.
|
|
if (segment == ZYDIS_REGISTER_FS) {
|
|
asm volatile("clflush %%fs:(%0)" : : "r"(address) : "memory");
|
|
} else if (segment == ZYDIS_REGISTER_GS) {
|
|
asm volatile("clflush %%gs:(%0)" : : "r"(address) : "memory");
|
|
} else {
|
|
_mm_clflush(reinterpret_cast<const void*>(address));
|
|
}
|
|
}
|
|
|
|
static bool TryEmulateCpuExtensions(Context& context) {
|
|
ZydisDecoder decoder {};
|
|
ZydisDecodedInstruction instruction {};
|
|
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT] {};
|
|
if (!ZYAN_SUCCESS(
|
|
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64)) ||
|
|
!ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, reinterpret_cast<const void*>(context.Rip()),
|
|
ZYDIS_MAX_INSTRUCTION_LENGTH, &instruction,
|
|
operands))) {
|
|
return false;
|
|
}
|
|
if (instruction.mnemonic == ZYDIS_MNEMONIC_RDPID &&
|
|
operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER) {
|
|
const auto reg_class = ZydisRegisterGetClass(operands[0].reg.value);
|
|
const auto index = ZydisRegisterGetId(operands[0].reg.value);
|
|
uint32_t auxiliary = 0;
|
|
if ((reg_class != ZYDIS_REGCLASS_GPR32 && reg_class != ZYDIS_REGCLASS_GPR64) || index < 0 ||
|
|
index >= 16 || !ReadTscAux(auxiliary)) {
|
|
return false;
|
|
}
|
|
// RDPID and guest RDTSCP observe the same host TSC_AUX; only RDPID's destination changes.
|
|
context.Gpr(static_cast<uint8_t>(index)) = auxiliary;
|
|
} else if (instruction.mnemonic == ZYDIS_MNEMONIC_CLWB &&
|
|
operands[0].type == ZYDIS_OPERAND_TYPE_MEMORY) {
|
|
ZydisRegisterContext registers {};
|
|
for (uint8_t i = 0; i < 16; ++i) {
|
|
const auto value = static_cast<uint64_t>(context.Gpr(i));
|
|
registers.values[ZYDIS_REGISTER_RAX + i] = value;
|
|
registers.values[ZYDIS_REGISTER_EAX + i] = static_cast<uint32_t>(value);
|
|
}
|
|
uint64_t address = 0;
|
|
if (!ZYAN_SUCCESS(ZydisCalcAbsoluteAddressEx(&instruction, &operands[0], context.Rip(),
|
|
®isters, &address))) {
|
|
return false;
|
|
}
|
|
FlushCacheLine(address, operands[0].mem.segment);
|
|
} else {
|
|
return false;
|
|
}
|
|
context.Advance(instruction.length);
|
|
return true;
|
|
}
|
|
|
|
static bool TryEmulateMonitorxMwaitx(Context& context) {
|
|
const auto* rip = reinterpret_cast<const uint8_t*>(context.Rip());
|
|
if (rip[0] != 0x0f || rip[1] != 0x01 || (rip[2] != 0xfa && rip[2] != 0xfb)) {
|
|
return false;
|
|
}
|
|
|
|
// Approximate AMD MONITORX/MWAITX as no-op/yield.
|
|
if (rip[2] == 0xfb) {
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
SwitchToThread();
|
|
#else
|
|
::sched_yield();
|
|
#endif
|
|
}
|
|
context.Advance(3);
|
|
return true;
|
|
}
|
|
|
|
static uint32_t ReciprocalSquareRoot(uint32_t bits) {
|
|
const uint32_t magnitude = bits & 0x7fffffffu;
|
|
const uint32_t exponent = magnitude & 0x7f800000u;
|
|
if (exponent == 0) {
|
|
// RSQRT treats denormals as signed zero regardless of MXCSR.DAZ.
|
|
return (bits & 0x80000000u) | 0x7f800000u;
|
|
}
|
|
if (magnitude > 0x7f800000u) {
|
|
return bits | 0x00400000u; // Quiet NaNs without raising an exception.
|
|
}
|
|
if ((bits & 0x80000000u) != 0) {
|
|
return 0xffc00000u;
|
|
}
|
|
if (magnitude == 0x7f800000u) {
|
|
return 0;
|
|
}
|
|
|
|
// A deterministic accurate estimate meets the instruction's relative-error
|
|
// bound without relying on the host vendor's approximation table.
|
|
const __m128d input = _mm_set_sd(static_cast<double>(std::bit_cast<float>(bits)));
|
|
const __m128d result = _mm_div_sd(_mm_set_sd(1.0), _mm_sqrt_sd(input, input));
|
|
return std::bit_cast<uint32_t>(_mm_cvtss_f32(_mm_cvtsd_ss(_mm_setzero_ps(), result)));
|
|
}
|
|
|
|
static bool TryEmulateReciprocalSquareRoot(Context& context) {
|
|
const auto* rip = reinterpret_cast<const uint8_t*>(context.Rip());
|
|
size_t prefix_size = 0;
|
|
uint8_t dest_extension = 0;
|
|
uint8_t src_extension = 0;
|
|
if (rip[0] == 0xc5 && (rip[1] & 0x7fu) == 0x70u) {
|
|
prefix_size = 2;
|
|
dest_extension = (~rip[1] & 0x80u) >> 4u;
|
|
} else if (rip[0] == 0xc4 && (rip[1] & 0x1fu) == 1 && (rip[2] & 0x7fu) == 0x70u) {
|
|
prefix_size = 3;
|
|
dest_extension = (~rip[1] & 0x80u) >> 4u;
|
|
src_extension = (~rip[1] & 0x20u) >> 2u;
|
|
} else {
|
|
return false;
|
|
}
|
|
if (rip[prefix_size] != 0x52 || (rip[prefix_size + 1] & 0xc0u) != 0xc0u) {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t modrm = rip[prefix_size + 1];
|
|
const uint8_t dest = ((modrm >> 3u) & 7u) | dest_extension;
|
|
const uint8_t source = (modrm & 7u) | src_extension;
|
|
auto* dest_xmm = context.Xmm(dest);
|
|
auto* src_xmm = context.Xmm(source);
|
|
if (dest_xmm == nullptr || src_xmm == nullptr) {
|
|
return false;
|
|
}
|
|
XmmWords result {};
|
|
std::memcpy(&result, src_xmm, sizeof(result));
|
|
// RSQRT ignores the rounding mode and never changes guest exception flags.
|
|
// Mask host exceptions while calculating, then restore the handler's state.
|
|
const uint32_t mxcsr = _mm_getcsr();
|
|
_mm_setcsr(0x1f80);
|
|
for (auto& word: result.w) {
|
|
word = ReciprocalSquareRoot(word);
|
|
}
|
|
_mm_setcsr(mxcsr);
|
|
std::memcpy(dest_xmm, &result, sizeof(result));
|
|
context.ClearUpperYmm(dest);
|
|
context.Advance(prefix_size + 2);
|
|
return true;
|
|
}
|
|
|
|
bool TryEmulate(void* native_context) {
|
|
if (native_context == nullptr) {
|
|
return false;
|
|
}
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
Context context {static_cast<PCONTEXT>(native_context)};
|
|
#elif defined(__APPLE__)
|
|
auto* saved_context = static_cast<ucontext_t*>(native_context);
|
|
if (saved_context->uc_mcontext == nullptr) {
|
|
return false;
|
|
}
|
|
Context context {saved_context};
|
|
#else
|
|
Context context {static_cast<ucontext_t*>(native_context)};
|
|
#endif
|
|
return TryEmulateReciprocalSquareRoot(context) || TryEmulateMonitorxMwaitx(context) ||
|
|
TryEmulateSse4a(context) ||
|
|
#if !defined(__APPLE__)
|
|
TryEmulateShaNi(context) ||
|
|
#endif
|
|
TryEmulateCpuExtensions(context);
|
|
}
|
|
|
|
} // namespace Loader::X64InstructionEmulator
|