fix 7 correctness and cleanup issues

Blocking fixes:
1. Read<T> now returns default(T) on failed/partial read instead of crash
   (applies to single Read<T>, array Read<T>, and ReadBytes)
2. InProcessReader uses ReadProcessMemory via handle instead of unsafe
   Buffer.MemoryCopy — fails soft on bad address instead of AV'ing
3. GetRelative now returns absolute - ImageBase (inverse of GetAbsolute).
   Fix round-trip test to validate at arbitrary offsets, not just ImageBase
4. ReadString reads in 64-byte chunks with encoding-aware null-terminator
   pattern matching (handles UTF-16's 2-byte null, UTF-32's 4-byte null)

Cleanup:
5. InProcessReader validates handle on open and uses RPM through it
   (handle is no longer unused)
6. Array marshal read: pin raw buffer once, PtrToStructure at offset
7. StructureToByteArray(Span) delegates to byte[] overload, no duplicate

New tests: 4 invalid-address grace tests (returns default/empty/false).
All 57 passing.
This commit is contained in:
kbe
2026-07-21 19:24:18 +02:00
parent 6eb78e7974
commit 8374650aac
4 changed files with 182 additions and 63 deletions
+27 -13
View File
@@ -7,6 +7,8 @@ namespace WhiteMagicTest;
/// <summary>
/// Tests for relative/absolute addressing in <see cref="MemoryBase"/>.
/// GetAbsolute(relative) = ImageBase + relative.
/// GetRelative(absolute) = absolute - ImageBase (inverse of GetAbsolute).
/// </summary>
public class AddressingTests
{
@@ -27,31 +29,45 @@ public class AddressingTests
}
[Fact]
public void GetRelative_computes_offset_from_image_base()
public void GetRelative_returns_absolute_minus_image_base()
{
using var reader = OpenSelf();
IntPtr imageBase = reader.ImageBase;
IntPtr absolute = imageBase + 0x2000;
IntPtr relative = reader.GetRelative(absolute);
// GetRelative returns ImageBase - absolute (GreyMagic convention)
Assert.Equal((IntPtr)((int)imageBase - (int)absolute), relative);
Assert.Equal((IntPtr)((nint)absolute - (nint)imageBase), relative);
}
[Fact]
public void GetAbsolute_after_GetRelative_at_image_base_returns_to_base()
public void GetAbsolute_and_GetRelative_are_inverses()
{
using var reader = OpenSelf();
IntPtr atBase = reader.ImageBase;
IntPtr relative = reader.GetRelative(atBase);
IntPtr back = reader.GetAbsolute(relative);
Assert.Equal(atBase, back);
IntPtr offset = (IntPtr)0x3000;
// Round-trip: offset -> absolute -> back to offset
IntPtr absolute = reader.GetAbsolute(offset);
IntPtr back = reader.GetRelative(absolute);
Assert.Equal(offset, back);
// Reverse round-trip: absolute -> offset -> back to absolute
IntPtr relative = reader.GetRelative(absolute);
IntPtr absoluteAgain = reader.GetAbsolute(relative);
Assert.Equal(absolute, absoluteAgain);
}
[Fact]
public void GetRelative_on_ImageBase_returns_zero()
{
using var reader = OpenSelf();
IntPtr relative = reader.GetRelative(reader.ImageBase);
Assert.Equal(IntPtr.Zero, relative);
}
[Fact]
public void Read_with_isRelative_true_uses_image_base()
{
using var reader = OpenSelf();
// Read the first byte at ImageBase (should be MZ header: 0x4D = 'M')
// DOS header 'MZ' at the image base
byte firstByte = reader.Read<byte>(IntPtr.Zero, isRelative: true);
Assert.Equal(0x4D, firstByte);
}
@@ -65,10 +81,9 @@ public class AddressingTests
try
{
IntPtr absolute = pin.AddrOfPinnedObject();
nint relative = (nint)absolute - (nint)reader.ImageBase;
IntPtr relativePtr = (IntPtr)relative;
IntPtr relative = reader.GetRelative(absolute);
Assert.True(reader.Write(relativePtr, 42, isRelative: true));
Assert.True(reader.Write(relative, 42, isRelative: true));
Assert.Equal(42, reader.Read<int>(absolute));
}
finally
@@ -81,7 +96,6 @@ public class AddressingTests
public void ReadBytes_with_isRelative_true_resolves_correctly()
{
using var reader = OpenSelf();
// DOS header 'MZ' at the image base
byte[] data = reader.ReadBytes(IntPtr.Zero, 2, isRelative: true);
Assert.Equal(0x4D, data[0]);
Assert.Equal(0x5A, data[1]);