Protect Windows guest red zones when emulating VRSQRTPS

This commit is contained in:
nmzik
2026-09-16 06:30:20 +02:00
parent 77680ce9d2
commit 86e5e01403
8 changed files with 700 additions and 507 deletions
+1 -1
View File
@@ -240,7 +240,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->checkBox_vulkan_validation->setChecked(info.vulkan_validation_enabled);
m_ui->checkBox_renderdoc_capture->setChecked(info.renderdoc_enabled);
m_ui->checkBox_amd_cpu->setChecked(info.amd_cpu_enabled);
#if !defined(__linux__)
#if defined(__APPLE__)
m_ui->checkBox_amd_cpu->setVisible(false);
#endif
#if defined(_WIN32)
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -14,7 +14,7 @@ namespace Loader {
struct RedZonePatchResult {
uint64_t function_count = 0;
uint64_t instruction_count = 0;
uint64_t red_zone_function_count = 0;
uint64_t red_zone_function_count = 0;
uint64_t memory_instruction_count = 0;
uint64_t short_memory_instruction_count = 0;
uint64_t patched_memory_instruction_count = 0;
@@ -22,14 +22,17 @@ struct RedZonePatchResult {
uint64_t control_flow_memory_instruction_count = 0;
uint64_t unrelocatable_memory_instruction_count = 0;
uint64_t indirect_red_zone_function_count = 0;
uint64_t reciprocal_sqrt_instruction_count = 0;
};
void RegisterRedZonePatchModule(void* module_ptr, uint64_t module_size, void* trampoline_area_ptr,
uint64_t trampoline_area_size);
void UnregisterRedZonePatchModule(void* module_ptr);
RedZonePatchResult PatchRedZoneMemoryInstructions(uint64_t segment_addr, uint64_t segment_size,
std::span<const uintptr_t> function_starts);
// Analyze and protect fault sites before replacing reciprocal roots with traps.
RedZonePatchResult PatchGuestInstructions(uint64_t segment_addr, uint64_t segment_size,
std::span<const uintptr_t> function_starts,
bool protect_memory, bool emulate_rsqrt);
bool DecodeEhFrameFunctionStarts(uint64_t eh_frame_header_addr, uint64_t eh_frame_header_size,
std::vector<uintptr_t>* function_starts);
+25 -24
View File
@@ -2014,9 +2014,11 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
uint64_t tls_handler_size = is_shared ? 0 : Jit::SafeCall::GetSize();
EXIT_IF(tls_handler_size > UINT64_MAX - program->base_size_aligned);
program->mapped_size = program->base_size_aligned + tls_handler_size;
const bool emulate_rsqrt = Config::AmdCpuEnabled();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
const bool use_red_zone_protection = Config::RedZoneProtectionEnabled();
const bool protect_memory_faults = Config::RedZoneProtectionEnabled();
const bool use_red_zone_protection = protect_memory_faults || emulate_rsqrt;
constexpr uint64_t RED_ZONE_TRAMPOLINE_SIZE = 8u * 1024u * 1024u;
if (use_red_zone_protection) {
EXIT_IF(RED_ZONE_TRAMPOLINE_SIZE > UINT64_MAX - program->mapped_size);
@@ -2062,9 +2064,8 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
EXIT("Failed to install the required vectored exception handler\n");
}
// program->elf->SetBaseVAddr(program->base_vaddr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
std::vector<std::pair<uint64_t, uint64_t>> executable_segments;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
uint64_t eh_frame_header_addr = 0;
uint64_t eh_frame_header_size = 0;
#endif
@@ -2089,22 +2090,8 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
mode == Common::VirtualMemory::Mode::NoAccess);
if (Common::VirtualMemory::IsExecute(mode)) {
if (Config::AmdCpuEnabled()) {
const auto reciprocal_sqrt_count =
X64InstructionEmulator::PatchReciprocalSquareRoots(segment_addr,
segment_file_size);
if (reciprocal_sqrt_count != 0) {
LOGF("Guest VRSQRTPS emulation: %s, instructions=%" PRIu64 "\n",
Common::PathToString(program->file_name.filename()).c_str(),
reciprocal_sqrt_count);
}
}
PatchProgram(program, segment_addr, segment_memory_size);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (use_red_zone_protection) {
executable_segments.emplace_back(segment_addr, segment_file_size);
}
#endif
executable_segments.emplace_back(segment_addr, segment_file_size);
}
if (!skip_protect) {
@@ -2149,16 +2136,22 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
std::vector<uintptr_t> function_starts;
if (use_red_zone_protection) {
std::vector<uintptr_t> function_starts;
if (!DecodeEhFrameFunctionStarts(eh_frame_header_addr, eh_frame_header_size,
&function_starts)) {
LOGF("Windows guest red-zone patching could not decode function boundaries for %s\n",
Common::PathToString(program->file_name).c_str());
}
for (const auto& [segment_addr, segment_size]: executable_segments) {
}
#endif
for (const auto& [segment_addr, segment_size]: executable_segments) {
uint64_t reciprocal_sqrt_count = 0;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (use_red_zone_protection) {
const auto result =
PatchRedZoneMemoryInstructions(segment_addr, segment_size, function_starts);
PatchGuestInstructions(segment_addr, segment_size, function_starts,
protect_memory_faults, emulate_rsqrt);
LOGF("Windows guest red-zone patching: %s, functions=%" PRIu64 ", red_zone=%" PRIu64
", memory=%" PRIu64 ", patched=%" PRIu64 ", short=%" PRIu64 ", stack=%" PRIu64
", control=%" PRIu64 ", unrelocatable=%" PRIu64 "\n",
@@ -2168,12 +2161,20 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
result.stack_dependent_memory_instruction_count,
result.control_flow_memory_instruction_count,
result.unrelocatable_memory_instruction_count);
reciprocal_sqrt_count = result.reciprocal_sqrt_instruction_count;
}
#else
if (emulate_rsqrt) {
reciprocal_sqrt_count =
X64InstructionEmulator::PatchReciprocalSquareRoots(segment_addr, segment_size);
Common::VirtualMemory::FlushInstructionCache(segment_addr, segment_size);
}
Common::VirtualMemory::FlushInstructionCache(program->red_zone_trampoline_vaddr,
program->red_zone_trampoline_size);
}
#endif
if (reciprocal_sqrt_count != 0) {
LOGF("Guest VRSQRTPS emulation: %s, instructions=%" PRIu64 "\n",
Common::PathToString(program->file_name.filename()).c_str(), reciprocal_sqrt_count);
}
}
if (!is_shared) {
SetupTlsHandler(program);
+28 -11
View File
@@ -5,7 +5,7 @@
#include <Zydis/Zydis.h>
#include <bit>
#include <cstring>
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
#if !defined(__APPLE__)
#include <emmintrin.h>
#include <xmmintrin.h>
#endif
@@ -424,6 +424,21 @@ struct Context {
native->R12, native->R13, native->R14, native->R15};
std::memcpy(gpr, registers, sizeof(gpr));
}
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] = {};
}
}
#else
ucontext_t* native;
@@ -588,8 +603,6 @@ static bool TryEmulateMonitorxMwaitx(Context& context) {
return true;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
static uint32_t ReciprocalSquareRoot(uint32_t bits) {
const uint32_t magnitude = bits & 0x7fffffffu;
const uint32_t exponent = magnitude & 0x7f800000u;
@@ -658,11 +671,18 @@ static bool TryEmulateReciprocalSquareRoot(Context& context) {
}
#endif
#endif
bool IsReciprocalSquareRoot(const ZydisDecodedInstruction& instruction,
const ZydisDecodedOperand* operands) {
return instruction.mnemonic == ZYDIS_MNEMONIC_VRSQRTPS &&
instruction.encoding == ZYDIS_INSTRUCTION_ENCODING_VEX &&
instruction.raw.vex.offset == 0 && operands[0].size == 128 &&
operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER;
}
uint64_t PatchReciprocalSquareRoots(uint64_t address, uint64_t size) {
uint64_t patched = 0;
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
#if !defined(__APPLE__)
ZydisDecoder decoder {};
if (!ZYAN_SUCCESS(
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64))) {
@@ -677,12 +697,9 @@ uint64_t PatchReciprocalSquareRoots(uint64_t address, uint64_t size) {
++offset;
continue;
}
if (instruction.mnemonic == ZYDIS_MNEMONIC_VRSQRTPS &&
instruction.encoding == ZYDIS_INSTRUCTION_ENCODING_VEX &&
instruction.raw.vex.offset == 0 && operands[0].size == 128 &&
operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER) {
if (IsReciprocalSquareRoot(instruction, operands)) {
// vvvv is reserved (must be 1111b). Clear one bit to route this
// otherwise intact instruction through the existing SIGILL emulator.
// otherwise intact instruction through the illegal-instruction emulator.
code[instruction.raw.vex.size - 1] &= ~0x08u;
++patched;
}
@@ -704,10 +721,10 @@ bool TryEmulate(void* native_context) {
Context context {static_cast<PCONTEXT>(native_context)};
#else
Context context {static_cast<ucontext_t*>(native_context)};
#endif
if (TryEmulateReciprocalSquareRoot(context)) {
return true;
}
#endif
return TryEmulateMonitorxMwaitx(context) || TryEmulateSse4a(context) ||
TryEmulateShaNi(context);
#else
+3
View File
@@ -1,10 +1,13 @@
#ifndef KYTY_LOADER_X64_INSTRUCTION_EMULATOR_H_
#define KYTY_LOADER_X64_INSTRUCTION_EMULATOR_H_
#include <Zydis/DecoderTypes.h>
#include <cstdint>
namespace Loader::X64InstructionEmulator {
[[nodiscard]] bool IsReciprocalSquareRoot(const ZydisDecodedInstruction& instruction,
const ZydisDecodedOperand* operands);
uint64_t PatchReciprocalSquareRoots(uint64_t address, uint64_t size);
[[nodiscard]] bool TryEmulate(void* native_context);
+1 -1
View File
@@ -56,7 +56,7 @@ static void PrintUsage() {
" --gpu <index> Vulkan physical device index. Default: auto.\n");
::printf(" --fullscreen Run in borderless desktop fullscreen.\n");
::printf(" --vr Enable the virtual VR headset.\n");
::printf(" --amd-cpu Apply AMD CPU instruction patches (Linux).\n");
::printf(" --amd-cpu Apply AMD CPU instruction patches.\n");
::printf(" --vblank-frequency <num> Virtual vblank frequency. Default: 60.\n");
::printf(" --console-language <0-29> Console language. Default: 1 (English US).\n");
::printf(" --vulkan-validation <true|false> Enable Vulkan validation.\n");