2022-08-31 03:26:40 +02:00
using Ryujinx.Common.Collections ;
2022-07-30 00:16:29 +02:00
using Ryujinx.Common.Memory.PartialUnmaps ;
2022-05-03 01:30:02 +02:00
using System ;
using System.Diagnostics ;
2022-07-30 00:16:29 +02:00
using System.Runtime.CompilerServices ;
2022-05-05 19:58:59 +02:00
using System.Runtime.Versioning ;
2022-05-03 01:30:02 +02:00
using System.Threading ;
namespace Ryujinx.Memory.WindowsShared
{
/// <summary>
/// Windows memory placeholder manager.
/// </summary>
2022-05-05 19:58:59 +02:00
[SupportedOSPlatform("windows")]
2022-05-03 01:30:02 +02:00
class PlaceholderManager
{
2022-06-23 09:05:56 +02:00
private const int InitialOverlapsSize = 10 ;
2022-05-03 01:30:02 +02:00
2022-06-23 09:05:56 +02:00
private readonly MappingTree < ulong > _mappings ;
private readonly MappingTree < MemoryPermission > _protections ;
2022-07-30 00:16:29 +02:00
private readonly IntPtr _partialUnmapStatePtr ;
private readonly Thread _partialUnmapTrimThread ;
2022-05-03 01:30:02 +02:00
/// <summary>
/// Creates a new instance of the Windows memory placeholder manager.
/// </summary>
public PlaceholderManager ( )
{
2022-06-23 09:05:56 +02:00
_mappings = new MappingTree < ulong > ( ) ;
_protections = new MappingTree < MemoryPermission > ( ) ;
2022-07-30 00:16:29 +02:00
_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 ( ) ;
}
2022-05-03 01:30:02 +02:00
}
/// <summary>
/// Reserves a range of the address space to be later mapped as shared memory views.
/// </summary>
/// <param name="address">Start address of the region to reserve</param>
/// <param name="size">Size in bytes of the region to reserve</param>
public void ReserveRange ( ulong address , ulong size )
{
lock ( _mappings )
{
2022-06-23 09:05:56 +02:00
_mappings . Add ( new RangeNode < ulong > ( address , address + size , ulong . MaxValue ) ) ;
2022-05-03 01:30:02 +02:00
}
2022-06-23 18:52:45 +02:00
lock ( _protections )
{
2022-09-14 17:46:37 +02:00
_protections . Add ( new RangeNode < MemoryPermission > ( address , address + size , MemoryPermission . None ) ) ;
2022-06-23 18:52:45 +02:00
}
2022-05-03 01:30:02 +02:00
}
2022-06-05 20:12:42 +02:00
/// <summary>
/// Unreserves a range of memory that has been previously reserved with <see cref="ReserveRange"/>.
/// </summary>
/// <param name="address">Start address of the region to unreserve</param>
/// <param name="size">Size in bytes of the region to unreserve</param>
/// <exception cref="WindowsApiException">Thrown when the Windows API returns an error unreserving the memory</exception>
public void UnreserveRange ( ulong address , ulong size )
{
ulong endAddress = address + size ;
lock ( _mappings )
{
2022-08-31 03:26:40 +02:00
RangeNode < ulong > node = _mappings . GetNodeByKey ( address ) ;
2022-08-31 03:15:16 +02:00
RangeNode < ulong > successorNode ;
2022-06-05 20:12:42 +02:00
2022-08-31 03:15:16 +02:00
for ( ; node ! = null ; node = successorNode )
2022-06-05 20:12:42 +02:00
{
2022-08-31 03:15:16 +02:00
successorNode = node . Successor ;
2022-07-15 22:05:14 +02:00
if ( IsMapped ( node . Value ) )
2022-06-05 20:12:42 +02:00
{
2022-07-15 22:05:14 +02:00
if ( ! WindowsApi . UnmapViewOfFile2 ( WindowsApi . CurrentProcessHandle , ( IntPtr ) node . Start , 2 ) )
2022-06-05 20:12:42 +02:00
{
throw new WindowsApiException ( "UnmapViewOfFile2" ) ;
}
}
2022-07-15 22:05:14 +02:00
_mappings . Remove ( node ) ;
if ( node . End > = endAddress )
{
break ;
}
2022-06-05 20:12:42 +02:00
}
}
RemoveProtection ( address , size ) ;
}
2022-05-03 01:30:02 +02:00
/// <summary>
/// Maps a shared memory view on a previously reserved memory region.
/// </summary>
/// <param name="sharedMemory">Shared memory that will be the backing storage for the view</param>
/// <param name="srcOffset">Offset in the shared memory to map</param>
/// <param name="location">Address to map the view into</param>
/// <param name="size">Size of the view in bytes</param>
2022-06-05 20:12:42 +02:00
/// <param name="owner">Memory block that owns the mapping</param>
public void MapView ( IntPtr sharedMemory , ulong srcOffset , IntPtr location , IntPtr size , MemoryBlock owner )
2022-05-03 01:30:02 +02:00
{
2022-07-30 00:16:29 +02:00
ref var partialUnmapLock = ref GetPartialUnmapState ( ) . PartialUnmapLock ;
partialUnmapLock . AcquireReaderLock ( ) ;
2022-05-03 01:30:02 +02:00
try
{
2022-06-23 18:52:45 +02:00
UnmapViewInternal ( sharedMemory , location , size , owner , updateProtection : false ) ;
2022-09-14 17:46:37 +02:00
MapViewInternal ( sharedMemory , srcOffset , location , size , updateProtection : true ) ;
2022-05-03 01:30:02 +02:00
}
finally
{
2022-07-30 00:16:29 +02:00
partialUnmapLock . ReleaseReaderLock ( ) ;
2022-05-03 01:30:02 +02:00
}
}
/// <summary>
/// Maps a shared memory view on a previously reserved memory region.
/// </summary>
/// <param name="sharedMemory">Shared memory that will be the backing storage for the view</param>
/// <param name="srcOffset">Offset in the shared memory to map</param>
/// <param name="location">Address to map the view into</param>
/// <param name="size">Size of the view in bytes</param>
2022-09-14 17:46:37 +02:00
/// <param name="updateProtection">Indicates if the memory protections should be updated after the map</param>
2022-05-03 01:30:02 +02:00
/// <exception cref="WindowsApiException">Thrown when the Windows API returns an error mapping the memory</exception>
2022-09-14 17:46:37 +02:00
private void MapViewInternal ( IntPtr sharedMemory , ulong srcOffset , IntPtr location , IntPtr size , bool updateProtection )
2022-05-03 01:30:02 +02:00
{
SplitForMap ( ( ulong ) location , ( ulong ) size , srcOffset ) ;
var ptr = WindowsApi . MapViewOfFile3 (
sharedMemory ,
WindowsApi . CurrentProcessHandle ,
location ,
srcOffset ,
size ,
0x4000 ,
MemoryProtection . ReadWrite ,
IntPtr . Zero ,
0 ) ;
if ( ptr = = IntPtr . Zero )
{
throw new WindowsApiException ( "MapViewOfFile3" ) ;
}
2022-06-23 18:52:45 +02:00
2022-09-14 17:46:37 +02:00
if ( updateProtection )
{
UpdateProtection ( ( ulong ) location , ( ulong ) size , MemoryPermission . ReadAndWrite ) ;
}
2022-05-03 01:30:02 +02:00
}
/// <summary>
/// Splits a larger placeholder, slicing at the start and end address, for a new memory mapping.
/// </summary>
/// <param name="address">Address to split</param>
/// <param name="size">Size of the new region</param>
/// <param name="backingOffset">Offset in the shared memory that will be mapped</param>
private void SplitForMap ( ulong address , ulong size , ulong backingOffset )
{
ulong endAddress = address + size ;
2022-06-23 09:05:56 +02:00
var overlaps = new RangeNode < ulong > [ InitialOverlapsSize ] ;
2022-05-03 01:30:02 +02:00
lock ( _mappings )
{
2022-06-23 09:05:56 +02:00
int count = _mappings . GetNodes ( address , endAddress , ref overlaps ) ;
2022-05-03 01:30:02 +02:00
Debug . Assert ( count = = 1 ) ;
Debug . Assert ( ! IsMapped ( overlaps [ 0 ] . Value ) ) ;
var overlap = overlaps [ 0 ] ;
ulong overlapStart = overlap . Start ;
ulong overlapEnd = overlap . End ;
ulong overlapValue = overlap . Value ;
_mappings . Remove ( overlap ) ;
bool overlapStartsBefore = overlapStart < address ;
bool overlapEndsAfter = overlapEnd > endAddress ;
if ( overlapStartsBefore & & overlapEndsAfter )
{
CheckFreeResult ( WindowsApi . VirtualFree (
( IntPtr ) address ,
( IntPtr ) size ,
AllocationType . Release | AllocationType . PreservePlaceholder ) ) ;
2022-06-23 09:05:56 +02:00
_mappings . Add ( new RangeNode < ulong > ( overlapStart , address , overlapValue ) ) ;
_mappings . Add ( new RangeNode < ulong > ( endAddress , overlapEnd , AddBackingOffset ( overlapValue , endAddress - overlapStart ) ) ) ;
2022-05-03 01:30:02 +02:00
}
else if ( overlapStartsBefore )
{
ulong overlappedSize = overlapEnd - address ;
CheckFreeResult ( WindowsApi . VirtualFree (
( IntPtr ) address ,
( IntPtr ) overlappedSize ,
AllocationType . Release | AllocationType . PreservePlaceholder ) ) ;
2022-06-23 09:05:56 +02:00
_mappings . Add ( new RangeNode < ulong > ( overlapStart , address , overlapValue ) ) ;
2022-05-03 01:30:02 +02:00
}
else if ( overlapEndsAfter )
{
ulong overlappedSize = endAddress - overlapStart ;
CheckFreeResult ( WindowsApi . VirtualFree (
( IntPtr ) overlapStart ,
( IntPtr ) overlappedSize ,
AllocationType . Release | AllocationType . PreservePlaceholder ) ) ;
2022-06-23 09:05:56 +02:00
_mappings . Add ( new RangeNode < ulong > ( endAddress , overlapEnd , AddBackingOffset ( overlapValue , overlappedSize ) ) ) ;
2022-05-03 01:30:02 +02:00
}
2022-06-23 09:05:56 +02:00
_mappings . Add ( new RangeNode < ulong > ( address , endAddress , backingOffset ) ) ;
2022-05-03 01:30:02 +02:00
}
}
/// <summary>
/// Unmaps a view that has been previously mapped with <see cref="MapView"/>.
/// </summary>
/// <remarks>
/// For "partial unmaps" (when not the entire mapped range is being unmapped), it might be
/// necessary to unmap the whole range and then remap the sub-ranges that should remain mapped.
/// </remarks>
/// <param name="sharedMemory">Shared memory that the view being unmapped belongs to</param>
/// <param name="location">Address to unmap</param>
/// <param name="size">Size of the region to unmap in bytes</param>
2022-06-05 20:12:42 +02:00
/// <param name="owner">Memory block that owns the mapping</param>
public void UnmapView ( IntPtr sharedMemory , IntPtr location , IntPtr size , MemoryBlock owner )
2022-05-03 01:30:02 +02:00
{
2022-07-30 00:16:29 +02:00
ref var partialUnmapLock = ref GetPartialUnmapState ( ) . PartialUnmapLock ;
partialUnmapLock . AcquireReaderLock ( ) ;
2022-05-03 01:30:02 +02:00
try
{
2022-06-23 18:52:45 +02:00
UnmapViewInternal ( sharedMemory , location , size , owner , updateProtection : true ) ;
2022-05-03 01:30:02 +02:00
}
finally
{
2022-07-30 00:16:29 +02:00
partialUnmapLock . ReleaseReaderLock ( ) ;
2022-05-03 01:30:02 +02:00
}
}
/// <summary>
/// Unmaps a view that has been previously mapped with <see cref="MapView"/>.
/// </summary>
/// <remarks>
/// For "partial unmaps" (when not the entire mapped range is being unmapped), it might be
/// necessary to unmap the whole range and then remap the sub-ranges that should remain mapped.
/// </remarks>
/// <param name="sharedMemory">Shared memory that the view being unmapped belongs to</param>
/// <param name="location">Address to unmap</param>
/// <param name="size">Size of the region to unmap in bytes</param>
2022-06-05 20:12:42 +02:00
/// <param name="owner">Memory block that owns the mapping</param>
2022-06-23 18:52:45 +02:00
/// <param name="updateProtection">Indicates if the memory protections should be updated after the unmap</param>
2022-05-03 01:30:02 +02:00
/// <exception cref="WindowsApiException">Thrown when the Windows API returns an error unmapping or remapping the memory</exception>
2022-06-23 18:52:45 +02:00
private void UnmapViewInternal ( IntPtr sharedMemory , IntPtr location , IntPtr size , MemoryBlock owner , bool updateProtection )
2022-05-03 01:30:02 +02:00
{
ulong startAddress = ( ulong ) location ;
ulong unmapSize = ( ulong ) size ;
ulong endAddress = startAddress + unmapSize ;
2022-06-23 09:05:56 +02:00
var overlaps = new RangeNode < ulong > [ InitialOverlapsSize ] ;
2022-05-05 19:58:59 +02:00
int count ;
2022-05-03 01:30:02 +02:00
lock ( _mappings )
{
2022-06-23 09:05:56 +02:00
count = _mappings . GetNodes ( startAddress , endAddress , ref overlaps ) ;
2022-05-03 01:30:02 +02:00
}
for ( int index = 0 ; index < count ; index + + )
{
var overlap = overlaps [ index ] ;
if ( IsMapped ( overlap . Value ) )
{
2022-05-05 19:58:59 +02:00
lock ( _mappings )
{
_mappings . Remove ( overlap ) ;
2022-06-23 18:59:43 +02:00
_mappings . Add ( new RangeNode < ulong > ( overlap . Start , overlap . End , ulong . MaxValue ) ) ;
2022-05-05 19:58:59 +02:00
}
2022-05-03 01:30:02 +02:00
2022-06-23 18:59:43 +02:00
bool overlapStartsBefore = overlap . Start < startAddress ;
bool overlapEndsAfter = overlap . End > endAddress ;
2022-05-03 01:30:02 +02:00
if ( overlapStartsBefore | | overlapEndsAfter )
{
// If the overlap extends beyond the region we are unmapping,
// then we need to re-map the regions that are supposed to remain mapped.
// 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.
2022-07-30 00:16:29 +02:00
ref var partialUnmapState = ref GetPartialUnmapState ( ) ;
ref var partialUnmapLock = ref partialUnmapState . PartialUnmapLock ;
partialUnmapLock . UpgradeToWriterLock ( ) ;
2022-05-03 01:30:02 +02:00
2022-07-30 00:16:29 +02:00
try
2022-05-03 01:30:02 +02:00
{
2022-07-30 00:16:29 +02:00
partialUnmapState . PartialUnmapsCount + + ;
2022-06-23 18:59:43 +02:00
if ( ! WindowsApi . UnmapViewOfFile2 ( WindowsApi . CurrentProcessHandle , ( IntPtr ) overlap . Start , 2 ) )
2022-07-30 00:16:29 +02:00
{
throw new WindowsApiException ( "UnmapViewOfFile2" ) ;
}
if ( overlapStartsBefore )
{
2022-06-23 18:59:43 +02:00
ulong remapSize = startAddress - overlap . Start ;
2022-07-30 00:16:29 +02:00
2022-09-14 17:46:37 +02:00
MapViewInternal ( sharedMemory , overlap . Value , ( IntPtr ) overlap . Start , ( IntPtr ) remapSize , updateProtection : false ) ;
2022-06-23 18:59:43 +02:00
RestoreRangeProtection ( overlap . Start , remapSize ) ;
2022-07-30 00:16:29 +02:00
}
if ( overlapEndsAfter )
{
2022-06-23 18:59:43 +02:00
ulong overlappedSize = endAddress - overlap . Start ;
ulong remapBackingOffset = overlap . Value + overlappedSize ;
ulong remapAddress = overlap . Start + overlappedSize ;
ulong remapSize = overlap . End - endAddress ;
2022-07-30 00:16:29 +02:00
2022-09-14 17:46:37 +02:00
MapViewInternal ( sharedMemory , remapBackingOffset , ( IntPtr ) remapAddress , ( IntPtr ) remapSize , updateProtection : false ) ;
2022-07-30 00:16:29 +02:00
RestoreRangeProtection ( remapAddress , remapSize ) ;
}
2022-05-03 01:30:02 +02:00
}
2022-07-30 00:16:29 +02:00
finally
2022-05-03 01:30:02 +02:00
{
2022-07-30 00:16:29 +02:00
partialUnmapLock . DowngradeFromWriterLock ( ) ;
2022-05-03 01:30:02 +02:00
}
2022-07-30 00:16:29 +02:00
}
2022-06-23 18:59:43 +02:00
else if ( ! WindowsApi . UnmapViewOfFile2 ( WindowsApi . CurrentProcessHandle , ( IntPtr ) overlap . Start , 2 ) )
2022-07-30 00:16:29 +02:00
{
throw new WindowsApiException ( "UnmapViewOfFile2" ) ;
2022-05-03 01:30:02 +02:00
}
}
}
2022-06-05 20:12:42 +02:00
CoalesceForUnmap ( startAddress , unmapSize , owner ) ;
2022-06-23 18:52:45 +02:00
if ( updateProtection )
{
UpdateProtection ( startAddress , unmapSize , MemoryPermission . None ) ;
}
2022-05-03 01:30:02 +02:00
}
/// <summary>
/// Coalesces adjacent placeholders after unmap.
/// </summary>
/// <param name="address">Address of the region that was unmapped</param>
/// <param name="size">Size of the region that was unmapped in bytes</param>
2022-06-05 20:12:42 +02:00
/// <param name="owner">Memory block that owns the mapping</param>
private void CoalesceForUnmap ( ulong address , ulong size , MemoryBlock owner )
2022-05-03 01:30:02 +02:00
{
ulong endAddress = address + size ;
2022-06-05 20:12:42 +02:00
ulong blockAddress = ( ulong ) owner . Pointer ;
ulong blockEnd = blockAddress + owner . Size ;
2022-05-03 01:30:02 +02:00
int unmappedCount = 0 ;
lock ( _mappings )
{
2022-08-31 03:26:40 +02:00
RangeNode < ulong > node = _mappings . GetNodeByKey ( address ) ;
2022-06-05 20:12:42 +02:00
2022-08-31 03:15:16 +02:00
if ( node = = null )
2022-05-03 01:30:02 +02:00
{
2022-08-31 03:15:16 +02:00
// Nothing to coalesce if we have no overlaps.
2022-05-03 01:30:02 +02:00
return ;
}
2022-08-31 03:15:16 +02:00
RangeNode < ulong > predecessor = node . Predecessor ;
RangeNode < ulong > successor = null ;
2022-06-23 09:05:56 +02:00
2022-08-31 03:15:16 +02:00
for ( ; node ! = null ; node = successor )
2022-05-03 01:30:02 +02:00
{
2022-08-31 03:15:16 +02:00
successor = node . Successor ;
var overlap = node ;
2022-05-03 01:30:02 +02:00
if ( ! IsMapped ( overlap . Value ) )
{
2022-06-23 09:05:56 +02:00
address = Math . Min ( address , overlap . Start ) ;
endAddress = Math . Max ( endAddress , overlap . End ) ;
2022-05-03 01:30:02 +02:00
_mappings . Remove ( overlap ) ;
unmappedCount + + ;
}
2022-08-31 03:15:16 +02:00
if ( node . End > = endAddress )
{
break ;
}
2022-05-03 01:30:02 +02:00
}
2022-06-23 09:05:56 +02:00
if ( predecessor ! = null & & ! IsMapped ( predecessor . Value ) & & predecessor . Start > = blockAddress )
{
address = Math . Min ( address , predecessor . Start ) ;
_mappings . Remove ( predecessor ) ;
unmappedCount + + ;
}
if ( successor ! = null & & ! IsMapped ( successor . Value ) & & successor . End < = blockEnd )
{
endAddress = Math . Max ( endAddress , successor . End ) ;
_mappings . Remove ( successor ) ;
unmappedCount + + ;
}
_mappings . Add ( new RangeNode < ulong > ( address , endAddress , ulong . MaxValue ) ) ;
2022-05-03 01:30:02 +02:00
}
if ( unmappedCount > 1 )
{
size = endAddress - address ;
CheckFreeResult ( WindowsApi . VirtualFree (
( IntPtr ) address ,
( IntPtr ) size ,
AllocationType . Release | AllocationType . CoalescePlaceholders ) ) ;
}
}
/// <summary>
/// Reprotects a region of memory that has been mapped.
/// </summary>
/// <param name="address">Address of the region to reprotect</param>
/// <param name="size">Size of the region to reprotect in bytes</param>
/// <param name="permission">New permissions</param>
/// <returns>True if the reprotection was successful, false otherwise</returns>
public bool ReprotectView ( IntPtr address , IntPtr size , MemoryPermission permission )
{
2022-07-30 00:16:29 +02:00
ref var partialUnmapLock = ref GetPartialUnmapState ( ) . PartialUnmapLock ;
partialUnmapLock . AcquireReaderLock ( ) ;
2022-05-03 01:30:02 +02:00
try
{
return ReprotectViewInternal ( address , size , permission , false ) ;
}
finally
{
2022-07-30 00:16:29 +02:00
partialUnmapLock . ReleaseReaderLock ( ) ;
2022-05-03 01:30:02 +02:00
}
}
/// <summary>
/// Reprotects a region of memory that has been mapped.
/// </summary>
/// <param name="address">Address of the region to reprotect</param>
/// <param name="size">Size of the region to reprotect in bytes</param>
/// <param name="permission">New permissions</param>
/// <param name="throwOnError">Throw an exception instead of returning an error if the operation fails</param>
/// <returns>True if the reprotection was successful or if <paramref name="throwOnError"/> is true, false otherwise</returns>
/// <exception cref="WindowsApiException">If <paramref name="throwOnError"/> is true, it is thrown when the Windows API returns an error reprotecting the memory</exception>
private bool ReprotectViewInternal ( IntPtr address , IntPtr size , MemoryPermission permission , bool throwOnError )
{
ulong reprotectAddress = ( ulong ) address ;
ulong reprotectSize = ( ulong ) size ;
ulong endAddress = reprotectAddress + reprotectSize ;
2022-08-31 03:15:16 +02:00
bool success = true ;
2022-05-03 01:30:02 +02:00
lock ( _mappings )
{
2022-08-31 03:26:40 +02:00
RangeNode < ulong > node = _mappings . GetNodeByKey ( reprotectAddress ) ;
2022-08-31 03:15:16 +02:00
RangeNode < ulong > successorNode ;
2022-05-03 01:30:02 +02:00
2022-08-31 03:15:16 +02:00
for ( ; node ! = null ; node = successorNode )
{
successorNode = node . Successor ;
var overlap = node ;
2022-05-03 01:30:02 +02:00
2022-08-31 03:15:16 +02:00
ulong mappedAddress = overlap . Start ;
ulong mappedSize = overlap . End - overlap . Start ;
2022-05-03 01:30:02 +02:00
2022-08-31 03:15:16 +02:00
if ( mappedAddress < reprotectAddress )
{
ulong delta = reprotectAddress - mappedAddress ;
mappedAddress = reprotectAddress ;
mappedSize - = delta ;
}
2022-05-03 01:30:02 +02:00
2022-08-31 03:15:16 +02:00
ulong mappedEndAddress = mappedAddress + mappedSize ;
2022-05-03 01:30:02 +02:00
2022-08-31 03:15:16 +02:00
if ( mappedEndAddress > endAddress )
{
ulong delta = mappedEndAddress - endAddress ;
mappedSize - = delta ;
}
2022-05-03 01:30:02 +02:00
2022-08-31 03:15:16 +02:00
if ( ! WindowsApi . VirtualProtect ( ( IntPtr ) mappedAddress , ( IntPtr ) mappedSize , WindowsApi . GetProtection ( permission ) , out _ ) )
2022-05-03 01:30:02 +02:00
{
2022-08-31 03:15:16 +02:00
if ( throwOnError )
{
throw new WindowsApiException ( "VirtualProtect" ) ;
}
success = false ;
2022-05-03 01:30:02 +02:00
}
2022-08-31 03:15:16 +02:00
if ( node . End > = endAddress )
{
break ;
}
2022-05-03 01:30:02 +02:00
}
}
2022-06-23 18:52:45 +02:00
UpdateProtection ( reprotectAddress , reprotectSize , permission ) ;
2022-05-03 01:30:02 +02:00
return success ;
}
/// <summary>
/// Checks the result of a VirtualFree operation, throwing if needed.
/// </summary>
/// <param name="success">Operation result</param>
/// <exception cref="WindowsApiException">Thrown if <paramref name="success"/> is false</exception>
private static void CheckFreeResult ( bool success )
{
if ( ! success )
{
throw new WindowsApiException ( "VirtualFree" ) ;
}
}
/// <summary>
/// Adds an offset to a backing offset. This will do nothing if the backing offset is the special "unmapped" value.
/// </summary>
/// <param name="backingOffset">Backing offset</param>
/// <param name="offset">Offset to be added</param>
/// <returns>Added offset or just <paramref name="backingOffset"/> if the region is unmapped</returns>
private static ulong AddBackingOffset ( ulong backingOffset , ulong offset )
{
if ( backingOffset = = ulong . MaxValue )
{
return backingOffset ;
}
return backingOffset + offset ;
}
/// <summary>
/// Checks if a region is unmapped.
/// </summary>
/// <param name="backingOffset">Backing offset to check</param>
/// <returns>True if the backing offset is the special "unmapped" value, false otherwise</returns>
private static bool IsMapped ( ulong backingOffset )
{
return backingOffset ! = ulong . MaxValue ;
}
/// <summary>
/// Adds a protection to the list of protections.
/// </summary>
/// <param name="address">Address of the protected region</param>
/// <param name="size">Size of the protected region in bytes</param>
/// <param name="permission">Memory permissions of the region</param>
2022-06-23 18:52:45 +02:00
private void UpdateProtection ( ulong address , ulong size , MemoryPermission permission )
2022-05-03 01:30:02 +02:00
{
ulong endAddress = address + size ;
lock ( _protections )
{
2022-08-31 03:26:40 +02:00
RangeNode < MemoryPermission > node = _protections . GetNodeByKey ( address ) ;
2022-05-03 01:30:02 +02:00
2022-08-31 03:15:16 +02:00
if ( node ! = null & &
node . Start < = address & &
node . End > = endAddress & &
node . Value = = permission )
2022-05-03 01:30:02 +02:00
{
return ;
}
2022-08-31 03:15:16 +02:00
RangeNode < MemoryPermission > successorNode ;
2022-05-03 01:30:02 +02:00
ulong startAddress = address ;
2022-08-31 03:15:16 +02:00
for ( ; node ! = null ; node = successorNode )
2022-05-03 01:30:02 +02:00
{
2022-08-31 03:15:16 +02:00
successorNode = node . Successor ;
var protection = node ;
2022-05-03 01:30:02 +02:00
ulong protAddress = protection . Start ;
ulong protEndAddress = protection . End ;
MemoryPermission protPermission = protection . Value ;
_protections . Remove ( protection ) ;
2022-09-14 17:46:37 +02:00
if ( protPermission = = permission )
2022-05-03 01:30:02 +02:00
{
if ( startAddress > protAddress )
{
startAddress = protAddress ;
}
if ( endAddress < protEndAddress )
{
endAddress = protEndAddress ;
}
}
else
{
if ( startAddress > protAddress )
{
2022-06-23 09:05:56 +02:00
_protections . Add ( new RangeNode < MemoryPermission > ( protAddress , startAddress , protPermission ) ) ;
2022-05-03 01:30:02 +02:00
}
if ( endAddress < protEndAddress )
{
2022-06-23 09:05:56 +02:00
_protections . Add ( new RangeNode < MemoryPermission > ( endAddress , protEndAddress , protPermission ) ) ;
2022-05-03 01:30:02 +02:00
}
}
2022-08-31 03:15:16 +02:00
if ( node . End > = endAddress )
{
break ;
}
2022-05-03 01:30:02 +02:00
}
2022-06-23 09:05:56 +02:00
_protections . Add ( new RangeNode < MemoryPermission > ( startAddress , endAddress , permission ) ) ;
2022-05-03 01:30:02 +02:00
}
}
/// <summary>
/// Removes protection from the list of protections.
/// </summary>
/// <param name="address">Address of the protected region</param>
/// <param name="size">Size of the protected region in bytes</param>
private void RemoveProtection ( ulong address , ulong size )
{
ulong endAddress = address + size ;
lock ( _protections )
{
2022-08-31 03:26:40 +02:00
RangeNode < MemoryPermission > node = _protections . GetNodeByKey ( address ) ;
2022-08-31 03:15:16 +02:00
RangeNode < MemoryPermission > successorNode ;
2022-05-03 01:30:02 +02:00
2022-08-31 03:15:16 +02:00
for ( ; node ! = null ; node = successorNode )
2022-05-03 01:30:02 +02:00
{
2022-08-31 03:15:16 +02:00
successorNode = node . Successor ;
var protection = node ;
2022-05-03 01:30:02 +02:00
ulong protAddress = protection . Start ;
ulong protEndAddress = protection . End ;
MemoryPermission protPermission = protection . Value ;
_protections . Remove ( protection ) ;
if ( address > protAddress )
{
2022-06-23 09:05:56 +02:00
_protections . Add ( new RangeNode < MemoryPermission > ( protAddress , address , protPermission ) ) ;
2022-05-03 01:30:02 +02:00
}
if ( endAddress < protEndAddress )
{
2022-06-23 09:05:56 +02:00
_protections . Add ( new RangeNode < MemoryPermission > ( endAddress , protEndAddress , protPermission ) ) ;
2022-05-03 01:30:02 +02:00
}
2022-08-31 03:15:16 +02:00
if ( node . End > = endAddress )
{
break ;
}
2022-05-03 01:30:02 +02:00
}
}
}
/// <summary>
/// Restores the protection of a given memory region that was remapped, using the protections list.
/// </summary>
/// <param name="address">Address of the remapped region</param>
/// <param name="size">Size of the remapped region in bytes</param>
private void RestoreRangeProtection ( ulong address , ulong size )
{
ulong endAddress = address + size ;
2022-06-23 09:05:56 +02:00
var overlaps = new RangeNode < MemoryPermission > [ InitialOverlapsSize ] ;
2022-05-04 19:07:10 +02:00
int count ;
2022-05-03 01:30:02 +02:00
lock ( _protections )
{
2022-06-23 09:05:56 +02:00
count = _protections . GetNodes ( address , endAddress , ref overlaps ) ;
2022-05-03 01:30:02 +02:00
}
ulong startAddress = address ;
for ( int index = 0 ; index < count ; index + + )
{
var protection = overlaps [ index ] ;
2022-06-23 18:52:45 +02:00
// If protection is R/W we don't need to reprotect as views are initially mapped as R/W.
if ( protection . Value = = MemoryPermission . ReadAndWrite )
{
continue ;
}
2022-05-03 01:30:02 +02:00
ulong protAddress = protection . Start ;
ulong protEndAddress = protection . End ;
if ( protAddress < address )
{
protAddress = address ;
}
if ( protEndAddress > endAddress )
{
protEndAddress = endAddress ;
}
ReprotectViewInternal ( ( IntPtr ) protAddress , ( IntPtr ) ( protEndAddress - protAddress ) , protection . Value , true ) ;
}
}
}
}