155 lines
5.9 KiB
C#
155 lines
5.9 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using SharpEmu.Core.Cpu.Native;
|
|
using SharpEmu.HLE;
|
|
using SharpEmu.HLE.Host;
|
|
using Xunit;
|
|
|
|
namespace SharpEmu.Libs.Tests.Cpu;
|
|
|
|
public sealed class NativeDiagnosticMemoryReadTests
|
|
{
|
|
private static readonly MethodInfo TryReadDiagnosticHostQword =
|
|
typeof(DirectExecutionBackend).GetMethod(
|
|
"TryReadDiagnosticHostQword",
|
|
BindingFlags.NonPublic | BindingFlags.Static)!;
|
|
|
|
private static readonly MethodInfo TryReadHostBytes =
|
|
typeof(DirectExecutionBackend).GetMethod(
|
|
"TryReadHostBytes",
|
|
BindingFlags.NonPublic | BindingFlags.Static)!;
|
|
|
|
[Fact]
|
|
public void DiagnosticReadsRejectNonCanonicalAddresses()
|
|
{
|
|
object?[] qwordArguments = [0x3120_2C30_202C_3120UL, 0UL];
|
|
Assert.False((bool)TryReadDiagnosticHostQword.Invoke(null, qwordArguments)!);
|
|
|
|
var destination = new byte[16];
|
|
Assert.False((bool)TryReadHostBytes.Invoke(
|
|
null,
|
|
[0x3120_2C30_202C_3120UL, destination])!);
|
|
}
|
|
|
|
[Fact]
|
|
public void MacOsDiagnosticReadsRespectNativePageProtection()
|
|
{
|
|
if (!OperatingSystem.IsMacOS() || RuntimeInformation.ProcessArchitecture != Architecture.X64) return;
|
|
|
|
var memory = HostPlatform.Current.Memory;
|
|
var size = (ulong)Environment.SystemPageSize;
|
|
var address = memory.Allocate(0, size, HostPageProtection.ReadWrite);
|
|
Assert.NotEqual(0UL, address);
|
|
try
|
|
{
|
|
const long expected = 0x1122334455667788;
|
|
Marshal.WriteInt64((nint)address, expected);
|
|
var bytes = new byte[8];
|
|
Assert.True((bool)TryReadHostBytes.Invoke(null, [address, bytes])!);
|
|
Assert.Equal(BitConverter.GetBytes(expected), bytes);
|
|
|
|
Assert.True(memory.Protect(address, size, HostPageProtection.NoAccess, out _));
|
|
Assert.False((bool)TryReadHostBytes.Invoke(null, [address, bytes])!, "byte read accepted a protected page");
|
|
object?[] arguments = [address, 0UL];
|
|
Assert.False((bool)TryReadDiagnosticHostQword.Invoke(null, arguments)!, "qword read accepted a protected page");
|
|
Assert.Equal(0UL, arguments[1]);
|
|
}
|
|
finally
|
|
{
|
|
Assert.True(memory.Free(address));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void DiagnosticReadsCopyCommittedMemory()
|
|
{
|
|
var source = Marshal.AllocHGlobal(16);
|
|
try
|
|
{
|
|
const long expected = 0x1122_3344_5566_7788;
|
|
Marshal.WriteInt64(source, expected);
|
|
|
|
object?[] qwordArguments = [(ulong)source, 0UL];
|
|
Assert.True((bool)TryReadDiagnosticHostQword.Invoke(null, qwordArguments)!);
|
|
Assert.Equal((ulong)expected, qwordArguments[1]);
|
|
|
|
var destination = new byte[8];
|
|
Assert.True((bool)TryReadHostBytes.Invoke(
|
|
null,
|
|
[(ulong)source, destination])!);
|
|
Assert.Equal(BitConverter.GetBytes(expected), destination);
|
|
}
|
|
finally
|
|
{
|
|
Marshal.FreeHGlobal(source);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(HostMemory.PAGE_NOACCESS)]
|
|
[InlineData(HostMemory.PAGE_READWRITE | 0x100u)]
|
|
public unsafe void WindowsDiagnosticReadsRejectAnUnreadableTrailingPage(uint protection)
|
|
{
|
|
if (!OperatingSystem.IsWindows()) return;
|
|
var pageSize = (nuint)Environment.SystemPageSize;
|
|
var memory = (byte*)HostMemory.Alloc(null, pageSize * 2, 0x3000, HostMemory.PAGE_READWRITE);
|
|
Assert.NotEqual(0, (nint)memory);
|
|
try
|
|
{
|
|
var address = (ulong)(memory + pageSize - 4);
|
|
var destination = new byte[16];
|
|
Assert.True((bool)TryReadHostBytes.Invoke(null, [address, destination])!);
|
|
Assert.True(HostMemory.Protect(memory + pageSize, pageSize, protection, out _));
|
|
Assert.False((bool)TryReadHostBytes.Invoke(null, [address, destination])!);
|
|
object?[] arguments = [address, 0UL];
|
|
Assert.False((bool)TryReadDiagnosticHostQword.Invoke(null, arguments)!);
|
|
Assert.Equal(0UL, arguments[1]);
|
|
Assert.NotEqual((nuint)0, HostMemory.Query(memory + pageSize, out var region));
|
|
Assert.Equal(protection, region.Protect);
|
|
}
|
|
finally
|
|
{
|
|
Assert.True(HostMemory.Free(memory, 0, HostMemory.MEM_RELEASE));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public unsafe void LinuxDiagnosticReadsRejectAnUnreadableTrailingPage()
|
|
{
|
|
if (!OperatingSystem.IsLinux()) return;
|
|
var pageSize = (nuint)Environment.SystemPageSize;
|
|
var memory = (byte*)HostMemory.Alloc(null, pageSize * 2, 0x3000, HostMemory.PAGE_READWRITE);
|
|
Assert.NotEqual(0, (nint)memory);
|
|
try
|
|
{
|
|
var address = (ulong)(memory + pageSize - 4);
|
|
Assert.True((bool)TryReadHostBytes.Invoke(null, [address, new byte[16]])!);
|
|
Assert.True(HostMemory.Protect(memory + pageSize, pageSize, HostMemory.PAGE_NOACCESS, out _));
|
|
Assert.False((bool)TryReadHostBytes.Invoke(null, [address, new byte[16]])!);
|
|
object?[] arguments = [address, 0UL];
|
|
Assert.False((bool)TryReadDiagnosticHostQword.Invoke(null, arguments)!);
|
|
Assert.Equal(0UL, arguments[1]);
|
|
}
|
|
finally
|
|
{
|
|
Assert.True(HostMemory.Free(memory, 0, HostMemory.MEM_RELEASE));
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0UL)]
|
|
[InlineData(0xFFF0UL)]
|
|
[InlineData(0x0000_7FFF_FFFF_FFF8UL)]
|
|
[InlineData(ulong.MaxValue - 7)]
|
|
public void DiagnosticReadsRejectInvalidRanges(ulong address)
|
|
{
|
|
Assert.False((bool)TryReadHostBytes.Invoke(null, [address, new byte[16]])!);
|
|
object?[] arguments = [address, 0UL];
|
|
Assert.False((bool)TryReadDiagnosticHostQword.Invoke(null, arguments)!);
|
|
Assert.Equal(0UL, arguments[1]);
|
|
}
|
|
}
|