mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-17 19:00:39 +02:00
Windows: Handle NtReadFile calls to RX protected RWX memory
Handling this safely requires blocking all compilation for the duration of the read, as fault-based tracking doesn't work when wine's unix side itself is the one performing the write into the RWX region (we cannot catch unix faults). Fixes a startup crash in Persona 5.
This commit is contained in:
1 parent
ec13e5d503
commit
8e9f593c39
4 files changed
+93
-23
No files matched your search
@@ -881,7 +881,30 @@ void BTCpu64NotifyMemoryDirty(void* Address, SIZE_T Size) {
|
||||
InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), false);
|
||||
}
|
||||
|
||||
void BTCpu64NotifyReadFile(HANDLE Handle, void* Address, SIZE_T Size, BOOL After, NTSTATUS Status) {}
|
||||
void BTCpu64NotifyReadFile(HANDLE Handle, void* Address, SIZE_T Size, BOOL After, NTSTATUS Status) {
|
||||
auto* ThreadState = GetCPUArea().ThreadState();
|
||||
if (!InvalidationTracker || !ThreadState) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& InLockedRWXRead = GetFrontendThreadData(ThreadState)->InLockedRWXRead;
|
||||
if (!After) {
|
||||
ThreadCreationMutex.lock();
|
||||
CTX->GetCodeInvalidationMutex().lock();
|
||||
if (InvalidationTracker->BeginUntrackedWriteLocked(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size))) {
|
||||
InLockedRWXRead = true;
|
||||
} else {
|
||||
CTX->GetCodeInvalidationMutex().unlock();
|
||||
ThreadCreationMutex.unlock();
|
||||
}
|
||||
} else {
|
||||
if (InLockedRWXRead) {
|
||||
InLockedRWXRead = false;
|
||||
CTX->GetCodeInvalidationMutex().unlock();
|
||||
ThreadCreationMutex.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NTSTATUS ThreadInit() {
|
||||
std::scoped_lock Lock(ThreadCreationMutex);
|
||||
|
||||
@@ -142,27 +142,7 @@ void InvalidationTracker::InvalidateAlignedInterval(uint64_t Address, uint64_t S
|
||||
}
|
||||
|
||||
void InvalidationTracker::ReprotectRWXIntervals(uint64_t Address, uint64_t Size) {
|
||||
const auto End = Address + Size;
|
||||
std::shared_lock Lock(IntervalsLock);
|
||||
|
||||
if (SMCDetectionDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
const auto Query = RWXIntervals.Query(Address);
|
||||
if (Query.Enclosed) {
|
||||
void* TmpAddress = reinterpret_cast<void*>(Address);
|
||||
SIZE_T TmpSize = static_cast<SIZE_T>(std::min(End, Address + Query.Size) - Address);
|
||||
ULONG TmpProt;
|
||||
NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, PAGE_EXECUTE_READ, &TmpProt);
|
||||
} else if (!Query.Size) {
|
||||
// No more regions past `Address` in the interval list
|
||||
break;
|
||||
}
|
||||
|
||||
Address += Query.Size;
|
||||
} while (Address < End);
|
||||
ProtectRWXIntervalsInternal(Address, Size, false);
|
||||
}
|
||||
|
||||
bool InvalidationTracker::HandleRWXAccessViolation(FEXCore::Core::InternalThreadState* Thread, uint64_t HostPc, uint64_t FaultAddress) {
|
||||
@@ -190,6 +170,10 @@ bool InvalidationTracker::HandleRWXAccessViolation(FEXCore::Core::InternalThread
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InvalidationTracker::BeginUntrackedWriteLocked(uint64_t Address, uint64_t Size) {
|
||||
return ProtectRWXIntervalsInternal(Address, Size, true);
|
||||
}
|
||||
|
||||
FEXCore::HLE::ExecutableRangeInfo InvalidationTracker::QueryExecutableRange(uint64_t Address) {
|
||||
std::shared_lock Lock(IntervalsLock);
|
||||
const auto XResult = XIntervals.Query(Address);
|
||||
@@ -266,4 +250,41 @@ void InvalidationTracker::InvalidateIntervalInternalLocked(uint64_t Address, uin
|
||||
}
|
||||
}
|
||||
|
||||
bool InvalidationTracker::ProtectRWXIntervalsInternal(uint64_t Address, uint64_t Size, bool ForWriteLocked) {
|
||||
const auto End = Address + Size;
|
||||
std::shared_lock Lock(IntervalsLock);
|
||||
|
||||
if (SMCDetectionDisabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HitRWXInterval = false;
|
||||
do {
|
||||
const auto Query = RWXIntervals.Query(Address);
|
||||
if (Query.Enclosed) {
|
||||
if (!HitRWXInterval) {
|
||||
if (ForWriteLocked) {
|
||||
// If we are protecting as writable, then the entire range must be invalidated before any protections are
|
||||
// applied and the invalidation mutex must be locked throughout.
|
||||
// Do this lazily only when an RWX region is actually hit.
|
||||
// NOTE: This assumes CodeInvalidationMutex is locked by the caller
|
||||
InvalidateIntervalInternalLocked(Address, Size);
|
||||
}
|
||||
HitRWXInterval = true;
|
||||
}
|
||||
void* TmpAddress = reinterpret_cast<void*>(Address);
|
||||
SIZE_T TmpSize = static_cast<SIZE_T>(std::min(End, Address + Query.Size) - Address);
|
||||
ULONG TmpProt;
|
||||
NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, ForWriteLocked ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ, &TmpProt);
|
||||
} else if (!Query.Size) {
|
||||
// No more regions past `Address` in the interval list
|
||||
break;
|
||||
}
|
||||
|
||||
Address += Query.Size;
|
||||
} while (Address < End);
|
||||
|
||||
return HitRWXInterval;
|
||||
}
|
||||
|
||||
} // namespace FEX::Windows
|
||||
@@ -33,6 +33,11 @@ public:
|
||||
void InvalidateAlignedInterval(uint64_t Address, uint64_t Size, bool Free);
|
||||
void ReprotectRWXIntervals(uint64_t Address, uint64_t Size);
|
||||
bool HandleRWXAccessViolation(FEXCore::Core::InternalThreadState* Thread, uint64_t HostPC, uint64_t FaultAddress);
|
||||
|
||||
// Unprotects any RWX intervals in the input interval and invalidates code
|
||||
// NOTE: CodeInvalidationMutex must be locked when calling this, and if true is returned, kept locked until the write ends.
|
||||
bool BeginUntrackedWriteLocked(uint64_t Address, uint64_t Size);
|
||||
|
||||
FEXCore::HLE::ExecutableRangeInfo QueryExecutableRange(uint64_t Address);
|
||||
|
||||
private:
|
||||
@@ -42,6 +47,9 @@ private:
|
||||
// NOTE: This assumed CodeInvalidationMutex is locked by the caller
|
||||
void InvalidateIntervalInternalLocked(uint64_t Address, uint64_t Size);
|
||||
|
||||
// NOTE: If ForWriteLocked is true then this assumes CodeInvalidationMutex is locked by the caller,
|
||||
// and any code in the range will be invalidated before protection as RWX, otherwise protects as RX if false.
|
||||
bool ProtectRWXIntervalsInternal(uint64_t Address, uint64_t Size, bool ForWriteLocked);
|
||||
|
||||
FEXCore::IntervalList<uint64_t> XIntervals;
|
||||
FEXCore::IntervalList<uint64_t> RWXIntervals;
|
||||
|
||||
@@ -1004,7 +1004,25 @@ void BTCpuNotifyUnmapViewOfSection(void* Address, BOOL After, ULONG Status) {
|
||||
}
|
||||
}
|
||||
|
||||
void BTCpuNotifyReadFile(HANDLE Handle, void* Address, SIZE_T Size, BOOL After, NTSTATUS Status) {}
|
||||
void BTCpuNotifyReadFile(HANDLE Handle, void* Address, SIZE_T Size, BOOL After, NTSTATUS Status) {
|
||||
auto& InLockedRWXRead = GetFrontendThreadData(GetTLS().ThreadState())->InLockedRWXRead;
|
||||
if (!After) {
|
||||
ThreadCreationMutex.lock();
|
||||
CTX->GetCodeInvalidationMutex().lock();
|
||||
if (InvalidationTracker->BeginUntrackedWriteLocked(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size))) {
|
||||
InLockedRWXRead = true;
|
||||
} else {
|
||||
CTX->GetCodeInvalidationMutex().unlock();
|
||||
ThreadCreationMutex.unlock();
|
||||
}
|
||||
} else {
|
||||
if (InLockedRWXRead) {
|
||||
InLockedRWXRead = false;
|
||||
CTX->GetCodeInvalidationMutex().unlock();
|
||||
ThreadCreationMutex.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOLEAN WINAPI BTCpuIsProcessorFeaturePresent(UINT Feature) {
|
||||
return CPUFeatures->IsFeaturePresent(Feature) ? TRUE : FALSE;
|
||||
|
||||
Reference in new issue
Block a user