mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 10:56:20 +08:00
Fix macOS fixed-address allocation collisions (#246)
This commit is contained in:
@@ -163,13 +163,18 @@ internal sealed unsafe class PosixHostMemory : IHostMemory
|
|||||||
private const int PROT_EXEC = 0x4;
|
private const int PROT_EXEC = 0x4;
|
||||||
|
|
||||||
private const int MAP_PRIVATE = 0x02;
|
private const int MAP_PRIVATE = 0x02;
|
||||||
private const int MAP_FIXED = 0x10;
|
|
||||||
private static readonly int MAP_ANON = OperatingSystem.IsMacOS() ? 0x1000 : 0x20;
|
private static readonly int MAP_ANON = OperatingSystem.IsMacOS() ? 0x1000 : 0x20;
|
||||||
private static readonly int MAP_NORESERVE = OperatingSystem.IsMacOS() ? 0 : 0x4000;
|
private static readonly int MAP_NORESERVE = OperatingSystem.IsMacOS() ? 0 : 0x4000;
|
||||||
|
|
||||||
// Linux-only: fail instead of clobbering an existing mapping.
|
// Linux-only: fail instead of clobbering an existing mapping.
|
||||||
private const int MAP_FIXED_NOREPLACE = 0x100000;
|
private const int MAP_FIXED_NOREPLACE = 0x100000;
|
||||||
|
|
||||||
|
private const int KERN_SUCCESS = 0;
|
||||||
|
|
||||||
|
// On Darwin fixed placement is represented by the absence of
|
||||||
|
// VM_FLAGS_ANYWHERE. Do not add VM_FLAGS_OVERWRITE: overlap must fail.
|
||||||
|
private const int VM_FLAGS_FIXED = 0;
|
||||||
|
|
||||||
private static readonly nint MAP_FAILED = -1;
|
private static readonly nint MAP_FAILED = -1;
|
||||||
|
|
||||||
private static readonly object Gate = new();
|
private static readonly object Gate = new();
|
||||||
@@ -181,6 +186,7 @@ internal sealed unsafe class PosixHostMemory : IHostMemory
|
|||||||
public ulong Size;
|
public ulong Size;
|
||||||
public uint DefaultProtect;
|
public uint DefaultProtect;
|
||||||
public Dictionary<ulong, uint>? PageProtects;
|
public Dictionary<ulong, uint>? PageProtects;
|
||||||
|
public bool UsesMachAllocation;
|
||||||
|
|
||||||
public ulong End => Base + Size;
|
public ulong End => Base + Size;
|
||||||
|
|
||||||
@@ -251,6 +257,7 @@ internal sealed unsafe class PosixHostMemory : IHostMemory
|
|||||||
}
|
}
|
||||||
|
|
||||||
nint result;
|
nint result;
|
||||||
|
var usesMachAllocation = false;
|
||||||
if (address != null)
|
if (address != null)
|
||||||
{
|
{
|
||||||
// Win32 maps at exactly the requested address or fails
|
// Win32 maps at exactly the requested address or fails
|
||||||
@@ -280,15 +287,17 @@ internal sealed unsafe class PosixHostMemory : IHostMemory
|
|||||||
if (result == MAP_FAILED && OperatingSystem.IsMacOS())
|
if (result == MAP_FAILED && OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
// The hint-only attempt above didn't land at the requested
|
// The hint-only attempt above didn't land at the requested
|
||||||
// address. This is routinely the case for the PS5's fixed
|
// address. mach_vm_allocate with fixed placement and no
|
||||||
// 32 GiB image base under Rosetta 2 on Apple Silicon, where
|
// overwrite flag atomically maps the requested range or
|
||||||
// the kernel never honors that hint. Retry with true
|
// fails if any host mapping already owns it. Unlike
|
||||||
// MAP_FIXED: this can clobber an untracked host mapping
|
// MAP_FIXED, it cannot clobber CLR, dyld, JIT, or Rosetta
|
||||||
// (dyld, the runtime's JIT heap, Rosetta) if one already
|
// memory that is absent from our shadow table.
|
||||||
// sits exactly there, but without it guest images that
|
Trace($"exact mmap hint failed, retrying with fixed Mach allocation: addr=0x{(ulong)address:X16}");
|
||||||
// require this base never load at all on macOS.
|
result = AllocateDarwinFixed(
|
||||||
Trace($"exact mmap hint failed, retrying with MAP_FIXED: addr=0x{(ulong)address:X16}");
|
(nint)address,
|
||||||
result = mmap((nint)address, (nuint)alignedSize, posixProtect, flags | MAP_FIXED, -1, 0);
|
alignedSize,
|
||||||
|
posixProtect);
|
||||||
|
usesMachAllocation = result != MAP_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == MAP_FAILED || (ulong)result != (ulong)address)
|
if (result == MAP_FAILED || (ulong)result != (ulong)address)
|
||||||
@@ -316,7 +325,8 @@ internal sealed unsafe class PosixHostMemory : IHostMemory
|
|||||||
{
|
{
|
||||||
Base = (ulong)result,
|
Base = (ulong)result,
|
||||||
Size = alignedSize,
|
Size = alignedSize,
|
||||||
DefaultProtect = protect
|
DefaultProtect = protect,
|
||||||
|
UsesMachAllocation = usesMachAllocation,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (void*)result;
|
return (void*)result;
|
||||||
@@ -335,8 +345,15 @@ internal sealed unsafe class PosixHostMemory : IHostMemory
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Regions.Remove((ulong)address);
|
var released = region.UsesMachAllocation
|
||||||
return munmap((nint)address, (nuint)region.Size) == 0;
|
? mach_vm_deallocate(mach_task_self(), (ulong)address, region.Size) == KERN_SUCCESS
|
||||||
|
: munmap((nint)address, (nuint)region.Size) == 0;
|
||||||
|
if (released)
|
||||||
|
{
|
||||||
|
Regions.Remove((ulong)address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return released;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,6 +521,40 @@ internal sealed unsafe class PosixHostMemory : IHostMemory
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static nint AllocateDarwinFixed(nint requestedAddress, ulong size, int posixProtect)
|
||||||
|
{
|
||||||
|
var address = (ulong)requestedAddress;
|
||||||
|
var result = mach_vm_allocate(
|
||||||
|
mach_task_self(),
|
||||||
|
ref address,
|
||||||
|
size,
|
||||||
|
VM_FLAGS_FIXED);
|
||||||
|
if (result != KERN_SUCCESS || address != (ulong)requestedAddress)
|
||||||
|
{
|
||||||
|
if (result == KERN_SUCCESS)
|
||||||
|
{
|
||||||
|
_ = mach_vm_deallocate(mach_task_self(), address, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace(
|
||||||
|
$"fixed Mach allocation failed: addr=0x{(ulong)requestedAddress:X16} " +
|
||||||
|
$"size=0x{size:X} kern_return={result}");
|
||||||
|
return MAP_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mprotect((nint)address, (nuint)size, posixProtect) == 0)
|
||||||
|
{
|
||||||
|
return (nint)address;
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = Marshal.GetLastPInvokeError();
|
||||||
|
_ = mach_vm_deallocate(mach_task_self(), address, size);
|
||||||
|
Trace(
|
||||||
|
$"fixed Mach allocation protection failed: addr=0x{address:X16} " +
|
||||||
|
$"size=0x{size:X} errno={error}");
|
||||||
|
return MAP_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
private static void Trace(string message)
|
private static void Trace(string message)
|
||||||
{
|
{
|
||||||
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_VMEM"), "1", StringComparison.Ordinal))
|
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_VMEM"), "1", StringComparison.Ordinal))
|
||||||
@@ -524,5 +575,14 @@ internal sealed unsafe class PosixHostMemory : IHostMemory
|
|||||||
|
|
||||||
[DllImport("libc", SetLastError = true)]
|
[DllImport("libc", SetLastError = true)]
|
||||||
private static extern int mprotect(nint addr, nuint length, int prot);
|
private static extern int mprotect(nint addr, nuint length, int prot);
|
||||||
|
|
||||||
|
[DllImport("libSystem.B.dylib")]
|
||||||
|
private static extern uint mach_task_self();
|
||||||
|
|
||||||
|
[DllImport("libSystem.B.dylib")]
|
||||||
|
private static extern int mach_vm_allocate(uint target, ref ulong address, ulong size, int flags);
|
||||||
|
|
||||||
|
[DllImport("libSystem.B.dylib")]
|
||||||
|
private static extern int mach_vm_deallocate(uint target, ulong address, ulong size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<InternalsVisibleTo Include="SharpEmu.Core" />
|
<InternalsVisibleTo Include="SharpEmu.Core" />
|
||||||
|
<InternalsVisibleTo Include="SharpEmu.Libs.Tests" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using SharpEmu.HLE.Host;
|
||||||
|
using SharpEmu.HLE.Host.Posix;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.Tests.Memory;
|
||||||
|
|
||||||
|
public sealed unsafe class PosixHostMemoryTests
|
||||||
|
{
|
||||||
|
private const int ProtRead = 0x1;
|
||||||
|
private const int ProtWrite = 0x2;
|
||||||
|
private const int MapPrivate = 0x02;
|
||||||
|
private const int MapAnonymousDarwin = 0x1000;
|
||||||
|
private static readonly nint MapFailed = -1;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExactAllocationDoesNotReplaceUntrackedDarwinMapping()
|
||||||
|
{
|
||||||
|
if (!OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = checked((nuint)Environment.SystemPageSize);
|
||||||
|
var externalMapping = mmap(
|
||||||
|
0,
|
||||||
|
size,
|
||||||
|
ProtRead | ProtWrite,
|
||||||
|
MapPrivate | MapAnonymousDarwin,
|
||||||
|
-1,
|
||||||
|
0);
|
||||||
|
Assert.NotEqual(MapFailed, externalMapping);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sentinel = new Span<byte>((void*)externalMapping, checked((int)size));
|
||||||
|
sentinel.Fill(0xA5);
|
||||||
|
|
||||||
|
var hostMemory = new PosixHostMemory();
|
||||||
|
var result = hostMemory.Allocate(
|
||||||
|
(ulong)externalMapping,
|
||||||
|
(ulong)size,
|
||||||
|
HostPageProtection.ReadWrite);
|
||||||
|
|
||||||
|
Assert.Equal(0UL, result);
|
||||||
|
Assert.All(sentinel.ToArray(), value => Assert.Equal(0xA5, value));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Assert.Equal(0, munmap(externalMapping, size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("libc", SetLastError = true)]
|
||||||
|
private static extern nint mmap(nint addr, nuint length, int prot, int flags, int fd, long offset);
|
||||||
|
|
||||||
|
[DllImport("libc", SetLastError = true)]
|
||||||
|
private static extern int munmap(nint addr, nuint length);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user