Browse Source

Initial stack analysis implementation:

Verify that the stack sizes are consistent - ie at a given location the stack size is statically known and constant.
Keep track of instruction which pushed a given value on the stack. (Not handling the potential problem at control merge points - this problem does not occur in the quick sort algorithm)
The state of the stack is outputted as a comment in the source code
pull/1/head^2
David Srbecký 18 years ago
parent
commit
238605c831
  1. 2
      Decompiler.csproj
  2. 43
      src/AstMetodBodyBuilder.cs
  3. 191
      src/StackAnalysis.cs
  4. 88
      src/Util.cs

2
Decompiler.csproj

@ -42,6 +42,8 @@
<Compile Include="src\MainForm.cs" /> <Compile Include="src\MainForm.cs" />
<Compile Include="src\MainForm.Designer.cs" /> <Compile Include="src\MainForm.Designer.cs" />
<Compile Include="src\Program.cs" /> <Compile Include="src\Program.cs" />
<Compile Include="src\StackAnalysis.cs" />
<Compile Include="src\Util.cs" />
<EmbeddedResource Include="src\MainForm.resx"> <EmbeddedResource Include="src\MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon> <DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>

43
src/AstMetodBodyBuilder.cs

@ -18,10 +18,12 @@ namespace Decompiler
methodDef.Body.Simplify(); methodDef.Body.Simplify();
StackAnalysis stackAnalysis = new StackAnalysis(methodDef);
foreach(Instruction instr in methodDef.Body.Instructions) { foreach(Instruction instr in methodDef.Body.Instructions) {
OpCode opCode = instr.OpCode; OpCode opCode = instr.OpCode;
string description = string description =
string.Format("/* {1, -22} # {2}->{3} {4} {5} */", string.Format(" {1, -22} # {2}->{3} {4} {5}",
instr.Offset, instr.Offset,
opCode + " " + FormatInstructionOperand(instr.Operand), opCode + " " + FormatInstructionOperand(instr.Operand),
opCode.StackBehaviourPop, opCode.StackBehaviourPop,
@ -46,7 +48,7 @@ namespace Decompiler
new Ast.IdentifierExpression("arg2"), new Ast.IdentifierExpression("arg2"),
new Ast.IdentifierExpression("arg3")); new Ast.IdentifierExpression("arg3"));
if (codeExpr is Ast.Expression) { if (codeExpr is Ast.Expression) {
if (GetNumberOfOutputs(methodDef, instr) == 1) { if (Util.GetNumberOfOutputs(methodDef, instr) == 1) {
type = type ?? "object"; type = type ?? "object";
string name = string.Format("expr{0:X2}", instr.Offset); string name = string.Format("expr{0:X2}", instr.Offset);
Ast.LocalVariableDeclaration astLocal = new Ast.LocalVariableDeclaration(new Ast.TypeReference(type.ToString())); Ast.LocalVariableDeclaration astLocal = new Ast.LocalVariableDeclaration(new Ast.TypeReference(type.ToString()));
@ -59,15 +61,22 @@ namespace Decompiler
astStatement = (Ast.Statement)codeExpr; astStatement = (Ast.Statement)codeExpr;
} }
} catch (NotImplementedException) { } 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(new Ast.LabelStatement(string.Format("IL_{0:X2}", instr.Offset)));
astBlock.Children.Add(astStatement); astBlock.Children.Add(astStatement);
astBlock.Children.Add(MakeComment(" " + stackAnalysis.StackAfter[instr].ToString()));
} }
return astBlock; return astBlock;
} }
static Ast.ExpressionStatement MakeComment(string text)
{
text = "/*" + text + "*/";
return new Ast.ExpressionStatement(new PrimitiveExpression(text, text));
}
static object FormatInstructionOperand(object operand) static object FormatInstructionOperand(object operand)
{ {
if (operand == null) { 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) static object MakeCodeDomExpression(MethodDefinition methodDef, Instruction inst, params Ast.Expression[] args)
{ {
OpCode opCode = inst.OpCode; OpCode opCode = inst.OpCode;

191
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);
}
}
}
/// <remarks> The tail of the list is the top of the stack </remarks>
public class CilStack: List<CilStackSlot> {
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<CilStackSlot> 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<Instruction, CilStack> stackBefore = new Dictionary<Instruction, CilStack>();
Dictionary<Instruction, CilStack> stackAfter = new Dictionary<Instruction, CilStack>();
public Dictionary<Instruction, CilStack> StackBefore {
get { return stackBefore; }
}
public Dictionary<Instruction, CilStack> 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;
}
}
}

88
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);
}
}
}
}
Loading…
Cancel
Save