using WhiteMagic.Assembly; namespace WhiteMagicTest; public class StubAssemblerTests { private static StubAssembler Create() => new(); // ── Emit primitives ──────────────────────────────────────────────────── [Fact] public void EmitU8_appends_a_single_byte() { var s=Create(); var b=new List(); s.EmitU8(b,0xAB); Assert.Equal([0xAB],b); } [Fact] public void EmitU32_appends_little_endian() { var s=Create(); var b=new List(); s.EmitU32(b,0x11223344); Assert.Equal([0x44,0x33,0x22,0x11],b); } [Fact] public void EmitU32_appends_zero() { var s=Create(); var b=new List(); s.EmitU32(b,0); Assert.Equal([0,0,0,0],b); } [Fact] public void EmitU64_appends_little_endian() { var s=Create(); var b=new List(); s.EmitU64(b,0x1122334455667788); Assert.Equal([0x88,0x77,0x66,0x55,0x44,0x33,0x22,0x11],b); } [Fact] public void EmitU64_appends_high_bits() { var s=Create(); var b=new List(); s.EmitU64(b,0xDEADBEEF_CAFEBABE); Assert.Equal([0xBE,0xBA,0xFE,0xCA,0xEF,0xBE,0xAD,0xDE],b); } [Fact] public void StubAssembler_is_IAssembler() { Assert.IsAssignableFrom(Create()); } [Fact] public void Assemble_throws() { Assert.Throws(()=>Create().Assemble("nop",0)); } // ── x86 cdecl ────────────────────────────────────────────────────────── [Fact] public void Cdecl_0args() { uint r = 0x12345678u-(0x10000000u+5); Assert.Equal([0xE8,(byte)r,(byte)(r>>8),(byte)(r>>16),(byte)(r>>24),0xC3], Create().BuildCallStub((IntPtr)0x10000000,(IntPtr)0x12345678,[],4,CallConvention.Cdecl)); } [Fact] public void Cdecl_1arg() { uint ca=0x10000000u+5,r=0x12345678u-(ca+5); Assert.Equal([ 0x68,0xDD,0xCC,0xBB,0xAA, 0xE8,(byte)r,(byte)(r>>8),(byte)(r>>16),(byte)(r>>24), 0x83,0xC4,0x04,0xC3], Create().BuildCallStub((IntPtr)0x10000000,(IntPtr)0x12345678,[0xAABBCCDD],4,CallConvention.Cdecl)); } [Fact] public void Cdecl_2args() { uint ca=0x10000000u+10,r=0x12345678u-(ca+5); Assert.Equal([ 0x68,0x22,0x22,0x22,0x22, 0x68,0x11,0x11,0x11,0x11, 0xE8,(byte)r,(byte)(r>>8),(byte)(r>>16),(byte)(r>>24), 0x83,0xC4,0x08,0xC3], Create().BuildCallStub((IntPtr)0x10000000,(IntPtr)0x12345678,[0x11111111,0x22222222],4,CallConvention.Cdecl)); } // ── x86 stdcall ────────────────────────────────────────────────────── [Fact] public void Stdcall_1arg() { uint ca=0x10000000u+5,r=0x12345678u-(ca+5); Assert.Equal([ 0x68,0xDD,0xCC,0xBB,0xAA, 0xE8,(byte)r,(byte)(r>>8),(byte)(r>>16),(byte)(r>>24),0xC3], Create().BuildCallStub((IntPtr)0x10000000,(IntPtr)0x12345678,[0xAABBCCDD],4,CallConvention.Stdcall)); } [Fact] public void Stdcall_2args() { uint ca=0x10000000u+10,r=0x12345678u-(ca+5); Assert.Equal([ 0x68,0x22,0x22,0x22,0x22, 0x68,0x11,0x11,0x11,0x11, 0xE8,(byte)r,(byte)(r>>8),(byte)(r>>16),(byte)(r>>24),0xC3], Create().BuildCallStub((IntPtr)0x10000000,(IntPtr)0x12345678,[0x11111111,0x22222222],4,CallConvention.Stdcall)); } // ── x86 thiscall ───────────────────────────────────────────────────── [Fact] public void Thiscall_ecx_then_stack() { uint ca=0x10000000u+10,r=0x12345678u-(ca+5); Assert.Equal([ 0xB9,0x55,0x55,0xAA,0xAA, 0x68,0x66,0x66,0xBB,0xBB, 0xE8,(byte)r,(byte)(r>>8),(byte)(r>>16),(byte)(r>>24),0xC3], Create().BuildCallStub((IntPtr)0x10000000,(IntPtr)0x12345678,[0xAAAA5555,0xBBBB6666],4,CallConvention.Thiscall)); } [Fact] public void Thiscall_1arg_ecx_only() { 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(0xE8,s[5]); Assert.Equal(r,BitConverter.ToUInt32(s,6)); Assert.Equal(0xC3,s[10]); } [Fact] public void Thiscall_0args_throws() { Assert.Throws(() => Create().BuildCallStub((IntPtr)0x10000000,(IntPtr)0x12345678,[],4,CallConvention.Thiscall)); } // ── x86 fastcall ──────────────────────────────────────────────────── [Fact] public void Fastcall_ecx_edx_stack() { uint ca=0x10000000u+15,r=0x12345678u-(ca+5); Assert.Equal([ 0xB9,0x11,0x11,0x11,0x11, 0xBA,0x22,0x22,0x22,0x22, 0x68,0x33,0x33,0x33,0x33, 0xE8,(byte)r,(byte)(r>>8),(byte)(r>>16),(byte)(r>>24),0xC3], Create().BuildCallStub((IntPtr)0x10000000,(IntPtr)0x12345678,[0x11111111,0x22222222,0x33333333],4,CallConvention.Fastcall)); } [Fact] public void Fastcall_2args_registers_only() { 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(0xE8,s[10]); Assert.Equal(r,BitConverter.ToUInt32(s,11)); Assert.Equal(0xC3,s[15]); } [Fact] public void Fastcall_0args_is_valid() { uint r=0x12345678u-(0x10000000u+5); Assert.Equal([0xE8,(byte)r,(byte)(r>>8),(byte)(r>>16),(byte)(r>>24),0xC3], Create().BuildCallStub((IntPtr)0x10000000,(IntPtr)0x12345678,[],4,CallConvention.Fastcall)); } // ── x64 ───────────────────────────────────────────────────────────── [Fact] public void X64_0args() { 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]); } [Fact] public void X64_1arg_mov_ecx() { 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)); } [Fact] public void X64_4args_rcx_rdx_r8_r9() { 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)); } [Fact] public void X64_5args_push_cleanup() { 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)); } // ── Edge cases ──────────────────────────────────────────────────────── [Fact] public void Far_target_throws() { Assert.Throws(() => Create().BuildCallStub(IntPtr.Zero, unchecked((IntPtr)(nint)0xC0000000), [], 4, CallConvention.Cdecl)); } [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); byte[] stub = Create().BuildCallStub( (IntPtr)0x10000000, (IntPtr)0x12345678, args, 4, CallConvention.Cdecl); for (int i = 0; i < stub.Length - 5; i++) { if (stub[i] == 0x81 && stub[i + 1] == 0xC4) { Assert.Equal(132, BitConverter.ToInt32(stub, i + 2)); return; } } Assert.Fail("Expected 0x81 0xC4 (add esp, imm32) not found"); } [Fact] public void Invalid_pointerSize_throws() { Assert.Throws(() => Create().BuildCallStub((IntPtr)0x10000000, (IntPtr)0x12345678, [], 2, CallConvention.Cdecl)); } [Fact] public void Invalid_calling_convention_throws() { Assert.Throws(() => Create().BuildCallStub((IntPtr)0x10000000, (IntPtr)0x12345678, [], 4, (CallConvention)99)); } // ── No-FASM ───────────────────────────────────────────────────────── [Fact] public void No_fasm_reference_in_output() { var asm = typeof(StubAssembler).Assembly; var refs = asm.GetReferencedAssemblies(); Assert.DoesNotContain(refs, r => r.Name!.Contains("Fasm", StringComparison.OrdinalIgnoreCase) || r.Name!.Contains("ManagedFasm", StringComparison.OrdinalIgnoreCase)); } }