246 lines
6.7 KiB
C#
246 lines
6.7 KiB
C#
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using WhiteMagic;
|
|
using WhiteMagic.Native;
|
|
|
|
namespace WhiteMagicTest;
|
|
|
|
/// <summary>
|
|
/// Tests for <see cref="MemoryBase"/> abstract contract and <see cref="ExternalReader"/>
|
|
/// round-trip (Read<T>/Write<T>, arrays) using the current process as target.
|
|
/// </summary>
|
|
public class MemoryBaseTests
|
|
{
|
|
private static ExternalReader OpenSelf()
|
|
{
|
|
return new ExternalReader(
|
|
Process.GetCurrentProcess(),
|
|
ProcessAccess.VmRead | ProcessAccess.VmWrite | ProcessAccess.VmOperation | ProcessAccess.QueryInformation);
|
|
}
|
|
|
|
[Fact]
|
|
public void ImageBase_is_nonzero_for_self()
|
|
{
|
|
using var reader = OpenSelf();
|
|
Assert.NotEqual(IntPtr.Zero, reader.ImageBase);
|
|
}
|
|
|
|
[Fact]
|
|
public void Read_int_writes_and_reads_back()
|
|
{
|
|
using var reader = OpenSelf();
|
|
|
|
int slot = 0;
|
|
GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned);
|
|
try
|
|
{
|
|
IntPtr addr = pin.AddrOfPinnedObject();
|
|
Assert.True(reader.Write(addr, 0x1BADB002));
|
|
Assert.Equal(0x1BADB002, reader.Read<int>(addr));
|
|
}
|
|
finally
|
|
{
|
|
pin.Free();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Read_byte_writes_and_reads_back()
|
|
{
|
|
using var reader = OpenSelf();
|
|
byte slot = 0;
|
|
GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned);
|
|
try
|
|
{
|
|
IntPtr addr = pin.AddrOfPinnedObject();
|
|
Assert.True(reader.Write(addr, (byte)0xAB));
|
|
Assert.Equal(0xAB, reader.Read<byte>(addr));
|
|
}
|
|
finally
|
|
{
|
|
pin.Free();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Read_long_writes_and_reads_back()
|
|
{
|
|
using var reader = OpenSelf();
|
|
long slot = 0;
|
|
GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned);
|
|
try
|
|
{
|
|
IntPtr addr = pin.AddrOfPinnedObject();
|
|
Assert.True(reader.Write(addr, unchecked((long)0xDEADBEEF_CAFEBABE)));
|
|
Assert.Equal(unchecked((long)0xDEADBEEF_CAFEBABE), reader.Read<long>(addr));
|
|
}
|
|
finally
|
|
{
|
|
pin.Free();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Read_blittable_struct_writes_and_reads_back()
|
|
{
|
|
using var reader = OpenSelf();
|
|
var slot = new TestStruct { X = 42, Y = 99 };
|
|
GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned);
|
|
try
|
|
{
|
|
IntPtr addr = pin.AddrOfPinnedObject();
|
|
Assert.True(reader.Write(addr, new TestStruct { X = 100, Y = 200 }));
|
|
var result = reader.Read<TestStruct>(addr);
|
|
Assert.Equal(100, result.X);
|
|
Assert.Equal(200, result.Y);
|
|
}
|
|
finally
|
|
{
|
|
pin.Free();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Read_bytes_writes_and_reads_back()
|
|
{
|
|
using var reader = OpenSelf();
|
|
byte[] buffer = new byte[16];
|
|
GCHandle pin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
|
try
|
|
{
|
|
IntPtr addr = pin.AddrOfPinnedObject();
|
|
byte[] expected = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
|
|
|
|
int written = reader.WriteBytes(addr, expected);
|
|
Assert.Equal(expected.Length, written);
|
|
|
|
byte[] actual = reader.ReadBytes(addr, expected.Length);
|
|
Assert.Equal(expected, actual);
|
|
}
|
|
finally
|
|
{
|
|
pin.Free();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Read_int_array_writes_and_reads_back()
|
|
{
|
|
using var reader = OpenSelf();
|
|
int[] buffer = new int[4];
|
|
GCHandle pin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
|
try
|
|
{
|
|
IntPtr addr = pin.AddrOfPinnedObject();
|
|
int[] expected = [10, 20, 30, 40];
|
|
|
|
Assert.True(reader.Write(addr, expected));
|
|
int[] actual = reader.Read<int>(addr, 4);
|
|
Assert.Equal(expected, actual);
|
|
}
|
|
finally
|
|
{
|
|
pin.Free();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Read_struct_array_writes_and_reads_back()
|
|
{
|
|
using var reader = OpenSelf();
|
|
var buffer = new TestStruct[4];
|
|
GCHandle pin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
|
try
|
|
{
|
|
IntPtr addr = pin.AddrOfPinnedObject();
|
|
var expected = new[]
|
|
{
|
|
new TestStruct { X = 1, Y = 2 },
|
|
new TestStruct { X = 3, Y = 4 },
|
|
new TestStruct { X = 5, Y = 6 },
|
|
new TestStruct { X = 7, Y = 8 },
|
|
};
|
|
|
|
Assert.True(reader.Write(addr, expected));
|
|
var actual = reader.Read<TestStruct>(addr, 4);
|
|
Assert.Equal(expected, actual);
|
|
}
|
|
finally
|
|
{
|
|
pin.Free();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Write_returns_false_for_invalid_address()
|
|
{
|
|
using var reader = OpenSelf();
|
|
Assert.False(reader.Write(IntPtr.Zero, 42));
|
|
}
|
|
|
|
[Fact]
|
|
public void Dispose_closes_handle()
|
|
{
|
|
var reader = OpenSelf();
|
|
Assert.False(reader.Handle.IsClosed);
|
|
reader.Dispose();
|
|
Assert.True(reader.Handle.IsClosed);
|
|
}
|
|
|
|
[Fact]
|
|
public void Double_dispose_does_not_throw()
|
|
{
|
|
var reader = OpenSelf();
|
|
reader.Dispose();
|
|
reader.Dispose();
|
|
}
|
|
|
|
// ── Graceful failure on invalid addresses ───────────────────────────────
|
|
|
|
[Fact]
|
|
public void Read_int_on_invalid_address_returns_default()
|
|
{
|
|
using var reader = OpenSelf();
|
|
Assert.Equal(0, reader.Read<int>(IntPtr.Zero));
|
|
}
|
|
|
|
[Fact]
|
|
public void Read_struct_on_invalid_address_returns_default()
|
|
{
|
|
using var reader = OpenSelf();
|
|
var result = reader.Read<TestStruct>(IntPtr.Zero);
|
|
Assert.Equal(0, result.X);
|
|
Assert.Equal(0, result.Y);
|
|
}
|
|
|
|
[Fact]
|
|
public void Read_int_array_on_invalid_address_returns_empty()
|
|
{
|
|
using var reader = OpenSelf();
|
|
Assert.Empty(reader.Read<int>(IntPtr.Zero, 10));
|
|
}
|
|
|
|
[Fact]
|
|
public void Read_bytes_on_invalid_address_returns_empty()
|
|
{
|
|
using var reader = OpenSelf();
|
|
Assert.Empty(reader.ReadBytes(IntPtr.Zero, 10));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A simple blittable struct for use in tests.
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct TestStruct : IEquatable<TestStruct>
|
|
{
|
|
public int X;
|
|
public int Y;
|
|
|
|
public bool Equals(TestStruct other) => X == other.X && Y == other.Y;
|
|
public override bool Equals(object? obj) => obj is TestStruct other && Equals(other);
|
|
public override int GetHashCode() => HashCode.Combine(X, Y);
|
|
public override string ToString() => $"({X}, {Y})";
|
|
}
|