task 3.1-3.2: IAssembler interface + StubAssembler emit primitives
Add IAssembler seam (Assemble(text, origin)) with StubAssembler default backend. StubAssembler provides EmitU8/EmitU32/EmitU64 little-endian byte emitters (zero dep, no FASM). Assemble throws NotSupportedException on StubAssembler (text assembly deferred to IcedAssembler, Phase 8). 7 new tests: EmitU8, EmitU32 x2, EmitU64 x2, IS-A check, Assemble throws. All passing (total: 64).
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
namespace WhiteMagic.Assembly;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstraction over an x86/x64 assembler. The default <see cref="StubAssembler"/>
|
||||||
|
/// hand-emits calling-convention trampolines (no parsing, zero dep). An optional
|
||||||
|
/// <see cref="IcedAssembler"/> (Phase 8) handles arbitrary mnemonics via the Iced
|
||||||
|
/// library.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAssembler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Assembles text mnemonics into machine code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyText">The assembly text (Intel syntax).</param>
|
||||||
|
/// <param name="origin">The base address for relative encodings.</param>
|
||||||
|
byte[] Assemble(string assemblyText, ulong origin = 0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
namespace WhiteMagic.Assembly;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default <see cref="IAssembler"/> backend. Hand-emits calling-convention
|
||||||
|
/// trampolines and injection stubs using deterministic byte emitters
|
||||||
|
/// (<see cref="EmitU8"/>, <see cref="EmitU32"/>, <see cref="EmitU64"/>). Has
|
||||||
|
/// no native or third-party dependency — no FASM, no Iced.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see cref="Assemble"/> is not supported by this backend (it is a parse-free
|
||||||
|
/// emitter, not a text assembler). Use <see cref="IcedAssembler"/> (Phase 8) for
|
||||||
|
/// arbitrary mnemonics.
|
||||||
|
/// </remarks>
|
||||||
|
public class StubAssembler : IAssembler
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <exception cref="NotSupportedException">Always thrown. StubAssembler does
|
||||||
|
/// not parse text assembly; use IcedAssembler for that.</exception>
|
||||||
|
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 ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>Appends a single byte to <paramref name="buffer"/>.</summary>
|
||||||
|
public void EmitU8(List<byte> buffer, byte value)
|
||||||
|
{
|
||||||
|
buffer.Add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Appends <paramref name="value"/> as 4 little-endian bytes.</summary>
|
||||||
|
public void EmitU32(List<byte> buffer, uint value)
|
||||||
|
{
|
||||||
|
buffer.Add((byte)value);
|
||||||
|
buffer.Add((byte)(value >> 8));
|
||||||
|
buffer.Add((byte)(value >> 16));
|
||||||
|
buffer.Add((byte)(value >> 24));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Appends <paramref name="value"/> as 8 little-endian bytes.</summary>
|
||||||
|
public void EmitU64(List<byte> buffer, ulong value)
|
||||||
|
{
|
||||||
|
EmitU32(buffer, (uint)value);
|
||||||
|
EmitU32(buffer, (uint)(value >> 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using WhiteMagic.Assembly;
|
||||||
|
|
||||||
|
namespace WhiteMagicTest;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for <see cref="StubAssembler"/> emit primitives (<see cref="StubAssembler.EmitU8"/>,
|
||||||
|
/// <see cref="StubAssembler.EmitU32"/>, <see cref="StubAssembler.EmitU64"/>) and the
|
||||||
|
/// <see cref="IAssembler"/> interface contract.
|
||||||
|
/// </summary>
|
||||||
|
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<byte>();
|
||||||
|
sut.EmitU8(buffer, 0xAB);
|
||||||
|
Assert.Equal([0xAB], buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EmitU32_appends_little_endian()
|
||||||
|
{
|
||||||
|
var sut = Create();
|
||||||
|
var buffer = new List<byte>();
|
||||||
|
sut.EmitU32(buffer, 0x11223344);
|
||||||
|
Assert.Equal([0x44, 0x33, 0x22, 0x11], buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EmitU32_appends_zero()
|
||||||
|
{
|
||||||
|
var sut = Create();
|
||||||
|
var buffer = new List<byte>();
|
||||||
|
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<byte>();
|
||||||
|
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<byte>();
|
||||||
|
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<IAssembler>(sut);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Assemble_from_StubAssembler_throws_NotSupported()
|
||||||
|
{
|
||||||
|
var sut = Create();
|
||||||
|
Assert.Throws<NotSupportedException>(() => sut.Assemble("nop", 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user