ARchive old spec
This commit is contained in:
@@ -4,9 +4,11 @@ using WhiteMagic;
|
||||
namespace WhiteMagicTest;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="InProcessReader"/> — direct pointer dereference against
|
||||
/// the own process. Verifies the shared <see cref="MemoryBase"/> API works for
|
||||
/// both external and in-process readers.
|
||||
/// Tests for <see cref="InProcessReader"/>. The current implementation uses
|
||||
/// <c>ReadProcessMemory</c>/<c>WriteProcessMemory</c> on a self-handle (per the D1
|
||||
/// revision — unsafe direct-pointer dereference was rejected because .NET cannot
|
||||
/// catch <see cref="AccessViolationException"/>). Verifies the shared
|
||||
/// <see cref="MemoryBase"/> API works for both external and in-process readers.
|
||||
/// </summary>
|
||||
public class InProcessReaderTests
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using WhiteMagic;
|
||||
|
||||
@@ -5,7 +6,7 @@ namespace WhiteMagicTest;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="MarshalCache{T}"/>: blittable size, marshal-required flag,
|
||||
/// IsIntPtr, and computed-once behavior.
|
||||
/// and computed-once behavior.
|
||||
/// </summary>
|
||||
public class MarshalCacheTests
|
||||
{
|
||||
@@ -45,12 +46,33 @@ public class MarshalCacheTests
|
||||
Assert.Equal(8, MarshalCache<BlittableStruct>.Size);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Size_for_struct_with_bool_is_managed_layout_width()
|
||||
{
|
||||
// Regression for the Marshal.SizeOf / Unsafe.SizeOf disagreement on a struct
|
||||
// whose only field is `bool`: Marshal reports 4 bytes (Win32 BOOL default marshaling),
|
||||
// but the blittable path (MemoryMarshal.Read<T>) actually lays out a bool as 1 byte.
|
||||
// MarshalCache.Size must match what the reader actually touches.
|
||||
Assert.Equal(Unsafe.SizeOf<SingleBoolStruct>(), MarshalCache<SingleBoolStruct>.Size);
|
||||
Assert.Equal(1, MarshalCache<SingleBoolStruct>.Size);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Size_for_struct_with_bools_in_sequence_matches_managed_layout()
|
||||
{
|
||||
// Sequential struct { bool, bool } — managed width is 2, Marshal width is 8 (two BOOLs).
|
||||
// The blittable path uses 1 byte per bool, so Size must equal the managed width.
|
||||
Assert.Equal(Unsafe.SizeOf<SequentialBoolStruct>(), MarshalCache<SequentialBoolStruct>.Size);
|
||||
Assert.Equal(2, MarshalCache<SequentialBoolStruct>.Size);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TypeRequiresMarshal_is_false_for_blittable_types()
|
||||
{
|
||||
Assert.False(MarshalCache<int>.TypeRequiresMarshal);
|
||||
Assert.False(MarshalCache<long>.TypeRequiresMarshal);
|
||||
Assert.False(MarshalCache<BlittableStruct>.TypeRequiresMarshal);
|
||||
Assert.False(MarshalCache<byte>.TypeRequiresMarshal);
|
||||
Assert.False(MarshalCache<IntPtr>.TypeRequiresMarshal);
|
||||
Assert.False(MarshalCache<IntPtr>.TypeRequiresMarshal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -60,39 +82,23 @@ public class MarshalCacheTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsIntPtr_is_true_for_IntPtr()
|
||||
public void TypeRequiresMarshal_is_true_for_reference_containing_types()
|
||||
{
|
||||
Assert.True(MarshalCache<IntPtr>.IsIntPtr);
|
||||
Assert.True(MarshalCache<string>.TypeRequiresMarshal);
|
||||
Assert.True(MarshalCache<ClassWithInt>.TypeRequiresMarshal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsIntPtr_is_false_for_non_IntPtr_types()
|
||||
{
|
||||
Assert.False(MarshalCache<int>.IsIntPtr);
|
||||
Assert.False(MarshalCache<long>.IsIntPtr);
|
||||
Assert.False(MarshalCache<BlittableStruct>.IsIntPtr);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void All_properties_are_computed_once_and_cached()
|
||||
public void Properties_are_computed_once_and_cached()
|
||||
{
|
||||
int size1 = MarshalCache<int>.Size;
|
||||
bool marshal1 = MarshalCache<int>.TypeRequiresMarshal;
|
||||
bool intPtr1 = MarshalCache<int>.IsIntPtr;
|
||||
|
||||
int size2 = MarshalCache<int>.Size;
|
||||
bool marshal2 = MarshalCache<int>.TypeRequiresMarshal;
|
||||
bool intPtr2 = MarshalCache<int>.IsIntPtr;
|
||||
|
||||
Assert.Equal(size1, size2);
|
||||
Assert.Equal(marshal1, marshal2);
|
||||
Assert.Equal(intPtr1, intPtr2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SizeU_matches_Size_as_uint()
|
||||
{
|
||||
Assert.Equal((uint)MarshalCache<int>.Size, MarshalCache<int>.SizeU);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
@@ -108,4 +114,22 @@ public class MarshalCacheTests
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] Data;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct SingleBoolStruct
|
||||
{
|
||||
public bool Flag;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct SequentialBoolStruct
|
||||
{
|
||||
public bool A;
|
||||
public bool B;
|
||||
}
|
||||
|
||||
private class ClassWithInt
|
||||
{
|
||||
public int Value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,13 +57,13 @@ public class NativeSurfaceTests
|
||||
{
|
||||
using SafeMemoryHandle handle = OpenSelf(
|
||||
ProcessAccess.VmWrite | ProcessAccess.VmOperation | ProcessAccess.QueryInformation);
|
||||
ReadOnlySpan<byte> payload = BitConverter.GetBytes(0x5EED);
|
||||
ReadOnlySpan<byte> bytes = BitConverter.GetBytes(0x5EED);
|
||||
|
||||
bool ok = NativeMethods.WriteProcessMemory(
|
||||
handle, pin.AddrOfPinnedObject(), payload, payload.Length, out nint written);
|
||||
handle, pin.AddrOfPinnedObject(), bytes, bytes.Length, out nint written);
|
||||
|
||||
Assert.True(ok, $"WriteProcessMemory failed: {Marshal.GetLastPInvokeError()}");
|
||||
Assert.Equal(payload.Length, (int)written);
|
||||
Assert.Equal(bytes.Length, (int)written);
|
||||
Assert.Equal(0x5EED, Marshal.ReadInt32(pin.AddrOfPinnedObject()));
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -90,7 +90,7 @@ public class StubAssemblerTests
|
||||
{
|
||||
uint ca=0x10000000u+5,r=0x12345678u-(ca+5);
|
||||
byte[] s=Create().BuildCallStub((IntPtr)0x10000000,(IntPtr)0x12345678,[0xCAFEBABE],4,CallConvention.Thiscall);
|
||||
Assert.Equal(11,s.Length); Assert.Equal(0xB9,s[0]); Assert.Equal(0xCAFEBABE,BitConverter.ToUInt32(s,1));
|
||||
Assert.Equal(11,s.Length); Assert.Equal(0xB9,s[0]); Assert.Equal(0xCAFEBABEu,BitConverter.ToUInt32(s,1));
|
||||
Assert.Equal(0xE8,s[5]); Assert.Equal(r,BitConverter.ToUInt32(s,6)); Assert.Equal(0xC3,s[10]);
|
||||
}
|
||||
|
||||
@@ -120,8 +120,8 @@ public class StubAssemblerTests
|
||||
{
|
||||
uint ca=0x10000000u+10,r=0x12345678u-(ca+5);
|
||||
byte[] s=Create().BuildCallStub((IntPtr)0x10000000,(IntPtr)0x12345678,[0xAAAAAAAA,0xBBBBBBBB],4,CallConvention.Fastcall);
|
||||
Assert.Equal(16,s.Length); Assert.Equal(0xB9,s[0]); Assert.Equal(0xAAAAAAAA,BitConverter.ToUInt32(s,1));
|
||||
Assert.Equal(0xBA,s[5]); Assert.Equal(0xBBBBBBBB,BitConverter.ToUInt32(s,6));
|
||||
Assert.Equal(16,s.Length); Assert.Equal(0xB9,s[0]); Assert.Equal(0xAAAAAAAAu,BitConverter.ToUInt32(s,1));
|
||||
Assert.Equal(0xBA,s[5]); Assert.Equal(0xBBBBBBBBu,BitConverter.ToUInt32(s,6));
|
||||
Assert.Equal(0xE8,s[10]); Assert.Equal(r,BitConverter.ToUInt32(s,11)); Assert.Equal(0xC3,s[15]);
|
||||
}
|
||||
|
||||
@@ -133,52 +133,207 @@ public class StubAssemblerTests
|
||||
Create().BuildCallStub((IntPtr)0x10000000,(IntPtr)0x12345678,[],4,CallConvention.Fastcall));
|
||||
}
|
||||
|
||||
// ── x64 ─────────────────────────────────────────────────────────────
|
||||
// ── x64 (Microsoft x64 ABI — shadow space + 16-byte alignment + 64-bit loads) ──
|
||||
//
|
||||
// Stub frame layout:
|
||||
// bytes 0..6 sub rsp, 0x20 (7 bytes — shadow space + realignment)
|
||||
// bytes 7..N mov r64, imm64 ... (10 bytes per reg move: 2-byte prefix + 8-byte imm)
|
||||
// mov rax, imm64 / mov [rsp+0x20+8*(i-4)], rax for stack args (15 bytes each)
|
||||
// E8 rel32 call target (5 bytes)
|
||||
// 48 81 C4 20 00... add rsp, 0x20 (7 bytes)
|
||||
// C3 ret (1 byte)
|
||||
//
|
||||
// Each `mov rNN, imm64` is 10 bytes regardless of the target register:
|
||||
// RCX REX.W 0xB9 + 8 imm (0x48 0xB9)
|
||||
// RDX REX.W 0xBA + 8 imm (0x48 0xBA)
|
||||
// R8 REX.WB 0xB8 + 8 imm (0x49 0xB8 — REX.W|R = 0x49)
|
||||
// R9 REX.WB 0xB9 + 8 imm (0x49 0xB9)
|
||||
// RAX REX.W 0xB8 + 8 imm (0x48 0xB8)
|
||||
|
||||
[Fact]
|
||||
public void X64_0args()
|
||||
public void X64_0args_allocates_shadow_space_and_aligns()
|
||||
{
|
||||
var s=Create(); ulong a=0x100000000,t=0x123456788;
|
||||
uint r=(uint)(t-(a+5));
|
||||
byte[] stub=s.BuildCallStub((IntPtr)(nint)a,(IntPtr)(nint)t,[],8,CallConvention.Cdecl);
|
||||
Assert.Equal(6,stub.Length); Assert.Equal(0xE8,stub[0]); Assert.Equal(r,BitConverter.ToUInt32(stub,1)); Assert.Equal(0xC3,stub[5]);
|
||||
var s = Create();
|
||||
ulong a = 0x100000000, t = 0x123456788;
|
||||
byte[] stub = s.BuildCallStub(
|
||||
(IntPtr)(nint)a, (IntPtr)(nint)t, [], 8, CallConvention.Cdecl);
|
||||
|
||||
// sub (7) + call (5) + add (7) + ret (1) = 20
|
||||
Assert.Equal(20, stub.Length);
|
||||
|
||||
uint rel = (uint)(t - (a + 7 + 5)); // = t - a - 12
|
||||
|
||||
Assert.Equal([0x48, 0x81, 0xEC, 0x20, 0x00, 0x00, 0x00], stub[..7]); // sub rsp, 0x20
|
||||
Assert.Equal(0xE8, stub[7]);
|
||||
Assert.Equal(rel, BitConverter.ToUInt32(stub, 8));
|
||||
Assert.Equal([0x48, 0x81, 0xC4, 0x20, 0x00, 0x00, 0x00], stub[12..19]); // add rsp, 0x20
|
||||
Assert.Equal(0xC3, stub[19]); // ret
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void X64_1arg_mov_ecx()
|
||||
public void X64_1arg_loads_rcx_as_64bit()
|
||||
{
|
||||
var s=Create(); ulong a=0x100000000,t=0x123456788;
|
||||
uint r=(uint)(t-(a+5+5));
|
||||
Assert.Equal([
|
||||
0xB9,0xDD,0xCC,0xBB,0xAA,
|
||||
0xE8,(byte)r,(byte)(r>>8),(byte)(r>>16),(byte)(r>>24),0xC3],
|
||||
s.BuildCallStub((IntPtr)(nint)a,(IntPtr)(nint)t,[0xAABBCCDD],8,CallConvention.Cdecl));
|
||||
var s = Create();
|
||||
ulong a = 0x100000000, t = 0x123456788;
|
||||
byte[] stub = s.BuildCallStub(
|
||||
(IntPtr)(nint)a, (IntPtr)(nint)t, [0xAABBCCDDu], 8, CallConvention.Cdecl);
|
||||
|
||||
// sub (7) + mov rcx, imm64 (10) + call (5) + add (7) + ret (1) = 30
|
||||
Assert.Equal(30, stub.Length);
|
||||
|
||||
uint rel = (uint)(t - (a + 7 + 10 + 5)); // = t - a - 22
|
||||
// stub[0..6] = sub rsp, 0x20
|
||||
Assert.Equal([0x48, 0x81, 0xEC, 0x20, 0x00, 0x00, 0x00], stub[..7]);
|
||||
// stub[7..16] = mov rcx, 0x00000000_AABBCCDD (zero-extended)
|
||||
Assert.Equal(0x48, stub[7]); Assert.Equal(0xB9, stub[8]);
|
||||
Assert.Equal(0xDD, stub[9]); Assert.Equal(0xCC, stub[10]);
|
||||
Assert.Equal(0xBB, stub[11]); Assert.Equal(0xAA, stub[12]);
|
||||
Assert.Equal(0x00, stub[13]); Assert.Equal(0x00, stub[14]);
|
||||
Assert.Equal(0x00, stub[15]); Assert.Equal(0x00, stub[16]);
|
||||
// stub[17..21] = call rel32
|
||||
Assert.Equal(0xE8, stub[17]);
|
||||
Assert.Equal(rel, BitConverter.ToUInt32(stub, 18));
|
||||
// stub[22..28] = add rsp, 0x20
|
||||
Assert.Equal([0x48, 0x81, 0xC4, 0x20, 0x00, 0x00, 0x00], stub[22..29]);
|
||||
Assert.Equal(0xC3, stub[29]); // ret
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void X64_4args_rcx_rdx_r8_r9()
|
||||
public void X64_4args_loads_rcx_rdx_r8_r9_as_64bit()
|
||||
{
|
||||
var s=Create(); ulong a=0x100000000,t=0x123456788;
|
||||
uint ca=(uint)a+5+5+6+6,r=(uint)(t-(ca+5));
|
||||
Assert.Equal([
|
||||
0xB9,0x11,0x11,0x11,0x11, 0xBA,0x22,0x22,0x22,0x22,
|
||||
0x41,0xB8,0x33,0x33,0x33,0x33, 0x41,0xB9,0x44,0x44,0x44,0x44,
|
||||
0xE8,(byte)r,(byte)(r>>8),(byte)(r>>16),(byte)(r>>24),0xC3],
|
||||
s.BuildCallStub((IntPtr)(nint)a,(IntPtr)(nint)t,[0x11111111,0x22222222,0x33333333,0x44444444],8,CallConvention.Cdecl));
|
||||
var s = Create();
|
||||
ulong a = 0x100000000, t = 0x123456788;
|
||||
byte[] stub = s.BuildCallStub(
|
||||
(IntPtr)(nint)a, (IntPtr)(nint)t,
|
||||
[0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u], 8, CallConvention.Cdecl);
|
||||
|
||||
// sub (7) + 4 x mov (4*10=40) + call (5) + add (7) + ret (1) = 60
|
||||
Assert.Equal(60, stub.Length);
|
||||
|
||||
uint rel = (uint)(t - (a + 7 + 40 + 5)); // = t - a - 52
|
||||
|
||||
Assert.Equal([0x48, 0x81, 0xEC, 0x20, 0x00, 0x00, 0x00], stub[..7]); // sub rsp, 0x20
|
||||
|
||||
// mov rcx, 0x11111111 (48 B9 + 8 imm) at [7..16]
|
||||
Assert.Equal(0x48, stub[7]); Assert.Equal(0xB9, stub[8]);
|
||||
Assert.Equal(0x11, stub[9]); Assert.Equal(0x11, stub[10]);
|
||||
Assert.Equal(0x11, stub[11]); Assert.Equal(0x11, stub[12]);
|
||||
Assert.Equal(0x00, stub[13]); Assert.Equal(0x00, stub[14]);
|
||||
Assert.Equal(0x00, stub[15]); Assert.Equal(0x00, stub[16]);
|
||||
|
||||
// mov rdx, 0x22222222 (48 BA + 8 imm) at [17..26]
|
||||
Assert.Equal(0x48, stub[17]); Assert.Equal(0xBA, stub[18]);
|
||||
Assert.Equal(0x22, stub[19]); Assert.Equal(0x22, stub[20]);
|
||||
Assert.Equal(0x22, stub[21]); Assert.Equal(0x22, stub[22]);
|
||||
Assert.Equal(0x00, stub[23]); Assert.Equal(0x00, stub[24]);
|
||||
Assert.Equal(0x00, stub[25]); Assert.Equal(0x00, stub[26]);
|
||||
|
||||
// mov r8, 0x33333333 (49 B8 + 8 imm) at [27..36]
|
||||
Assert.Equal(0x49, stub[27]); Assert.Equal(0xB8, stub[28]);
|
||||
Assert.Equal(0x33, stub[29]); Assert.Equal(0x33, stub[30]);
|
||||
Assert.Equal(0x33, stub[31]); Assert.Equal(0x33, stub[32]);
|
||||
Assert.Equal(0x00, stub[33]); Assert.Equal(0x00, stub[34]);
|
||||
Assert.Equal(0x00, stub[35]); Assert.Equal(0x00, stub[36]);
|
||||
|
||||
// mov r9, 0x44444444 (49 B9 + 8 imm) at [37..46]
|
||||
Assert.Equal(0x49, stub[37]); Assert.Equal(0xB9, stub[38]);
|
||||
Assert.Equal(0x44, stub[39]); Assert.Equal(0x44, stub[40]);
|
||||
Assert.Equal(0x44, stub[41]); Assert.Equal(0x44, stub[42]);
|
||||
Assert.Equal(0x00, stub[43]); Assert.Equal(0x00, stub[44]);
|
||||
Assert.Equal(0x00, stub[45]); Assert.Equal(0x00, stub[46]);
|
||||
|
||||
// call rel32 at [47..51]
|
||||
Assert.Equal(0xE8, stub[47]);
|
||||
Assert.Equal(rel, BitConverter.ToUInt32(stub, 48));
|
||||
|
||||
// add rsp, 0x20 at [52..58]
|
||||
Assert.Equal([0x48, 0x81, 0xC4, 0x20, 0x00, 0x00, 0x00], stub[52..59]);
|
||||
// ret at [59]
|
||||
Assert.Equal(0xC3, stub[59]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void X64_5args_push_cleanup()
|
||||
public void X64_5args_places_first_stack_arg_in_shadow_plus_0x20()
|
||||
{
|
||||
var s=Create(); ulong a=0x100000000,t=0x123456788;
|
||||
uint ca=(uint)a+5+5+6+6+5,r=(uint)(t-(ca+5));
|
||||
Assert.Equal([
|
||||
0xB9,1,0,0,0, 0xBA,2,0,0,0,
|
||||
0x41,0xB8,3,0,0,0, 0x41,0xB9,4,0,0,0,
|
||||
0x68,5,0,0,0,
|
||||
0xE8,(byte)r,(byte)(r>>8),(byte)(r>>16),(byte)(r>>24),
|
||||
0x48,0x83,0xC4,8, 0xC3],
|
||||
s.BuildCallStub((IntPtr)(nint)a,(IntPtr)(nint)t,[1,2,3,4,5],8,CallConvention.Cdecl));
|
||||
var s = Create();
|
||||
ulong a = 0x100000000, t = 0x123456788;
|
||||
byte[] stub = s.BuildCallStub(
|
||||
(IntPtr)(nint)a, (IntPtr)(nint)t,
|
||||
[(nuint)1, (nuint)2, (nuint)3, (nuint)4, (nuint)5], 8, CallConvention.Cdecl);
|
||||
|
||||
// sub (7) + 4 reg moves (40) + stack arg (mov rax 10 + mov [rsp+0x20],rax 5 = 15)
|
||||
// + call (5) + add (7) + ret (1) = 75
|
||||
Assert.Equal(75, stub.Length);
|
||||
|
||||
Assert.Equal([0x48, 0x81, 0xEC, 0x20, 0x00, 0x00, 0x00], stub[..7]);
|
||||
|
||||
// mov rcx, 1 at [7..16]
|
||||
Assert.Equal(0x48, stub[7]); Assert.Equal(0xB9, stub[8]);
|
||||
Assert.Equal(0x01, stub[9]); Assert.Equal(0x00, stub[10]);
|
||||
Assert.Equal(0x00, stub[11]); Assert.Equal(0x00, stub[12]);
|
||||
// mov rdx, 2 at [17..26]
|
||||
Assert.Equal(0x48, stub[17]); Assert.Equal(0xBA, stub[18]);
|
||||
Assert.Equal(0x02, stub[19]);
|
||||
// mov r8, 3 at [27..36]
|
||||
Assert.Equal(0x49, stub[27]); Assert.Equal(0xB8, stub[28]);
|
||||
Assert.Equal(0x03, stub[29]);
|
||||
// mov r9, 4 at [37..46]
|
||||
Assert.Equal(0x49, stub[37]); Assert.Equal(0xB9, stub[38]);
|
||||
Assert.Equal(0x04, stub[39]);
|
||||
|
||||
// mov rax, 5 (48 B8 + 8-byte imm) at [47..56]
|
||||
Assert.Equal(0x48, stub[47]); Assert.Equal(0xB8, stub[48]);
|
||||
Assert.Equal(0x05, stub[49]);
|
||||
for (int k = 50; k <= 56; k++) Assert.Equal(0x00, stub[k]);
|
||||
|
||||
// mov [rsp + 0x20], rax (48 89 44 24 20) at [57..61]
|
||||
Assert.Equal(0x48, stub[57]); Assert.Equal(0x89, stub[58]);
|
||||
Assert.Equal(0x44, stub[59]); Assert.Equal(0x24, stub[60]);
|
||||
Assert.Equal(0x20, stub[61]);
|
||||
|
||||
// call rel32 at [62..66]; distance = t - (a + 62 + 5) = t - a - 67
|
||||
uint rel = (uint)(t - (a + 62 + 5));
|
||||
Assert.Equal(0xE8, stub[62]);
|
||||
Assert.Equal(rel, BitConverter.ToUInt32(stub, 63));
|
||||
|
||||
// add rsp, 0x20 at [67..73]
|
||||
Assert.Equal([0x48, 0x81, 0xC4, 0x20, 0x00, 0x00, 0x00], stub[67..74]);
|
||||
// ret at [74]
|
||||
Assert.Equal(0xC3, stub[74]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void X64_full_64bit_args_are_preserved_not_truncated()
|
||||
{
|
||||
// The bug this catches: an earlier stub emitted "mov r32d, imm32" which zero-extended
|
||||
// a 32-bit immediate into the lower half of the 64-bit register, silently dropping
|
||||
// the high bits of any pointer-sized argument above 4 GiB.
|
||||
var s = Create();
|
||||
ulong a = 0x100000000, t = 0x123456788;
|
||||
nuint wideArg = unchecked((nuint)0xDEADBEEF_CAFEBABEUL);
|
||||
byte[] stub = s.BuildCallStub(
|
||||
(IntPtr)(nint)a, (IntPtr)(nint)t, [wideArg], 8, CallConvention.Cdecl);
|
||||
|
||||
// The 8-byte immediate for arg0 lives inside `mov rcx, imm64` at bytes [9..16].
|
||||
ulong read = BitConverter.ToUInt64(stub, 9);
|
||||
Assert.Equal(0xDEADBEEF_CAFEBABEul, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void X86_target_rejects_arg_value_larger_than_32_bits()
|
||||
{
|
||||
if (!Environment.Is64BitProcess)
|
||||
{
|
||||
// On a 32-bit host, nuint cannot exceed uint.MaxValue — the precondition
|
||||
// cannot be exercised. Mark the test as an intentional no-op.
|
||||
Assert.True(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var s = Create();
|
||||
nuint tooBig = unchecked((nuint)0x1_00000000UL);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
s.BuildCallStub((IntPtr)0x10000000, (IntPtr)0x12345678, [tooBig], 4, CallConvention.Cdecl));
|
||||
}
|
||||
|
||||
// ── Edge cases ────────────────────────────────────────────────────────
|
||||
@@ -193,8 +348,9 @@ public class StubAssemblerTests
|
||||
[Fact]
|
||||
public void Many_args_cleanup_uses_imm32_form()
|
||||
{
|
||||
var args = new uint[33];
|
||||
for (int i = 0; i < 33; i++) args[i] = (uint)(i * 0x10000 + i);
|
||||
// x86 path: 33 args, stack cleanup > 127 bytes → must emit add esp, imm32 (81 C4)
|
||||
var args = new nuint[33];
|
||||
for (int i = 0; i < 33; i++) args[i] = (nuint)(uint)(i * 0x10000 + i);
|
||||
byte[] stub = Create().BuildCallStub(
|
||||
(IntPtr)0x10000000, (IntPtr)0x12345678, args, 4, CallConvention.Cdecl);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user