diff --git a/Decompiler.csproj b/Decompiler.csproj index d34828ecf..335c96556 100644 --- a/Decompiler.csproj +++ b/Decompiler.csproj @@ -42,6 +42,8 @@ + + MainForm.cs diff --git a/src/AstMetodBodyBuilder.cs b/src/AstMetodBodyBuilder.cs index 0b6791973..66dfc153e 100644 --- a/src/AstMetodBodyBuilder.cs +++ b/src/AstMetodBodyBuilder.cs @@ -18,10 +18,12 @@ namespace Decompiler methodDef.Body.Simplify(); + StackAnalysis stackAnalysis = new StackAnalysis(methodDef); + foreach(Instruction instr in methodDef.Body.Instructions) { OpCode opCode = instr.OpCode; string description = - string.Format("/* {1, -22} # {2}->{3} {4} {5} */", + string.Format(" {1, -22} # {2}->{3} {4} {5}", instr.Offset, opCode + " " + FormatInstructionOperand(instr.Operand), opCode.StackBehaviourPop, @@ -46,7 +48,7 @@ namespace Decompiler new Ast.IdentifierExpression("arg2"), new Ast.IdentifierExpression("arg3")); if (codeExpr is Ast.Expression) { - if (GetNumberOfOutputs(methodDef, instr) == 1) { + if (Util.GetNumberOfOutputs(methodDef, instr) == 1) { type = type ?? "object"; string name = string.Format("expr{0:X2}", instr.Offset); Ast.LocalVariableDeclaration astLocal = new Ast.LocalVariableDeclaration(new Ast.TypeReference(type.ToString())); @@ -59,15 +61,22 @@ namespace Decompiler astStatement = (Ast.Statement)codeExpr; } } catch (NotImplementedException) { - astStatement = new Ast.ExpressionStatement(new PrimitiveExpression(description, description)); + astStatement = MakeComment(description); } astBlock.Children.Add(new Ast.LabelStatement(string.Format("IL_{0:X2}", instr.Offset))); astBlock.Children.Add(astStatement); + astBlock.Children.Add(MakeComment(" " + stackAnalysis.StackAfter[instr].ToString())); } return astBlock; } + static Ast.ExpressionStatement MakeComment(string text) + { + text = "/*" + text + "*/"; + return new Ast.ExpressionStatement(new PrimitiveExpression(text, text)); + } + static object FormatInstructionOperand(object operand) { if (operand == null) { @@ -91,34 +100,6 @@ namespace Decompiler } } - static int GetNumberOfOutputs(MethodDefinition methodDef, Instruction inst) - { - switch(inst.OpCode.StackBehaviourPush) { - case StackBehaviour.Push0: return 0; - case StackBehaviour.Push1: return 1; - case StackBehaviour.Push1_push1: return 2; - case StackBehaviour.Pushi: return 1; - case StackBehaviour.Pushi8: return 1; - case StackBehaviour.Pushr4: return 1; - case StackBehaviour.Pushr8: return 1; - case StackBehaviour.Pushref: return 1; - case StackBehaviour.Varpush: // Happens only for calls - switch(inst.OpCode.Code) { - case Code.Call: - Cecil.MethodReference cecilMethod = ((MethodReference)inst.Operand); - if (cecilMethod.ReturnType.ReturnType.FullName == Constants.Void) { - return 0; - } else { - return 1; - } - case Code.Calli: throw new NotImplementedException(); - case Code.Callvirt: throw new NotImplementedException(); - default: throw new Exception("Unknown Varpush opcode"); - } - default: throw new Exception("Unknown push behaviour: " + inst.OpCode.StackBehaviourPush); - } - } - static object MakeCodeDomExpression(MethodDefinition methodDef, Instruction inst, params Ast.Expression[] args) { OpCode opCode = inst.OpCode; diff --git a/src/StackAnalysis.cs b/src/StackAnalysis.cs new file mode 100644 index 000000000..3551e529d --- /dev/null +++ b/src/StackAnalysis.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; + +using Ast = ICSharpCode.NRefactory.Ast; +using ICSharpCode.NRefactory.Ast; + +using Cecil = Mono.Cecil; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace Decompiler +{ + // Imutable + public struct CilStackSlot { + Instruction allocadedBy; + + public Instruction AllocadedBy { + get { return allocadedBy; } + } + + public CilStackSlot(Instruction allocadedBy) + { + this.allocadedBy = allocadedBy; + } + + public override int GetHashCode() + { + int hashCode = 0; + if (allocadedBy != null) hashCode ^= allocadedBy.GetHashCode(); + return hashCode; + } + + public override bool Equals(object obj) + { + if (!(obj is CilStackSlot)) return false; + CilStackSlot myCilStackSlot = (CilStackSlot)obj; + return object.Equals(this.allocadedBy, myCilStackSlot.allocadedBy); + } + + public override string ToString() + { + if (allocadedBy == null) { + return ""; + } else { + return string.Format("expr{0:X2}", this.allocadedBy.Offset); + } + } + } + + /// The tail of the list is the top of the stack + public class CilStack: List { + public static CilStack Empty = new CilStack(); + + public CilStack Clone() + { + return new CilStack(this); + } + + public void PopCount(int count) + { + this.RemoveRange(this.Count - count, count); + } + + public void Push(CilStackSlot slot) + { + this.Add(slot); + } + + public CilStack(): base() + { + + } + + public CilStack(IEnumerable slotEnum): base(slotEnum) + { + + } + + public override string ToString() + { + string ret = "Stack: {"; + bool first = true; + foreach(CilStackSlot slot in this) { + if (!first) ret += ", "; + ret += slot.ToString(); + first = false; + } + ret += "}"; + return ret; + } + } + + public class StackAnalysis { + MethodDefinition methodDef; + Dictionary stackBefore = new Dictionary(); + Dictionary stackAfter = new Dictionary(); + + public Dictionary StackBefore { + get { return stackBefore; } + } + + public Dictionary StackAfter { + get { return stackAfter; } + } + + public StackAnalysis(MethodDefinition methodDef) { + this.methodDef = methodDef; + + foreach(Instruction inst in methodDef.Body.Instructions) { + stackBefore[inst] = null; + stackAfter[inst] = null; + } + + if (methodDef.Body.Instructions.Count > 0) { + Instruction firstInst = methodDef.Body.Instructions[0]; + stackBefore[firstInst] = CilStack.Empty; + ProcessInstructionRec(firstInst); + } + } + + void ProcessInstructionRec(Instruction inst) + { + stackAfter[inst] = ChangeStack(stackBefore[inst], inst); + + switch(inst.OpCode.FlowControl) { + case FlowControl.Branch: + CopyStack(inst, ((Instruction)inst.Operand)); + break; + case FlowControl.Cond_Branch: + CopyStack(inst, inst.Next); + CopyStack(inst, ((Instruction)inst.Operand)); + break; + case FlowControl.Next: + case FlowControl.Call: + CopyStack(inst, inst.Next); + break; + case FlowControl.Return: + if (stackAfter[inst].Count > 0) throw new Exception("Non-empty stack at the end"); + break; + default: throw new NotImplementedException(); + } + } + + CilStack ChangeStack(CilStack oldStack, Instruction inst) + { + CilStack newStack = oldStack.Clone(); + newStack.PopCount(Util.GetNumberOfInputs(methodDef, inst)); + for (int i = 0; i < Util.GetNumberOfOutputs(methodDef, inst); i++) { + newStack.Push(new CilStackSlot(inst)); + } + return newStack; + } + + void CopyStack(Instruction instFrom, Instruction instTo) + { + CilStack mergedStack; + if (!Merge(stackAfter[instFrom], stackBefore[instTo], out mergedStack)) { + stackBefore[instTo] = mergedStack; + ProcessInstructionRec(instTo); + } + } + + bool Merge(CilStack stack1, CilStack stack2, out CilStack merged) + { + // Both null + if (stack1 == null && stack2 == null) { + throw new Exception("Both stacks are null"); + } + // One of stacks null, one is not + if (stack1 == null || stack2 == null) { + merged = stack1 ?? stack2; + return false; + } + // Both are non-null + if (stack1.Count != stack2.Count) { + throw new Exception("Stack merge error: different sizes"); + } + + bool same = true; + int count = stack1.Count; + merged = stack1.Clone(); + for (int i = 0; i < count; i++) { + if (!stack1[i].Equals(stack2[i])) { + merged[i] = new CilStackSlot(null); // Merge slots + same = false; + } + } + return same; + } + } +} diff --git a/src/Util.cs b/src/Util.cs new file mode 100644 index 000000000..5e1cbfa1b --- /dev/null +++ b/src/Util.cs @@ -0,0 +1,88 @@ +using System; + +using Ast = ICSharpCode.NRefactory.Ast; +using ICSharpCode.NRefactory.Ast; + +using Cecil = Mono.Cecil; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace Decompiler +{ + public static class Util + { + public static int GetNumberOfInputs(MethodDefinition methodDef, Instruction inst) + { + switch(inst.OpCode.StackBehaviourPop) { + case StackBehaviour.Pop0: return 0; + case StackBehaviour.Pop1: return 1; + case StackBehaviour.Popi: return 1; + case StackBehaviour.Popref: return 1; + case StackBehaviour.Pop1_pop1: return 2; + case StackBehaviour.Popi_pop1: return 2; + case StackBehaviour.Popi_popi: return 2; + case StackBehaviour.Popi_popi8: return 2; + case StackBehaviour.Popi_popr4: return 2; + case StackBehaviour.Popi_popr8: return 2; + case StackBehaviour.Popref_pop1: return 2; + case StackBehaviour.Popref_popi: return 2; + case StackBehaviour.Popi_popi_popi: return 3; + case StackBehaviour.Popref_popi_popi: return 3; + case StackBehaviour.Popref_popi_popi8: return 3; + case StackBehaviour.Popref_popi_popr4: return 3; + case StackBehaviour.Popref_popi_popr8: return 3; + case StackBehaviour.Popref_popi_popref: return 3; + case StackBehaviour.PopAll: throw new Exception("PopAll"); + case StackBehaviour.Varpop: + switch(inst.OpCode.Code) { + case Code.Call: + Cecil.MethodReference cecilMethod = ((MethodReference)inst.Operand); + if (cecilMethod.HasThis) { + return cecilMethod.Parameters.Count + 1 /* this */; + } else { + return cecilMethod.Parameters.Count; + } + case Code.Calli: throw new NotImplementedException(); + case Code.Callvirt: throw new NotImplementedException(); + case Code.Ret: + if (methodDef.ReturnType.ReturnType.FullName == Constants.Void) { + return 0; + } else { + return 1; + } + case Code.Newobj: throw new NotImplementedException(); + default: throw new Exception("Unknown Varpop opcode"); + } + default: throw new Exception("Unknown pop behaviour: " + inst.OpCode.StackBehaviourPop); + } + } + + public static int GetNumberOfOutputs(MethodDefinition methodDef, Instruction inst) + { + switch(inst.OpCode.StackBehaviourPush) { + case StackBehaviour.Push0: return 0; + case StackBehaviour.Push1: return 1; + case StackBehaviour.Push1_push1: return 2; + case StackBehaviour.Pushi: return 1; + case StackBehaviour.Pushi8: return 1; + case StackBehaviour.Pushr4: return 1; + case StackBehaviour.Pushr8: return 1; + case StackBehaviour.Pushref: return 1; + case StackBehaviour.Varpush: // Happens only for calls + switch(inst.OpCode.Code) { + case Code.Call: + Cecil.MethodReference cecilMethod = ((MethodReference)inst.Operand); + if (cecilMethod.ReturnType.ReturnType.FullName == Constants.Void) { + return 0; + } else { + return 1; + } + case Code.Calli: throw new NotImplementedException(); + case Code.Callvirt: throw new NotImplementedException(); + default: throw new Exception("Unknown Varpush opcode"); + } + default: throw new Exception("Unknown push behaviour: " + inst.OpCode.StackBehaviourPush); + } + } + } +}