Move partial unmap handler to the native signal handler (#3437)
* Initial commit with a lot of testing stuff. * Partial Unmap Cleanup Part 1 * Fix some minor issues, hopefully windows tests. * Disable partial unmap tests on macos for now Weird issue. * Goodbye magic number * Add COMPlus_EnableAlternateStackCheck for tests `COMPlus_EnableAlternateStackCheck` is needed for NullReferenceException handling to work on linux after registering the signal handler, due to how dotnet registers its own signal handler. * Address some feedback * Force retry when memory is mapped in memory tracking This case existed before, but returning `false` no longer retries, so it would crash immediately after unprotecting the memory... Now, we return `true` to deliberately retry. This case existed before (was just broken by this change) and I don't really want to look into fixing the issue right now. Technically, this means that on guest code partial unmaps will retry _due to this_ rather than hitting the handler. I don't expect this to cause any issues. This should fix random crashes in Xenoblade Chronicles 2. * Use IsRangeMapped * Suppress MockMemoryManager.UnmapEvent warning This event is not signalled by the mock memory manager. * Remove 4kb mapping
This commit is contained in:
parent
952d013c67
commit
14ce9e1567
24 changed files with 1355 additions and 391 deletions
|
@ -35,12 +35,6 @@ namespace Ryujinx.Memory
|
|||
/// Indicates that the memory block should support mapping views of a mirrorable memory block.
|
||||
/// The block that is to have their views mapped should be created with the <see cref="Mirrorable"/> flag.
|
||||
/// </summary>
|
||||
ViewCompatible = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// Forces views to be mapped page by page on Windows. When partial unmaps are done, this avoids the need
|
||||
/// to unmap the full range and remap sub-ranges, which creates a time window with incorrectly unmapped memory.
|
||||
/// </summary>
|
||||
ForceWindows4KBViewMapping = 1 << 4
|
||||
ViewCompatible = 1 << 3
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,14 +13,11 @@ namespace Ryujinx.Memory
|
|||
private readonly bool _usesSharedMemory;
|
||||
private readonly bool _isMirror;
|
||||
private readonly bool _viewCompatible;
|
||||
private readonly bool _forceWindows4KBView;
|
||||
private IntPtr _sharedMemory;
|
||||
private IntPtr _pointer;
|
||||
private ConcurrentDictionary<MemoryBlock, byte> _viewStorages;
|
||||
private int _viewCount;
|
||||
|
||||
internal bool ForceWindows4KBView => _forceWindows4KBView;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the memory block data.
|
||||
/// </summary>
|
||||
|
@ -49,8 +46,7 @@ namespace Ryujinx.Memory
|
|||
else if (flags.HasFlag(MemoryAllocationFlags.Reserve))
|
||||
{
|
||||
_viewCompatible = flags.HasFlag(MemoryAllocationFlags.ViewCompatible);
|
||||
_forceWindows4KBView = flags.HasFlag(MemoryAllocationFlags.ForceWindows4KBViewMapping);
|
||||
_pointer = MemoryManagement.Reserve(size, _viewCompatible, _forceWindows4KBView);
|
||||
_pointer = MemoryManagement.Reserve(size, _viewCompatible);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -173,7 +169,7 @@ namespace Ryujinx.Memory
|
|||
/// <exception cref="MemoryProtectionException">Throw when <paramref name="permission"/> is invalid</exception>
|
||||
public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail = true)
|
||||
{
|
||||
MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, _viewCompatible, _forceWindows4KBView, throwOnFail);
|
||||
MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, _viewCompatible, throwOnFail);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -406,7 +402,7 @@ namespace Ryujinx.Memory
|
|||
}
|
||||
else
|
||||
{
|
||||
MemoryManagement.Free(ptr, Size, _forceWindows4KBView);
|
||||
MemoryManagement.Free(ptr, Size);
|
||||
}
|
||||
|
||||
foreach (MemoryBlock viewStorage in _viewStorages.Keys)
|
||||
|
|
|
@ -20,11 +20,11 @@ namespace Ryujinx.Memory
|
|||
}
|
||||
}
|
||||
|
||||
public static IntPtr Reserve(ulong size, bool viewCompatible, bool force4KBMap)
|
||||
public static IntPtr Reserve(ulong size, bool viewCompatible)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return MemoryManagementWindows.Reserve((IntPtr)size, viewCompatible, force4KBMap);
|
||||
return MemoryManagementWindows.Reserve((IntPtr)size, viewCompatible);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
|
@ -72,14 +72,7 @@ namespace Ryujinx.Memory
|
|||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (owner.ForceWindows4KBView)
|
||||
{
|
||||
MemoryManagementWindows.MapView4KB(sharedMemory, srcOffset, address, (IntPtr)size);
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, (IntPtr)size, owner);
|
||||
}
|
||||
MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, (IntPtr)size, owner);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
|
@ -95,14 +88,7 @@ namespace Ryujinx.Memory
|
|||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (owner.ForceWindows4KBView)
|
||||
{
|
||||
MemoryManagementWindows.UnmapView4KB(address, (IntPtr)size);
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryManagementWindows.UnmapView(sharedMemory, address, (IntPtr)size, owner);
|
||||
}
|
||||
MemoryManagementWindows.UnmapView(sharedMemory, address, (IntPtr)size, owner);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
|
@ -114,20 +100,13 @@ namespace Ryujinx.Memory
|
|||
}
|
||||
}
|
||||
|
||||
public static void Reprotect(IntPtr address, ulong size, MemoryPermission permission, bool forView, bool force4KBMap, bool throwOnFail)
|
||||
public static void Reprotect(IntPtr address, ulong size, MemoryPermission permission, bool forView, bool throwOnFail)
|
||||
{
|
||||
bool result;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (forView && force4KBMap)
|
||||
{
|
||||
result = MemoryManagementWindows.Reprotect4KB(address, (IntPtr)size, permission, forView);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = MemoryManagementWindows.Reprotect(address, (IntPtr)size, permission, forView);
|
||||
}
|
||||
result = MemoryManagementWindows.Reprotect(address, (IntPtr)size, permission, forView);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
|
@ -144,11 +123,11 @@ namespace Ryujinx.Memory
|
|||
}
|
||||
}
|
||||
|
||||
public static bool Free(IntPtr address, ulong size, bool force4KBMap)
|
||||
public static bool Free(IntPtr address, ulong size)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return MemoryManagementWindows.Free(address, (IntPtr)size, force4KBMap);
|
||||
return MemoryManagementWindows.Free(address, (IntPtr)size);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
|
|
|
@ -10,23 +10,19 @@ namespace Ryujinx.Memory
|
|||
public const int PageSize = 0x1000;
|
||||
|
||||
private static readonly PlaceholderManager _placeholders = new PlaceholderManager();
|
||||
private static readonly PlaceholderManager4KB _placeholders4KB = new PlaceholderManager4KB();
|
||||
|
||||
public static IntPtr Allocate(IntPtr size)
|
||||
{
|
||||
return AllocateInternal(size, AllocationType.Reserve | AllocationType.Commit);
|
||||
}
|
||||
|
||||
public static IntPtr Reserve(IntPtr size, bool viewCompatible, bool force4KBMap)
|
||||
public static IntPtr Reserve(IntPtr size, bool viewCompatible)
|
||||
{
|
||||
if (viewCompatible)
|
||||
{
|
||||
IntPtr baseAddress = AllocateInternal2(size, AllocationType.Reserve | AllocationType.ReservePlaceholder);
|
||||
|
||||
if (!force4KBMap)
|
||||
{
|
||||
_placeholders.ReserveRange((ulong)baseAddress, (ulong)size);
|
||||
}
|
||||
_placeholders.ReserveRange((ulong)baseAddress, (ulong)size);
|
||||
|
||||
return baseAddress;
|
||||
}
|
||||
|
@ -73,49 +69,11 @@ namespace Ryujinx.Memory
|
|||
_placeholders.MapView(sharedMemory, srcOffset, location, size, owner);
|
||||
}
|
||||
|
||||
public static void MapView4KB(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size)
|
||||
{
|
||||
_placeholders4KB.UnmapAndMarkRangeAsMapped(location, size);
|
||||
|
||||
ulong uaddress = (ulong)location;
|
||||
ulong usize = (ulong)size;
|
||||
IntPtr endLocation = (IntPtr)(uaddress + usize);
|
||||
|
||||
while (location != endLocation)
|
||||
{
|
||||
WindowsApi.VirtualFree(location, (IntPtr)PageSize, AllocationType.Release | AllocationType.PreservePlaceholder);
|
||||
|
||||
var ptr = WindowsApi.MapViewOfFile3(
|
||||
sharedMemory,
|
||||
WindowsApi.CurrentProcessHandle,
|
||||
location,
|
||||
srcOffset,
|
||||
(IntPtr)PageSize,
|
||||
0x4000,
|
||||
MemoryProtection.ReadWrite,
|
||||
IntPtr.Zero,
|
||||
0);
|
||||
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
throw new WindowsApiException("MapViewOfFile3");
|
||||
}
|
||||
|
||||
location += PageSize;
|
||||
srcOffset += PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnmapView(IntPtr sharedMemory, IntPtr location, IntPtr size, MemoryBlock owner)
|
||||
{
|
||||
_placeholders.UnmapView(sharedMemory, location, size, owner);
|
||||
}
|
||||
|
||||
public static void UnmapView4KB(IntPtr location, IntPtr size)
|
||||
{
|
||||
_placeholders4KB.UnmapView(location, size);
|
||||
}
|
||||
|
||||
public static bool Reprotect(IntPtr address, IntPtr size, MemoryPermission permission, bool forView)
|
||||
{
|
||||
if (forView)
|
||||
|
@ -128,34 +86,9 @@ namespace Ryujinx.Memory
|
|||
}
|
||||
}
|
||||
|
||||
public static bool Reprotect4KB(IntPtr address, IntPtr size, MemoryPermission permission, bool forView)
|
||||
public static bool Free(IntPtr address, IntPtr size)
|
||||
{
|
||||
ulong uaddress = (ulong)address;
|
||||
ulong usize = (ulong)size;
|
||||
while (usize > 0)
|
||||
{
|
||||
if (!WindowsApi.VirtualProtect((IntPtr)uaddress, (IntPtr)PageSize, WindowsApi.GetProtection(permission), out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uaddress += PageSize;
|
||||
usize -= PageSize;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool Free(IntPtr address, IntPtr size, bool force4KBMap)
|
||||
{
|
||||
if (force4KBMap)
|
||||
{
|
||||
_placeholders4KB.UnmapRange(address, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
_placeholders.UnreserveRange((ulong)address, (ulong)size);
|
||||
}
|
||||
_placeholders.UnreserveRange((ulong)address, (ulong)size);
|
||||
|
||||
return WindowsApi.VirtualFree(address, IntPtr.Zero, AllocationType.Release);
|
||||
}
|
||||
|
@ -207,10 +140,5 @@ namespace Ryujinx.Memory
|
|||
throw new ArgumentException("Invalid address.", nameof(address));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool RetryFromAccessViolation()
|
||||
{
|
||||
return _placeholders.RetryFromAccessViolation();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -188,30 +188,6 @@ namespace Ryujinx.Memory.Tracking
|
|||
return VirtualMemoryEvent(address, 1, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that a virtual memory event happened at the given location.
|
||||
/// This is similar VirtualMemoryEvent, but on Windows, it might also return true after a partial unmap.
|
||||
/// This should only be called from the exception handler.
|
||||
/// </summary>
|
||||
/// <param name="address">Virtual address accessed</param>
|
||||
/// <param name="size">Size of the region affected in bytes</param>
|
||||
/// <param name="write">Whether the region was written to or read</param>
|
||||
/// <param name="precise">True if the access is precise, false otherwise</param>
|
||||
/// <returns>True if the event triggered any tracking regions, false otherwise</returns>
|
||||
public bool VirtualMemoryEventEh(ulong address, ulong size, bool write, bool precise = false)
|
||||
{
|
||||
// Windows has a limitation, it can't do partial unmaps.
|
||||
// For this reason, we need to unmap the whole range and then remap the sub-ranges.
|
||||
// When this happens, we might have caused a undesirable access violation from the time that the range was unmapped.
|
||||
// In this case, try again as the memory might be mapped now.
|
||||
if (OperatingSystem.IsWindows() && MemoryManagementWindows.RetryFromAccessViolation())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return VirtualMemoryEvent(address, size, write, precise);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that a virtual memory event happened at the given location.
|
||||
/// This can be flagged as a precise event, which will avoid reprotection and call special handlers if possible.
|
||||
|
@ -237,10 +213,12 @@ namespace Ryujinx.Memory.Tracking
|
|||
|
||||
if (count == 0 && !precise)
|
||||
{
|
||||
if (_memoryManager.IsMapped(address))
|
||||
if (_memoryManager.IsRangeMapped(address, size))
|
||||
{
|
||||
// TODO: There is currently the possibility that a page can be protected after its virtual region is removed.
|
||||
// This code handles that case when it happens, but it would be better to find out how this happens.
|
||||
_memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite);
|
||||
return false; // We can't handle this - it's probably a real invalid access.
|
||||
return true; // This memory _should_ be mapped, so we need to try again.
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using Ryujinx.Common.Memory.PartialUnmaps;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
|
@ -13,13 +15,10 @@ namespace Ryujinx.Memory.WindowsShared
|
|||
{
|
||||
private const ulong MinimumPageSize = 0x1000;
|
||||
|
||||
[ThreadStatic]
|
||||
private static int _threadLocalPartialUnmapsCount;
|
||||
|
||||
private readonly IntervalTree<ulong, ulong> _mappings;
|
||||
private readonly IntervalTree<ulong, MemoryPermission> _protections;
|
||||
private readonly ReaderWriterLock _partialUnmapLock;
|
||||
private int _partialUnmapsCount;
|
||||
private readonly IntPtr _partialUnmapStatePtr;
|
||||
private readonly Thread _partialUnmapTrimThread;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the Windows memory placeholder manager.
|
||||
|
@ -28,7 +27,35 @@ namespace Ryujinx.Memory.WindowsShared
|
|||
{
|
||||
_mappings = new IntervalTree<ulong, ulong>();
|
||||
_protections = new IntervalTree<ulong, MemoryPermission>();
|
||||
_partialUnmapLock = new ReaderWriterLock();
|
||||
|
||||
_partialUnmapStatePtr = PartialUnmapState.GlobalState;
|
||||
|
||||
_partialUnmapTrimThread = new Thread(TrimThreadLocalMapLoop);
|
||||
_partialUnmapTrimThread.Name = "CPU.PartialUnmapTrimThread";
|
||||
_partialUnmapTrimThread.IsBackground = true;
|
||||
_partialUnmapTrimThread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the partial unmap state struct.
|
||||
/// </summary>
|
||||
/// <returns>A reference to the partial unmap state struct</returns>
|
||||
private unsafe ref PartialUnmapState GetPartialUnmapState()
|
||||
{
|
||||
return ref Unsafe.AsRef<PartialUnmapState>((void*)_partialUnmapStatePtr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trims inactive threads from the partial unmap state's thread mapping every few seconds.
|
||||
/// Should be run in a Background thread so that it doesn't stop the program from closing.
|
||||
/// </summary>
|
||||
private void TrimThreadLocalMapLoop()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Thread.Sleep(2000);
|
||||
GetPartialUnmapState().TrimThreads();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -98,7 +125,8 @@ namespace Ryujinx.Memory.WindowsShared
|
|||
/// <param name="owner">Memory block that owns the mapping</param>
|
||||
public void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size, MemoryBlock owner)
|
||||
{
|
||||
_partialUnmapLock.AcquireReaderLock(Timeout.Infinite);
|
||||
ref var partialUnmapLock = ref GetPartialUnmapState().PartialUnmapLock;
|
||||
partialUnmapLock.AcquireReaderLock();
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -107,7 +135,7 @@ namespace Ryujinx.Memory.WindowsShared
|
|||
}
|
||||
finally
|
||||
{
|
||||
_partialUnmapLock.ReleaseReaderLock();
|
||||
partialUnmapLock.ReleaseReaderLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -221,7 +249,8 @@ namespace Ryujinx.Memory.WindowsShared
|
|||
/// <param name="owner">Memory block that owns the mapping</param>
|
||||
public void UnmapView(IntPtr sharedMemory, IntPtr location, IntPtr size, MemoryBlock owner)
|
||||
{
|
||||
_partialUnmapLock.AcquireReaderLock(Timeout.Infinite);
|
||||
ref var partialUnmapLock = ref GetPartialUnmapState().PartialUnmapLock;
|
||||
partialUnmapLock.AcquireReaderLock();
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -229,7 +258,7 @@ namespace Ryujinx.Memory.WindowsShared
|
|||
}
|
||||
finally
|
||||
{
|
||||
_partialUnmapLock.ReleaseReaderLock();
|
||||
partialUnmapLock.ReleaseReaderLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,11 +294,6 @@ namespace Ryujinx.Memory.WindowsShared
|
|||
|
||||
if (IsMapped(overlap.Value))
|
||||
{
|
||||
if (!WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)overlap.Start, 2))
|
||||
{
|
||||
throw new WindowsApiException("UnmapViewOfFile2");
|
||||
}
|
||||
|
||||
// Tree operations might modify the node start/end values, so save a copy before we modify the tree.
|
||||
ulong overlapStart = overlap.Start;
|
||||
ulong overlapEnd = overlap.End;
|
||||
|
@ -291,30 +315,46 @@ namespace Ryujinx.Memory.WindowsShared
|
|||
// This is necessary because Windows does not support partial view unmaps.
|
||||
// That is, you can only fully unmap a view that was previously mapped, you can't just unmap a chunck of it.
|
||||
|
||||
LockCookie lockCookie = _partialUnmapLock.UpgradeToWriterLock(Timeout.Infinite);
|
||||
ref var partialUnmapState = ref GetPartialUnmapState();
|
||||
ref var partialUnmapLock = ref partialUnmapState.PartialUnmapLock;
|
||||
partialUnmapLock.UpgradeToWriterLock();
|
||||
|
||||
_partialUnmapsCount++;
|
||||
|
||||
if (overlapStartsBefore)
|
||||
try
|
||||
{
|
||||
ulong remapSize = startAddress - overlapStart;
|
||||
partialUnmapState.PartialUnmapsCount++;
|
||||
|
||||
MapViewInternal(sharedMemory, overlapValue, (IntPtr)overlapStart, (IntPtr)remapSize);
|
||||
RestoreRangeProtection(overlapStart, remapSize);
|
||||
if (!WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)overlapStart, 2))
|
||||
{
|
||||
throw new WindowsApiException("UnmapViewOfFile2");
|
||||
}
|
||||
|
||||
if (overlapStartsBefore)
|
||||
{
|
||||
ulong remapSize = startAddress - overlapStart;
|
||||
|
||||
MapViewInternal(sharedMemory, overlapValue, (IntPtr)overlapStart, (IntPtr)remapSize);
|
||||
RestoreRangeProtection(overlapStart, remapSize);
|
||||
}
|
||||
|
||||
if (overlapEndsAfter)
|
||||
{
|
||||
ulong overlappedSize = endAddress - overlapStart;
|
||||
ulong remapBackingOffset = overlapValue + overlappedSize;
|
||||
ulong remapAddress = overlapStart + overlappedSize;
|
||||
ulong remapSize = overlapEnd - endAddress;
|
||||
|
||||
MapViewInternal(sharedMemory, remapBackingOffset, (IntPtr)remapAddress, (IntPtr)remapSize);
|
||||
RestoreRangeProtection(remapAddress, remapSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (overlapEndsAfter)
|
||||
finally
|
||||
{
|
||||
ulong overlappedSize = endAddress - overlapStart;
|
||||
ulong remapBackingOffset = overlapValue + overlappedSize;
|
||||
ulong remapAddress = overlapStart + overlappedSize;
|
||||
ulong remapSize = overlapEnd - endAddress;
|
||||
|
||||
MapViewInternal(sharedMemory, remapBackingOffset, (IntPtr)remapAddress, (IntPtr)remapSize);
|
||||
RestoreRangeProtection(remapAddress, remapSize);
|
||||
partialUnmapLock.DowngradeFromWriterLock();
|
||||
}
|
||||
|
||||
_partialUnmapLock.DowngradeFromWriterLock(ref lockCookie);
|
||||
}
|
||||
else if (!WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)overlapStart, 2))
|
||||
{
|
||||
throw new WindowsApiException("UnmapViewOfFile2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -394,7 +434,8 @@ namespace Ryujinx.Memory.WindowsShared
|
|||
/// <returns>True if the reprotection was successful, false otherwise</returns>
|
||||
public bool ReprotectView(IntPtr address, IntPtr size, MemoryPermission permission)
|
||||
{
|
||||
_partialUnmapLock.AcquireReaderLock(Timeout.Infinite);
|
||||
ref var partialUnmapLock = ref GetPartialUnmapState().PartialUnmapLock;
|
||||
partialUnmapLock.AcquireReaderLock();
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -402,7 +443,7 @@ namespace Ryujinx.Memory.WindowsShared
|
|||
}
|
||||
finally
|
||||
{
|
||||
_partialUnmapLock.ReleaseReaderLock();
|
||||
partialUnmapLock.ReleaseReaderLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -659,31 +700,5 @@ namespace Ryujinx.Memory.WindowsShared
|
|||
ReprotectViewInternal((IntPtr)protAddress, (IntPtr)(protEndAddress - protAddress), protection.Value, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an access violation handler should retry execution due to a fault caused by partial unmap.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Due to Windows limitations, <see cref="UnmapView"/> might need to unmap more memory than requested.
|
||||
/// The additional memory that was unmapped is later remapped, however this leaves a time gap where the
|
||||
/// memory might be accessed but is unmapped. Users of the API must compensate for that by catching the
|
||||
/// access violation and retrying if it happened between the unmap and remap operation.
|
||||
/// This method can be used to decide if retrying in such cases is necessary or not.
|
||||
/// </remarks>
|
||||
/// <returns>True if execution should be retried, false otherwise</returns>
|
||||
public bool RetryFromAccessViolation()
|
||||
{
|
||||
_partialUnmapLock.AcquireReaderLock(Timeout.Infinite);
|
||||
|
||||
bool retry = _threadLocalPartialUnmapsCount != _partialUnmapsCount;
|
||||
if (retry)
|
||||
{
|
||||
_threadLocalPartialUnmapsCount = _partialUnmapsCount;
|
||||
}
|
||||
|
||||
_partialUnmapLock.ReleaseReaderLock();
|
||||
|
||||
return retry;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Memory.WindowsShared
|
||||
{
|
||||
/// <summary>
|
||||
/// Windows 4KB memory placeholder manager.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
class PlaceholderManager4KB
|
||||
{
|
||||
private const int PageSize = MemoryManagementWindows.PageSize;
|
||||
|
||||
private readonly IntervalTree<ulong, byte> _mappings;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the Windows 4KB memory placeholder manager.
|
||||
/// </summary>
|
||||
public PlaceholderManager4KB()
|
||||
{
|
||||
_mappings = new IntervalTree<ulong, byte>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unmaps the specified range of memory and marks it as mapped internally.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Since this marks the range as mapped, the expectation is that the range will be mapped after calling this method.
|
||||
/// </remarks>
|
||||
/// <param name="location">Memory address to unmap and mark as mapped</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
public void UnmapAndMarkRangeAsMapped(IntPtr location, IntPtr size)
|
||||
{
|
||||
ulong startAddress = (ulong)location;
|
||||
ulong unmapSize = (ulong)size;
|
||||
ulong endAddress = startAddress + unmapSize;
|
||||
|
||||
var overlaps = Array.Empty<IntervalTreeNode<ulong, byte>>();
|
||||
int count = 0;
|
||||
|
||||
lock (_mappings)
|
||||
{
|
||||
count = _mappings.Get(startAddress, endAddress, ref overlaps);
|
||||
}
|
||||
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
var overlap = overlaps[index];
|
||||
|
||||
// Tree operations might modify the node start/end values, so save a copy before we modify the tree.
|
||||
ulong overlapStart = overlap.Start;
|
||||
ulong overlapEnd = overlap.End;
|
||||
ulong overlapValue = overlap.Value;
|
||||
|
||||
_mappings.Remove(overlap);
|
||||
|
||||
ulong unmapStart = Math.Max(overlapStart, startAddress);
|
||||
ulong unmapEnd = Math.Min(overlapEnd, endAddress);
|
||||
|
||||
if (overlapStart < startAddress)
|
||||
{
|
||||
startAddress = overlapStart;
|
||||
}
|
||||
|
||||
if (overlapEnd > endAddress)
|
||||
{
|
||||
endAddress = overlapEnd;
|
||||
}
|
||||
|
||||
ulong currentAddress = unmapStart;
|
||||
while (currentAddress < unmapEnd)
|
||||
{
|
||||
WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)currentAddress, 2);
|
||||
currentAddress += PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
_mappings.Add(startAddress, endAddress, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unmaps views at the specified memory range.
|
||||
/// </summary>
|
||||
/// <param name="location">Address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
public void UnmapView(IntPtr location, IntPtr size)
|
||||
{
|
||||
ulong startAddress = (ulong)location;
|
||||
ulong unmapSize = (ulong)size;
|
||||
ulong endAddress = startAddress + unmapSize;
|
||||
|
||||
var overlaps = Array.Empty<IntervalTreeNode<ulong, byte>>();
|
||||
int count = 0;
|
||||
|
||||
lock (_mappings)
|
||||
{
|
||||
count = _mappings.Get(startAddress, endAddress, ref overlaps);
|
||||
}
|
||||
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
var overlap = overlaps[index];
|
||||
|
||||
// Tree operations might modify the node start/end values, so save a copy before we modify the tree.
|
||||
ulong overlapStart = overlap.Start;
|
||||
ulong overlapEnd = overlap.End;
|
||||
|
||||
_mappings.Remove(overlap);
|
||||
|
||||
if (overlapStart < startAddress)
|
||||
{
|
||||
_mappings.Add(overlapStart, startAddress, 0);
|
||||
}
|
||||
|
||||
if (overlapEnd > endAddress)
|
||||
{
|
||||
_mappings.Add(endAddress, overlapEnd, 0);
|
||||
}
|
||||
|
||||
ulong unmapStart = Math.Max(overlapStart, startAddress);
|
||||
ulong unmapEnd = Math.Min(overlapEnd, endAddress);
|
||||
|
||||
ulong currentAddress = unmapStart;
|
||||
while (currentAddress < unmapEnd)
|
||||
{
|
||||
WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)currentAddress, 2);
|
||||
currentAddress += PageSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unmaps mapped memory at a given range.
|
||||
/// </summary>
|
||||
/// <param name="location">Address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
public void UnmapRange(IntPtr location, IntPtr size)
|
||||
{
|
||||
ulong startAddress = (ulong)location;
|
||||
ulong unmapSize = (ulong)size;
|
||||
ulong endAddress = startAddress + unmapSize;
|
||||
|
||||
var overlaps = Array.Empty<IntervalTreeNode<ulong, byte>>();
|
||||
int count = 0;
|
||||
|
||||
lock (_mappings)
|
||||
{
|
||||
count = _mappings.Get(startAddress, endAddress, ref overlaps);
|
||||
}
|
||||
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
var overlap = overlaps[index];
|
||||
|
||||
// Tree operations might modify the node start/end values, so save a copy before we modify the tree.
|
||||
ulong unmapStart = Math.Max(overlap.Start, startAddress);
|
||||
ulong unmapEnd = Math.Min(overlap.End, endAddress);
|
||||
|
||||
_mappings.Remove(overlap);
|
||||
|
||||
ulong currentAddress = unmapStart;
|
||||
while (currentAddress < unmapEnd)
|
||||
{
|
||||
WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)currentAddress, 2);
|
||||
currentAddress += PageSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -76,6 +76,9 @@ namespace Ryujinx.Memory.WindowsShared
|
|||
[DllImport("kernel32.dll")]
|
||||
public static extern uint GetLastError();
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern int GetCurrentThreadId();
|
||||
|
||||
public static MemoryProtection GetProtection(MemoryPermission permission)
|
||||
{
|
||||
return permission switch
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue