diff --git a/WhiteMagic/Assembly/IAssembler.cs b/WhiteMagic/Assembly/IAssembler.cs new file mode 100644 index 0000000..321c4d8 --- /dev/null +++ b/WhiteMagic/Assembly/IAssembler.cs @@ -0,0 +1,17 @@ +namespace WhiteMagic.Assembly; + +/// +/// Abstraction over an x86/x64 assembler. The default +/// hand-emits calling-convention trampolines (no parsing, zero dep). An optional +/// (Phase 8) handles arbitrary mnemonics via the Iced +/// library. +/// +public interface IAssembler +{ + /// + /// Assembles text mnemonics into machine code. + /// + /// The assembly text (Intel syntax). + /// The base address for relative encodings. + byte[] Assemble(string assemblyText, ulong origin = 0); +} diff --git a/WhiteMagic/Assembly/StubAssembler.cs b/WhiteMagic/Assembly/StubAssembler.cs new file mode 100644 index 0000000..2d24a59 --- /dev/null +++ b/WhiteMagic/Assembly/StubAssembler.cs @@ -0,0 +1,49 @@ +namespace WhiteMagic.Assembly; + +/// +/// The default backend. Hand-emits calling-convention +/// trampolines and injection stubs using deterministic byte emitters +/// (, , ). Has +/// no native or third-party dependency — no FASM, no Iced. +/// +/// +/// is not supported by this backend (it is a parse-free +/// emitter, not a text assembler). Use (Phase 8) for +/// arbitrary mnemonics. +/// +public class StubAssembler : IAssembler +{ + /// + /// Always thrown. StubAssembler does + /// not parse text assembly; use IcedAssembler for that. + public byte[] Assemble(string assemblyText, ulong origin = 0) + { + throw new NotSupportedException( + "StubAssembler does not parse text assembly. " + + "Use IcedAssembler (Phase 8) for arbitrary mnemonics."); + } + + // ── Emit primitives ──────────────────────────────────────────────────── + + /// Appends a single byte to . + public void EmitU8(List buffer, byte value) + { + buffer.Add(value); + } + + /// Appends as 4 little-endian bytes. + public void EmitU32(List buffer, uint value) + { + buffer.Add((byte)value); + buffer.Add((byte)(value >> 8)); + buffer.Add((byte)(value >> 16)); + buffer.Add((byte)(value >> 24)); + } + + /// Appends as 8 little-endian bytes. + public void EmitU64(List buffer, ulong value) + { + EmitU32(buffer, (uint)value); + EmitU32(buffer, (uint)(value >> 32)); + } +} diff --git a/WhiteMagicTest/StubAssemblerTests.cs b/WhiteMagicTest/StubAssemblerTests.cs new file mode 100644 index 0000000..e004a53 --- /dev/null +++ b/WhiteMagicTest/StubAssemblerTests.cs @@ -0,0 +1,78 @@ +using WhiteMagic.Assembly; + +namespace WhiteMagicTest; + +/// +/// Tests for emit primitives (, +/// , ) and the +/// interface contract. +/// +public class StubAssemblerTests +{ + private static StubAssembler Create() => new(); + + // ── Emit primitives ──────────────────────────────────────────────────── + + [Fact] + public void EmitU8_appends_a_single_byte() + { + var sut = Create(); + var buffer = new List(); + sut.EmitU8(buffer, 0xAB); + Assert.Equal([0xAB], buffer); + } + + [Fact] + public void EmitU32_appends_little_endian() + { + var sut = Create(); + var buffer = new List(); + sut.EmitU32(buffer, 0x11223344); + Assert.Equal([0x44, 0x33, 0x22, 0x11], buffer); + } + + [Fact] + public void EmitU32_appends_zero() + { + var sut = Create(); + var buffer = new List(); + sut.EmitU32(buffer, 0); + Assert.Equal([0x00, 0x00, 0x00, 0x00], buffer); + } + + [Fact] + public void EmitU64_appends_little_endian() + { + var sut = Create(); + var buffer = new List(); + sut.EmitU64(buffer, 0x1122334455667788); + byte[] expected = [0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11]; + Assert.Equal(expected, buffer); + } + + [Fact] + public void EmitU64_appends_high_bits() + { + var sut = Create(); + var buffer = new List(); + sut.EmitU64(buffer, 0xDEADBEEF_CAFEBABE); + byte[] expected = [0xBE, 0xBA, 0xFE, 0xCA, 0xEF, 0xBE, 0xAD, 0xDE]; + Assert.Equal(expected, buffer); + } + + // ── IAssembler interface ─────────────────────────────────────────────── + + [Fact] + public void StubAssembler_is_an_IAssembler() + { + var sut = Create(); + Assert.IsAssignableFrom(sut); + } + + [Fact] + public void Assemble_from_StubAssembler_throws_NotSupported() + { + var sut = Create(); + Assert.Throws(() => sut.Assemble("nop", 0)); + } +}