diff --git a/libs/UIX.DecompXml/ControlFlowAnalyzer.cs b/libs/UIX.DecompXml/ControlFlowAnalyzer.cs new file mode 100644 index 0000000..7dbaf8e --- /dev/null +++ b/libs/UIX.DecompXml/ControlFlowAnalyzer.cs @@ -0,0 +1,93 @@ +using Microsoft.Iris.Asm.Models; +using Microsoft.Iris.Markup; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; + +namespace Microsoft.Iris.DecompXml; + +public static class ControlFlowAnalyzer +{ + private static readonly ImmutableHashSet _jumpOpCodes = [ + OpCode.JumpIfFalse, OpCode.JumpIfFalsePeek, OpCode.JumpIfTruePeek, + OpCode.JumpIfDictionaryContains, OpCode.JumpIfNullPeek, OpCode.Jump, + ]; + + public static List CreateGraph(Instruction[] instructions) + { + // See slide 15 (page 8) of https://www.cs.utexas.edu/~lin/cs380c/handout03.pdf + + var procedureMap = instructions.ToImmutableDictionary(i => i.Offset); + + // Pass I: Find the start of each block + HashSet leaderOffsets = [instructions[0].Offset]; + + for (int i = 1; i < instructions.Length; i++) + { + var instruction = instructions[i]; + + if (!_jumpOpCodes.Contains(instruction.OpCode)) + continue; + + var jumpOffset = (uint)instruction.Operands.First().Value; + leaderOffsets.Add(jumpOffset); + + if (instruction.OpCode is not OpCode.Jump) + leaderOffsets.Add(instructions[i + 1].Offset); + } + + // Pass II: Segment the procedure so a leader starts each block + List blocks = []; + + foreach (var leaderOffset in leaderOffsets) + { + List body = [procedureMap[leaderOffset]]; + + foreach (var instruction in instructions.SkipWhile(i => i.Offset <= leaderOffset)) + { + if (leaderOffsets.Contains(instruction.Offset)) + break; + + body.Add(instruction); + } + + ControlFlowBlock block = new(leaderOffset, body[^1].Offset, body); + blocks.Add(block); + } + + // Pass III: Resolve next and branch target blocks + for (var b = 0; b < blocks.Count; b++) + { + var block = blocks[b]; + + var leaderInstruction = block.Body[^1]; + var jumpOffset = (uint)leaderInstruction.Operands.First().Value; + leaderOffsets.Add(jumpOffset); + + if (leaderInstruction.OpCode is not OpCode.Jump) + leaderOffsets.Add(instructions[i + 1].Offset); + } + + return blocks; + } + + public static string SerializeToGraphviz(IEnumerable blocks) + { + var sortedBlocks = blocks.OrderBy(b => b.StartOffset).ToArray(); + StringBuilder sb = new(); + + sb.AppendLine("digraph G {"); + sb.AppendLine(" node [shape=record];"); + + + + sb.AppendLine("}"); + + return sb.ToString(); + } +} + +public record ControlFlowBlock(uint StartOffset, uint EndOffset, List Body, + uint NextOffset = uint.MaxValue, uint BranchTargetOffset = uint.MaxValue, + ControlFlowBlock Next = null, ControlFlowBlock BranchTarget = null); diff --git a/libs/UIX.DecompXml/Decompiler.Script.cs b/libs/UIX.DecompXml/Decompiler.Script.cs index f8d1477..fdcaf7a 100644 --- a/libs/UIX.DecompXml/Decompiler.Script.cs +++ b/libs/UIX.DecompXml/Decompiler.Script.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; -using System.Xml.Linq; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; @@ -32,6 +31,8 @@ partial class Decompiler { var methodBody = _context.GetMethodBody(startOffset).ToArray(); + var controlBlocks = ControlFlowAnalyzer.CreateGraph(methodBody); + Stack blockStack = []; blockStack.Push(new(0, methodBody[^1].Offset, SyntaxKind.Block, null));