diff --git a/FEXCore/Source/Interface/Core/JIT/JIT.cpp b/FEXCore/Source/Interface/Core/JIT/JIT.cpp index 16e81ae06..51d52d864 100644 --- a/FEXCore/Source/Interface/Core/JIT/JIT.cpp +++ b/FEXCore/Source/Interface/Core/JIT/JIT.cpp @@ -824,6 +824,7 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size this->DebugData = DebugData; this->IR = IR; RequiresFarARM64Jumps = false; + SSANodeMultiplier = 24; // Prepare restart via long jump in case branch encoding fails. // This uses UncheckedLongJump since we don't implement std::longjmp in WoA setups @@ -832,7 +833,12 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size // Nothing break; case RestartOptions::Control::EnableFarARM64Jumps: RequiresFarARM64Jumps = true; break; - default: ERROR_AND_DIE_FMT("Unhandled Arm64 restart condition!"); + case RestartOptions::Control::NeedsLargerJITSpace: + // Get rid of the claimed buffer immediately, we can't fit in it at all. + TempAllocator.UnclaimBuffer(); + SSANodeMultiplier *= 2; + break; + default: LOGMAN_MSG_A_FMT("Unhandled Arm64 restart condition!"); } uint32_t SSACount = IR->GetSSACount(); @@ -845,8 +851,7 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size // Fairly excessive buffer range to make sure we don't overflow // One page baseline, plus SSANodeMultipler bytes, plus another page for guard page. - // TODO: Change '24' to SSANodeMultipler when supported. - const uint32_t BufferRange = AlignUp(FEXCore::Utils::FEX_PAGE_SIZE * 2 + SSACount * 24, FEXCore::Utils::FEX_PAGE_SIZE); + const uint32_t BufferRange = AlignUp(FEXCore::Utils::FEX_PAGE_SIZE * 2 + SSACount * SSANodeMultiplier, FEXCore::Utils::FEX_PAGE_SIZE); const uint32_t UsableBufferRange = BufferRange - FEXCore::Utils::FEX_PAGE_SIZE; // JIT output is first written to a temporary buffer and later relocated to the CodeBuffer. @@ -854,6 +859,9 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size auto TempCodeBuffer = TempAllocator.ReownOrClaimBuffer(BufferRange); SetBuffer(TempCodeBuffer, UsableBufferRange); + ThreadState->JITGuardPage = reinterpret_cast(TempCodeBuffer) + UsableBufferRange; + ThreadState->JITGuardOverflowArgument = FEXCore::ToUnderlying(RestartOptions::Control::NeedsLargerJITSpace); + CodeData.BlockBegin = GetCursorAddress(); // Put the code header at the start of the data block. @@ -1063,7 +1071,6 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size // Query size of generated code const auto TempSize = GetCursorOffset(); - LOGMAN_THROW_A_FMT(TempSize <= BufferRange, "Exceeded bounds of temporary buffer ({:#x} vs {:#x})", TempSize, BufferRange); // Bring CodeBuffer up to date { diff --git a/FEXCore/Source/Interface/Core/JIT/JITClass.h b/FEXCore/Source/Interface/Core/JIT/JITClass.h index d40d56b8a..d931d3b9c 100644 --- a/FEXCore/Source/Interface/Core/JIT/JITClass.h +++ b/FEXCore/Source/Interface/Core/JIT/JITClass.h @@ -71,6 +71,7 @@ private: enum class Control : uint64_t { Incoming = 0, EnableFarARM64Jumps = 1, + NeedsLargerJITSpace = 2, }; }; @@ -78,6 +79,8 @@ private: // In the rare case when those assumptions are broken, FEX needs to safely restart the JIT. RestartOptions RestartControl {}; bool RequiresFarARM64Jumps {}; + // Default to 6 instructions per SSA node. + uint32_t SSANodeMultiplier {24}; ARMEmitter::BiDirectionalLabel* PendingTargetLabel {}; ARMEmitter::BiDirectionalLabel* PendingCallReturnTargetLabel {}; diff --git a/FEXCore/include/FEXCore/Debug/InternalThreadState.h b/FEXCore/include/FEXCore/Debug/InternalThreadState.h index f582b6645..a32636933 100644 --- a/FEXCore/include/FEXCore/Debug/InternalThreadState.h +++ b/FEXCore/include/FEXCore/Debug/InternalThreadState.h @@ -119,6 +119,8 @@ struct alignas(FEXCore::Utils::FEX_PAGE_SIZE) InternalThreadState : public FEXCo // The low address of the call-ret stack allocation (not including guard pages) void* CallRetStackBase {}; + uintptr_t JITGuardPage {}; + uint64_t JITGuardOverflowArgument {}; FEXCore::UncheckedLongJump::JumpBuf RestartJump; // BaseFrameState should always be at the end, directly before the interrupt fault page diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp index 9282ad481..71c6dd4f8 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp @@ -581,6 +581,16 @@ bool SignalDelegator::HandleFrontendSIGSEGV(FEXCore::Core::InternalThreadState* ERROR_AND_DIE_FMT("Received invalid data to syscall. Crashing now!"); } +#ifdef _M_ARM_64 + if (Signal == SIGSEGV && SigInfo.si_code == SEGV_ACCERR && SigInfo.si_addr >= reinterpret_cast(Thread->JITGuardPage) && + SigInfo.si_addr < reinterpret_cast(Thread->JITGuardPage + FEXCore::Utils::FEX_PAGE_SIZE)) { + FEXCore::UncheckedLongJump::ManuallyLoadJumpBuf(Thread->RestartJump, Thread->JITGuardOverflowArgument, + ArchHelpers::Context::GetArmGPRs(UContext), ArchHelpers::Context::GetArmFPRs(UContext), + ArchHelpers::Context::GetArmPc(UContext)); + return true; + } +#endif + return false; } diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 8b072b99f..723465800 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -28,6 +28,7 @@ $end_info$ #include #include "Common/CallRetStack.h" +#include "Common/JITGuardPage.h" #include "Common/Config.h" #include "Common/Exception.h" #include "Common/InvalidationTracker.h" @@ -736,6 +737,11 @@ bool ResetToConsistentStateImpl(const ThreadCPUArea CPUArea, EXCEPTION_RECORD* E return true; } + if (FEX::Windows::JITGuardPage::HandleJITGuardPage(Thread, reinterpret_cast(FaultAddress), NativeContext->X, + reinterpret_cast<__uint128_t*>(NativeContext->V), &NativeContext->Pc)) { + return true; + } + std::scoped_lock Lock(ThreadCreationMutex); if (InvalidationTracker && InvalidationTracker->HandleRWXAccessViolation(Thread, NativeContext->Pc, FaultAddress)) { FEXCORE_PROFILE_INSTANT_INCREMENT(Thread, AccumulatedSMCCount, 1); diff --git a/Source/Windows/Common/JITGuardPage.h b/Source/Windows/Common/JITGuardPage.h new file mode 100644 index 000000000..5e51fb113 --- /dev/null +++ b/Source/Windows/Common/JITGuardPage.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +#pragma once +#include +#include + +namespace FEX::Windows::JITGuardPage { +static inline bool HandleJITGuardPage(FEXCore::Core::InternalThreadState* Thread, void* Address, uint64_t* GPRs, __uint128_t* FPRs, uint64_t* PC) { + if (Address >= reinterpret_cast(Thread->JITGuardPage) && + Address < reinterpret_cast(Thread->JITGuardPage + FEXCore::Utils::FEX_PAGE_SIZE)) { + FEXCore::UncheckedLongJump::ManuallyLoadJumpBuf(Thread->RestartJump, Thread->JITGuardOverflowArgument, GPRs, FPRs, PC); + return true; + } + + return false; +} + +} // namespace FEX::Windows::JITGuardPage diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index 0855a8e51..4d539ca7e 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -29,6 +29,7 @@ $end_info$ #include #include "Common/CallRetStack.h" +#include "Common/JITGuardPage.h" #include "Common/Config.h" #include "Common/Exception.h" #include "Common/TSOHandlerConfig.h" @@ -884,6 +885,11 @@ bool BTCpuResetToConsistentStateImpl(EXCEPTION_POINTERS* Ptrs) { return true; } + if (FEX::Windows::JITGuardPage::HandleJITGuardPage(Thread, reinterpret_cast(FaultAddress), Context->X, + reinterpret_cast<__uint128_t*>(Context->V), &Context->Pc)) { + return true; + } + if (Thread) { std::scoped_lock Lock(ThreadCreationMutex); FEXCORE_PROFILE_INSTANT_INCREMENT(Thread, AccumulatedSMCCount, 1); diff --git a/unittests/ASM/Disabled_Tests_Simulator b/unittests/ASM/Disabled_Tests_Simulator index d735f321e..9d2c29162 100644 --- a/unittests/ASM/Disabled_Tests_Simulator +++ b/unittests/ASM/Disabled_Tests_Simulator @@ -116,3 +116,6 @@ Test_SSE4a/extrq_imm.asm Test_SSE4a/insertq_imm.asm Test_SSE4a/extrq_variable.asm Test_SSE4a/insertq_variable.asm + +# Simulator can't handle long jump through signal handler +Test_FEX_bugs/CodeBufferOverflow.asm diff --git a/unittests/ASM/FEX_bugs/CodeBufferOverflow.asm b/unittests/ASM/FEX_bugs/CodeBufferOverflow.asm new file mode 100644 index 000000000..ea75f799c --- /dev/null +++ b/unittests/ASM/FEX_bugs/CodeBufferOverflow.asm @@ -0,0 +1,35 @@ +%ifdef CONFIG +{ + "RegData": { + "RAX": "1" + }, + "Env": { "FEX_MAXINST" : "41010", "FEX_MULTIBLOCK": "1", "FEX_TSOENABLED": "0" } +} +%endif + +; FEX had a bug where its JIT heuristic wouldn't catch all cases, and allocations would overflow and crash. + +%macro OverflowBuffer 0 + %rep 256 + ; This instruction is absolutely abysmal under FEX. + ; Easily stresses our heuristic for block sizes + rep movsq + %endrep +%endmacro + +mov rax, 0 +cmp rax, 0 +mov rcx, 0 +mov rdi, 1 +mov rsi, 2 + +jz long_jump + +OverflowBuffer + +long_jump: +mov rax, 1 +hlt + +data: +dq 0, 0, 0, 0