Files
whitemagic/WhiteMagicTest/Native/NativeSurfaceTests.cs
T
2026-07-21 22:30:10 +02:00

113 lines
4.0 KiB
C#

using System.Runtime.InteropServices;
using WhiteMagic.Native;
namespace WhiteMagicTest.Native;
/// <summary>
/// Integration tests that exercise the P/Invoke surface against the current
/// process. They prove the marshalling signatures are correct end-to-end.
/// </summary>
public class NativeSurfaceTests
{
private static SafeMemoryHandle OpenSelf(ProcessAccess access)
{
SafeMemoryHandle handle = NativeMethods.OpenProcess(access, false, Environment.ProcessId);
Assert.False(handle.IsInvalid, $"OpenProcess failed: {Marshal.GetLastPInvokeError()}");
return handle;
}
[Fact]
public void OpenProcess_on_self_returns_valid_handle_and_closes_on_dispose()
{
SafeMemoryHandle handle = OpenSelf(ProcessAccess.QueryInformation);
Assert.False(handle.IsClosed);
handle.Dispose();
Assert.True(handle.IsClosed);
}
[Fact]
public void ReadProcessMemory_reads_a_known_value_from_own_memory()
{
int value = 0x1BADB002;
GCHandle pin = GCHandle.Alloc(value, GCHandleType.Pinned);
try
{
using SafeMemoryHandle handle = OpenSelf(ProcessAccess.VmRead | ProcessAccess.QueryInformation);
Span<byte> buffer = stackalloc byte[sizeof(int)];
bool ok = NativeMethods.ReadProcessMemory(
handle, pin.AddrOfPinnedObject(), buffer, buffer.Length, out nint read);
Assert.True(ok, $"ReadProcessMemory failed: {Marshal.GetLastPInvokeError()}");
Assert.Equal(sizeof(int), (int)read);
Assert.Equal(value, BitConverter.ToInt32(buffer));
}
finally
{
pin.Free();
}
}
[Fact]
public void WriteProcessMemory_writes_a_value_into_own_memory()
{
int slot = 0;
GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned);
try
{
using SafeMemoryHandle handle = OpenSelf(
ProcessAccess.VmWrite | ProcessAccess.VmOperation | ProcessAccess.QueryInformation);
ReadOnlySpan<byte> payload = BitConverter.GetBytes(0x5EED);
bool ok = NativeMethods.WriteProcessMemory(
handle, pin.AddrOfPinnedObject(), payload, payload.Length, out nint written);
Assert.True(ok, $"WriteProcessMemory failed: {Marshal.GetLastPInvokeError()}");
Assert.Equal(payload.Length, (int)written);
Assert.Equal(0x5EED, Marshal.ReadInt32(pin.AddrOfPinnedObject()));
}
finally
{
pin.Free();
}
}
[Fact]
public void VirtualAllocEx_commits_then_protects_then_frees()
{
using SafeMemoryHandle handle = OpenSelf(ProcessAccess.VmOperation | ProcessAccess.QueryInformation);
IntPtr region = NativeMethods.VirtualAllocEx(
handle, IntPtr.Zero, 0x1000,
MemoryAllocationType.Commit | MemoryAllocationType.Reserve,
MemoryProtectionType.ReadWrite);
Assert.NotEqual(IntPtr.Zero, region);
bool protect = NativeMethods.VirtualProtectEx(
handle, region, 0x1000, MemoryProtectionType.ExecuteReadWrite, out MemoryProtectionType old);
Assert.True(protect, $"VirtualProtectEx failed: {Marshal.GetLastPInvokeError()}");
Assert.Equal(MemoryProtectionType.ReadWrite, old);
bool free = NativeMethods.VirtualFreeEx(handle, region, 0, MemoryFreeType.Release);
Assert.True(free, $"VirtualFreeEx failed: {Marshal.GetLastPInvokeError()}");
}
[Fact]
public void LoadLibrary_then_GetProcAddress_resolves_an_export()
{
IntPtr module = NativeMethods.LoadLibrary("kernel32.dll");
Assert.NotEqual(IntPtr.Zero, module);
IntPtr proc = NativeMethods.GetProcAddress(module, "CloseHandle");
Assert.NotEqual(IntPtr.Zero, proc);
}
[Theory]
[InlineData(typeof(Context32), 716)]
[InlineData(typeof(Context64), 1232)]
public void Thread_context_struct_has_the_exact_native_size(Type contextType, int expectedSize)
{
Assert.Equal(expectedSize, Marshal.SizeOf(contextType));
}
}