diff --git a/.gitignore b/.gitignore index 47d3ab486..d4c7a164b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ packages/ \.idea/ +.vscode/ bin/ obj/ diff --git a/Cpp2IL.Core.Tests/Graphing/BasicGraph.cs b/Cpp2IL.Core.Tests/Graphing/BasicGraph.cs index 5a7865d67..ec0e1ad28 100644 --- a/Cpp2IL.Core.Tests/Graphing/BasicGraph.cs +++ b/Cpp2IL.Core.Tests/Graphing/BasicGraph.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Cpp2IL.Core.Graphs; using Cpp2IL.Core.ISIL; @@ -10,29 +11,36 @@ public class BasicGraph [SetUp] public void Setup() { - var isilBuilder = new IsilBuilder(); + var instructions = new List(); + void Add(int index, OpCode opCode, params object[] operands) => instructions.Add(new Instruction(index, opCode, operands)); - isilBuilder.ShiftStack(0x0000, -40); - isilBuilder.Compare(0x0001, InstructionSetIndependentOperand.MakeRegister("test1"), InstructionSetIndependentOperand.MakeRegister("test2")); - isilBuilder.JumpIfNotEqual(0x0002, 0x0006); - isilBuilder.Move(0x0003, InstructionSetIndependentOperand.MakeRegister("test3"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.Call(0x0004, 0xDEADBEEF); - isilBuilder.Move(0x0005, InstructionSetIndependentOperand.MakeRegister("test4"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.Move(0x0006, InstructionSetIndependentOperand.MakeRegister("test5"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.Compare(0x0007, InstructionSetIndependentOperand.MakeRegister("test1"), InstructionSetIndependentOperand.MakeRegister("test2")); - isilBuilder.JumpIfEqual(0x0008, 0x000C); - isilBuilder.Compare(0x0009, InstructionSetIndependentOperand.MakeRegister("test1"), InstructionSetIndependentOperand.MakeRegister("test2")); - isilBuilder.JumpIfNotEqual(0x000A, 0x000C); - isilBuilder.Call(0x000B, 0xDEADBEEF); - isilBuilder.Move(0x000C, InstructionSetIndependentOperand.MakeRegister("test4"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.Move(0x000D, InstructionSetIndependentOperand.MakeRegister("test5"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.ShiftStack(0x000E, 40); - isilBuilder.Call(0x000F, 0xDEADBEEF); + Add(00, OpCode.ShiftStack, -40); + Add(01, OpCode.CheckEqual, new Register(null, "zf"), new Register(null, "test1"), new Register(null, "test2")); + Add(02, OpCode.Not, new Register(null, "zf"), new Register(null, "zf")); + Add(03, OpCode.ConditionalJump, 7, new Register(null, "zf")); + Add(04, OpCode.Move, new Register(null, "test3"), 0); + Add(05, OpCode.Call, 0xDEADBEEF); + Add(06, OpCode.Move, new Register(null, "test4"), 0); + Add(07, OpCode.Move, new Register(null, "test5"), 0); + Add(08, OpCode.CheckEqual, new Register(null, "zf"), new Register(null, "test1"), new Register(null, "test2")); + Add(09, OpCode.ConditionalJump, 14, new Register(null, "zf")); + Add(10, OpCode.CheckEqual, new Register(null, "zf"), new Register(null, "test1"), new Register(null, "test2")); + Add(11, OpCode.Not, new Register(null, "zf"), new Register(null, "zf")); + Add(12, OpCode.ConditionalJump, 14, new Register(null, "zf")); + Add(13, OpCode.Call, 0xDEADBEEF); + Add(14, OpCode.Move, new Register(null, "test4"), 0); + Add(15, OpCode.Move, new Register(null, "test5"), 0); + Add(16, OpCode.ShiftStack, 40); + Add(17, OpCode.Call, 0xDEADBEEF); - isilBuilder.FixJumps(); + foreach (var instruction in instructions) + { + if (instruction.OpCode != OpCode.Jump && instruction.OpCode != OpCode.ConditionalJump) + continue; + instruction.Operands[0] = instructions[(int)instruction.Operands[0]]; + } - graph = new(); - graph.Build(isilBuilder.BackingStatementList); + graph = new ISILControlFlowGraph(instructions); } [Test] diff --git a/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs b/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs index caaddd9e9..144a88a8e 100644 --- a/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs +++ b/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs @@ -1,7 +1,9 @@ +using System.Collections.Generic; using Cpp2IL.Core.Graphs; using Cpp2IL.Core.ISIL; namespace Cpp2IL.Core.Tests.Graphing; + public class ExceptionThrowingGraph { ISILControlFlowGraph graph; @@ -9,70 +11,79 @@ public class ExceptionThrowingGraph [SetUp] public void Setup() { - var isilBuilder = new IsilBuilder(); + var instructions = new List(); + void Add(int index, OpCode opCode, params object[] operands) => instructions.Add(new Instruction(index, opCode, operands)); - isilBuilder.Push(001, InstructionSetIndependentOperand.MakeRegister("sp"), InstructionSetIndependentOperand.MakeRegister("reg1")); - isilBuilder.ShiftStack(002, -80); - isilBuilder.Compare(003, InstructionSetIndependentOperand.MakeRegister("reg2"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.Move(004, InstructionSetIndependentOperand.MakeRegister("reg3"), InstructionSetIndependentOperand.MakeRegister("reg4")); - isilBuilder.JumpIfNotEqual(005, 9); - isilBuilder.Move(006, InstructionSetIndependentOperand.MakeRegister("reg5"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.Call(007, 0xDEADBEEF); - isilBuilder.Move(008, InstructionSetIndependentOperand.MakeRegister("reg6"), InstructionSetIndependentOperand.MakeImmediate(1)); - isilBuilder.Compare(009, InstructionSetIndependentOperand.MakeRegister("reg7"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.JumpIfEqual(010, 35); - isilBuilder.Move(011, InstructionSetIndependentOperand.MakeRegister("reg8"), InstructionSetIndependentOperand.MakeImmediate(1)); - isilBuilder.Move(012, InstructionSetIndependentOperand.MakeRegister("reg9"), InstructionSetIndependentOperand.MakeImmediate(2)); - isilBuilder.Move(013, InstructionSetIndependentOperand.MakeRegister("reg10"), InstructionSetIndependentOperand.MakeImmediate(3)); - isilBuilder.Move(014, InstructionSetIndependentOperand.MakeStack(0x40), InstructionSetIndependentOperand.MakeRegister("reg11")); - isilBuilder.Move(015, InstructionSetIndependentOperand.MakeRegister("reg12"), InstructionSetIndependentOperand.MakeImmediate("input")); - isilBuilder.Move(016, InstructionSetIndependentOperand.MakeStack(0x30), InstructionSetIndependentOperand.MakeRegister("reg13")); - isilBuilder.Compare(017, InstructionSetIndependentOperand.MakeRegister("reg14"), InstructionSetIndependentOperand.MakeImmediate(2)); - isilBuilder.Move(018, InstructionSetIndependentOperand.MakeStack(0x20), InstructionSetIndependentOperand.MakeRegister("reg15")); - isilBuilder.Move(019, InstructionSetIndependentOperand.MakeStack(0x40), InstructionSetIndependentOperand.MakeImmediate(1)); - isilBuilder.Move(020, InstructionSetIndependentOperand.MakeStack(0x38), InstructionSetIndependentOperand.MakeRegister("reg16")); - isilBuilder.JumpIfEqual(021, 25); - isilBuilder.Compare(022, InstructionSetIndependentOperand.MakeMemory(new IsilMemoryOperand(InstructionSetIndependentOperand.MakeRegister("reg17"), 224)), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.JumpIfNotEqual(023, 25); - isilBuilder.Call(024, 0xDEADBEEF); - isilBuilder.Move(025, InstructionSetIndependentOperand.MakeRegister("reg18"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.LoadAddress(026, InstructionSetIndependentOperand.MakeRegister("reg19"), InstructionSetIndependentOperand.MakeStack(0x20)); - isilBuilder.Move(027, InstructionSetIndependentOperand.MakeRegister("reg20"), InstructionSetIndependentOperand.MakeRegister("reg21")); - isilBuilder.Call(028, 0xDEADBEEF); - isilBuilder.Compare(029, InstructionSetIndependentOperand.MakeRegister("reg22"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.JumpIfEqual(030, 46); - isilBuilder.Move(031, InstructionSetIndependentOperand.MakeRegister("reg23"), InstructionSetIndependentOperand.MakeStack(0x20)); - isilBuilder.ShiftStack(032, 80); - isilBuilder.Pop(033, InstructionSetIndependentOperand.MakeRegister("sp"), InstructionSetIndependentOperand.MakeRegister("reg24")); - isilBuilder.Return(034, InstructionSetIndependentOperand.MakeRegister("reg25")); - isilBuilder.Move(035, InstructionSetIndependentOperand.MakeRegister("reg26"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.Call(036, 0xDEADBEEF); - isilBuilder.Move(037, InstructionSetIndependentOperand.MakeRegister("reg27"), InstructionSetIndependentOperand.MakeImmediate("input")); - isilBuilder.Move(038, InstructionSetIndependentOperand.MakeRegister("reg28"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.Move(039, InstructionSetIndependentOperand.MakeRegister("reg29"), InstructionSetIndependentOperand.MakeRegister("reg30")); - isilBuilder.Move(040, InstructionSetIndependentOperand.MakeRegister("reg31"), InstructionSetIndependentOperand.MakeRegister("reg32")); - isilBuilder.Call(041, 0xDEADBEEF); - isilBuilder.Move(042, InstructionSetIndependentOperand.MakeRegister("reg33"), InstructionSetIndependentOperand.MakeMemory(new IsilMemoryOperand(0xDEADBEEF))); - isilBuilder.Move(043, InstructionSetIndependentOperand.MakeRegister("reg34"), InstructionSetIndependentOperand.MakeRegister("reg35")); - isilBuilder.Call(044, 0xDEADBEEF); - isilBuilder.Interrupt(045); - isilBuilder.Move(046, InstructionSetIndependentOperand.MakeRegister("reg36"), InstructionSetIndependentOperand.MakeImmediate(0)); - isilBuilder.LoadAddress(047, InstructionSetIndependentOperand.MakeRegister("reg37"), InstructionSetIndependentOperand.MakeStack(0x20)); - isilBuilder.Call(048, 0xDEADBEEF); - isilBuilder.Move(049, InstructionSetIndependentOperand.MakeRegister("reg38"), InstructionSetIndependentOperand.MakeMemory(new IsilMemoryOperand(0x1809C39E0))); - isilBuilder.Move(050, InstructionSetIndependentOperand.MakeRegister("reg39"), InstructionSetIndependentOperand.MakeRegister("reg40")); - isilBuilder.Call(051, 0xDEADBEEF); + Add(001, OpCode.ShiftStack, -8); + Add(002, OpCode.Move, new StackOffset(0), new Register(null, "reg1")); + Add(003, OpCode.ShiftStack, -80); + Add(004, OpCode.CheckEqual, new Register(null, "zf"), new Register(null, "reg2"), 0); + Add(005, OpCode.Move, new Register(null, "reg3"), new Register(null, "reg4")); + Add(006, OpCode.Not, new Register(null, "zf"), new Register(null, "zf")); + Add(007, OpCode.ConditionalJump, 11, new Register(null, "zf")); + Add(008, OpCode.Move, new Register(null, "reg5"), 0); + Add(009, OpCode.Call, 0xDEADBEEF); + Add(010, OpCode.Move, new Register(null, "reg6"), 1); + Add(011, OpCode.CheckEqual, new Register(null, "zf"), new Register(null, "reg7"), 0); + Add(012, OpCode.ConditionalJump, 38, new Register(null, "zf")); + Add(013, OpCode.Move, new Register(null, "reg8"), 1); + Add(014, OpCode.Move, new Register(null, "reg9"), 2); + Add(015, OpCode.Move, new Register(null, "reg10"), 3); + Add(016, OpCode.Move, new StackOffset(0x40), new Register(null, "reg11")); + Add(017, OpCode.Move, new Register(null, "reg12"), "input"); + Add(018, OpCode.Move, new StackOffset(0x30), new Register(null, "reg13")); + Add(019, OpCode.CheckEqual, new Register(null, "zf"), new Register(null, "reg14"), 2); + Add(020, OpCode.Move, new StackOffset(0x20), new Register(null, "reg15")); + Add(021, OpCode.Move, new StackOffset(0x40), 1); + Add(022, OpCode.Move, new StackOffset(0x38), new Register(null, "reg16")); + Add(023, OpCode.ConditionalJump, 28, new Register(null, "zf")); + Add(024, OpCode.CheckEqual, new Register(null, "zf"), new MemoryOperand(new Register(null, "reg17"), addend: 224), 0); + Add(025, OpCode.Not, new Register(null, "zf"), new Register(null, "zf")); + Add(026, OpCode.ConditionalJump, 28, new Register(null, "zf")); + Add(027, OpCode.Call, 0xDEADBEEF); + Add(028, OpCode.Move, new Register(null, "reg18"), 0); + Add(029, OpCode.Move, new Register(null, "reg19"), new StackOffset(0x20)); + Add(030, OpCode.Move, new Register(null, "reg20"), new Register(null, "reg21")); + Add(031, OpCode.Call, 0xDEADBEEF); + Add(032, OpCode.CheckEqual, new Register(null, "zf"), new Register(null, "reg22"), 0); + Add(033, OpCode.ConditionalJump, 50, new Register(null, "zf")); + Add(034, OpCode.Move, new Register(null, "reg23"), new StackOffset(0x20)); + Add(035, OpCode.ShiftStack, 80); + Add(036, OpCode.Move, new Register(null, "reg24"), new StackOffset(0)); + Add(037, OpCode.ShiftStack, 8); + Add(038, OpCode.Return, new Register(null, "reg25")); + Add(039, OpCode.Move, new Register(null, "reg26"), 0); + Add(040, OpCode.Call, 0xDEADBEEF); + Add(041, OpCode.Move, new Register(null, "reg27"), "input"); + Add(042, OpCode.Move, new Register(null, "reg28"), 0); + Add(043, OpCode.Move, new Register(null, "reg29"), new Register(null, "reg30")); + Add(044, OpCode.Move, new Register(null, "reg31"), new Register(null, "reg32")); + Add(045, OpCode.Call, 0xDEADBEEF); + Add(046, OpCode.Move, new Register(null, "reg33"), new MemoryOperand(addend: 0xDEADBEEF)); + Add(047, OpCode.Move, new Register(null, "reg34"), new Register(null, "reg35")); + Add(048, OpCode.Call, 0xDEADBEEF); + Add(049, OpCode.Interrupt); + Add(050, OpCode.Move, new Register(null, "reg36"), 0); + Add(051, OpCode.Move, new Register(null, "reg37"), new StackOffset(0x20)); + Add(052, OpCode.Call, 0xDEADBEEF); + Add(053, OpCode.Move, new Register(null, "reg38"), new MemoryOperand(addend: 0x1809C39E0)); + Add(054, OpCode.Move, new Register(null, "reg39"), new Register(null, "reg40")); + Add(055, OpCode.Call, 0xDEADBEEF); - isilBuilder.FixJumps(); + foreach (var instruction in instructions) + { + if (instruction.OpCode != OpCode.Jump && instruction.OpCode != OpCode.ConditionalJump) + continue; + instruction.Operands[0] = instructions[(int)instruction.Operands[0]]; + } - graph = new(); - graph.Build(isilBuilder.BackingStatementList); + graph = new ISILControlFlowGraph(instructions); } [Test] public void VerifyNumberOfBlocks() { - Assert.That(graph.Blocks.Count == 18); + Assert.That(graph.Blocks.Count == 19); } [Test] diff --git a/Cpp2IL.Core/Analysis/LocalVariables.cs b/Cpp2IL.Core/Analysis/LocalVariables.cs new file mode 100644 index 000000000..59b432928 --- /dev/null +++ b/Cpp2IL.Core/Analysis/LocalVariables.cs @@ -0,0 +1,337 @@ +using System.Collections.Generic; +using System.Linq; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Analysis; + +public static class LocalVariables +{ + public static int MaxTypePropagationLoopCount = 5000; + + public static void CreateAll(MethodAnalysisContext method) + { + var cfg = method.ControlFlowGraph!; + + // Get all registers + var registers = new List(); + foreach (var instruction in cfg.Instructions) + registers.AddRange(GetRegisters(instruction)); + + // Remove duplicates + registers = registers.Distinct().ToList(); + + // Map those to locals + var locals = new Dictionary(); + for (var i = 0; i < registers.Count; i++) + { + var register = registers[i]; + locals.Add(register, new LocalVariable($"v{i}", register)); + } + + // Replace registers with locals + foreach (var instruction in cfg.Instructions) + { + for (var i = 0; i < instruction.Operands.Count; i++) + { + var operand = instruction.Operands[i]; + + if (operand is Register register) + instruction.Operands[i] = locals[register]; + + if (operand is MemoryOperand memory) + { + if (memory.Base != null) + { + var baseRegister = (Register)memory.Base; + memory.Base = locals[baseRegister]; + } + + if (memory.Index != null) + { + var index = (Register)memory.Index; + memory.Index = locals[index]; + } + + instruction.Operands[i] = memory; + } + } + } + + method.Locals = locals.Select(kv => kv.Value).ToList(); + + // Return local names + var retValIndex = 0; + for (var i = 0; i < cfg.Instructions.Count; i++) + { + var instruction = cfg.Instructions[i]; + if (instruction.OpCode != OpCode.Return || instruction.Operands.Count != 1) continue; + + var returnLocal = (LocalVariable)instruction.Sources[0]; + + returnLocal.Name = $"returnVal{retValIndex + 1}"; + returnLocal.IsReturn = true; + retValIndex++; + } + + // Add parameter names + var paramLocals = new List(); + + var operandOffset = method.IsStatic ? 0 : 1; // 'this' + + // 'this' param + if (!method.IsStatic && method.Locals.Count > 0) + { + var thisOperand = (Register)method.ParameterOperands[0]; + var thisLocal = method.Locals.FirstOrDefault(l => l.Register.Number == thisOperand.Number && l.Register.Version == -1); + + if (thisLocal != null) + { + thisLocal.Name = "this"; + thisLocal.IsThis = true; + paramLocals.Add(thisLocal); + } + else + { + method.AddWarning($"'this' local not found (operand: {thisOperand})"); + } + } + + // Check if method has MethodInfo* + var hasMethodInfo = (method.ParameterOperands.Count - operandOffset) > method.Parameters.Count; + var methodInfoIndex = method.ParameterOperands.Count - 1; + + // Add normal parameter names + for (var i = 0; i < method.Parameters.Count; i++) + { + var operandIndex = i + operandOffset; + if (hasMethodInfo && operandIndex == methodInfoIndex) + break; // Skip MethodInfo* + + if (operandIndex >= method.ParameterOperands.Count) + break; + + if (method.ParameterOperands[operandIndex] is not Register reg) + continue; + + var local = method.Locals.FirstOrDefault(l => l.Register.Number == reg.Number && l.Register.Version == -1); + if (local == null) + continue; + + local.Name = method.Parameters[i].ParameterName; + paramLocals.Add(local); + } + + // Add MethodInfo* + if (hasMethodInfo) + { + var methodInfoOperand = (Register)method.ParameterOperands[methodInfoIndex]; + var methodInfoLocal = method.Locals.FirstOrDefault(l => l.Register.Number == methodInfoOperand.Number && l.Register.Version == -1); + + if (methodInfoLocal != null) + { + methodInfoLocal.Name = "methodInfo"; + methodInfoLocal.IsMethodInfo = true; + paramLocals.Add(methodInfoLocal); + } + } + + method.ParameterLocals = paramLocals; + } + + public static void RemoveUnused(MethodAnalysisContext method) + { + var cfg = method.ControlFlowGraph!; + cfg.BuildUseDefLists(); + + for (var i = 0; i < method.Locals.Count; i++) + { + var local = method.Locals[i]; + + if (cfg.Blocks.Any(b => b.Use.Contains(local) || b.Def.Contains(local))) + continue; + + method.Locals.Remove(local); + i--; + } + } + + private static List GetRegisters(Instruction instruction) + { + var registers = new List(); + + foreach (var operand in instruction.Operands) + { + if (operand is Register register) + { + if (!registers.Contains(register)) + registers.Add(register); + } + + if (operand is MemoryOperand memory) + { + if (memory.Base != null) + { + var baseRegister = (Register)memory.Base; + if (!registers.Contains(baseRegister)) + registers.Add(baseRegister); + } + + if (memory.Index != null) + { + var index = (Register)memory.Index; + if (!registers.Contains(index)) + registers.Add(index); + } + } + } + + return registers; + } + + public static void PropagateTypes(MethodAnalysisContext method) + { + PropagateFromReturn(method); + PropagateFromParameters(method); + PropagateFromCallParameters(method); + PropagateThroughMoves(method); + } + + private static void PropagateThroughMoves(MethodAnalysisContext method) + { + var changed = true; + var loopCount = 0; + + while (changed) + { + changed = false; + loopCount++; + + if (MaxTypePropagationLoopCount != -1 && loopCount > MaxTypePropagationLoopCount) + throw new DecompilerException($"Type propagation through moves not settling! (looped {MaxTypePropagationLoopCount} times)"); + + foreach (var instruction in method.ControlFlowGraph!.Instructions) + { + if (instruction.OpCode != OpCode.Move) + continue; + + if (instruction.Operands[0] is LocalVariable destination && instruction.Operands[1] is LocalVariable source) + { + // Move ??, local + if (destination.Type == null && source.Type != null) + { + destination.Type = source.Type; + changed = true; + } + // Move local, ?? + else if (source.Type == null && destination.Type != null) + { + source.Type = destination.Type; + changed = true; + } + } + + if (instruction.Operands[0] is LocalVariable destination2 && instruction.Operands[1] is TypeAnalysisContext source2) + { + // Move ??, type + if (destination2.Type == null) + { + destination2.Type = source2; + changed = true; + } + } + } + } + } + + private static void PropagateFromCallParameters(MethodAnalysisContext method) + { + foreach (var instruction in method.ControlFlowGraph!.Instructions) + { + if (!instruction.IsCall) + continue; + + if (instruction.Operands[0] is not MethodAnalysisContext calledMethod) + continue; + + // Constructor, set return variable type + if (calledMethod.Name == ".ctor" || calledMethod.Name == ".cctor") + { + if (instruction.Destination is LocalVariable constructorReturn) + { + constructorReturn.Type = calledMethod.DeclaringType; + continue; + } + } + else // Return value + { + if (instruction.Destination is LocalVariable returnValue) + returnValue.Type = calledMethod.ReturnType; + } + + // 'this' param + if (!calledMethod.IsStatic) + { + if (instruction.Operands[instruction.OpCode == OpCode.CallVoid ? 1 : 2] is LocalVariable thisParam) + thisParam.Type = calledMethod.DeclaringType; + } + + // Set types + var paramOffset = calledMethod.IsStatic ? 1 : 2; + if (instruction.OpCode == OpCode.Call) // Skip return value + paramOffset += 1; + + for (var i = paramOffset; i < instruction.Operands.Count; i++) + { + var operand = instruction.Operands[i]; + + if (operand is LocalVariable local) + { + if ((i - paramOffset) > calledMethod.Parameters.Count - 1) // Probably MethodInfo* + continue; + + local.Type = calledMethod.Parameters[i - paramOffset].ParameterType; + } + } + } + } + + private static void PropagateFromParameters(MethodAnalysisContext method) + { + if (method.Parameters.Count == 0) + return; + + // 'this' + if (!method.IsStatic) + { + var thisLocal = method.ParameterLocals.FirstOrDefault(p => p.IsThis); + if (thisLocal != null) + thisLocal.Type = method.DeclaringType; + } + + // Normal params + var paramIndex = 0; + foreach (var local in method.ParameterLocals) + { + if (local.IsThis || local.IsMethodInfo) + continue; + + if (paramIndex >= method.Parameters.Count) + break; + + local.Type = method.Parameters[paramIndex].ParameterType; + paramIndex++; + } + } + + private static void PropagateFromReturn(MethodAnalysisContext method) + { + var returns = method.ControlFlowGraph!.Instructions.Where(i => i.OpCode == OpCode.Return); + + foreach (var instruction in returns) + { + if (instruction.Operands.Count == 1 && instruction.Operands[0] is LocalVariable local) + local.Type = method.ReturnType; + } + } +} diff --git a/Cpp2IL.Core/Analysis/MetadataResolver.cs b/Cpp2IL.Core/Analysis/MetadataResolver.cs new file mode 100644 index 000000000..e0fbf9248 --- /dev/null +++ b/Cpp2IL.Core/Analysis/MetadataResolver.cs @@ -0,0 +1,176 @@ +using System.Linq; +using Cpp2IL.Core.Extensions; +using Cpp2IL.Core.Graphs; +using Cpp2IL.Core.Il2CppApiFunctions; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Utils; +using LibCpp2IL; + +namespace Cpp2IL.Core.Analysis; + +public static class MetadataResolver +{ + public static void ResolveAll(MethodAnalysisContext method) + { + ResolveCalls(method); + ResolveFieldOffsets(method); + ResolveGetter(method); + ResolveStrings(method); + } + + private static void ResolveStrings(MethodAnalysisContext method) + { + foreach (var instruction in method.ControlFlowGraph!.Instructions) + { + if (instruction.OpCode != OpCode.Move) + continue; + + if ((instruction.Operands[0] is not LocalVariable) || (instruction.Operands[1] is not MemoryOperand memory)) + continue; + + if (memory.Base == null && memory.Index == null && memory.Scale == 0) + { + var stringLiteral = LibCpp2IlMain.GetLiteralByAddress((ulong)memory.Addend); + + if (stringLiteral == null) + { + // Try instead check if its type metadata usage + var metadataUsage = LibCpp2IlMain.GetTypeGlobalByAddress((ulong)memory.Addend); + if (metadataUsage != null && method.DeclaringType is not null) + { + var typeAnalysisContext = metadataUsage.ToContext(method.DeclaringType!.DeclaringAssembly); + if (typeAnalysisContext != null) + instruction.Operands[1] = typeAnalysisContext; + } + + continue; + } + + instruction.Operands[1] = stringLiteral; + } + } + } + + private static void ResolveFieldOffsets(MethodAnalysisContext method) + { + foreach (var instruction in method.ControlFlowGraph!.Instructions) + { + for (var i = 0; i < instruction.Operands.Count; i++) + { + var operand = instruction.Operands[i]; + + if (operand is not MemoryOperand memory) + continue; + + // Has to be [base (local) + addend (field offset)] + if (memory.Index != null || memory.Scale != 0) + continue; + + if (memory.Base is not LocalVariable local || local?.Type == null) + continue; + + var field = local.Type.Fields.FirstOrDefault(f => f.BackingData?.FieldOffset == memory.Addend); + + if (field == null) // TODO: Support nested fields (Field1.Field2.Field3) + continue; + + instruction.Operands[i] = new FieldReference(field, local, (int)memory.Addend); + } + } + } + + private static void ResolveCalls(MethodAnalysisContext method) + { + foreach (var block in method.ControlFlowGraph!.Blocks) + { + if (block.BlockType != BlockType.Call && block.BlockType != BlockType.TailCall) + continue; + + var callInstruction = block.Instructions[^1]; + var dest = callInstruction.Operands[0]; + + if (!dest.IsNumeric()) + continue; + + var target = (ulong)dest; + + var keyFunctionAddresses = method.AppContext.GetOrCreateKeyFunctionAddresses(); + + if (keyFunctionAddresses.IsKeyFunctionAddress(target)) + { + HandleKeyFunction(method.AppContext, callInstruction, target, keyFunctionAddresses); + continue; + } + + //Non-key function call. Try to find a single match + if (!method.AppContext.MethodsByAddress.TryGetValue(target, out var targetMethods)) + continue; + + if (targetMethods is not [{ } singleTargetMethod]) + continue; + + callInstruction.Operands[0] = singleTargetMethod; + } + + method.ControlFlowGraph.MergeCallBlocks(); + } + + private static void HandleKeyFunction(ApplicationAnalysisContext appContext, Instruction instruction, ulong target, BaseKeyFunctionAddresses kFA) + { + var method = ""; + if (target == kFA.il2cpp_codegen_initialize_method || target == kFA.il2cpp_codegen_initialize_runtime_metadata) + { + if (appContext.MetadataVersion < 27) + { + method = nameof(kFA.il2cpp_codegen_initialize_method); + } + else + { + method = nameof(kFA.il2cpp_codegen_initialize_runtime_metadata); + } + } + else + { + var pairs = kFA.Pairs.ToList(); + var key = pairs.FirstOrDefault(pair => pair.Value == target).Key; + if (key == null) + return; + method = key; + } + + if (method != "") + { + instruction.Operands[0] = method; + } + } + + // Because of il2cpp fields (like cctor_finished_or_no_cctor) [local @ reg+offset] sometimes can't be resolved, but this works for now + private static void ResolveGetter(MethodAnalysisContext method) + { + if (!method.Name.StartsWith("get_")) + return; + + // Default get: Return [this @ reg+offset] + var instructions = method.ControlFlowGraph!.Instructions; + if (instructions.Count == 1) + { + var instr = instructions[0]; + + if (instr.OpCode != OpCode.Return + || instr.Operands.Count < 1 + || instr.Operands[0] is not MemoryOperand memory + || memory.Index != null || memory.Scale != 0 + || memory.Base is not LocalVariable local) + return; + + var fieldName = $"<{method.Name[4..]}>k__BackingField"; + + var field = method.DeclaringType!.Fields.Find(f => f.Name == fieldName); + if (field == null) + return; + + instr.Operands[0] = new FieldReference(field, local, (int)memory.Addend); + } + } +} diff --git a/Cpp2IL.Core/Analysis/Simplifier.cs b/Cpp2IL.Core/Analysis/Simplifier.cs new file mode 100644 index 000000000..9ccdfda86 --- /dev/null +++ b/Cpp2IL.Core/Analysis/Simplifier.cs @@ -0,0 +1,231 @@ +using System.Collections.Generic; +using Cpp2IL.Core.Graphs; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Analysis; + +public static class Simplifier +{ + public static void Simplify(MethodAnalysisContext method) + { + var cfg = method.ControlFlowGraph!; + + InlineLocals(method); + + // Repeat until no change + var changed = true; + while (changed) + changed = InlineConstantsSinglePass(cfg); + + // More locals can now be inlined + InlineLocals(method); + + cfg.RemoveNops(); + cfg.RemoveEmptyBlocks(); + } + + private static bool InlineConstantsSinglePass(ISILControlFlowGraph graph) + { + var changed = false; + + var visited = new HashSet(); + var queue = new Queue(); + + queue.Enqueue(graph.EntryBlock); + visited.Add(graph.EntryBlock); + + while (queue.Count > 0) + { + var block = queue.Dequeue(); + + for (var i = 0; i < block.Instructions.Count; i++) + { + var instruction = block.Instructions[i]; + + // If it's move and it moves something to local, replace and remove it + if (instruction.OpCode == OpCode.Move && instruction.Operands[0] is LocalVariable local) + { + if (IsLocalUsedAfterInstruction(block, i + 1, local, out var usedByMemory)) + { + // This can't be inlined into memory operand + if (usedByMemory) continue; + + // Replace local + ReplaceLocalsUntilReassignment(block, i + 1, local, instruction.Operands[1]); + + // Change that move to nop + instruction.OpCode = OpCode.Nop; + instruction.Operands = []; + + changed = true; + } + } + } + + foreach (var successor in block.Successors) + { + if (visited.Add(successor)) + queue.Enqueue(successor); + } + } + + return changed; + } + + private static void InlineLocals(MethodAnalysisContext method) + { + var graph = method.ControlFlowGraph; + + var visited = new HashSet(); + var queue = new Queue(); + + queue.Enqueue(graph!.EntryBlock); + visited.Add(graph.EntryBlock); + + while (queue.Count > 0) + { + var block = queue.Dequeue(); + + for (var i = 0; i < block.Instructions.Count; i++) + { + var instruction = block.Instructions[i]; + + // If it's move and it moves local to local, replace and remove it + if (instruction.OpCode == OpCode.Move && instruction.Operands[0] is LocalVariable local && instruction.Operands[1] is LocalVariable source) + { + // Replace local with source + ReplaceLocalsUntilReassignment(block, i + 1, local, source); + + if (!method.ParameterLocals.Contains(local)) + method.Locals.Remove(local); + + // Change that move to nop + instruction.OpCode = OpCode.Nop; + instruction.Operands = []; + } + } + + foreach (var successor in block.Successors) + { + if (visited.Add(successor)) + queue.Enqueue(successor); + } + } + } + + private static void ReplaceLocalsUntilReassignment(Block block, int startIndex, LocalVariable local, object replacement) + { + var visited = new HashSet<(Block, int)>(); + + void ProcessBlock(Block currentBlock, int index) + { + var key = (currentBlock, index); + + if (!visited.Add(key)) + return; + + // Process instructions starting at the given index + for (var i = index; i < currentBlock.Instructions.Count; i++) + { + var instruction = currentBlock.Instructions[i]; + + // Stop on this branch when reassigned + if (instruction.Destination is LocalVariable destLocal && destLocal == local) + return; + + // Replace operands + for (var j = 0; j < instruction.Operands.Count; j++) + { + var operand = instruction.Operands[j]; + + if (operand is LocalVariable usedLocal && usedLocal == local) + instruction.Operands[j] = replacement; + + // [base] + if (operand is MemoryOperand { Index: null, Addend: 0, Scale: 0 } memoryLocal) + { + if (memoryLocal.Base is LocalVariable baseLocal && baseLocal == local) + instruction.Operands[j] = replacement; + } + + if (operand is MemoryOperand memory) + { + // [addend] + if (memory.IsConstant && (replacement is MemoryOperand { IsConstant: true } replacementMemory)) + memory.Addend = replacementMemory.Addend; + + if (memory.Base is LocalVariable baseLocal && baseLocal == local) + memory.Base = replacement; + + if (memory.Index is LocalVariable indexLocal && indexLocal == local) + memory.Index = replacement; + } + } + } + + // Process successors + foreach (var successor in currentBlock.Successors) + { + ProcessBlock(successor, 0); + } + } + + ProcessBlock(block, startIndex); + } + + private static bool IsLocalUsedAfterInstruction(Block block, int startIndex, LocalVariable local, out bool usedByMemory) + { + var visited = new HashSet<(Block, int)>(); + + bool ProcessBlock(Block currentBlock, int index, out bool usedByMemory2) + { + usedByMemory2 = false; + + var key = (currentBlock, index); + + if (!visited.Add(key)) + return false; + + // Process instructions + for (var i = index; i < currentBlock.Instructions.Count; i++) + { + var instruction = currentBlock.Instructions[i]; + + // Direct usage check + if (instruction.Sources.Contains(local)) + return true; + + // Used in memory operand + foreach (var source in instruction.Sources) + { + if (source is MemoryOperand memory) + { + if (memory.Base is LocalVariable memLocal && memLocal == local) + { + usedByMemory2 = true; + return true; + } + + if (memory.Index is LocalVariable memLocal2 && memLocal2 == local) + { + usedByMemory2 = true; + return true; + } + } + } + } + + // Process successors + foreach (var successor in currentBlock.Successors) + { + if (ProcessBlock(successor, 0, out usedByMemory2)) + return true; + } + + return false; + } + + return ProcessBlock(block, startIndex, out usedByMemory); + } +} diff --git a/Cpp2IL.Core/Analysis/SsaForm.cs b/Cpp2IL.Core/Analysis/SsaForm.cs new file mode 100644 index 000000000..388c4f8c0 --- /dev/null +++ b/Cpp2IL.Core/Analysis/SsaForm.cs @@ -0,0 +1,313 @@ +using System.Collections.Generic; +using System.Linq; +using Cpp2IL.Core.Graphs; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Analysis; + +public class SsaForm +{ + private Dictionary> _versions = new(); + private Dictionary _versionCount = new(); + private Dictionary> _blockOutVersions = new(); + + public static void Build(MethodAnalysisContext method) + { + var ssa = new SsaForm(); + + method.ControlFlowGraph!.BuildUseDefLists(); + + ssa._versions.Clear(); + ssa._versionCount.Clear(); + ssa._blockOutVersions.Clear(); + + var graph = method.ControlFlowGraph!; + var dominatorInfo = method.DominatorInfo!; + + ssa.ProcessBlock(graph.EntryBlock, dominatorInfo.DominanceTree); + ssa.InsertAllPhiFunctions(graph, dominatorInfo, method.ParameterOperands); + } + + private void InsertAllPhiFunctions(ISILControlFlowGraph graph, DominatorInfo dominance, List parameters) + { + // Check where registers are defined + var defSites = GetDefinitionSites(graph); + + // For each register + foreach (var entry in defSites) + { + var regNumber = entry.Key; + + var workList = new Queue(entry.Value); + var phiInserted = new HashSet(); + + while (workList.Count > 0) + { + var block = workList.Dequeue(); + + // For each dominance frontier block of the current block + if (!dominance.DominanceFrontier.TryGetValue(block, out var dfBlocks)) + continue; + + foreach (var dfBlock in dfBlocks) + { + // Already visited + if (phiInserted.Contains(dfBlock)) continue; + + // For each predecessor, get it's last register version + var sources = new List(); + foreach (var pred in dfBlock.Predecessors) + { + if (_blockOutVersions.TryGetValue(pred, out var mapping) + && mapping.TryGetValue(regNumber, out var versionedReg)) + { + sources.Add(versionedReg); + } + else + { + // It's not in predecessors so it's probably a parameter + var param = parameters.OfType().FirstOrDefault(p => p.Number == regNumber); + sources.Add(param); + } + } + + // Insert phi into the frontier block + InsertPhiFunction(sources, dfBlock); + phiInserted.Add(dfBlock); + + // If dfBlock doesn't define this register, add it to queue + var defines = dfBlock.Def.Any(operand => operand is Register r && r.Number == regNumber); + if (!defines) + workList.Enqueue(dfBlock); + } + } + } + } + + private static Dictionary> GetDefinitionSites(ISILControlFlowGraph graph) + { + // Check what registers are defined and where + var defSites = new Dictionary>(); + + foreach (var block in graph.Blocks) + { + for (var i = 0; i < block.Def.Count; i++) + { + var operand = block.Def[i]; + + if (operand is Register register) + { + if (!defSites.ContainsKey(register.Number)) + defSites[register.Number] = []; + defSites[register.Number].Add(block); + } + } + } + + return defSites; + } + + private void InsertPhiFunction(List sources, Block block) + { + // Create phi dest, src1, src2, etc. + var destination = GetNewVersion(sources[0]); + var phi = new Instruction(-1, OpCode.Phi, destination); + + foreach (var source in sources.Distinct()) + phi.Operands.Add(source); + + // Add it + block.Instructions.Insert(0, phi); + // Replace uses + ReplaceRegistersUntilReassignment(block, 1, destination); + } + + private static void ReplaceRegistersUntilReassignment(Block block, int startIndex, Register register) + { + for (var i = startIndex; i < block.Instructions.Count; i++) + { + var instruction = block.Instructions[i]; + + // Reassignment? + if (instruction.Destination is Register destination) + { + if (destination.Number == register.Number) + return; + } + + // Replace it + for (var j = 0; j < instruction.Operands.Count; j++) + { + var operand = instruction.Operands[j]; + + if (operand is Register register2) + { + if (register2.Number == register.Number) + instruction.Operands[j] = register; + } + + if (operand is MemoryOperand memory) + { + if (memory.Base != null) + { + var baseRegister = (Register)memory.Base; + + if (baseRegister.Number == register.Number) + memory.Base = register; + } + + if (memory.Index != null) + { + var index = (Register)memory.Index; + + if (index.Number == register.Number) + memory.Index = register; + } + + instruction.Operands[j] = memory; + } + } + } + } + + private Register GetNewVersion(Register old) + { + if (!_versionCount.ContainsKey(old.Number)) + { + // Params are version 0 + _versionCount.Add(old.Number, 0); + _versions.Add(old.Number, new Stack()); + _versions[old.Number].Push(old.Copy(0)); + } + + _versionCount[old.Number]++; + var newRegister = old.Copy(_versionCount[old.Number]); + _versions[old.Number].Push(newRegister); + return newRegister; + } + + private void ProcessBlock(Block block, Dictionary> dominanceTree) + { + foreach (var instruction in block.Instructions) + { + // Replace registers with SSA versions + for (var i = 0; i < instruction.Operands.Count; i++) + { + if (instruction.Operands[i] is Register register) + { + if (_versions.TryGetValue(register.Number, out var versions)) + instruction.Operands[i] = register.Copy(versions.Peek().Version); + } + + if (instruction.Operands[i] is MemoryOperand memory) + { + if (memory.Base != null) + { + var baseRegister = (Register)memory.Base; + + if (_versions.TryGetValue(baseRegister.Number, out var versions)) + memory.Base = baseRegister.Copy(versions.Peek().Version); + } + + if (memory.Index != null) + { + var indexRegister = (Register)memory.Index; + + if (_versions.TryGetValue(indexRegister.Number, out var versions)) + memory.Index = indexRegister.Copy(versions.Peek().Version); + } + + instruction.Operands[i] = memory; + } + } + + // Create new version + if (instruction.Destination is Register destination) + instruction.Destination = GetNewVersion(destination); + } + + // Record last register version + var outMapping = new Dictionary(); + foreach (var kvp in _versions) + { + if (kvp.Value.Count > 0) + outMapping[kvp.Key] = kvp.Value.Peek(); + } + + _blockOutVersions[block] = outMapping; + + // Process children in the tree + if (dominanceTree.TryGetValue(block, out var children)) + { + foreach (var child in children) + ProcessBlock(child, dominanceTree); + } + + // Remove registers from versions but not from count + foreach (var instruction in block.Instructions.Where(i => i.Destination is Register)) + { + var register = (Register)instruction.Destination!; + _versions.FirstOrDefault(kv => kv.Key == register.Number).Value.Pop(); + } + } + + public static void Remove(MethodAnalysisContext method) + { + var cfg = method.ControlFlowGraph!; + + foreach (var block in cfg.Blocks) + { + // Get all phis + var phiInstructions = block.Instructions + .Where(i => i.OpCode == OpCode.Phi) + .ToList(); + + if (phiInstructions.Count == 0) continue; + + foreach (var predecessor in block.Predecessors) + { + if (predecessor.Instructions.Count == 0) + continue; + + predecessor.Instructions.RemoveAt(0); + var moves = new List(); + + foreach (var phi in phiInstructions) + { + var result = (LocalVariable)phi.Operands[0]!; + var sources = phi.Operands.Skip(1).Cast().ToList(); + + var predIndex = block.Predecessors.IndexOf(predecessor); + + if (predIndex < 0 || predIndex >= sources.Count) + continue; + + var source = sources[predIndex]; + + // Add move for it + moves.Add(new Instruction(-1, OpCode.Move, result, source)); + } + + // Add all of those moves + if (predecessor.Instructions.Count == 0) + predecessor.Instructions = moves; + else + predecessor.Instructions.InsertRange(predecessor.Instructions.Count - (predecessor.Instructions.Count == 1 ? 1 : 2), moves); + } + + // Remove all phis + foreach (var instruction in block.Instructions) + { + if (instruction.OpCode == OpCode.Phi) + { + instruction.OpCode = OpCode.Nop; + instruction.Operands = []; + } + } + } + + cfg.RemoveNops(); + cfg.RemoveEmptyBlocks(); + } +} diff --git a/Cpp2IL.Core/Analysis/StackAnalyzer.cs b/Cpp2IL.Core/Analysis/StackAnalyzer.cs new file mode 100644 index 000000000..8df9fec82 --- /dev/null +++ b/Cpp2IL.Core/Analysis/StackAnalyzer.cs @@ -0,0 +1,170 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Cpp2IL.Core.Graphs; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Analysis; + +public class StackAnalyzer +{ + [DebuggerDisplay("Size = {Size}")] + private class StackState + { + public int Size; + public StackState Copy() => new() { Size = this.Size }; + } + + private Dictionary _inComingState = []; + private Dictionary _outGoingState = []; + private Dictionary _instructionState = []; + + /// + /// Max allowed count of blocks to visit (-1 for no limit). + /// + public static int MaxBlockVisitCount = 2000; + + public static void Analyze(MethodAnalysisContext method) + { + var analyzer = new StackAnalyzer(); + + var graph = method.ControlFlowGraph!; + graph.RemoveUnreachableBlocks(); // Without this indirect jumps (in try catch i think) cause some weird stuff + + analyzer._inComingState = new Dictionary { { graph.EntryBlock, new StackState() } }; + + analyzer.TraverseGraph(graph.EntryBlock); + + var outDelta = analyzer._outGoingState[graph.ExitBlock]; + if (outDelta.Size != 0) + { + var outText = outDelta.Size < 0 ? "-" + (-outDelta.Size).ToString("X") : outDelta.Size.ToString("X"); + method.AddWarning($"Method ends with non empty stack ({outText}), the output could be wrong!"); + } + + analyzer.CorrectOffsets(graph); + ReplaceStackWithRegisters(method); + + graph.RemoveNops(); + graph.RemoveEmptyBlocks(); + } + + private void CorrectOffsets(ISILControlFlowGraph graph) + { + foreach (var block in graph.Blocks) + { + foreach (var instruction in block.Instructions) + { + if (instruction is { OpCode: OpCode.ShiftStack }) + { + // Nop the shift stack instruction + instruction.OpCode = OpCode.Nop; + instruction.Operands = []; + } + + // Correct offset for stack operands. + for (var i = 0; i < instruction.Operands.Count; i++) + { + var op = instruction.Operands[i]; + + if (op is StackOffset offset) + { + var state = _instructionState[instruction].Size; + var actual = state + offset.Offset; + instruction.Operands[i] = new StackOffset(actual); + } + } + } + } + } + + // Traverse the graph and calculate the stack state for each block and instruction + private void TraverseGraph(Block block, int visitedBlockCount = 0) + { + // Copy current state + var incomingState = _inComingState[block]; + var currentState = incomingState.Copy(); + + // Process instructions + foreach (var instruction in block.Instructions) + { + _instructionState[instruction] = currentState; + + if (instruction.OpCode == OpCode.ShiftStack) + { + var offset = (int)instruction.Operands[0]; + currentState = currentState.Copy(); + currentState.Size += offset; + } + else if (block.Instructions[block.Instructions.Count - 1] == instruction && block.BlockType == BlockType.TailCall) + { + // Tail calls clear stack + currentState = currentState.Copy(); + currentState.Size = 0; + } + } + + // Tail calls clear stack + if (block.BlockType == BlockType.TailCall) + currentState.Size = 0; + + _outGoingState[block] = currentState; + + visitedBlockCount++; + + if (MaxBlockVisitCount != -1 && visitedBlockCount > MaxBlockVisitCount) + throw new DecompilerException($"Stack state not settling! ({MaxBlockVisitCount} blocks already visited)"); + + // Visit successors + foreach (var successor in block.Successors) + { + // Already visited + if (_inComingState.TryGetValue(successor, out var existingState)) + { + if (existingState.Size != currentState.Size) + { + _inComingState[successor] = currentState.Copy(); + TraverseGraph(successor, visitedBlockCount + 1); + } + } + else + { + // Set incoming delta and add to queue + _inComingState[successor] = currentState.Copy(); + TraverseGraph(successor, visitedBlockCount + 1); + } + } + } + + private static void ReplaceStackWithRegisters(MethodAnalysisContext method) + { + var instructions = method.ControlFlowGraph!.Instructions; + + // Replace stack offset operands + foreach (var instruction in instructions) + { + for (var i = 0; i < instruction.Operands.Count; i++) + { + var operand = instruction.Operands[i]; + + if (operand is StackOffset offset) + { + var name = offset.Offset < 0 ? $"stack_-{-offset.Offset:X}" : $"stack_{offset.Offset:X}"; + instruction.Operands[i] = new Register(null, name); + } + } + } + + // Replace params + for (var i = 0; i < method.ParameterOperands.Count; i++) + { + var parameter = method.ParameterOperands[i]; + + if (parameter is StackOffset offset) + { + var name = offset.Offset < 0 ? $"stack_-{-offset.Offset:X}" : $"stack_{offset.Offset:X}"; + method.ParameterOperands[i] = new Register(null, name); + } + } + } +} diff --git a/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs b/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs index b400ca1bb..cd036485c 100644 --- a/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs +++ b/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs @@ -31,8 +31,15 @@ public abstract class Cpp2IlInstructionSet /// analyzed. /// /// The method to convert to ISIL - /// An array of structs representing the functionality of this method in an instruction-set-independent manner. - public abstract List GetIsilFromMethod(MethodAnalysisContext context); + /// An array of structs representing the functionality of this method in an instruction-set-independent manner. + public abstract List GetIsilFromMethod(MethodAnalysisContext context); + + /// + /// Returns operands that represent the parameters. + /// + /// The method to get parameters from. + /// A list of parameter operands. + public abstract List GetParameterOperandsFromMethod(MethodAnalysisContext context); /// /// Create and populate a BaseKeyFunctionAddresses object which can then be populated. diff --git a/Cpp2IL.Core/Api/ICSharpSourceToken.cs b/Cpp2IL.Core/Api/ICSharpSourceToken.cs deleted file mode 100644 index 044317c7d..000000000 --- a/Cpp2IL.Core/Api/ICSharpSourceToken.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Cpp2IL.Core.Api; - -public interface ICSharpSourceToken -{ - /// - /// Returns a string of C# source code which would be equivalent to this token. For example, for types, this would be the type name, for generic types, the full type name with generic arguments, etc. - /// - public string GetCSharpSourceString(); -} diff --git a/Cpp2IL.Core/DecompilerException.cs b/Cpp2IL.Core/DecompilerException.cs new file mode 100644 index 000000000..b503633f2 --- /dev/null +++ b/Cpp2IL.Core/DecompilerException.cs @@ -0,0 +1,8 @@ +namespace Cpp2IL.Core; + +public class DecompilerException : System.Exception +{ + public DecompilerException() { } + public DecompilerException(string message) : base("Decompilation failed: " + message) { } + public DecompilerException(string message, System.Exception inner) : base("Decompilation failed: " + message, inner) { } +} diff --git a/Cpp2IL.Core/Extensions/MiscExtensions.cs b/Cpp2IL.Core/Extensions/MiscExtensions.cs index f4bcea597..7ab466642 100644 --- a/Cpp2IL.Core/Extensions/MiscExtensions.cs +++ b/Cpp2IL.Core/Extensions/MiscExtensions.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Cpp2IL.Core.ISIL; using Gee.External.Capstone.Arm; using Gee.External.Capstone.Arm64; using Iced.Intel; @@ -13,7 +12,7 @@ namespace Cpp2IL.Core.Extensions; public static class MiscExtensions { - public static InstructionSetIndependentOperand MakeIndependent(this Register reg) => InstructionSetIndependentOperand.MakeRegister(reg.ToString().ToLower()); + public static object MakeIndependent(this Register reg) => new ISIL.Register(null, reg.ToString().ToLower()); public static ulong GetImmediateSafe(this Instruction instruction, int op) => instruction.GetOpKind(op).IsImmediate() ? instruction.GetImmediate(op) : 0; @@ -74,7 +73,7 @@ public static List Clone(this List original) { var arr = new T[original.Count]; original.CopyTo(arr, 0); - return [..arr]; + return [.. arr]; } public static Dictionary Clone(this Dictionary original) where T1 : notnull @@ -180,4 +179,18 @@ public static bool BitsAreEqual(this BitArray first, BitArray second) return !areDifferent; } + + public static bool IsNumeric(this object value) + { + if (value == null) return false; + + return Type.GetTypeCode(value.GetType()) switch + { + TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 or + TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16 or + TypeCode.Int32 or TypeCode.Int64 or TypeCode.Decimal or + TypeCode.Double or TypeCode.Single => true, + _ => false, + }; + } } diff --git a/Cpp2IL.Core/Graphs/Block.cs b/Cpp2IL.Core/Graphs/Block.cs index 0bfc7dfa4..132f138c3 100644 --- a/Cpp2IL.Core/Graphs/Block.cs +++ b/Cpp2IL.Core/Graphs/Block.cs @@ -11,20 +11,22 @@ public class Block public List Predecessors = []; public List Successors = []; - public List isilInstructions = []; + public List Use = []; + public List Def = []; + + public List Instructions = []; public int ID { get; set; } = -1; public bool Dirty { get; set; } public bool Visited = false; - public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine("Type: " + BlockType); + stringBuilder.AppendLine($"{BlockType} {ID}"); stringBuilder.AppendLine(); - foreach (var instruction in isilInstructions) + foreach (var instruction in Instructions) { stringBuilder.AppendLine(instruction.ToString()); } @@ -32,44 +34,29 @@ public override string ToString() return stringBuilder.ToString(); } - public void AddInstruction(InstructionSetIndependentInstruction instruction) - { - isilInstructions.Add(instruction); - } + public void AddInstruction(Instruction instruction) => Instructions.Add(instruction); - public void CaculateBlockType() + public void CalculateBlockType() { - // This enum is kind of redundant, can be possibly swapped for IsilFlowControl and no need for BlockType? - if (isilInstructions.Count > 0) + if (Instructions.Count <= 0) + return; + + var instruction = Instructions.Last(); + + BlockType = instruction.OpCode switch + { + OpCode.Jump => BlockType.OneWay, + OpCode.ConditionalJump => BlockType.TwoWay, + OpCode.IndirectJump => BlockType.NWay, + OpCode.Call or OpCode.CallVoid => BlockType.Call, + OpCode.Return => BlockType.Return, + _ => BlockType.Fall, + }; + + if (BlockType == BlockType.Call && Successors.Count > 0) { - var instruction = isilInstructions.Last(); - switch (instruction.FlowControl) - { - case IsilFlowControl.UnconditionalJump: - BlockType = BlockType.OneWay; - break; - case IsilFlowControl.ConditionalJump: - BlockType = BlockType.TwoWay; - break; - case IsilFlowControl.IndexedJump: - BlockType = BlockType.NWay; - break; - case IsilFlowControl.MethodCall: - BlockType = BlockType.Call; - break; - case IsilFlowControl.MethodReturn: - BlockType = BlockType.Return; - break; - case IsilFlowControl.Interrupt: - BlockType = BlockType.Interrupt; - break; - case IsilFlowControl.Continue: - BlockType = BlockType.Fall; - break; - default: - BlockType = BlockType.Unknown; - break; - } + if (Successors[0].BlockType == BlockType.Exit) + BlockType = BlockType.TailCall; } } } diff --git a/Cpp2IL.Core/Graphs/BlockType.cs b/Cpp2IL.Core/Graphs/BlockType.cs index e6ae9b6fc..b1eac4fe9 100644 --- a/Cpp2IL.Core/Graphs/BlockType.cs +++ b/Cpp2IL.Core/Graphs/BlockType.cs @@ -6,17 +6,9 @@ public enum BlockType : byte TwoWay, // etc. Jumps conditionally to two blocks NWay, // switch statement nonsense I think Call, // Block finishes with call + TailCall, // Block finishes with tail call, clears stack, and returns Return, // Block finishes with return - - // we fall into next block, for example block A has - // mov reg1, reg2 - // mov reg2, reg3 - // - // block b has - // mov reg3, reg4 - // mov reg2, reg4 - // and another block finishes with a jump to start instruction of block b meaning block a falls into b (bad explanation) - Fall, + Fall, // Falls to the next block, like if the block has more than one predecessor and this is one of those // Block type is not known yet Unknown, diff --git a/Cpp2IL.Core/Graphs/DominatorInfo.cs b/Cpp2IL.Core/Graphs/DominatorInfo.cs new file mode 100644 index 000000000..52a7e2f4c --- /dev/null +++ b/Cpp2IL.Core/Graphs/DominatorInfo.cs @@ -0,0 +1,226 @@ +using System.Collections.Generic; + +namespace Cpp2IL.Core.Graphs; + +public class DominatorInfo +{ + public Dictionary> DominanceTree = new(); + public Dictionary> DominanceFrontier = new(); + public Dictionary ImmediateDominators = new(); + public Dictionary ImmediatePostDominators = new(); + public Dictionary> PostDominators = new(); + public Dictionary> Dominators = new(); + + public DominatorInfo(ISILControlFlowGraph graph) + { + CalculateDominators(graph); + CalculatePostDominators(graph); + CalculateImmediateDominators(graph); + CalculateImmediatePostDominators(graph); + CalculateDominanceFrontiers(graph); + BuildDominanceTree(); + } + + public bool Dominates(Block a, Block b) + { + if (a == b) + return true; + if (Dominators.ContainsKey(b) && Dominators.ContainsKey(a)) + return Dominators[b].Contains(a); + return false; + } + + private void BuildDominanceTree() + { + foreach (var block in ImmediateDominators.Keys) + { + var immediateDominator = ImmediateDominators[block]; + if (immediateDominator == null) continue; + + if (!DominanceTree.ContainsKey(immediateDominator)) + DominanceTree[immediateDominator] = []; + + DominanceTree[immediateDominator].Add(block); + } + } + + private void CalculateDominators(ISILControlFlowGraph graph) + { + Dominators.Clear(); + + // Entry block dominates itself, all others are initialized with all blocks + foreach (var block in graph.Blocks) + { + if (block == graph.EntryBlock) + Dominators[block] = [block]; + else + Dominators[block] = new HashSet(graph.Blocks); + } + + var changed = true; + + // Get dominators + while (changed) + { + changed = false; + + foreach (var block in graph.Blocks) + { + if (block == graph.EntryBlock) + continue; + + var tempDoms = block.Predecessors.Count == 0 + ? new HashSet() + : new HashSet(Dominators[block.Predecessors[0]]); + + for (var i = 1; i < block.Predecessors.Count; i++) + tempDoms.IntersectWith(Dominators[block.Predecessors[i]]); + + tempDoms.Add(block); + + if (!tempDoms.SetEquals(Dominators[block])) + { + Dominators[block] = tempDoms; + changed = true; + } + } + } + } + + private void CalculatePostDominators(ISILControlFlowGraph graph) + { + PostDominators.Clear(); + + foreach (var block in graph.Blocks) + { + if (block == graph.ExitBlock) + PostDominators[block] = [block]; + else + PostDominators[block] = new HashSet(graph.Blocks); + } + + var changed = true; + + while (changed) + { + changed = false; + + foreach (var block in graph.Blocks) + { + if (block.Successors.Count == 0 && block != graph.ExitBlock) + continue; + + var tempPostDoms = block.Successors.Count == 0 + ? new HashSet() + : new HashSet(PostDominators[block.Successors[0]]); + + for (var i = 1; i < block.Successors.Count; i++) + tempPostDoms.IntersectWith(PostDominators[block.Successors[i]]); + + tempPostDoms.Add(block); + + if (!tempPostDoms.SetEquals(PostDominators[block])) + { + PostDominators[block] = tempPostDoms; + changed = true; + } + } + } + } + + private void CalculateDominanceFrontiers(ISILControlFlowGraph graph) + { + DominanceFrontier.Clear(); + + foreach (var block in graph.Blocks) + DominanceFrontier[block] = []; + + foreach (var block in graph.Blocks) + { + if (block.Predecessors.Count < 2) continue; + + foreach (var predecessor in block.Predecessors) + { + var runner = predecessor; + + while (runner != ImmediateDominators[block] && runner != null) + { + DominanceFrontier[runner].Add(block); + runner = ImmediateDominators[runner]; + } + } + } + } + + private void CalculateImmediatePostDominators(ISILControlFlowGraph graph) + { + foreach (var block in graph.Blocks) + { + if (block.Successors.Count == 0 || block == graph.ExitBlock) + { + ImmediatePostDominators[block] = null; + continue; + } + + foreach (var candidate in PostDominators[block]) + { + if (candidate == block) + continue; + + if (PostDominators[block].Count == 2) + { + ImmediatePostDominators[block] = candidate; + break; + } + + foreach (var otherCandidate in PostDominators[block]) + { + if (candidate == otherCandidate || candidate == block) + continue; + + if (!PostDominators[otherCandidate].Contains(candidate)) + { + ImmediatePostDominators[block] = candidate; + break; + } + } + } + } + } + + private void CalculateImmediateDominators(ISILControlFlowGraph graph) + { + foreach (var block in graph.Blocks) + ImmediateDominators[block] = null; + + foreach (var block in graph.Blocks) + { + if (block.Predecessors.Count == 0 || block == graph.EntryBlock) + continue; + + foreach (var candidate in Dominators[block]) + { + if (candidate == block) + continue; + + if (Dominators[block].Count == 2) + { + ImmediateDominators[block] = candidate; + break; + } + + foreach (var otherCandidate in Dominators[block]) + { + if (candidate == otherCandidate || candidate == block) + continue; + + if (!Dominators[otherCandidate].Contains(candidate)) + { + ImmediateDominators[block] = candidate; + break; + } + } + } + } + } +} diff --git a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs index c4ac34a25..c33e36ad7 100644 --- a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs +++ b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs @@ -1,44 +1,79 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; -using System.Text; using Cpp2IL.Core.ISIL; namespace Cpp2IL.Core.Graphs; public class ISILControlFlowGraph { - public Block EntryBlock => entryBlock; - public Block ExitBlock => exitBlock; - public int Count => blockSet.Count; - public Collection Blocks => blockSet; + public Block EntryBlock; + public Block ExitBlock; + public int Count => Blocks.Count; + public List Blocks; + public List Instructions + { + get + { + // BFS search + var visited = new HashSet(); + var queue = new Queue(); + var result = new List(); + + queue.Enqueue(EntryBlock); + + while (queue.Count > 0) + { + var current = queue.Dequeue(); + + if (!visited.Add(current)) + continue; + + result.AddRange(current.Instructions); + + foreach (var successor in current.Successors) + { + if (!visited.Contains(successor)) + queue.Enqueue(successor); + } + } + + return result; // Should this be cached? + } + } private int idCounter; - private Collection blockSet; - private Block exitBlock; - private Block entryBlock; - public ISILControlFlowGraph() + public ISILControlFlowGraph(List instructions) { - entryBlock = new Block() { ID = idCounter++ }; - entryBlock.BlockType = BlockType.Entry; - exitBlock = new Block() { ID = idCounter++ }; - exitBlock.BlockType = BlockType.Exit; - blockSet = + EntryBlock = new Block + { + ID = idCounter++, + BlockType = BlockType.Entry + }; + + ExitBlock = new Block + { + ID = idCounter++, + BlockType = BlockType.Exit + }; + + Blocks = [ - entryBlock, - exitBlock + EntryBlock, + ExitBlock ]; + + Build(instructions); } - private bool TryGetTargetJumpInstructionIndex(InstructionSetIndependentInstruction instruction, out uint jumpInstructionIndex) + private bool TryGetTargetJumpInstructionIndex(Instruction instruction, out int jumpInstructionIndex) { jumpInstructionIndex = 0; try { - jumpInstructionIndex = ((InstructionSetIndependentInstruction)instruction.Operands[0].Data).InstructionIndex; + jumpInstructionIndex = ((Instruction)instruction.Operands[0]).Index; return true; } catch @@ -49,137 +84,312 @@ private bool TryGetTargetJumpInstructionIndex(InstructionSetIndependentInstructi return false; } + public void RemoveUnreachableBlocks() + { + if (Blocks.Count == 0) + return; + + // Get blocks reachable from entry + var reachable = new List(); + var visited = new List { EntryBlock }; + reachable.Add(EntryBlock); + + var total = 0; + while (total < reachable.Count) + { + var block = reachable[total]; + total++; + + foreach (var successor in block.Successors) + { + if (visited.Contains(successor)) + continue; + visited.Add(successor); + reachable.Add(successor); + } + } + + // Get unreachable blocks + var unreachable = Blocks.Where(block => !visited.Remove(block)).ToList(); + + // Remove those + foreach (var block in unreachable) + { + // Don't remove entry or exit + if (block == EntryBlock || block == ExitBlock) + continue; + + block.Successors.Clear(); + Blocks.Remove(block); + } + } + + public void RemoveNops() + { + var usedAsTarget = new HashSet(); + + // Get all instructions used as branch targets + foreach (var block in Blocks) + { + foreach (var instr in block.Instructions) + { + foreach (var operand in instr.Operands) + { + if (operand is Instruction target) + usedAsTarget.Add(target); + } + } + } + + // Build replacement map for NOPs that are safe to replace + var instructionReplacement = new Dictionary(); + foreach (var block in Blocks) + { + Instruction? replacement = null; + for (var i = block.Instructions.Count - 1; i >= 0; i--) + { + var instr = block.Instructions[i]; + if (instr.OpCode == OpCode.Nop) + { + if (replacement != null && !usedAsTarget.Contains(instr)) + instructionReplacement[instr] = replacement; + } + else + { + replacement = instr; + } + } + } + + // Update operands + foreach (var block in Blocks) + { + foreach (var instr in block.Instructions) + { + for (var i = 0; i < instr.Operands.Count; i++) + { + if (instr.Operands[i] is Instruction target && instructionReplacement.TryGetValue(target, out var newTarget)) + instr.Operands[i] = newTarget; + } + } + } + + // Remove NOPs + foreach (var block in Blocks) + { + block.Instructions.RemoveAll(i => i.OpCode == OpCode.Nop && !usedAsTarget.Contains(i)); + } + } - public void Build(List instructions) + public void RemoveEmptyBlocks() + { + var toRemove = new List(); + + foreach (var block in Blocks) + { + if (block == EntryBlock || block == ExitBlock) + continue; + + if (block.Instructions.Count == 0) + { + // Redirect predecessors to successors + foreach (var pred in block.Predecessors) + { + pred.Successors.Remove(block); + foreach (var succ in block.Successors) + { + if (!pred.Successors.Contains(succ)) + pred.Successors.Add(succ); + } + } + + // Redirect successors to predecessors + foreach (var succ in block.Successors) + { + succ.Predecessors.Remove(block); + foreach (var pred in block.Predecessors) + { + if (!succ.Predecessors.Contains(pred)) + succ.Predecessors.Add(pred); + } + } + + toRemove.Add(block); + } + } + + foreach (var block in toRemove) + Blocks.Remove(block); + } + + public void BuildUseDefLists() + { + foreach (var block in Blocks) + { + var use = new List(); + var def = new List(); + + foreach (var instruction in block.Instructions) + { + foreach (var operand in instruction.Sources.Where(operand => !use.Contains(operand))) + use.Add(operand); + + if (instruction.Destination != null && !def.Contains(instruction.Destination)) + def.Add(instruction.Destination); + } + + block.Use = use; + block.Def = def; + } + } + + public void MergeCallBlocks() + { + var toRemove = new List(); + + for (var i = 0; i < Blocks.Count; i++) + { + var block = Blocks[i]; + if (block.BlockType != BlockType.Call) continue; + + if (block.Successors.Count != 1) + continue; + + var nextBlock = block.Successors[0]; + + // make sure that the next block only has one predecessor (this) + if (nextBlock.Predecessors.Count != 1 || nextBlock.Predecessors[0] != block) + continue; + + // merge instructions + block.Instructions.AddRange(nextBlock.Instructions); + block.Successors = nextBlock.Successors; + + // fix up successors predecessors + foreach (var successor in nextBlock.Successors) + { + for (var j = 0; j < successor.Predecessors.Count; j++) + { + if (successor.Predecessors[j] == nextBlock) + successor.Predecessors[j] = block; + } + } + + toRemove.Add(nextBlock); + } + + // Remove all merged blocks + foreach (var removed in toRemove) + Blocks.Remove(removed); + + foreach (var block in Blocks) + block.CalculateBlockType(); + } + + private void Build(List instructions) { if (instructions == null) throw new ArgumentNullException(nameof(instructions)); - var currentBlock = new Block() { ID = idCounter++ }; - AddNode(currentBlock); - AddDirectedEdge(entryBlock, currentBlock); + AddBlock(currentBlock); + AddDirectedEdge(EntryBlock, currentBlock); + for (var i = 0; i < instructions.Count; i++) { var isLast = i == instructions.Count - 1; - switch (instructions[i].FlowControl) + Block newBlock; + + switch (instructions[i].OpCode) { - case IsilFlowControl.UnconditionalJump: + case OpCode.Jump: + case OpCode.ConditionalJump: currentBlock.AddInstruction(instructions[i]); + if (!isLast) { - var newNodeFromJmp = new Block() { ID = idCounter++ }; - AddNode(newNodeFromJmp); - if (TryGetTargetJumpInstructionIndex(instructions[i], out uint jumpTargetIndex)) + newBlock = new Block() { ID = idCounter++ }; + AddBlock(newBlock); + + if (instructions[i].OpCode == OpCode.Jump) { - // var result = instructions.Any(instruction => instruction.InstructionIndex == jumpTargetIndex); - currentBlock.Dirty = true; + if (TryGetTargetJumpInstructionIndex(instructions[i], out int jumpTargetIndex)) + currentBlock.Dirty = true; + else + AddDirectedEdge(currentBlock, ExitBlock); } else { - AddDirectedEdge(currentBlock, exitBlock); + AddDirectedEdge(currentBlock, newBlock); + currentBlock.Dirty = true; } - currentBlock.CaculateBlockType(); - currentBlock = newNodeFromJmp; + currentBlock.CalculateBlockType(); + currentBlock = newBlock; } else { - AddDirectedEdge(currentBlock, exitBlock); - currentBlock.Dirty = true; - } + AddDirectedEdge(currentBlock, ExitBlock); - break; - case IsilFlowControl.MethodCall: - currentBlock.AddInstruction(instructions[i]); - if (!isLast) - { - var newNodeFromCall = new Block() { ID = idCounter++ }; - AddNode(newNodeFromCall); - AddDirectedEdge(currentBlock, newNodeFromCall); - currentBlock.CaculateBlockType(); - currentBlock = newNodeFromCall; - } - else - { - AddDirectedEdge(currentBlock, exitBlock); - currentBlock.CaculateBlockType(); + if (instructions[i].OpCode == OpCode.Jump) + currentBlock.Dirty = true; } break; - case IsilFlowControl.Continue: - currentBlock.AddInstruction(instructions[i]); - if (isLast) - { - // TODO: Investiage - /* This shouldn't happen, we've either smashed into another method or random data such as a jump table */ - } - break; - case IsilFlowControl.MethodReturn: + case OpCode.Call: + case OpCode.CallVoid: + case OpCode.Return: + var isReturn = instructions[i].OpCode == OpCode.Return; + currentBlock.AddInstruction(instructions[i]); + if (!isLast) { - var newNodeFromReturn = new Block() { ID = idCounter++ }; - AddNode(newNodeFromReturn); - AddDirectedEdge(currentBlock, exitBlock); - currentBlock.CaculateBlockType(); - currentBlock = newNodeFromReturn; + newBlock = new Block() { ID = idCounter++ }; + AddBlock(newBlock); + AddDirectedEdge(currentBlock, isReturn ? ExitBlock : newBlock); + currentBlock.CalculateBlockType(); + currentBlock = newBlock; } else { - AddDirectedEdge(currentBlock, exitBlock); - currentBlock.CaculateBlockType(); + AddDirectedEdge(currentBlock, ExitBlock); + currentBlock.CalculateBlockType(); } break; - case IsilFlowControl.ConditionalJump: + + default: currentBlock.AddInstruction(instructions[i]); - if (!isLast) - { - var newNodeFromConditionalBranch = new Block() { ID = idCounter++ }; - AddNode(newNodeFromConditionalBranch); - AddDirectedEdge(currentBlock, newNodeFromConditionalBranch); - currentBlock.CaculateBlockType(); - currentBlock.Dirty = true; - currentBlock = newNodeFromConditionalBranch; - } - else + if (isLast) { - AddDirectedEdge(currentBlock, exitBlock); + AddDirectedEdge(currentBlock, ExitBlock); + currentBlock.CalculateBlockType(); } - break; - case IsilFlowControl.Interrupt: - currentBlock.AddInstruction(instructions[i]); - var newNodeFromInterrupt = new Block() { ID = idCounter++ }; - AddNode(newNodeFromInterrupt); - AddDirectedEdge(currentBlock, exitBlock); - currentBlock.CaculateBlockType(); - currentBlock = newNodeFromInterrupt; - break; - case IsilFlowControl.IndexedJump: - // This could be a part of either 2 things, a jmp to a jump table (switch statement) or a tail call to another function maybe? I dunno - throw new NotImplementedException("Indirect branch not implemented currently"); - default: - throw new NotImplementedException($"{instructions[i]} {instructions[i].FlowControl}"); } } - - for (var index = 0; index < blockSet.Count; index++) + for (var index = 0; index < Blocks.Count; index++) { - var node = blockSet[index]; + var node = Blocks[index]; if (node.Dirty) FixBlock(node); } - } - public void CalculateDominations() - { - foreach (var block in blockSet) + // Connect blocks without successors to exit + foreach (var block in Blocks) + { + if (block.Successors.Count == 0 && block != EntryBlock && block != ExitBlock) + AddDirectedEdge(block, ExitBlock); + } + + // Change branch targets to blocks + foreach (var instruction in Blocks.SelectMany(block => block.Instructions)) { - throw new NotImplementedException(); + if (instruction.Operands.Count > 0 && instruction.Operands[0] is Instruction target) + instruction.Operands[0] = FindBlockByInstruction(target)!; } } @@ -188,21 +398,21 @@ private void FixBlock(Block block, bool removeJmp = false) if (block.BlockType is BlockType.Fall) return; - var jump = block.isilInstructions.Last(); + var jump = block.Instructions.Last(); - var targetInstruction = jump.Operands[0].Data as InstructionSetIndependentInstruction; + var targetInstruction = jump.Operands[0] as Instruction; - var destination = FindNodeByInstruction(targetInstruction); + var destination = FindBlockByInstruction(targetInstruction); if (destination == null) { //We assume that we're tail calling another method somewhere. Need to verify if this breaks anywhere but it shouldn't in general - block.BlockType = BlockType.Call; + block.BlockType = BlockType.TailCall; return; } - int index = destination.isilInstructions.FindIndex(instruction => instruction == targetInstruction); + int index = destination.Instructions.FindIndex(instruction => instruction == targetInstruction); var targetNode = SplitAndCreate(destination, index); @@ -210,20 +420,20 @@ private void FixBlock(Block block, bool removeJmp = false) block.Dirty = false; if (removeJmp) - block.isilInstructions.Remove(jump); + block.Instructions.Remove(jump); } - protected Block? FindNodeByInstruction(InstructionSetIndependentInstruction? instruction) + public Block? FindBlockByInstruction(Instruction? instruction) { if (instruction == null) return null; - for (var i = 0; i < blockSet.Count; i++) + for (var i = 0; i < Blocks.Count; i++) { - var block = blockSet[i]; - for (var j = 0; j < block.isilInstructions.Count; j++) + var block = Blocks[i]; + for (var j = 0; j < block.Instructions.Count; j++) { - var instr = block.isilInstructions[j]; + var instr = block.Instructions[j]; if (instr == instruction) { return block; @@ -236,50 +446,50 @@ private void FixBlock(Block block, bool removeJmp = false) private Block SplitAndCreate(Block target, int index) { - if (index < 0 || index >= target.isilInstructions.Count) + if (index < 0 || index >= target.Instructions.Count) throw new ArgumentOutOfRangeException(nameof(index)); // Don't need to split... if (index == 0) return target; - var newNode = new Block() { ID = idCounter++ }; + var newBlock = new Block() { ID = idCounter++ }; // target split in two // targetFirstPart -> targetSecondPart aka newNode // Take the instructions for the secondPart - var instructions = target.isilInstructions.GetRange(index, target.isilInstructions.Count - index); - target.isilInstructions.RemoveRange(index, target.isilInstructions.Count - index); + var instructions = target.Instructions.GetRange(index, target.Instructions.Count - index); + target.Instructions.RemoveRange(index, target.Instructions.Count - index); // Add those to the newNode - newNode.isilInstructions.AddRange(instructions); + newBlock.Instructions.AddRange(instructions); // Transfer control flow - newNode.BlockType = target.BlockType; + newBlock.BlockType = target.BlockType; target.BlockType = BlockType.Fall; // Transfer successors - newNode.Successors = target.Successors; + newBlock.Successors = target.Successors; if (target.Dirty) - newNode.Dirty = true; + newBlock.Dirty = true; target.Dirty = false; target.Successors = []; // Correct the predecessors for all the successors - foreach (var successor in newNode.Successors) + foreach (var successor in newBlock.Successors) { for (int i = 0; i < successor.Predecessors.Count; i++) { if (successor.Predecessors[i].ID == target.ID) - successor.Predecessors[i] = newNode; + successor.Predecessors[i] = newBlock; } } // Add newNode and connect it - AddNode(newNode); - AddDirectedEdge(target, newNode); + AddBlock(newBlock); + AddDirectedEdge(target, newBlock); - return newNode; + return newBlock; } private void AddDirectedEdge(Block from, Block to) @@ -288,5 +498,5 @@ private void AddDirectedEdge(Block from, Block to) to.Predecessors.Add(from); } - protected void AddNode(Block block) => blockSet.Add(block); + protected void AddBlock(Block block) => Blocks.Add(block); } diff --git a/Cpp2IL.Core/Graphs/Processors/CallProcessor.cs b/Cpp2IL.Core/Graphs/Processors/CallProcessor.cs deleted file mode 100644 index a3f3784f5..000000000 --- a/Cpp2IL.Core/Graphs/Processors/CallProcessor.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System.Linq; -using Cpp2IL.Core.Il2CppApiFunctions; -using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.Graphs.Processors; - -internal class CallProcessor : IBlockProcessor -{ - public void Process(MethodAnalysisContext methodAnalysisContext, Block block) - { - if (block.BlockType != BlockType.Call) - return; - var callInstruction = block.isilInstructions[^1]; - if (callInstruction == null) - return; - if (callInstruction.OpCode != InstructionSetIndependentOpCode.Call) - return; - - if (callInstruction.Operands.Length <= 0) - return; - var dest = callInstruction.Operands[0]; - if (dest.Type != InstructionSetIndependentOperand.OperandType.Immediate) - return; - - var target = (ulong)((IsilImmediateOperand)dest.Data).Value; - - var keyFunctionAddresses = methodAnalysisContext.AppContext.GetOrCreateKeyFunctionAddresses(); - - if (keyFunctionAddresses.IsKeyFunctionAddress(target)) - { - HandleKeyFunction(methodAnalysisContext.AppContext, callInstruction, target, keyFunctionAddresses); - return; - } - - //Non-key function call. Try to find a single match - if (!methodAnalysisContext.AppContext.MethodsByAddress.TryGetValue(target, out var targetMethods)) - return; - - if (targetMethods is not [{ } singleTargetMethod]) - return; - - callInstruction.Operands[0] = InstructionSetIndependentOperand.MakeMethodReference(singleTargetMethod); - } - - private void HandleKeyFunction(ApplicationAnalysisContext appContext, InstructionSetIndependentInstruction instruction, ulong target, BaseKeyFunctionAddresses kFA) - { - // TODO: Handle labelling functions calls that match these in a more graceful manner - var method = ""; - if (target == kFA.il2cpp_codegen_initialize_method || target == kFA.il2cpp_codegen_initialize_runtime_metadata) - { - if (appContext.MetadataVersion < 27) - { - method = nameof(kFA.il2cpp_codegen_initialize_method); - } - else - { - method = nameof(kFA.il2cpp_codegen_initialize_runtime_metadata); - } - } - else if (target == kFA.il2cpp_vm_metadatacache_initializemethodmetadata) - { - method = nameof(kFA.il2cpp_vm_metadatacache_initializemethodmetadata); - } - else if (target == kFA.il2cpp_runtime_class_init_export) - { - method = nameof(kFA.il2cpp_runtime_class_init_export); - } - else if (target == kFA.il2cpp_runtime_class_init_actual) - { - method = nameof(kFA.il2cpp_runtime_class_init_actual); - } - else if (target == kFA.il2cpp_object_new) - { - method = nameof(kFA.il2cpp_vm_object_new); - } - else if (target == kFA.il2cpp_codegen_object_new) - { - method = nameof(kFA.il2cpp_codegen_object_new); - } - else if (target == kFA.il2cpp_array_new_specific) - { - method = nameof(kFA.il2cpp_array_new_specific); - } - else if (target == kFA.il2cpp_vm_array_new_specific) - { - method = nameof(kFA.il2cpp_vm_array_new_specific); - } - else if (target == kFA.SzArrayNew) - { - method = nameof(kFA.SzArrayNew); - } - else if (target == kFA.il2cpp_type_get_object) - { - method = nameof(kFA.il2cpp_type_get_object); - } - else if (target == kFA.il2cpp_vm_reflection_get_type_object) - { - method = nameof(kFA.il2cpp_vm_reflection_get_type_object); - } - else if (target == kFA.il2cpp_resolve_icall) - { - method = nameof(kFA.il2cpp_resolve_icall); - } - else if (target == kFA.InternalCalls_Resolve) - { - method = nameof(kFA.InternalCalls_Resolve); - } - else if (target == kFA.il2cpp_string_new) - { - method = nameof(kFA.il2cpp_string_new); - } - else if (target == kFA.il2cpp_vm_string_new) - { - method = nameof(kFA.il2cpp_vm_string_new); - } - else if (target == kFA.il2cpp_string_new_wrapper) - { - method = nameof(kFA.il2cpp_string_new_wrapper); - } - else if (target == kFA.il2cpp_vm_string_newWrapper) - { - method = nameof(kFA.il2cpp_vm_string_newWrapper); - } - else if (target == kFA.il2cpp_codegen_string_new_wrapper) - { - method = nameof(kFA.il2cpp_codegen_string_new_wrapper); - } - else if (target == kFA.il2cpp_value_box) - { - method = nameof(kFA.il2cpp_value_box); - } - else if (target == kFA.il2cpp_vm_object_box) - { - method = nameof(kFA.il2cpp_vm_object_box); - } - else if (target == kFA.il2cpp_object_unbox) - { - method = nameof(kFA.il2cpp_object_unbox); - } - else if (target == kFA.il2cpp_vm_object_unbox) - { - method = nameof(kFA.il2cpp_vm_object_unbox); - } - else if (target == kFA.il2cpp_raise_exception) - { - method = nameof(kFA.il2cpp_raise_exception); - } - else if (target == kFA.il2cpp_vm_exception_raise) - { - method = nameof(kFA.il2cpp_vm_exception_raise); - } - else if (target == kFA.il2cpp_codegen_raise_exception) - { - method = nameof(kFA.il2cpp_codegen_raise_exception); - } - else if (target == kFA.il2cpp_vm_object_is_inst) - { - method = nameof(kFA.il2cpp_vm_object_is_inst); - } - else if (target == kFA.AddrPInvokeLookup) - { - method = nameof(kFA.AddrPInvokeLookup); - } - - if (method != "") - { - instruction.Operands[0] = InstructionSetIndependentOperand.MakeImmediate(method); - } - } -} diff --git a/Cpp2IL.Core/Graphs/Processors/IBlockProcessor.cs b/Cpp2IL.Core/Graphs/Processors/IBlockProcessor.cs deleted file mode 100644 index ce46d76da..000000000 --- a/Cpp2IL.Core/Graphs/Processors/IBlockProcessor.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.Graphs.Processors; - -internal interface IBlockProcessor -{ - public void Process(MethodAnalysisContext methodAnalysisContext, Block block); -} diff --git a/Cpp2IL.Core/Graphs/Processors/MetadataProcessor.cs b/Cpp2IL.Core/Graphs/Processors/MetadataProcessor.cs deleted file mode 100644 index 19ad5bdbb..000000000 --- a/Cpp2IL.Core/Graphs/Processors/MetadataProcessor.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Linq; -using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Model.Contexts; -using Cpp2IL.Core.Utils; -using LibCpp2IL; - -namespace Cpp2IL.Core.Graphs.Processors; - -internal class MetadataProcessor : IBlockProcessor -{ - public void Process(MethodAnalysisContext methodAnalysisContext, Block block) - { - foreach (var instruction in block.isilInstructions) - { - // TODO: Check if it shows up in any other - if (instruction.OpCode != InstructionSetIndependentOpCode.Move) - { - continue; - } - - if (instruction.Operands[0].Type != InstructionSetIndependentOperand.OperandType.Register || instruction.Operands[1].Type != InstructionSetIndependentOperand.OperandType.Memory) - { - continue; - } - - var memoryOp = (IsilMemoryOperand)instruction.Operands[1].Data; - if (memoryOp.Base == null && memoryOp.Index == null && memoryOp.Scale == 0) - { - var val = LibCpp2IlMain.GetLiteralByAddress((ulong)memoryOp.Addend); - if (val == null) - { - // Try instead check if its type metadata usage - var metadataUsage = LibCpp2IlMain.GetTypeGlobalByAddress((ulong)memoryOp.Addend); - if (metadataUsage != null && methodAnalysisContext.DeclaringType is not null) - { - var typeAnalysisContext = metadataUsage.ToContext(methodAnalysisContext.DeclaringType!.DeclaringAssembly); - if (typeAnalysisContext != null) - instruction.Operands[1] = InstructionSetIndependentOperand.MakeTypeMetadataUsage(typeAnalysisContext); - } - - continue; - } - - instruction.Operands[1] = InstructionSetIndependentOperand.MakeImmediate(val); - } - } - } -} diff --git a/Cpp2IL.Core/ISIL/FieldReference.cs b/Cpp2IL.Core/ISIL/FieldReference.cs new file mode 100644 index 000000000..535c8d28a --- /dev/null +++ b/Cpp2IL.Core/ISIL/FieldReference.cs @@ -0,0 +1,12 @@ +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.ISIL; + +public class FieldReference(FieldAnalysisContext field, LocalVariable local, int offset) +{ + public FieldAnalysisContext Field = field; + public LocalVariable Local = local; + public int Offset = offset; + + public override string ToString() => $"{Local.Name}.{Field.Name} ({Field.FieldType.FullName})"; +} diff --git a/Cpp2IL.Core/ISIL/Instruction.cs b/Cpp2IL.Core/ISIL/Instruction.cs new file mode 100644 index 000000000..498240dba --- /dev/null +++ b/Cpp2IL.Core/ISIL/Instruction.cs @@ -0,0 +1,130 @@ +using System.Collections.Generic; +using System.Linq; +using AsmResolver.DotNet; +using Cpp2IL.Core.Graphs; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.ISIL; + +public class Instruction(int index, OpCode opcode, params object[] operands) +{ + public int Index = index; + + public OpCode OpCode = opcode; + + public List Operands = operands.ToList(); + + public bool IsFallThrough => + OpCode switch + { + OpCode.Return or OpCode.Jump or OpCode.ConditionalJump or OpCode.IndirectJump => false, + _ => true + }; + + public bool IsCall => OpCode is OpCode.Call or OpCode.CallVoid; + + public bool IsAssignment => Destination != null; + + public List Sources => GetSources(); + + public List SourcesAndConstants => GetSources(false); + + public object? Destination + { + get => GetOrSetDestination(); + set => GetOrSetDestination(value); + } + + private object? GetOrSetDestination(object? newDestination = null) + { + switch (OpCode) + { + case OpCode.Move: + case OpCode.Phi: + case OpCode.Call: + case OpCode.Add: + case OpCode.Subtract: + case OpCode.Multiply: + case OpCode.Divide: + case OpCode.ShiftLeft: + case OpCode.ShiftRight: + case OpCode.And: + case OpCode.Or: + case OpCode.Xor: + case OpCode.Not: + case OpCode.Negate: + case OpCode.CheckEqual: + case OpCode.CheckGreater: + case OpCode.CheckLess: + if (newDestination != null) + Operands[0] = newDestination; + return IsConstantValue(Operands[0]) ? null : Operands[0]; + default: + return null; + } + } + + private List GetSources(bool constantsOnly = true) + { + var sources = OpCode switch + { + OpCode.Move or OpCode.ConditionalJump + or OpCode.ShiftStack or OpCode.Not or OpCode.Negate + => [Operands[1]], + + OpCode.Add or OpCode.Subtract or OpCode.Multiply + or OpCode.Divide or OpCode.ShiftLeft or OpCode.ShiftRight + or OpCode.And or OpCode.Or or OpCode.Xor + => [Operands[2], Operands[1]], + + OpCode.Call => Operands.Skip(2).ToList(), + OpCode.CallVoid or OpCode.Phi => Operands.Skip(1).ToList(), + OpCode.CheckEqual or OpCode.CheckGreater or OpCode.CheckLess + => [Operands[1], Operands[2]], + + _ => [] + }; + + if (OpCode == OpCode.Return && Operands.Count == 1) + sources.Add(Operands[0]); + + if (constantsOnly) + sources = sources.Where(o => !IsConstantValue(o)).ToList(); + + return sources; + } + + public override string ToString() + { + if (OpCode == OpCode.Jump && Operands[0] is ulong jumpTarget) + return $"{Index} {OpCode} {jumpTarget:X4}"; + if (OpCode == OpCode.ConditionalJump && Operands[0] is ulong jumpTarget2) + return $"{Index} {OpCode} {jumpTarget2:X4}, {FormatOperand(Operands[1])}"; + + if ((OpCode is OpCode.CallVoid or OpCode.Call) && Operands[0] is ulong callTarget) + return $"{Index} {OpCode} {callTarget:X4}, {string.Join(", ", Operands.Skip(1).Select(FormatOperand))}"; + + return $"{Index} {OpCode} {string.Join(", ", Operands.Select(FormatOperand))}"; + } + + private static string FormatOperand(object operand) + { + return operand switch + { + string text => $"\"{text}\"", + MethodAnalysisContext method => $"{method.DeclaringType!.Name}.{method.Name}", + TypeAnalysisContext type => $"typeof({type.FullName})", + Instruction instruction => $"@{instruction.Index}", + Block block => $"@b{block.ID}", + _ => operand.ToString()! + }; + } + + public static bool IsConstantValue(object operand) => + operand switch + { + Register or StackOffset or LocalVariable => false, + MemoryOperand memory => memory.IsConstant, + _ => true + }; +} diff --git a/Cpp2IL.Core/ISIL/InstructionSetIndependentInstruction.cs b/Cpp2IL.Core/ISIL/InstructionSetIndependentInstruction.cs deleted file mode 100644 index fac7931a0..000000000 --- a/Cpp2IL.Core/ISIL/InstructionSetIndependentInstruction.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; - -namespace Cpp2IL.Core.ISIL; - -public class InstructionSetIndependentInstruction : IsilOperandData -{ - public InstructionSetIndependentOpCode OpCode; - public InstructionSetIndependentOperand[] Operands; - public ulong ActualAddress; - public uint InstructionIndex = 0; - public IsilFlowControl FlowControl; - - public InstructionSetIndependentInstruction(InstructionSetIndependentOpCode opCode, ulong address, IsilFlowControl flowControl, params InstructionSetIndependentOperand[] operands) - { - OpCode = opCode; - Operands = operands; - ActualAddress = address; - FlowControl = flowControl; - OpCode.Validate(this); - } - - public override string ToString() => $"{InstructionIndex:000} {OpCode} {string.Join(", ", Operands)}"; - - /// - /// Marks the instruction as . - /// - /// The reason that this instruction is being invalidated. - public void Invalidate(string reason) - { - OpCode = InstructionSetIndependentOpCode.Invalid; - Operands = [InstructionSetIndependentOperand.MakeImmediate(reason)]; - FlowControl = IsilFlowControl.Continue; - } -} diff --git a/Cpp2IL.Core/ISIL/InstructionSetIndependentOpCode.cs b/Cpp2IL.Core/ISIL/InstructionSetIndependentOpCode.cs deleted file mode 100644 index de75395cc..000000000 --- a/Cpp2IL.Core/ISIL/InstructionSetIndependentOpCode.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Linq; -using Cpp2IL.Core.Extensions; - -namespace Cpp2IL.Core.ISIL; - -public class InstructionSetIndependentOpCode -{ - public static readonly InstructionSetIndependentOpCode Move = new(IsilMnemonic.Move, 2, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - public static readonly InstructionSetIndependentOpCode LoadAddress = new(IsilMnemonic.LoadAddress, 2, InstructionSetIndependentOperand.OperandType.NotStack, InstructionSetIndependentOperand.OperandType.MemoryOrStack); - public static readonly InstructionSetIndependentOpCode Call = new(IsilMnemonic.Call); - public static readonly InstructionSetIndependentOpCode CallNoReturn = new(IsilMnemonic.CallNoReturn); - public static readonly InstructionSetIndependentOpCode Exchange = new(IsilMnemonic.Exchange, 2, InstructionSetIndependentOperand.OperandType.NotStack, InstructionSetIndependentOperand.OperandType.NotStack); - public static readonly InstructionSetIndependentOpCode Add = new(IsilMnemonic.Add, 3, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - public static readonly InstructionSetIndependentOpCode Subtract = new(IsilMnemonic.Subtract, 3, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - public static readonly InstructionSetIndependentOpCode Multiply = new(IsilMnemonic.Multiply, 3, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - public static readonly InstructionSetIndependentOpCode Divide = new(IsilMnemonic.Divide, 3, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - public static readonly InstructionSetIndependentOpCode ShiftLeft = new(IsilMnemonic.ShiftLeft, 2, InstructionSetIndependentOperand.OperandType.NotStack, InstructionSetIndependentOperand.OperandType.NotStack); - public static readonly InstructionSetIndependentOpCode ShiftRight = new(IsilMnemonic.ShiftRight, 2, InstructionSetIndependentOperand.OperandType.NotStack, InstructionSetIndependentOperand.OperandType.NotStack); - public static readonly InstructionSetIndependentOpCode And = new(IsilMnemonic.And, 3, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - public static readonly InstructionSetIndependentOpCode Or = new(IsilMnemonic.Or, 3, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - public static readonly InstructionSetIndependentOpCode Xor = new(IsilMnemonic.Xor, 3, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - public static readonly InstructionSetIndependentOpCode Not = new(IsilMnemonic.Not, 1, InstructionSetIndependentOperand.OperandType.NotStack); - public static readonly InstructionSetIndependentOpCode Neg = new(IsilMnemonic.Neg, 1, InstructionSetIndependentOperand.OperandType.NotStack); - - public static readonly InstructionSetIndependentOpCode Compare = new(IsilMnemonic.Compare, 2, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - - //public static readonly InstructionSetIndependentOpCode CompareNotEqual = new(IsilMnemonic.CompareNotEqual, 2, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - //public static readonly InstructionSetIndependentOpCode CompareLessThan = new(IsilMnemonic.CompareLessThan, 2, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - //public static readonly InstructionSetIndependentOpCode CompareGreaterThan = new(IsilMnemonic.CompareGreaterThan, 2, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - //public static readonly InstructionSetIndependentOpCode CompareLessThanOrEqual = new(IsilMnemonic.CompareLessThanOrEqual, 2, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - //public static readonly InstructionSetIndependentOpCode CompareGreaterThanOrEqual = new(IsilMnemonic.CompareGreaterThanOrEqual, 2, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any); - public static readonly InstructionSetIndependentOpCode ShiftStack = new(IsilMnemonic.ShiftStack, 1, InstructionSetIndependentOperand.OperandType.Immediate); - public static readonly InstructionSetIndependentOpCode Push = new(IsilMnemonic.Push, 2, InstructionSetIndependentOperand.OperandType.Register, InstructionSetIndependentOperand.OperandType.Any); - public static readonly InstructionSetIndependentOpCode Pop = new(IsilMnemonic.Pop, 2, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Register); - public static readonly InstructionSetIndependentOpCode Return = new(IsilMnemonic.Return, 1, InstructionSetIndependentOperand.OperandType.NotStack); - - public static readonly InstructionSetIndependentOpCode Goto = new(IsilMnemonic.Goto, 1, InstructionSetIndependentOperand.OperandType.Instruction); - - public static readonly InstructionSetIndependentOpCode JumpIfEqual = new(IsilMnemonic.JumpIfEqual, 1, InstructionSetIndependentOperand.OperandType.Instruction); - public static readonly InstructionSetIndependentOpCode JumpIfNotEqual = new(IsilMnemonic.JumpIfNotEqual, 1, InstructionSetIndependentOperand.OperandType.Instruction); - public static readonly InstructionSetIndependentOpCode JumpIfSign = new(IsilMnemonic.JumpIfSign, 1, InstructionSetIndependentOperand.OperandType.Instruction); - public static readonly InstructionSetIndependentOpCode JumpIfNotSign = new(IsilMnemonic.JumpIfNotSign, 1, InstructionSetIndependentOperand.OperandType.Instruction); - public static readonly InstructionSetIndependentOpCode JumpIfGreater = new(IsilMnemonic.JumpIfGreater, 1, InstructionSetIndependentOperand.OperandType.Instruction); - public static readonly InstructionSetIndependentOpCode JumpIfLess = new(IsilMnemonic.JumpIfLess, 1, InstructionSetIndependentOperand.OperandType.Instruction); - public static readonly InstructionSetIndependentOpCode JumpIfGreaterOrEqual = new(IsilMnemonic.JumpIfGreaterOrEqual, 1, InstructionSetIndependentOperand.OperandType.Instruction); - public static readonly InstructionSetIndependentOpCode JumpIfLessOrEqual = new(IsilMnemonic.JumpIfLessOrEqual, 1, InstructionSetIndependentOperand.OperandType.Instruction); - - public static readonly InstructionSetIndependentOpCode Interrupt = new(IsilMnemonic.Interrupt, 0); - public static readonly InstructionSetIndependentOpCode Nop = new(IsilMnemonic.Nop, 0); - - public static readonly InstructionSetIndependentOpCode NotImplemented = new(IsilMnemonic.NotImplemented, 1, InstructionSetIndependentOperand.OperandType.Immediate); - - public static readonly InstructionSetIndependentOpCode Invalid = new(IsilMnemonic.Invalid, 1, InstructionSetIndependentOperand.OperandType.Immediate); - - - public readonly IsilMnemonic Mnemonic; - public readonly InstructionSetIndependentOperand.OperandType[] PermittedOperandTypes; - public readonly int MaxOperands; - - public InstructionSetIndependentOpCode(IsilMnemonic mnemonic) - { - Mnemonic = mnemonic; - MaxOperands = int.MaxValue; - PermittedOperandTypes = []; - } - - public InstructionSetIndependentOpCode(IsilMnemonic mnemonic, int maxOperands) - { - Mnemonic = mnemonic; - MaxOperands = maxOperands; - PermittedOperandTypes = InstructionSetIndependentOperand.OperandType.Any.Repeat(maxOperands).ToArray(); - } - - private InstructionSetIndependentOpCode(IsilMnemonic mnemonic, int maxOperands, params InstructionSetIndependentOperand.OperandType[] permittedOperandTypes) - { - Mnemonic = mnemonic; - PermittedOperandTypes = permittedOperandTypes; - MaxOperands = maxOperands; - } - - public void Validate(InstructionSetIndependentInstruction instruction) - { - var operands = instruction.Operands; - - if (operands.Length > MaxOperands) - { - instruction.Invalidate($"Too many operands! We have {operands.Length} but we only allow {MaxOperands}"); - return; - } - - if (PermittedOperandTypes.Length == 0) - return; - - for (var i = 0; i < operands.Length; i++) - { - if ((operands[i].Type & PermittedOperandTypes[i]) == 0) - { - instruction.Invalidate($"Operand {operands[i]} at index {i} (0-based) is of type {operands[i].Type}, which is not permitted for this index of a {Mnemonic} instruction"); - return; - } - } - } - - public override string ToString() => Mnemonic.ToString(); -} diff --git a/Cpp2IL.Core/ISIL/InstructionSetIndependentOperand.cs b/Cpp2IL.Core/ISIL/InstructionSetIndependentOperand.cs deleted file mode 100644 index f92e72102..000000000 --- a/Cpp2IL.Core/ISIL/InstructionSetIndependentOperand.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.ISIL; - -public readonly struct InstructionSetIndependentOperand -{ - public readonly OperandType Type; - public readonly IsilOperandData Data; - - public static InstructionSetIndependentOperand MakeRegister(string registerName) => new(OperandType.Register, new IsilRegisterOperand(registerName)); - public static InstructionSetIndependentOperand MakeMemory(IsilMemoryOperand memory) => new(OperandType.Memory, memory); - public static InstructionSetIndependentOperand MakeImmediate(IConvertible value) => new(OperandType.Immediate, new IsilImmediateOperand(value)); - public static InstructionSetIndependentOperand MakeStack(int value) => new(OperandType.StackOffset, new IsilStackOperand(value)); - public static InstructionSetIndependentOperand MakeInstruction(InstructionSetIndependentInstruction instruction) => new(OperandType.Instruction, instruction); - public static InstructionSetIndependentOperand MakeVectorElement(string registerName, IsilVectorRegisterElementOperand.VectorElementWidth width, int index) => new(OperandType.Register, new IsilVectorRegisterElementOperand(registerName, width, index)); - public static InstructionSetIndependentOperand MakeTypeMetadataUsage(TypeAnalysisContext value) => new(OperandType.TypeMetadataUsage, new IsilTypeMetadataUsageOperand(value)); - public static InstructionSetIndependentOperand MakeMethodReference(MethodAnalysisContext value) => new(OperandType.MethodReference, new IsilMethodOperand(value)); - - - private InstructionSetIndependentOperand(OperandType type, IsilOperandData data) - { - Type = type; - Data = data; - } - - public override string? ToString() - { - if (Data is InstructionSetIndependentInstruction instruction) - return $"{{{instruction.InstructionIndex.ToString()}}}"; //Special case for instructions, we want to show the index in braces. Otherwise we print the entire instruction and it looks weird. - - return Data.ToString(); - } - - [Flags] - public enum OperandType - { - Immediate = 1, - StackOffset = 2, - Register = 4, - Memory = 8, - Instruction = 16, - TypeMetadataUsage = 32, - MethodReference = 64, - - MemoryOrStack = Memory | StackOffset, - NotStack = Immediate | Register | Memory | Instruction | TypeMetadataUsage | MethodReference, - - - Any = Immediate | StackOffset | Register | Memory | TypeMetadataUsage | MethodReference - } -} diff --git a/Cpp2IL.Core/ISIL/IsilBuilder.cs b/Cpp2IL.Core/ISIL/IsilBuilder.cs deleted file mode 100644 index ca94f17f7..000000000 --- a/Cpp2IL.Core/ISIL/IsilBuilder.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Cpp2IL.Core.Exceptions; - -namespace Cpp2IL.Core.ISIL; - -public class IsilBuilder -{ - public List BackingStatementList; - - public Dictionary> InstructionAddressMap; - - // (Goto instruction, target instruction address) - private readonly List<(InstructionSetIndependentInstruction, ulong)> _jumpsToFix; - - public IsilBuilder() - { - BackingStatementList = []; - InstructionAddressMap = new(); - _jumpsToFix = []; - } - - public IsilBuilder(List backingStatementList) - { - BackingStatementList = backingStatementList; - InstructionAddressMap = new(); - _jumpsToFix = []; - } - - private void AddInstruction(InstructionSetIndependentInstruction instruction) - { - if (InstructionAddressMap.TryGetValue(instruction.ActualAddress, out var list)) - { - list.Add(instruction); - } - else - { - InstructionAddressMap[instruction.ActualAddress] = [instruction]; - } - - BackingStatementList.Add(instruction); - instruction.InstructionIndex = (uint)BackingStatementList.Count; - } - - public void FixJumps() - { - foreach (var tuple in _jumpsToFix) - { - if (InstructionAddressMap.TryGetValue(tuple.Item2, out var list)) - { - var target = list.First(); - - if (target.Equals(tuple.Item1)) - tuple.Item1.Invalidate("Invalid jump target for instruction: Instruction can't jump to itself"); - else - tuple.Item1.Operands = [InstructionSetIndependentOperand.MakeInstruction(target)]; - } - else - { - tuple.Item1.Invalidate("Jump target not found in method."); - } - } - } - - public void Move(ulong instructionAddress, InstructionSetIndependentOperand dest, InstructionSetIndependentOperand src) => AddInstruction(new(InstructionSetIndependentOpCode.Move, instructionAddress, IsilFlowControl.Continue, dest, src)); - - public void LoadAddress(ulong instructionAddress, InstructionSetIndependentOperand dest, InstructionSetIndependentOperand src) => AddInstruction(new(InstructionSetIndependentOpCode.LoadAddress, instructionAddress, IsilFlowControl.Continue, dest, src)); - - public void ShiftStack(ulong instructionAddress, int amount) => AddInstruction(new(InstructionSetIndependentOpCode.ShiftStack, instructionAddress, IsilFlowControl.Continue, InstructionSetIndependentOperand.MakeImmediate(amount))); - - public void Push(ulong instructionAddress, InstructionSetIndependentOperand stackPointerRegister, InstructionSetIndependentOperand operand) => AddInstruction(new(InstructionSetIndependentOpCode.Push, instructionAddress, IsilFlowControl.Continue, stackPointerRegister, operand)); - public void Pop(ulong instructionAddress, InstructionSetIndependentOperand stackPointerRegister, InstructionSetIndependentOperand operand) => AddInstruction(new(InstructionSetIndependentOpCode.Pop, instructionAddress, IsilFlowControl.Continue, operand, stackPointerRegister)); - - public void Exchange(ulong instructionAddress, InstructionSetIndependentOperand place1, InstructionSetIndependentOperand place2) => AddInstruction(new(InstructionSetIndependentOpCode.Exchange, instructionAddress, IsilFlowControl.Continue, place1, place2)); - - public void Subtract(ulong instructionAddress, InstructionSetIndependentOperand dest, InstructionSetIndependentOperand left, InstructionSetIndependentOperand right) => AddInstruction(new(InstructionSetIndependentOpCode.Subtract, instructionAddress, IsilFlowControl.Continue, dest, left, right)); - public void Add(ulong instructionAddress, InstructionSetIndependentOperand dest, InstructionSetIndependentOperand left, InstructionSetIndependentOperand right) => AddInstruction(new(InstructionSetIndependentOpCode.Add, instructionAddress, IsilFlowControl.Continue, dest, left, right)); - - public void Xor(ulong instructionAddress, InstructionSetIndependentOperand dest, InstructionSetIndependentOperand left, InstructionSetIndependentOperand right) => AddInstruction(new(InstructionSetIndependentOpCode.Xor, instructionAddress, IsilFlowControl.Continue, dest, left, right)); - - // The following 4 had their opcode implemented but not the builder func - // I don't know why - public void ShiftLeft(ulong instructionAddress, InstructionSetIndependentOperand left, InstructionSetIndependentOperand right) => AddInstruction(new(InstructionSetIndependentOpCode.ShiftLeft, instructionAddress, IsilFlowControl.Continue, left, right)); - public void ShiftRight(ulong instructionAddress, InstructionSetIndependentOperand left, InstructionSetIndependentOperand right) => AddInstruction(new(InstructionSetIndependentOpCode.ShiftRight, instructionAddress, IsilFlowControl.Continue, left, right)); - public void And(ulong instructionAddress, InstructionSetIndependentOperand dest, InstructionSetIndependentOperand left, InstructionSetIndependentOperand right) => AddInstruction(new(InstructionSetIndependentOpCode.And, instructionAddress, IsilFlowControl.Continue, dest, left, right)); - public void Or(ulong instructionAddress, InstructionSetIndependentOperand dest, InstructionSetIndependentOperand left, InstructionSetIndependentOperand right) => AddInstruction(new(InstructionSetIndependentOpCode.Or, instructionAddress, IsilFlowControl.Continue, dest, left, right)); - - public void Not(ulong instructionAddress, InstructionSetIndependentOperand src) => AddInstruction(new(InstructionSetIndependentOpCode.Not, instructionAddress, IsilFlowControl.Continue, src)); - public void Neg(ulong instructionAddress, InstructionSetIndependentOperand src) => AddInstruction(new(InstructionSetIndependentOpCode.Neg, instructionAddress, IsilFlowControl.Continue, src)); - public void Multiply(ulong instructionAddress, InstructionSetIndependentOperand dest, InstructionSetIndependentOperand src1, InstructionSetIndependentOperand src2) => AddInstruction(new(InstructionSetIndependentOpCode.Multiply, instructionAddress, IsilFlowControl.Continue, dest, src1, src2)); - public void Divide(ulong instructionAddress, InstructionSetIndependentOperand dest, InstructionSetIndependentOperand src1, InstructionSetIndependentOperand src2) => AddInstruction(new(InstructionSetIndependentOpCode.Divide, instructionAddress, IsilFlowControl.Continue, dest, src1, src2)); - - public void Call(ulong instructionAddress, ulong dest, params InstructionSetIndependentOperand[] args) => AddInstruction(new(InstructionSetIndependentOpCode.Call, instructionAddress, IsilFlowControl.MethodCall, PrepareCallOperands(dest, args))); - - public void CallRegister(ulong instructionAddress, InstructionSetIndependentOperand dest, bool noReturn = false) => AddInstruction(new(noReturn ? InstructionSetIndependentOpCode.CallNoReturn : InstructionSetIndependentOpCode.Call, instructionAddress, IsilFlowControl.MethodCall, dest)); - - public void Return(ulong instructionAddress, InstructionSetIndependentOperand? returnValue = null) => AddInstruction(new(InstructionSetIndependentOpCode.Return, instructionAddress, IsilFlowControl.MethodReturn, returnValue != null - ? - [ - returnValue.Value - ] - : [])); - - public void Goto(ulong instructionAddress, ulong target) => CreateJump(instructionAddress, target, InstructionSetIndependentOpCode.Goto, IsilFlowControl.UnconditionalJump); - - public void JumpIfEqual(ulong instructionAddress, ulong target) => CreateJump(instructionAddress, target, InstructionSetIndependentOpCode.JumpIfEqual, IsilFlowControl.ConditionalJump); - - public void JumpIfSign(ulong instructionAddress, ulong target) => CreateJump(instructionAddress, target, InstructionSetIndependentOpCode.JumpIfSign, IsilFlowControl.ConditionalJump); - - public void JumpIfNotSign(ulong instructionAddress, ulong target) => CreateJump(instructionAddress, target, InstructionSetIndependentOpCode.JumpIfNotSign, IsilFlowControl.ConditionalJump); - - public void JumpIfNotEqual(ulong instructionAddress, ulong target) => CreateJump(instructionAddress, target, InstructionSetIndependentOpCode.JumpIfNotEqual, IsilFlowControl.ConditionalJump); - - public void JumpIfGreater(ulong instructionAddress, ulong target) => CreateJump(instructionAddress, target, InstructionSetIndependentOpCode.JumpIfGreater, IsilFlowControl.ConditionalJump); - - public void JumpIfLess(ulong instructionAddress, ulong target) => CreateJump(instructionAddress, target, InstructionSetIndependentOpCode.JumpIfLess, IsilFlowControl.ConditionalJump); - - public void JumpIfGreaterOrEqual(ulong instructionAddress, ulong target) => CreateJump(instructionAddress, target, InstructionSetIndependentOpCode.JumpIfGreaterOrEqual, IsilFlowControl.ConditionalJump); - - public void JumpIfLessOrEqual(ulong instructionAddress, ulong target) => CreateJump(instructionAddress, target, InstructionSetIndependentOpCode.JumpIfLessOrEqual, IsilFlowControl.ConditionalJump); - - private void CreateJump(ulong instructionAddress, ulong target, InstructionSetIndependentOpCode independentOpCode, IsilFlowControl flowControl) - { - var newInstruction = new InstructionSetIndependentInstruction( - independentOpCode, - instructionAddress, - flowControl - ); - AddInstruction(newInstruction); - _jumpsToFix.Add((newInstruction, target)); - } - - - public void Compare(ulong instructionAddress, InstructionSetIndependentOperand left, InstructionSetIndependentOperand right) => AddInstruction(new(InstructionSetIndependentOpCode.Compare, instructionAddress, IsilFlowControl.Continue, left, right)); - - public void NotImplemented(ulong instructionAddress, string text) => AddInstruction(new(InstructionSetIndependentOpCode.NotImplemented, instructionAddress, IsilFlowControl.Continue, InstructionSetIndependentOperand.MakeImmediate(text))); - - public void Invalid(ulong instructionAddress, string text) => AddInstruction(new(InstructionSetIndependentOpCode.Invalid, instructionAddress, IsilFlowControl.Continue, InstructionSetIndependentOperand.MakeImmediate(text))); - - public void Interrupt(ulong instructionAddress) => AddInstruction(new(InstructionSetIndependentOpCode.Interrupt, instructionAddress, IsilFlowControl.Interrupt)); - public void Nop(ulong instructionAddress) => AddInstruction(new(InstructionSetIndependentOpCode.Nop, instructionAddress, IsilFlowControl.Continue)); - - private InstructionSetIndependentOperand[] PrepareCallOperands(ulong dest, InstructionSetIndependentOperand[] args) - { - var parameters = new InstructionSetIndependentOperand[args.Length + 1]; - parameters[0] = InstructionSetIndependentOperand.MakeImmediate(dest); - Array.Copy(args, 0, parameters, 1, args.Length); - return parameters; - } -} diff --git a/Cpp2IL.Core/ISIL/IsilCondition.cs b/Cpp2IL.Core/ISIL/IsilCondition.cs deleted file mode 100644 index 078867f6d..000000000 --- a/Cpp2IL.Core/ISIL/IsilCondition.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Cpp2IL.Core.ISIL; - -public class IsilCondition( - InstructionSetIndependentOperand left, - InstructionSetIndependentOperand right, - InstructionSetIndependentOpCode opCode) -{ - public InstructionSetIndependentOperand Left = left; - public InstructionSetIndependentOperand Right = right; - public InstructionSetIndependentOpCode OpCode = opCode; - - public bool IsAnd; //E.g. x86 TEST instruction vs CMP - - public IsilCondition MarkAsAnd() - { - IsAnd = true; - return this; - } - - public override string ToString() - { - return $"{OpCode} {Left},{Right}"; - } -} diff --git a/Cpp2IL.Core/ISIL/IsilFlowControl.cs b/Cpp2IL.Core/ISIL/IsilFlowControl.cs deleted file mode 100644 index bafb07f93..000000000 --- a/Cpp2IL.Core/ISIL/IsilFlowControl.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Cpp2IL.Core.ISIL; - -public enum IsilFlowControl -{ - // Goto - UnconditionalJump, - - // JumpIfEqual etc. - ConditionalJump, - - // Switch - IndexedJump, - - // Call - MethodCall, - - // Return - MethodReturn, - - // Interrupt - Interrupt, - - // Add, Sub etc. - Continue, -} diff --git a/Cpp2IL.Core/ISIL/IsilImmediateOperand.cs b/Cpp2IL.Core/ISIL/IsilImmediateOperand.cs deleted file mode 100644 index 6a0dfcb96..000000000 --- a/Cpp2IL.Core/ISIL/IsilImmediateOperand.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Globalization; - -namespace Cpp2IL.Core.ISIL; - -public readonly struct IsilImmediateOperand(IConvertible value) : IsilOperandData -{ - public readonly IConvertible Value = value; - - public override string ToString() - { - if (Value is string) - { - return "\"" + Value + "\""; - } - - try - { - //Quick sanity to reduce the possibility of throwing exceptions here, because that's slow - var isUlongAndTooLarge = Value is ulong and >= long.MaxValue; - - if (!isUlongAndTooLarge && Convert.ToInt64(Value) > 0x1000) - return $"0x{Value:X}"; - } - catch - { - //Ignore - } - - return Value.ToString(CultureInfo.InvariantCulture); - } -} diff --git a/Cpp2IL.Core/ISIL/IsilInstructionStatement.cs b/Cpp2IL.Core/ISIL/IsilInstructionStatement.cs deleted file mode 100644 index f11eaeb19..000000000 --- a/Cpp2IL.Core/ISIL/IsilInstructionStatement.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Cpp2IL.Core.ISIL; - -public struct IsilInstructionStatement(InstructionSetIndependentInstruction instruction) : IsilStatement -{ - public readonly InstructionSetIndependentInstruction Instruction = instruction; - - public override string ToString() => Instruction.ToString(); -} diff --git a/Cpp2IL.Core/ISIL/IsilMemoryOperand.cs b/Cpp2IL.Core/ISIL/IsilMemoryOperand.cs deleted file mode 100644 index d35b987b5..000000000 --- a/Cpp2IL.Core/ISIL/IsilMemoryOperand.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Diagnostics; -using System.Text; - -namespace Cpp2IL.Core.ISIL; - -//Disable this because it's an invalid warning - the values have to be initialized or it's a compiler error in a readonly struct -// ReSharper disable RedundantDefaultMemberInitializer -/// -/// Represents a memory operand in the format of [Base + Addend + Index * Scale] -/// -public readonly struct IsilMemoryOperand : IsilOperandData -{ - public readonly InstructionSetIndependentOperand? Base = null; //Must be literal - public readonly InstructionSetIndependentOperand? Index = null; - public readonly long Addend = 0; - public readonly int Scale = 0; - - /// - /// Create a new memory operand representing just a constant address - /// - /// The constant address which will be represented as the addent - public IsilMemoryOperand(long addend) - { - Addend = addend; - } - - /// - /// Create a new memory operand representing a base address with a zero addend. - /// - /// The base. Should be an operand of type - public IsilMemoryOperand(InstructionSetIndependentOperand @base) - { - Debug.Assert(@base.Type == InstructionSetIndependentOperand.OperandType.Register); - - Base = @base; - } - - /// - /// Create a new memory operand representing a constant offset on a base. - /// - /// The base. Should be an operand of type - /// The addend relative to the memory base. - public IsilMemoryOperand(InstructionSetIndependentOperand @base, long addend) - { - Debug.Assert(@base.Type == InstructionSetIndependentOperand.OperandType.Register); - - Base = @base; - Addend = addend; - } - - /// - /// Create a new memory operand representing a base plus an index multiplied by a constant scale. - /// - /// The base. Should be an operand of type - /// The index. Should be an operand of type - /// The scale that the index is multiplied by. Should be a positive integer. - public IsilMemoryOperand(InstructionSetIndependentOperand @base, InstructionSetIndependentOperand index, int scale) - { - Debug.Assert(@base.Type == InstructionSetIndependentOperand.OperandType.Register); - Debug.Assert(index.Type == InstructionSetIndependentOperand.OperandType.Register); - Debug.Assert(scale > 0); - - Base = @base; - Index = index; - Scale = scale; - } - - /// - /// Create a new memory operand representing a base plus an index multiplied by a constant scale, plus a constant addend. - /// - /// The base. Should be an operand of type - /// The index. Should be an operand of type - /// A constant addend to be added to the memory address after adding the index multiplied by the scale. - /// The scale that the index is multiplied by. Should be a positive integer. - public IsilMemoryOperand(InstructionSetIndependentOperand @base, InstructionSetIndependentOperand index, long addend, int scale) - { - Debug.Assert(@base.Type == InstructionSetIndependentOperand.OperandType.Register); - Debug.Assert(index.Type == InstructionSetIndependentOperand.OperandType.Register); - Debug.Assert(scale > 0); - - Base = @base; - Index = index; - Addend = addend; - Scale = scale; - } - - public override string ToString() - { - var ret = new StringBuilder("["); - var needsPlus = false; - - if (Base != null) - { - ret.Append(Base); - needsPlus = true; - } - - if (Addend != 0) - { - if (needsPlus) - ret.Append(Addend > 0 ? '+' : '-'); - - if (Addend > 0x10000) - ret.AppendFormat("0x{0:X}", Math.Abs(Addend)); - else - ret.Append(Math.Abs(Addend)); - needsPlus = true; - } - - if (Index != null) - { - if (needsPlus) - ret.Append("+"); - ret.Append(Index); - - if (Scale > 1) - { - ret.Append("*"); - ret.Append(Scale); - } - } - - ret.Append(']'); - - return ret.ToString(); - } -} diff --git a/Cpp2IL.Core/ISIL/IsilMethodOperand.cs b/Cpp2IL.Core/ISIL/IsilMethodOperand.cs deleted file mode 100644 index f2c161998..000000000 --- a/Cpp2IL.Core/ISIL/IsilMethodOperand.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.ISIL; - -public readonly struct IsilMethodOperand(MethodAnalysisContext method) : IsilOperandData -{ - public readonly MethodAnalysisContext Method { get; } = method; - - public override string ToString() => Method.DeclaringType?.Name + "." + Method.Name; -} diff --git a/Cpp2IL.Core/ISIL/IsilMnemonic.cs b/Cpp2IL.Core/ISIL/IsilMnemonic.cs deleted file mode 100644 index 008025c64..000000000 --- a/Cpp2IL.Core/ISIL/IsilMnemonic.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Cpp2IL.Core.ISIL; - -public enum IsilMnemonic -{ - Move, - LoadAddress, - Call, - CallNoReturn, - Exchange, - Add, - Subtract, - Multiply, - Divide, - ShiftLeft, - ShiftRight, - And, - Or, - Xor, - Not, - Neg, - Compare, - ShiftStack, - Push, - Pop, - Return, - Goto, - JumpIfEqual, - JumpIfNotEqual, - JumpIfGreater, - JumpIfGreaterOrEqual, - JumpIfLess, - JumpIfLessOrEqual, - JumpIfSign, - JumpIfNotSign, - SignExtend, - Interrupt, - Nop, - NotImplemented, - Invalid, -} diff --git a/Cpp2IL.Core/ISIL/IsilOperandData.cs b/Cpp2IL.Core/ISIL/IsilOperandData.cs deleted file mode 100644 index 7bf7dfc32..000000000 --- a/Cpp2IL.Core/ISIL/IsilOperandData.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Cpp2IL.Core.ISIL; - -public interface IsilOperandData -{ -} diff --git a/Cpp2IL.Core/ISIL/IsilRegisterOperand.cs b/Cpp2IL.Core/ISIL/IsilRegisterOperand.cs deleted file mode 100644 index 1e64e44ca..000000000 --- a/Cpp2IL.Core/ISIL/IsilRegisterOperand.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Cpp2IL.Core.ISIL; - -public readonly struct IsilRegisterOperand(string registerName) : IsilOperandData -{ - public readonly string RegisterName = registerName; - - public override string ToString() => RegisterName; -} diff --git a/Cpp2IL.Core/ISIL/IsilStackOperand.cs b/Cpp2IL.Core/ISIL/IsilStackOperand.cs deleted file mode 100644 index a89a1e8e7..000000000 --- a/Cpp2IL.Core/ISIL/IsilStackOperand.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Cpp2IL.Core.ISIL; - -public readonly struct IsilStackOperand(int offset) : IsilOperandData -{ - public readonly int Offset = offset; - - public override string ToString() => $"stack:0x{Offset:X}"; -} diff --git a/Cpp2IL.Core/ISIL/IsilStatement.cs b/Cpp2IL.Core/ISIL/IsilStatement.cs deleted file mode 100644 index a9dee5f83..000000000 --- a/Cpp2IL.Core/ISIL/IsilStatement.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Cpp2IL.Core.ISIL; - -public interface IsilStatement -{ -} diff --git a/Cpp2IL.Core/ISIL/IsilTypeMetadataUsageOperand.cs b/Cpp2IL.Core/ISIL/IsilTypeMetadataUsageOperand.cs deleted file mode 100644 index 0777c0627..000000000 --- a/Cpp2IL.Core/ISIL/IsilTypeMetadataUsageOperand.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.ISIL; - -public readonly struct IsilTypeMetadataUsageOperand(TypeAnalysisContext typeAnalysisContext) : IsilOperandData -{ - public readonly TypeAnalysisContext TypeAnalysisContext = typeAnalysisContext; - - public override string ToString() => "typeof(" + TypeAnalysisContext.FullName + ")"; -} diff --git a/Cpp2IL.Core/ISIL/IsilVectorRegisterElementOperand.cs b/Cpp2IL.Core/ISIL/IsilVectorRegisterElementOperand.cs deleted file mode 100644 index e1fe49aa7..000000000 --- a/Cpp2IL.Core/ISIL/IsilVectorRegisterElementOperand.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Globalization; - -namespace Cpp2IL.Core.ISIL; - -public readonly struct IsilVectorRegisterElementOperand( - string registerName, - IsilVectorRegisterElementOperand.VectorElementWidth width, - int index) - : IsilOperandData -{ - public readonly string RegisterName = registerName; - public readonly VectorElementWidth Width = width; - public readonly int Index = index; - - public override string ToString() - { - return $"{RegisterName}.{Width}[{Index}]"; - } - - public enum VectorElementWidth - { - B, //Byte - H, //Half - S, //Single - D, //Double - } -} diff --git a/Cpp2IL.Core/ISIL/LocalVariable.cs b/Cpp2IL.Core/ISIL/LocalVariable.cs new file mode 100644 index 000000000..978f66577 --- /dev/null +++ b/Cpp2IL.Core/ISIL/LocalVariable.cs @@ -0,0 +1,20 @@ +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.ISIL; + +public class LocalVariable(string name, Register register, TypeAnalysisContext? type = null) +{ + public string Name = name; + public Register Register = register; + + /// + /// null if typeprop has not been done yet, or if the type could not be determined. + /// + public TypeAnalysisContext? Type = type; + + public bool IsThis = false; + public bool IsReturn = false; + public bool IsMethodInfo = false; + + public override string ToString() => Type == null ? $"{Name} @ {Register}" : $"{Name} @ {Register} ({Type.FullName})"; +} diff --git a/Cpp2IL.Core/ISIL/MemoryOperand.cs b/Cpp2IL.Core/ISIL/MemoryOperand.cs new file mode 100644 index 000000000..6f316f25c --- /dev/null +++ b/Cpp2IL.Core/ISIL/MemoryOperand.cs @@ -0,0 +1,53 @@ +using System; +using System.Text; + +namespace Cpp2IL.Core.ISIL; + +/// +/// Memory operand in the format of [base+addend+index*scale] +/// +public struct MemoryOperand(object? baseRegister = null, object? indexRegister = null, long addend = 0, int scale = 0) +{ + public object? Base = baseRegister; + public object? Index = indexRegister; + public long Addend = addend; + public int Scale = scale; + + public bool IsConstant => Base == null && Index == null && Scale == 0; + + public override string ToString() + { + var sb = new StringBuilder("["); + var needsPlus = false; + + if (Base != null) + { + sb.Append(Base); + needsPlus = true; + } + + if (Addend != 0) + { + if (needsPlus || Addend < 0) + sb.Append(Addend > 0 ? '+' : '-'); + sb.Append($"{Math.Abs(Addend):X}"); + needsPlus = true; + } + + if (Index != null) + { + if (needsPlus) + sb.Append('+'); + sb.Append(Index); + + if (Scale > 1) + { + sb.Append('*'); + sb.Append(Scale); + } + } + + sb.Append(']'); + return sb.ToString(); + } +} diff --git a/Cpp2IL.Core/ISIL/OpCode.cs b/Cpp2IL.Core/ISIL/OpCode.cs new file mode 100644 index 000000000..4a18c3098 --- /dev/null +++ b/Cpp2IL.Core/ISIL/OpCode.cs @@ -0,0 +1,95 @@ +namespace Cpp2IL.Core.ISIL; + +/// +/// If changing this, also update +/// +public enum OpCode +{ + /// Invalid instruction, op 1 is the debug string + Invalid, + + /// Not implemented instruction, op 1 is the debug string + NotImplemented, + + /// + /// Interrupt, kept for stack analysis + /// + Interrupt, + + /// + /// No operation + /// + Nop, + + /// Moves op 2 into op 1 + Move, + + /// Moves the result of phi function into op 1, other operands are inputs + Phi, + + /// Calls a method @ op 1, moves the result into op 2, and the rest are params + Call, + + /// Calls a method @ op 1, the rest are params + CallVoid, + + /// Calls a method @ op 1, the rest are params + IndirectCall, + + /// Returns from the method, op 1 is the value to return (optional) + Return, + + /// Jumps to op 1 + Jump, + + /// Jumps to op 1 + IndirectJump, + + /// If op 2 is true, jumps to op 1 + ConditionalJump, + + /// Adds op 1 to stack pointer + ShiftStack, + + /// Adds op 2 and op 3, and moves the result into op 1 + Add, + + /// Subtracts op 3 from op 2, and moves the result into op 1 + Subtract, + + /// Multiplies op 2 by op 3, and moves the result into op 1 + Multiply, + + /// Divides op 2 by op 3, and moves the result into op 1 + Divide, + + /// Shifts the bits of op 2 left by op 3, and moves the result into op 1 + ShiftLeft, + + /// Shifts the bits of op 2 right by op 3, and moves the result into op 1 + ShiftRight, + + /// Bitwise AND on op 2 and op 3, moves the result into op 1 + And, + + /// Bitwise OR on op 2 and op 3, moves the result into op 1 + Or, + + /// Bitwise XOR on op 2 and op 3, moves the result into op 1 + Xor, + + /// Logical not on op 2, moves the result into op 1 + Not, + + /// Negates op 2, moves the result into op 1 + Negate, + + /// Moves 1 into op 1, if op 2 and op 3 are equal + CheckEqual, + + /// Moves 1 into op 1, if op 2 is greater than op 3 + CheckGreater, + + /// Moves 1 into op 1, if op 2 is less than op 3 + CheckLess +} diff --git a/Cpp2IL.Core/ISIL/Register.cs b/Cpp2IL.Core/ISIL/Register.cs new file mode 100644 index 000000000..f4a355a68 --- /dev/null +++ b/Cpp2IL.Core/ISIL/Register.cs @@ -0,0 +1,80 @@ +using System; + +namespace Cpp2IL.Core.ISIL; + +public struct Register : IEquatable +{ + public Register(int? number, string? name, int version = -1) + { + if (number == null && name == null) + throw new ArgumentException("Either number or name must be provided, not both null."); + + if (number == null) + { + Name = name!; + Number = name!.GetHashCode(); + } + else if (name == null) + { + Name = "reg" + number; + Number = (int)number; + } + else + { + Name = name; + Number = (int)number; + } + + Version = version; + } + + public int Number; + public string Name; + + /// + /// SSA version of the register, -1 = not in SSA form. + /// + public int Version; + + /// + /// Creates a copy of the register with different version. + /// + /// The SSA version. + /// The register. + public Register Copy(int version = -1) => new(Number, Name, version); + + public override string ToString() => Name + (Version == -1 ? "" : $"_v{Version}"); + + public static bool operator ==(Register left, Register right) + { + return left.Equals(right); + } + + public static bool operator !=(Register left, Register right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (obj is not Register register) + return false; + return Equals(register); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Number; + hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ Version; + return hashCode; + } + } + + public bool Equals(Register other) + { + return Name == other.Name && Number == other.Number && Version == other.Version; + } +} diff --git a/Cpp2IL.Core/ISIL/StackOffset.cs b/Cpp2IL.Core/ISIL/StackOffset.cs new file mode 100644 index 000000000..b0dced155 --- /dev/null +++ b/Cpp2IL.Core/ISIL/StackOffset.cs @@ -0,0 +1,8 @@ +namespace Cpp2IL.Core.ISIL; + +public struct StackOffset(int offset) +{ + public int Offset = offset; + + public override string ToString() => $"stack[{(Offset < 0 ? ("-" + (-Offset).ToString("X")) : Offset.ToString("X"))}]"; +} diff --git a/Cpp2IL.Core/IlGenerator.cs b/Cpp2IL.Core/IlGenerator.cs new file mode 100644 index 000000000..28f6dfbbf --- /dev/null +++ b/Cpp2IL.Core/IlGenerator.cs @@ -0,0 +1,462 @@ +using System.Collections.Generic; +using System.Linq; +using AsmResolver.DotNet; +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Signatures; +using AsmResolver.PE.DotNet.Cil; +using Cpp2IL.Core.Graphs; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Utils.AsmResolver; + +namespace Cpp2IL.Core; + +public static class IlGenerator +{ + public static void GenerateIl(MethodAnalysisContext context, MethodDefinition definition) + { + var assembly = context.DeclaringType!.DeclaringAssembly; + var module = definition.Module!; + var importer = module.DefaultImporter; + var factory = module.CorLibTypeFactory; + + var writeLine = factory.CorLibScope + .CreateTypeReference("System", "Console") + .CreateMemberReference("WriteLine", MethodSignature.CreateStatic(factory.Void, factory.String)) + .ImportWith(importer); + + var stringType = factory.CorLibScope.CreateTypeReference("System", "String"); + var stringCtor = stringType + .CreateMemberReference(".ctor", MethodSignature.CreateStatic(stringType.ToTypeSignature(), factory.String)) + .ImportWith(importer); + + // Change branch targets to instructions + foreach (var instruction in context.ControlFlowGraph!.Blocks.SelectMany(block => block.Instructions)) + { + if (instruction.Operands.Count > 0 && instruction.Operands[0] is Block target) + { + if (target.Instructions.Count > 0) + instruction.Operands[0] = target.Instructions[^1]; + } + } + + var body = new CilMethodBody(definition) + { + InitializeLocals = true, // Without this ILSpy does: CompilerServices.Unsafe.SkipInit(out object obj); + ComputeMaxStackOnBuild = false // There's stack imbalance somewhere, but this works for now + }; + + definition.CilMethodBody = body; + + // Make sure context.Locals actually has all locals (idk why it doesn't sometimes) + foreach (var operand in context.ControlFlowGraph.Instructions.SelectMany(i => i.Operands)) + { + LocalVariable? local = null; + + if (operand is FieldReference field) + local = field.Local; + + if (operand is LocalVariable local2) + local = local2; + + if (operand is MemoryOperand memory && memory.Base is LocalVariable local3) + local = local3; + + if (local != null && !context.Locals.Contains(local)) + context.Locals.Add(local); + } + + // Map ISIL locals to IL + Dictionary locals = []; + foreach (var local in context.Locals) + { + TypeSignature ilType; + + // Use object if type couldn't be determined + if (local.Type != null) + ilType = local.Type.ToTypeSignature(module); + else + ilType = module.CorLibTypeFactory.Object; + + var ilLocal = new CilLocalVariable(ilType); + body.LocalVariables.Add(ilLocal); + locals.Add(local, ilLocal); + } + + /* foreach (var instruction in context.ControlFlowGraph!.Instructions) + { + body.Instructions.Add(CilOpCodes.Ldstr, instruction.ToString()); + body.Instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); + } + body.Instructions.Add(CilOpCodes.Ldstr, "-------------------------------------------------------------------------"); + body.Instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); */ + + // Generate IL + Dictionary> instructionMap = []; + foreach (var instruction in context.ControlFlowGraph!.Instructions) // context.ConvertedIsil is probably not up to date anymore here + instructionMap.Add(instruction, GenerateInstructions(instruction, context, definition, locals, writeLine, stringCtor)); + + // Set IL branch targets + foreach (var kvp in instructionMap) + { + var instruction = kvp.Key; + var il = kvp.Value; + + if (instruction.OpCode == OpCode.Jump || instruction.OpCode == OpCode.ConditionalJump) + { + var ilBranch = il.First(i => i.OpCode == CilOpCodes.Br || i.OpCode == CilOpCodes.Brtrue); + + if (instruction.Operands[0] is Block targetBlock) + { + context.AddWarning($"Branch target block not in cfg: {instruction} ({targetBlock})"); + ilBranch.OpCode = CilOpCodes.Nop; + ilBranch.Operand = null; + continue; + } + + var target = (Instruction)instruction.Operands[0]; + + if (!instructionMap.ContainsKey(target)) + { + context.AddWarning($"Branch target not in ISIL to IL map: {instruction} --- {target}"); + ilBranch.OpCode = CilOpCodes.Nop; + ilBranch.Operand = null; + continue; + } + + ilBranch.Operand = new CilInstructionLabel(instructionMap[target][0]); + } + } + + // Add analysis warnings + var instructions = body.Instructions; + foreach (var warning in context.AnalysisWarnings) + { + instructions.Add(CilOpCodes.Ldstr, "Warning: " + warning); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); + } + } + + private static List GenerateInstructions(Instruction instruction, MethodAnalysisContext context, + MethodDefinition method, Dictionary locals, MemberReference writeLine, MemberReference stringCtor) + { + var body = method.CilMethodBody!; + var instructions = body.Instructions; + var currentCount = instructions.Count; + var startIndex = instructions.Count; + + var module = method.Module!; + var importer = module.DefaultImporter!; + + switch (instruction.OpCode) + { + case OpCode.Invalid: + instructions.Add(CilOpCodes.Ldstr, $"Invalid instruction: {instruction}"); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); + break; + + case OpCode.NotImplemented: + instructions.Add(CilOpCodes.Ldstr, $"Not implemented instruction: {instruction.Operands[0]}"); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); + break; + + case OpCode.Interrupt: + case OpCode.Nop: + instructions.Add(CilOpCodes.Nop); + break; + + case OpCode.Move: + if (instruction.Operands[0] is FieldReference field) // stfld takes instance before value so LoadOperand StoreToOperand doesn't work + { + var param = method.Parameters.FirstOrDefault(p => p.Name == field.Local.Name); + if (param != null) + instructions.Add(CilOpCodes.Ldarg, param); + else if (field.Local.IsThis) + instructions.Add(CilOpCodes.Ldarg_0); + else + instructions.Add(CilOpCodes.Ldloc, locals[field.Local]); + + LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor); + instructions.Add(CilOpCodes.Stfld, field.Field.ToFieldDescriptor(module)); + break; + } + + LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor); + StoreToOperand(instruction.Operands[0], method, locals, writeLine); + break; + + case OpCode.Phi: + instructions.Add(CilOpCodes.Ldstr, $"Phi opcodes should not exist at this point in decompilation ({instruction})"); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); + break; + + case OpCode.Call: + case OpCode.CallVoid: + if (instruction.Operands[0] is not MethodAnalysisContext targetMethod) + { + if (instruction.Operands[0] is ulong targetAddress) + instructions.Add(CilOpCodes.Ldstr, $"Method not found @{targetAddress:X}"); + else // Probably key function + instructions.Add(CilOpCodes.Ldstr, $"Unknown call target operand: {instruction}"); + + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); + break; + } + + var importedMethod = importer.ImportMethod(targetMethod.ToMethodDescriptor(module)); + var resolvedMethod = importedMethod.Resolve()!; + + var thisParamIndex = instruction.OpCode == OpCode.Call ? 2 : 1; + + if (!resolvedMethod.IsStatic) // Load 'this' param + { + if ((instruction.Operands.Count - 1) >= thisParamIndex) + LoadOperand(instruction.Operands[thisParamIndex], method, locals, writeLine, stringCtor); + else + instructions.Add(CilOpCodes.Ldstr, $"Non static method called without 'this' param ({instruction})"); + } + + // Load normal params + var callParams = instruction.Operands.Skip(thisParamIndex + (resolvedMethod.IsStatic ? 0 : -1)); + foreach (var param in callParams) + LoadOperand(param, method, locals, writeLine, stringCtor); + + instructions.Add(CilOpCodes.Call, importedMethod); + + if (instruction.OpCode == OpCode.Call) // Store return value + StoreToOperand(instruction.Operands[1], method, locals, writeLine); + + break; + + case OpCode.IndirectCall: + instructions.Add(CilOpCodes.Ldstr, $"Indirect call: {instruction} (should have been resolved before IL gen)"); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); + break; + + case OpCode.Return: + if (!context.IsVoid && instruction.Operands.Count == 1) + LoadOperand(instruction.Operands[0], method, locals, writeLine, stringCtor); + instructions.Add(CilOpCodes.Ret); + break; + + case OpCode.Jump: + instructions.Add(CilOpCodes.Br, new CilInstructionLabel()); + break; + + case OpCode.ConditionalJump: + LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor); + instructions.Add(CilOpCodes.Brtrue, new CilInstructionLabel()); + break; + + case OpCode.IndirectJump: + instructions.Add(CilOpCodes.Ldstr, $"Indirect jump: {instruction} (should have been resolved before IL gen)"); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); + break; + + case OpCode.ShiftStack: + instructions.Add(CilOpCodes.Ldstr, $"Stack shift: {instruction} (stack analysis should have removed these)"); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); + break; + + case OpCode.CheckEqual: + case OpCode.CheckGreater: + case OpCode.CheckLess: + + case OpCode.Add: + case OpCode.Subtract: + case OpCode.Multiply: + case OpCode.Divide: + + case OpCode.ShiftLeft: + case OpCode.ShiftRight: + + case OpCode.And: + case OpCode.Or: + case OpCode.Xor: + LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor); + LoadOperand(instruction.Operands[2], method, locals, writeLine, stringCtor); + + switch (instruction.OpCode) + { + case OpCode.CheckEqual: instructions.Add(CilOpCodes.Ceq); break; + case OpCode.CheckGreater: instructions.Add(CilOpCodes.Cgt); break; + case OpCode.CheckLess: instructions.Add(CilOpCodes.Clt); break; + + case OpCode.Add: instructions.Add(CilOpCodes.Add); break; + case OpCode.Subtract: instructions.Add(CilOpCodes.Sub); break; + case OpCode.Multiply: instructions.Add(CilOpCodes.Mul); break; + case OpCode.Divide: instructions.Add(CilOpCodes.Div); break; + + case OpCode.ShiftLeft: instructions.Add(CilOpCodes.Shl); break; + case OpCode.ShiftRight: instructions.Add(CilOpCodes.Shr); break; + + case OpCode.And: instructions.Add(CilOpCodes.And); break; + case OpCode.Or: instructions.Add(CilOpCodes.Or); break; + case OpCode.Xor: instructions.Add(CilOpCodes.Xor); break; + } + + StoreToOperand(instruction.Operands[0], method, locals, writeLine); + break; + + case OpCode.Not: + case OpCode.Negate: + LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor); + + switch (instruction.OpCode) + { + case OpCode.Not: instructions.Add(CilOpCodes.Not); break; + case OpCode.Negate: instructions.Add(CilOpCodes.Neg); break; + } + + StoreToOperand(instruction.Operands[0], method, locals, writeLine); + break; + + default: + instructions.Add(CilOpCodes.Ldstr, $"Unknown instruction: {instruction}"); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); + break; + } + + return instructions.ToList().GetRange(startIndex, instructions.Count - startIndex); // Return added IL + } + + private static void LoadOperand(object operand, MethodDefinition method, + Dictionary locals, MemberReference writeLine, MemberReference stringCtor) + { + var instructions = method.CilMethodBody!.Instructions; + + var module = method.Module!; + var importer = module.DefaultImporter!; + + switch (operand) + { + case int i: + instructions.Add(CilOpCodes.Ldc_I4, i); + break; + case uint ui: + instructions.Add(CilOpCodes.Ldc_I4, unchecked((int)ui)); + break; + case short s: + instructions.Add(CilOpCodes.Ldc_I4, s); + break; + case ushort us: + instructions.Add(CilOpCodes.Ldc_I4, us); + break; + case byte b8: + instructions.Add(CilOpCodes.Ldc_I4, b8); + break; + case sbyte sb8: + instructions.Add(CilOpCodes.Ldc_I4, sb8); + break; + case long l: + instructions.Add(CilOpCodes.Ldc_I8, l); + break; + case ulong ul: + instructions.Add(CilOpCodes.Ldc_I8, unchecked((long)ul)); + break; + case float f: + instructions.Add(CilOpCodes.Ldc_R4, f); + break; + case double d: + instructions.Add(CilOpCodes.Ldc_R8, d); + break; + case bool b: + instructions.Add(CilOpCodes.Ldc_I4, b ? 1 : 0); + break; + case string s: + instructions.Add(CilOpCodes.Ldstr, s); + break; + case LocalVariable local: + var param = method.Parameters.FirstOrDefault(p => p.Name == local.Name); + if (param != null) + instructions.Add(CilOpCodes.Ldarg, param); + else + instructions.Add(CilOpCodes.Ldloc, locals[local]); + break; + case FieldReference field: + instructions.Add(CilOpCodes.Ldarg_0); // TODO: Use local instead of 'this' without causing stack imbalance, i have no idea why that happens + //instructions.Add(CilOpCodes.Ldloca, _locals[field.Local]); + instructions.Add(CilOpCodes.Ldfld, field.Field.ToFieldDescriptor(module)); + break; + case MemoryOperand memory: + if (memory.Index == null && memory.Addend == 0 && memory.Scale == 0 + && memory.Base is LocalVariable local2) + { + var param2 = method.Parameters.FirstOrDefault(p => p.Name == local2.Name); + if (param2 != null) + instructions.Add(CilOpCodes.Ldarg, param2); + else + instructions.Add(CilOpCodes.Ldloc, locals[local2]); + break; + } + instructions.Add(CilOpCodes.Ldstr, "Unmanaged memory load: " + operand.ToString()); + instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(stringCtor)); + break; + case TypeAnalysisContext type: + if (type.Name == "T") + { + // idk what to do here + instructions.Add(CilOpCodes.Ldstr, ""); + instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(stringCtor)); + break; + } + + var cilType = type.ToTypeSignature(module!).Resolve()!; + + // Try to first get constructor without params + var constructor = cilType.Methods.FirstOrDefault(m => m.ParameterDefinitions.Count == 0 && m.Name == ".ctor" || m.Name == ".cctor"); + constructor ??= cilType.Methods.FirstOrDefault(m => m.Name == ".ctor" || m.Name == ".cctor"); + + if (constructor == null) + { + instructions.Add(CilOpCodes.Ldstr, $"Constructor not found for: {operand} (probably static type)"); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); + break; + } + + foreach (var param2 in constructor.ParameterDefinitions) + instructions.Add(CilOpCodes.Ldstr, "Constructor param: " + param2.ToString()); + instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(constructor)); + break; + default: + instructions.Add(CilOpCodes.Ldstr, "Unknown operand: " + operand.ToString()); + instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(stringCtor)); + break; + } + } + + private static void StoreToOperand(object operand, MethodDefinition method, + Dictionary locals, MemberReference writeLine) + { + var instructions = method.CilMethodBody!.Instructions; + + var module = method.Module!; + var importer = module.DefaultImporter!; + + switch (operand) + { + case LocalVariable local: + instructions.Add(CilOpCodes.Stloc, locals[local]); + break; + + case FieldReference field: + instructions.Add(CilOpCodes.Ldarg_0); + instructions.Add(CilOpCodes.Stfld, field.Field.ToFieldDescriptor(module)); + break; + + case MemoryOperand memory: + if (memory.Index == null && memory.Addend == 0 && memory.Scale == 0 + && memory.Base is LocalVariable local2) + { + // Can pointer assignments just be ignored because it's C#? (Move [local], 123) + instructions.Add(CilOpCodes.Stloc, locals[local2]); + } + break; + + default: + instructions.Add(CilOpCodes.Ldstr, $"Store into unknown operand: {operand}"); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); + break; + } + } +} diff --git a/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs b/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs index 74dafcaea..dd2b31b81 100644 --- a/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Text; using Cpp2IL.Core.Api; -using Cpp2IL.Core.Graphs; using Cpp2IL.Core.Il2CppApiFunctions; using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Model.Contexts; @@ -33,7 +32,12 @@ public override Memory GetRawBytesForMethod(MethodAnalysisContext context, return instructions.SelectMany(i => i.Bytes).ToArray(); } - public override List GetIsilFromMethod(MethodAnalysisContext context) + public override List GetIsilFromMethod(MethodAnalysisContext context) + { + return []; + } + + public override List GetParameterOperandsFromMethod(MethodAnalysisContext context) { return []; } diff --git a/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs b/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs index 8f621dc7f..b30e29bec 100644 --- a/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs @@ -23,7 +23,12 @@ public override Memory GetRawBytesForMethod(MethodAnalysisContext context, return instructions.SelectMany(i => i.Bytes).ToArray(); } - public override List GetIsilFromMethod(MethodAnalysisContext context) + public override List GetIsilFromMethod(MethodAnalysisContext context) + { + return []; + } + + public override List GetParameterOperandsFromMethod(MethodAnalysisContext context) { return []; } diff --git a/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs b/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs index e22f6d727..7bbb34037 100644 --- a/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using Disarm; using Cpp2IL.Core.Api; @@ -10,6 +9,7 @@ using Cpp2IL.Core.Utils; using Disarm.InternalDisassembly; using LibCpp2IL; +using Cpp2IL.Core.Logging; namespace Cpp2IL.Core.InstructionSets; @@ -17,7 +17,7 @@ public class NewArmV8InstructionSet : Cpp2IlInstructionSet { [ThreadStatic] private static Dictionary adrpOffsets = new(); - + public override Memory GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) { if (context is not ConcreteGenericMethodAnalysisContext) @@ -44,31 +44,63 @@ public override Memory GetRawBytesForMethod(MethodAnalysisContext context, return context.AppContext.Binary.GetRawBinaryContent().AsMemory(start, end - start); } - public override List GetIsilFromMethod(MethodAnalysisContext context) + public override List GetParameterOperandsFromMethod(MethodAnalysisContext context) { - var insns = NewArm64Utils.GetArm64MethodBodyAtVirtualAddress(context.UnderlyingPointer); + return []; + } - var builder = new IsilBuilder(); + public override List GetIsilFromMethod(MethodAnalysisContext context) + { + var insns = NewArm64Utils.GetArm64MethodBodyAtVirtualAddress(context.UnderlyingPointer); if (adrpOffsets == null!) //Null suppress because thread static weirdness adrpOffsets = new(); - + adrpOffsets.Clear(); + var instructions = new List(); + var addresses = new List(); + foreach (var instruction in insns) + ConvertInstructionStatement(instruction, instructions, addresses, context); + + // fix branches + for (var i = 0; i < instructions.Count; i++) { - ConvertInstructionStatement(instruction, builder, context); - } - - adrpOffsets.Clear(); + var instruction = instructions[i]; + + if (instruction.OpCode != OpCode.Jump && instruction.OpCode != OpCode.ConditionalJump) + continue; - builder.FixJumps(); + var targetAddress = (ulong)instruction.Operands[0]; + var targetIndex = addresses.FindIndex(addr => addr == targetAddress); - return builder.BackingStatementList; + if (targetIndex == -1) + { + instruction.OpCode = OpCode.Invalid; + instruction.Operands = [$"Jump target not found in method: 0x{targetAddress:X4}"]; + continue; + } + + var targetInstruction = instructions[targetIndex]; + + instruction.Operands[0] = targetInstruction; + } + + adrpOffsets.Clear(); + return instructions; } - private void ConvertInstructionStatement(Arm64Instruction instruction, IsilBuilder builder, MethodAnalysisContext context) + private void ConvertInstructionStatement(Arm64Instruction instruction, List instructions, List addresses, MethodAnalysisContext context) { + var address = instruction.Address; + + void Add(ulong address, OpCode opCode, params object[] operands) + { + addresses.Add(address); + instructions.Add(new Instruction(instructions.Count, opCode, operands)); + } + switch (instruction.Mnemonic) { case Arm64Mnemonic.MOV: @@ -78,23 +110,21 @@ private void ConvertInstructionStatement(Arm64Instruction instruction, IsilBuild case Arm64Mnemonic.LDR: case Arm64Mnemonic.LDRB: //Load and move are (dest, src) - + if (instruction.MemIsPreIndexed) // such as X8, [X19,#0x30]! { //Regardless of anything else, we're trashing any possible ADRP offsets in the dest here, so let's clear that - if(instruction.Op0Kind == Arm64OperandKind.Register) + if (instruction.Op0Kind == Arm64OperandKind.Register) adrpOffsets.Remove(instruction.Op0Reg); - + var operate = ConvertOperand(instruction, 1); - if (operate.Data is IsilMemoryOperand operand) + if (operate is MemoryOperand operand) { - var register = operand.Base!.Value; + var register = (Register)operand.Base!; // X19= X19, #0x30 - builder.Add(instruction.Address, register, register, InstructionSetIndependentOperand.MakeImmediate(operand.Addend)); + Add(address, OpCode.Add, register, register, operand.Addend); //X8 = [X19] - builder.Move(instruction.Address, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeMemory(new IsilMemoryOperand( - InstructionSetIndependentOperand.MakeRegister(register.ToString()!.ToUpperInvariant()), - 0))); + Add(address, OpCode.Move, ConvertOperand(instruction, 0), new MemoryOperand(new Register(null, register.ToString()!.ToUpperInvariant()))); break; } } @@ -105,73 +135,74 @@ private void ConvertInstructionStatement(Arm64Instruction instruction, IsilBuild //LDR X0, [X1, #0x1000], where X1 was previously loaded with a page address via an ADRP instruction //We just return the final address, it makes ISIL happier. //TODO check if this is correct - var offset = instruction.MemOffset + (long) page; - + var offset = instruction.MemOffset + (long)page; + //We're also trashing any possible ADRP offsets in the dest here, so let's clear that now we've possibly grabbed the value if we need it (it's common to store the page and final address in the same register) - if(instruction.Op0Kind == Arm64OperandKind.Register) + if (instruction.Op0Kind == Arm64OperandKind.Register) adrpOffsets.Remove(instruction.Op0Reg); - - builder.Move(instruction.Address, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeMemory(new(offset))); + + Add(address, OpCode.Move, ConvertOperand(instruction, 0), new MemoryOperand(addend: offset)); break; } //And again here we're trashing any possible ADRP offsets in the dest here, so let's clear that - if(instruction.Op0Kind == Arm64OperandKind.Register) + if (instruction.Op0Kind == Arm64OperandKind.Register) adrpOffsets.Remove(instruction.Op0Reg); - - builder.Move(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + + Add(address, OpCode.Move, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + Add(address, OpCode.CheckEqual, new Register(null, "Z"), ConvertOperand(instruction, 0), 0); break; case Arm64Mnemonic.MOVN: - { - // dest = ~src - - //See above re: ADRP offsets - if(instruction.Op0Kind == Arm64OperandKind.Register) - adrpOffsets.Remove(instruction.Op0Reg); - - var temp = InstructionSetIndependentOperand.MakeRegister("TEMP"); - builder.Move(instruction.Address, temp, ConvertOperand(instruction, 1)); - builder.Not(instruction.Address, temp); - builder.Move(instruction.Address, ConvertOperand(instruction, 0), temp); - break; - } + { + // dest = ~src + + //See above re: ADRP offsets + if (instruction.Op0Kind == Arm64OperandKind.Register) + adrpOffsets.Remove(instruction.Op0Reg); + + var temp2 = new Register(null, "TEMP"); + Add(address, OpCode.Move, temp2, ConvertOperand(instruction, 1)); + Add(address, OpCode.Not, temp2, temp2); + Add(address, OpCode.Move, ConvertOperand(instruction, 0), temp2); + break; + } case Arm64Mnemonic.STR: case Arm64Mnemonic.STUR: // unscaled case Arm64Mnemonic.STRB: //Store is (src, dest) - builder.Move(instruction.Address, ConvertOperand(instruction, 1), ConvertOperand(instruction, 0)); + Add(address, OpCode.Move, ConvertOperand(instruction, 1), ConvertOperand(instruction, 0)); break; case Arm64Mnemonic.STP: // store pair of registers (reg1, reg2, dest) - { - var dest = ConvertOperand(instruction, 2); - if (dest.Data is IsilRegisterOperand { RegisterName: "X31" }) // if stack { - builder.Move(instruction.Address, dest, ConvertOperand(instruction, 0)); - builder.Move(instruction.Address, dest, ConvertOperand(instruction, 1)); - } - else if (dest.Data is IsilMemoryOperand memory) - { - var firstRegister = ConvertOperand(instruction, 0); - long size = ((IsilRegisterOperand)firstRegister.Data).RegisterName[0] == 'W' ? 4 : 8; - builder.Move(instruction.Address, dest, firstRegister); // [REG + offset] = REG1 - memory = new IsilMemoryOperand(memory.Base!.Value, memory.Addend + size); - dest = InstructionSetIndependentOperand.MakeMemory(memory); - builder.Move(instruction.Address, dest, ConvertOperand(instruction, 1)); // [REG + offset + size] = REG2 - } - else // reg pointer - { - var firstRegister = ConvertOperand(instruction, 0); - long size = ((IsilRegisterOperand)firstRegister.Data).RegisterName[0] == 'W' ? 4 : 8; - builder.Move(instruction.Address, dest, firstRegister); - builder.Add(instruction.Address, dest, dest, InstructionSetIndependentOperand.MakeImmediate(size)); - builder.Move(instruction.Address, dest, ConvertOperand(instruction, 1)); + var dest3 = ConvertOperand(instruction, 2); + if (dest3 is Register { Name: "X31" }) // if stack + { + Add(address, OpCode.Move, dest3, ConvertOperand(instruction, 0)); + Add(address, OpCode.Move, dest3, ConvertOperand(instruction, 1)); + } + else if (dest3 is MemoryOperand memory) + { + var firstRegister = ConvertOperand(instruction, 0); + long size = ((Register)firstRegister).Name[0] == 'W' ? 4 : 8; + Add(address, OpCode.Move, dest3, firstRegister); // [REG + offset] = REG1 + memory = new MemoryOperand((Register)memory.Base!, addend: memory.Addend + size); + dest3 = memory; + Add(address, OpCode.Move, dest3, ConvertOperand(instruction, 1)); // [REG + offset + size] = REG2 + } + else // reg pointer + { + var firstRegister = ConvertOperand(instruction, 0); + long size = ((Register)firstRegister).Name[0] == 'W' ? 4 : 8; + Add(address, OpCode.Move, dest3, firstRegister); + Add(address, OpCode.Add, dest3, dest3, size); + Add(address, OpCode.Move, dest3, ConvertOperand(instruction, 1)); + } } - } break; case Arm64Mnemonic.ADRP: //Just handle as a move - // builder.Move(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + Add(address, OpCode.Move, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); adrpOffsets[instruction.Op0Reg] = (ulong)instruction.Op1Imm; break; case Arm64Mnemonic.LDP when instruction.Op2Kind == Arm64OperandKind.Memory: @@ -198,17 +229,21 @@ private void ConvertInstructionStatement(Arm64Instruction instruction, IsilBuild var mem = ConvertOperand(instruction, 2); //TODO clean this mess up - var memInternal = mem.Data as IsilMemoryOperand?; - var mem2 = new IsilMemoryOperand(memInternal!.Value.Base!.Value, memInternal.Value.Addend + destRegSize); + var memInternal = mem as MemoryOperand?; + var mem2 = new MemoryOperand((Register)memInternal!.Value.Base!, addend: memInternal.Value.Addend + destRegSize); - builder.Move(instruction.Address, dest1, mem); - builder.Move(instruction.Address, dest2, InstructionSetIndependentOperand.MakeMemory(mem2)); + Add(address, OpCode.Move, dest1, mem); + Add(address, OpCode.Move, dest2, mem2); break; case Arm64Mnemonic.BL: - builder.Call(instruction.Address, instruction.BranchTarget, GetArgumentOperandsForCall(context, instruction.BranchTarget).ToArray()); + Add(address, OpCode.Call, instruction.BranchTarget, GetArgumentOperandsForCall(context, instruction.BranchTarget).ToArray()); break; case Arm64Mnemonic.RET: - builder.Return(instruction.Address, GetReturnRegisterForContext(context)); + var returnRegister = GetReturnRegisterForContext(context); + if (returnRegister == null) + Add(address, OpCode.Return); + else + Add(address, OpCode.Return, returnRegister); break; case Arm64Mnemonic.B: var target = instruction.BranchTarget; @@ -216,36 +251,54 @@ private void ConvertInstructionStatement(Arm64Instruction instruction, IsilBuild if (target < context.UnderlyingPointer || target > context.UnderlyingPointer + (ulong)context.RawBytes.Length) { //Unconditional branch to outside the method, treat as call (tail-call, specifically) followed by return - builder.Call(instruction.Address, instruction.BranchTarget, GetArgumentOperandsForCall(context, instruction.BranchTarget).ToArray()); - builder.Return(instruction.Address, GetReturnRegisterForContext(context)); + + Add(address, OpCode.Call, instruction.BranchTarget, GetArgumentOperandsForCall(context, instruction.BranchTarget).ToArray()); + var returnRegister2 = GetReturnRegisterForContext(context); + if (returnRegister2 == null) + Add(address, OpCode.Return); + else + Add(address, OpCode.Return, returnRegister2); + } + else + { + Add(address, OpCode.Jump, instruction.BranchTarget); } break; case Arm64Mnemonic.BR: // branches unconditionally to an address in a register, with a hint that this is not a subroutine return. - builder.CallRegister(instruction.Address, ConvertOperand(instruction, 0), noReturn: true); + Add(address, OpCode.IndirectCall, ConvertOperand(instruction, 0)); break; case Arm64Mnemonic.CBNZ: case Arm64Mnemonic.CBZ: - { - //Compare and branch if (non-)zero - var targetAddr = (ulong)((long)instruction.Address + instruction.Op1Imm); + { + //Compare and branch if (non-)zero + var targetAddr = (ulong)((long)instruction.Address + instruction.Op1Imm); - //Compare to zero... - builder.Compare(instruction.Address, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(0)); + //Compare to zero... + Add(address, OpCode.CheckEqual, new Register(null, "Z"), ConvertOperand(instruction, 0), 0); - //And jump if (not) equal - if (instruction.Mnemonic == Arm64Mnemonic.CBZ) - builder.JumpIfEqual(instruction.Address, targetAddr); - else - builder.JumpIfNotEqual(instruction.Address, targetAddr); - } + //And jump if (not) equal + if (instruction.Mnemonic == Arm64Mnemonic.CBZ) + { + Add(address, OpCode.ConditionalJump, targetAddr, new Register(null, "Z")); + } + else + { + Add(address, OpCode.Not, new Register(null, "TEMP"), new Register(null, "Z")); + Add(address, OpCode.ConditionalJump, targetAddr, new Register(null, "TEMP")); + } + } break; case Arm64Mnemonic.CMP: - // Compare: set flag (N or Z or C or V) = (reg1 - reg2) - // builder.Compare(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0)); - goto default; + var op1 = ConvertOperand(instruction, 0); + var op2 = ConvertOperand(instruction, 1); + var temp = new Register(null, "TEMP"); + + Add(address, OpCode.Subtract, temp, op1, op2); + Add(address, OpCode.CheckEqual, new Register(null, "Z"), temp, 0); + break; case Arm64Mnemonic.TBNZ: // TBNZ R, #imm, label @@ -253,76 +306,96 @@ private void ConvertInstructionStatement(Arm64Instruction instruction, IsilBuild case Arm64Mnemonic.TBZ: // TBZ R, #imm, label // test bit and branch if Zero - { - var targetAddr = (ulong)((long)instruction.Address + instruction.Op2Imm); - var bit = InstructionSetIndependentOperand.MakeImmediate(1 << (int)instruction.Op1Imm); - var temp = InstructionSetIndependentOperand.MakeRegister("TEMP"); - var src = ConvertOperand(instruction, 0); - builder.Move(instruction.Address, temp, src); // temp = src - builder.And(instruction.Address, temp, temp, bit); // temp = temp & bit - builder.Compare(instruction.Address, temp, bit); // result = temp == bit - if (instruction.Mnemonic == Arm64Mnemonic.TBNZ) - builder.JumpIfEqual(instruction.Address, targetAddr); // if (result) goto targetAddr - else - builder.JumpIfNotEqual(instruction.Address, targetAddr); // if (result) goto targetAddr - } + { + var targetAddr = (ulong)((long)instruction.Address + instruction.Op2Imm); + var bit = 1 << (int)instruction.Op1Imm; + var temp2 = new Register(null, "TEMP"); + var src = ConvertOperand(instruction, 0); + Add(address, OpCode.Move, temp2, src); // temp = src + Add(address, OpCode.Move, temp2, bit); // temp = temp & bit + Add(address, OpCode.Move, temp2, bit); // result = temp == bit + if (instruction.Mnemonic == Arm64Mnemonic.TBNZ) + { + Add(address, OpCode.ConditionalJump, targetAddr, new Register(null, "Z")); // if (result) goto targetAddr + } + else + { + Add(address, OpCode.Not, new Register(null, "TEMP"), new Register(null, "Z")); + Add(address, OpCode.ConditionalJump, targetAddr, new Register(null, "TEMP")); // if (result) goto targetAddr + } + } break; case Arm64Mnemonic.UBFM: // UBFM dest, src, #, # // dest = (src >> #) & ((1 << #) - 1) - { - var dest = ConvertOperand(instruction, 0); - builder.Move(instruction.Address, dest, ConvertOperand(instruction, 1)); // dest = src - builder.ShiftRight(instruction.Address, dest, ConvertOperand(instruction, 2)); // dest >> # - var imms = (int)instruction.Op3Imm; - builder.And(instruction.Address, dest, dest, - InstructionSetIndependentOperand.MakeImmediate((1 << imms) - 1)); // dest & constexpr { ((1 << #) - 1) } - } + { + var dest3 = ConvertOperand(instruction, 0); + Add(address, OpCode.Move, dest3, ConvertOperand(instruction, 1)); // dest = src + Add(address, OpCode.ShiftRight, dest3, dest3, ConvertOperand(instruction, 2)); // dest = dest >> # + var imms = (int)instruction.Op3Imm; + Add(address, OpCode.And, dest3, dest3, (1 << imms) - 1); // dest = dest & constexpr { ((1 << #) - 1) } + } break; case Arm64Mnemonic.MUL: case Arm64Mnemonic.FMUL: //Multiply is (dest, src1, src2) - builder.Multiply(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); + Add(address, OpCode.Multiply, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); break; case Arm64Mnemonic.ADD: - case Arm64Mnemonic.ADDS: // settings flags case Arm64Mnemonic.FADD: //Add is (dest, src1, src2) - builder.Add(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); + Add(address, OpCode.Add, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); break; case Arm64Mnemonic.SUB: - case Arm64Mnemonic.SUBS: // settings flags case Arm64Mnemonic.FSUB: //Sub is (dest, src1, src2) - builder.Subtract(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); + Add(address, OpCode.Subtract, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); break; case Arm64Mnemonic.AND: - case Arm64Mnemonic.ANDS: //And is (dest, src1, src2) - builder.And(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); + Add(address, OpCode.And, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); + break; + + case Arm64Mnemonic.ADDS: + case Arm64Mnemonic.SUBS: + case Arm64Mnemonic.ANDS: + var dest = ConvertOperand(instruction, 0); + var src1 = ConvertOperand(instruction, 1); + var src2 = ConvertOperand(instruction, 2); + + var opCode = instruction.Mnemonic switch + { + Arm64Mnemonic.ADDS => OpCode.Add, + Arm64Mnemonic.SUBS => OpCode.Subtract, + Arm64Mnemonic.ANDS => OpCode.And, + _ => OpCode.Invalid + }; + + Add(address, opCode, dest, src1, src2); + Add(address, OpCode.CheckEqual, new Register(null, "Z"), dest, 0); break; case Arm64Mnemonic.ORR: //Orr is (dest, src1, src2) - builder.Or(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); + Add(address, OpCode.Or, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); break; case Arm64Mnemonic.EOR: //Eor (aka xor) is (dest, src1, src2) - builder.Xor(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); + Add(address, OpCode.Xor, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); break; default: - builder.NotImplemented(instruction.Address, $"Instruction {instruction.Mnemonic} not yet implemented."); + Add(address, OpCode.NotImplemented, $"Instruction {instruction.Mnemonic} not yet implemented."); break; } } - private InstructionSetIndependentOperand ConvertOperand(Arm64Instruction instruction, int operand) + private object ConvertOperand(Arm64Instruction instruction, int operand) { var kind = operand switch { @@ -347,7 +420,7 @@ private InstructionSetIndependentOperand ConvertOperand(Arm64Instruction instruc if (kind == Arm64OperandKind.ImmediatePcRelative) imm += (long)instruction.Address + 4; //Add 4 to the address to get the address of the next instruction (PC-relative addressing is relative to the address of the next instruction, not the current one - return InstructionSetIndependentOperand.MakeImmediate(imm); + return imm; } if (kind == Arm64OperandKind.FloatingPointImmediate) @@ -361,7 +434,7 @@ private InstructionSetIndependentOperand ConvertOperand(Arm64Instruction instruc _ => throw new ArgumentOutOfRangeException(nameof(operand), $"Operand must be between 0 and 3, inclusive. Got {operand}") }; - return InstructionSetIndependentOperand.MakeImmediate(imm); + return imm; } if (kind == Arm64OperandKind.Register) @@ -375,7 +448,7 @@ private InstructionSetIndependentOperand ConvertOperand(Arm64Instruction instruc _ => throw new ArgumentOutOfRangeException(nameof(operand), $"Operand must be between 0 and 3, inclusive. Got {operand}") }; - return InstructionSetIndependentOperand.MakeRegister(reg.ToString().ToUpperInvariant()); + return new Register(null, reg.ToString().ToUpperInvariant()); } if (kind == Arm64OperandKind.Memory) @@ -386,12 +459,10 @@ private InstructionSetIndependentOperand ConvertOperand(Arm64Instruction instruc if (reg == Arm64Register.INVALID) //Offset only - return InstructionSetIndependentOperand.MakeMemory(new IsilMemoryOperand(offset)); + return new MemoryOperand(addend: offset); //TODO Handle more stuff here - return InstructionSetIndependentOperand.MakeMemory(new IsilMemoryOperand( - InstructionSetIndependentOperand.MakeRegister(reg.ToString().ToUpperInvariant()), - offset)); + return new MemoryOperand(new Register(null, reg.ToString().ToUpperInvariant()), addend: offset); } if (kind == Arm64OperandKind.VectorRegisterElement) @@ -416,25 +487,25 @@ private InstructionSetIndependentOperand ConvertOperand(Arm64Instruction instruc var width = vectorElement.Width switch { - Arm64VectorElementWidth.B => IsilVectorRegisterElementOperand.VectorElementWidth.B, - Arm64VectorElementWidth.H => IsilVectorRegisterElementOperand.VectorElementWidth.H, - Arm64VectorElementWidth.S => IsilVectorRegisterElementOperand.VectorElementWidth.S, - Arm64VectorElementWidth.D => IsilVectorRegisterElementOperand.VectorElementWidth.D, + Arm64VectorElementWidth.B => "B", + Arm64VectorElementWidth.H => "H", + Arm64VectorElementWidth.S => "S", + Arm64VectorElementWidth.D => "D", _ => throw new ArgumentOutOfRangeException(nameof(vectorElement.Width), $"Unknown vector element width {vectorElement.Width}") }; - //.[] - return InstructionSetIndependentOperand.MakeVectorElement(reg.ToString().ToUpperInvariant(), width, vectorElement.Index); + var name = $"{reg.ToString().ToUpperInvariant()}.{width}{vectorElement.Index}"; + return new Register(null, name); } - return InstructionSetIndependentOperand.MakeImmediate($""); + return $""; } public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance() => new NewArm64KeyFunctionAddresses(); public override string PrintAssembly(MethodAnalysisContext context) => context.RawBytes.Span.Length <= 0 ? "" : string.Join("\n", Disassembler.Disassemble(context.RawBytes.Span, context.UnderlyingPointer, new Disassembler.Options(true, true, false)).ToList()); - private InstructionSetIndependentOperand? GetReturnRegisterForContext(MethodAnalysisContext context) + private object? GetReturnRegisterForContext(MethodAnalysisContext context) { var returnType = context.ReturnType; if (returnType.Namespace == nameof(System)) @@ -442,19 +513,19 @@ private InstructionSetIndependentOperand ConvertOperand(Arm64Instruction instruc return returnType.Name switch { "Void" => null, //Void is no return - "Double" => InstructionSetIndependentOperand.MakeRegister(nameof(Arm64Register.V0)), //Builtin double is v0 - "Single" => InstructionSetIndependentOperand.MakeRegister(nameof(Arm64Register.V0)), //Builtin float is v0 - _ => InstructionSetIndependentOperand.MakeRegister(nameof(Arm64Register.X0)), //All other system types are x0 like any other pointer + "Double" => new Register(null, nameof(Arm64Register.V0)), //Builtin double is v0 + "Single" => new Register(null, nameof(Arm64Register.V0)), //Builtin float is v0 + _ => new Register(null, nameof(Arm64Register.X0)), //All other system types are x0 like any other pointer }; } //TODO Do certain value types have different return registers? //Any user type is returned in x0 - return InstructionSetIndependentOperand.MakeRegister(nameof(Arm64Register.X0)); + return new Register(null, nameof(Arm64Register.X0)); } - private List GetArgumentOperandsForCall(MethodAnalysisContext contextBeingAnalyzed, ulong callAddr) + private List GetArgumentOperandsForCall(MethodAnalysisContext contextBeingAnalyzed, ulong callAddr) { if (!contextBeingAnalyzed.AppContext.MethodsByAddress.TryGetValue(callAddr, out var methodsAtAddress)) //TODO @@ -466,12 +537,12 @@ private List GetArgumentOperandsForCall(Method var vectorCount = 0; var nonVectorCount = 0; - var ret = new List(); + var ret = new List(); //Handle 'this' if it's an instance method if (!contextBeingCalled.IsStatic) { - ret.Add(InstructionSetIndependentOperand.MakeRegister(nameof(Arm64Register.X0))); + ret.Add(new Register(null, nameof(Arm64Register.X0))); nonVectorCount++; } @@ -484,16 +555,16 @@ private List GetArgumentOperandsForCall(Method { case "Single": case "Double": - ret.Add(InstructionSetIndependentOperand.MakeRegister((Arm64Register.V0 + vectorCount++).ToString().ToUpperInvariant())); + ret.Add(new Register(null, (Arm64Register.V0 + vectorCount++).ToString().ToUpperInvariant())); break; default: - ret.Add(InstructionSetIndependentOperand.MakeRegister((Arm64Register.X0 + nonVectorCount++).ToString().ToUpperInvariant())); + ret.Add(new Register(null, (Arm64Register.X0 + nonVectorCount++).ToString().ToUpperInvariant())); break; } } else { - ret.Add(InstructionSetIndependentOperand.MakeRegister((Arm64Register.X0 + nonVectorCount++).ToString().ToUpperInvariant())); + ret.Add(new Register(null, (Arm64Register.X0 + nonVectorCount++).ToString().ToUpperInvariant())); } } diff --git a/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs b/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs index 122c19c8f..b44b3fc07 100644 --- a/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs @@ -33,7 +33,12 @@ public override Memory GetRawBytesForMethod(MethodAnalysisContext context, return Array.Empty(); } - public override List GetIsilFromMethod(MethodAnalysisContext context) + public override List GetIsilFromMethod(MethodAnalysisContext context) + { + return []; + } + + public override List GetParameterOperandsFromMethod(MethodAnalysisContext context) { return []; } diff --git a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs index 0484b2ce7..68fc561a2 100644 --- a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs @@ -4,8 +4,6 @@ using Cpp2IL.Core.Api; using Cpp2IL.Core.Extensions; using Cpp2IL.Core.Il2CppApiFunctions; -using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Logging; using Cpp2IL.Core.Model.Contexts; using Cpp2IL.Core.Utils; using Iced.Intel; @@ -47,26 +45,71 @@ public override string PrintAssembly(MethodAnalysisContext context) } } - public override List GetIsilFromMethod(MethodAnalysisContext context) + public override List GetIsilFromMethod(MethodAnalysisContext context) { - var builder = new IsilBuilder(); + var instructions = new List(); + var addresses = new List(); foreach (var instruction in X86Utils.Iterate(context)) + ConvertInstructionStatement(instruction, instructions, addresses, context); + + // Add return if the function doesn't end with one already + if (instructions.Count > 0 && instructions[^1].OpCode != ISIL.OpCode.Return) { - ConvertInstructionStatement(instruction, builder, context); + var index = instructions[^1].Index + 1; + + if (context.IsVoid) + instructions.Add(new ISIL.Instruction(index, ISIL.OpCode.Return)); + else if (context.Definition?.RawReturnType?.Type is Il2CppTypeEnum.IL2CPP_TYPE_R4 or Il2CppTypeEnum.IL2CPP_TYPE_R8) + instructions.Add(new ISIL.Instruction(index, ISIL.OpCode.Return, new ISIL.Register(null, "xmm0"))); + else + instructions.Add(new ISIL.Instruction(index, ISIL.OpCode.Return, new ISIL.Register(null, "rax"))); } - builder.FixJumps(); + // fix branches + for (var i = 0; i < instructions.Count; i++) + { + var instruction = instructions[i]; + + if (instruction.OpCode != ISIL.OpCode.Jump && instruction.OpCode != ISIL.OpCode.ConditionalJump) + continue; + + var targetAddress = (ulong)instruction.Operands[0]; + var targetIndex = addresses.FindIndex(addr => addr == targetAddress); + + if (targetIndex == -1) + { + instruction.OpCode = ISIL.OpCode.Invalid; + instruction.Operands = [$"Jump target not found in method: 0x{targetAddress:X4}"]; + continue; + } + + var targetInstruction = instructions[targetIndex]; - return builder.BackingStatementList; + instruction.Operands[0] = targetInstruction; + } + + return instructions; } + public override List GetParameterOperandsFromMethod(MethodAnalysisContext context) + { + return X64CallingConventionResolver.ResolveForManaged(context).ToList(); + } - private void ConvertInstructionStatement(Instruction instruction, IsilBuilder builder, MethodAnalysisContext context) + private void ConvertInstructionStatement(Instruction instruction, List instructions, List addresses, MethodAnalysisContext context) { var callNoReturn = false; int operandSize; + ISIL.Instruction Add(ulong address, ISIL.OpCode opCode, params object[] operands) + { + addresses.Add(address); + var newInstruction = new ISIL.Instruction(instructions.Count, opCode, operands); + instructions.Add(newInstruction); + return newInstruction; + } + switch (instruction.Mnemonic) { case Mnemonic.Mov: @@ -84,97 +127,100 @@ private void ConvertInstructionStatement(Instruction instruction, IsilBuilder bu case Mnemonic.Cvtps2pd: // same, but float to double case Mnemonic.Cvttsd2si: // same, but double to integer case Mnemonic.Movdqu: // DEST[127:0] := SRC[127:0] - builder.Move(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + Add(instruction.IP, ISIL.OpCode.Move, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); break; case Mnemonic.Cbw: // AX := sign-extend AL - builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.AX)), - InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.AL))); + Add(instruction.IP, ISIL.OpCode.Move, new ISIL.Register(null, X86Utils.GetRegisterName(Register.AX)), + new ISIL.Register(null, X86Utils.GetRegisterName(Register.AL))); break; case Mnemonic.Cwde: // EAX := sign-extend AX - builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EAX)), - InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.AX))); + Add(instruction.IP, ISIL.OpCode.Move, new ISIL.Register(null, X86Utils.GetRegisterName(Register.EAX)), + new ISIL.Register(null, X86Utils.GetRegisterName(Register.AX))); break; case Mnemonic.Cdqe: // RAX := sign-extend EAX - builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.RAX)), - InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EAX))); + Add(instruction.IP, ISIL.OpCode.Move, new ISIL.Register(null, X86Utils.GetRegisterName(Register.RAX)), + new ISIL.Register(null, X86Utils.GetRegisterName(Register.EAX))); break; // it's very unsafe if there's been a jump to the next instruction here before. case Mnemonic.Cwd: // Convert Word to Doubleword - { - // The CWD instruction copies the sign (bit 15) of the value in the AX register into every bit position in the DX register - var temp = InstructionSetIndependentOperand.MakeRegister("TEMP"); - builder.Move(instruction.IP, temp, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.AX))); // TEMP = AX - builder.ShiftRight(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(15)); // TEMP >>= 15 - builder.Compare(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(1)); // temp == 1 - builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1); - // temp == 1 ? DX := ushort.Max (1111111111) or DX := 0 - builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.DX)), InstructionSetIndependentOperand.MakeImmediate(ushort.MaxValue)); - builder.Goto(instruction.IP, instruction.IP + 2); - builder.Move(instruction.IP + 1, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.DX)), InstructionSetIndependentOperand.MakeImmediate(0)); - builder.Nop(instruction.IP + 2); - break; - } + { + // The CWD instruction copies the sign (bit 15) of the value in the AX register into every bit position in the DX register + var temp = new ISIL.Register(null, "TEMP"); + Add(instruction.IP, ISIL.OpCode.Move, temp, new ISIL.Register(null, X86Utils.GetRegisterName(Register.AX))); // TEMP = AX + Add(instruction.IP, ISIL.OpCode.ShiftRight, temp, temp, 15); // TEMP >>= 15 + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, temp, 1); // temp == 1 + Add(instruction.IP, ISIL.OpCode.Not, temp, temp); // temp = !temp + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, temp); + // temp == 1 ? DX := ushort.Max (1111111111) or DX := 0 + Add(instruction.IP, ISIL.OpCode.Move, new ISIL.Register(null, X86Utils.GetRegisterName(Register.DX)), ushort.MaxValue); + Add(instruction.IP, ISIL.OpCode.Jump, instruction.IP + 2); + Add(instruction.IP + 1, ISIL.OpCode.Move, new ISIL.Register(null, X86Utils.GetRegisterName(Register.DX)), 0); + Add(instruction.IP + 2, ISIL.OpCode.Nop); + break; + } case Mnemonic.Cdq: // Convert Doubleword to Quadword - { - // The CDQ instruction copies the sign (bit 31) of the value in the EAX register into every bit position in the EDX register. - var temp = InstructionSetIndependentOperand.MakeRegister("TEMP"); - builder.Move(instruction.IP, temp, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EAX))); // TEMP = EAX - builder.ShiftRight(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(31)); // TEMP >>= 31 - builder.Compare(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(1)); // temp == 1 - builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1); - // temp == 1 ? EDX := uint.Max (1111111111) or EDX := 0 - builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EDX)), InstructionSetIndependentOperand.MakeImmediate(uint.MaxValue)); - builder.Goto(instruction.IP, instruction.IP + 2); - builder.Move(instruction.IP + 1, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EDX)), InstructionSetIndependentOperand.MakeImmediate(0)); - builder.Nop(instruction.IP + 2); - break; - } + { + // The CDQ instruction copies the sign (bit 31) of the value in the EAX register into every bit position in the EDX register. + var temp = new ISIL.Register(null, "TEMP"); + Add(instruction.IP, ISIL.OpCode.Move, temp, new ISIL.Register(null, X86Utils.GetRegisterName(Register.EAX))); // TEMP = EAX + Add(instruction.IP, ISIL.OpCode.ShiftRight, temp, temp, 31); // TEMP >>= 31 + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, temp, 1); // temp == 1 + Add(instruction.IP, ISIL.OpCode.Not, temp, temp); // temp = !temp + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, temp); + // temp == 1 ? EDX := uint.Max (1111111111) or EDX := 0 + Add(instruction.IP, ISIL.OpCode.Move, new ISIL.Register(null, X86Utils.GetRegisterName(Register.EDX)), uint.MaxValue); + Add(instruction.IP, ISIL.OpCode.Jump, instruction.IP + 2); + Add(instruction.IP + 1, ISIL.OpCode.Move, new ISIL.Register(null, X86Utils.GetRegisterName(Register.EDX)), 0); + Add(instruction.IP + 2, ISIL.OpCode.Nop); + break; + } case Mnemonic.Cqo: // same... - { - // The CQO instruction copies the sign (bit 63) of the value in the EAX register into every bit position in the RDX register. - var temp = InstructionSetIndependentOperand.MakeRegister("TEMP"); - builder.Move(instruction.IP, temp, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.RAX))); // TEMP = RAX - builder.ShiftRight(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(63)); // TEMP >>= 63 - builder.Compare(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(1)); // temp == 1 - builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1); - // temp == 1 ? RDX := ulong.Max (1111111111) or RDX := 0 - builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.RDX)), InstructionSetIndependentOperand.MakeImmediate(ulong.MaxValue)); - builder.Goto(instruction.IP, instruction.IP + 2); - builder.Move(instruction.IP + 1, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.RDX)), InstructionSetIndependentOperand.MakeImmediate(0)); - builder.Nop(instruction.IP + 2); - break; - } + { + // The CQO instruction copies the sign (bit 63) of the value in the EAX register into every bit position in the RDX register. + var temp = new ISIL.Register(null, "TEMP"); + Add(instruction.IP, ISIL.OpCode.Move, temp, new ISIL.Register(null, X86Utils.GetRegisterName(Register.RAX))); // TEMP = RAX + Add(instruction.IP, ISIL.OpCode.ShiftRight, temp, temp, 63); // TEMP >>= 63 + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, temp, 1); // temp == 1 + Add(instruction.IP, ISIL.OpCode.Not, temp, temp); // temp = !temp + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, temp); + // temp == 1 ? RDX := ulong.Max (1111111111) or RDX := 0 + Add(instruction.IP, ISIL.OpCode.Move, new ISIL.Register(null, X86Utils.GetRegisterName(Register.RDX)), ulong.MaxValue); + Add(instruction.IP, ISIL.OpCode.Jump, instruction.IP + 2); + Add(instruction.IP + 1, ISIL.OpCode.Move, new ISIL.Register(null, X86Utils.GetRegisterName(Register.RDX)), 0); + Add(instruction.IP + 2, ISIL.OpCode.Nop); + break; + } case Mnemonic.Lea: - builder.LoadAddress(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + Add(instruction.IP, ISIL.OpCode.Move, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1, true)); break; case Mnemonic.Xor: case Mnemonic.Xorps: //xorps is just floating point xor if (instruction.Op0Kind == OpKind.Register && instruction.Op1Kind == OpKind.Register && instruction.Op0Register == instruction.Op1Register) - builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(0)); + Add(instruction.IP, ISIL.OpCode.Move, ConvertOperand(instruction, 0), 0); else - builder.Xor(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + Add(instruction.IP, ISIL.OpCode.Xor, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); break; case Mnemonic.Shl: // unsigned shift case Mnemonic.Sal: // signed shift - builder.ShiftLeft(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + Add(instruction.IP, ISIL.OpCode.ShiftLeft, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); break; case Mnemonic.Shr: // unsigned shift case Mnemonic.Sar: // signed shift - builder.ShiftRight(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + Add(instruction.IP, ISIL.OpCode.ShiftRight, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); break; case Mnemonic.And: case Mnemonic.Andps: //Floating point and - builder.And(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + Add(instruction.IP, ISIL.OpCode.And, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); break; case Mnemonic.Or: case Mnemonic.Orps: //Floating point or - builder.Or(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + Add(instruction.IP, ISIL.OpCode.Or, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); break; case Mnemonic.Not: - builder.Neg(instruction.IP, ConvertOperand(instruction, 0)); + Add(instruction.IP, ISIL.OpCode.Not, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0)); break; case Mnemonic.Neg: // dest := -dest - builder.Neg(instruction.IP, ConvertOperand(instruction, 0)); + Add(instruction.IP, ISIL.OpCode.Negate, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0)); break; case Mnemonic.Imul: if (instruction.OpCount == 1) @@ -183,7 +229,7 @@ private void ConvertInstructionStatement(Instruction instruction, IsilBuilder bu switch (opSize) // TODO: I don't know how to work with dual registers here, I left hints though { case 1: // Op0 * AL -> AX - builder.Multiply(instruction.IP, Register.AX.MakeIndependent(), ConvertOperand(instruction, 0), Register.AL.MakeIndependent()); + Add(instruction.IP, ISIL.OpCode.Multiply, Register.AX.MakeIndependent(), ConvertOperand(instruction, 0), Register.AL.MakeIndependent()); return; case 2: // Op0 * AX -> DX:AX @@ -202,28 +248,28 @@ private void ConvertInstructionStatement(Instruction instruction, IsilBuilder bu // if got to here, it didn't work goto default; } - else if (instruction.OpCount == 3) builder.Multiply(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); - else builder.Multiply(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + else if (instruction.OpCount == 3) Add(instruction.IP, ISIL.OpCode.Multiply, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); + else Add(instruction.IP, ISIL.OpCode.Multiply, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); break; case Mnemonic.Mulss: case Mnemonic.Vmulss: if (instruction.OpCount == 3) - builder.Multiply(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); + Add(instruction.IP, ISIL.OpCode.Multiply, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); else if (instruction.OpCount == 2) - builder.Multiply(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + Add(instruction.IP, ISIL.OpCode.Multiply, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); else goto default; break; - + case Mnemonic.Divss: // Divide Scalar Single Precision Floating-Point Values. DEST[31:0] = DEST[31:0] / SRC[31:0] - builder.Divide(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + Add(instruction.IP, ISIL.OpCode.Divide, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); break; case Mnemonic.Vdivss: // VEX Divide Scalar Single Precision Floating-Point Values. DEST[31:0] = SRC1[31:0] / SRC2[31:0] - builder.Divide(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); + Add(instruction.IP, ISIL.OpCode.Divide, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2)); break; - + case Mnemonic.Ret: // TODO: Verify correctness of operation with Vectors. @@ -232,21 +278,21 @@ private void ConvertInstructionStatement(Instruction instruction, IsilBuilder bu // Are st* registers even used in il2cpp games? if (context.IsVoid) - builder.Return(instruction.IP); + Add(instruction.IP, ISIL.OpCode.Return); else if (context.Definition?.RawReturnType?.Type is Il2CppTypeEnum.IL2CPP_TYPE_R4 or Il2CppTypeEnum.IL2CPP_TYPE_R8) - builder.Return(instruction.IP, InstructionSetIndependentOperand.MakeRegister("xmm0")); + Add(instruction.IP, ISIL.OpCode.Return, new ISIL.Register(null, "xmm0")); else - builder.Return(instruction.IP, InstructionSetIndependentOperand.MakeRegister("rax")); + Add(instruction.IP, ISIL.OpCode.Return, new ISIL.Register(null, "rax")); break; case Mnemonic.Push: operandSize = instruction.Op0Kind == OpKind.Register ? instruction.Op0Register.GetSize() : instruction.MemorySize.GetSize(); - builder.ShiftStack(instruction.IP, -operandSize); - builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeStack(0), ConvertOperand(instruction, 0)); + Add(instruction.IP, ISIL.OpCode.ShiftStack, -operandSize); + Add(instruction.IP, ISIL.OpCode.Move, new ISIL.StackOffset(0), ConvertOperand(instruction, 0)); break; case Mnemonic.Pop: operandSize = instruction.Op0Kind == OpKind.Register ? instruction.Op0Register.GetSize() : instruction.MemorySize.GetSize(); - builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeStack(0)); - builder.ShiftStack(instruction.IP, operandSize); + Add(instruction.IP, ISIL.OpCode.Move, ConvertOperand(instruction, 0), new ISIL.StackOffset(0)); + Add(instruction.IP, ISIL.OpCode.ShiftStack, operandSize); break; case Mnemonic.Sub: case Mnemonic.Add: @@ -256,117 +302,145 @@ private void ConvertInstructionStatement(Instruction instruction, IsilBuilder bu if (instruction.Op0Register == Register.RSP && instruction.Op1Kind.IsImmediate()) { var amount = (int)instruction.GetImmediate(1); - builder.ShiftStack(instruction.IP, isSubtract ? -amount : amount); + Add(instruction.IP, ISIL.OpCode.ShiftStack, isSubtract ? -amount : amount); break; } var left = ConvertOperand(instruction, 0); var right = ConvertOperand(instruction, 1); if (isSubtract) - builder.Subtract(instruction.IP, left, left, right); + Add(instruction.IP, ISIL.OpCode.Subtract, left, left, right); else - builder.Add(instruction.IP, left, left, right); + Add(instruction.IP, ISIL.OpCode.Add, left, left, right); break; case Mnemonic.Addss: case Mnemonic.Subss: - { - // Addss and subss are just floating point add/sub, but we don't need to handle the stack stuff - // But we do need to handle 2 vs 3 operand forms - InstructionSetIndependentOperand dest; - InstructionSetIndependentOperand src1; - InstructionSetIndependentOperand src2; - - if (instruction.OpCount == 3) { - //dest, src1, src2 - dest = ConvertOperand(instruction, 0); - src1 = ConvertOperand(instruction, 1); - src2 = ConvertOperand(instruction, 2); - } - else if (instruction.OpCount == 2) - { - //DestAndSrc1, Src2 - dest = ConvertOperand(instruction, 0); - src1 = dest; - src2 = ConvertOperand(instruction, 1); - } - else - goto default; + // Addss and subss are just floating point add/sub, but we don't need to handle the stack stuff + // But we do need to handle 2 vs 3 operand forms + object dest; + object src1; + object src2; - if (instruction.Mnemonic == Mnemonic.Subss) - builder.Subtract(instruction.IP, dest, src1, src2); - else - builder.Add(instruction.IP, dest, src1, src2); - break; - } + if (instruction.OpCount == 3) + { + //dest, src1, src2 + dest = ConvertOperand(instruction, 0); + src1 = ConvertOperand(instruction, 1); + src2 = ConvertOperand(instruction, 2); + } + else if (instruction.OpCount == 2) + { + //DestAndSrc1, Src2 + dest = ConvertOperand(instruction, 0); + src1 = dest; + src2 = ConvertOperand(instruction, 1); + } + else + goto default; + + if (instruction.Mnemonic == Mnemonic.Subss) + Add(instruction.IP, ISIL.OpCode.Subtract, dest, src1, src2); + else + Add(instruction.IP, ISIL.OpCode.Add, dest, src1, src2); + break; + } // The following pair of instructions does not update the Carry Flag (CF): case Mnemonic.Dec: - builder.Subtract(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(1)); + Add(instruction.IP, ISIL.OpCode.Subtract, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), 1); break; case Mnemonic.Inc: - builder.Add(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(1)); + Add(instruction.IP, ISIL.OpCode.Add, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), 1); break; case Mnemonic.Shufps: // Packed Interleave Shuffle of Quadruplets of Single Precision Floating-Point Values - { - if (instruction.Op1Kind == OpKind.Memory) - goto default; - - var imm = instruction.Immediate8; - var src1 = X86Utils.GetRegisterName(instruction.Op0Register); - var src2 = X86Utils.GetRegisterName(instruction.Op1Register); - var dest = "XMM_TEMP"; - //TEMP_DEST[31:0] := Select4(SRC1[127:0], imm8[1:0]); - builder.Move(instruction.IP, ConvertVector(dest, 0), ConvertVector(src1, imm & 0b11)); - //TEMP_DEST[63:32] := Select4(SRC1[127:0], imm8[3:2]); - builder.Move(instruction.IP, ConvertVector(dest, 1), ConvertVector(src1, (imm >> 2) & 0b11)); - //TEMP_DEST[95:64] := Select4(SRC2[127:0], imm8[5:4]); - builder.Move(instruction.IP, ConvertVector(dest, 2), ConvertVector(src2, (imm >> 4) & 0b11)); - //TEMP_DEST[127:96] := Select4(SRC2[127:0], imm8[7:6]); - builder.Move(instruction.IP, ConvertVector(dest, 3), ConvertVector(src2, (imm >> 6) & 0b11)); - // where Select4(regSlice, imm) => regSlice.[imm switch => { 0 => 0..31, 1 => 32..63, 2 => 64..95, 3 => 96...127 }]; - builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeRegister(dest)); // DEST = TEMP_DEST - break; + { + if (instruction.Op1Kind == OpKind.Memory) + goto default; - static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => - InstructionSetIndependentOperand.MakeVectorElement(reg, IsilVectorRegisterElementOperand.VectorElementWidth.S, imm); - } - - case Mnemonic.Unpcklps : // Unpack and Interleave Low Packed Single Precision Floating-Point Values - { - if (instruction.Op1Kind == OpKind.Memory) - goto default; - - var src1 = X86Utils.GetRegisterName(instruction.Op0Register); - var src2 = X86Utils.GetRegisterName(instruction.Op1Register); - var dest = "XMM_TEMP"; - builder.Move(instruction.IP, ConvertVector(dest, 0), ConvertVector(src1, 0)); //TMP_DEST[31:0] := SRC1[31:0] - builder.Move(instruction.IP, ConvertVector(dest, 1), ConvertVector(src2, 0)); //TMP_DEST[63:32] := SRC2[31:0] - builder.Move(instruction.IP, ConvertVector(dest, 2), ConvertVector(src1, 1)); //TMP_DEST[95:64] := SRC1[63:32] - builder.Move(instruction.IP, ConvertVector(dest, 3), ConvertVector(src2, 1)); //TMP_DEST[127:96] := SRC2[63:32] - builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeRegister(dest)); // DEST = TEMP_DEST - break; + var imm = instruction.Immediate8; + var src1 = X86Utils.GetRegisterName(instruction.Op0Register); + var src2 = X86Utils.GetRegisterName(instruction.Op1Register); + + // Element selection + Add(instruction.IP, ISIL.OpCode.Move, + new ISIL.Register(null, "XMM_TEMP" + "_0"), + new ISIL.Register(null, $"{src1}_{imm & 0b11}")); + + Add(instruction.IP, ISIL.OpCode.Move, + new ISIL.Register(null, "XMM_TEMP" + "_1"), + new ISIL.Register(null, $"{src1}_{(imm >> 2) & 0b11}")); + + Add(instruction.IP, ISIL.OpCode.Move, + new ISIL.Register(null, "XMM_TEMP" + "_2"), + new ISIL.Register(null, $"{src2}_{(imm >> 4) & 0b11}")); + + Add(instruction.IP, ISIL.OpCode.Move, + new ISIL.Register(null, "XMM_TEMP" + "_3"), + new ISIL.Register(null, $"{src2}_{(imm >> 6) & 0b11}")); + + Add(instruction.IP, ISIL.OpCode.Move, + ConvertOperand(instruction, 0), + new ISIL.Register(null, "XMM_TEMP")); + + break; + } + + case Mnemonic.Unpcklps: // Unpack and Interleave Low Packed Single Precision Floating-Point Values + { + if (instruction.Op1Kind == OpKind.Memory) + goto default; + + var src1 = X86Utils.GetRegisterName(instruction.Op0Register); + var src2 = X86Utils.GetRegisterName(instruction.Op1Register); + + // Interleaving lanes + Add(instruction.IP, ISIL.OpCode.Move, + new ISIL.Register(null, (string?)"XMM_TEMP" + "_0"), + new ISIL.Register(null, $"{src1}_0")); // SRC1[31:0] + + Add(instruction.IP, ISIL.OpCode.Move, + new ISIL.Register(null, (string?)"XMM_TEMP" + "_1"), + new ISIL.Register(null, $"{src2}_0")); // SRC2[31:0] + + Add(instruction.IP, ISIL.OpCode.Move, + new ISIL.Register(null, (string?)"XMM_TEMP" + "_2"), + new ISIL.Register(null, $"{src1}_1")); // SRC1[63:32] + + Add(instruction.IP, ISIL.OpCode.Move, + new ISIL.Register(null, (string?)"XMM_TEMP" + "_3"), + new ISIL.Register(null, $"{src2}_1")); // SRC2[63:32] + + Add(instruction.IP, ISIL.OpCode.Move, + ConvertOperand(instruction, 0), + new ISIL.Register(null, (string?)"XMM_TEMP")); + + break; + } - static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => - InstructionSetIndependentOperand.MakeVectorElement(reg, IsilVectorRegisterElementOperand.VectorElementWidth.S, imm); - } - case Mnemonic.Call: // We don't try and resolve which method is being called, but we do need to know how many parameters it has // I would hope that all of these methods have the same number of arguments, else how can they be inlined? var target = instruction.NearBranchTarget; - if (instruction.Op0Kind == OpKind.Register) + if (instruction.Op0Kind == OpKind.Register || instruction.Op0Kind == OpKind.Memory) { - builder.CallRegister(instruction.IP, ConvertOperand(instruction, 0)); + Add(instruction.IP, ISIL.OpCode.IndirectCall, ConvertOperand(instruction, 0)); } else if (context.AppContext.MethodsByAddress.TryGetValue(target, out var possibleMethods)) { if (possibleMethods.Count == 1) { - builder.Call(instruction.IP, target, X64CallingConventionResolver.ResolveForManaged(possibleMethods[0])); + ISIL.Instruction call; + + if (possibleMethods[0].IsVoid) + call = Add(instruction.IP, ISIL.OpCode.CallVoid, target); + else + call = Add(instruction.IP, ISIL.OpCode.Call, target, new ISIL.Register(null, "eax") /* return value */); + + call.Operands.AddRange(X64CallingConventionResolver.ResolveForManaged(possibleMethods[0])); } else { @@ -388,7 +462,14 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => // On post-analysis, you can discard methods according to the registers used, see X64CallingConventionResolver. // This is less effective on GCC because MSVC doesn't overlap registers. - builder.Call(instruction.IP, target, X64CallingConventionResolver.ResolveForManaged(ctx)); + ISIL.Instruction call; + + if (ctx.IsVoid) + call = Add(instruction.IP, ISIL.OpCode.CallVoid, target); + else + call = Add(instruction.IP, ISIL.OpCode.Call, target, new ISIL.Register(null, "eax") /* return value */); + + call.Operands.AddRange(X64CallingConventionResolver.ResolveForManaged(ctx)); } } else @@ -397,7 +478,8 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => // This will need to be rewritten if we ever stumble upon an unmanaged method that accepts more than 4 parameters. // These can be converted to dedicated ISIL instructions for specific API functions at a later stage. (by a post-processing step) - builder.Call(instruction.IP, target, X64CallingConventionResolver.ResolveForUnmanaged(context.AppContext, target)); + var call = Add(instruction.IP, ISIL.OpCode.Call, target, new ISIL.Register(null, "eax") /* return value */); + call.Operands.AddRange(X64CallingConventionResolver.ResolveForUnmanaged(context.AppContext, target)); } if (callNoReturn) @@ -418,7 +500,7 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => case Mnemonic.Test: if (instruction.Op0Kind == OpKind.Register && instruction.Op1Kind == OpKind.Register && instruction.Op0Register == instruction.Op1Register) { - builder.Compare(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(0)); + AddCompareInstruction(instruction.IP, ConvertOperand(instruction, 0), 0); break; } @@ -427,11 +509,11 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => case Mnemonic.Cmp: case Mnemonic.Comiss: //comiss is just a floating point compare dest[31:0] == src[31:0] case Mnemonic.Ucomiss: // same, but unsigned - builder.Compare(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + AddCompareInstruction(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); break; - + case Mnemonic.Cmove: // move if condition - case Mnemonic.Cmovne: + case Mnemonic.Cmovne: case Mnemonic.Cmova: case Mnemonic.Cmovg: case Mnemonic.Cmovae: @@ -439,84 +521,112 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => case Mnemonic.Cmovb: case Mnemonic.Cmovl: case Mnemonic.Cmovbe: - case Mnemonic.Cmovle: - case Mnemonic.Cmovs: - case Mnemonic.Cmovns: + case Mnemonic.Cmovle: + case Mnemonic.Cmovs: + case Mnemonic.Cmovns: switch (instruction.Mnemonic) { case Mnemonic.Cmove: // equals - builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1); // skip if not eq + Add(instruction.IP, ISIL.OpCode.Not, new ISIL.Register(null, "TEMP"), new ISIL.Register(null, "ZF")); // TEMP = !ZF + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, new ISIL.Register(null, "TEMP")); // skip if not eq break; case Mnemonic.Cmovne: // not equals - builder.JumpIfEqual(instruction.IP, instruction.IP + 1); // skip if eq + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, new ISIL.Register(null, "ZF")); // skip if eq break; case Mnemonic.Cmovs: // sign - builder.JumpIfNotSign(instruction.IP, instruction.IP + 1); // skip if not sign + Add(instruction.IP, ISIL.OpCode.Not, new ISIL.Register(null, "TEMP"), new ISIL.Register(null, "SF")); // TEMP = !SF + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, new ISIL.Register(null, "TEMP")); // skip if not sign break; case Mnemonic.Cmovns: // not sign - builder.JumpIfSign(instruction.IP, instruction.IP + 1); // skip if sign + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, new ISIL.Register(null, "SF")); // skip if sign break; case Mnemonic.Cmova: case Mnemonic.Cmovg: // greater - builder.JumpIfLessOrEqual(instruction.IP, instruction.IP + 1); // skip if not gt + var temp = new ISIL.Register(null, "TEMP"); + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, new ISIL.Register(null, "SF"), new ISIL.Register(null, "OF")); // TEMP = SF == OF + Add(instruction.IP, ISIL.OpCode.Not, temp, temp); // TEMP = !TEMP + Add(instruction.IP, ISIL.OpCode.Or, temp, temp, new ISIL.Register(null, "ZF")); // TEMP = TEMP || ZF + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, temp); // skip if not gt break; case Mnemonic.Cmovae: case Mnemonic.Cmovge: // greater or eq - builder.JumpIfLess(instruction.IP, instruction.IP + 1); // skip if not gt or eq + temp = new ISIL.Register(null, "TEMP"); + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, new ISIL.Register(null, "SF"), new ISIL.Register(null, "OF")); // TEMP = SF == OF + Add(instruction.IP, ISIL.OpCode.Not, temp, temp); // TEMP = !TEMP + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, temp); // skip if not gt or eq break; case Mnemonic.Cmovb: case Mnemonic.Cmovl: // less - builder.JumpIfGreaterOrEqual(instruction.IP, instruction.IP + 1); // skip if not lt + temp = new ISIL.Register(null, "TEMP"); + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, new ISIL.Register(null, "SF"), new ISIL.Register(null, "OF")); // TEMP = SF == OF + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, temp); // skip if not lt break; case Mnemonic.Cmovbe: case Mnemonic.Cmovle: // less or eq - builder.JumpIfGreater(instruction.IP, instruction.IP + 1); // skip if not lt or eq + temp = new ISIL.Register(null, "TEMP"); + var temp2 = new ISIL.Register(null, "TEMP2"); + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, new ISIL.Register(null, "SF"), new ISIL.Register(null, "OF")); // TEMP = SF == OF + Add(instruction.IP, ISIL.OpCode.Not, temp2, new ISIL.Register(null, "ZF")); // TEMP2 = !ZF + Add(instruction.IP, ISIL.OpCode.And, temp, temp, temp2); // TEMP = TEMP && TEMP2 + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, temp); // skip if not lt or eq break; } - builder.Move(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); // set if cond - builder.Nop(instruction.IP + 1); + Add(instruction.IP, ISIL.OpCode.Move, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); // set if cond + Add(instruction.IP + 1, ISIL.OpCode.Nop); break; case Mnemonic.Maxss: // dest < src ? src : dest case Mnemonic.Minss: // dest > src ? src : dest - { - var dest = ConvertOperand(instruction, 0); - var src = ConvertOperand(instruction, 1); - builder.Compare(instruction.IP, dest, src); // compare dest & src - if (instruction.Mnemonic == Mnemonic.Maxss) - builder.JumpIfGreaterOrEqual(instruction.IP, instruction.IP + 1); // enter if dest < src - else - builder.JumpIfLessOrEqual(instruction.IP, instruction.IP + 1); // enter if dest > src - builder.Move(instruction.IP, dest, src); // dest = src - builder.Nop(instruction.IP + 1); // exit for IF - break; - } - + { + var dest = ConvertOperand(instruction, 0); + var src = ConvertOperand(instruction, 1); + AddCompareInstruction(instruction.IP, dest, src); // compare dest & src + if (instruction.Mnemonic == Mnemonic.Maxss) + { + var temp = new ISIL.Register(null, "TEMP"); + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, new ISIL.Register(null, "SF"), new ISIL.Register(null, "OF")); // TEMP = SF == OF + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, temp); // enter if dest < src + } + else + { + var temp = new ISIL.Register(null, "TEMP"); + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, new ISIL.Register(null, "SF"), new ISIL.Register(null, "OF")); // TEMP = SF == OF + Add(instruction.IP, ISIL.OpCode.Not, temp, temp); // TEMP = !TEMP + Add(instruction.IP, ISIL.OpCode.Or, temp, temp, new ISIL.Register(null, "ZF")); // TEMP = TEMP || ZF + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, temp); // enter if dest > src + } + + Add(instruction.IP, ISIL.OpCode.Move, dest, src); // dest = src + Add(instruction.IP + 1, ISIL.OpCode.Nop); // exit for IF + break; + } + case Mnemonic.Cmpxchg: // compare and exchange - { - var accumulator = InstructionSetIndependentOperand.MakeRegister(instruction.Op1Register.GetSize() switch { - 8 => X86Utils.GetRegisterName(Register.RAX), - 4 => X86Utils.GetRegisterName(Register.EAX), - 2 => X86Utils.GetRegisterName(Register.AX), - 1 => X86Utils.GetRegisterName(Register.AL), - _ => throw new NotSupportedException("unexpected behavior") - }); - var dest = ConvertOperand(instruction, 0); - var src = ConvertOperand(instruction, 1); - builder.Compare(instruction.IP, accumulator, dest); - builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1); // if accumulator == dest - // SET ZF = 1 - builder.Move(instruction.IP, dest, src); // DEST = SRC - builder.Goto(instruction.IP, instruction.IP + 2); // END IF - // ELSE - // SET ZF = 0 - builder.Move(instruction.IP + 1, accumulator, dest); // accumulator = dest - - builder.Nop(instruction.IP + 2); // exit for IF - break; - } - + var accumulator = new ISIL.Register(null, instruction.Op1Register.GetSize() switch + { + 8 => X86Utils.GetRegisterName(Register.RAX), + 4 => X86Utils.GetRegisterName(Register.EAX), + 2 => X86Utils.GetRegisterName(Register.AX), + 1 => X86Utils.GetRegisterName(Register.AL), + _ => throw new NotSupportedException("unexpected behavior") + }); + var dest = ConvertOperand(instruction, 0); + var src = ConvertOperand(instruction, 1); + AddCompareInstruction(instruction.IP, accumulator, dest); // compare dest & accumulator + Add(instruction.IP, ISIL.OpCode.Not, new ISIL.Register(null, "TEMP"), new ISIL.Register(null, "ZF")); // TEMP = !ZF + Add(instruction.IP, ISIL.OpCode.ConditionalJump, instruction.IP + 1, new ISIL.Register(null, "TEMP")); // if accumulator == dest + // SET ZF = 1 + Add(instruction.IP, ISIL.OpCode.Move, dest, src); // DEST = SRC + Add(instruction.IP, ISIL.OpCode.Jump, instruction.IP + 2); // END IF + // ELSE + // SET ZF = 0 + Add(instruction.IP + 1, ISIL.OpCode.Move, accumulator, dest); // accumulator = dest + + Add(instruction.IP + 2, ISIL.OpCode.Nop); // exit for IF + break; + } + case Mnemonic.Jmp: if (instruction.Op0Kind != OpKind.Register) { @@ -532,13 +642,13 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => } else { - builder.Goto(instruction.IP, jumpTarget); + Add(instruction.IP, ISIL.OpCode.Jump, jumpTarget); break; } } if (instruction.Op0Kind == OpKind.Register) // ex: jmp rax { - builder.CallRegister(instruction.IP, ConvertOperand(instruction, 0), noReturn: true); + Add(instruction.IP, ISIL.OpCode.IndirectCall, ConvertOperand(instruction, 0)); break; } @@ -548,7 +658,7 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => { var jumpTarget = instruction.NearBranchTarget; - builder.JumpIfEqual(instruction.IP, jumpTarget); + Add(instruction.IP, ISIL.OpCode.ConditionalJump, jumpTarget, new ISIL.Register(null, "ZF")); // if ZF == 1 break; } @@ -558,7 +668,8 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => { var jumpTarget = instruction.NearBranchTarget; - builder.JumpIfNotEqual(instruction.IP, jumpTarget); + Add(instruction.IP, ISIL.OpCode.Not, new ISIL.Register(null, "TEMP"), new ISIL.Register(null, "ZF")); // TEMP = !ZF + Add(instruction.IP, ISIL.OpCode.ConditionalJump, jumpTarget, new ISIL.Register(null, "TEMP")); break; } goto default; @@ -567,28 +678,34 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => { var jumpTarget = instruction.NearBranchTarget; - builder.JumpIfSign(instruction.IP, jumpTarget); + Add(instruction.IP, ISIL.OpCode.ConditionalJump, jumpTarget, new ISIL.Register(null, "SF")); // if SF == 1 break; } - + goto default; case Mnemonic.Jns: if (instruction.Op0Kind != OpKind.Register) { var jumpTarget = instruction.NearBranchTarget; - builder.JumpIfNotSign(instruction.IP, jumpTarget); + Add(instruction.IP, ISIL.OpCode.Not, new ISIL.Register(null, "TEMP"), new ISIL.Register(null, "SF")); // TEMP = !SF + Add(instruction.IP, ISIL.OpCode.ConditionalJump, jumpTarget, new ISIL.Register(null, "TEMP")); break; } - + goto default; case Mnemonic.Jg: case Mnemonic.Ja: if (instruction.Op0Kind != OpKind.Register) { var jumpTarget = instruction.NearBranchTarget; + var temp = new ISIL.Register(null, "TEMP"); + var temp2 = new ISIL.Register(null, "TEMP2"); - builder.JumpIfGreater(instruction.IP, jumpTarget); + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, new ISIL.Register(null, "SF"), new ISIL.Register(null, "OF")); // TEMP = SF == OF + Add(instruction.IP, ISIL.OpCode.Not, temp2, new ISIL.Register(null, "ZF")); // TEMP2 = !ZF + Add(instruction.IP, ISIL.OpCode.And, temp, temp, temp2); // TEMP = TEMP && TEMP2 + Add(instruction.IP, ISIL.OpCode.ConditionalJump, jumpTarget, temp); break; } @@ -598,8 +715,11 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => if (instruction.Op0Kind != OpKind.Register) { var jumpTarget = instruction.NearBranchTarget; + var temp = new ISIL.Register(null, "TEMP"); - builder.JumpIfLess(instruction.IP, jumpTarget); + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, new ISIL.Register(null, "SF"), new ISIL.Register(null, "OF")); // TEMP = SF == OF + Add(instruction.IP, ISIL.OpCode.Not, temp, temp); // TEMP = !TEMP + Add(instruction.IP, ISIL.OpCode.ConditionalJump, jumpTarget, temp); break; } @@ -609,8 +729,10 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => if (instruction.Op0Kind != OpKind.Register) { var jumpTarget = instruction.NearBranchTarget; + var temp = new ISIL.Register(null, "TEMP"); - builder.JumpIfGreaterOrEqual(instruction.IP, jumpTarget); + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, new ISIL.Register(null, "SF"), new ISIL.Register(null, "OF")); // TEMP = SF == OF + Add(instruction.IP, ISIL.OpCode.ConditionalJump, jumpTarget, temp); break; } @@ -620,80 +742,112 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => if (instruction.Op0Kind != OpKind.Register) { var jumpTarget = instruction.NearBranchTarget; + var temp = new ISIL.Register(null, "TEMP"); - builder.JumpIfLessOrEqual(instruction.IP, jumpTarget); + Add(instruction.IP, ISIL.OpCode.CheckEqual, temp, new ISIL.Register(null, "SF"), new ISIL.Register(null, "OF")); // TEMP = SF == OF + Add(instruction.IP, ISIL.OpCode.Not, temp, temp); // TEMP = !TEMP + Add(instruction.IP, ISIL.OpCode.Or, temp, temp, new ISIL.Register(null, "ZF")); // TEMP = TEMP || ZF + Add(instruction.IP, ISIL.OpCode.ConditionalJump, jumpTarget, temp); break; } goto default; case Mnemonic.Xchg: - // There was supposed to be a push-mov-pop set but instructionAddress said no - builder.Exchange(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); + Add(instruction.IP, ISIL.OpCode.Move, new ISIL.Register(null, "TEMP"), ConvertOperand(instruction, 0)); // TEMP = op0 + Add(instruction.IP, ISIL.OpCode.Move, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); // op0 = op1 + Add(instruction.IP, ISIL.OpCode.Move, ConvertOperand(instruction, 1), new ISIL.Register(null, "TEMP")); // op1 = TEMP break; case Mnemonic.Int: case Mnemonic.Int3: - builder.Interrupt(instruction.IP); // We'll add it but eliminate later, can be used as a hint since compilers only emit it in normally unreachable code or in error handlers + Add(instruction.IP, ISIL.OpCode.Interrupt); // We'll add it but eliminate later, can be used as a hint since compilers only emit it in normally unreachable code or in error handlers break; case Mnemonic.Prefetchw: // Fetches the cache line containing the specified byte from memory to the 1st or 2nd level cache, invalidating other cached copies. case Mnemonic.Nop: // While this is literally a nop and there's in theory no point emitting anything for it, it could be used as a jump target. // So we'll emit an ISIL nop for it. - builder.Nop(instruction.IP); + Add(instruction.IP, ISIL.OpCode.Nop); break; default: - builder.NotImplemented(instruction.IP, FormatInstruction(instruction)); + Add(instruction.IP, ISIL.OpCode.NotImplemented, FormatInstruction(instruction)); break; } + + void AddCompareInstruction(ulong ip, object op0, object op1) + { + var temp1 = new ISIL.Register(null, "TEMP1"); + var temp2 = new ISIL.Register(null, "TEMP2"); + var temp3 = new ISIL.Register(null, "TEMP3"); + var temp4 = new ISIL.Register(null, "TEMP4"); + var temp5 = new ISIL.Register(null, "TEMP5"); + + Add(ip, ISIL.OpCode.CheckLess, new ISIL.Register(null, "CF"), op0, op1); // CF = op1 < op2 + Add(ip, ISIL.OpCode.Subtract, temp1, op0, op1); // temp1 = op1 - op2 + Add(ip, ISIL.OpCode.Xor, temp2, op0, op1); // temp2 = op1 ^ op2 + Add(ip, ISIL.OpCode.Xor, temp3, op0, temp1); // temp3 = op1 ^ temp1 + Add(ip, ISIL.OpCode.And, temp4, temp2, temp3); // temp4 = temp2 & temp3 + Add(ip, ISIL.OpCode.CheckLess, new ISIL.Register(null, "OF"), temp4, 0); // OF = temp4 < 0 + Add(ip, ISIL.OpCode.CheckLess, new ISIL.Register(null, "SF"), temp1, 0); // SF = temp1 < 0 + Add(ip, ISIL.OpCode.CheckEqual, new ISIL.Register(null, "ZF"), temp1, 0); // ZF = temp1 == 0 + Add(ip, ISIL.OpCode.And, temp5, temp2, 1); // temp5 = tmp2 & 1 + Add(ip, ISIL.OpCode.CheckEqual, new ISIL.Register(null, "PF"), temp5, 0); // PF = temp5 == 0 + } } - private InstructionSetIndependentOperand ConvertOperand(Instruction instruction, int operand) + private object ConvertOperand(Instruction instruction, int operand, bool isLeaAddress = false) { var kind = instruction.GetOpKind(operand); if (kind == OpKind.Register) - return InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.GetOpRegister(operand))); + return new ISIL.Register(null, X86Utils.GetRegisterName(instruction.GetOpRegister(operand))); if (kind.IsImmediate()) - return InstructionSetIndependentOperand.MakeImmediate(instruction.GetImmediate(operand)); + return instruction.GetImmediate(operand); if (kind == OpKind.Memory && instruction.MemoryBase == Register.RSP) - return InstructionSetIndependentOperand.MakeStack((int)instruction.MemoryDisplacement32); + return new ISIL.StackOffset((int)instruction.MemoryDisplacement32); //Memory //Most complex to least complex if (instruction.IsIPRelativeMemoryOperand) - return InstructionSetIndependentOperand.MakeMemory(new((long)instruction.IPRelativeMemoryAddress)); + return new ISIL.MemoryOperand(addend: (long)instruction.IPRelativeMemoryAddress); //All four components if (instruction.MemoryIndex != Register.None && instruction.MemoryBase != Register.None && instruction.MemoryDisplacement64 != 0) { - var mBase = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase)); - var mIndex = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryIndex)); - return InstructionSetIndependentOperand.MakeMemory(new(mBase, mIndex, instruction.MemoryDisplacement32, instruction.MemoryIndexScale)); + var mBase = new ISIL.Register(null, X86Utils.GetRegisterName(instruction.MemoryBase)); + var mIndex = new ISIL.Register(null, X86Utils.GetRegisterName(instruction.MemoryIndex)); + return new ISIL.MemoryOperand(mBase, mIndex, instruction.MemoryDisplacement32, instruction.MemoryIndexScale); } //No addend if (instruction.MemoryIndex != Register.None && instruction.MemoryBase != Register.None) { - var mBase = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase)); - var mIndex = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryIndex)); - return InstructionSetIndependentOperand.MakeMemory(new(mBase, mIndex, instruction.MemoryIndexScale)); + var mBase = new ISIL.Register(null, X86Utils.GetRegisterName(instruction.MemoryBase)); + var mIndex = new ISIL.Register(null, X86Utils.GetRegisterName(instruction.MemoryIndex)); + return new ISIL.MemoryOperand(mBase, mIndex, instruction.MemoryIndexScale); + } + + //No base + if (instruction.MemoryIndex != Register.None && instruction.MemoryDisplacement64 != 0) + { + var mIndex = new ISIL.Register(null, X86Utils.GetRegisterName(instruction.MemoryIndex)); + return new ISIL.MemoryOperand(null, mIndex, instruction.MemoryDisplacement32, instruction.MemoryIndexScale); } //No index (and so no scale) if (instruction.MemoryBase != Register.None && instruction.MemoryDisplacement64 > 0) { - var mBase = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase)); - return InstructionSetIndependentOperand.MakeMemory(new(mBase, (long)instruction.MemoryDisplacement64)); + var mBase = new ISIL.Register(null, X86Utils.GetRegisterName(instruction.MemoryBase)); + return new ISIL.MemoryOperand(mBase, addend: (long)instruction.MemoryDisplacement64); } //Only base if (instruction.MemoryBase != Register.None) { - return InstructionSetIndependentOperand.MakeMemory(new(InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase)))); + return new ISIL.MemoryOperand(new ISIL.Register(null, X86Utils.GetRegisterName(instruction.MemoryBase))); } //Only addend - return InstructionSetIndependentOperand.MakeMemory(new((long)instruction.MemoryDisplacement64)); + return new ISIL.MemoryOperand(addend: (long)instruction.MemoryDisplacement64); } } diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index 66c5a30ce..6c716a0bb 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -3,8 +3,8 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; +using Cpp2IL.Core.Analysis; using Cpp2IL.Core.Graphs; -using Cpp2IL.Core.Graphs.Processors; using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Logging; using Cpp2IL.Core.Utils; @@ -46,15 +46,36 @@ public class MethodAnalysisContext : HasGenericParameters, IMethodInfoProvider /// /// The first-stage-analyzed Instruction-Set-Independent Language Instructions. /// - public List? ConvertedIsil; + public List? ConvertedIsil; + + /// + /// All ISIL local variables. + /// + public List Locals = []; + + /// + /// Operands used as parameters. + /// + public List ParameterOperands = []; /// /// The control flow graph for this method, if one is built. /// public ISILControlFlowGraph? ControlFlowGraph; + /// + /// Dominance info for the control flow graph. + /// + public DominatorInfo? DominatorInfo; + + public List AnalysisWarnings = []; + + private const int MaxMethodSizeBytes = 18000; // 18KB + public List Parameters = []; + public List ParameterLocals = []; + /// /// Does this method return void? /// @@ -117,7 +138,7 @@ public override List GenericParameters //TODO Support custom attributes on return types (v31 feature) public TypeAnalysisContext ReturnType => OverrideReturnType ?? DefaultReturnType; - + protected Memory? rawMethodBody; public MethodAnalysisContext? BaseMethod => Overrides.FirstOrDefault(m => m.DeclaringType?.IsInterface is false); @@ -207,12 +228,6 @@ IEnumerable GetOverriddenMethods(Il2CppTypeDefinition dec } } - private static readonly List blockProcessors = - [ - new MetadataProcessor(), - new CallProcessor() - ]; - public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext) { DeclaringType = parent; @@ -265,6 +280,13 @@ protected MethodAnalysisContext(ApplicationAnalysisContext context) : base(0, co [MemberNotNull(nameof(ConvertedIsil))] public void Analyze() { + if (RawBytes.Length > MaxMethodSizeBytes) + { + Logger.WarnNewline($"Method {FullName} is too big ({RawBytes.Length} bytes), skipping analysis."); + ConvertedIsil = []; + return; + } + if (ConvertedIsil != null) return; @@ -275,27 +297,38 @@ public void Analyze() } ConvertedIsil = AppContext.InstructionSet.GetIsilFromMethod(this); + ParameterOperands = AppContext.InstructionSet.GetParameterOperandsFromMethod(this); if (ConvertedIsil.Count == 0) return; //Nothing to do, empty function - ControlFlowGraph = new ISILControlFlowGraph(); - ControlFlowGraph.Build(ConvertedIsil); + ControlFlowGraph = new ISILControlFlowGraph(ConvertedIsil); + DominatorInfo = new DominatorInfo(ControlFlowGraph); - // Post step to convert metadata usage. Ldstr Opcodes etc. - foreach (var block in ControlFlowGraph.Blocks) - { - foreach (var converter in blockProcessors) - { - converter.Process(this, block); - } - } + // Indirect jumps/calls should probably be resolved here before stack analysis + + StackAnalyzer.Analyze(this); + + // Create locals + SsaForm.Build(this); + LocalVariables.CreateAll(this); + SsaForm.Remove(this); + + MetadataResolver.ResolveAll(this); + Simplifier.Simplify(this); + + // Propagate types and clean up locals + LocalVariables.PropagateTypes(this); + LocalVariables.RemoveUnused(this); } + public void AddWarning(string warning) => AnalysisWarnings.Add(warning); + public void ReleaseAnalysisData() { ConvertedIsil = null; ControlFlowGraph = null; + DominatorInfo = null; } public ConcreteGenericMethodAnalysisContext MakeGenericInstanceMethod(params IEnumerable methodGenericParameters) diff --git a/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs index dbaaa2e1e..5f16e3e59 100644 --- a/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs @@ -16,7 +16,7 @@ namespace Cpp2IL.Core.Model.Contexts; /// /// Represents one managed type in the application. /// -public class TypeAnalysisContext : HasGenericParameters, ITypeInfoProvider, ICSharpSourceToken +public class TypeAnalysisContext : HasGenericParameters, ITypeInfoProvider { /// /// The context for the assembly this type was defined in. diff --git a/Cpp2IL.Core/OutputFormats/AsmResolverDummyDllOutputFormat.cs b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormat.cs similarity index 96% rename from Cpp2IL.Core/OutputFormats/AsmResolverDummyDllOutputFormat.cs rename to Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormat.cs index eff554126..bfb23bb8c 100644 --- a/Cpp2IL.Core/OutputFormats/AsmResolverDummyDllOutputFormat.cs +++ b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormat.cs @@ -21,6 +21,8 @@ namespace Cpp2IL.Core.OutputFormats; public abstract class AsmResolverDllOutputFormat : Cpp2IlOutputFormat { private AssemblyDefinition? MostRecentCorLib { get; set; } + protected int TotalMethodCount; + protected int SuccessfulMethodCount; public sealed override void DoOutput(ApplicationAnalysisContext context, string outputRoot) { @@ -52,6 +54,12 @@ public sealed override void DoOutput(ApplicationAnalysisContext context, string } Logger.VerboseNewline($"{(DateTime.Now - start).TotalMilliseconds:F1}ms", "DllOutput"); + + if (TotalMethodCount != 0) + { + var percent = Math.Round((SuccessfulMethodCount / (float)TotalMethodCount) * 100); + Logger.InfoNewline($"{percent}% of methods successfully decompiled ({SuccessfulMethodCount} / {TotalMethodCount})", "DllOutput"); + } } public virtual List BuildAssemblies(ApplicationAnalysisContext context) diff --git a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs index c69e28a17..b84a46ae2 100644 --- a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs +++ b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs @@ -1,7 +1,16 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; using AsmResolver.DotNet; using AsmResolver.PE.DotNet.Cil; using AssetRipper.CIL; +using Cpp2IL.Core.Extensions; +using Cpp2IL.Core.Graphs; +using Cpp2IL.Core.Logging; using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Utils; namespace Cpp2IL.Core.OutputFormats; @@ -11,14 +20,137 @@ public class AsmResolverDllOutputFormatIlRecovery : AsmResolverDllOutputFormat public override string OutputFormatName => "DLL files with IL Recovery"; + private MethodDefinition? _exceptionConstructor; + protected override void FillMethodBody(MethodDefinition methodDefinition, MethodAnalysisContext methodContext) { - if (methodDefinition.IsManagedMethodWithBody()) + if (_exceptionConstructor == null) + FindExceptionConstructor(methodDefinition); + + var module = methodDefinition.Module!; + var moduleName = module.Name!.ToString(); + var shouldSkip = moduleName.StartsWith("UnityEngine.") || moduleName.StartsWith("Unity.") || + moduleName.StartsWith("System.") || moduleName == "System" || + moduleName.StartsWith("mscorlib"); + var importer = new ReferenceImporter(module); + + if (!methodDefinition.IsManagedMethodWithBody()) + return; + + methodDefinition.CilMethodBody = new(methodDefinition); + var instructions = methodDefinition.CilMethodBody.Instructions; + + if (shouldSkip) + { + methodDefinition.ReplaceMethodBodyWithMinimalImplementation(); + return; + } + + try + { + TotalMethodCount++; + + methodContext.Analyze(); + + if (methodContext.ConvertedIsil.Count == 0) + methodDefinition.ReplaceMethodBodyWithMinimalImplementation(); + else + IlGenerator.GenerateIl(methodContext, methodDefinition); + + //WriteControlFlowGraph(methodContext, Path.Combine(Environment.CurrentDirectory, "Cpp2IL", "bin", "Debug", "net9.0", "cpp2il_out", "cfg")); + + SuccessfulMethodCount++; + } + catch (Exception e) { - methodDefinition.CilMethodBody = new(methodDefinition); - var instructions = methodDefinition.CilMethodBody.Instructions; - instructions.Add(CilOpCodes.Ldnull); + Logger.ErrorNewline($"Decompiling {methodContext.FullName} failed: {e}"); + + // throw new Exception(error); + instructions.Add(CilOpCodes.Ldstr, e.ToString()); + instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(_exceptionConstructor!)); instructions.Add(CilOpCodes.Throw); } + + methodContext.ReleaseAnalysisData(); + } + + private void FindExceptionConstructor(MethodDefinition method) + { + var module = method.Module!; + var mscorlibReference = module.AssemblyReferences.First(a => a.Name == "mscorlib"); + var mscorlib = mscorlibReference.Resolve()!.Modules[0]; + + var exception = mscorlib.TopLevelTypes.First(t => t.FullName == "System.Exception"); + _exceptionConstructor = exception.Methods.First(m => + m.Name == ".ctor" && m.Parameters is [{ ParameterType.FullName: "System.String" }]); + } + + public static void WriteControlFlowGraph(MethodAnalysisContext method, string outputPath) + { + var graph = method.ControlFlowGraph; + + var sb = new StringBuilder(); + var edges = new List<(int, int)>(); + + sb.AppendLine("digraph ControlFlowGraph {"); + sb.AppendLine(" \"label\"=\"Control flow graph\""); + + // no instructions + graph ??= new ISILControlFlowGraph([]); + + var methodText = $@"{CsFileUtils.GetKeyWordsForMethod(method)} {method.FullNameWithSignature} +parameter locals: {string.Join(", ", method.ParameterLocals)} +parameter operands: {string.Join(", ", method.ParameterOperands)}"; + + foreach (var block in graph.Blocks) + { + if (block == graph.EntryBlock || block == graph.ExitBlock) + { + var isEntry = block == graph.EntryBlock; + sb.AppendLine($""" + {block.ID} [ + "color"="{(isEntry ? "green" : "red")}" + "label"="{(isEntry ? $"Entry ({block.ID})\n{methodText}" : $"Exit ({block.ID})")}" + ] + """); + } + else + { + sb.AppendLine($""" + {block.ID} [ + "shape"="box" + "label"="{block.ToString().EscapeString().Replace("\\r", "")}" + ] + """); + } + + edges.AddRange(block.Successors.Select(b => (block.ID, b.ID))); + } + + foreach (var edge in edges) + sb.AppendLine($" {edge.Item1} -> {edge.Item2}"); + + sb.AppendLine("}"); + + var type = method.DeclaringType!; + var assemblyName = MiscUtils.CleanPathElement(type.DeclaringAssembly.CleanAssemblyName); + var typePath = Path.Combine(type.FullName.Split('.').Select(MiscUtils.CleanPathElement).ToArray()); + var directoryPath = Path.Combine(outputPath, assemblyName, typePath); + + var methodName = MiscUtils.CleanPathElement(method.Name + "_" + string.Join("_", + method.Parameters.Select(p => MiscUtils.CleanPathElement(p.ParameterType.Name)))); + var path = Path.Combine(directoryPath, methodName) + ".dot"; + + if (path.Length > 260) + { + path = path[..250]; + path += ".dot"; + } + + var directory = Path.GetDirectoryName(path)!; + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + File.WriteAllText(path, sb.ToString()); } } diff --git a/Cpp2IL.Core/ProcessingLayers/CallAnalysisProcessingLayer.cs b/Cpp2IL.Core/ProcessingLayers/CallAnalysisProcessingLayer.cs index d2c2cb69c..31b89f9b6 100644 --- a/Cpp2IL.Core/ProcessingLayers/CallAnalysisProcessingLayer.cs +++ b/Cpp2IL.Core/ProcessingLayers/CallAnalysisProcessingLayer.cs @@ -81,26 +81,25 @@ private static void InjectAttribute(ApplicationAnalysisContext appContext) continue; } - if (convertedIsil.Any(i => i.OpCode == InstructionSetIndependentOpCode.Invalid)) + if (convertedIsil.Any(i => i.OpCode == OpCode.Invalid)) { AttributeInjectionUtils.AddZeroParameterAttribute(m, invalidInstructionsConstructor); } - if (convertedIsil.Any(i => i.OpCode == InstructionSetIndependentOpCode.NotImplemented)) + if (convertedIsil.Any(i => i.OpCode == OpCode.NotImplemented)) { AttributeInjectionUtils.AddZeroParameterAttribute(m, unimplementedInstructionsConstructor); } foreach (var instruction in convertedIsil) { - if (instruction.OpCode != InstructionSetIndependentOpCode.Call && instruction.OpCode != InstructionSetIndependentOpCode.CallNoReturn) + if (instruction.OpCode != OpCode.Call && instruction.OpCode != OpCode.CallVoid) { continue; } - if (instruction.Operands.Length > 0 && instruction.Operands[0].Data is IsilImmediateOperand operand && operand.Value is not string) + if (instruction.Operands.Count > 0 && instruction.Operands[0] is ulong address) { - var address = operand.Value.ToUInt64(null); if (appContext.MethodsByAddress.TryGetValue(address, out var list)) { callCounts[address] = callCounts.GetOrDefault(address, 0) + 1; diff --git a/Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs b/Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs index 0ea55b140..5eb28ed1b 100644 --- a/Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs +++ b/Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reflection; using Cpp2IL.Core.Api; +using Cpp2IL.Core.Extensions; using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Model.Contexts; @@ -63,14 +64,14 @@ private static void AnalyzeMethod(ApplicationAnalysisContext appContext, MethodA foreach (var instruction in convertedIsil) { - if (instruction.OpCode == InstructionSetIndependentOpCode.Call) + if (instruction.OpCode == OpCode.Call) { if (TryGetAddressFromInstruction(instruction, out var address) && !appContext.MethodsByAddress.ContainsKey(address)) { nativeMethodInfoStack.Push((address, true)); } } - else if (instruction.OpCode == InstructionSetIndependentOpCode.CallNoReturn) + else if (instruction.OpCode == OpCode.CallVoid) { if (TryGetAddressFromInstruction(instruction, out var address) && !appContext.MethodsByAddress.ContainsKey(address)) { @@ -80,11 +81,13 @@ private static void AnalyzeMethod(ApplicationAnalysisContext appContext, MethodA } } - private static bool TryGetAddressFromInstruction(InstructionSetIndependentInstruction instruction, out ulong address) + private static bool TryGetAddressFromInstruction(Instruction instruction, out ulong address) { - if (instruction.Operands.Length > 0 && instruction.Operands[0].Data is IsilImmediateOperand operand && operand.Value is not string) + var operand = instruction.Operands[0]; + + if (instruction.Operands.Count > 0 && instruction.Operands[0].IsNumeric()) { - address = operand.Value.ToUInt64(null); + address = (ulong)operand; return true; } diff --git a/Cpp2IL.Core/Utils/MiscUtils.cs b/Cpp2IL.Core/Utils/MiscUtils.cs index c720ede44..4766607e8 100644 --- a/Cpp2IL.Core/Utils/MiscUtils.cs +++ b/Cpp2IL.Core/Utils/MiscUtils.cs @@ -268,7 +268,9 @@ bool F2(T t) "install.exe", "launch.exe", "MelonLoader.Installer.exe", - "crashpad_handler.exe" + "crashpad_handler.exe", + "EOSBootstrapper.exe", + "start_protected_game.exe" ]; public static string AnalyzeStackTracePointers(ulong[] pointers) diff --git a/Cpp2IL.Core/Utils/X64CallingConventionResolver.cs b/Cpp2IL.Core/Utils/X64CallingConventionResolver.cs index f0084df1a..2a14e325c 100644 --- a/Cpp2IL.Core/Utils/X64CallingConventionResolver.cs +++ b/Cpp2IL.Core/Utils/X64CallingConventionResolver.cs @@ -1,311 +1,311 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Model.Contexts; -using LibCpp2IL.BinaryStructures; -using LibCpp2IL.PE; - -namespace Cpp2IL.Core.Utils; - -#pragma warning disable IDE0305, IDE0300 - -public static class X64CallingConventionResolver -{ - // TODO: GCC(Linux) ABI - - // This class must be used in good faith. - // If that's not possible, uncomment the binary type checks. - // This *will* break everything on x32. - - const int ptrSize = 8; - - private static bool IsXMM(ParameterAnalysisContext par) - { - var parameterType = par.ParameterType; - return parameterType == parameterType.AppContext.SystemTypes.SystemSingleType - || parameterType == parameterType.AppContext.SystemTypes.SystemDoubleType; - } - - public static InstructionSetIndependentOperand[] ResolveForUnmanaged(ApplicationAnalysisContext app, ulong target) - { - // This is mostly a stub and may be extended in the future. You can traverse exports here for example. - // For now, we'll return all normal registers and omit the floating point registers. - - return app.Binary is PE ? new[] { - ToOperand(MicrosoftNormalRegister.rcx), - ToOperand(MicrosoftNormalRegister.rdx), - ToOperand(MicrosoftNormalRegister.r8), - ToOperand(MicrosoftNormalRegister.r9) - } : new[] { - ToOperand(LinuxNormalRegister.rdi), - ToOperand(LinuxNormalRegister.rsi), - ToOperand(LinuxNormalRegister.rdx), - ToOperand(LinuxNormalRegister.rcx), - ToOperand(LinuxNormalRegister.r8), - ToOperand(LinuxNormalRegister.r9) - }; - } - - public static InstructionSetIndependentOperand[] ResolveForManaged(MethodAnalysisContext ctx) - { - // if (ctx.AppContext.Binary.is32Bit) - // throw new NotSupportedException("Resolution of 64-bit calling conventions in 32-bit binaries is not supported."); - - List args = new(); - - var addThis = !ctx.IsStatic; - var isReturningAnOversizedStructure = false; // TODO: Determine whether we return a structure and whether that structure is oversized. - - /* - GCC: - Small structures - packed into N registers: - { - int x; - int y; - } - will be packed as a single normal register - - Big structures - packed into N registers: - { - int x; - int y; - int z; - int w; - } - will be packed as two normal registers - - Large structures - always in the stack: - { - int x; - int y; - int z; - int w; - int kk; - int mm; - } - will be packed into the stack - - Small XMM structures - packed into NX registers: - { - int x; - double y; - } - x will be packed into normal register, y will be packed into fp register, they do overlap (xmm0 and rdi are used) - - Fit XMM structures - packed into X registers: - { - double x; - double y; - } - will be packed as 2 xmm registers - - Big XMM structures - always in the stack: - { - double x; - double y; - int z; - } - will be packed into the stack, even though technically you could pack it into 1 normal and 2 xmm registers (same goes for if the int is a double) - - Small float structures - packed into N registers: - { - float x; - int y; - } - x and y will be packed into a single normal register - - Fit float structures - packed into XN registers: - { - float x; - float y; - int z; - } - x and y will be packed into a single fp register(doesn't match int behavior!!!), z will be packed into a normal register - - Complete float structures - packed into X registers: - { - float x; - float y; - float z; - float w; - } - x,y and z,w will be packed into 2 fp registers - - Everything else is always in the stack. - Multiple structures in args also follow this rule. - 16-byte structures will be put into the stack after the 8th structure. The others stay in registers according to spec. - 32-byte structures will be put into the stack after the 4th structure. The others stay in registers according to spec. - Structure sizes above are determined by their register size(16-byte fits into one R*X, 32-byte fits into two R*X, no matter the actual size). - The structures don't get cross-packed in the registers, which means they can't overlap, even if possible on a bit level. - Check .IsRef and pray it's true (don't need to handle struct fields individually, it's a pointer) - */ - - /* - MSVC doesn't need any special code to be implemented. - */ - - if (ctx.AppContext.Binary is PE) - { - /* - MSVC cconv: - RCX = XMM0 - RDX = XMM1 - R8 = XMM2 - R9 = XMM3 - [stack, every field is 8 incl. f & d, uses mov] - */ - - var i = 0; - - if (isReturningAnOversizedStructure) - { - args.Add(ToOperand(MicrosoftNormalRegister.rcx + i)); - i++; - } - - if (addThis) - { - args.Add(ToOperand(MicrosoftNormalRegister.rcx + i)); - i++; - } - - void AddParameter(ParameterAnalysisContext? par) - { - if (i < 4) - { - args.Add((par != null && IsXMM(par)) ? ToOperand(LinuxFloatingRegister.xmm0 + i) : ToOperand(MicrosoftNormalRegister.rcx + i)); - } - else - { - args.Add(InstructionSetIndependentOperand.MakeStack((i - 4) * ptrSize)); - } - } - - for (; i < ctx.Parameters.Count; i++) - { - AddParameter(ctx.Parameters[i]); - } - - AddParameter(null); // The MethodInfo argument - } - else // if (ctx.AppContext.Binary is ElfFile) - { - /* - GCC cconv (-O2): - Integers & Longs: - rdi - rsi - rdx - rcx - r8 - r9 - [stack, uses push] - Doubles: - xmm0 - xmm1 - xmm2 - xmm3 - xmm4 - xmm5 - xmm6 - xmm7 - [stack, uses push] - */ - - LinuxNormalRegister nreg = 0; - LinuxFloatingRegister freg = 0; - var stack = 0; - - void AddParameter(ParameterAnalysisContext? par) - { - if (par != null && IsXMM(par)) - { - if (freg == LinuxFloatingRegister.Stack) - { - args.Add(InstructionSetIndependentOperand.MakeStack(stack)); - stack += ptrSize; - } - else args.Add(ToOperand(freg++)); - } - else - { - if (nreg == LinuxNormalRegister.Stack) - { - args.Add(InstructionSetIndependentOperand.MakeStack(stack)); - stack += ptrSize; - } - else args.Add(ToOperand(nreg++)); - } - } - - if (isReturningAnOversizedStructure) - { - args.Add(ToOperand(nreg++)); - } - - if (addThis) - { - args.Add(ToOperand(nreg++)); - } - - foreach (var par in ctx.Parameters) - { - AddParameter(par); - } - - AddParameter(null); // The MethodInfo argument - } - // else throw new NotSupportedException($"Resolution of 64-bit calling conventions is not supported for this binary type."); - - return args.ToArray(); - } - - private static InstructionSetIndependentOperand ToOperand(MicrosoftNormalRegister Reg) => Reg switch - { - MicrosoftNormalRegister.rcx => InstructionSetIndependentOperand.MakeRegister("rcx"), - MicrosoftNormalRegister.rdx => InstructionSetIndependentOperand.MakeRegister("rdx"), - MicrosoftNormalRegister.r8 => InstructionSetIndependentOperand.MakeRegister("r8"), - MicrosoftNormalRegister.r9 => InstructionSetIndependentOperand.MakeRegister("r9"), - _ => throw new InvalidOperationException("Went past the register limit during resolution.") - }; - - private static InstructionSetIndependentOperand ToOperand(LinuxNormalRegister Reg) => Reg switch - { - LinuxNormalRegister.rdi => InstructionSetIndependentOperand.MakeRegister("rdi"), - LinuxNormalRegister.rsi => InstructionSetIndependentOperand.MakeRegister("rsi"), - LinuxNormalRegister.rdx => InstructionSetIndependentOperand.MakeRegister("rdx"), - LinuxNormalRegister.rcx => InstructionSetIndependentOperand.MakeRegister("rcx"), - LinuxNormalRegister.r8 => InstructionSetIndependentOperand.MakeRegister("r8"), - LinuxNormalRegister.r9 => InstructionSetIndependentOperand.MakeRegister("r9"), - _ => throw new InvalidOperationException("Went past the register limit during resolution.") - }; - - private static InstructionSetIndependentOperand ToOperand(LinuxFloatingRegister Reg) => Reg switch - { - LinuxFloatingRegister.xmm0 => InstructionSetIndependentOperand.MakeRegister("xmm0"), - LinuxFloatingRegister.xmm1 => InstructionSetIndependentOperand.MakeRegister("xmm1"), - LinuxFloatingRegister.xmm2 => InstructionSetIndependentOperand.MakeRegister("xmm2"), - LinuxFloatingRegister.xmm3 => InstructionSetIndependentOperand.MakeRegister("xmm3"), - LinuxFloatingRegister.xmm4 => InstructionSetIndependentOperand.MakeRegister("xmm4"), - LinuxFloatingRegister.xmm5 => InstructionSetIndependentOperand.MakeRegister("xmm5"), - LinuxFloatingRegister.xmm6 => InstructionSetIndependentOperand.MakeRegister("xmm6"), - LinuxFloatingRegister.xmm7 => InstructionSetIndependentOperand.MakeRegister("xmm7"), - _ => throw new InvalidOperationException("Went past the register limit during resolution.") - }; - - private enum MicrosoftNormalRegister - { - rcx, rdx, r8, r9 - } - - private enum LinuxNormalRegister - { - rdi, rsi, rdx, rcx, r8, r9, Stack - } - - private enum LinuxFloatingRegister - { - xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, Stack - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; +using LibCpp2IL.BinaryStructures; +using LibCpp2IL.PE; + +namespace Cpp2IL.Core.Utils; + +#pragma warning disable IDE0305, IDE0300 + +public static class X64CallingConventionResolver +{ + // TODO: GCC(Linux) ABI + + // This class must be used in good faith. + // If that's not possible, uncomment the binary type checks. + // This *will* break everything on x32. + + const int ptrSize = 8; + + private static bool IsXMM(ParameterAnalysisContext par) + { + var parameterType = par.ParameterType; + return parameterType == parameterType.AppContext.SystemTypes.SystemSingleType + || parameterType == parameterType.AppContext.SystemTypes.SystemDoubleType; + } + + public static object[] ResolveForUnmanaged(ApplicationAnalysisContext app, ulong target) + { + // This is mostly a stub and may be extended in the future. You can traverse exports here for example. + // For now, we'll return all normal registers and omit the floating point registers. + + return app.Binary is PE ? new[] { + ToOperand(MicrosoftNormalRegister.rcx), + ToOperand(MicrosoftNormalRegister.rdx), + ToOperand(MicrosoftNormalRegister.r8), + ToOperand(MicrosoftNormalRegister.r9) + } : new[] { + ToOperand(LinuxNormalRegister.rdi), + ToOperand(LinuxNormalRegister.rsi), + ToOperand(LinuxNormalRegister.rdx), + ToOperand(LinuxNormalRegister.rcx), + ToOperand(LinuxNormalRegister.r8), + ToOperand(LinuxNormalRegister.r9) + }; + } + + public static object[] ResolveForManaged(MethodAnalysisContext ctx) + { + // if (ctx.AppContext.Binary.is32Bit) + // throw new NotSupportedException("Resolution of 64-bit calling conventions in 32-bit binaries is not supported."); + + List args = new(); + + var addThis = !ctx.IsStatic; + var isReturningAnOversizedStructure = false; // TODO: Determine whether we return a structure and whether that structure is oversized. + + /* + GCC: + Small structures - packed into N registers: + { + int x; + int y; + } + will be packed as a single normal register + + Big structures - packed into N registers: + { + int x; + int y; + int z; + int w; + } + will be packed as two normal registers + + Large structures - always in the stack: + { + int x; + int y; + int z; + int w; + int kk; + int mm; + } + will be packed into the stack + + Small XMM structures - packed into NX registers: + { + int x; + double y; + } + x will be packed into normal register, y will be packed into fp register, they do overlap (xmm0 and rdi are used) + + Fit XMM structures - packed into X registers: + { + double x; + double y; + } + will be packed as 2 xmm registers + + Big XMM structures - always in the stack: + { + double x; + double y; + int z; + } + will be packed into the stack, even though technically you could pack it into 1 normal and 2 xmm registers (same goes for if the int is a double) + + Small float structures - packed into N registers: + { + float x; + int y; + } + x and y will be packed into a single normal register + + Fit float structures - packed into XN registers: + { + float x; + float y; + int z; + } + x and y will be packed into a single fp register(doesn't match int behavior!!!), z will be packed into a normal register + + Complete float structures - packed into X registers: + { + float x; + float y; + float z; + float w; + } + x,y and z,w will be packed into 2 fp registers + + Everything else is always in the stack. + Multiple structures in args also follow this rule. + 16-byte structures will be put into the stack after the 8th structure. The others stay in registers according to spec. + 32-byte structures will be put into the stack after the 4th structure. The others stay in registers according to spec. + Structure sizes above are determined by their register size(16-byte fits into one R*X, 32-byte fits into two R*X, no matter the actual size). + The structures don't get cross-packed in the registers, which means they can't overlap, even if possible on a bit level. + Check .IsRef and pray it's true (don't need to handle struct fields individually, it's a pointer) + */ + + /* + MSVC doesn't need any special code to be implemented. + */ + + if (ctx.AppContext.Binary is PE) + { + /* + MSVC cconv: + RCX = XMM0 + RDX = XMM1 + R8 = XMM2 + R9 = XMM3 + [stack, every field is 8 incl. f & d, uses mov] + */ + + var i = 0; + + if (isReturningAnOversizedStructure) + { + args.Add(ToOperand(MicrosoftNormalRegister.rcx + i)); + i++; + } + + if (addThis) + { + args.Add(ToOperand(MicrosoftNormalRegister.rcx + i)); + i++; + } + + void AddParameter(ParameterAnalysisContext? par) + { + if (i < 4) + { + args.Add((par != null && IsXMM(par)) ? ToOperand(LinuxFloatingRegister.xmm0 + i) : ToOperand(MicrosoftNormalRegister.rcx + i)); + } + else + { + args.Add(new StackOffset((i - 4) * ptrSize)); + } + } + + for (; i < ctx.Parameters.Count; i++) + { + AddParameter(ctx.Parameters[i]); + } + + AddParameter(null); // The MethodInfo argument + } + else // if (ctx.AppContext.Binary is ElfFile) + { + /* + GCC cconv (-O2): + Integers & Longs: + rdi + rsi + rdx + rcx + r8 + r9 + [stack, uses push] + Doubles: + xmm0 + xmm1 + xmm2 + xmm3 + xmm4 + xmm5 + xmm6 + xmm7 + [stack, uses push] + */ + + LinuxNormalRegister nreg = 0; + LinuxFloatingRegister freg = 0; + var stack = 0; + + void AddParameter(ParameterAnalysisContext? par) + { + if (par != null && IsXMM(par)) + { + if (freg == LinuxFloatingRegister.Stack) + { + args.Add(new StackOffset(stack)); + stack += ptrSize; + } + else args.Add(ToOperand(freg++)); + } + else + { + if (nreg == LinuxNormalRegister.Stack) + { + args.Add(new StackOffset(stack)); + stack += ptrSize; + } + else args.Add(ToOperand(nreg++)); + } + } + + if (isReturningAnOversizedStructure) + { + args.Add(ToOperand(nreg++)); + } + + if (addThis) + { + args.Add(ToOperand(nreg++)); + } + + foreach (var par in ctx.Parameters) + { + AddParameter(par); + } + + AddParameter(null); // The MethodInfo argument + } + // else throw new NotSupportedException($"Resolution of 64-bit calling conventions is not supported for this binary type."); + + return args.ToArray(); + } + + private static object ToOperand(MicrosoftNormalRegister Reg) => Reg switch + { + MicrosoftNormalRegister.rcx => new Register(null, "rcx"), + MicrosoftNormalRegister.rdx => new Register(null, "rdx"), + MicrosoftNormalRegister.r8 => new Register(null, "r8"), + MicrosoftNormalRegister.r9 => new Register(null, "r9"), + _ => throw new InvalidOperationException("Went past the register limit during resolution.") + }; + + private static object ToOperand(LinuxNormalRegister Reg) => Reg switch + { + LinuxNormalRegister.rdi => new Register(null, "rdi"), + LinuxNormalRegister.rsi => new Register(null, "rsi"), + LinuxNormalRegister.rdx => new Register(null, "rdx"), + LinuxNormalRegister.rcx => new Register(null, "rcx"), + LinuxNormalRegister.r8 => new Register(null, "r8"), + LinuxNormalRegister.r9 => new Register(null, "r9"), + _ => throw new InvalidOperationException("Went past the register limit during resolution.") + }; + + private static object ToOperand(LinuxFloatingRegister Reg) => Reg switch + { + LinuxFloatingRegister.xmm0 => new Register(null, "xmm0"), + LinuxFloatingRegister.xmm1 => new Register(null, "xmm1"), + LinuxFloatingRegister.xmm2 => new Register(null, "xmm2"), + LinuxFloatingRegister.xmm3 => new Register(null, "xmm3"), + LinuxFloatingRegister.xmm4 => new Register(null, "xmm4"), + LinuxFloatingRegister.xmm5 => new Register(null, "xmm5"), + LinuxFloatingRegister.xmm6 => new Register(null, "xmm6"), + LinuxFloatingRegister.xmm7 => new Register(null, "xmm7"), + _ => throw new InvalidOperationException("Went past the register limit during resolution.") + }; + + private enum MicrosoftNormalRegister + { + rcx, rdx, r8, r9 + } + + private enum LinuxNormalRegister + { + rdi, rsi, rdx, rcx, r8, r9, Stack + } + + private enum LinuxFloatingRegister + { + xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, Stack + } +} diff --git a/README.md b/README.md index 0ceb6e5ef..37d8d61b8 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,19 @@ Uses [LibCpp2IL](LibCpp2IL) for the initial parsing and loading of metadata stru build artifacts if you want to do something yourself with IL2CPP metadata, and is released under the MIT license. The link above will take you to the documentation for LibCpp2IL. +## Decompiler +The decompiled CIL is pretty messy right now, the next thing is probably pattern matching to convert il2cpp specific stuff into C# +(generic ISIL should be converted into more C# specific ISIL, new object, throw, etc. instructions should be added to ISIL), +but most of the times it's at least possible to see what the method does. +Use ILSpy because it works with broken CIL better than dnSpy. + +The entry point to decompilation is `MethodAnalysisContext.Analyze()`, it translates platform specific assembly into ISIL with `Cpp2IlInstructionSet`, +builds the control flow graph and dominator info, and the rest of the decompilation is done with `IAction` classes (`MethodAnalysisContext.analysisActions`), +these include stack analysis, simplification, applying metadata, etc. and then `Ilgenerator.GenerateIl()` translates the ISIL into CIL that's saved into managed dlls. + ### Development Branch Notes -Cpp2IL is currently undergoing a major rewrite. This branch represents work in progress, and is subject to change. +Cpp2IL has undergone a major rewrite. This branch represents work in progress, and is subject to change. CI builds for developers can be obtained from [My Nuget Feed](https://nuget.samboy.dev/). @@ -27,8 +37,7 @@ compared to the previously released versions. #### Obvious Changes: -Many options, such as `--analysis-level`, `--skip-analysis`, etc, have been removed. Ignoring the fact that analysis is not yet implemented, these options will not be coming back. -Analysis will be off by default, and will be enabled via the usage of a processing layer. +Many options, such as `--analysis-level`, `--skip-analysis`, etc, have been removed. Analysis is now off by default and can be enabled with `--output-as dll_il_recovery`. Equally, options like `--supress-attributes`, which previously suppressed the Cpp2ILInjected attributes, have been replaced with a process layer - this one is actually implemented, and is called `attributeinjector`. You can enable this layer using the `--use-processor` option, and you can list other options using `--list-processors`. @@ -43,8 +52,7 @@ Under the hood, the application has been almost completely rewritten. Primarily, limitations. When we looked into switching, we realised how reliant we were on the library. This is no longer the case - the application is written around LibCpp2IL types and the new Analysis Context objects, and the Mono.Cecil library is no longer used, having been replaced with AsmResolver.DotNet. -On top of that, we are currently in the process of reimplementing analysis based around an intermediate representation called ISIL (Instruction-Set-Independent Language), which -will allow for much easier support of new instruction sets. The ISIL is then converted into a Control Flow Graph, which can be analysed more intelligently than a raw disassembly. +Analysis is now based on an intermediate representation called ISIL (Instruction-Set-Independent Language), which is converted into a Control Flow Graph for more intelligent analysis. We're also working on a Plugin system which will allow third-party developers to write plugins to add support for custom instruction sets, binary formats, and eventually load obfuscated or encrypted metadata or binary files. @@ -58,7 +66,7 @@ run `Cpp2IL-Win.exe --game-path=C:\Path\To\Your\Game` and Cpp2IL will detect your unity version, locate the files it needs, and dump the output into a cpp2il_out folder wherever you ran the command from. -Assuming you have a single APK file (not an APKM or XAPK), and are running at least cpp2il 2021.4.0, you can use the +Assuming you have a single APK file (not an APKM), and are running at least cpp2il 2021.4.0, you can use the same argument as above but pass in the path to the APK, and cpp2il will extract the files it needs from the APK. ### Supported Command Line Option Listing @@ -72,7 +80,7 @@ same argument as above but pass in the path to the APK, and cpp2il will extract | --use-processor | attributeinjector | Select a processing layer to use, which can change the raw data prior to outputting. This option can appear multiple times. | | --processor-config | key=value | Provide configuration options to the selected processing layers. These will be documented by the plugin which adds the processing layer. | | --list-output-formats | <None> | List available output formats, then exit. | -| --output-as | dummydll | Specify the output format you wish to use. | +| --output-as | dll_il_recovery | Specify the output format you wish to use. | | --output-to | cpp2il_out | Root directory to output to. This path will be passed to the selected output format, which may then create subdirectories etc. within this location. | | --wasm-framework-file | C:\Path\To\webgl.framework.js | Only used in conjunction with WASM binaries. Some of these have obfuscated exports but they can be recovered via a framework.js file, which you can provide the path to using this argument. |