loader: add INSERTQ RDPID and CLWB fallbacks

Co-authored-by: nomolao2-cell <nomolao2@gmail.com>
This commit is contained in:
nmzik
2026-10-03 20:13:58 +04:00
parent a4d26cf81a
commit 19a9464b7d
6 changed files with 667 additions and 107 deletions
+104 -35
View File
@@ -132,7 +132,14 @@ struct DecodedFunction {
bool requires_conservative_red_zone_tracking {};
};
enum class InstructionReplacement { None, ReciprocalSquareRoot, ExtractQ };
enum class InstructionReplacement {
None,
ReciprocalSquareRoot,
ExtractQ,
InsertQ,
ReadProcessorId,
CacheLineWriteBack
};
struct InstructionRewrite {
bool protect_red_zone {};
@@ -143,8 +150,19 @@ struct InstructionRewrite {
InstructionPatchCounts& ReplacementCounts(GuestInstructionPatchResult& result,
InstructionReplacement replacement) {
ASSERT(replacement != InstructionReplacement::None);
return replacement == InstructionReplacement::ReciprocalSquareRoot ? result.reciprocal_sqrt
: result.extrq;
switch (replacement) {
case InstructionReplacement::ReciprocalSquareRoot: return result.reciprocal_sqrt;
case InstructionReplacement::ExtractQ: return result.extrq;
case InstructionReplacement::InsertQ: return result.insertq;
case InstructionReplacement::ReadProcessorId: return result.rdpid;
default: return result.clwb;
}
}
bool UsesInstructionTrap(InstructionReplacement replacement, bool trap_replacements) {
return replacement != InstructionReplacement::None &&
(trap_replacements || replacement == InstructionReplacement::InsertQ ||
replacement == InstructionReplacement::ReadProcessorId);
}
bool IsStackPointerRegister(ZydisRegister reg) {
@@ -216,10 +234,11 @@ DecodedCodeInstruction DecodeCodeInstruction(uintptr_t address, uintptr_t end) {
if (decoded.changes_stack_pointer) {
const auto& operands = decoded.operands;
if (decoded.instruction.mnemonic == ZYDIS_MNEMONIC_MOV &&
operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
if (operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
IsStackPointerRegister(operands[0].reg.value) &&
operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY) {
(decoded.instruction.mnemonic == ZYDIS_MNEMONIC_RDPID ||
(decoded.instruction.mnemonic == ZYDIS_MNEMONIC_MOV &&
operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY))) {
decoded.replaces_stack_pointer = true;
} else if ((decoded.instruction.mnemonic == ZYDIS_MNEMONIC_ADD ||
decoded.instruction.mnemonic == ZYDIS_MNEMONIC_SUB) &&
@@ -653,13 +672,17 @@ void AnalyzeRedZoneLiveness(DecodedFunction& function) {
}
bool EncodeRelocatedInstruction(const DecodedCodeInstruction& decoded,
Xbyak::CodeGenerator& generator) {
Xbyak::CodeGenerator& generator, s64 stack_adjustment = 0,
ZydisMnemonic replacement = ZYDIS_MNEMONIC_INVALID) {
ZydisEncoderRequest request;
if (!ZYAN_SUCCESS(ZydisEncoderDecodedInstructionToEncoderRequest(
&decoded.instruction, decoded.operands.data(),
decoded.instruction.operand_count_visible, &request))) {
return false;
}
if (replacement != ZYDIS_MNEMONIC_INVALID) {
request.mnemonic = replacement;
}
for (u8 index = 0; index < decoded.instruction.operand_count_visible; ++index) {
const auto& operand = decoded.operands[index];
@@ -679,6 +702,9 @@ bool EncodeRelocatedInstruction(const DecodedCodeInstruction& decoded,
return false;
}
request.operands[index].mem.displacement = static_cast<ZyanI64>(absolute_address);
} else if (operand.type == ZYDIS_OPERAND_TYPE_MEMORY &&
IsStackPointerRegister(operand.mem.base)) {
request.operands[index].mem.displacement += stack_adjustment;
}
}
@@ -823,8 +849,9 @@ void GenerateReciprocalSquareRoot(const DecodedCodeInstruction& decoded,
generator.L(done);
}
bool IsSupportedExtractQ(const DecodedCodeInstruction& decoded) {
if (decoded.instruction.mnemonic != ZYDIS_MNEMONIC_EXTRQ) {
bool IsSupportedBitFieldInstruction(const DecodedCodeInstruction& decoded) {
const bool insert = decoded.instruction.mnemonic == ZYDIS_MNEMONIC_INSERTQ;
if (!insert && decoded.instruction.mnemonic != ZYDIS_MNEMONIC_EXTRQ) {
return false;
}
const auto is_xmm = [](const ZydisDecodedOperand& operand) {
@@ -832,18 +859,20 @@ bool IsSupportedExtractQ(const DecodedCodeInstruction& decoded) {
operand.reg.value >= ZYDIS_REGISTER_XMM0 &&
operand.reg.value <= ZYDIS_REGISTER_XMM15;
};
const bool immediate = decoded.instruction.operand_count_visible == 3 &&
decoded.operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
decoded.operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
if (!is_xmm(decoded.operands[0]) ||
const size_t immediate_index = insert ? 2 : 1;
const bool immediate =
decoded.instruction.operand_count_visible == immediate_index + 2 &&
decoded.operands[immediate_index].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
decoded.operands[immediate_index + 1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
if (!is_xmm(decoded.operands[0]) || (insert && !is_xmm(decoded.operands[1])) ||
(!immediate &&
!(decoded.instruction.operand_count_visible == 2 && is_xmm(decoded.operands[1])))) {
return false;
}
// Match the trap handler's encoding support: 66 [REX] 0F 78/79 ModRM [imm8 imm8].
// Match the trap handler's encoding support: 66/F2 [REX] 0F 78/79 ModRM [imm8 imm8].
const auto* code = reinterpret_cast<const u8*>(decoded.address);
const size_t opcode = (code[1] & 0xf0u) == 0x40u ? 2 : 1;
return code[0] == 0x66 && code[opcode] == 0x0f &&
return code[0] == (insert ? 0xf2 : 0x66) && code[opcode] == 0x0f &&
code[opcode + 1] == (immediate ? 0x78 : 0x79) &&
decoded.instruction.length == opcode + (immediate ? 5 : 3);
}
@@ -890,8 +919,8 @@ void GenerateExtractQ(const DecodedCodeInstruction& decoded, Xbyak::CodeGenerato
void CollectAmdInstructions(const DecodedFunction& function,
std::map<uintptr_t, InstructionRewrite>& rewrite_sites,
GuestInstructionPatchResult& result) {
static const bool host_lacks_sse4a = !Xbyak::util::Cpu().has(Xbyak::util::Cpu::tSSE4a);
GuestInstructionPatchResult& result,
GuestInstructionHostFeatures host_features) {
for (const auto& [address, decoded]: function.instructions) {
const auto& instruction = decoded.instruction;
InstructionReplacement replacement {};
@@ -900,8 +929,14 @@ void CollectAmdInstructions(const DecodedFunction& function,
instruction.raw.vex.offset == 0 && decoded.operands[0].size == 128 &&
decoded.operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER) {
replacement = InstructionReplacement::ReciprocalSquareRoot;
} else if (host_lacks_sse4a && IsSupportedExtractQ(decoded)) {
replacement = InstructionReplacement::ExtractQ;
} else if (!host_features.sse4a && IsSupportedBitFieldInstruction(decoded)) {
replacement = instruction.mnemonic == ZYDIS_MNEMONIC_EXTRQ
? InstructionReplacement::ExtractQ
: InstructionReplacement::InsertQ;
} else if (!host_features.rdpid && instruction.mnemonic == ZYDIS_MNEMONIC_RDPID) {
replacement = InstructionReplacement::ReadProcessorId;
} else if (!host_features.clwb && instruction.mnemonic == ZYDIS_MNEMONIC_CLWB) {
replacement = InstructionReplacement::CacheLineWriteBack;
} else {
continue;
}
@@ -916,16 +951,21 @@ void MarkInstructionTrap(u8* code, const ZydisDecodedInstruction& instruction,
// Clear a reserved VEX.vvvv bit, retaining both register operands for the handler.
code[instruction.raw.vex.size - 1] &= ~0x08u;
}
// EXTRQ already raises #UD on hosts without SSE4a; keep its original encoding.
// Other replacements already raise #UD on hosts without their CPU feature.
}
void GenerateInstructionTrap(const DecodedCodeInstruction& decoded,
InstructionReplacement replacement, Xbyak::CodeGenerator& generator) {
bool GenerateInstructionTrap(const DecodedCodeInstruction& decoded,
InstructionReplacement replacement, Xbyak::CodeGenerator& generator,
s64 stack_adjustment) {
if (replacement == InstructionReplacement::CacheLineWriteBack) {
return EncodeRelocatedInstruction(decoded, generator, stack_adjustment);
}
std::array<u8, ZYDIS_MAX_INSTRUCTION_LENGTH> code {};
std::memcpy(code.data(), reinterpret_cast<const void*>(decoded.address),
decoded.instruction.length);
MarkInstructionTrap(code.data(), decoded.instruction, replacement);
generator.db(code.data(), decoded.instruction.length);
return true;
}
bool NeedsTrapRedZoneProtection(const DecodedCodeInstruction& decoded) {
@@ -946,11 +986,11 @@ void TrapUnrelocatedInstructions(const PatchModule& module, const DecodedFunctio
}
const auto& decoded = function.instructions.at(address);
if (NeedsTrapRedZoneProtection(decoded)) {
if (rewrite.replacement == InstructionReplacement::ExtractQ) {
// Unlike VRSQRTPS, leaving EXTRQ unchanged would still trap and corrupt the red
// zone.
EXIT("AMD CPU compatibility: cannot safely trap EXTRQ at %p (guest red zone is "
if (rewrite.replacement != InstructionReplacement::ReciprocalSquareRoot) {
// Leaving an unsupported instruction unchanged still traps with a live red zone.
EXIT("AMD CPU compatibility: cannot safely trap %s at %p (guest red zone is "
"live)\n",
ZydisMnemonicGetString(decoded.instruction.mnemonic),
reinterpret_cast<void*>(address));
}
continue;
@@ -990,23 +1030,31 @@ void RelocateGuestInstructions(PatchModule* module, const DecodedFunction& funct
const auto replacement = rewrite != rewrite_sites.end()
? rewrite->second.replacement
: InstructionReplacement::None;
const bool uses_trap = UsesInstructionTrap(replacement, trap_replacements);
const bool protected_indirect_call =
rewrite != rewrite_sites.end() && rewrite->second.protected_indirect_call;
const bool protect_red_zone =
(rewrite != rewrite_sites.end() && rewrite->second.protect_red_zone &&
!protected_indirect_call) ||
(replacement != InstructionReplacement::None && trap_replacements &&
((uses_trap || replacement == InstructionReplacement::CacheLineWriteBack) &&
NeedsTrapRedZoneProtection(*decoded));
if (protect_red_zone) {
generator.lea(rsp, ptr[rsp - GuestRedZoneSize]);
}
if (replacement != InstructionReplacement::None) {
if (trap_replacements) {
GenerateInstructionTrap(*decoded, replacement, generator);
if (uses_trap) {
encoded = GenerateInstructionTrap(*decoded, replacement, generator,
protect_red_zone ? GuestRedZoneSize : 0);
} else if (replacement == InstructionReplacement::ReciprocalSquareRoot) {
GenerateReciprocalSquareRoot(*decoded, generator);
} else {
} else if (replacement == InstructionReplacement::ExtractQ) {
GenerateExtractQ(*decoded, generator);
} else {
// CLFLUSH writes back dirty data too; invalidation is a permitted stronger
// action.
encoded = EncodeRelocatedInstruction(
*decoded, generator, protect_red_zone ? GuestRedZoneSize : 0,
ZYDIS_MNEMONIC_CLFLUSH);
}
} else if (protected_indirect_call) {
encoded = GenerateProtectedIndirectCall(*decoded, generator);
@@ -1053,7 +1101,7 @@ void RelocateGuestInstructions(PatchModule* module, const DecodedFunction& funct
}
if (rewrite->second.replacement != InstructionReplacement::None) {
auto& counts = ReplacementCounts(result, rewrite->second.replacement);
if (span.trap_replacements) {
if (UsesInstructionTrap(rewrite->second.replacement, span.trap_replacements)) {
++counts.trapped;
} else {
++counts.native;
@@ -1425,9 +1473,26 @@ void RelocateGuestInstructions(PatchModule* module, const DecodedFunction& funct
}
} // namespace
GuestInstructionHostFeatures GetGuestInstructionHostFeatures() {
static const auto features = [] {
const Xbyak::util::Cpu cpu;
uint32_t leaf[4] {};
Xbyak::util::Cpu::getCpuid(0, leaf);
bool rdpid = false;
if (leaf[0] >= 7) {
Xbyak::util::Cpu::getCpuidEx(7, 0, leaf);
rdpid = (leaf[2] & (1u << 22)) != 0;
}
return GuestInstructionHostFeatures {cpu.has(Xbyak::util::Cpu::tSSE4a), rdpid,
cpu.has(Xbyak::util::Cpu::tCLWB)};
}();
return features;
}
GuestInstructionPatchResult PatchGuestInstructions(u64 segment_addr, u64 segment_size,
std::span<const uintptr_t> function_starts,
bool protect_memory, bool emulate_amd) {
bool protect_memory, bool emulate_amd,
GuestInstructionHostFeatures host_features) {
GuestInstructionPatchResult result {};
if (!protect_memory && !emulate_amd) {
return result;
@@ -1480,7 +1545,7 @@ GuestInstructionPatchResult PatchGuestInstructions(u64 segment_addr, u64 segment
CollectRedZoneMemoryInstructions(function, rewrite_sites, result);
}
if (emulate_amd) {
CollectAmdInstructions(function, rewrite_sites, result);
CollectAmdInstructions(function, rewrite_sites, result, host_features);
}
if (!rewrite_sites.empty()) {
RelocateGuestInstructions(module, function, rewrite_sites, result);
@@ -1499,8 +1564,12 @@ GuestInstructionPatchResult PatchGuestInstructions(u64 segment_addr, u64 segment
#else
GuestInstructionPatchResult PatchGuestInstructions(u64, u64, std::span<const uintptr_t>, bool,
bool) {
GuestInstructionHostFeatures GetGuestInstructionHostFeatures() {
return {};
}
GuestInstructionPatchResult PatchGuestInstructions(u64, u64, std::span<const uintptr_t>, bool, bool,
GuestInstructionHostFeatures) {
return {};
}
+15 -3
View File
@@ -11,6 +11,14 @@
namespace Loader {
struct GuestInstructionHostFeatures {
bool sse4a = false;
bool rdpid = false;
bool clwb = false;
};
GuestInstructionHostFeatures GetGuestInstructionHostFeatures();
struct InstructionPatchCounts {
uint64_t found = 0;
uint64_t native = 0;
@@ -39,6 +47,9 @@ struct GuestInstructionPatchResult {
uint64_t indirect_red_zone_function_count = 0;
InstructionPatchCounts reciprocal_sqrt;
InstructionPatchCounts extrq;
InstructionPatchCounts insertq;
InstructionPatchCounts rdpid;
InstructionPatchCounts clwb;
};
void RegisterGuestInstructionPatchModule(void* module_ptr, uint64_t module_size,
@@ -46,9 +57,10 @@ void RegisterGuestInstructionPatchModule(void* module_ptr, uint64_t module_size,
void UnregisterGuestInstructionPatchModule(void* module_ptr);
// Apply enabled instruction fixes using native trampolines or safe trap fallbacks.
GuestInstructionPatchResult PatchGuestInstructions(uint64_t segment_addr, uint64_t segment_size,
std::span<const uintptr_t> function_starts,
bool protect_memory, bool emulate_amd);
GuestInstructionPatchResult PatchGuestInstructions(
uint64_t segment_addr, uint64_t segment_size, std::span<const uintptr_t> function_starts,
bool protect_memory, bool emulate_amd,
GuestInstructionHostFeatures host_features = GetGuestInstructionHostFeatures());
bool DecodeEhFrameFunctionStarts(uint64_t eh_frame_header_addr, uint64_t eh_frame_header_size,
std::vector<uintptr_t>* function_starts);
+19 -8
View File
@@ -1949,6 +1949,9 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
protect_memory_faults, emulate_amd);
totals.reciprocal_sqrt += result.reciprocal_sqrt;
totals.extrq += result.extrq;
totals.insertq += result.insertq;
totals.rdpid += result.rdpid;
totals.clwb += result.clwb;
if (protect_memory_faults) {
LOGF("Windows guest red-zone patching: %s, functions=%" PRIu64 ", red_zone=%" PRIu64
", memory=%" PRIu64 ", patched=%" PRIu64 ", short=%" PRIu64 ", stack=%" PRIu64
@@ -1963,18 +1966,26 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
}
}
if (emulate_amd && have_function_starts) {
const auto found = totals.reciprocal_sqrt.found + totals.extrq.found;
const auto skipped = totals.reciprocal_sqrt.Skipped() + totals.extrq.Skipped();
InstructionPatchCounts combined {};
std::string details;
for (const auto& [name, counts]: {std::pair {"VRSQRTPS", totals.reciprocal_sqrt},
{"EXTRQ", totals.extrq},
{"INSERTQ", totals.insertq},
{"RDPID", totals.rdpid},
{"CLWB", totals.clwb}}) {
combined += counts;
if (!details.empty()) details += "; ";
details += fmt::format("{}: native={}, trapped={}, skipped={}", name, counts.native,
counts.trapped, counts.Skipped());
}
const auto found = combined.found;
const auto skipped = combined.Skipped();
const char* status = found == 0 ? "no matching instructions"
: skipped == found ? "not patched"
: skipped != 0 ? "partially patched"
: "patched";
Log::WriteToConsoleAndLog(fmt::format(
"AMD CPU compatibility: {} {} (VRSQRTPS: native={}, trapped={}, skipped={}; "
"EXTRQ: native={}, trapped={}, skipped={})\n",
module_name, status, totals.reciprocal_sqrt.native, totals.reciprocal_sqrt.trapped,
totals.reciprocal_sqrt.Skipped(), totals.extrq.native, totals.extrq.trapped,
totals.extrq.Skipped()));
Log::WriteToConsoleAndLog(
fmt::format("AMD CPU compatibility: {} {} ({})\n", module_name, status, details));
}
}
+103 -23
View File
@@ -2,12 +2,12 @@
#include "common/common.h"
#include <Zydis/Zydis.h>
#include <bit>
#include <cstring>
#if !defined(__APPLE__)
#include <emmintrin.h>
#include <xmmintrin.h>
#endif
#include <cpuid.h>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep
@@ -406,12 +406,12 @@ struct Context {
void Advance(size_t length) { native->Rip += length; }
[[nodiscard]] void* Xmm(uint8_t index) const { return &native->Xmm0 + index; }
void LoadGprs(uint64_t (&gpr)[16]) const {
const uint64_t 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};
std::memcpy(gpr, registers, sizeof(gpr));
[[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 {
@@ -437,6 +437,14 @@ struct Context {
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;
@@ -476,13 +484,11 @@ struct Context {
return native->uc_mcontext.fpregs->_xmm[index].element;
}
void LoadGprs(uint64_t (&gpr)[16]) const {
[[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};
for (size_t i = 0; i < 16; ++i) {
gpr[i] = static_cast<uint64_t>(native->uc_mcontext.gregs[registers[i]]);
}
return native->uc_mcontext.gregs[registers[index]];
}
void ClearUpperYmm(uint8_t index) const {
@@ -506,6 +512,12 @@ struct Context {
}
}
#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__)
@@ -574,8 +586,8 @@ static bool TryEmulateSse4a(Context& context) {
if (rip[offset] != 0x0f) {
return false;
}
const bool register_extract = prefix == 0x66 && rip[offset + 1] == 0x79;
if (rip[offset + 1] != 0x78 && !register_extract) {
const bool register_form = rip[offset + 1] == 0x79;
if (rip[offset + 1] != 0x78 && !register_form) {
return false;
}
@@ -589,7 +601,7 @@ static bool TryEmulateSse4a(Context& context) {
// Immediate EXTRQ encodes its destination in r/m; the two-register form uses reg.
uint8_t dest_index = reg;
if (prefix == 0x66 && !register_extract) {
if (prefix == 0x66 && !register_form) {
dest_index = rm;
}
auto* dest_xmm = context.Xmm(dest_index);
@@ -598,15 +610,17 @@ static bool TryEmulateSse4a(Context& context) {
return false;
}
uint64_t dest[2] {};
uint64_t source = 0;
uint64_t source[2] {};
std::memcpy(dest, dest_xmm, sizeof(dest));
std::memcpy(&source, src_xmm, sizeof(source));
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<uint8_t>(source);
index = static_cast<uint8_t>(source >> 8u);
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];
@@ -616,13 +630,79 @@ static bool TryEmulateSse4a(Context& context) {
dest[0] = ExtractBitField(dest[0], length, index);
dest[1] = 0;
} else {
dest[0] = InsertBitField(dest[0], source, length, index);
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(),
&registers, &address))) {
return false;
}
FlushCacheLine(address, operands[0].mem.segment);
} else {
return false;
}
context.Advance(instruction.length);
return true;
}
#if !defined(__APPLE__)
static bool TryEmulateMonitorxMwaitx(Context& context) {
@@ -729,9 +809,9 @@ bool TryEmulate(void* native_context) {
#endif
#if !defined(__APPLE__)
return TryEmulateReciprocalSquareRoot(context) || TryEmulateMonitorxMwaitx(context) ||
TryEmulateSse4a(context) || TryEmulateShaNi(context);
TryEmulateSse4a(context) || TryEmulateShaNi(context) || TryEmulateCpuExtensions(context);
#else
return TryEmulateSse4a(context);
return TryEmulateSse4a(context) || TryEmulateCpuExtensions(context);
#endif
}