diff --git a/MSILEmulator/Context.cs b/MSILEmulator/Context.cs new file mode 100644 index 0000000..c2ca45b --- /dev/null +++ b/MSILEmulator/Context.cs @@ -0,0 +1,23 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System.Collections.Generic; + +namespace MSILEmulator +{ + public class Context + { + public Dictionary Args = new(); + public Dictionary Fields = new(); + public Dictionary Locals = new(); + public Dictionary Offsets = new(); + public Stack Stack = new(); + + public Context(IList instructions) + { + for (int i = 0; i < instructions.Count; i++) + { + Offsets[instructions[i].Offset] = i; + } + } + } +} diff --git a/MSILEmulator/EmulatorException.cs b/MSILEmulator/EmulatorException.cs new file mode 100644 index 0000000..9a49b58 --- /dev/null +++ b/MSILEmulator/EmulatorException.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator +{ + internal class EmulatorException : Exception + { + + public EmulatorException() + { + } + + public EmulatorException(string message) : base(message) + { + } + + public EmulatorException(string message, Exception inner) : base(message, inner) + { + } + + } +} diff --git a/MSILEmulator/ILMethod.cs b/MSILEmulator/ILMethod.cs new file mode 100644 index 0000000..ba6fe46 --- /dev/null +++ b/MSILEmulator/ILMethod.cs @@ -0,0 +1,217 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using MSILEmulator.Instructions.Arithmetic; +using MSILEmulator.Instructions.Branch; +using MSILEmulator.Instructions.Load; +using MSILEmulator.Instructions.Logic; +using MSILEmulator.Instructions.Store; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MSILEmulator +{ + public class ILMethod + { + private MethodDef? Method; + private List Instructions; + private Context Ctx; + + public ILMethod(MethodDef method) + { + Method = method; + Instructions = Method.Body.Instructions.ToList(); + Ctx = new(Instructions); + } + + public ILMethod(MethodDef method, int start, int end) + { + Method = method; + Instructions = Method.Body.Instructions.Skip(start).Take(end - start).ToList(); + Ctx = new(Instructions); + } + + public ILMethod(IEnumerable instructions) + { + Method = null; + Instructions = instructions.ToList(); + Ctx = new(Instructions); + } + + public void SetArg(int index, object value) + { + Ctx.Args[index] = value; + } + + public void SetLocal(int index, object value) + { + Ctx.Locals[index] = value; + } + + public Context Emulate() + { + + for (int i = 0; i < Instructions.Count; ) + { + var instr = Instructions[i]; + + i = EmulateInstruction(Ctx, instr, i); + + if (i == -1) + { + break; + } + } + + return Ctx; + } + + private int EmulateInstruction(Context ctx, Instruction instr, int index) + { + + switch (instr.OpCode.Code) + { + // Load + case Code.Ldc_I4: + case Code.Ldc_I4_S: + ctx.Stack.Push(instr.GetLdcI4Value()); + break; + case Code.Ldc_I4_0: + case Code.Ldc_I4_1: + case Code.Ldc_I4_2: + case Code.Ldc_I4_3: + case Code.Ldc_I4_4: + case Code.Ldc_I4_5: + case Code.Ldc_I4_6: + case Code.Ldc_I4_7: + case Code.Ldc_I4_8: + ctx.Stack.Push((int)instr.OpCode.Code - (int)Code.Ldc_I4_0); + break; + case Code.Ldc_I4_M1: + ctx.Stack.Push((int)-1); + break; + + case Code.Ldc_I8: + ctx.Stack.Push((long)instr.Operand); + break; + + case Code.Ldarg: + case Code.Ldarg_0: + case Code.Ldarg_1: + case Code.Ldarg_2: + case Code.Ldarg_3: + case Code.Ldarg_S: + Ldarg.Emulate(ctx, instr); + break; + + case Code.Ldloc: + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + case Code.Ldloc_S: + Ldloc.Emulate(ctx, instr); + break; + + case Code.Ldelem_U4: + Ldelem.EmulateU4(ctx, instr); + break; + + // Store + case Code.Stloc: + case Code.Stloc_0: + case Code.Stloc_1: + case Code.Stloc_2: + case Code.Stloc_3: + case Code.Stloc_S: + Stloc.Emulate(ctx, instr); + break; + + case Code.Stelem_I4: + Stelem.Emulate(ctx, instr); + break; + + case Code.Stfld: + ctx.Fields[(IField)instr.Operand] = ctx.Stack.Pop(); + if (ctx.Stack.Count > 0) + { + ctx.Stack.Pop(); + } + break; + + // Arithmetic operators + case Code.Add: + Add.Emulate(ctx); + break; + case Code.Mul: + Mul.Emulate(ctx); + break; + case Code.Neg: + Neg.Emulate(ctx); + break; + case Code.Or: + Or.Emulate(ctx); + break; + case Code.Sub: + Sub.Emulate(ctx); + break; + + // Logic operators + case Code.And: + And.Emulate(ctx); + break; + case Code.Not: + Not.Emulate(ctx); + break; + case Code.Shl: + Shl.Emulate(ctx); + break; + case Code.Shr: + Shr.Emulate(ctx); + break; + case Code.Shr_Un: + Shr_Un.Emulate(ctx); + break; + case Code.Xor: + Xor.Emulate(ctx); + break; + + + // Branching + case Code.Br: + case Code.Br_S: + return ctx.Offsets[((Instruction)instr.Operand).Offset]; + case Code.Beq: + case Code.Beq_S: + return Beq.Emulate(ctx, instr); + case Code.Bne_Un: + case Code.Bne_Un_S: + return Bne.Emulate(ctx, instr); + + // Conversion + case Code.Conv_U8: + var type = ctx.Stack.Peek().GetType(); + if (type == typeof(int)) + ctx.Stack.Push((long)(int)ctx.Stack.Pop()); + else + throw new NotImplementedException(); + break; + + + // Misc + case Code.Nop: + // ...No-op + break; + + case Code.Ret: + return -1; + + default: + throw new EmulatorException($"Unhandled OpCode {instr.OpCode}"); + } + + return ++index; + } + + } +} diff --git a/MSILEmulator/Instructions/Arithmetic/Add.cs b/MSILEmulator/Instructions/Arithmetic/Add.cs new file mode 100644 index 0000000..40c22e0 --- /dev/null +++ b/MSILEmulator/Instructions/Arithmetic/Add.cs @@ -0,0 +1,20 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Arithmetic +{ + internal class Add + { + public static void Emulate(Context ctx) + { + int val2 = (int)ctx.Stack.Pop(); + int val1 = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val1 + val2); + } + } +} diff --git a/MSILEmulator/Instructions/Arithmetic/Mul.cs b/MSILEmulator/Instructions/Arithmetic/Mul.cs new file mode 100644 index 0000000..51bacd9 --- /dev/null +++ b/MSILEmulator/Instructions/Arithmetic/Mul.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Arithmetic +{ + internal class Mul + { + public static void Emulate(Context ctx) + { + int val2 = (int)ctx.Stack.Pop(); + int val1 = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val1 * val2); + } + } +} diff --git a/MSILEmulator/Instructions/Arithmetic/Neg.cs b/MSILEmulator/Instructions/Arithmetic/Neg.cs new file mode 100644 index 0000000..eb8cfdd --- /dev/null +++ b/MSILEmulator/Instructions/Arithmetic/Neg.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Arithmetic +{ + internal class Neg + { + public static void Emulate(Context ctx) + { + int val = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(-val); + } + } +} diff --git a/MSILEmulator/Instructions/Arithmetic/Sub.cs b/MSILEmulator/Instructions/Arithmetic/Sub.cs new file mode 100644 index 0000000..01aa7df --- /dev/null +++ b/MSILEmulator/Instructions/Arithmetic/Sub.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Arithmetic +{ + internal class Sub + { + public static void Emulate(Context ctx) + { + int val2 = (int)ctx.Stack.Pop(); + int val1 = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val1 - val2); + } + } +} diff --git a/MSILEmulator/Instructions/Branch/Beq.cs b/MSILEmulator/Instructions/Branch/Beq.cs new file mode 100644 index 0000000..0447db2 --- /dev/null +++ b/MSILEmulator/Instructions/Branch/Beq.cs @@ -0,0 +1,12 @@ +using dnlib.DotNet.Emit; + +namespace MSILEmulator.Instructions.Branch +{ + internal class Beq + { + public static int Emulate(Context ctx, Instruction instr) + { + return BranchCompare.Emulate(ctx, instr, static (left, right) => left == right, static (left, right) => left == right); + } + } +} diff --git a/MSILEmulator/Instructions/Branch/Bne.cs b/MSILEmulator/Instructions/Branch/Bne.cs new file mode 100644 index 0000000..ebf0c1a --- /dev/null +++ b/MSILEmulator/Instructions/Branch/Bne.cs @@ -0,0 +1,12 @@ +using dnlib.DotNet.Emit; + +namespace MSILEmulator.Instructions.Branch +{ + internal class Bne + { + public static int Emulate(Context ctx, Instruction instr) + { + return BranchCompare.Emulate(ctx, instr, static (left, right) => left != right, static (left, right) => left != right); + } + } +} diff --git a/MSILEmulator/Instructions/Branch/BranchCompare.cs b/MSILEmulator/Instructions/Branch/BranchCompare.cs new file mode 100644 index 0000000..237009f --- /dev/null +++ b/MSILEmulator/Instructions/Branch/BranchCompare.cs @@ -0,0 +1,30 @@ +using dnlib.DotNet.Emit; +using System; + +namespace MSILEmulator.Instructions.Branch +{ + internal static class BranchCompare + { + public static int Emulate(Context ctx, Instruction instr, Func compareInt, Func compareLong) + { + object val2 = ctx.Stack.Pop(); + object val1 = ctx.Stack.Pop(); + + if (val1.GetType() != val2.GetType()) + { + throw new EmulatorException($"Attempted to compare different types {val1.GetType()} != {val2.GetType()}"); + } + + bool branch = val1 switch + { + int leftInt => compareInt(leftInt, (int)val2), + long leftLong => compareLong(leftLong, (long)val2), + _ => throw new NotImplementedException($"Comparison of {val1.GetType().Name} not handled") + }; + + return branch + ? ctx.Offsets[((Instruction)instr.Operand).Offset] + : ctx.Offsets[instr.Offset] + 1; + } + } +} diff --git a/MSILEmulator/Instructions/Load/Ldarg.cs b/MSILEmulator/Instructions/Load/Ldarg.cs new file mode 100644 index 0000000..8bfbc29 --- /dev/null +++ b/MSILEmulator/Instructions/Load/Ldarg.cs @@ -0,0 +1,34 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Load +{ + internal class Ldarg + { + public static void Emulate(Context ctx, Instruction instr) + { + int index; + if (instr.Operand == null) + { + index = instr.OpCode.Code - Code.Ldarg_0; + } + else + { + index = (int)instr.Operand; + } + + if (ctx.Args.TryGetValue(index, out var val)) + { + ctx.Stack.Push(val); + } + else + { + ctx.Stack.Push(0); + } + } + } +} diff --git a/MSILEmulator/Instructions/Load/Ldelem.cs b/MSILEmulator/Instructions/Load/Ldelem.cs new file mode 100644 index 0000000..dfc9803 --- /dev/null +++ b/MSILEmulator/Instructions/Load/Ldelem.cs @@ -0,0 +1,20 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Load +{ + internal class Ldelem + { + public static void EmulateU4(Context ctx, Instruction instr) + { + int index = (int)ctx.Stack.Pop(); + var array = (uint[])ctx.Stack.Pop(); + + ctx.Stack.Push(unchecked((int)array[index])); + } + } +} diff --git a/MSILEmulator/Instructions/Load/Ldloc.cs b/MSILEmulator/Instructions/Load/Ldloc.cs new file mode 100644 index 0000000..d776d74 --- /dev/null +++ b/MSILEmulator/Instructions/Load/Ldloc.cs @@ -0,0 +1,35 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Load +{ + internal class Ldloc + { + public static void Emulate(Context ctx, Instruction instr) + { + int index; + if (instr.Operand == null) + { + index = instr.OpCode.Code - Code.Ldloc_0; + } + else + { + index = ((Local)instr.Operand).Index; + } + + if (ctx.Locals.TryGetValue(index, out var local)) + { + ctx.Stack.Push(local); + } + else + { + throw new NotImplementedException("Attempted to load local that hasn't been set."); + } + } + + } +} diff --git a/MSILEmulator/Instructions/Logic/And.cs b/MSILEmulator/Instructions/Logic/And.cs new file mode 100644 index 0000000..85809dc --- /dev/null +++ b/MSILEmulator/Instructions/Logic/And.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class And + { + public static void Emulate(Context ctx) + { + int val2 = (int)ctx.Stack.Pop(); + int val1 = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val1 & val2); + } + } +} diff --git a/MSILEmulator/Instructions/Logic/Not.cs b/MSILEmulator/Instructions/Logic/Not.cs new file mode 100644 index 0000000..cf68a69 --- /dev/null +++ b/MSILEmulator/Instructions/Logic/Not.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class Not + { + public static void Emulate(Context ctx) + { + int val = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(~val); + } + } +} diff --git a/MSILEmulator/Instructions/Logic/Or.cs b/MSILEmulator/Instructions/Logic/Or.cs new file mode 100644 index 0000000..9827c9c --- /dev/null +++ b/MSILEmulator/Instructions/Logic/Or.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class Or + { + public static void Emulate(Context ctx) + { + int val2 = (int)ctx.Stack.Pop(); + int val1 = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val1 | val2); + } + } +} diff --git a/MSILEmulator/Instructions/Logic/Shl.cs b/MSILEmulator/Instructions/Logic/Shl.cs new file mode 100644 index 0000000..133f3fd --- /dev/null +++ b/MSILEmulator/Instructions/Logic/Shl.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class Shl + { + public static void Emulate(Context ctx) + { + var shift = (int)ctx.Stack.Pop(); + var val = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val << shift); + } + } +} diff --git a/MSILEmulator/Instructions/Logic/Shr.cs b/MSILEmulator/Instructions/Logic/Shr.cs new file mode 100644 index 0000000..0d39d8f --- /dev/null +++ b/MSILEmulator/Instructions/Logic/Shr.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class Shr + { + public static void Emulate(Context ctx) + { + var shift = (int)ctx.Stack.Pop(); + var val = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val >> shift); + } + } +} diff --git a/MSILEmulator/Instructions/Logic/Shr_Un.cs b/MSILEmulator/Instructions/Logic/Shr_Un.cs new file mode 100644 index 0000000..8e1c450 --- /dev/null +++ b/MSILEmulator/Instructions/Logic/Shr_Un.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class Shr_Un + { + public static void Emulate(Context ctx) + { + var shift = (int)ctx.Stack.Pop(); + var val = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val >>> shift); + } + } +} diff --git a/MSILEmulator/Instructions/Logic/Xor.cs b/MSILEmulator/Instructions/Logic/Xor.cs new file mode 100644 index 0000000..0026897 --- /dev/null +++ b/MSILEmulator/Instructions/Logic/Xor.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class Xor + { + public static void Emulate(Context ctx) + { + int val2 = (int)ctx.Stack.Pop(); + int val1 = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val2 ^ val1); + } + } +} diff --git a/MSILEmulator/Instructions/Store/Stelem.cs b/MSILEmulator/Instructions/Store/Stelem.cs new file mode 100644 index 0000000..b606a9f --- /dev/null +++ b/MSILEmulator/Instructions/Store/Stelem.cs @@ -0,0 +1,21 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Store +{ + internal class Stelem + { + public static void Emulate(Context ctx, Instruction instr) + { + int value = (int)ctx.Stack.Pop(); + int index = (int)ctx.Stack.Pop(); + var array = (int[])ctx.Stack.Pop(); + + array[index] = value; + } + } +} diff --git a/MSILEmulator/Instructions/Store/Stloc.cs b/MSILEmulator/Instructions/Store/Stloc.cs new file mode 100644 index 0000000..6a56cf0 --- /dev/null +++ b/MSILEmulator/Instructions/Store/Stloc.cs @@ -0,0 +1,27 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Store +{ + internal class Stloc + { + public static void Emulate(Context ctx, Instruction instr) + { + int index; + if (instr.Operand == null) + { + index = instr.OpCode.Code - Code.Stloc_0; + } + else + { + index = ((Local)instr.Operand).Index; + } + + ctx.Locals[index] = ctx.Stack.Pop(); + } + } +} diff --git a/MSILEmulator/MSILEmulator.csproj b/MSILEmulator/MSILEmulator.csproj new file mode 100644 index 0000000..253b565 --- /dev/null +++ b/MSILEmulator/MSILEmulator.csproj @@ -0,0 +1,11 @@ + + + + net9.0 + + + + + + +