From c351644488e7751651fef950c943af4aa3d9f206 Mon Sep 17 00:00:00 2001 From: itsmilos Date: Tue, 24 Jun 2025 21:28:30 +0300 Subject: [PATCH 01/22] Stack analysis and dominance --- Cpp2IL.Core/Graphs/Block.cs | 1 + Cpp2IL.Core/Graphs/DominatorInfo.cs | 228 ++++++++++++++++++ Cpp2IL.Core/Graphs/StackAnalyzer.cs | 173 +++++++++++++ .../Model/Contexts/MethodAnalysisContext.cs | 8 + .../AsmResolverDllOutputFormatIlRecovery.cs | 135 ++++++++++- .../AsmResolverDummyDllOutputFormat.cs | 8 + 6 files changed, 550 insertions(+), 3 deletions(-) create mode 100644 Cpp2IL.Core/Graphs/DominatorInfo.cs create mode 100644 Cpp2IL.Core/Graphs/StackAnalyzer.cs diff --git a/Cpp2IL.Core/Graphs/Block.cs b/Cpp2IL.Core/Graphs/Block.cs index 0bfc7dfa4..b2ad4895b 100644 --- a/Cpp2IL.Core/Graphs/Block.cs +++ b/Cpp2IL.Core/Graphs/Block.cs @@ -18,6 +18,7 @@ public class Block public bool Dirty { get; set; } public bool Visited = false; + public bool IsTailCall => Successors.Count != 0 && Successors.Any(s => s.BlockType == BlockType.Exit); public override string ToString() { diff --git a/Cpp2IL.Core/Graphs/DominatorInfo.cs b/Cpp2IL.Core/Graphs/DominatorInfo.cs new file mode 100644 index 000000000..d57cc6195 --- /dev/null +++ b/Cpp2IL.Core/Graphs/DominatorInfo.cs @@ -0,0 +1,228 @@ +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 static DominatorInfo Build(ISILControlFlowGraph graph) + { + var dominatorInfo = new DominatorInfo(); + dominatorInfo.CalculateDominators(graph); + dominatorInfo.CalculatePostDominators(graph); + dominatorInfo.CalculateImmediateDominators(graph); + dominatorInfo.CalculateImmediatePostDominators(graph); + dominatorInfo.CalculateDominanceFrontiers(graph); + dominatorInfo.BuildDominanceTree(); + return dominatorInfo; + } + + 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 == 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/StackAnalyzer.cs b/Cpp2IL.Core/Graphs/StackAnalyzer.cs new file mode 100644 index 000000000..c8adb1cae --- /dev/null +++ b/Cpp2IL.Core/Graphs/StackAnalyzer.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Graphs; + +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 = []; + + private const int MaxBlockVisitCount = 5000; + + private StackAnalyzer() + { + } + + public static void Analyze(MethodAnalysisContext method) + { + var analyzer = new StackAnalyzer(); + + var graph = method.ControlFlowGraph; + + 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"); + throw new Exception($"Method {method.FullName} ends with non empty stack: {outText})"); + } + + analyzer.CorrectOffsets(graph); + ReplaceStackWithRegisters(method); + } + + private void CorrectOffsets(ISILControlFlowGraph graph) + { + foreach (var block in graph.Blocks) + { + foreach (var instruction in block.isilInstructions) + { + if (instruction is { OpCode.Mnemonic: IsilMnemonic.ShiftStack }) + { + // Nop the shift stack instruction + instruction.OpCode = InstructionSetIndependentOpCode.Nop; + instruction.Operands = []; + } + + // Correct offset for stack operands + for (var i = 0; i < instruction.Operands.Length; i++) + { + var op = instruction.Operands[i]; + + if (op.Data is IsilStackOperand offset) + { + // TODO: sometimes try catch causes something weird, probably indirect jump somewhere, so some instructions are in cfg but not in _instructionState + var state = _instructionState[instruction].Size; + var actual = state + offset.Offset; + instruction.Operands[i] = InstructionSetIndependentOperand.MakeStack(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 + for (var i = 0; i < block.isilInstructions.Count; i++) + { + var instruction = block.isilInstructions[i]; + + _instructionState[instruction] = currentState; + + if (instruction.OpCode.Mnemonic == IsilMnemonic.ShiftStack) + { + var offset = (int)(((IsilImmediateOperand)instruction.Operands[0].Data).Value); + currentState = currentState.Copy(); + currentState.Size += offset; + } + else if (i == block.isilInstructions.Count - 1 && block.IsTailCall) + { + // Tail calls clear stack + currentState = currentState.Copy(); + currentState.Size = 0; + } + } + + // Tail calls clear stack + if (block.IsTailCall) + currentState.Size = 0; + + _outGoingState[block] = currentState; + + visitedBlockCount++; + + if (MaxBlockVisitCount != -1 && visitedBlockCount > MaxBlockVisitCount) + throw new Exception($"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) + { + // Get all offsets without duplicates + var offsets = new List(); + foreach (var operand in method.ConvertedIsil!.SelectMany(instruction => instruction.Operands)) + { + if (operand.Data is IsilStackOperand offset) + { + if (!offsets.Contains(offset.Offset)) + offsets.Add(offset.Offset); + } + } + + // Map offsets to registers + var offsetToRegister = new Dictionary(); + foreach (var offset in offsets) + { + var name = offset < 0 ? $"stack_-{-offset:X}" : $"stack_{offset:X}"; + offsetToRegister.Add(offset, name); + } + + // Replace stack offset operands + foreach (var instruction in method.ConvertedIsil!) + { + for (var i = 0; i < instruction.Operands.Length; i++) + { + var operand = instruction.Operands[i]; + + if (operand.Data is IsilStackOperand offset) + instruction.Operands[i] = + InstructionSetIndependentOperand.MakeRegister(offsetToRegister[offset.Offset]); + } + } + } +} diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index f5ca35cd7..e8281a221 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -53,6 +53,11 @@ public class MethodAnalysisContext : HasGenericParameters, IMethodInfoProvider /// public ISILControlFlowGraph? ControlFlowGraph; + /// + /// Dominance info for the control flow graph. + /// + public DominatorInfo? DominatorInfo; + public List Parameters = []; /// @@ -280,6 +285,9 @@ public void Analyze() ControlFlowGraph = new ISILControlFlowGraph(); ControlFlowGraph.Build(ConvertedIsil); + StackAnalyzer.Analyze(this); + DominatorInfo = DominatorInfo.Build(ControlFlowGraph); + // Post step to convert metadata usage. Ldstr Opcodes etc. foreach (var block in ControlFlowGraph.Blocks) { diff --git a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs index c69e28a17..27d3a098f 100644 --- a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs +++ b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs @@ -1,7 +1,15 @@ +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.Logging; using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Utils; namespace Cpp2IL.Core.OutputFormats; @@ -11,14 +19,135 @@ 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.CilMethodBody = new(methodDefinition); - var instructions = methodDefinition.CilMethodBody.Instructions; instructions.Add(CilOpCodes.Ldnull); instructions.Add(CilOpCodes.Throw); + return; + } + + try + { + TotalMethodCount++; + + methodContext.Analyze(); + + // throw new Exception(isil); + // i have no idea why but when doing this for 1 class it's fine, but with entire game strings get all messed up + /*instructions.Add(CilOpCodes.Ldstr, string.Join("\n ", methodContext.ConvertedIsil)); + instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(_exceptionConstructor!)); + instructions.Add(CilOpCodes.Throw);*/ + + instructions.Add(CilOpCodes.Ldnull); + instructions.Add(CilOpCodes.Throw); + + //WriteControlFlowGraph(methodContext, Path.Combine(Environment.CurrentDirectory, "cpp2il_out")); + + SuccessfulMethodCount++; + } + catch (Exception e) + { + 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" }]); + } + + private 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\""); + + 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" : "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/OutputFormats/AsmResolverDummyDllOutputFormat.cs b/Cpp2IL.Core/OutputFormats/AsmResolverDummyDllOutputFormat.cs index eff554126..0298235a1 100644 --- a/Cpp2IL.Core/OutputFormats/AsmResolverDummyDllOutputFormat.cs +++ b/Cpp2IL.Core/OutputFormats/AsmResolverDummyDllOutputFormat.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 processed ({SuccessfulMethodCount} / {TotalMethodCount})", "DllOutput"); + } } public virtual List BuildAssemblies(ApplicationAnalysisContext context) From f97691c74567066ebfb0ef08d9513dd3bc4d78e5 Mon Sep 17 00:00:00 2001 From: itsmilos Date: Sun, 20 Jul 2025 16:14:30 +0300 Subject: [PATCH 02/22] Added .vscode to gitignore, added some blacklisted exe names, renamed AsmResolverDummyDllOutputFormat.cs to match class name --- .gitignore | 1 + ...rDummyDllOutputFormat.cs => AsmResolverDllOutputFormat.cs} | 0 Cpp2IL.Core/Utils/MiscUtils.cs | 4 +++- 3 files changed, 4 insertions(+), 1 deletion(-) rename Cpp2IL.Core/OutputFormats/{AsmResolverDummyDllOutputFormat.cs => AsmResolverDllOutputFormat.cs} (100%) 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/OutputFormats/AsmResolverDummyDllOutputFormat.cs b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormat.cs similarity index 100% rename from Cpp2IL.Core/OutputFormats/AsmResolverDummyDllOutputFormat.cs rename to Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormat.cs 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) From 7630140ef1beafb4f71493d8bba070c3f4637dd7 Mon Sep 17 00:00:00 2001 From: itsmilos Date: Tue, 22 Jul 2025 01:38:56 +0300 Subject: [PATCH 03/22] Make isil more verbose, less platform dependent, and make all side effects like flags visible --- Cpp2IL.Core.Tests/Graphing/BasicGraph.cs | 47 +- .../Graphing/ExceptionThrowingGraph.cs | 120 ++-- Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs | 2 +- Cpp2IL.Core/Extensions/MiscExtensions.cs | 19 +- Cpp2IL.Core/Graphs/Block.cs | 59 +- Cpp2IL.Core/Graphs/BlockType.cs | 1 + Cpp2IL.Core/Graphs/DominatorInfo.cs | 4 +- Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs | 196 +++--- .../Graphs/Processors/CallProcessor.cs | 123 +--- .../Graphs/Processors/MetadataProcessor.cs | 12 +- Cpp2IL.Core/Graphs/StackAnalyzer.cs | 42 +- Cpp2IL.Core/ISIL/Instruction.cs | 129 ++++ .../InstructionSetIndependentInstruction.cs | 34 - .../ISIL/InstructionSetIndependentOpCode.cs | 106 --- .../ISIL/InstructionSetIndependentOperand.cs | 52 -- Cpp2IL.Core/ISIL/IsilBuilder.cs | 151 ----- Cpp2IL.Core/ISIL/IsilCondition.cs | 24 - Cpp2IL.Core/ISIL/IsilFlowControl.cs | 25 - Cpp2IL.Core/ISIL/IsilImmediateOperand.cs | 32 - Cpp2IL.Core/ISIL/IsilInstructionStatement.cs | 8 - Cpp2IL.Core/ISIL/IsilMemoryOperand.cs | 128 ---- Cpp2IL.Core/ISIL/IsilMethodOperand.cs | 10 - Cpp2IL.Core/ISIL/IsilMnemonic.cs | 40 -- Cpp2IL.Core/ISIL/IsilOperandData.cs | 5 - Cpp2IL.Core/ISIL/IsilRegisterOperand.cs | 8 - Cpp2IL.Core/ISIL/IsilStackOperand.cs | 8 - Cpp2IL.Core/ISIL/IsilStatement.cs | 5 - .../ISIL/IsilTypeMetadataUsageOperand.cs | 10 - .../ISIL/IsilVectorRegisterElementOperand.cs | 28 - Cpp2IL.Core/ISIL/MemoryOperand.cs | 53 ++ Cpp2IL.Core/ISIL/OpCode.cs | 96 +++ Cpp2IL.Core/ISIL/Register.cs | 75 +++ Cpp2IL.Core/ISIL/StackOffset.cs | 8 + .../InstructionSets/Arm64InstructionSet.cs | 3 +- .../InstructionSets/ArmV7InstructionSet.cs | 2 +- .../InstructionSets/NewArmV8InstructionSet.cs | 360 +++++----- .../InstructionSets/WasmInstructionSet.cs | 2 +- .../InstructionSets/X86InstructionSet.cs | 605 ++++++++++------- .../Model/Contexts/MethodAnalysisContext.cs | 13 +- .../AsmResolverDllOutputFormatIlRecovery.cs | 11 +- .../CallAnalysisProcessingLayer.cs | 9 +- .../NativeMethodDetectionProcessingLayer.cs | 13 +- .../Utils/X64CallingConventionResolver.cs | 622 +++++++++--------- 43 files changed, 1549 insertions(+), 1751 deletions(-) create mode 100644 Cpp2IL.Core/ISIL/Instruction.cs delete mode 100644 Cpp2IL.Core/ISIL/InstructionSetIndependentInstruction.cs delete mode 100644 Cpp2IL.Core/ISIL/InstructionSetIndependentOpCode.cs delete mode 100644 Cpp2IL.Core/ISIL/InstructionSetIndependentOperand.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilBuilder.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilCondition.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilFlowControl.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilImmediateOperand.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilInstructionStatement.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilMemoryOperand.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilMethodOperand.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilMnemonic.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilOperandData.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilRegisterOperand.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilStackOperand.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilStatement.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilTypeMetadataUsageOperand.cs delete mode 100644 Cpp2IL.Core/ISIL/IsilVectorRegisterElementOperand.cs create mode 100644 Cpp2IL.Core/ISIL/MemoryOperand.cs create mode 100644 Cpp2IL.Core/ISIL/OpCode.cs create mode 100644 Cpp2IL.Core/ISIL/Register.cs create mode 100644 Cpp2IL.Core/ISIL/StackOffset.cs diff --git a/Cpp2IL.Core.Tests/Graphing/BasicGraph.cs b/Cpp2IL.Core.Tests/Graphing/BasicGraph.cs index 5a7865d67..557e24c1f 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,37 @@ 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.Build(instructions); } [Test] diff --git a/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs b/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs index caaddd9e9..fdc24d8f1 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,64 +11,74 @@ 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, 39, 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.LoadAddress, 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.LoadAddress, 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.Build(instructions); } [Test] diff --git a/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs b/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs index b400ca1bb..c5219ffe3 100644 --- a/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs +++ b/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs @@ -32,7 +32,7 @@ public abstract class Cpp2IlInstructionSet /// /// 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); + public abstract List GetIsilFromMethod(MethodAnalysisContext context); /// /// Create and populate a BaseKeyFunctionAddresses object which can then be populated. 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 b2ad4895b..d3c2cdb11 100644 --- a/Cpp2IL.Core/Graphs/Block.cs +++ b/Cpp2IL.Core/Graphs/Block.cs @@ -11,21 +11,19 @@ public class Block public List Predecessors = []; public List Successors = []; - public List isilInstructions = []; + public List Instructions = []; public int ID { get; set; } = -1; public bool Dirty { get; set; } public bool Visited = false; - public bool IsTailCall => Successors.Count != 0 && Successors.Any(s => s.BlockType == BlockType.Exit); - public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Type: " + BlockType); stringBuilder.AppendLine(); - foreach (var instruction in isilInstructions) + foreach (var instruction in Instructions) { stringBuilder.AppendLine(instruction.ToString()); } @@ -33,44 +31,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() { - // 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 or OpCode.ReturnVoid => 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..25a5c16e8 100644 --- a/Cpp2IL.Core/Graphs/BlockType.cs +++ b/Cpp2IL.Core/Graphs/BlockType.cs @@ -6,6 +6,7 @@ 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 diff --git a/Cpp2IL.Core/Graphs/DominatorInfo.cs b/Cpp2IL.Core/Graphs/DominatorInfo.cs index d57cc6195..0bf807f89 100644 --- a/Cpp2IL.Core/Graphs/DominatorInfo.cs +++ b/Cpp2IL.Core/Graphs/DominatorInfo.cs @@ -109,11 +109,11 @@ private void CalculatePostDominators(ISILControlFlowGraph graph) foreach (var block in graph.Blocks) { - if (block == graph.ExitBlock) + if (block.Successors.Count == 0 && block != graph.ExitBlock) continue; var tempPostDoms = block.Successors.Count == 0 - ? new HashSet() + ? new HashSet { block } : new HashSet(PostDominators[block.Successors[0]]); for (var i = 1; i < block.Successors.Count; i++) diff --git a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs index c4ac34a25..05110d434 100644 --- a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs +++ b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Text; using Cpp2IL.Core.ISIL; namespace Cpp2IL.Core.Graphs; @@ -14,7 +13,6 @@ public class ISILControlFlowGraph public int Count => blockSet.Count; public Collection Blocks => blockSet; - private int idCounter; private Collection blockSet; private Block exitBlock; @@ -33,12 +31,12 @@ public ISILControlFlowGraph() ]; } - 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,82 +47,104 @@ private bool TryGetTargetJumpInstructionIndex(InstructionSetIndependentInstructi return false; } + public void RemoveUnreachableBlocks() + { + // Get reachable blocks + var reachable = new HashSet(); + var worklist = new Queue(); + worklist.Enqueue(EntryBlock); + + while (worklist.Count > 0) + { + var block = worklist.Dequeue(); + + if (!reachable.Add(block)) + continue; + + foreach (var succ in block.Successors) + worklist.Enqueue(succ); + } - public void Build(List instructions) + // Remove unreachable blocks + var toRemove = blockSet.Where(b => !reachable.Contains(b)).ToList(); + foreach (var block in toRemove) + { + foreach (var pred in block.Predecessors) + pred.Successors.Remove(block); + + foreach (var succ in block.Successors) + succ.Predecessors.Remove(block); + + blockSet.Remove(block); + } + } + + public void Build(List instructions) { if (instructions == null) throw new ArgumentNullException(nameof(instructions)); - var currentBlock = new Block() { ID = idCounter++ }; - AddNode(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 = newBlock; } else { AddDirectedEdge(currentBlock, exitBlock); - currentBlock.Dirty = true; - } - 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: + case OpCode.ReturnVoid: + var isReturn = instructions[i].OpCode == OpCode.Return || instructions[i].OpCode == OpCode.ReturnVoid; + currentBlock.AddInstruction(instructions[i]); + if (!isLast) { - var newNodeFromReturn = new Block() { ID = idCounter++ }; - AddNode(newNodeFromReturn); - AddDirectedEdge(currentBlock, exitBlock); + newBlock = new Block() { ID = idCounter++ }; + AddBlock(newBlock); + AddDirectedEdge(currentBlock, isReturn ? exitBlock : newBlock); currentBlock.CaculateBlockType(); - currentBlock = newNodeFromReturn; + currentBlock = newBlock; } else { @@ -133,40 +153,18 @@ public void Build(List instructions) } 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); + currentBlock.CaculateBlockType(); } - - 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++) { var node = blockSet[index]; @@ -175,34 +173,26 @@ public void Build(List instructions) } } - public void CalculateDominations() - { - foreach (var block in blockSet) - { - throw new NotImplementedException(); - } - } - 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,10 +200,10 @@ 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) + protected Block? FindBlockByInstruction(Instruction? instruction) { if (instruction == null) return null; @@ -221,9 +211,9 @@ private void FixBlock(Block block, bool removeJmp = false) for (var i = 0; i < blockSet.Count; i++) { var block = blockSet[i]; - for (var j = 0; j < block.isilInstructions.Count; j++) + 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 +226,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 +278,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) => blockSet.Add(block); } diff --git a/Cpp2IL.Core/Graphs/Processors/CallProcessor.cs b/Cpp2IL.Core/Graphs/Processors/CallProcessor.cs index a3f3784f5..62fd3a1e4 100644 --- a/Cpp2IL.Core/Graphs/Processors/CallProcessor.cs +++ b/Cpp2IL.Core/Graphs/Processors/CallProcessor.cs @@ -1,4 +1,5 @@ using System.Linq; +using Cpp2IL.Core.Extensions; using Cpp2IL.Core.Il2CppApiFunctions; using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Model.Contexts; @@ -11,19 +12,19 @@ public void Process(MethodAnalysisContext methodAnalysisContext, Block block) { if (block.BlockType != BlockType.Call) return; - var callInstruction = block.isilInstructions[^1]; + var callInstruction = block.Instructions[^1]; if (callInstruction == null) return; - if (callInstruction.OpCode != InstructionSetIndependentOpCode.Call) + if (!callInstruction.IsCall) return; - if (callInstruction.Operands.Length <= 0) + if (callInstruction.Operands.Count <= 0) return; var dest = callInstruction.Operands[0]; - if (dest.Type != InstructionSetIndependentOperand.OperandType.Immediate) + if (!dest.IsNumeric()) return; - var target = (ulong)((IsilImmediateOperand)dest.Data).Value; + var target = (ulong)dest; var keyFunctionAddresses = methodAnalysisContext.AppContext.GetOrCreateKeyFunctionAddresses(); @@ -40,10 +41,10 @@ public void Process(MethodAnalysisContext methodAnalysisContext, Block block) if (targetMethods is not [{ } singleTargetMethod]) return; - callInstruction.Operands[0] = InstructionSetIndependentOperand.MakeMethodReference(singleTargetMethod); + callInstruction.Operands[0] = singleTargetMethod; } - private void HandleKeyFunction(ApplicationAnalysisContext appContext, InstructionSetIndependentInstruction instruction, ulong target, BaseKeyFunctionAddresses kFA) + private void HandleKeyFunction(ApplicationAnalysisContext appContext, Instruction instruction, ulong target, BaseKeyFunctionAddresses kFA) { // TODO: Handle labelling functions calls that match these in a more graceful manner var method = ""; @@ -58,114 +59,16 @@ private void HandleKeyFunction(ApplicationAnalysisContext appContext, Instructio method = nameof(kFA.il2cpp_codegen_initialize_runtime_metadata); } } - else if (target == kFA.il2cpp_vm_metadatacache_initializemethodmetadata) + else { - 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); + var pairs = kFA.Pairs.ToList(); + var index = pairs.FindIndex(pair => pair.Value == target); + method = pairs[index].Key; } if (method != "") { - instruction.Operands[0] = InstructionSetIndependentOperand.MakeImmediate(method); + instruction.Operands[0] = method; } } } diff --git a/Cpp2IL.Core/Graphs/Processors/MetadataProcessor.cs b/Cpp2IL.Core/Graphs/Processors/MetadataProcessor.cs index 19ad5bdbb..fa4cb3fbc 100644 --- a/Cpp2IL.Core/Graphs/Processors/MetadataProcessor.cs +++ b/Cpp2IL.Core/Graphs/Processors/MetadataProcessor.cs @@ -10,20 +10,20 @@ internal class MetadataProcessor : IBlockProcessor { public void Process(MethodAnalysisContext methodAnalysisContext, Block block) { - foreach (var instruction in block.isilInstructions) + foreach (var instruction in block.Instructions) { // TODO: Check if it shows up in any other - if (instruction.OpCode != InstructionSetIndependentOpCode.Move) + if (instruction.OpCode != OpCode.Move) { continue; } - if (instruction.Operands[0].Type != InstructionSetIndependentOperand.OperandType.Register || instruction.Operands[1].Type != InstructionSetIndependentOperand.OperandType.Memory) + if ((instruction.Operands[0] is not Register) || (instruction.Operands[1] is not MemoryOperand)) { continue; } - var memoryOp = (IsilMemoryOperand)instruction.Operands[1].Data; + var memoryOp = (MemoryOperand)instruction.Operands[1]; if (memoryOp.Base == null && memoryOp.Index == null && memoryOp.Scale == 0) { var val = LibCpp2IlMain.GetLiteralByAddress((ulong)memoryOp.Addend); @@ -35,13 +35,13 @@ public void Process(MethodAnalysisContext methodAnalysisContext, Block block) { var typeAnalysisContext = metadataUsage.ToContext(methodAnalysisContext.DeclaringType!.DeclaringAssembly); if (typeAnalysisContext != null) - instruction.Operands[1] = InstructionSetIndependentOperand.MakeTypeMetadataUsage(typeAnalysisContext); + instruction.Operands[1] = typeAnalysisContext; } continue; } - instruction.Operands[1] = InstructionSetIndependentOperand.MakeImmediate(val); + instruction.Operands[1] = val; } } } diff --git a/Cpp2IL.Core/Graphs/StackAnalyzer.cs b/Cpp2IL.Core/Graphs/StackAnalyzer.cs index c8adb1cae..ce1d42086 100644 --- a/Cpp2IL.Core/Graphs/StackAnalyzer.cs +++ b/Cpp2IL.Core/Graphs/StackAnalyzer.cs @@ -1,9 +1,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; +using System.Text; +using Cpp2IL.Core.Extensions; using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Logging; using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Utils; namespace Cpp2IL.Core.Graphs; @@ -18,7 +23,7 @@ private class StackState private Dictionary _inComingState = []; private Dictionary _outGoingState = []; - private Dictionary _instructionState = []; + private Dictionary _instructionState = []; private const int MaxBlockVisitCount = 5000; @@ -39,7 +44,8 @@ public static void Analyze(MethodAnalysisContext method) if (outDelta.Size != 0) { var outText = outDelta.Size < 0 ? "-" + (-outDelta.Size).ToString("X") : outDelta.Size.ToString("X"); - throw new Exception($"Method {method.FullName} ends with non empty stack: {outText})"); + method.AnalysisWarnings.Add($"warning: method ends with non empty stack: {outText}"); + Logger.Warn($"Method {method.FullName} ends with non empty stack: {outText}", "StackAnalyzer"); } analyzer.CorrectOffsets(graph); @@ -50,26 +56,26 @@ private void CorrectOffsets(ISILControlFlowGraph graph) { foreach (var block in graph.Blocks) { - foreach (var instruction in block.isilInstructions) + foreach (var instruction in block.Instructions) { - if (instruction is { OpCode.Mnemonic: IsilMnemonic.ShiftStack }) + if (instruction is { OpCode: OpCode.ShiftStack }) { // Nop the shift stack instruction - instruction.OpCode = InstructionSetIndependentOpCode.Nop; + instruction.OpCode = OpCode.Nop; instruction.Operands = []; } // Correct offset for stack operands - for (var i = 0; i < instruction.Operands.Length; i++) + for (var i = 0; i < instruction.Operands.Count; i++) { var op = instruction.Operands[i]; - if (op.Data is IsilStackOperand offset) + if (op is StackOffset offset) { // TODO: sometimes try catch causes something weird, probably indirect jump somewhere, so some instructions are in cfg but not in _instructionState var state = _instructionState[instruction].Size; var actual = state + offset.Offset; - instruction.Operands[i] = InstructionSetIndependentOperand.MakeStack(actual); + instruction.Operands[i] = new StackOffset(actual); } } } @@ -84,19 +90,19 @@ private void TraverseGraph(Block block, int visitedBlockCount = 0) var currentState = incomingState.Copy(); // Process instructions - for (var i = 0; i < block.isilInstructions.Count; i++) + for (var i = 0; i < block.Instructions.Count; i++) { - var instruction = block.isilInstructions[i]; + var instruction = block.Instructions[i]; _instructionState[instruction] = currentState; - if (instruction.OpCode.Mnemonic == IsilMnemonic.ShiftStack) + if (instruction.OpCode == OpCode.ShiftStack) { - var offset = (int)(((IsilImmediateOperand)instruction.Operands[0].Data).Value); + var offset = (int)instruction.Operands[0]; currentState = currentState.Copy(); currentState.Size += offset; } - else if (i == block.isilInstructions.Count - 1 && block.IsTailCall) + else if (i == block.Instructions.Count - 1 && block.BlockType == BlockType.TailCall) { // Tail calls clear stack currentState = currentState.Copy(); @@ -105,7 +111,7 @@ private void TraverseGraph(Block block, int visitedBlockCount = 0) } // Tail calls clear stack - if (block.IsTailCall) + if (block.BlockType == BlockType.TailCall) currentState.Size = 0; _outGoingState[block] = currentState; @@ -142,7 +148,7 @@ private static void ReplaceStackWithRegisters(MethodAnalysisContext method) var offsets = new List(); foreach (var operand in method.ConvertedIsil!.SelectMany(instruction => instruction.Operands)) { - if (operand.Data is IsilStackOperand offset) + if (operand is StackOffset offset) { if (!offsets.Contains(offset.Offset)) offsets.Add(offset.Offset); @@ -160,13 +166,13 @@ private static void ReplaceStackWithRegisters(MethodAnalysisContext method) // Replace stack offset operands foreach (var instruction in method.ConvertedIsil!) { - for (var i = 0; i < instruction.Operands.Length; i++) + for (var i = 0; i < instruction.Operands.Count; i++) { var operand = instruction.Operands[i]; - if (operand.Data is IsilStackOperand offset) + if (operand is StackOffset offset) instruction.Operands[i] = - InstructionSetIndependentOperand.MakeRegister(offsetToRegister[offset.Offset]); + new Register(null, offsetToRegister[offset.Offset]); } } } diff --git a/Cpp2IL.Core/ISIL/Instruction.cs b/Cpp2IL.Core/ISIL/Instruction.cs new file mode 100644 index 000000000..38611f3dd --- /dev/null +++ b/Cpp2IL.Core/ISIL/Instruction.cs @@ -0,0 +1,129 @@ +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 => false, + _ => true + }; + + public bool IsCall => OpCode is OpCode.Call or OpCode.CallVoid; + + public bool IsReturn => OpCode is OpCode.Return or OpCode.ReturnVoid; + + 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.LoadAddress: + 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.LoadAddress 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 => Operands.Skip(1).ToList(), + OpCode.Return => [Operands[0]], + OpCode.CheckEqual or OpCode.CheckGreater or OpCode.CheckLess + => [Operands[1], Operands[2]], + _ => [] + }; + + 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 == OpCode.CallVoid && Operands[0] is ulong callTarget) + return $"{Index} {OpCode} {callTarget:X4} {string.Join(", ", Operands.Skip(1).Select(FormatOperand))}"; + if (OpCode == OpCode.Call && Operands[1] is ulong callTarget2) + return $"{Index} {OpCode} {FormatOperand(Operands[0])} {callTarget2:X4} {string.Join(", ", Operands.Skip(2).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 => 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/MemoryOperand.cs b/Cpp2IL.Core/ISIL/MemoryOperand.cs new file mode 100644 index 000000000..16c28cf6a --- /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(Register? baseRegister = null, Register? indexRegister = null, long addend = 0, int scale = 0) +{ + public Register? Base = baseRegister; + public Register? 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..876222c39 --- /dev/null +++ b/Cpp2IL.Core/ISIL/OpCode.cs @@ -0,0 +1,96 @@ +namespace Cpp2IL.Core.ISIL; + +/// +/// If changing this, also update +/// +public enum OpCode // There is some weird stuff in doc comments because i can't use <, >, & (idk why & doesn't work) + // doc comments are in this format: Move dest, src : dest = src + // [isil] [decompiled code/what the instruction does] +{ + /// Invalid (optional) text + Invalid, + + /// NotImplemented (optional) text + NotImplemented, + + /// Interrupt + Interrupt, + + /// No operation + Nop, + + /// Move dest, src : dest = src + Move, + + /// LoadAddress dest, src : dest = address(src) + LoadAddress, + + /// Call target, dest, arg1, arg2, etc. : dest = target(arg1, arg2, etc.) + Call, + + /// CallNoReturn target, arg1, arg2, etc. : target(arg1, arg2, etc.) + CallVoid, + + /// IndirectCallVoid target, arg1, arg2, etc. : target(arg1, arg2, etc.) + IndirectCallVoid, + + /// Return value : return value + Return, + + /// Return : return + ReturnVoid, + + /// Jump target : goto target + Jump, + + /// IndirectJump target : goto target + IndirectJump, + + /// ConditionalJump target, cond : if (cond) goto target + ConditionalJump, + + /// ShiftStack value : sp += value + ShiftStack, + + /// Add dest, l, r : dest = l + r + Add, + + /// Subtract dest, l, r : dest = l - r + Subtract, + + /// Multiply dest, l, r : dest = l * r + Multiply, + + /// Divide dest, l, r : dest = l / r + Divide, + + /// ShiftLeft dest, src, count : dest = src shl count + ShiftLeft, + + /// ShiftRight dest, src, count : dest = src shr count + ShiftRight, + + /// And dest, l, r : dest = l and r + And, + + /// Or dest, l, r : dest = l | r + Or, + + /// Xor dest, l, r : dest = l ^ r + Xor, + + /// Not dest, src : dest = !src + Not, + + /// Negate dest, src : dest = -src + Negate, + + /// CheckEqual dest, l, r : dest = l == r + CheckEqual, + + /// CheckGreater dest, l, r : dest = l greater r + CheckGreater, + + /// CheckLess dest, l, r : dest = l less r + CheckLess +} diff --git a/Cpp2IL.Core/ISIL/Register.cs b/Cpp2IL.Core/ISIL/Register.cs new file mode 100644 index 000000000..bdb3e572b --- /dev/null +++ b/Cpp2IL.Core/ISIL/Register.cs @@ -0,0 +1,75 @@ +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 + { + Name = "reg" + number; + 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/InstructionSets/Arm64InstructionSet.cs b/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs index 74dafcaea..9c7ee1897 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,7 @@ 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 []; } diff --git a/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs b/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs index 8f621dc7f..bd82e4169 100644 --- a/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs @@ -23,7 +23,7 @@ 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 []; } diff --git a/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs b/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs index e22f6d727..a12f53ea9 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,58 @@ public override Memory GetRawBytesForMethod(MethodAnalysisContext context, return context.AppContext.Binary.GetRawBinaryContent().AsMemory(start, end - start); } - public override List GetIsilFromMethod(MethodAnalysisContext context) + public override List GetIsilFromMethod(MethodAnalysisContext context) { var insns = NewArm64Utils.GetArm64MethodBodyAtVirtualAddress(context.UnderlyingPointer); - var builder = new IsilBuilder(); - 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; + + var targetAddress = (ulong)instruction.Operands[0]; + var targetIndex = addresses.FindIndex(addr => addr == targetAddress); + + if (targetIndex == -1) + { + instruction.OpCode = OpCode.Invalid; + instruction.Operands = [$"Jump target not found in method: 0x{targetAddress:X4}"]; + continue; + } + + var targetInstruction = instructions[targetIndex]; - builder.FixJumps(); + instruction.Operands[0] = targetInstruction; + } - return builder.BackingStatementList; + 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 +105,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; // 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 +130,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(memory.Base!.Value, 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 +224,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(memInternal!.Value.Base!.Value, 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.ReturnVoid); + else + Add(address, OpCode.Return, returnRegister); break; case Arm64Mnemonic.B: var target = instruction.BranchTarget; @@ -216,36 +246,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.ReturnVoid); + 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.IndirectCallVoid, 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 +301,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 +415,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 +429,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 +443,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 +454,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 +482,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 +508,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 +532,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 +550,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..552e394fb 100644 --- a/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs @@ -33,7 +33,7 @@ public override Memory GetRawBytesForMethod(MethodAnalysisContext context, return Array.Empty(); } - public override List GetIsilFromMethod(MethodAnalysisContext context) + public override List GetIsilFromMethod(MethodAnalysisContext context) { return []; } diff --git a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs index 0484b2ce7..aca240cb5 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,52 @@ 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); + + // fix branches + for (var i = 0; i < instructions.Count; i++) { - ConvertInstructionStatement(instruction, builder, context); - } + 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; + } - builder.FixJumps(); + var targetInstruction = instructions[targetIndex]; - return builder.BackingStatementList; + instruction.Operands[0] = targetInstruction; + } + + return instructions; } - 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; + void Add(ulong address, ISIL.OpCode opCode, params object[] operands) + { + addresses.Add(address); + instructions.Add(new ISIL.Instruction(instructions.Count, opCode, operands)); + } + switch (instruction.Mnemonic) { case Mnemonic.Mov: @@ -84,97 +108,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.LoadAddress, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); 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 +210,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 +229,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 +259,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.ReturnVoid); 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,102 +283,123 @@ 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? @@ -360,13 +408,13 @@ static InstructionSetIndependentOperand ConvertVector(string reg, int imm) => if (instruction.Op0Kind == OpKind.Register) { - builder.CallRegister(instruction.IP, ConvertOperand(instruction, 0)); + Add(instruction.IP, ISIL.OpCode.IndirectCallVoid, 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])); + Add(instruction.IP, ISIL.OpCode.Call, target, X64CallingConventionResolver.ResolveForManaged(possibleMethods[0])); } else { @@ -388,7 +436,7 @@ 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)); + Add(instruction.IP, ISIL.OpCode.Call, target, X64CallingConventionResolver.ResolveForManaged(ctx)); } } else @@ -397,7 +445,7 @@ 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)); + Add(instruction.IP, ISIL.OpCode.Call, target, X64CallingConventionResolver.ResolveForUnmanaged(context.AppContext, target)); } if (callNoReturn) @@ -418,7 +466,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 +475,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 +487,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 +608,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.IndirectCallVoid, ConvertOperand(instruction, 0)); break; } @@ -548,7 +624,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 +634,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 +644,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 +681,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 +695,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 +708,105 @@ 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) { 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 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 e8281a221..436450301 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -46,7 +46,7 @@ public class MethodAnalysisContext : HasGenericParameters, IMethodInfoProvider /// /// The first-stage-analyzed Instruction-Set-Independent Language Instructions. /// - public List? ConvertedIsil; + public List? ConvertedIsil; /// /// The control flow graph for this method, if one is built. @@ -58,6 +58,10 @@ public class MethodAnalysisContext : HasGenericParameters, IMethodInfoProvider /// public DominatorInfo? DominatorInfo; + public List AnalysisWarnings = []; + + private const int MaxMethodSizeBytes = 256000; // 256KB + public List Parameters = []; /// @@ -268,6 +272,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; diff --git a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs index 27d3a098f..63a35da60 100644 --- a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs +++ b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs @@ -54,14 +54,14 @@ protected override void FillMethodBody(MethodDefinition methodDefinition, Method // throw new Exception(isil); // i have no idea why but when doing this for 1 class it's fine, but with entire game strings get all messed up - /*instructions.Add(CilOpCodes.Ldstr, string.Join("\n ", methodContext.ConvertedIsil)); + /* instructions.Add(CilOpCodes.Ldstr, string.Join("\n ", methodContext.ConvertedIsil)); instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(_exceptionConstructor!)); - instructions.Add(CilOpCodes.Throw);*/ + instructions.Add(CilOpCodes.Throw); */ instructions.Add(CilOpCodes.Ldnull); instructions.Add(CilOpCodes.Throw); - //WriteControlFlowGraph(methodContext, Path.Combine(Environment.CurrentDirectory, "cpp2il_out")); + //WriteControlFlowGraph(methodContext, Path.Combine(Environment.CurrentDirectory, "Cpp2IL", "bin", "Debug", "net9.0", "cpp2il_out", "cfg")); SuccessfulMethodCount++; } @@ -91,7 +91,7 @@ private void FindExceptionConstructor(MethodDefinition method) private static void WriteControlFlowGraph(MethodAnalysisContext method, string outputPath) { - var graph = method.ControlFlowGraph!; + var graph = method.ControlFlowGraph; var sb = new StringBuilder(); var edges = new List<(int, int)>(); @@ -99,6 +99,9 @@ private static void WriteControlFlowGraph(MethodAnalysisContext method, string o sb.AppendLine("digraph ControlFlowGraph {"); sb.AppendLine(" \"label\"=\"Control flow graph\""); + if (graph == null) // no instructions + graph = new Graphs.ISILControlFlowGraph(); + foreach (var block in graph.Blocks) { if (block == graph.EntryBlock || block == graph.ExitBlock) 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/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 + } +} From a4252a90fdea4042085111c49543a5d33bf5fa98 Mon Sep 17 00:00:00 2001 From: itsmilos Date: Tue, 22 Jul 2025 03:36:09 +0300 Subject: [PATCH 04/22] Remove unreachable blocks, nops, and merge call blocks --- Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs | 87 +++++++++++++++++++ Cpp2IL.Core/ISIL/Instruction.cs | 4 +- .../Model/Contexts/MethodAnalysisContext.cs | 4 + .../AsmResolverDllOutputFormatIlRecovery.cs | 2 +- 4 files changed, 93 insertions(+), 4 deletions(-) diff --git a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs index 05110d434..5a955b11c 100644 --- a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs +++ b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs @@ -79,6 +79,93 @@ public void RemoveUnreachableBlocks() } } + public void RemoveNops() + { + // Build a map from old instructions to next non nop instruction + 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) + instructionReplacement[instr] = replacement; + } + else + { + replacement = instr; + } + } + } + + // Remove NOPs + foreach (var block in Blocks) + { + block.Instructions.RemoveAll(i => i.OpCode == OpCode.Nop); + } + + // Fix all branch targets + 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; + } + } + } + } + } + + 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 (int 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); + blockSet.Remove(removed); + } + } + public void Build(List instructions) { if (instructions == null) diff --git a/Cpp2IL.Core/ISIL/Instruction.cs b/Cpp2IL.Core/ISIL/Instruction.cs index 38611f3dd..ed6cc5f62 100644 --- a/Cpp2IL.Core/ISIL/Instruction.cs +++ b/Cpp2IL.Core/ISIL/Instruction.cs @@ -98,10 +98,8 @@ public override string ToString() if (OpCode == OpCode.ConditionalJump && Operands[0] is ulong jumpTarget2) return $"{Index} {OpCode} {jumpTarget2:X4} {FormatOperand(Operands[1])}"; - if (OpCode == OpCode.CallVoid && Operands[0] is ulong callTarget) + 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))}"; - if (OpCode == OpCode.Call && Operands[1] is ulong callTarget2) - return $"{Index} {OpCode} {FormatOperand(Operands[0])} {callTarget2:X4} {string.Join(", ", Operands.Skip(2).Select(FormatOperand))}"; return $"{Index} {OpCode} {string.Join(", ", Operands.Select(FormatOperand))}"; } diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index 436450301..72ddded01 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -295,8 +295,12 @@ public void Analyze() ControlFlowGraph = new ISILControlFlowGraph(); ControlFlowGraph.Build(ConvertedIsil); + // Indirect jumps should probably be resolved here + ControlFlowGraph.RemoveUnreachableBlocks(); StackAnalyzer.Analyze(this); + ControlFlowGraph.MergeCallBlocks(); + ControlFlowGraph.RemoveNops(); DominatorInfo = DominatorInfo.Build(ControlFlowGraph); // Post step to convert metadata usage. Ldstr Opcodes etc. diff --git a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs index 63a35da60..ca7f87763 100644 --- a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs +++ b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs @@ -89,7 +89,7 @@ private void FindExceptionConstructor(MethodDefinition method) m.Name == ".ctor" && m.Parameters is [{ ParameterType.FullName: "System.String" }]); } - private static void WriteControlFlowGraph(MethodAnalysisContext method, string outputPath) + public static void WriteControlFlowGraph(MethodAnalysisContext method, string outputPath) { var graph = method.ControlFlowGraph; From cea2a44486e0fe32243c593fcbd46ec7d936ca43 Mon Sep 17 00:00:00 2001 From: itsmilos Date: Tue, 22 Jul 2025 17:18:12 +0300 Subject: [PATCH 05/22] Remove redundant assignments --- Cpp2IL.Core/Graphs/Block.cs | 2 +- Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs | 13 ++- .../RemoveRedundantAssignmentsProcessor.cs | 93 +++++++++++++++++++ .../Model/Contexts/MethodAnalysisContext.cs | 3 +- 4 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 Cpp2IL.Core/Graphs/Processors/RemoveRedundantAssignmentsProcessor.cs diff --git a/Cpp2IL.Core/Graphs/Block.cs b/Cpp2IL.Core/Graphs/Block.cs index d3c2cdb11..fd5514d95 100644 --- a/Cpp2IL.Core/Graphs/Block.cs +++ b/Cpp2IL.Core/Graphs/Block.cs @@ -33,7 +33,7 @@ public override string ToString() public void AddInstruction(Instruction instruction) => Instructions.Add(instruction); - public void CaculateBlockType() + public void CalculateBlockType() { if (Instructions.Count <= 0) return; diff --git a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs index 5a955b11c..9cb9c876a 100644 --- a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs +++ b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs @@ -164,6 +164,9 @@ public void MergeCallBlocks() Blocks.Remove(removed); blockSet.Remove(removed); } + + foreach (var block in blockSet) + block.CalculateBlockType(); } public void Build(List instructions) @@ -204,7 +207,7 @@ public void Build(List instructions) currentBlock.Dirty = true; } - currentBlock.CaculateBlockType(); + currentBlock.CalculateBlockType(); currentBlock = newBlock; } else @@ -230,13 +233,13 @@ public void Build(List instructions) newBlock = new Block() { ID = idCounter++ }; AddBlock(newBlock); AddDirectedEdge(currentBlock, isReturn ? exitBlock : newBlock); - currentBlock.CaculateBlockType(); + currentBlock.CalculateBlockType(); currentBlock = newBlock; } else { AddDirectedEdge(currentBlock, exitBlock); - currentBlock.CaculateBlockType(); + currentBlock.CalculateBlockType(); } break; @@ -246,7 +249,7 @@ public void Build(List instructions) if (isLast) { AddDirectedEdge(currentBlock, exitBlock); - currentBlock.CaculateBlockType(); + currentBlock.CalculateBlockType(); } break; } @@ -290,7 +293,7 @@ private void FixBlock(Block block, bool removeJmp = false) block.Instructions.Remove(jump); } - protected Block? FindBlockByInstruction(Instruction? instruction) + public Block? FindBlockByInstruction(Instruction? instruction) { if (instruction == null) return null; diff --git a/Cpp2IL.Core/Graphs/Processors/RemoveRedundantAssignmentsProcessor.cs b/Cpp2IL.Core/Graphs/Processors/RemoveRedundantAssignmentsProcessor.cs new file mode 100644 index 000000000..d0d7162d6 --- /dev/null +++ b/Cpp2IL.Core/Graphs/Processors/RemoveRedundantAssignmentsProcessor.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using Cpp2IL.Core.Graphs.Processors; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Graphs; + +public class RemoveRedundantAssignmentsProcessor : IBlockProcessor +{ + public void Process(MethodAnalysisContext methodAnalysisContext, Block block) + { + // Repeat until no change + var changed = true; + while (changed) + { + changed = false; + + // For each instruction + for (var i = 0; i < block.Instructions.Count - 1; i++) + { + var current = block.Instructions[i]; + var next = block.Instructions[i + 1]; + + // Don't make return register same as args + if (next.OpCode == OpCode.Call) + continue; + + if (current.OpCode != OpCode.Move) continue; + if (current.Operands[0] is not Register moveDestRegister) continue; + var moveSrc = current.Operands[1]; + + // Check if it's used before being reassigned (+2 to skip next) + if (IsRegisterUsedBeforeReassignment(i + 2, moveDestRegister, block)) + continue; + + // Replace occurrences of the redundant register in the next instruction + for (var j = 0; j < next.Operands.Count; j++) + { + if (next.Operands[j] is Register register && register == moveDestRegister) + { + next.Operands[j] = moveSrc; + RemoveInstruction(current, methodAnalysisContext.ControlFlowGraph!); + changed = true; + break; + } + } + } + } + } + + private static bool IsRegisterUsedBeforeReassignment(int startIndex, Register register, Block block) + { + // Traverse everything reachable from current block + var reachable = new HashSet(); + var worklist = new Queue(); + worklist.Enqueue(block); + + while (worklist.Count > 0) + { + var nextBlock = worklist.Dequeue(); + + if (!reachable.Add(nextBlock)) + continue; + + for (int i = (nextBlock == block) ? startIndex : 0; i < block.Instructions.Count; i++) + { + var instruction = block.Instructions[i]; + + // It's reassigned + if (instruction.Destination is Register destRegister && destRegister == register) + return false; + + // is it used? + foreach (var operand in instruction.Sources) + { + if (operand is Register usedReg && usedReg == register) + return true; + } + } + + foreach (var succ in nextBlock.Successors) + worklist.Enqueue(succ); + } + + return false; + } + + private static void RemoveInstruction(Instruction instruction, ISILControlFlowGraph graph) + { + var block = graph.FindBlockByInstruction(instruction); + block?.Instructions.Remove(instruction); + } +} diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index 72ddded01..0a7b3e17e 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -217,7 +217,8 @@ IEnumerable GetOverriddenMethods(Il2CppTypeDefinition dec private static readonly List blockProcessors = [ new MetadataProcessor(), - new CallProcessor() + new CallProcessor(), + new RemoveRedundantAssignmentsProcessor() ]; public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext) From 30cc2ff12880ee6b889911cc21730a56076c95dc Mon Sep 17 00:00:00 2001 From: itsmilos Date: Wed, 23 Jul 2025 03:09:04 +0300 Subject: [PATCH 06/22] SSA form --- Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs | 9 +- Cpp2IL.Core/Api/ICSharpSourceToken.cs | 9 - Cpp2IL.Core/Graphs/Block.cs | 5 +- Cpp2IL.Core/Graphs/BuildSsaForm.cs | 435 ++++++++++++++++++ Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs | 148 ++++-- Cpp2IL.Core/ISIL/Instruction.cs | 8 +- Cpp2IL.Core/ISIL/LocalVariable.cs | 54 +++ Cpp2IL.Core/ISIL/MemoryOperand.cs | 6 +- Cpp2IL.Core/ISIL/OpCode.cs | 7 +- .../InstructionSets/Arm64InstructionSet.cs | 5 + .../InstructionSets/ArmV7InstructionSet.cs | 5 + .../InstructionSets/NewArmV8InstructionSet.cs | 11 +- .../InstructionSets/WasmInstructionSet.cs | 5 + .../InstructionSets/X86InstructionSet.cs | 4 + .../Model/Contexts/MethodAnalysisContext.cs | 25 +- .../Model/Contexts/TypeAnalysisContext.cs | 2 +- 16 files changed, 675 insertions(+), 63 deletions(-) delete mode 100644 Cpp2IL.Core/Api/ICSharpSourceToken.cs create mode 100644 Cpp2IL.Core/Graphs/BuildSsaForm.cs create mode 100644 Cpp2IL.Core/ISIL/LocalVariable.cs diff --git a/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs b/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs index c5219ffe3..cd036485c 100644 --- a/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs +++ b/Cpp2IL.Core/Api/Cpp2IlInstructionSet.cs @@ -31,9 +31,16 @@ 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. + /// 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/Graphs/Block.cs b/Cpp2IL.Core/Graphs/Block.cs index fd5514d95..4f012c3d4 100644 --- a/Cpp2IL.Core/Graphs/Block.cs +++ b/Cpp2IL.Core/Graphs/Block.cs @@ -11,6 +11,9 @@ public class Block public List Predecessors = []; public List Successors = []; + public List Use = []; + public List Def = []; + public List Instructions = []; public int ID { get; set; } = -1; @@ -21,7 +24,7 @@ public class Block public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine("Type: " + BlockType); + stringBuilder.AppendLine($"{BlockType} {ID}"); stringBuilder.AppendLine(); foreach (var instruction in Instructions) { diff --git a/Cpp2IL.Core/Graphs/BuildSsaForm.cs b/Cpp2IL.Core/Graphs/BuildSsaForm.cs new file mode 100644 index 000000000..ab17db26f --- /dev/null +++ b/Cpp2IL.Core/Graphs/BuildSsaForm.cs @@ -0,0 +1,435 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Graphs; + +public class BuildSsaForm // Can't be static because of parallel stuff +{ + private Dictionary> _versions = new(); + private Dictionary _versionCount = new(); + private Dictionary> _blockOutVersions = new(); + + public void Build(MethodAnalysisContext method) + { + _versions.Clear(); + _versionCount.Clear(); + _blockOutVersions.Clear(); + + var graph = method.ControlFlowGraph!; + var dominance = method.DominatorInfo!; + + ProcessBlock(graph.EntryBlock, dominance.DominanceTree); + InsertAllPhiFunctions(graph, dominance, method.ParameterOperands); + CreateLocals(method); + } + + 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, 1); + _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 void CreateLocals(MethodAnalysisContext method) + { + var instructions = method.ControlFlowGraph!.Blocks.SelectMany(b => b.Instructions).ToList(); + + // Get all registers + var registers = new List(); + foreach (var instruction in 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, null)); + } + + // Replace registers with locals + foreach (var instruction in 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 + for (var i = 0; i < instructions.Count; i++) + { + var instruction = instructions[i]; + if (instruction.OpCode != OpCode.Return) continue; + + var returnLocal = (LocalVariable)instruction.Sources[0]; + + returnLocal.Name = $"returnVal{i}"; + } + + // Add parameter names + var paramLocals = new List(); + + foreach (var local in method.Locals) + { + // Get param index of the local + var paramIndex = method.ParameterOperands.FindIndex(p => p is Register r && r.Number == local.Register.Number && local.Register.Version == -1); + if (paramIndex == -1) continue; + + // this param + if (paramIndex == 0 && !method.Definition!.IsStatic) + { + local.Name = "this"; + paramLocals.Add(local); + local.IsThis = true; + } + else + { + // Set the name + var index = paramIndex + (method.Definition!.IsStatic ? 0 : 1); // +1 to skip 'this' param + + if ((index > method.Definition.Parameters!.Length - 1) || index == -1) + continue; + + local.Name = method.Definition.Parameters[index].ParameterName; + paramLocals.Add(local); + } + } + + method.ParameterOperands = paramLocals.Cast().ToList(); + } + + 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 void RemoveSsaForm(MethodAnalysisContext method) + { + foreach (var block in method.ControlFlowGraph!.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 = []; + } + } + } + + method.ControlFlowGraph.RemoveNops(); + method.ControlFlowGraph.RemoveEmptyBlocks(); + } +} diff --git a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs index 9cb9c876a..1878fa14d 100644 --- a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs +++ b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs @@ -1,33 +1,30 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; 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; private int idCounter; - private Collection blockSet; - private Block exitBlock; - private Block entryBlock; public ISILControlFlowGraph() { - entryBlock = new Block() { ID = idCounter++ }; - entryBlock.BlockType = BlockType.Entry; - exitBlock = new Block() { ID = idCounter++ }; - exitBlock.BlockType = BlockType.Exit; - blockSet = + EntryBlock = new Block() { ID = idCounter++ }; + EntryBlock.BlockType = BlockType.Entry; + ExitBlock = new Block() { ID = idCounter++ }; + ExitBlock.BlockType = BlockType.Exit; + Blocks = [ - entryBlock, - exitBlock + EntryBlock, + ExitBlock ]; } @@ -66,7 +63,7 @@ public void RemoveUnreachableBlocks() } // Remove unreachable blocks - var toRemove = blockSet.Where(b => !reachable.Contains(b)).ToList(); + var toRemove = Blocks.Where(b => !reachable.Contains(b)).ToList(); foreach (var block in toRemove) { foreach (var pred in block.Predecessors) @@ -75,13 +72,28 @@ public void RemoveUnreachableBlocks() foreach (var succ in block.Successors) succ.Predecessors.Remove(block); - blockSet.Remove(block); + Blocks.Remove(block); } } public void RemoveNops() { - // Build a map from old instructions to next non nop instruction + 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) { @@ -91,7 +103,7 @@ public void RemoveNops() var instr = block.Instructions[i]; if (instr.OpCode == OpCode.Nop) { - if (replacement != null) + if (replacement != null && !usedAsTarget.Contains(instr)) instructionReplacement[instr] = replacement; } else @@ -101,26 +113,86 @@ public void RemoveNops() } } + // 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); + block.Instructions.RemoveAll(i => i.OpCode == OpCode.Nop && !usedAsTarget.Contains(i)); } + } + + public void RemoveEmptyBlocks() + { + var toRemove = new List(); - // Fix all branch targets foreach (var block in Blocks) { - foreach (var instr in block.Instructions) + if (block == EntryBlock || block == ExitBlock) + continue; + + if (block.Instructions.Count == 0) { - for (var i = 0; i < instr.Operands.Count; i++) + // Redirect predecessors to successors + foreach (var pred in block.Predecessors) { - if (instr.Operands[i] is Instruction target && instructionReplacement.TryGetValue(target, out var newTarget)) + pred.Successors.Remove(block); + foreach (var succ in block.Successors) { - instr.Operands[i] = newTarget; + 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() @@ -162,10 +234,10 @@ public void MergeCallBlocks() foreach (var removed in toRemove) { Blocks.Remove(removed); - blockSet.Remove(removed); + Blocks.Remove(removed); } - foreach (var block in blockSet) + foreach (var block in Blocks) block.CalculateBlockType(); } @@ -176,7 +248,7 @@ public void Build(List instructions) var currentBlock = new Block() { ID = idCounter++ }; AddBlock(currentBlock); - AddDirectedEdge(entryBlock, currentBlock); + AddDirectedEdge(EntryBlock, currentBlock); for (var i = 0; i < instructions.Count; i++) { @@ -199,7 +271,7 @@ public void Build(List instructions) if (TryGetTargetJumpInstructionIndex(instructions[i], out int jumpTargetIndex)) currentBlock.Dirty = true; else - AddDirectedEdge(currentBlock, exitBlock); + AddDirectedEdge(currentBlock, ExitBlock); } else { @@ -212,7 +284,7 @@ public void Build(List instructions) } else { - AddDirectedEdge(currentBlock, exitBlock); + AddDirectedEdge(currentBlock, ExitBlock); if (instructions[i].OpCode == OpCode.Jump) currentBlock.Dirty = true; @@ -232,13 +304,13 @@ public void Build(List instructions) { newBlock = new Block() { ID = idCounter++ }; AddBlock(newBlock); - AddDirectedEdge(currentBlock, isReturn ? exitBlock : newBlock); + AddDirectedEdge(currentBlock, isReturn ? ExitBlock : newBlock); currentBlock.CalculateBlockType(); currentBlock = newBlock; } else { - AddDirectedEdge(currentBlock, exitBlock); + AddDirectedEdge(currentBlock, ExitBlock); currentBlock.CalculateBlockType(); } @@ -248,16 +320,16 @@ public void Build(List instructions) currentBlock.AddInstruction(instructions[i]); if (isLast) { - AddDirectedEdge(currentBlock, exitBlock); + AddDirectedEdge(currentBlock, ExitBlock); currentBlock.CalculateBlockType(); } break; } } - 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); } @@ -298,9 +370,9 @@ private void FixBlock(Block block, bool removeJmp = false) 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]; + var block = Blocks[i]; for (var j = 0; j < block.Instructions.Count; j++) { var instr = block.Instructions[j]; @@ -368,5 +440,5 @@ private void AddDirectedEdge(Block from, Block to) to.Predecessors.Add(from); } - protected void AddBlock(Block block) => blockSet.Add(block); + protected void AddBlock(Block block) => Blocks.Add(block); } diff --git a/Cpp2IL.Core/ISIL/Instruction.cs b/Cpp2IL.Core/ISIL/Instruction.cs index ed6cc5f62..b779a2d16 100644 --- a/Cpp2IL.Core/ISIL/Instruction.cs +++ b/Cpp2IL.Core/ISIL/Instruction.cs @@ -25,6 +25,8 @@ public class Instruction(int index, OpCode opcode, params object[] operands) public bool IsReturn => OpCode is OpCode.Return or OpCode.ReturnVoid; + public bool IsAssignment => Destination != null; + public List Sources => GetSources(); public List SourcesAndConstants => GetSources(false); @@ -40,6 +42,7 @@ public object? Destination switch (OpCode) { case OpCode.Move: + case OpCode.Phi: case OpCode.LoadAddress: case OpCode.Call: case OpCode.Add: @@ -78,10 +81,11 @@ or OpCode.And or OpCode.Or or OpCode.Xor => [Operands[2], Operands[1]], OpCode.Call => Operands.Skip(2).ToList(), - OpCode.CallVoid => Operands.Skip(1).ToList(), + OpCode.CallVoid or OpCode.Phi => Operands.Skip(1).ToList(), OpCode.Return => [Operands[0]], OpCode.CheckEqual or OpCode.CheckGreater or OpCode.CheckLess => [Operands[1], Operands[2]], + _ => [] }; @@ -120,7 +124,7 @@ private static string FormatOperand(object operand) public static bool IsConstantValue(object operand) => operand switch { - Register or StackOffset => false, + Register or StackOffset or LocalVariable => false, MemoryOperand memory => memory.IsConstant, _ => true }; diff --git a/Cpp2IL.Core/ISIL/LocalVariable.cs b/Cpp2IL.Core/ISIL/LocalVariable.cs new file mode 100644 index 000000000..f7623ae07 --- /dev/null +++ b/Cpp2IL.Core/ISIL/LocalVariable.cs @@ -0,0 +1,54 @@ +using System; +using System.Text; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.ISIL; + +public class LocalVariable(string name, Register register, TypeAnalysisContext? type) : IEquatable +{ + public string Name = name; + public Register Register = register; + + /// + /// null if typeprop has not been done yet. + /// + public TypeAnalysisContext? Type = type; + + public bool IsThis = false; + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append(Name); + if (Type != null) + sb.Append($" ({Type.Name})"); + return sb.ToString(); + } + + public static bool operator ==(LocalVariable left, LocalVariable right) + { + return left.Equals(right); + } + + public static bool operator !=(LocalVariable left, LocalVariable right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (obj is not LocalVariable local) + return false; + return Equals(local); + } + + public bool Equals(LocalVariable other) + { + return Name == other.Name; + } + + public override int GetHashCode() + { + return HashCode.Combine(Name); + } +} diff --git a/Cpp2IL.Core/ISIL/MemoryOperand.cs b/Cpp2IL.Core/ISIL/MemoryOperand.cs index 16c28cf6a..6f316f25c 100644 --- a/Cpp2IL.Core/ISIL/MemoryOperand.cs +++ b/Cpp2IL.Core/ISIL/MemoryOperand.cs @@ -6,10 +6,10 @@ namespace Cpp2IL.Core.ISIL; /// /// Memory operand in the format of [base+addend+index*scale] /// -public struct MemoryOperand(Register? baseRegister = null, Register? indexRegister = null, long addend = 0, int scale = 0) +public struct MemoryOperand(object? baseRegister = null, object? indexRegister = null, long addend = 0, int scale = 0) { - public Register? Base = baseRegister; - public Register? Index = indexRegister; + public object? Base = baseRegister; + public object? Index = indexRegister; public long Addend = addend; public int Scale = scale; diff --git a/Cpp2IL.Core/ISIL/OpCode.cs b/Cpp2IL.Core/ISIL/OpCode.cs index 876222c39..9b8b5644a 100644 --- a/Cpp2IL.Core/ISIL/OpCode.cs +++ b/Cpp2IL.Core/ISIL/OpCode.cs @@ -22,13 +22,16 @@ public enum OpCode // There is some weird stuff in doc comments because i can't /// Move dest, src : dest = src Move, + /// Phi dest, src1, src2, etc. : dest = phi(src1, src2, etc.) + Phi, + /// LoadAddress dest, src : dest = address(src) LoadAddress, /// Call target, dest, arg1, arg2, etc. : dest = target(arg1, arg2, etc.) Call, - /// CallNoReturn target, arg1, arg2, etc. : target(arg1, arg2, etc.) + /// CallVoid target, arg1, arg2, etc. : target(arg1, arg2, etc.) CallVoid, /// IndirectCallVoid target, arg1, arg2, etc. : target(arg1, arg2, etc.) @@ -37,7 +40,7 @@ public enum OpCode // There is some weird stuff in doc comments because i can't /// Return value : return value Return, - /// Return : return + /// ReturnVoid : return ReturnVoid, /// Jump target : goto target diff --git a/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs b/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs index 9c7ee1897..dd2b31b81 100644 --- a/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/Arm64InstructionSet.cs @@ -37,6 +37,11 @@ public override List GetIsilFromMethod(MethodAnalysisContext contex return []; } + public override List GetParameterOperandsFromMethod(MethodAnalysisContext context) + { + return []; + } + public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance() => new Arm64KeyFunctionAddresses(); public override string PrintAssembly(MethodAnalysisContext context) diff --git a/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs b/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs index bd82e4169..b30e29bec 100644 --- a/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/ArmV7InstructionSet.cs @@ -28,6 +28,11 @@ public override List GetIsilFromMethod(MethodAnalysisContext contex return []; } + public override List GetParameterOperandsFromMethod(MethodAnalysisContext context) + { + return []; + } + public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance() { //TODO Fix diff --git a/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs b/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs index a12f53ea9..8472c3fa5 100644 --- a/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs @@ -44,6 +44,11 @@ public override Memory GetRawBytesForMethod(MethodAnalysisContext context, return context.AppContext.Binary.GetRawBinaryContent().AsMemory(start, end - start); } + public override List GetParameterOperandsFromMethod(MethodAnalysisContext context) + { + return []; + } + public override List GetIsilFromMethod(MethodAnalysisContext context) { var insns = NewArm64Utils.GetArm64MethodBodyAtVirtualAddress(context.UnderlyingPointer); @@ -115,7 +120,7 @@ void Add(ulong address, OpCode opCode, params object[] operands) var operate = ConvertOperand(instruction, 1); if (operate is MemoryOperand operand) { - var register = operand.Base!.Value; + var register = (Register)operand.Base!; // X19= X19, #0x30 Add(address, OpCode.Add, register, register, operand.Addend); //X8 = [X19] @@ -181,7 +186,7 @@ void Add(ulong address, OpCode opCode, params object[] operands) 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(memory.Base!.Value, addend: memory.Addend + size); + memory = new MemoryOperand((Register)memory.Base!, addend: memory.Addend + size); dest3 = memory; Add(address, OpCode.Move, dest3, ConvertOperand(instruction, 1)); // [REG + offset + size] = REG2 } @@ -225,7 +230,7 @@ void Add(ulong address, OpCode opCode, params object[] operands) //TODO clean this mess up var memInternal = mem as MemoryOperand?; - var mem2 = new MemoryOperand(memInternal!.Value.Base!.Value, addend: memInternal.Value.Addend + destRegSize); + var mem2 = new MemoryOperand((Register)memInternal!.Value.Base!, addend: memInternal.Value.Addend + destRegSize); Add(address, OpCode.Move, dest1, mem); Add(address, OpCode.Move, dest2, mem2); diff --git a/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs b/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs index 552e394fb..b44b3fc07 100644 --- a/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs @@ -38,6 +38,11 @@ public override List GetIsilFromMethod(MethodAnalysisContext contex return []; } + public override List GetParameterOperandsFromMethod(MethodAnalysisContext context) + { + return []; + } + public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance() { return new WasmKeyFunctionAddresses(); diff --git a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs index aca240cb5..47fa1dec2 100644 --- a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs @@ -79,6 +79,10 @@ public override string PrintAssembly(MethodAnalysisContext context) return instructions; } + public override List GetParameterOperandsFromMethod(MethodAnalysisContext context) + { + return X64CallingConventionResolver.ResolveForManaged(context).ToList(); + } private void ConvertInstructionStatement(Instruction instruction, List instructions, List addresses, MethodAnalysisContext context) { diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index 0a7b3e17e..7c1fdf5c3 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -48,6 +48,16 @@ public class MethodAnalysisContext : HasGenericParameters, IMethodInfoProvider /// 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. /// @@ -124,7 +134,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); @@ -290,6 +300,7 @@ public void Analyze() } ConvertedIsil = AppContext.InstructionSet.GetIsilFromMethod(this); + ParameterOperands = AppContext.InstructionSet.GetParameterOperandsFromMethod(this); if (ConvertedIsil.Count == 0) return; //Nothing to do, empty function @@ -298,11 +309,13 @@ public void Analyze() ControlFlowGraph.Build(ConvertedIsil); // Indirect jumps should probably be resolved here ControlFlowGraph.RemoveUnreachableBlocks(); - + ControlFlowGraph.RemoveNops(); StackAnalyzer.Analyze(this); ControlFlowGraph.MergeCallBlocks(); - ControlFlowGraph.RemoveNops(); DominatorInfo = DominatorInfo.Build(ControlFlowGraph); + ControlFlowGraph.BuildUseDefLists(); + var ssa = new BuildSsaForm(); + ssa.Build(this); // Post step to convert metadata usage. Ldstr Opcodes etc. foreach (var block in ControlFlowGraph.Blocks) @@ -312,6 +325,12 @@ public void Analyze() converter.Process(this, block); } } + + ssa.RemoveSsaForm(this); + + var rra = new RemoveRedundantAssignmentsProcessor(); + foreach (var block in ControlFlowGraph.Blocks) + rra.Process(this, block); } public void ReleaseAnalysisData() diff --git a/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs index 00fcc3790..215a97c2c 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. From f69470c8f68f3648efa8e5ee0c93b2b28c15968b Mon Sep 17 00:00:00 2001 From: itsmilos Date: Thu, 24 Jul 2025 00:59:35 +0300 Subject: [PATCH 07/22] Move analysis stuff into IAction classes --- Cpp2IL.Core.Tests/Graphing/BasicGraph.cs | 3 +- .../Graphing/ExceptionThrowingGraph.cs | 3 +- .../ApplyMetadata.cs} | 12 +- .../{Graphs => Actions}/BuildSsaForm.cs | 203 +-------------- Cpp2IL.Core/Actions/CreateLocals.cs | 164 +++++++++++++ Cpp2IL.Core/Actions/IAction.cs | 8 + Cpp2IL.Core/Actions/Inlining.cs | 231 ++++++++++++++++++ Cpp2IL.Core/Actions/RemoveSsaForm.cs | 68 ++++++ Cpp2IL.Core/Actions/ResolveCalls.cs | 77 ++++++ .../{Graphs => Actions}/StackAnalyzer.cs | 92 ++++--- Cpp2IL.Core/DecompilerException.cs | 7 + Cpp2IL.Core/Graphs/BlockType.cs | 11 +- Cpp2IL.Core/Graphs/DominatorInfo.cs | 18 +- Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs | 99 +++++--- .../Graphs/Processors/CallProcessor.cs | 74 ------ .../Graphs/Processors/IBlockProcessor.cs | 8 - .../RemoveRedundantAssignmentsProcessor.cs | 93 ------- Cpp2IL.Core/ISIL/Instruction.cs | 4 +- Cpp2IL.Core/ISIL/LocalVariable.cs | 31 +-- Cpp2IL.Core/ISIL/Register.cs | 7 +- .../InstructionSets/X86InstructionSet.cs | 27 +- .../Model/Contexts/MethodAnalysisContext.cs | 50 ++-- .../AsmResolverDllOutputFormatIlRecovery.cs | 11 +- 23 files changed, 754 insertions(+), 547 deletions(-) rename Cpp2IL.Core/{Graphs/Processors/MetadataProcessor.cs => Actions/ApplyMetadata.cs} (76%) rename Cpp2IL.Core/{Graphs => Actions}/BuildSsaForm.cs (56%) create mode 100644 Cpp2IL.Core/Actions/CreateLocals.cs create mode 100644 Cpp2IL.Core/Actions/IAction.cs create mode 100644 Cpp2IL.Core/Actions/Inlining.cs create mode 100644 Cpp2IL.Core/Actions/RemoveSsaForm.cs create mode 100644 Cpp2IL.Core/Actions/ResolveCalls.cs rename Cpp2IL.Core/{Graphs => Actions}/StackAnalyzer.cs (63%) create mode 100644 Cpp2IL.Core/DecompilerException.cs delete mode 100644 Cpp2IL.Core/Graphs/Processors/CallProcessor.cs delete mode 100644 Cpp2IL.Core/Graphs/Processors/IBlockProcessor.cs delete mode 100644 Cpp2IL.Core/Graphs/Processors/RemoveRedundantAssignmentsProcessor.cs diff --git a/Cpp2IL.Core.Tests/Graphing/BasicGraph.cs b/Cpp2IL.Core.Tests/Graphing/BasicGraph.cs index 557e24c1f..ec0e1ad28 100644 --- a/Cpp2IL.Core.Tests/Graphing/BasicGraph.cs +++ b/Cpp2IL.Core.Tests/Graphing/BasicGraph.cs @@ -40,8 +40,7 @@ public void Setup() instruction.Operands[0] = instructions[(int)instruction.Operands[0]]; } - graph = new(); - graph.Build(instructions); + graph = new ISILControlFlowGraph(instructions); } [Test] diff --git a/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs b/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs index fdc24d8f1..ba43f6222 100644 --- a/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs +++ b/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs @@ -77,8 +77,7 @@ public void Setup() instruction.Operands[0] = instructions[(int)instruction.Operands[0]]; } - graph = new(); - graph.Build(instructions); + graph = new ISILControlFlowGraph(instructions); } [Test] diff --git a/Cpp2IL.Core/Graphs/Processors/MetadataProcessor.cs b/Cpp2IL.Core/Actions/ApplyMetadata.cs similarity index 76% rename from Cpp2IL.Core/Graphs/Processors/MetadataProcessor.cs rename to Cpp2IL.Core/Actions/ApplyMetadata.cs index fa4cb3fbc..aa15a40d1 100644 --- a/Cpp2IL.Core/Graphs/Processors/MetadataProcessor.cs +++ b/Cpp2IL.Core/Actions/ApplyMetadata.cs @@ -4,13 +4,13 @@ using Cpp2IL.Core.Utils; using LibCpp2IL; -namespace Cpp2IL.Core.Graphs.Processors; +namespace Cpp2IL.Core.Actions; -internal class MetadataProcessor : IBlockProcessor +public class ApplyMetadata : IAction { - public void Process(MethodAnalysisContext methodAnalysisContext, Block block) + public void Apply(MethodAnalysisContext method) { - foreach (var instruction in block.Instructions) + foreach (var instruction in method.ControlFlowGraph!.Blocks.SelectMany(b => b.Instructions)) { // TODO: Check if it shows up in any other if (instruction.OpCode != OpCode.Move) @@ -31,9 +31,9 @@ public void Process(MethodAnalysisContext methodAnalysisContext, Block block) { // Try instead check if its type metadata usage var metadataUsage = LibCpp2IlMain.GetTypeGlobalByAddress((ulong)memoryOp.Addend); - if (metadataUsage != null && methodAnalysisContext.DeclaringType is not null) + if (metadataUsage != null && method.DeclaringType is not null) { - var typeAnalysisContext = metadataUsage.ToContext(methodAnalysisContext.DeclaringType!.DeclaringAssembly); + var typeAnalysisContext = metadataUsage.ToContext(method.DeclaringType!.DeclaringAssembly); if (typeAnalysisContext != null) instruction.Operands[1] = typeAnalysisContext; } diff --git a/Cpp2IL.Core/Graphs/BuildSsaForm.cs b/Cpp2IL.Core/Actions/BuildSsaForm.cs similarity index 56% rename from Cpp2IL.Core/Graphs/BuildSsaForm.cs rename to Cpp2IL.Core/Actions/BuildSsaForm.cs index ab17db26f..f315b8cfa 100644 --- a/Cpp2IL.Core/Graphs/BuildSsaForm.cs +++ b/Cpp2IL.Core/Actions/BuildSsaForm.cs @@ -1,29 +1,30 @@ -using System; using System.Collections.Generic; using System.Linq; +using Cpp2IL.Core.Graphs; using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Model.Contexts; -namespace Cpp2IL.Core.Graphs; +namespace Cpp2IL.Core.Actions; -public class BuildSsaForm // Can't be static because of parallel stuff +public class BuildSsaForm : IAction { private Dictionary> _versions = new(); private Dictionary _versionCount = new(); private Dictionary> _blockOutVersions = new(); - public void Build(MethodAnalysisContext method) + public void Apply(MethodAnalysisContext method) { + method.ControlFlowGraph!.BuildUseDefLists(); + _versions.Clear(); _versionCount.Clear(); _blockOutVersions.Clear(); var graph = method.ControlFlowGraph!; - var dominance = method.DominatorInfo!; + var dominatorInfo = method.DominatorInfo!; - ProcessBlock(graph.EntryBlock, dominance.DominanceTree); - InsertAllPhiFunctions(graph, dominance, method.ParameterOperands); - CreateLocals(method); + ProcessBlock(graph.EntryBlock, dominatorInfo.DominanceTree); + InsertAllPhiFunctions(graph, dominatorInfo, method.ParameterOperands); } private void InsertAllPhiFunctions(ISILControlFlowGraph graph, DominatorInfo dominance, List parameters) @@ -173,7 +174,7 @@ private Register GetNewVersion(Register old) if (!_versionCount.ContainsKey(old.Number)) { // Params are version 0 - _versionCount.Add(old.Number, 1); + _versionCount.Add(old.Number, 0); _versions.Add(old.Number, new Stack()); _versions[old.Number].Push(old.Copy(0)); } @@ -248,188 +249,4 @@ private void ProcessBlock(Block block, Dictionary> dominanceT _versions.FirstOrDefault(kv => kv.Key == register.Number).Value.Pop(); } } - - public void CreateLocals(MethodAnalysisContext method) - { - var instructions = method.ControlFlowGraph!.Blocks.SelectMany(b => b.Instructions).ToList(); - - // Get all registers - var registers = new List(); - foreach (var instruction in 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, null)); - } - - // Replace registers with locals - foreach (var instruction in 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 - for (var i = 0; i < instructions.Count; i++) - { - var instruction = instructions[i]; - if (instruction.OpCode != OpCode.Return) continue; - - var returnLocal = (LocalVariable)instruction.Sources[0]; - - returnLocal.Name = $"returnVal{i}"; - } - - // Add parameter names - var paramLocals = new List(); - - foreach (var local in method.Locals) - { - // Get param index of the local - var paramIndex = method.ParameterOperands.FindIndex(p => p is Register r && r.Number == local.Register.Number && local.Register.Version == -1); - if (paramIndex == -1) continue; - - // this param - if (paramIndex == 0 && !method.Definition!.IsStatic) - { - local.Name = "this"; - paramLocals.Add(local); - local.IsThis = true; - } - else - { - // Set the name - var index = paramIndex + (method.Definition!.IsStatic ? 0 : 1); // +1 to skip 'this' param - - if ((index > method.Definition.Parameters!.Length - 1) || index == -1) - continue; - - local.Name = method.Definition.Parameters[index].ParameterName; - paramLocals.Add(local); - } - } - - method.ParameterOperands = paramLocals.Cast().ToList(); - } - - 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 void RemoveSsaForm(MethodAnalysisContext method) - { - foreach (var block in method.ControlFlowGraph!.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 = []; - } - } - } - - method.ControlFlowGraph.RemoveNops(); - method.ControlFlowGraph.RemoveEmptyBlocks(); - } } diff --git a/Cpp2IL.Core/Actions/CreateLocals.cs b/Cpp2IL.Core/Actions/CreateLocals.cs new file mode 100644 index 000000000..ee37b6780 --- /dev/null +++ b/Cpp2IL.Core/Actions/CreateLocals.cs @@ -0,0 +1,164 @@ +using System.Collections.Generic; +using System.Linq; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Actions; + +public class CreateLocals : IAction +{ + public void Apply(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) 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) + { + var thisOperand = (Register)method.ParameterOperands[0]; + var thisLocal = method.Locals.First(l => l.Register.Number == thisOperand.Number && l.Register.Version == -1); + + thisLocal.Name = "this"; + thisLocal.IsThis = true; + paramLocals.Add(thisLocal); + } + + // 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"; + paramLocals.Add(methodInfoLocal); + } + } + + method.ParameterLocals = paramLocals; + } + + 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; + } +} diff --git a/Cpp2IL.Core/Actions/IAction.cs b/Cpp2IL.Core/Actions/IAction.cs new file mode 100644 index 000000000..ccb3b27f0 --- /dev/null +++ b/Cpp2IL.Core/Actions/IAction.cs @@ -0,0 +1,8 @@ +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Actions; + +public interface IAction +{ + public void Apply(MethodAnalysisContext method); +} diff --git a/Cpp2IL.Core/Actions/Inlining.cs b/Cpp2IL.Core/Actions/Inlining.cs new file mode 100644 index 000000000..bd942fd75 --- /dev/null +++ b/Cpp2IL.Core/Actions/Inlining.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.Actions; + +public class Inlining : IAction +{ + public void Apply(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.ParameterLocals.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/Actions/RemoveSsaForm.cs b/Cpp2IL.Core/Actions/RemoveSsaForm.cs new file mode 100644 index 000000000..48f20f93b --- /dev/null +++ b/Cpp2IL.Core/Actions/RemoveSsaForm.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Linq; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Actions; + +public class RemoveSsaForm : IAction +{ + public void Apply(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/Actions/ResolveCalls.cs b/Cpp2IL.Core/Actions/ResolveCalls.cs new file mode 100644 index 000000000..793c37e78 --- /dev/null +++ b/Cpp2IL.Core/Actions/ResolveCalls.cs @@ -0,0 +1,77 @@ +using System.Linq; +using Cpp2IL.Core.Extensions; +using Cpp2IL.Core.Graphs; +using Cpp2IL.Core.Il2CppApiFunctions; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Actions; + +public class ResolveCalls : IAction +{ + public void Apply(MethodAnalysisContext method) + { + foreach (var block in method.ControlFlowGraph!.Blocks) + { + if (block.BlockType != BlockType.Call) + return; + var callInstruction = block.Instructions[^1]; + if (callInstruction == null) + return; + if (!callInstruction.IsCall) + return; + + if (callInstruction.Operands.Count <= 0) + return; + var dest = callInstruction.Operands[0]; + if (!dest.IsNumeric()) + return; + + var target = (ulong)dest; + + var keyFunctionAddresses = method.AppContext.GetOrCreateKeyFunctionAddresses(); + + if (keyFunctionAddresses.IsKeyFunctionAddress(target)) + { + HandleKeyFunction(method.AppContext, callInstruction, target, keyFunctionAddresses); + return; + } + + //Non-key function call. Try to find a single match + if (!method.AppContext.MethodsByAddress.TryGetValue(target, out var targetMethods)) + return; + + if (targetMethods is not [{ } singleTargetMethod]) + return; + + callInstruction.Operands[0] = singleTargetMethod; + } + } + + private 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 index = pairs.FindIndex(pair => pair.Value == target); + method = pairs[index].Key; + } + + if (method != "") + { + instruction.Operands[0] = method; + } + } +} diff --git a/Cpp2IL.Core/Graphs/StackAnalyzer.cs b/Cpp2IL.Core/Actions/StackAnalyzer.cs similarity index 63% rename from Cpp2IL.Core/Graphs/StackAnalyzer.cs rename to Cpp2IL.Core/Actions/StackAnalyzer.cs index ce1d42086..fa4200189 100644 --- a/Cpp2IL.Core/Graphs/StackAnalyzer.cs +++ b/Cpp2IL.Core/Actions/StackAnalyzer.cs @@ -1,18 +1,13 @@ -using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; -using System.Text; -using Cpp2IL.Core.Extensions; +using Cpp2IL.Core.Graphs; using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Logging; using Cpp2IL.Core.Model.Contexts; -using Cpp2IL.Core.Utils; -namespace Cpp2IL.Core.Graphs; +namespace Cpp2IL.Core.Actions; -public class StackAnalyzer +public class StackAnalyzer : IAction { [DebuggerDisplay("Size = {Size}")] private class StackState @@ -25,31 +20,34 @@ private class StackState private Dictionary _outGoingState = []; private Dictionary _instructionState = []; - private const int MaxBlockVisitCount = 5000; + /// + /// Max allowed count of blocks to visit (-1 for no limit). + /// + public int MaxBlockVisitCount = -1; - private StackAnalyzer() + public void Apply(MethodAnalysisContext method) { - } - - public static void Analyze(MethodAnalysisContext method) - { - var analyzer = new StackAnalyzer(); + var graph = method.ControlFlowGraph!; - var graph = method.ControlFlowGraph; + _inComingState = new Dictionary { { graph.EntryBlock, new StackState() } }; + _outGoingState.Clear(); + _instructionState.Clear(); - analyzer._inComingState = new Dictionary { { graph!.EntryBlock, new StackState() } }; - analyzer.TraverseGraph(graph.EntryBlock); + TraverseGraph(graph.EntryBlock); - var outDelta = analyzer._outGoingState[graph.ExitBlock]; + var outDelta = _outGoingState[graph.ExitBlock]; if (outDelta.Size != 0) { var outText = outDelta.Size < 0 ? "-" + (-outDelta.Size).ToString("X") : outDelta.Size.ToString("X"); - method.AnalysisWarnings.Add($"warning: method ends with non empty stack: {outText}"); - Logger.Warn($"Method {method.FullName} ends with non empty stack: {outText}", "StackAnalyzer"); + method.AddWarning($"Method ends with non empty stack ({outText}), the output could be wrong!"); } - analyzer.CorrectOffsets(graph); + CorrectOffsets(graph); ReplaceStackWithRegisters(method); + + graph.MergeCallBlocks(); + graph.RemoveNops(); + graph.RemoveEmptyBlocks(); } private void CorrectOffsets(ISILControlFlowGraph graph) @@ -65,14 +63,13 @@ private void CorrectOffsets(ISILControlFlowGraph graph) instruction.Operands = []; } - // Correct offset for stack 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) { - // TODO: sometimes try catch causes something weird, probably indirect jump somewhere, so some instructions are in cfg but not in _instructionState var state = _instructionState[instruction].Size; var actual = state + offset.Offset; instruction.Operands[i] = new StackOffset(actual); @@ -90,10 +87,8 @@ private void TraverseGraph(Block block, int visitedBlockCount = 0) var currentState = incomingState.Copy(); // Process instructions - for (var i = 0; i < block.Instructions.Count; i++) + foreach (var instruction in block.Instructions) { - var instruction = block.Instructions[i]; - _instructionState[instruction] = currentState; if (instruction.OpCode == OpCode.ShiftStack) @@ -102,7 +97,7 @@ private void TraverseGraph(Block block, int visitedBlockCount = 0) currentState = currentState.Copy(); currentState.Size += offset; } - else if (i == block.Instructions.Count - 1 && block.BlockType == BlockType.TailCall) + else if (block.Instructions[block.Instructions.Count - 1] == instruction && block.BlockType == BlockType.TailCall) { // Tail calls clear stack currentState = currentState.Copy(); @@ -119,7 +114,7 @@ private void TraverseGraph(Block block, int visitedBlockCount = 0) visitedBlockCount++; if (MaxBlockVisitCount != -1 && visitedBlockCount > MaxBlockVisitCount) - throw new Exception($"Stack state not settling ({MaxBlockVisitCount} blocks already visited)"); + throw new DecompilerException($"Stack state not settling! ({MaxBlockVisitCount} blocks already visited)"); // Visit successors foreach (var successor in block.Successors) @@ -144,35 +139,32 @@ private void TraverseGraph(Block block, int visitedBlockCount = 0) private static void ReplaceStackWithRegisters(MethodAnalysisContext method) { - // Get all offsets without duplicates - var offsets = new List(); - foreach (var operand in method.ConvertedIsil!.SelectMany(instruction => instruction.Operands)) - { - if (operand is StackOffset offset) - { - if (!offsets.Contains(offset.Offset)) - offsets.Add(offset.Offset); - } - } - - // Map offsets to registers - var offsetToRegister = new Dictionary(); - foreach (var offset in offsets) - { - var name = offset < 0 ? $"stack_-{-offset:X}" : $"stack_{offset:X}"; - offsetToRegister.Add(offset, name); - } + var instructions = method.ControlFlowGraph!.Blocks.SelectMany(b => b.Instructions); // Replace stack offset operands - foreach (var instruction in method.ConvertedIsil!) + foreach (var instruction in instructions) { for (var i = 0; i < instruction.Operands.Count; i++) { var operand = instruction.Operands[i]; if (operand is StackOffset offset) - instruction.Operands[i] = - new Register(null, offsetToRegister[offset.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/DecompilerException.cs b/Cpp2IL.Core/DecompilerException.cs new file mode 100644 index 000000000..66358cbea --- /dev/null +++ b/Cpp2IL.Core/DecompilerException.cs @@ -0,0 +1,7 @@ +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) { } + public DecompilerException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } +} diff --git a/Cpp2IL.Core/Graphs/BlockType.cs b/Cpp2IL.Core/Graphs/BlockType.cs index 25a5c16e8..b1eac4fe9 100644 --- a/Cpp2IL.Core/Graphs/BlockType.cs +++ b/Cpp2IL.Core/Graphs/BlockType.cs @@ -8,16 +8,7 @@ public enum BlockType : byte 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 index 0bf807f89..52a7e2f4c 100644 --- a/Cpp2IL.Core/Graphs/DominatorInfo.cs +++ b/Cpp2IL.Core/Graphs/DominatorInfo.cs @@ -11,16 +11,14 @@ public class DominatorInfo public Dictionary> PostDominators = new(); public Dictionary> Dominators = new(); - public static DominatorInfo Build(ISILControlFlowGraph graph) + public DominatorInfo(ISILControlFlowGraph graph) { - var dominatorInfo = new DominatorInfo(); - dominatorInfo.CalculateDominators(graph); - dominatorInfo.CalculatePostDominators(graph); - dominatorInfo.CalculateImmediateDominators(graph); - dominatorInfo.CalculateImmediatePostDominators(graph); - dominatorInfo.CalculateDominanceFrontiers(graph); - dominatorInfo.BuildDominanceTree(); - return dominatorInfo; + CalculateDominators(graph); + CalculatePostDominators(graph); + CalculateImmediateDominators(graph); + CalculateImmediatePostDominators(graph); + CalculateDominanceFrontiers(graph); + BuildDominanceTree(); } public bool Dominates(Block a, Block b) @@ -113,7 +111,7 @@ private void CalculatePostDominators(ISILControlFlowGraph graph) continue; var tempPostDoms = block.Successors.Count == 0 - ? new HashSet { block } + ? new HashSet() : new HashSet(PostDominators[block.Successors[0]]); for (var i = 1; i < block.Successors.Count; i++) diff --git a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs index 1878fa14d..757840067 100644 --- a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs +++ b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Model.Contexts; namespace Cpp2IL.Core.Graphs; @@ -13,19 +12,40 @@ public class ISILControlFlowGraph public int Count => Blocks.Count; public List Blocks; + public List Instructions + { + get + { + var instructions = new List(); + foreach (var block in Blocks) + instructions.AddRange(block.Instructions); + return instructions.OrderBy(i => i.Index).ToList(); + } + } + private int idCounter; - public ISILControlFlowGraph() + public ISILControlFlowGraph(List instructions) { - EntryBlock = new Block() { ID = idCounter++ }; - EntryBlock.BlockType = BlockType.Entry; - ExitBlock = new Block() { ID = idCounter++ }; - ExitBlock.BlockType = BlockType.Exit; + EntryBlock = new Block + { + ID = idCounter++, + BlockType = BlockType.Entry + }; + + ExitBlock = new Block + { + ID = idCounter++, + BlockType = BlockType.Exit + }; + Blocks = [ EntryBlock, ExitBlock ]; + + Build(instructions); } private bool TryGetTargetJumpInstructionIndex(Instruction instruction, out int jumpInstructionIndex) @@ -46,32 +66,40 @@ private bool TryGetTargetJumpInstructionIndex(Instruction instruction, out int j public void RemoveUnreachableBlocks() { - // Get reachable blocks - var reachable = new HashSet(); - var worklist = new Queue(); - worklist.Enqueue(EntryBlock); + if (Blocks.Count == 0) + return; - while (worklist.Count > 0) - { - var block = worklist.Dequeue(); + // Get blocks reachable from entry + var reachable = new List(); + var visited = new List { EntryBlock }; + reachable.Add(EntryBlock); - if (!reachable.Add(block)) - continue; + var total = 0; + while (total < reachable.Count) + { + var block = reachable[total]; + total++; - foreach (var succ in block.Successors) - worklist.Enqueue(succ); + foreach (var successor in block.Successors) + { + if (visited.Contains(successor)) + continue; + visited.Add(successor); + reachable.Add(successor); + } } - // Remove unreachable blocks - var toRemove = Blocks.Where(b => !reachable.Contains(b)).ToList(); - foreach (var block in toRemove) - { - foreach (var pred in block.Predecessors) - pred.Successors.Remove(block); + // Get unreachable blocks + var unreachable = Blocks.Where(block => !visited.Remove(block)).ToList(); - foreach (var succ in block.Successors) - succ.Predecessors.Remove(block); + // 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); } } @@ -220,7 +248,7 @@ public void MergeCallBlocks() // fix up successors predecessors foreach (var successor in nextBlock.Successors) { - for (int j = 0; j < successor.Predecessors.Count; j++) + for (var j = 0; j < successor.Predecessors.Count; j++) { if (successor.Predecessors[j] == nextBlock) successor.Predecessors[j] = block; @@ -232,16 +260,13 @@ public void MergeCallBlocks() // Remove all merged blocks foreach (var removed in toRemove) - { Blocks.Remove(removed); - Blocks.Remove(removed); - } foreach (var block in Blocks) block.CalculateBlockType(); } - public void Build(List instructions) + private void Build(List instructions) { if (instructions == null) throw new ArgumentNullException(nameof(instructions)); @@ -333,6 +358,20 @@ public void Build(List instructions) if (node.Dirty) FixBlock(node); } + + // 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)) + { + if (instruction.Operands.Count > 0 && instruction.Operands[0] is Instruction target) + instruction.Operands[0] = FindBlockByInstruction(target)!; + } } private void FixBlock(Block block, bool removeJmp = false) diff --git a/Cpp2IL.Core/Graphs/Processors/CallProcessor.cs b/Cpp2IL.Core/Graphs/Processors/CallProcessor.cs deleted file mode 100644 index 62fd3a1e4..000000000 --- a/Cpp2IL.Core/Graphs/Processors/CallProcessor.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Linq; -using Cpp2IL.Core.Extensions; -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.Instructions[^1]; - if (callInstruction == null) - return; - if (!callInstruction.IsCall) - return; - - if (callInstruction.Operands.Count <= 0) - return; - var dest = callInstruction.Operands[0]; - if (!dest.IsNumeric()) - return; - - var target = (ulong)dest; - - 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] = singleTargetMethod; - } - - private void HandleKeyFunction(ApplicationAnalysisContext appContext, Instruction 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 - { - var pairs = kFA.Pairs.ToList(); - var index = pairs.FindIndex(pair => pair.Value == target); - method = pairs[index].Key; - } - - if (method != "") - { - instruction.Operands[0] = 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/RemoveRedundantAssignmentsProcessor.cs b/Cpp2IL.Core/Graphs/Processors/RemoveRedundantAssignmentsProcessor.cs deleted file mode 100644 index d0d7162d6..000000000 --- a/Cpp2IL.Core/Graphs/Processors/RemoveRedundantAssignmentsProcessor.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Collections.Generic; -using Cpp2IL.Core.Graphs.Processors; -using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.Graphs; - -public class RemoveRedundantAssignmentsProcessor : IBlockProcessor -{ - public void Process(MethodAnalysisContext methodAnalysisContext, Block block) - { - // Repeat until no change - var changed = true; - while (changed) - { - changed = false; - - // For each instruction - for (var i = 0; i < block.Instructions.Count - 1; i++) - { - var current = block.Instructions[i]; - var next = block.Instructions[i + 1]; - - // Don't make return register same as args - if (next.OpCode == OpCode.Call) - continue; - - if (current.OpCode != OpCode.Move) continue; - if (current.Operands[0] is not Register moveDestRegister) continue; - var moveSrc = current.Operands[1]; - - // Check if it's used before being reassigned (+2 to skip next) - if (IsRegisterUsedBeforeReassignment(i + 2, moveDestRegister, block)) - continue; - - // Replace occurrences of the redundant register in the next instruction - for (var j = 0; j < next.Operands.Count; j++) - { - if (next.Operands[j] is Register register && register == moveDestRegister) - { - next.Operands[j] = moveSrc; - RemoveInstruction(current, methodAnalysisContext.ControlFlowGraph!); - changed = true; - break; - } - } - } - } - } - - private static bool IsRegisterUsedBeforeReassignment(int startIndex, Register register, Block block) - { - // Traverse everything reachable from current block - var reachable = new HashSet(); - var worklist = new Queue(); - worklist.Enqueue(block); - - while (worklist.Count > 0) - { - var nextBlock = worklist.Dequeue(); - - if (!reachable.Add(nextBlock)) - continue; - - for (int i = (nextBlock == block) ? startIndex : 0; i < block.Instructions.Count; i++) - { - var instruction = block.Instructions[i]; - - // It's reassigned - if (instruction.Destination is Register destRegister && destRegister == register) - return false; - - // is it used? - foreach (var operand in instruction.Sources) - { - if (operand is Register usedReg && usedReg == register) - return true; - } - } - - foreach (var succ in nextBlock.Successors) - worklist.Enqueue(succ); - } - - return false; - } - - private static void RemoveInstruction(Instruction instruction, ISILControlFlowGraph graph) - { - var block = graph.FindBlockByInstruction(instruction); - block?.Instructions.Remove(instruction); - } -} diff --git a/Cpp2IL.Core/ISIL/Instruction.cs b/Cpp2IL.Core/ISIL/Instruction.cs index b779a2d16..c2907c8af 100644 --- a/Cpp2IL.Core/ISIL/Instruction.cs +++ b/Cpp2IL.Core/ISIL/Instruction.cs @@ -100,10 +100,10 @@ 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])}"; + 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} {callTarget:X4}, {string.Join(", ", Operands.Skip(1).Select(FormatOperand))}"; return $"{Index} {OpCode} {string.Join(", ", Operands.Select(FormatOperand))}"; } diff --git a/Cpp2IL.Core/ISIL/LocalVariable.cs b/Cpp2IL.Core/ISIL/LocalVariable.cs index f7623ae07..2c0ff7b1d 100644 --- a/Cpp2IL.Core/ISIL/LocalVariable.cs +++ b/Cpp2IL.Core/ISIL/LocalVariable.cs @@ -4,7 +4,7 @@ namespace Cpp2IL.Core.ISIL; -public class LocalVariable(string name, Register register, TypeAnalysisContext? type) : IEquatable +public class LocalVariable(string name, Register register, TypeAnalysisContext? type = null) { public string Name = name; public Register Register = register; @@ -15,6 +15,7 @@ public class LocalVariable(string name, Register register, TypeAnalysisContext? public TypeAnalysisContext? Type = type; public bool IsThis = false; + public bool IsReturn = false; public override string ToString() { @@ -22,33 +23,7 @@ public override string ToString() sb.Append(Name); if (Type != null) sb.Append($" ({Type.Name})"); + sb.Append($" ({Register})"); return sb.ToString(); } - - public static bool operator ==(LocalVariable left, LocalVariable right) - { - return left.Equals(right); - } - - public static bool operator !=(LocalVariable left, LocalVariable right) - { - return !(left == right); - } - - public override bool Equals(object? obj) - { - if (obj is not LocalVariable local) - return false; - return Equals(local); - } - - public bool Equals(LocalVariable other) - { - return Name == other.Name; - } - - public override int GetHashCode() - { - return HashCode.Combine(Name); - } } diff --git a/Cpp2IL.Core/ISIL/Register.cs b/Cpp2IL.Core/ISIL/Register.cs index bdb3e572b..f4a355a68 100644 --- a/Cpp2IL.Core/ISIL/Register.cs +++ b/Cpp2IL.Core/ISIL/Register.cs @@ -14,11 +14,16 @@ public Register(int? number, string? name, int version = -1) Name = name!; Number = name!.GetHashCode(); } - else + else if (name == null) { Name = "reg" + number; Number = (int)number; } + else + { + Name = name; + Number = (int)number; + } Version = version; } diff --git a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs index 47fa1dec2..2751ca9ef 100644 --- a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs @@ -89,10 +89,12 @@ private void ConvertInstructionStatement(Instruction instruction, List Parameters = []; + public List ParameterLocals = []; + /// /// Does this method return void? /// @@ -224,11 +226,16 @@ IEnumerable GetOverriddenMethods(Il2CppTypeDefinition dec } } - private static readonly List blockProcessors = + private static readonly List analysisActions = [ - new MetadataProcessor(), - new CallProcessor(), - new RemoveRedundantAssignmentsProcessor() + // Indirect jumps should probably be resolved here before stack analysis + new StackAnalyzer() { MaxBlockVisitCount = 5000 }, + new BuildSsaForm(), + new CreateLocals(), + new ResolveCalls(), + new ApplyMetadata(), + new RemoveSsaForm(), + new Inlining() ]; public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext) @@ -305,38 +312,21 @@ public void Analyze() if (ConvertedIsil.Count == 0) return; //Nothing to do, empty function - ControlFlowGraph = new ISILControlFlowGraph(); - ControlFlowGraph.Build(ConvertedIsil); - // Indirect jumps should probably be resolved here - ControlFlowGraph.RemoveUnreachableBlocks(); - ControlFlowGraph.RemoveNops(); - StackAnalyzer.Analyze(this); - ControlFlowGraph.MergeCallBlocks(); - DominatorInfo = DominatorInfo.Build(ControlFlowGraph); - ControlFlowGraph.BuildUseDefLists(); - var ssa = new BuildSsaForm(); - ssa.Build(this); - - // Post step to convert metadata usage. Ldstr Opcodes etc. - foreach (var block in ControlFlowGraph.Blocks) - { - foreach (var converter in blockProcessors) - { - converter.Process(this, block); - } - } - - ssa.RemoveSsaForm(this); + ControlFlowGraph = new ISILControlFlowGraph(ConvertedIsil); + DominatorInfo = new DominatorInfo(ControlFlowGraph); - var rra = new RemoveRedundantAssignmentsProcessor(); - foreach (var block in ControlFlowGraph.Blocks) - rra.Process(this, block); + foreach (var action in analysisActions) + action.Apply(this); } + public void AddWarning(string warning) => AnalysisWarnings.Add($"warning: {warning}"); + public void AddError(string error) => AnalysisWarnings.Add($"error: {error}"); + public void ReleaseAnalysisData() { ConvertedIsil = null; ControlFlowGraph = null; + DominatorInfo = null; } public ConcreteGenericMethodAnalysisContext MakeGenericInstanceMethod(params IEnumerable methodGenericParameters) diff --git a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs index ca7f87763..9148f732f 100644 --- a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs +++ b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs @@ -7,6 +7,7 @@ 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; @@ -99,8 +100,12 @@ public static void WriteControlFlowGraph(MethodAnalysisContext method, string ou sb.AppendLine("digraph ControlFlowGraph {"); sb.AppendLine(" \"label\"=\"Control flow graph\""); - if (graph == null) // no instructions - graph = new Graphs.ISILControlFlowGraph(); + // 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) { @@ -110,7 +115,7 @@ public static void WriteControlFlowGraph(MethodAnalysisContext method, string ou sb.AppendLine($""" {block.ID} [ "color"="{(isEntry ? "green" : "red")}" - "label"="{(isEntry ? "Entry" : "Exit")} ({block.ID})" + "label"="{(isEntry ? $"Entry ({block.ID})\n{methodText}" : $"Exit ({block.ID})")}" ] """); } From b5079d100110eb8c468bd23e924bd0d7200bbfc4 Mon Sep 17 00:00:00 2001 From: itsmilos Date: Thu, 24 Jul 2025 01:08:34 +0300 Subject: [PATCH 08/22] Support call [memory] x86 instructions --- Cpp2IL.Core/InstructionSets/X86InstructionSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs index 2751ca9ef..8dd1444f3 100644 --- a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs @@ -412,7 +412,7 @@ ISIL.Instruction Add(ulong address, ISIL.OpCode opCode, params object[] operands var target = instruction.NearBranchTarget; - if (instruction.Op0Kind == OpKind.Register) + if (instruction.Op0Kind == OpKind.Register || instruction.Op0Kind == OpKind.Memory) { Add(instruction.IP, ISIL.OpCode.IndirectCallVoid, ConvertOperand(instruction, 0)); } From 7205c7d47a58408164e215680cad2a79e468d426 Mon Sep 17 00:00:00 2001 From: itsmilos Date: Fri, 25 Jul 2025 00:13:17 +0300 Subject: [PATCH 09/22] Type propagation --- Cpp2IL.Core/Actions/ApplyMetadata.cs | 4 +- Cpp2IL.Core/Actions/CreateLocals.cs | 3 +- Cpp2IL.Core/Actions/Inlining.cs | 2 +- Cpp2IL.Core/Actions/PropagateTypes.cs | 156 ++++++++++++++++++ Cpp2IL.Core/Actions/ResolveCalls.cs | 29 ++-- Cpp2IL.Core/Actions/StackAnalyzer.cs | 3 +- Cpp2IL.Core/Graphs/Block.cs | 2 +- Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs | 14 +- Cpp2IL.Core/ISIL/Instruction.cs | 6 +- Cpp2IL.Core/ISIL/LocalVariable.cs | 13 +- Cpp2IL.Core/ISIL/OpCode.cs | 7 +- .../InstructionSets/NewArmV8InstructionSet.cs | 6 +- .../InstructionSets/X86InstructionSet.cs | 12 +- .../Model/Contexts/MethodAnalysisContext.cs | 5 +- 14 files changed, 198 insertions(+), 64 deletions(-) create mode 100644 Cpp2IL.Core/Actions/PropagateTypes.cs diff --git a/Cpp2IL.Core/Actions/ApplyMetadata.cs b/Cpp2IL.Core/Actions/ApplyMetadata.cs index aa15a40d1..ae9e7b247 100644 --- a/Cpp2IL.Core/Actions/ApplyMetadata.cs +++ b/Cpp2IL.Core/Actions/ApplyMetadata.cs @@ -10,10 +10,10 @@ public class ApplyMetadata : IAction { public void Apply(MethodAnalysisContext method) { - foreach (var instruction in method.ControlFlowGraph!.Blocks.SelectMany(b => b.Instructions)) + foreach (var instruction in method.ControlFlowGraph!.Instructions) { // TODO: Check if it shows up in any other - if (instruction.OpCode != OpCode.Move) + if (instruction.OpCode != OpCode.Move && instruction.OpCode != OpCode.LoadAddress) { continue; } diff --git a/Cpp2IL.Core/Actions/CreateLocals.cs b/Cpp2IL.Core/Actions/CreateLocals.cs index ee37b6780..f8b0b04ee 100644 --- a/Cpp2IL.Core/Actions/CreateLocals.cs +++ b/Cpp2IL.Core/Actions/CreateLocals.cs @@ -63,7 +63,7 @@ public void Apply(MethodAnalysisContext method) for (var i = 0; i < cfg.Instructions.Count; i++) { var instruction = cfg.Instructions[i]; - if (instruction.OpCode != OpCode.Return) continue; + if (instruction.OpCode != OpCode.Return || instruction.Operands.Count != 1) continue; var returnLocal = (LocalVariable)instruction.Sources[0]; @@ -122,6 +122,7 @@ public void Apply(MethodAnalysisContext method) if (methodInfoLocal != null) { methodInfoLocal.Name = "methodInfo"; + methodInfoLocal.IsMethodInfo = true; paramLocals.Add(methodInfoLocal); } } diff --git a/Cpp2IL.Core/Actions/Inlining.cs b/Cpp2IL.Core/Actions/Inlining.cs index bd942fd75..9ee9391f6 100644 --- a/Cpp2IL.Core/Actions/Inlining.cs +++ b/Cpp2IL.Core/Actions/Inlining.cs @@ -98,7 +98,7 @@ private static void InlineLocals(MethodAnalysisContext method) ReplaceLocalsUntilReassignment(block, i + 1, local, source); if (!method.ParameterLocals.Contains(local)) - method.ParameterLocals.Remove(local); + method.Locals.Remove(local); // Change that move to nop instruction.OpCode = OpCode.Nop; diff --git a/Cpp2IL.Core/Actions/PropagateTypes.cs b/Cpp2IL.Core/Actions/PropagateTypes.cs new file mode 100644 index 000000000..874a2f8d9 --- /dev/null +++ b/Cpp2IL.Core/Actions/PropagateTypes.cs @@ -0,0 +1,156 @@ +using System.Linq; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Actions; + +public class PropagateTypes : IAction +{ + public int MaxLoopCount = -1; + + public void Apply(MethodAnalysisContext method) + { + PropagateFromReturn(method); + PropagateFromParameters(method); + PropagateFromCallParameters(method); + PropagateThroughMoves(method); + } + + private void PropagateThroughMoves(MethodAnalysisContext method) + { + var changed = true; + var loopCount = 0; + + while (changed) + { + changed = false; + loopCount++; + + if (MaxLoopCount != -1 && loopCount > MaxLoopCount) + throw new DecompilerException($"Type propagation through moves not settling! (looped {MaxLoopCount} times)"); + + foreach (var instruction in method.ControlFlowGraph!.Instructions) + { + if (instruction.OpCode != OpCode.Move && instruction.OpCode != OpCode.LoadAddress) + 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/Actions/ResolveCalls.cs b/Cpp2IL.Core/Actions/ResolveCalls.cs index 793c37e78..934ae633b 100644 --- a/Cpp2IL.Core/Actions/ResolveCalls.cs +++ b/Cpp2IL.Core/Actions/ResolveCalls.cs @@ -13,19 +13,14 @@ public void Apply(MethodAnalysisContext method) { foreach (var block in method.ControlFlowGraph!.Blocks) { - if (block.BlockType != BlockType.Call) - return; - var callInstruction = block.Instructions[^1]; - if (callInstruction == null) - return; - if (!callInstruction.IsCall) - return; + if (block.BlockType != BlockType.Call && block.BlockType != BlockType.TailCall) + continue; - if (callInstruction.Operands.Count <= 0) - return; + var callInstruction = block.Instructions[^1]; var dest = callInstruction.Operands[0]; + if (!dest.IsNumeric()) - return; + continue; var target = (ulong)dest; @@ -34,18 +29,20 @@ public void Apply(MethodAnalysisContext method) if (keyFunctionAddresses.IsKeyFunctionAddress(target)) { HandleKeyFunction(method.AppContext, callInstruction, target, keyFunctionAddresses); - return; + continue; } //Non-key function call. Try to find a single match if (!method.AppContext.MethodsByAddress.TryGetValue(target, out var targetMethods)) - return; + continue; if (targetMethods is not [{ } singleTargetMethod]) - return; + continue; callInstruction.Operands[0] = singleTargetMethod; } + + method.ControlFlowGraph.MergeCallBlocks(); } private void HandleKeyFunction(ApplicationAnalysisContext appContext, Instruction instruction, ulong target, BaseKeyFunctionAddresses kFA) @@ -65,8 +62,10 @@ private void HandleKeyFunction(ApplicationAnalysisContext appContext, Instructio else { var pairs = kFA.Pairs.ToList(); - var index = pairs.FindIndex(pair => pair.Value == target); - method = pairs[index].Key; + var key = pairs.FirstOrDefault(pair => pair.Value == target).Key; + if (key == null) + return; + method = key; } if (method != "") diff --git a/Cpp2IL.Core/Actions/StackAnalyzer.cs b/Cpp2IL.Core/Actions/StackAnalyzer.cs index fa4200189..c58bf78d0 100644 --- a/Cpp2IL.Core/Actions/StackAnalyzer.cs +++ b/Cpp2IL.Core/Actions/StackAnalyzer.cs @@ -45,7 +45,6 @@ public void Apply(MethodAnalysisContext method) CorrectOffsets(graph); ReplaceStackWithRegisters(method); - graph.MergeCallBlocks(); graph.RemoveNops(); graph.RemoveEmptyBlocks(); } @@ -139,7 +138,7 @@ private void TraverseGraph(Block block, int visitedBlockCount = 0) private static void ReplaceStackWithRegisters(MethodAnalysisContext method) { - var instructions = method.ControlFlowGraph!.Blocks.SelectMany(b => b.Instructions); + var instructions = method.ControlFlowGraph!.Instructions; // Replace stack offset operands foreach (var instruction in instructions) diff --git a/Cpp2IL.Core/Graphs/Block.cs b/Cpp2IL.Core/Graphs/Block.cs index 4f012c3d4..132f138c3 100644 --- a/Cpp2IL.Core/Graphs/Block.cs +++ b/Cpp2IL.Core/Graphs/Block.cs @@ -49,7 +49,7 @@ public void CalculateBlockType() OpCode.ConditionalJump => BlockType.TwoWay, OpCode.IndirectJump => BlockType.NWay, OpCode.Call or OpCode.CallVoid => BlockType.Call, - OpCode.Return or OpCode.ReturnVoid => BlockType.Return, + OpCode.Return => BlockType.Return, _ => BlockType.Fall, }; diff --git a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs index 757840067..81077aaee 100644 --- a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs +++ b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs @@ -12,16 +12,7 @@ public class ISILControlFlowGraph public int Count => Blocks.Count; public List Blocks; - public List Instructions - { - get - { - var instructions = new List(); - foreach (var block in Blocks) - instructions.AddRange(block.Instructions); - return instructions.OrderBy(i => i.Index).ToList(); - } - } + public List Instructions => Blocks.SelectMany(b => b.Instructions).OrderBy(i => i.Index).ToList(); private int idCounter; @@ -320,8 +311,7 @@ private void Build(List instructions) case OpCode.Call: case OpCode.CallVoid: case OpCode.Return: - case OpCode.ReturnVoid: - var isReturn = instructions[i].OpCode == OpCode.Return || instructions[i].OpCode == OpCode.ReturnVoid; + var isReturn = instructions[i].OpCode == OpCode.Return; currentBlock.AddInstruction(instructions[i]); diff --git a/Cpp2IL.Core/ISIL/Instruction.cs b/Cpp2IL.Core/ISIL/Instruction.cs index c2907c8af..029afc17f 100644 --- a/Cpp2IL.Core/ISIL/Instruction.cs +++ b/Cpp2IL.Core/ISIL/Instruction.cs @@ -23,8 +23,6 @@ public class Instruction(int index, OpCode opcode, params object[] operands) public bool IsCall => OpCode is OpCode.Call or OpCode.CallVoid; - public bool IsReturn => OpCode is OpCode.Return or OpCode.ReturnVoid; - public bool IsAssignment => Destination != null; public List Sources => GetSources(); @@ -82,13 +80,15 @@ or OpCode.And or OpCode.Or or OpCode.Xor OpCode.Call => Operands.Skip(2).ToList(), OpCode.CallVoid or OpCode.Phi => Operands.Skip(1).ToList(), - OpCode.Return => [Operands[0]], 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(); diff --git a/Cpp2IL.Core/ISIL/LocalVariable.cs b/Cpp2IL.Core/ISIL/LocalVariable.cs index 2c0ff7b1d..71fd566c4 100644 --- a/Cpp2IL.Core/ISIL/LocalVariable.cs +++ b/Cpp2IL.Core/ISIL/LocalVariable.cs @@ -1,5 +1,3 @@ -using System; -using System.Text; using Cpp2IL.Core.Model.Contexts; namespace Cpp2IL.Core.ISIL; @@ -16,14 +14,7 @@ public class LocalVariable(string name, Register register, TypeAnalysisContext? public bool IsThis = false; public bool IsReturn = false; + public bool IsMethodInfo = false; - public override string ToString() - { - var sb = new StringBuilder(); - sb.Append(Name); - if (Type != null) - sb.Append($" ({Type.Name})"); - sb.Append($" ({Register})"); - return sb.ToString(); - } + public override string ToString() => Type == null ? $"{Name} @ {Register}" : $"{Name} @ {Register} ({Type.FullName})"; } diff --git a/Cpp2IL.Core/ISIL/OpCode.cs b/Cpp2IL.Core/ISIL/OpCode.cs index 9b8b5644a..76c63baa6 100644 --- a/Cpp2IL.Core/ISIL/OpCode.cs +++ b/Cpp2IL.Core/ISIL/OpCode.cs @@ -35,14 +35,11 @@ public enum OpCode // There is some weird stuff in doc comments because i can't CallVoid, /// IndirectCallVoid target, arg1, arg2, etc. : target(arg1, arg2, etc.) - IndirectCallVoid, + IndirectCall, - /// Return value : return value + /// Return (optional) value : return value Return, - /// ReturnVoid : return - ReturnVoid, - /// Jump target : goto target Jump, diff --git a/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs b/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs index 8472c3fa5..7bbb34037 100644 --- a/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs @@ -241,7 +241,7 @@ void Add(ulong address, OpCode opCode, params object[] operands) case Arm64Mnemonic.RET: var returnRegister = GetReturnRegisterForContext(context); if (returnRegister == null) - Add(address, OpCode.ReturnVoid); + Add(address, OpCode.Return); else Add(address, OpCode.Return, returnRegister); break; @@ -255,7 +255,7 @@ void Add(ulong address, OpCode opCode, params object[] operands) Add(address, OpCode.Call, instruction.BranchTarget, GetArgumentOperandsForCall(context, instruction.BranchTarget).ToArray()); var returnRegister2 = GetReturnRegisterForContext(context); if (returnRegister2 == null) - Add(address, OpCode.ReturnVoid); + Add(address, OpCode.Return); else Add(address, OpCode.Return, returnRegister2); } @@ -267,7 +267,7 @@ void Add(ulong address, OpCode opCode, params object[] operands) break; case Arm64Mnemonic.BR: // branches unconditionally to an address in a register, with a hint that this is not a subroutine return. - Add(address, OpCode.IndirectCallVoid, ConvertOperand(instruction, 0)); + Add(address, OpCode.IndirectCall, ConvertOperand(instruction, 0)); break; case Arm64Mnemonic.CBNZ: case Arm64Mnemonic.CBZ: diff --git a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs index 8dd1444f3..104ed6954 100644 --- a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs @@ -265,7 +265,7 @@ ISIL.Instruction Add(ulong address, ISIL.OpCode opCode, params object[] operands // Are st* registers even used in il2cpp games? if (context.IsVoid) - Add(instruction.IP, ISIL.OpCode.ReturnVoid); + Add(instruction.IP, ISIL.OpCode.Return); else if (context.Definition?.RawReturnType?.Type is Il2CppTypeEnum.IL2CPP_TYPE_R4 or Il2CppTypeEnum.IL2CPP_TYPE_R8) Add(instruction.IP, ISIL.OpCode.Return, new ISIL.Register(null, "xmm0")); else @@ -414,7 +414,7 @@ ISIL.Instruction Add(ulong address, ISIL.OpCode opCode, params object[] operands if (instruction.Op0Kind == OpKind.Register || instruction.Op0Kind == OpKind.Memory) { - Add(instruction.IP, ISIL.OpCode.IndirectCallVoid, ConvertOperand(instruction, 0)); + Add(instruction.IP, ISIL.OpCode.IndirectCall, ConvertOperand(instruction, 0)); } else if (context.AppContext.MethodsByAddress.TryGetValue(target, out var possibleMethods)) { @@ -425,7 +425,7 @@ ISIL.Instruction Add(ulong address, ISIL.OpCode opCode, params object[] operands if (possibleMethods[0].IsVoid) call = Add(instruction.IP, ISIL.OpCode.CallVoid, target); else - call = Add(instruction.IP, ISIL.OpCode.Call, target); + call = Add(instruction.IP, ISIL.OpCode.Call, target, new ISIL.Register(null, "eax") /* return value */); call.Operands.AddRange(X64CallingConventionResolver.ResolveForManaged(possibleMethods[0])); } @@ -454,7 +454,7 @@ ISIL.Instruction Add(ulong address, ISIL.OpCode opCode, params object[] operands if (ctx.IsVoid) call = Add(instruction.IP, ISIL.OpCode.CallVoid, target); else - call = Add(instruction.IP, ISIL.OpCode.Call, target); + call = Add(instruction.IP, ISIL.OpCode.Call, target, new ISIL.Register(null, "eax") /* return value */); call.Operands.AddRange(X64CallingConventionResolver.ResolveForManaged(ctx)); } @@ -465,7 +465,7 @@ ISIL.Instruction Add(ulong address, ISIL.OpCode opCode, params object[] operands // 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) - var call = Add(instruction.IP, ISIL.OpCode.Call, 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)); } @@ -635,7 +635,7 @@ ISIL.Instruction Add(ulong address, ISIL.OpCode opCode, params object[] operands } if (instruction.Op0Kind == OpKind.Register) // ex: jmp rax { - Add(instruction.IP, ISIL.OpCode.IndirectCallVoid, ConvertOperand(instruction, 0)); + Add(instruction.IP, ISIL.OpCode.IndirectCall, ConvertOperand(instruction, 0)); break; } diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index b28ec3d92..939115b4d 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -228,14 +228,15 @@ IEnumerable GetOverriddenMethods(Il2CppTypeDefinition dec private static readonly List analysisActions = [ - // Indirect jumps should probably be resolved here before stack analysis + // Indirect jumps/calls should probably be resolved here before stack analysis new StackAnalyzer() { MaxBlockVisitCount = 5000 }, new BuildSsaForm(), new CreateLocals(), new ResolveCalls(), new ApplyMetadata(), new RemoveSsaForm(), - new Inlining() + new Inlining(), + new PropagateTypes() { MaxLoopCount = 5000 } ]; public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext) From 39373e62f2be05d5a8a6b87f7ac1fd47ea48974d Mon Sep 17 00:00:00 2001 From: itsmilos Date: Fri, 25 Jul 2025 03:42:34 +0300 Subject: [PATCH 10/22] Use move instead of load address --- .../Graphing/ExceptionThrowingGraph.cs | 4 ++-- Cpp2IL.Core/Actions/ApplyMetadata.cs | 22 +++++++------------ Cpp2IL.Core/Actions/PropagateTypes.cs | 2 +- Cpp2IL.Core/ISIL/Instruction.cs | 3 +-- Cpp2IL.Core/ISIL/OpCode.cs | 3 --- .../InstructionSets/X86InstructionSet.cs | 4 ++-- 6 files changed, 14 insertions(+), 24 deletions(-) diff --git a/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs b/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs index ba43f6222..d0eef327f 100644 --- a/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs +++ b/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs @@ -42,7 +42,7 @@ public void Setup() 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.LoadAddress, new Register(null, "reg19"), new StackOffset(0x20)); + 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); @@ -64,7 +64,7 @@ public void Setup() Add(048, OpCode.Call, 0xDEADBEEF); Add(049, OpCode.Interrupt); Add(050, OpCode.Move, new Register(null, "reg36"), 0); - Add(051, OpCode.LoadAddress, new Register(null, "reg37"), new StackOffset(0x20)); + 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")); diff --git a/Cpp2IL.Core/Actions/ApplyMetadata.cs b/Cpp2IL.Core/Actions/ApplyMetadata.cs index ae9e7b247..9ce8bc975 100644 --- a/Cpp2IL.Core/Actions/ApplyMetadata.cs +++ b/Cpp2IL.Core/Actions/ApplyMetadata.cs @@ -1,4 +1,3 @@ -using System.Linq; using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Model.Contexts; using Cpp2IL.Core.Utils; @@ -12,25 +11,20 @@ public void Apply(MethodAnalysisContext method) { foreach (var instruction in method.ControlFlowGraph!.Instructions) { - // TODO: Check if it shows up in any other - if (instruction.OpCode != OpCode.Move && instruction.OpCode != OpCode.LoadAddress) - { + if (instruction.OpCode != OpCode.Move) continue; - } - if ((instruction.Operands[0] is not Register) || (instruction.Operands[1] is not MemoryOperand)) - { + if ((instruction.Operands[0] is not LocalVariable) || (instruction.Operands[1] is not MemoryOperand memory)) continue; - } - var memoryOp = (MemoryOperand)instruction.Operands[1]; - if (memoryOp.Base == null && memoryOp.Index == null && memoryOp.Scale == 0) + if (memory.Base == null && memory.Index == null && memory.Scale == 0) { - var val = LibCpp2IlMain.GetLiteralByAddress((ulong)memoryOp.Addend); - if (val == null) + var stringLiteral = LibCpp2IlMain.GetLiteralByAddress((ulong)memory.Addend); + + if (stringLiteral == null) { // Try instead check if its type metadata usage - var metadataUsage = LibCpp2IlMain.GetTypeGlobalByAddress((ulong)memoryOp.Addend); + var metadataUsage = LibCpp2IlMain.GetTypeGlobalByAddress((ulong)memory.Addend); if (metadataUsage != null && method.DeclaringType is not null) { var typeAnalysisContext = metadataUsage.ToContext(method.DeclaringType!.DeclaringAssembly); @@ -41,7 +35,7 @@ public void Apply(MethodAnalysisContext method) continue; } - instruction.Operands[1] = val; + instruction.Operands[1] = stringLiteral; } } } diff --git a/Cpp2IL.Core/Actions/PropagateTypes.cs b/Cpp2IL.Core/Actions/PropagateTypes.cs index 874a2f8d9..c4a995cfb 100644 --- a/Cpp2IL.Core/Actions/PropagateTypes.cs +++ b/Cpp2IL.Core/Actions/PropagateTypes.cs @@ -31,7 +31,7 @@ private void PropagateThroughMoves(MethodAnalysisContext method) foreach (var instruction in method.ControlFlowGraph!.Instructions) { - if (instruction.OpCode != OpCode.Move && instruction.OpCode != OpCode.LoadAddress) + if (instruction.OpCode != OpCode.Move) continue; if (instruction.Operands[0] is LocalVariable destination && instruction.Operands[1] is LocalVariable source) diff --git a/Cpp2IL.Core/ISIL/Instruction.cs b/Cpp2IL.Core/ISIL/Instruction.cs index 029afc17f..73b7a2ed3 100644 --- a/Cpp2IL.Core/ISIL/Instruction.cs +++ b/Cpp2IL.Core/ISIL/Instruction.cs @@ -41,7 +41,6 @@ public object? Destination { case OpCode.Move: case OpCode.Phi: - case OpCode.LoadAddress: case OpCode.Call: case OpCode.Add: case OpCode.Subtract: @@ -69,7 +68,7 @@ private List GetSources(bool constantsOnly = true) { var sources = OpCode switch { - OpCode.Move or OpCode.LoadAddress or OpCode.ConditionalJump + OpCode.Move or OpCode.ConditionalJump or OpCode.ShiftStack or OpCode.Not or OpCode.Negate => [Operands[1]], diff --git a/Cpp2IL.Core/ISIL/OpCode.cs b/Cpp2IL.Core/ISIL/OpCode.cs index 76c63baa6..bfadf843a 100644 --- a/Cpp2IL.Core/ISIL/OpCode.cs +++ b/Cpp2IL.Core/ISIL/OpCode.cs @@ -25,9 +25,6 @@ public enum OpCode // There is some weird stuff in doc comments because i can't /// Phi dest, src1, src2, etc. : dest = phi(src1, src2, etc.) Phi, - /// LoadAddress dest, src : dest = address(src) - LoadAddress, - /// Call target, dest, arg1, arg2, etc. : dest = target(arg1, arg2, etc.) Call, diff --git a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs index 104ed6954..b854fd507 100644 --- a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs @@ -178,7 +178,7 @@ ISIL.Instruction Add(ulong address, ISIL.OpCode opCode, params object[] operands break; } case Mnemonic.Lea: - Add(instruction.IP, ISIL.OpCode.LoadAddress, 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 @@ -781,7 +781,7 @@ void AddCompareInstruction(ulong ip, object op0, object op1) } - private object ConvertOperand(Instruction instruction, int operand) + private object ConvertOperand(Instruction instruction, int operand, bool isLeaAddress = false) { var kind = instruction.GetOpKind(operand); From 762e380c02aaa7688fe97b749ae868970a21dbd4 Mon Sep 17 00:00:00 2001 From: itsmilos Date: Sat, 26 Jul 2025 00:04:51 +0300 Subject: [PATCH 11/22] Resolve field offsets --- Cpp2IL.Core/Actions/ResolveFieldOffsets.cs | 36 +++++++++++++++++++ Cpp2IL.Core/ISIL/FieldReference.cs | 12 +++++++ Cpp2IL.Core/ISIL/LocalVariable.cs | 2 +- .../Model/Contexts/MethodAnalysisContext.cs | 3 +- 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 Cpp2IL.Core/Actions/ResolveFieldOffsets.cs create mode 100644 Cpp2IL.Core/ISIL/FieldReference.cs diff --git a/Cpp2IL.Core/Actions/ResolveFieldOffsets.cs b/Cpp2IL.Core/Actions/ResolveFieldOffsets.cs new file mode 100644 index 000000000..255aa1ebc --- /dev/null +++ b/Cpp2IL.Core/Actions/ResolveFieldOffsets.cs @@ -0,0 +1,36 @@ +using System.Linq; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Actions; + +public class ResolveFieldOffsets : IAction +{ + public void Apply(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); + } + } + } +} 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/LocalVariable.cs b/Cpp2IL.Core/ISIL/LocalVariable.cs index 71fd566c4..978f66577 100644 --- a/Cpp2IL.Core/ISIL/LocalVariable.cs +++ b/Cpp2IL.Core/ISIL/LocalVariable.cs @@ -8,7 +8,7 @@ public class LocalVariable(string name, Register register, TypeAnalysisContext? public Register Register = register; /// - /// null if typeprop has not been done yet. + /// null if typeprop has not been done yet, or if the type could not be determined. /// public TypeAnalysisContext? Type = type; diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index 939115b4d..5f4927545 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -236,7 +236,8 @@ IEnumerable GetOverriddenMethods(Il2CppTypeDefinition dec new ApplyMetadata(), new RemoveSsaForm(), new Inlining(), - new PropagateTypes() { MaxLoopCount = 5000 } + new PropagateTypes() { MaxLoopCount = 5000 }, + new ResolveFieldOffsets() ]; public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext) From ee569327605d770a01cae91162dd652dab655845 Mon Sep 17 00:00:00 2001 From: itsmilos Date: Sat, 26 Jul 2025 18:13:32 +0300 Subject: [PATCH 12/22] Remove unused locals --- Cpp2IL.Core/Actions/RemoveUnusedLocals.cs | 24 +++++++++++++++++++ .../Model/Contexts/MethodAnalysisContext.cs | 3 ++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 Cpp2IL.Core/Actions/RemoveUnusedLocals.cs diff --git a/Cpp2IL.Core/Actions/RemoveUnusedLocals.cs b/Cpp2IL.Core/Actions/RemoveUnusedLocals.cs new file mode 100644 index 000000000..d6f5f875f --- /dev/null +++ b/Cpp2IL.Core/Actions/RemoveUnusedLocals.cs @@ -0,0 +1,24 @@ +using System.Linq; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Actions; + +public class RemoveUnusedLocals : IAction +{ + public void Apply(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--; + } + } +} diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index 5f4927545..0eb2e7bac 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -237,7 +237,8 @@ IEnumerable GetOverriddenMethods(Il2CppTypeDefinition dec new RemoveSsaForm(), new Inlining(), new PropagateTypes() { MaxLoopCount = 5000 }, - new ResolveFieldOffsets() + new ResolveFieldOffsets(), + new RemoveUnusedLocals() ]; public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext) From 19212e6b2aaee814c1d851c2a6e2ce1dc96753a6 Mon Sep 17 00:00:00 2001 From: itsmilos Date: Sun, 27 Jul 2025 03:57:56 +0300 Subject: [PATCH 13/22] Basic IL generation --- Cpp2IL.Core/DecompilerException.cs | 1 - Cpp2IL.Core/ISIL/Instruction.cs | 2 +- Cpp2IL.Core/IlGenerator.cs | 179 ++++++++++++++++++ .../InstructionSets/X86InstructionSet.cs | 13 ++ .../AsmResolverDllOutputFormat.cs | 2 +- .../AsmResolverDllOutputFormatIlRecovery.cs | 11 +- 6 files changed, 196 insertions(+), 12 deletions(-) create mode 100644 Cpp2IL.Core/IlGenerator.cs diff --git a/Cpp2IL.Core/DecompilerException.cs b/Cpp2IL.Core/DecompilerException.cs index 66358cbea..fc2a9f9ba 100644 --- a/Cpp2IL.Core/DecompilerException.cs +++ b/Cpp2IL.Core/DecompilerException.cs @@ -3,5 +3,4 @@ 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) { } - public DecompilerException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } diff --git a/Cpp2IL.Core/ISIL/Instruction.cs b/Cpp2IL.Core/ISIL/Instruction.cs index 73b7a2ed3..498240dba 100644 --- a/Cpp2IL.Core/ISIL/Instruction.cs +++ b/Cpp2IL.Core/ISIL/Instruction.cs @@ -17,7 +17,7 @@ public class Instruction(int index, OpCode opcode, params object[] operands) public bool IsFallThrough => OpCode switch { - OpCode.Return or OpCode.Jump or OpCode.ConditionalJump => false, + OpCode.Return or OpCode.Jump or OpCode.ConditionalJump or OpCode.IndirectJump => false, _ => true }; diff --git a/Cpp2IL.Core/IlGenerator.cs b/Cpp2IL.Core/IlGenerator.cs new file mode 100644 index 000000000..1f9fc79b8 --- /dev/null +++ b/Cpp2IL.Core/IlGenerator.cs @@ -0,0 +1,179 @@ +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 class Ilgenerator +{ + private Dictionary _locals = []; + private ModuleDefinition? _module; + private ReferenceImporter? _importer; + private CorLibTypeFactory? _factory; + private MemberReference? _writeLine; + + public void GenerateIl(MethodAnalysisContext context, MethodDefinition definition) + { + // 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]; + } + } + + _module = definition.Module!; + _importer = _module.DefaultImporter; + _factory = _module.CorLibTypeFactory; + + _writeLine = _factory.CorLibScope + .CreateTypeReference("System", "Console") + .CreateMemberReference("WriteLine", MethodSignature.CreateStatic(_factory.Void, _factory.String)) + .ImportWith(_importer); + + var body = new CilMethodBody(definition) + { + InitializeLocals = true // Without this ILSpy does: CompilerServices.Unsafe.SkipInit(out object obj); + }; + + definition.CilMethodBody = body; + + // Map ISIL locals to IL + _locals.Clear(); + 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); + } + + // Generate IL + foreach (var instruction in context.ControlFlowGraph!.Instructions) // context.ConvertedIsil is probably not up to date anymore here + GenerateInstructions(instruction, context, body); + } + + private void GenerateInstructions(Instruction instruction, MethodAnalysisContext context, CilMethodBody body) + { + var instructions = body.Instructions; + + 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}"); + instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); + break; + + case OpCode.Interrupt: + case OpCode.Nop: + instructions.Add(CilOpCodes.Nop); + break; + + case OpCode.Move: + instructions.Add(CilOpCodes.Ldstr, instruction.ToString()); + instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_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: + case OpCode.IndirectCall: + instructions.Add(CilOpCodes.Ldstr, instruction.ToString()); + instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); + break; + + case OpCode.Return: + if (!context.IsVoid && instruction.Operands.Count == 1) + LoadOperand(instruction.Operands[0], body); + instructions.Add(CilOpCodes.Ret); + break; + + case OpCode.Jump: + case OpCode.IndirectJump: + case OpCode.ConditionalJump: + case OpCode.ShiftStack: + 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: + instructions.Add(CilOpCodes.Ldstr, instruction.ToString()); + instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); + break; + + default: + instructions.Add(CilOpCodes.Ldstr, $"Unknown instruction: {instruction}"); + instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); + break; + } + } + + private void LoadOperand(object operand, CilMethodBody body) + { + var instructions = body.Instructions; + + switch (operand) + { + case int i: + instructions.Add(CilOpCodes.Ldc_I4, i); + 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: + 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; + default: + instructions.Add(CilOpCodes.Ldstr, operand.ToString()); + break; + } + } +} diff --git a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs index b854fd507..6671d8d1e 100644 --- a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs @@ -53,6 +53,19 @@ public override string PrintAssembly(MethodAnalysisContext context) 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) + { + 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"))); + } + // fix branches for (var i = 0; i < instructions.Count; i++) { diff --git a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormat.cs b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormat.cs index 0298235a1..bfb23bb8c 100644 --- a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormat.cs +++ b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormat.cs @@ -58,7 +58,7 @@ public sealed override void DoOutput(ApplicationAnalysisContext context, string if (TotalMethodCount != 0) { var percent = Math.Round((SuccessfulMethodCount / (float)TotalMethodCount) * 100); - Logger.InfoNewline($"{percent}% of methods successfully processed ({SuccessfulMethodCount} / {TotalMethodCount})", "DllOutput"); + Logger.InfoNewline($"{percent}% of methods successfully decompiled ({SuccessfulMethodCount} / {TotalMethodCount})", "DllOutput"); } } diff --git a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs index 9148f732f..27435bf07 100644 --- a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs +++ b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs @@ -21,6 +21,7 @@ public class AsmResolverDllOutputFormatIlRecovery : AsmResolverDllOutputFormat public override string OutputFormatName => "DLL files with IL Recovery"; private MethodDefinition? _exceptionConstructor; + private Ilgenerator _ilGenerator = new(); protected override void FillMethodBody(MethodDefinition methodDefinition, MethodAnalysisContext methodContext) { @@ -52,15 +53,7 @@ protected override void FillMethodBody(MethodDefinition methodDefinition, Method TotalMethodCount++; methodContext.Analyze(); - - // throw new Exception(isil); - // i have no idea why but when doing this for 1 class it's fine, but with entire game strings get all messed up - /* instructions.Add(CilOpCodes.Ldstr, string.Join("\n ", methodContext.ConvertedIsil)); - instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(_exceptionConstructor!)); - instructions.Add(CilOpCodes.Throw); */ - - instructions.Add(CilOpCodes.Ldnull); - instructions.Add(CilOpCodes.Throw); + _ilGenerator.GenerateIl(methodContext, methodDefinition); //WriteControlFlowGraph(methodContext, Path.Combine(Environment.CurrentDirectory, "Cpp2IL", "bin", "Debug", "net9.0", "cpp2il_out", "cfg")); From 08ba9089d89228cd56aab710356be63fca8f844a Mon Sep 17 00:00:00 2001 From: itsmilos Date: Mon, 28 Jul 2025 02:16:03 +0300 Subject: [PATCH 14/22] Fix exception in stack analyzer caused by indirect jumps --- Cpp2IL.Core/Actions/StackAnalyzer.cs | 1 + Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs | 31 +++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Cpp2IL.Core/Actions/StackAnalyzer.cs b/Cpp2IL.Core/Actions/StackAnalyzer.cs index c58bf78d0..4bdaa11fa 100644 --- a/Cpp2IL.Core/Actions/StackAnalyzer.cs +++ b/Cpp2IL.Core/Actions/StackAnalyzer.cs @@ -28,6 +28,7 @@ private class StackState public void Apply(MethodAnalysisContext method) { var graph = method.ControlFlowGraph!; + graph.RemoveUnreachableBlocks(); // Without this indirect jumps (in try catch i think) cause some weird stuff _inComingState = new Dictionary { { graph.EntryBlock, new StackState() } }; _outGoingState.Clear(); diff --git a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs index 81077aaee..c33e36ad7 100644 --- a/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs +++ b/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs @@ -12,7 +12,36 @@ public class ISILControlFlowGraph public int Count => Blocks.Count; public List Blocks; - public List Instructions => Blocks.SelectMany(b => b.Instructions).OrderBy(i => i.Index).ToList(); + 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; From a6550601175a34d2fa052ff46e1579c713496eab Mon Sep 17 00:00:00 2001 From: itsmilos Date: Thu, 7 Aug 2025 19:49:29 +0300 Subject: [PATCH 15/22] Generate IL for move and call ISIL instructions, updated opcode docs --- Cpp2IL.Core/ISIL/OpCode.cs | 64 +++++++++++++-------------- Cpp2IL.Core/IlGenerator.cs | 88 +++++++++++++++++++++++++++++++++++--- 2 files changed, 116 insertions(+), 36 deletions(-) diff --git a/Cpp2IL.Core/ISIL/OpCode.cs b/Cpp2IL.Core/ISIL/OpCode.cs index bfadf843a..cb6811aa0 100644 --- a/Cpp2IL.Core/ISIL/OpCode.cs +++ b/Cpp2IL.Core/ISIL/OpCode.cs @@ -3,91 +3,93 @@ /// /// If changing this, also update /// -public enum OpCode // There is some weird stuff in doc comments because i can't use <, >, & (idk why & doesn't work) - // doc comments are in this format: Move dest, src : dest = src - // [isil] [decompiled code/what the instruction does] +public enum OpCode { - /// Invalid (optional) text + /// Logs to console (in the decompiled code) that there was an invalid instruction, first operand is the instruction string Invalid, - /// NotImplemented (optional) text + /// Logs to console (in the decompiled code) that there was a not implemented instruction, first operand is the instruction string NotImplemented, - /// Interrupt + /// + /// Interrupt, kept for stack analysis + /// Interrupt, - /// No operation + /// + /// No operation + /// Nop, - /// Move dest, src : dest = src + /// Moves the second operand into the first Move, - /// Phi dest, src1, src2, etc. : dest = phi(src1, src2, etc.) + /// Moves the result of phi function into first operand, other operands are inputs Phi, - /// Call target, dest, arg1, arg2, etc. : dest = target(arg1, arg2, etc.) + /// Calls a method (first operand), moves the result into second, and the rest are params Call, - /// CallVoid target, arg1, arg2, etc. : target(arg1, arg2, etc.) + /// Calls a method (first operand), rest are params CallVoid, - /// IndirectCallVoid target, arg1, arg2, etc. : target(arg1, arg2, etc.) + /// Calls a method (first operand), rest are params IndirectCall, - /// Return (optional) value : return value + /// Returns from the method, the return operand is optional Return, - /// Jump target : goto target + /// Jumps to the first operand Jump, - /// IndirectJump target : goto target + /// Jumps to the first operand IndirectJump, - /// ConditionalJump target, cond : if (cond) goto target + /// If the second operand is true, jumps to the first ConditionalJump, - /// ShiftStack value : sp += value + /// Adds the first operand to stack pointer ShiftStack, - /// Add dest, l, r : dest = l + r + /// Adds the second and third operands and moves the result into the first Add, - /// Subtract dest, l, r : dest = l - r + /// Subtracts the third operand from the second and moves the result into the first Subtract, - /// Multiply dest, l, r : dest = l * r + /// Multiplies the second operand by the third and moves the result into the first Multiply, - /// Divide dest, l, r : dest = l / r + /// Divides the second operand by the third and moves the result into the first Divide, - /// ShiftLeft dest, src, count : dest = src shl count + /// Shifts the bits of the second operand left by the third and moves the result into the first ShiftLeft, - /// ShiftRight dest, src, count : dest = src shr count + /// Shifts the bits of the second operand right by the third and moves the result into the first ShiftRight, - /// And dest, l, r : dest = l and r + /// Performs and on the second and third operands and moves the result into the first And, - /// Or dest, l, r : dest = l | r + /// Performs or on the second and third operands and moves the result into the first Or, - /// Xor dest, l, r : dest = l ^ r + /// Performs xor on the second and third operands and moves the result into the first Xor, - /// Not dest, src : dest = !src + /// Performs not on the second operands and moves the result into the first Not, - /// Negate dest, src : dest = -src + /// Moves the negated second operand into the first Negate, - /// CheckEqual dest, l, r : dest = l == r + /// Moves 1 into the first operand, if the second and third are equal CheckEqual, - /// CheckGreater dest, l, r : dest = l greater r + /// Moves 1 into the first operand, if the second is greater than the third CheckGreater, - /// CheckLess dest, l, r : dest = l less r + /// Moves 1 into the first operand, if the second is less than the third CheckLess } diff --git a/Cpp2IL.Core/IlGenerator.cs b/Cpp2IL.Core/IlGenerator.cs index 1f9fc79b8..1db6a83a9 100644 --- a/Cpp2IL.Core/IlGenerator.cs +++ b/Cpp2IL.Core/IlGenerator.cs @@ -42,11 +42,26 @@ public void GenerateIl(MethodAnalysisContext context, MethodDefinition definitio var body = new CilMethodBody(definition) { - InitializeLocals = true // Without this ILSpy does: CompilerServices.Unsafe.SkipInit(out object obj); + InitializeLocals = true, // Without this ILSpy does: CompilerServices.Unsafe.SkipInit(out object obj); + ComputeMaxStackOnBuild = false }; definition.CilMethodBody = body; + 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 (local != null && !context.Locals.Contains(local)) + context.Locals.Add(local); + } + // Map ISIL locals to IL _locals.Clear(); foreach (var local in context.Locals) @@ -91,8 +106,16 @@ private void GenerateInstructions(Instruction instruction, MethodAnalysisContext break; case OpCode.Move: - instructions.Add(CilOpCodes.Ldstr, instruction.ToString()); - instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); + if (instruction.Operands[0] is FieldReference field) // stfld takes instance before value so LoadOperand StoreToOperand doesn't work + { + instructions.Add(CilOpCodes.Ldloc, _locals[field.Local]); + LoadOperand(instruction.Operands[1], body); + instructions.Add(CilOpCodes.Stfld, field.Field.ToFieldDescriptor(_module!)); + break; + } + + LoadOperand(instruction.Operands[1], body); + StoreToOperand(instruction.Operands[0], body); break; case OpCode.Phi: @@ -102,8 +125,44 @@ private void GenerateInstructions(Instruction instruction, MethodAnalysisContext 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 method = _importer!.ImportMethod(targetMethod.ToMethodDescriptor(_module!)); + var resolvedMethod = method.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], body); + 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, body); + + instructions.Add(CilOpCodes.Call, method); + + if (instruction.OpCode == OpCode.Call) // Store return value + StoreToOperand(instruction.Operands[1], body); + + break; + case OpCode.IndirectCall: - instructions.Add(CilOpCodes.Ldstr, instruction.ToString()); + instructions.Add(CilOpCodes.Ldstr, $"Indirect calls should have been resolved before IL gen ({instruction})"); instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); break; @@ -172,7 +231,26 @@ private void LoadOperand(object operand, CilMethodBody body) instructions.Add(CilOpCodes.Ldfld, field.Field.ToFieldDescriptor(_module!)); break; default: - instructions.Add(CilOpCodes.Ldstr, operand.ToString()); + instructions.Add(CilOpCodes.Ldstr, operand.ToString() ?? "[null operand]"); + break; + } + } + + private void StoreToOperand(object operand, CilMethodBody body) + { + var instructions = body.Instructions; + + switch (operand) + { + case LocalVariable local: + instructions.Add(CilOpCodes.Stloc, _locals[local]); + break; + case FieldReference field: + instructions.Add(CilOpCodes.Stfld, field.Field.ToFieldDescriptor(_module!)); + break; + default: + instructions.Add(CilOpCodes.Ldstr, $"Store into unknown operand: {operand}"); + instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); break; } } From 69a92cdb6b70396cf3eee58d6390e658c01a0c4e Mon Sep 17 00:00:00 2001 From: itsmilos Date: Fri, 8 Aug 2025 20:38:59 +0300 Subject: [PATCH 16/22] Use ldarg when locals are args, resolve getters by name when offset doesn't work --- Cpp2IL.Core/Actions/ResolveGetters.cs | 37 +++++++++++ Cpp2IL.Core/IlGenerator.cs | 61 +++++++++++++------ .../Model/Contexts/MethodAnalysisContext.cs | 3 +- 3 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 Cpp2IL.Core/Actions/ResolveGetters.cs diff --git a/Cpp2IL.Core/Actions/ResolveGetters.cs b/Cpp2IL.Core/Actions/ResolveGetters.cs new file mode 100644 index 000000000..e1aa29b30 --- /dev/null +++ b/Cpp2IL.Core/Actions/ResolveGetters.cs @@ -0,0 +1,37 @@ +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.Actions; + +public class ResolveGetters : IAction // Because of il2cpp fields [local @ reg+offset] sometimes can't be resolved +{ + public void Apply(MethodAnalysisContext method) + { + var isGetter = method.Name.StartsWith("get_"); + var instructions = method.ControlFlowGraph!.Instructions; + + if (!isGetter) + return; + + // Default get: Return [this @ reg+offset] + 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); // TODO: Check the offset while ignoring all il2cpp fields + if (field == null) + return; + + instr.Operands[0] = new FieldReference(field, local, (int)memory.Addend); + } + } +} diff --git a/Cpp2IL.Core/IlGenerator.cs b/Cpp2IL.Core/IlGenerator.cs index 1db6a83a9..89b67d4d7 100644 --- a/Cpp2IL.Core/IlGenerator.cs +++ b/Cpp2IL.Core/IlGenerator.cs @@ -43,7 +43,7 @@ public void GenerateIl(MethodAnalysisContext context, MethodDefinition definitio var body = new CilMethodBody(definition) { InitializeLocals = true, // Without this ILSpy does: CompilerServices.Unsafe.SkipInit(out object obj); - ComputeMaxStackOnBuild = false + ComputeMaxStackOnBuild = false // There's stack imbalance somewhere, but this works for now }; definition.CilMethodBody = body; @@ -79,13 +79,22 @@ public void GenerateIl(MethodAnalysisContext context, MethodDefinition definitio _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 foreach (var instruction in context.ControlFlowGraph!.Instructions) // context.ConvertedIsil is probably not up to date anymore here - GenerateInstructions(instruction, context, body); + GenerateInstructions(instruction, context, definition); } - private void GenerateInstructions(Instruction instruction, MethodAnalysisContext context, CilMethodBody body) + private void GenerateInstructions(Instruction instruction, MethodAnalysisContext context, MethodDefinition method) { + var body = method.CilMethodBody!; var instructions = body.Instructions; switch (instruction.OpCode) @@ -108,14 +117,21 @@ private void GenerateInstructions(Instruction instruction, MethodAnalysisContext case OpCode.Move: if (instruction.Operands[0] is FieldReference field) // stfld takes instance before value so LoadOperand StoreToOperand doesn't work { - instructions.Add(CilOpCodes.Ldloc, _locals[field.Local]); - LoadOperand(instruction.Operands[1], body); + 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); instructions.Add(CilOpCodes.Stfld, field.Field.ToFieldDescriptor(_module!)); break; } - LoadOperand(instruction.Operands[1], body); - StoreToOperand(instruction.Operands[0], body); + LoadOperand(instruction.Operands[1], method); + StoreToOperand(instruction.Operands[0], method); break; case OpCode.Phi: @@ -136,15 +152,15 @@ private void GenerateInstructions(Instruction instruction, MethodAnalysisContext break; } - var method = _importer!.ImportMethod(targetMethod.ToMethodDescriptor(_module!)); - var resolvedMethod = method.Resolve()!; + 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], body); + LoadOperand(instruction.Operands[thisParamIndex], method); else instructions.Add(CilOpCodes.Ldstr, $"Non static method called without 'this' param ({instruction})"); } @@ -152,12 +168,12 @@ private void GenerateInstructions(Instruction instruction, MethodAnalysisContext // Load normal params var callParams = instruction.Operands.Skip(thisParamIndex + (resolvedMethod.IsStatic ? 0 : -1)); foreach (var param in callParams) - LoadOperand(param, body); + LoadOperand(param, method); - instructions.Add(CilOpCodes.Call, method); + instructions.Add(CilOpCodes.Call, importedMethod); if (instruction.OpCode == OpCode.Call) // Store return value - StoreToOperand(instruction.Operands[1], body); + StoreToOperand(instruction.Operands[1], method); break; @@ -168,7 +184,7 @@ private void GenerateInstructions(Instruction instruction, MethodAnalysisContext case OpCode.Return: if (!context.IsVoid && instruction.Operands.Count == 1) - LoadOperand(instruction.Operands[0], body); + LoadOperand(instruction.Operands[0], method); instructions.Add(CilOpCodes.Ret); break; @@ -201,9 +217,9 @@ private void GenerateInstructions(Instruction instruction, MethodAnalysisContext } } - private void LoadOperand(object operand, CilMethodBody body) + private void LoadOperand(object operand, MethodDefinition method) { - var instructions = body.Instructions; + var instructions = method.CilMethodBody!.Instructions; switch (operand) { @@ -223,7 +239,11 @@ private void LoadOperand(object operand, CilMethodBody body) instructions.Add(CilOpCodes.Ldstr, s); break; case LocalVariable local: - instructions.Add(CilOpCodes.Ldloc, _locals[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 @@ -236,18 +256,21 @@ private void LoadOperand(object operand, CilMethodBody body) } } - private void StoreToOperand(object operand, CilMethodBody body) + private void StoreToOperand(object operand, MethodDefinition method) { - var instructions = body.Instructions; + var instructions = method.CilMethodBody!.Instructions; 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; + default: instructions.Add(CilOpCodes.Ldstr, $"Store into unknown operand: {operand}"); instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index 0eb2e7bac..3e17f6fe1 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -238,7 +238,8 @@ IEnumerable GetOverriddenMethods(Il2CppTypeDefinition dec new Inlining(), new PropagateTypes() { MaxLoopCount = 5000 }, new ResolveFieldOffsets(), - new RemoveUnusedLocals() + new RemoveUnusedLocals(), + new ResolveGetters() ]; public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext) From 1f2d33c3f56f0a7736a6625414359a3f4f7e8804 Mon Sep 17 00:00:00 2001 From: itsmilos Date: Fri, 15 Aug 2025 21:15:24 +0300 Subject: [PATCH 17/22] Generate IL for all ISIL instructions --- Cpp2IL.Core/ISIL/OpCode.cs | 52 ++++---- Cpp2IL.Core/IlGenerator.cs | 124 ++++++++++++++++-- .../Model/Contexts/MethodAnalysisContext.cs | 5 +- 3 files changed, 141 insertions(+), 40 deletions(-) diff --git a/Cpp2IL.Core/ISIL/OpCode.cs b/Cpp2IL.Core/ISIL/OpCode.cs index cb6811aa0..4a18c3098 100644 --- a/Cpp2IL.Core/ISIL/OpCode.cs +++ b/Cpp2IL.Core/ISIL/OpCode.cs @@ -5,10 +5,10 @@ /// public enum OpCode { - /// Logs to console (in the decompiled code) that there was an invalid instruction, first operand is the instruction string + /// Invalid instruction, op 1 is the debug string Invalid, - /// Logs to console (in the decompiled code) that there was a not implemented instruction, first operand is the instruction string + /// Not implemented instruction, op 1 is the debug string NotImplemented, /// @@ -21,75 +21,75 @@ public enum OpCode /// Nop, - /// Moves the second operand into the first + /// Moves op 2 into op 1 Move, - /// Moves the result of phi function into first operand, other operands are inputs + /// Moves the result of phi function into op 1, other operands are inputs Phi, - /// Calls a method (first operand), moves the result into second, and the rest are params + /// Calls a method @ op 1, moves the result into op 2, and the rest are params Call, - /// Calls a method (first operand), rest are params + /// Calls a method @ op 1, the rest are params CallVoid, - /// Calls a method (first operand), rest are params + /// Calls a method @ op 1, the rest are params IndirectCall, - /// Returns from the method, the return operand is optional + /// Returns from the method, op 1 is the value to return (optional) Return, - /// Jumps to the first operand + /// Jumps to op 1 Jump, - /// Jumps to the first operand + /// Jumps to op 1 IndirectJump, - /// If the second operand is true, jumps to the first + /// If op 2 is true, jumps to op 1 ConditionalJump, - /// Adds the first operand to stack pointer + /// Adds op 1 to stack pointer ShiftStack, - /// Adds the second and third operands and moves the result into the first + /// Adds op 2 and op 3, and moves the result into op 1 Add, - /// Subtracts the third operand from the second and moves the result into the first + /// Subtracts op 3 from op 2, and moves the result into op 1 Subtract, - /// Multiplies the second operand by the third and moves the result into the first + /// Multiplies op 2 by op 3, and moves the result into op 1 Multiply, - /// Divides the second operand by the third and moves the result into the first + /// Divides op 2 by op 3, and moves the result into op 1 Divide, - /// Shifts the bits of the second operand left by the third and moves the result into the first + /// Shifts the bits of op 2 left by op 3, and moves the result into op 1 ShiftLeft, - /// Shifts the bits of the second operand right by the third and moves the result into the first + /// Shifts the bits of op 2 right by op 3, and moves the result into op 1 ShiftRight, - /// Performs and on the second and third operands and moves the result into the first + /// Bitwise AND on op 2 and op 3, moves the result into op 1 And, - /// Performs or on the second and third operands and moves the result into the first + /// Bitwise OR on op 2 and op 3, moves the result into op 1 Or, - /// Performs xor on the second and third operands and moves the result into the first + /// Bitwise XOR on op 2 and op 3, moves the result into op 1 Xor, - /// Performs not on the second operands and moves the result into the first + /// Logical not on op 2, moves the result into op 1 Not, - /// Moves the negated second operand into the first + /// Negates op 2, moves the result into op 1 Negate, - /// Moves 1 into the first operand, if the second and third are equal + /// Moves 1 into op 1, if op 2 and op 3 are equal CheckEqual, - /// Moves 1 into the first operand, if the second is greater than the third + /// Moves 1 into op 1, if op 2 is greater than op 3 CheckGreater, - /// Moves 1 into the first operand, if the second is less than the third + /// Moves 1 into op 1, if op 2 is less than op 3 CheckLess } diff --git a/Cpp2IL.Core/IlGenerator.cs b/Cpp2IL.Core/IlGenerator.cs index 89b67d4d7..86f7ba728 100644 --- a/Cpp2IL.Core/IlGenerator.cs +++ b/Cpp2IL.Core/IlGenerator.cs @@ -18,6 +18,7 @@ public class Ilgenerator private ReferenceImporter? _importer; private CorLibTypeFactory? _factory; private MemberReference? _writeLine; + private MemberReference? _stringCtor; public void GenerateIl(MethodAnalysisContext context, MethodDefinition definition) { @@ -40,6 +41,11 @@ public void GenerateIl(MethodAnalysisContext context, MethodDefinition definitio .CreateMemberReference("WriteLine", MethodSignature.CreateStatic(_factory.Void, _factory.String)) .ImportWith(_importer); + var stringType = _factory.CorLibScope.CreateTypeReference("System", "String"); + _stringCtor = stringType + .CreateMemberReference(".ctor", MethodSignature.CreateStatic(stringType.ToTypeSignature(), _factory.String)) + .ImportWith(_importer); + var body = new CilMethodBody(definition) { InitializeLocals = true, // Without this ILSpy does: CompilerServices.Unsafe.SkipInit(out object obj); @@ -88,14 +94,57 @@ public void GenerateIl(MethodAnalysisContext context, MethodDefinition definitio 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 - GenerateInstructions(instruction, context, definition); + instructionMap.Add(instruction, GenerateInstructions(instruction, context, definition)); + + // 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 void GenerateInstructions(Instruction instruction, MethodAnalysisContext context, MethodDefinition method) + private List GenerateInstructions(Instruction instruction, MethodAnalysisContext context, MethodDefinition method) { var body = method.CilMethodBody!; var instructions = body.Instructions; + var currentCount = instructions.Count; + var startIndex = instructions.Count; switch (instruction.OpCode) { @@ -105,7 +154,7 @@ private void GenerateInstructions(Instruction instruction, MethodAnalysisContext break; case OpCode.NotImplemented: - instructions.Add(CilOpCodes.Ldstr, $"Not implemented instruction: {instruction}"); + instructions.Add(CilOpCodes.Ldstr, $"Not implemented instruction: {instruction.Operands[0]}"); instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); break; @@ -178,7 +227,7 @@ private void GenerateInstructions(Instruction instruction, MethodAnalysisContext break; case OpCode.IndirectCall: - instructions.Add(CilOpCodes.Ldstr, $"Indirect calls should have been resolved before IL gen ({instruction})"); + instructions.Add(CilOpCodes.Ldstr, $"Indirect call: {instruction} (should have been resolved before IL gen)"); instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); break; @@ -189,25 +238,75 @@ private void GenerateInstructions(Instruction instruction, MethodAnalysisContext break; case OpCode.Jump: - case OpCode.IndirectJump: + instructions.Add(CilOpCodes.Br, new CilInstructionLabel()); + break; + case OpCode.ConditionalJump: + LoadOperand(instruction.Operands[1], method); + 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); + LoadOperand(instruction.Operands[2], method); + + 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); + break; + case OpCode.Not: case OpCode.Negate: - case OpCode.CheckEqual: - case OpCode.CheckGreater: - case OpCode.CheckLess: - instructions.Add(CilOpCodes.Ldstr, instruction.ToString()); - instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); + LoadOperand(instruction.Operands[1], method); + + switch (instruction.OpCode) + { + case OpCode.Not: instructions.Add(CilOpCodes.Not); break; + case OpCode.Negate: instructions.Add(CilOpCodes.Neg); break; + } + + StoreToOperand(instruction.Operands[0], method); break; default: @@ -215,6 +314,8 @@ private void GenerateInstructions(Instruction instruction, MethodAnalysisContext instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); break; } + + return instructions.ToList().GetRange(startIndex, instructions.Count - startIndex); // Return added IL } private void LoadOperand(object operand, MethodDefinition method) @@ -251,7 +352,8 @@ private void LoadOperand(object operand, MethodDefinition method) instructions.Add(CilOpCodes.Ldfld, field.Field.ToFieldDescriptor(_module!)); break; default: - instructions.Add(CilOpCodes.Ldstr, operand.ToString() ?? "[null operand]"); + instructions.Add(CilOpCodes.Ldstr, "Unknown operand: " + operand.ToString()); + instructions.Add(CilOpCodes.Newobj, _importer!.ImportMethod(_stringCtor!)); break; } } diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index 3e17f6fe1..fd6f6fff7 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -70,7 +70,7 @@ public class MethodAnalysisContext : HasGenericParameters, IMethodInfoProvider public List AnalysisWarnings = []; - private const int MaxMethodSizeBytes = 256000; // 256KB + private const int MaxMethodSizeBytes = 18000; // 18KB public List Parameters = []; @@ -323,8 +323,7 @@ public void Analyze() action.Apply(this); } - public void AddWarning(string warning) => AnalysisWarnings.Add($"warning: {warning}"); - public void AddError(string error) => AnalysisWarnings.Add($"error: {error}"); + public void AddWarning(string warning) => AnalysisWarnings.Add(warning); public void ReleaseAnalysisData() { From 1f2d9c2d971dbd83619311c4a9485c3f3719b1bb Mon Sep 17 00:00:00 2001 From: itsmilos Date: Sun, 17 Aug 2025 10:36:30 +0300 Subject: [PATCH 18/22] Update readme, and some small fixes --- Cpp2IL.Core/DecompilerException.cs | 2 ++ Cpp2IL.Core/IlGenerator.cs | 47 ++++++++++++++++++++++++++++++ README.md | 12 ++++---- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/Cpp2IL.Core/DecompilerException.cs b/Cpp2IL.Core/DecompilerException.cs index fc2a9f9ba..b503633f2 100644 --- a/Cpp2IL.Core/DecompilerException.cs +++ b/Cpp2IL.Core/DecompilerException.cs @@ -1,3 +1,5 @@ +namespace Cpp2IL.Core; + public class DecompilerException : System.Exception { public DecompilerException() { } diff --git a/Cpp2IL.Core/IlGenerator.cs b/Cpp2IL.Core/IlGenerator.cs index 86f7ba728..8e7786dae 100644 --- a/Cpp2IL.Core/IlGenerator.cs +++ b/Cpp2IL.Core/IlGenerator.cs @@ -64,6 +64,9 @@ public void GenerateIl(MethodAnalysisContext context, MethodDefinition definitio 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); } @@ -327,6 +330,27 @@ private void LoadOperand(object operand, MethodDefinition method) 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; @@ -351,6 +375,20 @@ private void LoadOperand(object operand, MethodDefinition method) //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; default: instructions.Add(CilOpCodes.Ldstr, "Unknown operand: " + operand.ToString()); instructions.Add(CilOpCodes.Newobj, _importer!.ImportMethod(_stringCtor!)); @@ -373,6 +411,15 @@ private void StoreToOperand(object operand, MethodDefinition method) 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!)); diff --git a/README.md b/README.md index 0ceb6e5ef..cb1076905 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ link above will take you to the documentation for LibCpp2IL. ### 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 +27,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 +42,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 +56,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 +70,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. | From 58b2792e486855d6cd9027e85d8c6ccc3d57660f Mon Sep 17 00:00:00 2001 From: itsmilos Date: Tue, 19 Aug 2025 21:39:38 +0300 Subject: [PATCH 19/22] Update readme --- Cpp2IL.Core/IlGenerator.cs | 18 ++++++++++++++++++ README.md | 8 ++++++++ 2 files changed, 26 insertions(+) diff --git a/Cpp2IL.Core/IlGenerator.cs b/Cpp2IL.Core/IlGenerator.cs index 8e7786dae..02b5e9f6f 100644 --- a/Cpp2IL.Core/IlGenerator.cs +++ b/Cpp2IL.Core/IlGenerator.cs @@ -389,6 +389,24 @@ private void LoadOperand(object operand, MethodDefinition method) instructions.Add(CilOpCodes.Ldstr, "Unmanaged memory load: " + operand.ToString()); instructions.Add(CilOpCodes.Newobj, _importer!.ImportMethod(_stringCtor!)); break; + case TypeAnalysisContext type: + 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!)); diff --git a/README.md b/README.md index cb1076905..98902c274 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,14 @@ 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#, 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 has undergone a major rewrite. This branch represents work in progress, and is subject to change. From 0b7f56d668c9ab2d7f8e7aa22bf2035b3dce5b4f Mon Sep 17 00:00:00 2001 From: itsmilos Date: Tue, 19 Aug 2025 22:20:28 +0300 Subject: [PATCH 20/22] Update readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 98902c274..37d8d61b8 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ build artifacts if you want to do something yourself with IL2CPP metadata, and i 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#, but most of the times it's at least possible to see what the method does. +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`, From 42542dde22e31da517b199e122b4693d02eea06d Mon Sep 17 00:00:00 2001 From: itsmilos Date: Wed, 20 Aug 2025 17:16:11 +0300 Subject: [PATCH 21/22] Fix graph tests --- Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs b/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs index d0eef327f..144a88a8e 100644 --- a/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs +++ b/Cpp2IL.Core.Tests/Graphing/ExceptionThrowingGraph.cs @@ -25,7 +25,7 @@ public void Setup() 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, 39, new Register(null, "zf")); + 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); @@ -83,7 +83,7 @@ public void Setup() [Test] public void VerifyNumberOfBlocks() { - Assert.That(graph.Blocks.Count == 18); + Assert.That(graph.Blocks.Count == 19); } [Test] From b82763a24a0a54a8a419c24311d913e491818a7e Mon Sep 17 00:00:00 2001 From: itsmilos Date: Mon, 25 Aug 2025 20:35:19 +0300 Subject: [PATCH 22/22] Fix analysis thread safety --- Cpp2IL.Core/Actions/ApplyMetadata.cs | 42 --- Cpp2IL.Core/Actions/CreateLocals.cs | 165 --------- Cpp2IL.Core/Actions/IAction.cs | 8 - Cpp2IL.Core/Actions/PropagateTypes.cs | 156 -------- Cpp2IL.Core/Actions/RemoveSsaForm.cs | 68 ---- Cpp2IL.Core/Actions/RemoveUnusedLocals.cs | 24 -- Cpp2IL.Core/Actions/ResolveCalls.cs | 76 ---- Cpp2IL.Core/Actions/ResolveFieldOffsets.cs | 36 -- Cpp2IL.Core/Actions/ResolveGetters.cs | 37 -- Cpp2IL.Core/Analysis/LocalVariables.cs | 337 ++++++++++++++++++ Cpp2IL.Core/Analysis/MetadataResolver.cs | 176 +++++++++ .../Inlining.cs => Analysis/Simplifier.cs} | 6 +- .../BuildSsaForm.cs => Analysis/SsaForm.cs} | 77 +++- .../{Actions => Analysis}/StackAnalyzer.cs | 21 +- Cpp2IL.Core/IlGenerator.cs | 151 ++++---- .../InstructionSets/X86InstructionSet.cs | 7 + .../Model/Contexts/MethodAnalysisContext.cs | 35 +- .../AsmResolverDllOutputFormatIlRecovery.cs | 10 +- 18 files changed, 707 insertions(+), 725 deletions(-) delete mode 100644 Cpp2IL.Core/Actions/ApplyMetadata.cs delete mode 100644 Cpp2IL.Core/Actions/CreateLocals.cs delete mode 100644 Cpp2IL.Core/Actions/IAction.cs delete mode 100644 Cpp2IL.Core/Actions/PropagateTypes.cs delete mode 100644 Cpp2IL.Core/Actions/RemoveSsaForm.cs delete mode 100644 Cpp2IL.Core/Actions/RemoveUnusedLocals.cs delete mode 100644 Cpp2IL.Core/Actions/ResolveCalls.cs delete mode 100644 Cpp2IL.Core/Actions/ResolveFieldOffsets.cs delete mode 100644 Cpp2IL.Core/Actions/ResolveGetters.cs create mode 100644 Cpp2IL.Core/Analysis/LocalVariables.cs create mode 100644 Cpp2IL.Core/Analysis/MetadataResolver.cs rename Cpp2IL.Core/{Actions/Inlining.cs => Analysis/Simplifier.cs} (98%) rename Cpp2IL.Core/{Actions/BuildSsaForm.cs => Analysis/SsaForm.cs} (78%) rename Cpp2IL.Core/{Actions => Analysis}/StackAnalyzer.cs (91%) diff --git a/Cpp2IL.Core/Actions/ApplyMetadata.cs b/Cpp2IL.Core/Actions/ApplyMetadata.cs deleted file mode 100644 index 9ce8bc975..000000000 --- a/Cpp2IL.Core/Actions/ApplyMetadata.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Model.Contexts; -using Cpp2IL.Core.Utils; -using LibCpp2IL; - -namespace Cpp2IL.Core.Actions; - -public class ApplyMetadata : IAction -{ - public void Apply(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; - } - } - } -} diff --git a/Cpp2IL.Core/Actions/CreateLocals.cs b/Cpp2IL.Core/Actions/CreateLocals.cs deleted file mode 100644 index f8b0b04ee..000000000 --- a/Cpp2IL.Core/Actions/CreateLocals.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.Actions; - -public class CreateLocals : IAction -{ - public void Apply(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) - { - var thisOperand = (Register)method.ParameterOperands[0]; - var thisLocal = method.Locals.First(l => l.Register.Number == thisOperand.Number && l.Register.Version == -1); - - thisLocal.Name = "this"; - thisLocal.IsThis = true; - paramLocals.Add(thisLocal); - } - - // 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; - } - - 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; - } -} diff --git a/Cpp2IL.Core/Actions/IAction.cs b/Cpp2IL.Core/Actions/IAction.cs deleted file mode 100644 index ccb3b27f0..000000000 --- a/Cpp2IL.Core/Actions/IAction.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.Actions; - -public interface IAction -{ - public void Apply(MethodAnalysisContext method); -} diff --git a/Cpp2IL.Core/Actions/PropagateTypes.cs b/Cpp2IL.Core/Actions/PropagateTypes.cs deleted file mode 100644 index c4a995cfb..000000000 --- a/Cpp2IL.Core/Actions/PropagateTypes.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System.Linq; -using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.Actions; - -public class PropagateTypes : IAction -{ - public int MaxLoopCount = -1; - - public void Apply(MethodAnalysisContext method) - { - PropagateFromReturn(method); - PropagateFromParameters(method); - PropagateFromCallParameters(method); - PropagateThroughMoves(method); - } - - private void PropagateThroughMoves(MethodAnalysisContext method) - { - var changed = true; - var loopCount = 0; - - while (changed) - { - changed = false; - loopCount++; - - if (MaxLoopCount != -1 && loopCount > MaxLoopCount) - throw new DecompilerException($"Type propagation through moves not settling! (looped {MaxLoopCount} 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/Actions/RemoveSsaForm.cs b/Cpp2IL.Core/Actions/RemoveSsaForm.cs deleted file mode 100644 index 48f20f93b..000000000 --- a/Cpp2IL.Core/Actions/RemoveSsaForm.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.Actions; - -public class RemoveSsaForm : IAction -{ - public void Apply(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/Actions/RemoveUnusedLocals.cs b/Cpp2IL.Core/Actions/RemoveUnusedLocals.cs deleted file mode 100644 index d6f5f875f..000000000 --- a/Cpp2IL.Core/Actions/RemoveUnusedLocals.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Linq; -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.Actions; - -public class RemoveUnusedLocals : IAction -{ - public void Apply(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--; - } - } -} diff --git a/Cpp2IL.Core/Actions/ResolveCalls.cs b/Cpp2IL.Core/Actions/ResolveCalls.cs deleted file mode 100644 index 934ae633b..000000000 --- a/Cpp2IL.Core/Actions/ResolveCalls.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Linq; -using Cpp2IL.Core.Extensions; -using Cpp2IL.Core.Graphs; -using Cpp2IL.Core.Il2CppApiFunctions; -using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.Actions; - -public class ResolveCalls : IAction -{ - public void Apply(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 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; - } - } -} diff --git a/Cpp2IL.Core/Actions/ResolveFieldOffsets.cs b/Cpp2IL.Core/Actions/ResolveFieldOffsets.cs deleted file mode 100644 index 255aa1ebc..000000000 --- a/Cpp2IL.Core/Actions/ResolveFieldOffsets.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Linq; -using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.Actions; - -public class ResolveFieldOffsets : IAction -{ - public void Apply(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); - } - } - } -} diff --git a/Cpp2IL.Core/Actions/ResolveGetters.cs b/Cpp2IL.Core/Actions/ResolveGetters.cs deleted file mode 100644 index e1aa29b30..000000000 --- a/Cpp2IL.Core/Actions/ResolveGetters.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Cpp2IL.Core.ISIL; -using Cpp2IL.Core.Model.Contexts; - -namespace Cpp2IL.Core.Actions; - -public class ResolveGetters : IAction // Because of il2cpp fields [local @ reg+offset] sometimes can't be resolved -{ - public void Apply(MethodAnalysisContext method) - { - var isGetter = method.Name.StartsWith("get_"); - var instructions = method.ControlFlowGraph!.Instructions; - - if (!isGetter) - return; - - // Default get: Return [this @ reg+offset] - 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); // TODO: Check the offset while ignoring all il2cpp fields - if (field == null) - return; - - instr.Operands[0] = new FieldReference(field, local, (int)memory.Addend); - } - } -} 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/Actions/Inlining.cs b/Cpp2IL.Core/Analysis/Simplifier.cs similarity index 98% rename from Cpp2IL.Core/Actions/Inlining.cs rename to Cpp2IL.Core/Analysis/Simplifier.cs index 9ee9391f6..9ccdfda86 100644 --- a/Cpp2IL.Core/Actions/Inlining.cs +++ b/Cpp2IL.Core/Analysis/Simplifier.cs @@ -3,11 +3,11 @@ using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Model.Contexts; -namespace Cpp2IL.Core.Actions; +namespace Cpp2IL.Core.Analysis; -public class Inlining : IAction +public static class Simplifier { - public void Apply(MethodAnalysisContext method) + public static void Simplify(MethodAnalysisContext method) { var cfg = method.ControlFlowGraph!; diff --git a/Cpp2IL.Core/Actions/BuildSsaForm.cs b/Cpp2IL.Core/Analysis/SsaForm.cs similarity index 78% rename from Cpp2IL.Core/Actions/BuildSsaForm.cs rename to Cpp2IL.Core/Analysis/SsaForm.cs index f315b8cfa..388c4f8c0 100644 --- a/Cpp2IL.Core/Actions/BuildSsaForm.cs +++ b/Cpp2IL.Core/Analysis/SsaForm.cs @@ -4,27 +4,29 @@ using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Model.Contexts; -namespace Cpp2IL.Core.Actions; +namespace Cpp2IL.Core.Analysis; -public class BuildSsaForm : IAction +public class SsaForm { private Dictionary> _versions = new(); private Dictionary _versionCount = new(); private Dictionary> _blockOutVersions = new(); - public void Apply(MethodAnalysisContext method) + public static void Build(MethodAnalysisContext method) { + var ssa = new SsaForm(); + method.ControlFlowGraph!.BuildUseDefLists(); - _versions.Clear(); - _versionCount.Clear(); - _blockOutVersions.Clear(); + ssa._versions.Clear(); + ssa._versionCount.Clear(); + ssa._blockOutVersions.Clear(); var graph = method.ControlFlowGraph!; var dominatorInfo = method.DominatorInfo!; - ProcessBlock(graph.EntryBlock, dominatorInfo.DominanceTree); - InsertAllPhiFunctions(graph, dominatorInfo, method.ParameterOperands); + ssa.ProcessBlock(graph.EntryBlock, dominatorInfo.DominanceTree); + ssa.InsertAllPhiFunctions(graph, dominatorInfo, method.ParameterOperands); } private void InsertAllPhiFunctions(ISILControlFlowGraph graph, DominatorInfo dominance, List parameters) @@ -249,4 +251,63 @@ private void ProcessBlock(Block block, Dictionary> dominanceT _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/Actions/StackAnalyzer.cs b/Cpp2IL.Core/Analysis/StackAnalyzer.cs similarity index 91% rename from Cpp2IL.Core/Actions/StackAnalyzer.cs rename to Cpp2IL.Core/Analysis/StackAnalyzer.cs index 4bdaa11fa..8df9fec82 100644 --- a/Cpp2IL.Core/Actions/StackAnalyzer.cs +++ b/Cpp2IL.Core/Analysis/StackAnalyzer.cs @@ -1,13 +1,12 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using Cpp2IL.Core.Graphs; using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Model.Contexts; -namespace Cpp2IL.Core.Actions; +namespace Cpp2IL.Core.Analysis; -public class StackAnalyzer : IAction +public class StackAnalyzer { [DebuggerDisplay("Size = {Size}")] private class StackState @@ -23,27 +22,27 @@ private class StackState /// /// Max allowed count of blocks to visit (-1 for no limit). /// - public int MaxBlockVisitCount = -1; + public static int MaxBlockVisitCount = 2000; - public void Apply(MethodAnalysisContext method) + 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 - _inComingState = new Dictionary { { graph.EntryBlock, new StackState() } }; - _outGoingState.Clear(); - _instructionState.Clear(); + analyzer._inComingState = new Dictionary { { graph.EntryBlock, new StackState() } }; - TraverseGraph(graph.EntryBlock); + analyzer.TraverseGraph(graph.EntryBlock); - var outDelta = _outGoingState[graph.ExitBlock]; + 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!"); } - CorrectOffsets(graph); + analyzer.CorrectOffsets(graph); ReplaceStackWithRegisters(method); graph.RemoveNops(); diff --git a/Cpp2IL.Core/IlGenerator.cs b/Cpp2IL.Core/IlGenerator.cs index 02b5e9f6f..28f6dfbbf 100644 --- a/Cpp2IL.Core/IlGenerator.cs +++ b/Cpp2IL.Core/IlGenerator.cs @@ -11,17 +11,25 @@ namespace Cpp2IL.Core; -public class Ilgenerator +public static class IlGenerator { - private Dictionary _locals = []; - private ModuleDefinition? _module; - private ReferenceImporter? _importer; - private CorLibTypeFactory? _factory; - private MemberReference? _writeLine; - private MemberReference? _stringCtor; - - public void GenerateIl(MethodAnalysisContext context, MethodDefinition definition) + 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)) { @@ -32,20 +40,6 @@ public void GenerateIl(MethodAnalysisContext context, MethodDefinition definitio } } - _module = definition.Module!; - _importer = _module.DefaultImporter; - _factory = _module.CorLibTypeFactory; - - _writeLine = _factory.CorLibScope - .CreateTypeReference("System", "Console") - .CreateMemberReference("WriteLine", MethodSignature.CreateStatic(_factory.Void, _factory.String)) - .ImportWith(_importer); - - var stringType = _factory.CorLibScope.CreateTypeReference("System", "String"); - _stringCtor = stringType - .CreateMemberReference(".ctor", MethodSignature.CreateStatic(stringType.ToTypeSignature(), _factory.String)) - .ImportWith(_importer); - var body = new CilMethodBody(definition) { InitializeLocals = true, // Without this ILSpy does: CompilerServices.Unsafe.SkipInit(out object obj); @@ -54,6 +48,7 @@ public void GenerateIl(MethodAnalysisContext context, MethodDefinition definitio 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; @@ -72,20 +67,20 @@ public void GenerateIl(MethodAnalysisContext context, MethodDefinition definitio } // Map ISIL locals to IL - _locals.Clear(); + 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); + ilType = local.Type.ToTypeSignature(module); else - ilType = _module.CorLibTypeFactory.Object; + ilType = module.CorLibTypeFactory.Object; var ilLocal = new CilLocalVariable(ilType); body.LocalVariables.Add(ilLocal); - _locals.Add(local, ilLocal); + locals.Add(local, ilLocal); } /* foreach (var instruction in context.ControlFlowGraph!.Instructions) @@ -99,7 +94,7 @@ public void GenerateIl(MethodAnalysisContext context, MethodDefinition definitio // 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)); + instructionMap.Add(instruction, GenerateInstructions(instruction, context, definition, locals, writeLine, stringCtor)); // Set IL branch targets foreach (var kvp in instructionMap) @@ -138,27 +133,31 @@ public void GenerateIl(MethodAnalysisContext context, MethodDefinition definitio foreach (var warning in context.AnalysisWarnings) { instructions.Add(CilOpCodes.Ldstr, "Warning: " + warning); - instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); } } - private List GenerateInstructions(Instruction instruction, MethodAnalysisContext context, MethodDefinition method) + 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!)); + 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!)); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); break; case OpCode.Interrupt: @@ -175,20 +174,20 @@ private List GenerateInstructions(Instruction instruction, Metho else if (field.Local.IsThis) instructions.Add(CilOpCodes.Ldarg_0); else - instructions.Add(CilOpCodes.Ldloc, _locals[field.Local]); + instructions.Add(CilOpCodes.Ldloc, locals[field.Local]); - LoadOperand(instruction.Operands[1], method); - instructions.Add(CilOpCodes.Stfld, field.Field.ToFieldDescriptor(_module!)); + LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor); + instructions.Add(CilOpCodes.Stfld, field.Field.ToFieldDescriptor(module)); break; } - LoadOperand(instruction.Operands[1], method); - StoreToOperand(instruction.Operands[0], method); + 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!)); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); break; case OpCode.Call: @@ -200,11 +199,11 @@ private List GenerateInstructions(Instruction instruction, Metho else // Probably key function instructions.Add(CilOpCodes.Ldstr, $"Unknown call target operand: {instruction}"); - instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); break; } - var importedMethod = _importer!.ImportMethod(targetMethod.ToMethodDescriptor(_module!)); + var importedMethod = importer.ImportMethod(targetMethod.ToMethodDescriptor(module)); var resolvedMethod = importedMethod.Resolve()!; var thisParamIndex = instruction.OpCode == OpCode.Call ? 2 : 1; @@ -212,7 +211,7 @@ private List GenerateInstructions(Instruction instruction, Metho if (!resolvedMethod.IsStatic) // Load 'this' param { if ((instruction.Operands.Count - 1) >= thisParamIndex) - LoadOperand(instruction.Operands[thisParamIndex], method); + LoadOperand(instruction.Operands[thisParamIndex], method, locals, writeLine, stringCtor); else instructions.Add(CilOpCodes.Ldstr, $"Non static method called without 'this' param ({instruction})"); } @@ -220,23 +219,23 @@ private List GenerateInstructions(Instruction instruction, Metho // Load normal params var callParams = instruction.Operands.Skip(thisParamIndex + (resolvedMethod.IsStatic ? 0 : -1)); foreach (var param in callParams) - LoadOperand(param, method); + LoadOperand(param, method, locals, writeLine, stringCtor); instructions.Add(CilOpCodes.Call, importedMethod); if (instruction.OpCode == OpCode.Call) // Store return value - StoreToOperand(instruction.Operands[1], method); + 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!)); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); break; case OpCode.Return: if (!context.IsVoid && instruction.Operands.Count == 1) - LoadOperand(instruction.Operands[0], method); + LoadOperand(instruction.Operands[0], method, locals, writeLine, stringCtor); instructions.Add(CilOpCodes.Ret); break; @@ -245,18 +244,18 @@ private List GenerateInstructions(Instruction instruction, Metho break; case OpCode.ConditionalJump: - LoadOperand(instruction.Operands[1], method); + 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!)); + 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!)); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); break; case OpCode.CheckEqual: @@ -274,8 +273,8 @@ private List GenerateInstructions(Instruction instruction, Metho case OpCode.And: case OpCode.Or: case OpCode.Xor: - LoadOperand(instruction.Operands[1], method); - LoadOperand(instruction.Operands[2], method); + LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor); + LoadOperand(instruction.Operands[2], method, locals, writeLine, stringCtor); switch (instruction.OpCode) { @@ -296,12 +295,12 @@ private List GenerateInstructions(Instruction instruction, Metho case OpCode.Xor: instructions.Add(CilOpCodes.Xor); break; } - StoreToOperand(instruction.Operands[0], method); + StoreToOperand(instruction.Operands[0], method, locals, writeLine); break; case OpCode.Not: case OpCode.Negate: - LoadOperand(instruction.Operands[1], method); + LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor); switch (instruction.OpCode) { @@ -309,22 +308,26 @@ private List GenerateInstructions(Instruction instruction, Metho case OpCode.Negate: instructions.Add(CilOpCodes.Neg); break; } - StoreToOperand(instruction.Operands[0], method); + StoreToOperand(instruction.Operands[0], method, locals, writeLine); break; default: instructions.Add(CilOpCodes.Ldstr, $"Unknown instruction: {instruction}"); - instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); break; } return instructions.ToList().GetRange(startIndex, instructions.Count - startIndex); // Return added IL } - private void LoadOperand(object operand, MethodDefinition method) + 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: @@ -368,12 +371,12 @@ private void LoadOperand(object operand, MethodDefinition method) if (param != null) instructions.Add(CilOpCodes.Ldarg, param); else - instructions.Add(CilOpCodes.Ldloc, _locals[local]); + 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!)); + instructions.Add(CilOpCodes.Ldfld, field.Field.ToFieldDescriptor(module)); break; case MemoryOperand memory: if (memory.Index == null && memory.Addend == 0 && memory.Scale == 0 @@ -383,14 +386,22 @@ private void LoadOperand(object operand, MethodDefinition method) if (param2 != null) instructions.Add(CilOpCodes.Ldarg, param2); else - instructions.Add(CilOpCodes.Ldloc, _locals[local2]); + instructions.Add(CilOpCodes.Ldloc, locals[local2]); break; } instructions.Add(CilOpCodes.Ldstr, "Unmanaged memory load: " + operand.ToString()); - instructions.Add(CilOpCodes.Newobj, _importer!.ImportMethod(_stringCtor!)); + instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(stringCtor)); break; case TypeAnalysisContext type: - var cilType = type.ToTypeSignature(_module!).Resolve()!; + 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"); @@ -399,34 +410,38 @@ private void LoadOperand(object operand, MethodDefinition method) if (constructor == null) { instructions.Add(CilOpCodes.Ldstr, $"Constructor not found for: {operand} (probably static type)"); - instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); + 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)); + instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(constructor)); break; default: instructions.Add(CilOpCodes.Ldstr, "Unknown operand: " + operand.ToString()); - instructions.Add(CilOpCodes.Newobj, _importer!.ImportMethod(_stringCtor!)); + instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(stringCtor)); break; } } - private void StoreToOperand(object operand, MethodDefinition method) + 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]); + instructions.Add(CilOpCodes.Stloc, locals[local]); break; case FieldReference field: instructions.Add(CilOpCodes.Ldarg_0); - instructions.Add(CilOpCodes.Stfld, field.Field.ToFieldDescriptor(_module!)); + instructions.Add(CilOpCodes.Stfld, field.Field.ToFieldDescriptor(module)); break; case MemoryOperand memory: @@ -434,13 +449,13 @@ private void StoreToOperand(object operand, MethodDefinition method) && memory.Base is LocalVariable local2) { // Can pointer assignments just be ignored because it's C#? (Move [local], 123) - instructions.Add(CilOpCodes.Stloc, _locals[local2]); + instructions.Add(CilOpCodes.Stloc, locals[local2]); } break; default: instructions.Add(CilOpCodes.Ldstr, $"Store into unknown operand: {operand}"); - instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); + instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine)); break; } } diff --git a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs index 6671d8d1e..68fc561a2 100644 --- a/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs @@ -827,6 +827,13 @@ private object ConvertOperand(Instruction instruction, int operand, bool isLeaAd 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) { diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index 42c7c5aa8..6c716a0bb 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; -using Cpp2IL.Core.Actions; +using Cpp2IL.Core.Analysis; using Cpp2IL.Core.Graphs; using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Logging; @@ -228,22 +228,6 @@ IEnumerable GetOverriddenMethods(Il2CppTypeDefinition dec } } - private static readonly List analysisActions = - [ - // Indirect jumps/calls should probably be resolved here before stack analysis - new StackAnalyzer() { MaxBlockVisitCount = 5000 }, - new BuildSsaForm(), - new CreateLocals(), - new ResolveCalls(), - new ApplyMetadata(), - new RemoveSsaForm(), - new Inlining(), - new PropagateTypes() { MaxLoopCount = 5000 }, - new ResolveFieldOffsets(), - new RemoveUnusedLocals(), - new ResolveGetters() - ]; - public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext) { DeclaringType = parent; @@ -321,8 +305,21 @@ public void Analyze() ControlFlowGraph = new ISILControlFlowGraph(ConvertedIsil); DominatorInfo = new DominatorInfo(ControlFlowGraph); - foreach (var action in analysisActions) - action.Apply(this); + // 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); diff --git a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs index 27435bf07..b84a46ae2 100644 --- a/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs +++ b/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormatIlRecovery.cs @@ -21,7 +21,6 @@ public class AsmResolverDllOutputFormatIlRecovery : AsmResolverDllOutputFormat public override string OutputFormatName => "DLL files with IL Recovery"; private MethodDefinition? _exceptionConstructor; - private Ilgenerator _ilGenerator = new(); protected override void FillMethodBody(MethodDefinition methodDefinition, MethodAnalysisContext methodContext) { @@ -43,8 +42,7 @@ protected override void FillMethodBody(MethodDefinition methodDefinition, Method if (shouldSkip) { - instructions.Add(CilOpCodes.Ldnull); - instructions.Add(CilOpCodes.Throw); + methodDefinition.ReplaceMethodBodyWithMinimalImplementation(); return; } @@ -53,7 +51,11 @@ protected override void FillMethodBody(MethodDefinition methodDefinition, Method TotalMethodCount++; methodContext.Analyze(); - _ilGenerator.GenerateIl(methodContext, methodDefinition); + + 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"));