diff --git a/WhiteMagicTest/StubAssemblerTests.cs b/WhiteMagicTest/StubAssemblerTests.cs index 53dfa9c..0244328 100644 --- a/WhiteMagicTest/StubAssemblerTests.cs +++ b/WhiteMagicTest/StubAssemblerTests.cs @@ -2,11 +2,6 @@ using WhiteMagic.Assembly; namespace WhiteMagicTest; -/// -/// Tests for emit primitives (, -/// , ) and -/// calling-convention stub encoding. -/// public class StubAssemblerTests { private static StubAssembler Create() => new(); @@ -76,67 +71,171 @@ public class StubAssemblerTests Assert.Throws(() => sut.Assemble("nop", 0)); } - // ── Calling convention: x86 cdecl ────────────────────────────────────── + // ── x86 cdecl ────────────────────────────────────────────────────────── [Fact] - public void Cdecl_stub_with_zero_args_is_call_then_ret() + public void Cdecl_stub_zero_args_call_then_ret() { var sut = Create(); - IntPtr stubAddr = (IntPtr)0x10000000; - IntPtr target = (IntPtr)0x12345678; - uint[] args = []; + uint rel32 = 0x12345678u - (0x10000000u + 5); + byte[] stub = sut.BuildCallStub( + (IntPtr)0x10000000, (IntPtr)0x12345678, [], 4, CallingConvention.Cdecl); - uint rel32 = (uint)target - ((uint)stubAddr + 5); - byte[] stub = sut.BuildCallStub(stubAddr, target, args, 4, CallingConvention.Cdecl); - - Assert.Equal(6, stub.Length); - Assert.Equal(0xE8, stub[0]); // call - Assert.Equal(rel32, BitConverter.ToUInt32(stub, 1)); // rel32 - Assert.Equal(0xC3, stub[5]); // ret + Assert.Equal([0xE8, (byte)rel32, (byte)(rel32>>8), (byte)(rel32>>16), (byte)(rel32>>24), 0xC3], stub); } [Fact] - public void Cdecl_stub_one_arg_reverse_push_then_call_then_cleanup() + public void Cdecl_stub_one_arg_push_call_cleanup_ret() { var sut = Create(); - IntPtr stubAddr = (IntPtr)0x10000000; - IntPtr target = (IntPtr)0x12345678; - uint[] args = [0xAABBCCDD]; + uint callAddr = 0x10000000u + 5; + uint rel32 = 0x12345678u - (callAddr + 5); + byte[] stub = sut.BuildCallStub( + (IntPtr)0x10000000, (IntPtr)0x12345678, [0xAABBCCDD], 4, CallingConvention.Cdecl); - uint callAddr = (uint)stubAddr + 5; - uint rel32 = (uint)target - (callAddr + 5); - byte[] stub = sut.BuildCallStub(stubAddr, target, args, 4, CallingConvention.Cdecl); - - byte[] expected = [ - 0x68, 0xDD, 0xCC, 0xBB, 0xAA, // push 0xAABBCCDD - 0xE8, - (byte)rel32, (byte)(rel32 >> 8), (byte)(rel32 >> 16), (byte)(rel32 >> 24), - 0x83, 0xC4, 0x04, // add esp, 4 - 0xC3 // ret - ]; - Assert.Equal(expected, stub); + Assert.Equal([ + 0x68, 0xDD, 0xCC, 0xBB, 0xAA, + 0xE8, (byte)rel32, (byte)(rel32>>8), (byte)(rel32>>16), (byte)(rel32>>24), + 0x83, 0xC4, 0x04, + 0xC3 + ], stub); } [Fact] public void Cdecl_stub_two_args_reverse_order() { var sut = Create(); - IntPtr stubAddr = (IntPtr)0x10000000; - IntPtr target = (IntPtr)0x12345678; - uint[] args = [0x11111111, 0x22222222]; + uint callAddr = 0x10000000u + 10; + uint rel32 = 0x12345678u - (callAddr + 5); + byte[] stub = sut.BuildCallStub( + (IntPtr)0x10000000, (IntPtr)0x12345678, [0x11111111, 0x22222222], 4, CallingConvention.Cdecl); - uint callAddr = (uint)stubAddr + 10; - uint rel32 = (uint)target - (callAddr + 5); - byte[] stub = sut.BuildCallStub(stubAddr, target, args, 4, CallingConvention.Cdecl); - - byte[] expected = [ - 0x68, 0x22, 0x22, 0x22, 0x22, // push arg1 (reverse order) - 0x68, 0x11, 0x11, 0x11, 0x11, // push arg0 - 0xE8, - (byte)rel32, (byte)(rel32 >> 8), (byte)(rel32 >> 16), (byte)(rel32 >> 24), - 0x83, 0xC4, 0x08, // add esp, 8 + Assert.Equal([ + 0x68, 0x22, 0x22, 0x22, 0x22, + 0x68, 0x11, 0x11, 0x11, 0x11, + 0xE8, (byte)rel32, (byte)(rel32>>8), (byte)(rel32>>16), (byte)(rel32>>24), + 0x83, 0xC4, 0x08, 0xC3 - ]; - Assert.Equal(expected, stub); + ], stub); + } + + // ── x86 stdcall ──────────────────────────────────────────────────────── + + [Fact] + public void Stdcall_stub_one_arg_no_cleanup() + { + var sut = Create(); + uint callAddr = 0x10000000u + 5; + uint rel32 = 0x12345678u - (callAddr + 5); + byte[] stub = sut.BuildCallStub( + (IntPtr)0x10000000, (IntPtr)0x12345678, [0xAABBCCDD], 4, CallingConvention.Stdcall); + + Assert.Equal([ + 0x68, 0xDD, 0xCC, 0xBB, 0xAA, + 0xE8, (byte)rel32, (byte)(rel32>>8), (byte)(rel32>>16), (byte)(rel32>>24), + 0xC3 + ], stub); + } + + [Fact] + public void Stdcall_stub_two_args_reverse_no_cleanup() + { + var sut = Create(); + uint callAddr = 0x10000000u + 10; + uint rel32 = 0x12345678u - (callAddr + 5); + byte[] stub = sut.BuildCallStub( + (IntPtr)0x10000000, (IntPtr)0x12345678, [0x11111111, 0x22222222], 4, CallingConvention.Stdcall); + + Assert.Equal([ + 0x68, 0x22, 0x22, 0x22, 0x22, + 0x68, 0x11, 0x11, 0x11, 0x11, + 0xE8, (byte)rel32, (byte)(rel32>>8), (byte)(rel32>>16), (byte)(rel32>>24), + 0xC3 + ], stub); + } + + // ── x86 thiscall ─────────────────────────────────────────────────────── + + [Fact] + public void Thiscall_stub_ecx_then_stack_then_call() + { + var sut = Create(); + uint callAddr = 0x10000000u + 10; + uint rel32 = 0x12345678u - (callAddr + 5); + byte[] stub = sut.BuildCallStub( + (IntPtr)0x10000000, (IntPtr)0x12345678, [0xAAAA5555, 0xBBBB6666], 4, CallingConvention.Thiscall); + + Assert.Equal([ + 0xB9, 0x55, 0x55, 0xAA, 0xAA, + 0x68, 0x66, 0x66, 0xBB, 0xBB, + 0xE8, (byte)rel32, (byte)(rel32>>8), (byte)(rel32>>16), (byte)(rel32>>24), + 0xC3 + ], stub); + } + + [Fact] + public void Thiscall_stub_one_arg_ecx_only_then_call() + { + var sut = Create(); + uint callAddr = 0x10000000u + 5; + uint rel32 = 0x12345678u - (callAddr + 5); + byte[] stub = sut.BuildCallStub( + (IntPtr)0x10000000, (IntPtr)0x12345678, [0xCAFEBABE], 4, CallingConvention.Thiscall); + + Assert.Equal(11, stub.Length); + Assert.Equal(0xB9, stub[0]); + Assert.Equal(0xCAFEBABE, BitConverter.ToUInt32(stub, 1)); + Assert.Equal(0xE8, stub[5]); + Assert.Equal(rel32, BitConverter.ToUInt32(stub, 6)); + Assert.Equal(0xC3, stub[10]); + } + + // ── x86 fastcall ─────────────────────────────────────────────────────── + + [Fact] + public void Fastcall_stub_ecx_edx_stack_call() + { + var sut = Create(); + uint callAddr = 0x10000000u + 15; + uint rel32 = 0x12345678u - (callAddr + 5); + byte[] stub = sut.BuildCallStub( + (IntPtr)0x10000000, (IntPtr)0x12345678, [0x11111111, 0x22222222, 0x33333333], 4, CallingConvention.Fastcall); + + Assert.Equal([ + 0xB9, 0x11, 0x11, 0x11, 0x11, + 0xBA, 0x22, 0x22, 0x22, 0x22, + 0x68, 0x33, 0x33, 0x33, 0x33, + 0xE8, (byte)rel32, (byte)(rel32>>8), (byte)(rel32>>16), (byte)(rel32>>24), + 0xC3 + ], stub); + } + + [Fact] + public void Fastcall_stub_two_args_registers_only() + { + var sut = Create(); + uint callAddr = 0x10000000u + 10; + uint rel32 = 0x12345678u - (callAddr + 5); + byte[] stub = sut.BuildCallStub( + (IntPtr)0x10000000, (IntPtr)0x12345678, [0xAAAAAAAA, 0xBBBBBBBB], 4, CallingConvention.Fastcall); + + Assert.Equal(16, stub.Length); + Assert.Equal(0xB9, stub[0]); + Assert.Equal(0xAAAAAAAA, BitConverter.ToUInt32(stub, 1)); + Assert.Equal(0xBA, stub[5]); + Assert.Equal(0xBBBBBBBB, BitConverter.ToUInt32(stub, 6)); + Assert.Equal(0xE8, stub[10]); + Assert.Equal(rel32, BitConverter.ToUInt32(stub, 11)); + Assert.Equal(0xC3, stub[15]); + } + + // ── x64 ──────────────────────────────────────────────────────────────── + + [Fact] + public void X64_stub_throws_not_implemented_by_default() + { + var sut = Create(); + Assert.Throws(() => + sut.BuildCallStub((IntPtr)0x10000000, (IntPtr)0x12345678, [], 8, CallingConvention.Cdecl)); } }