mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-03 16:39:51 +08:00
[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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user