238 lines
10 KiB
C#
238 lines
10 KiB
C#
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<byte>(); s.EmitU8(b,0xAB); Assert.Equal([0xAB],b); }
|
|
[Fact] public void EmitU32_appends_little_endian() { var s=Create(); var b=new List<byte>(); s.EmitU32(b,0x11223344); Assert.Equal([0x44,0x33,0x22,0x11],b); }
|
|
[Fact] public void EmitU32_appends_zero() { var s=Create(); var b=new List<byte>(); 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<byte>(); 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<byte>(); s.EmitU64(b,0xDEADBEEF_CAFEBABE); Assert.Equal([0xBE,0xBA,0xFE,0xCA,0xEF,0xBE,0xAD,0xDE],b); }
|
|
[Fact] public void StubAssembler_is_IAssembler() { Assert.IsAssignableFrom<IAssembler>(Create()); }
|
|
[Fact] public void Assemble_throws() { Assert.Throws<NotSupportedException>(()=>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<ArgumentOutOfRangeException>(() =>
|
|
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<ArgumentOutOfRangeException>(() =>
|
|
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<ArgumentOutOfRangeException>(() =>
|
|
Create().BuildCallStub((IntPtr)0x10000000, (IntPtr)0x12345678, [], 2, CallConvention.Cdecl));
|
|
}
|
|
|
|
[Fact]
|
|
public void Invalid_calling_convention_throws()
|
|
{
|
|
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
|
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));
|
|
}
|
|
}
|