From 044714fe29801951950d510888eb559c230b0527 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Tue, 8 Feb 2011 00:06:09 +0100 Subject: [PATCH] Use a simpler loop detection for the disassembler. --- ICSharpCode.Decompiler/CecilExtensions.cs | 8 + .../Disassembler/ILStructure.cs | 223 ++++++++++++++++++ .../Disassembler/MethodBodyDisassembler.cs | 155 ++++++------ .../FlowAnalysis/ControlFlowGraphBuilder.cs | 20 +- .../FlowAnalysis/ControlFlowNode.cs | 2 +- .../FlowAnalysis/OpCodeInfo.cs | 18 ++ .../ICSharpCode.Decompiler.csproj | 1 + 7 files changed, 332 insertions(+), 95 deletions(-) create mode 100644 ICSharpCode.Decompiler/Disassembler/ILStructure.cs diff --git a/ICSharpCode.Decompiler/CecilExtensions.cs b/ICSharpCode.Decompiler/CecilExtensions.cs index d4be05b48..4c17373ec 100644 --- a/ICSharpCode.Decompiler/CecilExtensions.cs +++ b/ICSharpCode.Decompiler/CecilExtensions.cs @@ -123,6 +123,14 @@ namespace ICSharpCode.Decompiler } #endregion + /// + /// Gets the (exclusive) end offset of this instruction. + /// + public static int GetEndOffset(this Instruction inst) + { + return inst.Offset + inst.GetSize(); + } + public static string OffsetToString(int offset) { return string.Format("IL_{0:x4}", offset); diff --git a/ICSharpCode.Decompiler/Disassembler/ILStructure.cs b/ICSharpCode.Decompiler/Disassembler/ILStructure.cs new file mode 100644 index 000000000..3739fdd9f --- /dev/null +++ b/ICSharpCode.Decompiler/Disassembler/ILStructure.cs @@ -0,0 +1,223 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using ICSharpCode.Decompiler.FlowAnalysis; +using Mono.Cecil.Cil; + +namespace ICSharpCode.Decompiler.Disassembler +{ + /// + /// Specifies the type of an IL structure. + /// + public enum ILStructureType + { + /// + /// The root block of the method + /// + Root, + /// + /// A nested control structure representing a loop. + /// + Loop, + /// + /// A nested control structure representing a try block. + /// + Try, + /// + /// A nested control structure representing a catch, finally, or fault block. + /// + Handler, + /// + /// A nested control structure representing an exception filter block. + /// + Filter + } + + /// + /// An IL structure. + /// + public class ILStructure + { + public readonly ILStructureType Type; + + /// + /// Start position of the structure. + /// + public readonly int StartOffset; + + /// + /// End position of the structure. (exclusive) + /// + public readonly int EndOffset; + + /// + /// The exception handler associated with the Try, Filter or Handler block. + /// + public readonly ExceptionHandler ExceptionHandler; + + /// + /// The loop's entry point. + /// + public readonly Instruction LoopEntryPoint; + + /// + /// The list of child structures. + /// + public readonly List Children = new List(); + + public ILStructure(MethodBody body) + : this(ILStructureType.Root, 0, body.CodeSize) + { + // Build the tree of exception structures: + foreach (ExceptionHandler eh in body.ExceptionHandlers) { + AddNestedStructure(new ILStructure(ILStructureType.Try, eh.TryStart.Offset, eh.TryEnd.Offset, eh)); + if (eh.HandlerType == ExceptionHandlerType.Filter) + AddNestedStructure(new ILStructure(ILStructureType.Filter, eh.FilterStart.Offset, eh.FilterEnd.Offset, eh)); + AddNestedStructure(new ILStructure(ILStructureType.Handler, eh.HandlerStart.Offset, eh.HandlerEnd.Offset, eh)); + } + // Very simple loop detection: look for backward branches + List> allBranches = FindAllBranches(body); + // We go through the branches in reverse so that we find the biggest possible loop boundary first (think loops with "continue;") + for (int i = allBranches.Count - 1; i >= 0; i--) { + int loopEnd = allBranches[i].Key.GetEndOffset(); + int loopStart = allBranches[i].Value.Offset; + if (loopStart < loopEnd) { + // We found a backward branch. This is a potential loop. + // Check that is has only one entry point: + Instruction entryPoint = null; + + // entry point is first instruction in loop if prev inst isn't an unconditional branch + Instruction prev = allBranches[i].Value.Previous; + if (prev != null && !OpCodeInfo.IsUnconditionalBranch(prev.OpCode)) + entryPoint = allBranches[i].Value; + + bool multipleEntryPoints = false; + foreach (var pair in allBranches) { + if (pair.Key.Offset < loopStart || pair.Key.Offset >= loopEnd) { + if (loopStart <= pair.Value.Offset && pair.Value.Offset < loopEnd) { + // jump from outside the loop into the loop + if (entryPoint == null) + entryPoint = pair.Value; + else if (pair.Value != entryPoint) + multipleEntryPoints = true; + } + } + } + if (!multipleEntryPoints) { + AddNestedStructure(new ILStructure(ILStructureType.Loop, loopStart, loopEnd, entryPoint)); + } + } + } + SortChildren(); + } + + public ILStructure(ILStructureType type, int startOffset, int endOffset, ExceptionHandler handler = null) + { + Debug.Assert(startOffset < endOffset); + this.Type = type; + this.StartOffset = startOffset; + this.EndOffset = endOffset; + this.ExceptionHandler = handler; + } + + public ILStructure(ILStructureType type, int startOffset, int endOffset, Instruction loopEntryPoint) + { + Debug.Assert(startOffset < endOffset); + this.Type = type; + this.StartOffset = startOffset; + this.EndOffset = endOffset; + this.LoopEntryPoint = loopEntryPoint; + } + + bool AddNestedStructure(ILStructure newStructure) + { + // special case: don't consider the loop-like structure of "continue;" statements to be nested loops + if (this.Type == ILStructureType.Loop && newStructure.Type == ILStructureType.Loop && newStructure.StartOffset == this.StartOffset) + return false; + + // use <= for end-offset comparisons because both end and EndOffset are exclusive + Debug.Assert(StartOffset <= newStructure.StartOffset && newStructure.EndOffset <= EndOffset); + foreach (ILStructure child in this.Children) { + if (child.StartOffset <= newStructure.StartOffset && newStructure.EndOffset <= child.EndOffset) { + return child.AddNestedStructure(newStructure); + } else if (!(child.EndOffset <= newStructure.StartOffset || newStructure.EndOffset <= child.StartOffset)) { + // Overlap (invalid nesting), can't build a tree. -> Don't add the new structure. + return false; + } + } + // Move existing structures into the new structure: + for (int i = 0; i < this.Children.Count; i++) { + ILStructure child = this.Children[i]; + if (newStructure.StartOffset <= child.StartOffset && child.EndOffset <= newStructure.EndOffset) { + this.Children.RemoveAt(i--); + newStructure.Children.Add(child); + } + } + // Add the structure here: + this.Children.Add(newStructure); + return true; + } + + /// + /// Finds all branches. Returns list of source offset->target offset mapping. + /// Multiple entries for the same source offset are possible (switch statements). + /// The result is sorted by source offset. + /// + List> FindAllBranches(MethodBody body) + { + var result = new List>(); + foreach (Instruction inst in body.Instructions) { + switch (inst.OpCode.OperandType) { + case OperandType.InlineBrTarget: + case OperandType.ShortInlineBrTarget: + result.Add(new KeyValuePair(inst, (Instruction)inst.Operand)); + break; + case OperandType.InlineSwitch: + foreach (Instruction target in (Instruction[])inst.Operand) + result.Add(new KeyValuePair(inst, target)); + break; + } + } + return result; + } + + void SortChildren() + { + Children.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset)); + foreach (ILStructure child in Children) + child.SortChildren(); + } + + /// + /// Gets the innermost structure containing the specified offset. + /// + public ILStructure GetInnermost(int offset) + { + Debug.Assert(StartOffset <= offset && offset < EndOffset); + foreach (ILStructure child in this.Children) { + if (child.StartOffset <= offset && offset < child.EndOffset) + return child.GetInnermost(offset); + } + return this; + } + } +} diff --git a/ICSharpCode.Decompiler/Disassembler/MethodBodyDisassembler.cs b/ICSharpCode.Decompiler/Disassembler/MethodBodyDisassembler.cs index 708425ccd..55ff87df0 100644 --- a/ICSharpCode.Decompiler/Disassembler/MethodBodyDisassembler.cs +++ b/ICSharpCode.Decompiler/Disassembler/MethodBodyDisassembler.cs @@ -17,9 +17,9 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.Linq; using System.Threading; - using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.FlowAnalysis; using Mono.Cecil; @@ -72,12 +72,9 @@ namespace ICSharpCode.Decompiler.Disassembler } output.WriteLine(); - if (detectControlStructure) { - var cfg = ControlFlowGraphBuilder.Build(method.Body); - cfg.ComputeDominance(cancellationToken); - cfg.ComputeDominanceFrontier(); - var s = ControlStructureDetector.DetectStructure(cfg, method.Body.ExceptionHandlers, cancellationToken); - WriteStructure(s); + if (detectControlStructure && body.Instructions.Count > 0) { + Instruction inst = body.Instructions[0]; + WriteStructureBody(new ILStructure(body), ref inst); } else { foreach (var inst in method.Body.Instructions) { inst.WriteTo(output); @@ -91,79 +88,87 @@ namespace ICSharpCode.Decompiler.Disassembler } } - void WriteStructure(ControlStructure s) + void WriteStructureHeader(ILStructure s) { - if (s.Type != ControlStructureType.Root) { - switch (s.Type) { - case ControlStructureType.Loop: - output.Write("// loop start"); - if (s.EntryPoint.Start != null) { - output.Write(" (head: "); - DisassemblerHelpers.WriteOffsetReference(output, s.EntryPoint.Start); - output.Write(')'); - } - output.WriteLine(); - break; - case ControlStructureType.Try: - output.WriteLine(".try {"); - break; - case ControlStructureType.Handler: - switch (s.ExceptionHandler.HandlerType) { - case Mono.Cecil.Cil.ExceptionHandlerType.Catch: - case Mono.Cecil.Cil.ExceptionHandlerType.Filter: - output.Write("catch"); - if (s.ExceptionHandler.CatchType != null) { - output.Write(' '); - s.ExceptionHandler.CatchType.WriteTo(output); - } - output.WriteLine(" {"); - break; - case Mono.Cecil.Cil.ExceptionHandlerType.Finally: - output.WriteLine("finally {"); - break; - case Mono.Cecil.Cil.ExceptionHandlerType.Fault: - output.WriteLine("fault {"); - break; - default: - throw new NotSupportedException(); - } - break; - case ControlStructureType.Filter: - output.WriteLine("filter {"); - break; - default: - throw new NotSupportedException(); - } - output.Indent(); - } - foreach (var node in s.Nodes.Concat(from c in s.Children select c.EntryPoint).OrderBy(c => c.Offset)) { - if (s.Nodes.Contains(node)) { - foreach (var inst in node.Instructions) { - inst.WriteTo(output); - output.WriteLine(); + switch (s.Type) { + case ILStructureType.Loop: + output.Write("// loop start"); + if (s.LoopEntryPoint != null) { + output.Write(" (head: "); + DisassemblerHelpers.WriteOffsetReference(output, s.LoopEntryPoint); + output.Write(')'); } + output.WriteLine(); + break; + case ILStructureType.Try: + output.WriteLine(".try {"); + break; + case ILStructureType.Handler: + switch (s.ExceptionHandler.HandlerType) { + case Mono.Cecil.Cil.ExceptionHandlerType.Catch: + case Mono.Cecil.Cil.ExceptionHandlerType.Filter: + output.Write("catch"); + if (s.ExceptionHandler.CatchType != null) { + output.Write(' '); + s.ExceptionHandler.CatchType.WriteTo(output); + } + output.WriteLine(" {"); + break; + case Mono.Cecil.Cil.ExceptionHandlerType.Finally: + output.WriteLine("finally {"); + break; + case Mono.Cecil.Cil.ExceptionHandlerType.Fault: + output.WriteLine("fault {"); + break; + default: + throw new NotSupportedException(); + } + break; + case ILStructureType.Filter: + output.WriteLine("filter {"); + break; + default: + throw new NotSupportedException(); + } + output.Indent(); + } + + void WriteStructureBody(ILStructure s, ref Instruction inst) + { + int childIndex = 0; + while (inst != null && inst.Offset < s.EndOffset) { + int offset = inst.Offset; + if (childIndex < s.Children.Count && s.Children[childIndex].StartOffset <= offset && offset < s.Children[childIndex].EndOffset) { + ILStructure child = s.Children[childIndex++]; + WriteStructureHeader(child); + WriteStructureBody(child, ref inst); + WriteStructureFooter(child); } else { - WriteStructure(s.Children.Single(c => c.EntryPoint == node)); + inst.WriteTo(output); + output.WriteLine(); + inst = inst.Next; } } - if (s.Type != ControlStructureType.Root) { - output.Unindent(); - switch (s.Type) { - case ControlStructureType.Loop: - output.WriteLine("// end loop"); - break; - case ControlStructureType.Try: - output.WriteLine("} // end .try"); - break; - case ControlStructureType.Handler: - output.WriteLine("} // end handler"); - break; - case ControlStructureType.Filter: - output.WriteLine("} // end filter"); - break; - default: - throw new NotSupportedException(); - } + } + + void WriteStructureFooter(ILStructure s) + { + output.Unindent(); + switch (s.Type) { + case ILStructureType.Loop: + output.WriteLine("// end loop"); + break; + case ILStructureType.Try: + output.WriteLine("} // end .try"); + break; + case ILStructureType.Handler: + output.WriteLine("} // end handler"); + break; + case ILStructureType.Filter: + output.WriteLine("} // end filter"); + break; + default: + throw new NotSupportedException(); } } } diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraphBuilder.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraphBuilder.cs index b58bc651c..4b0c434d4 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraphBuilder.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraphBuilder.cs @@ -155,7 +155,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis foreach (ControlFlowNode node in nodes) { if (node.End != null) { // create normal edges from one instruction to the next - if (!IsUnconditionalBranch(node.End.OpCode)) + if (!OpCodeInfo.IsUnconditionalBranch(node.End.OpCode)) CreateEdge(node, node.End.Next, JumpType.Normal); // create edges for branch instructions @@ -434,24 +434,6 @@ namespace ICSharpCode.Decompiler.FlowAnalysis throw new NotSupportedException(opcode.FlowControl.ToString()); } } - - static bool IsUnconditionalBranch(OpCode opcode) - { - if (opcode.OpCodeType == OpCodeType.Prefix) - return false; - switch (opcode.FlowControl) { - case FlowControl.Branch: - case FlowControl.Throw: - case FlowControl.Return: - return true; - case FlowControl.Next: - case FlowControl.Call: - case FlowControl.Cond_Branch: - return false; - default: - throw new NotSupportedException(opcode.FlowControl.ToString()); - } - } #endregion } } diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs index a1d9c7c40..890b62898 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs @@ -248,7 +248,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis StringWriter writer = new StringWriter(); switch (NodeType) { case ControlFlowNodeType.Normal: - int endOffset = End.Next != null ? End.Next.Offset : End.Offset + End.GetSize(); + int endOffset = End.GetEndOffset(); writer.Write("Block #{0}: IL_{1:x4} to IL_{2:x4}", BlockIndex, Start.Offset, endOffset); break; case ControlFlowNodeType.CatchHandler: diff --git a/ICSharpCode.Decompiler/FlowAnalysis/OpCodeInfo.cs b/ICSharpCode.Decompiler/FlowAnalysis/OpCodeInfo.cs index 1b76f300f..e26447ebc 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/OpCodeInfo.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/OpCodeInfo.cs @@ -29,6 +29,24 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// sealed class OpCodeInfo { + public static bool IsUnconditionalBranch(OpCode opcode) + { + if (opcode.OpCodeType == OpCodeType.Prefix) + return false; + switch (opcode.FlowControl) { + case FlowControl.Branch: + case FlowControl.Throw: + case FlowControl.Return: + return true; + case FlowControl.Next: + case FlowControl.Call: + case FlowControl.Cond_Branch: + return false; + default: + throw new NotSupportedException(opcode.FlowControl.ToString()); + } + } + static readonly OpCodeInfo[] knownOpCodes = { #region Base Instructions new OpCodeInfo(OpCodes.Add) { CanThrow = false }, diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 5b4a81737..4dfda9609 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -51,6 +51,7 @@ +