[Memory/Kernel] Fix Windows allocation-granularity and mutex-resolution bugs (#748)

- Fixed guest mappings now go through a granule-aware allocator so
  adjacent PS5 16 KiB pages sharing a 64 KiB Windows allocation
  granule no longer collide and fail.
- TryBackFixedRange routes free/reserved gaps through the same
  granule-safe path, fixing strays that stranded the rest of a granule.
- NORMAL pthread mutex self-relock reverted to real EDEADLK instead of
  silent compatibility recursion, which was starving other threads.
- TryResolveMutexState now checks the handle-keyed lookup before
  falling through to "not found" on a fresh, never-cached address.
This commit is contained in:
Foued Attar
2026-08-02 23:15:42 +02:00
committed by GitHub
parent 4b5ea6a793
commit f36ce4084a
3 changed files with 493 additions and 37 deletions
@@ -104,6 +104,45 @@ public sealed class GuestMemoryAllocatorTests
Assert.Equal(0UL, (ulong)memory.GetPointer(address));
}
[Fact]
public void AdjacentFixedGuestPageMappingsShareAHostGranule()
{
if (!OperatingSystem.IsWindows())
{
return;
}
using var memory = new PhysicalVirtualMemory(new GranularityAwareHostMemory());
const ulong baseAddress = 0x0000008001600000;
Assert.Equal(baseAddress, memory.AllocateAt(baseAddress, 0x4000, executable: false, allowAlternative: false));
Assert.Equal(
baseAddress + 0x4000,
memory.AllocateAt(baseAddress + 0x4000, 0x4000, executable: false, allowAlternative: false));
Assert.Equal(
baseAddress + 0x8000,
memory.AllocateAt(baseAddress + 0x8000, 0x8000, executable: false, allowAlternative: false));
Assert.True(memory.IsAccessible(baseAddress, 0x10000));
}
[Fact]
public void TryBackFixedRangeSharesAHostGranuleAcrossCallsOnWindows()
{
if (!OperatingSystem.IsWindows())
{
return;
}
using var memory = new PhysicalVirtualMemory(new GranularityAwareHostMemory());
const ulong baseAddress = 0x0000008001600000;
Assert.True(memory.TryBackFixedRange(baseAddress, 0x4000, executable: false));
Assert.True(memory.TryBackFixedRange(baseAddress + 0x4000, 0x4000, executable: false));
Assert.True(memory.IsAccessible(baseAddress, 0x8000));
}
[Fact]
public void AlignedAllocationDoesNotRetainOverallocatedMappingsOutsideMacOS()
{
@@ -133,6 +172,11 @@ public sealed class GuestMemoryAllocatorTests
[Fact]
public void TryBackFixedRangeRollsBackEarlierGapsWhenLaterGapCannotBeBacked()
{
if (OperatingSystem.IsWindows())
{
return;
}
// Layout: committed | free | committed | free
// First free gap allocates successfully, second fails.
// The first allocation must be freed — nothing should leak.
@@ -154,6 +198,11 @@ public sealed class GuestMemoryAllocatorTests
[Fact]
public void TryBackFixedRangeFillsOnlyTheFreePagesOfAPartiallyOccupiedRange()
{
if (OperatingSystem.IsWindows())
{
return;
}
const ulong rangeBase = 0x0000_0020_2F00_0000;
const ulong rangeSize = 0x40_0000;
const ulong occupiedSize = 0x4_0000;
@@ -519,6 +568,154 @@ public sealed class GuestMemoryAllocatorTests
}
}
private sealed class GranularityAwareHostMemory : IHostMemory
{
private const ulong Granularity = 0x10000;
private const ulong Page = 0x1000;
private readonly SortedDictionary<ulong, (ulong Size, SortedSet<ulong> CommittedPages)> _allocations = new();
public ulong Allocate(ulong desiredAddress, ulong size, HostPageProtection protection)
{
var reservedBase = Reserve(desiredAddress, size, protection);
if (reservedBase != 0)
{
var start = desiredAddress == 0 ? reservedBase : AlignDown(desiredAddress, Page);
Commit(start, size, protection);
}
return reservedBase;
}
public ulong Reserve(ulong desiredAddress, ulong size, HostPageProtection protection)
{
if (desiredAddress == 0)
{
return 0;
}
var allocationBase = AlignDown(desiredAddress, Granularity);
var end = AlignUp(desiredAddress + size, Page);
foreach (var (existingBase, existing) in _allocations)
{
if (allocationBase < existingBase + existing.Size && existingBase < end)
{
return 0;
}
}
_allocations[allocationBase] = (end - allocationBase, new SortedSet<ulong>());
return allocationBase;
}
public bool Commit(ulong address, ulong size, HostPageProtection protection)
{
var start = AlignDown(address, Page);
var end = AlignUp(address + size, Page);
if (!TryFindAllocation(start, out var allocationBase, out var allocation) ||
end > allocationBase + allocation.Size)
{
return false;
}
for (var page = start; page < end; page += Page)
{
allocation.CommittedPages.Add(page);
}
return true;
}
public bool Free(ulong address) => _allocations.Remove(address);
public bool Protect(ulong address, ulong size, HostPageProtection protection, out uint rawOldProtection)
{
rawOldProtection = 0;
return true;
}
public bool ProtectRaw(ulong address, ulong size, uint rawProtection, out uint rawOldProtection)
{
rawOldProtection = 0;
return true;
}
public bool Query(ulong address, out HostRegionInfo info)
{
var page = AlignDown(address, Page);
if (TryFindAllocation(page, out var allocationBase, out var allocation))
{
var committed = allocation.CommittedPages.Contains(page);
var runEnd = page + Page;
while (runEnd < allocationBase + allocation.Size &&
allocation.CommittedPages.Contains(runEnd) == committed)
{
runEnd += Page;
}
info = new HostRegionInfo(
page,
allocationBase,
runEnd - page,
committed ? HostRegionState.Committed : HostRegionState.Reserved,
0,
committed ? HostPageProtection.ReadWrite : HostPageProtection.NoAccess,
0,
0);
return true;
}
var freeEnd = ulong.MaxValue;
foreach (var existingBase in _allocations.Keys)
{
if (existingBase > page)
{
freeEnd = existingBase;
break;
}
}
info = new HostRegionInfo(
page,
0,
freeEnd - page,
HostRegionState.Free,
0,
HostPageProtection.NoAccess,
0,
0);
return true;
}
public void FlushInstructionCache(ulong address, ulong size)
{
}
private bool TryFindAllocation(
ulong address,
out ulong allocationBase,
out (ulong Size, SortedSet<ulong> CommittedPages) allocation)
{
foreach (var (existingBase, existing) in _allocations)
{
if (address >= existingBase && address < existingBase + existing.Size)
{
allocationBase = existingBase;
allocation = existing;
return true;
}
}
allocationBase = 0;
allocation = default;
return false;
}
private static ulong AlignDown(ulong value, ulong alignment) => value & ~(alignment - 1);
private static ulong AlignUp(ulong value, ulong alignment) => (value + alignment - 1) & ~(alignment - 1);
}
private sealed class LazyHostMemory(ulong address) : IHostMemory, IDisposable
{
public bool CommitSucceeds { get; set; } = true;