playing with less memory

This commit is contained in:
Adam
2016-04-03 15:53:13 -04:00
parent c9bfbb1352
commit d6c7d7dc8a
6 changed files with 415 additions and 181 deletions

View File

@@ -17,14 +17,14 @@ import org.apache.commons.collections4.map.MultiValueMap;
public class Execution public class Execution
{ {
private ClassGroup group; private ClassGroup group;
public List<Frame> frames = new LinkedList<>(), public List<Frame> frames = new LinkedList<>();
processedFrames = new LinkedList<>();
public Set<Method> methods = new HashSet<>(); // all methods public Set<Method> methods = new HashSet<>(); // all methods
public Set<Instruction> executed = new HashSet<>(); // executed instructions public Set<Instruction> executed = new HashSet<>(); // executed instructions
private MultiValueMap<InstructionContext, Method> invokes = new MultiValueMap<>(); private MultiValueMap<WeakInstructionContext, Method> invokes = new MultiValueMap<>();
public MultiValueMap<Instruction, InstructionContext> contexts = new MultiValueMap<>(); public MultiValueMap<Instruction, InstructionContext> contexts = new MultiValueMap<>(); // XXX this should move to method ctx probably
public boolean paused; public boolean paused;
public boolean step = false; public boolean step = false;
private List<ExecutionVisitor> visitors = new ArrayList<>();
public Execution(ClassGroup group) public Execution(ClassGroup group)
{ {
@@ -76,11 +76,11 @@ public class Execution
public boolean hasInvoked(InstructionContext from, Method to) public boolean hasInvoked(InstructionContext from, Method to)
{ {
Collection<Method> methods = invokes.getCollection(from); Collection<Method> methods = invokes.getCollection(from.toWeak());
if (methods != null && methods.contains(to)) if (methods != null && methods.contains(to))
return true; return true;
invokes.put(from, to); invokes.put(from.toWeak(), to);
return false; return false;
} }
@@ -128,7 +128,6 @@ public class Execution
assert !frame.isExecuting(); assert !frame.isExecuting();
frames.remove(frame); frames.remove(frame);
processedFrames.add(frame);
} }
System.out.println("Processed " + fcount + " frames"); System.out.println("Processed " + fcount + " frames");
@@ -138,4 +137,14 @@ public class Execution
{ {
return contexts.getCollection(i); return contexts.getCollection(i);
} }
public void addExecutionVisitor(ExecutionVisitor ev)
{
this.visitors.add(ev);
}
public void accept(InstructionContext ic)
{
visitors.forEach(v -> v.visit(ic));
}
} }

View File

@@ -0,0 +1,6 @@
package net.runelite.asm.execution;
public interface ExecutionVisitor
{
void visit(InstructionContext context);
}

View File

@@ -239,6 +239,8 @@ public class Frame
execution.contexts.put(oldCur, ictx); execution.contexts.put(oldCur, ictx);
execution.executed.add(oldCur); execution.executed.add(oldCur);
execution.accept(ictx);
processExceptions(ictx); processExceptions(ictx);

View File

@@ -209,4 +209,20 @@ public class InstructionContext
return ctx; return ctx;
} }
public WeakInstructionContext toWeak()
{
WeakInstructionContext wic = new WeakInstructionContext(this.getInstruction());
for (StackContext sctx : stack.getStack())
{
if (sctx == null)
break;
InstructionContext i = sctx.getPushed();
wic.addStack(i.getInstruction());
}
return wic;
}
} }

View File

@@ -0,0 +1,58 @@
package net.runelite.asm.execution;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import net.runelite.asm.attributes.code.Instruction;
public class WeakInstructionContext
{
private Instruction ins;
private List<Instruction> stack = new ArrayList<>();
public WeakInstructionContext(Instruction ins)
{
this.ins = ins;
}
public void addStack(Instruction i)
{
stack.add(i);
}
@Override
public int hashCode()
{
int hash = 3;
hash = 37 * hash + Objects.hashCode(this.ins);
hash = 37 * hash + Objects.hashCode(this.stack);
return hash;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final WeakInstructionContext other = (WeakInstructionContext) obj;
if (!Objects.equals(this.ins, other.ins))
{
return false;
}
if (!Objects.equals(this.stack, other.stack))
{
return false;
}
return true;
}
}

View File

@@ -39,6 +39,8 @@ class ConstantMethodParameter
public int paramIndex; public int paramIndex;
public int lvtIndex; public int lvtIndex;
public Object value; public Object value;
List<InstructionContext> operations = new ArrayList<>();
Boolean result;
@Override @Override
public int hashCode() public int hashCode()
@@ -88,6 +90,8 @@ class MethodGroup
public class ConstantParameter implements Deobfuscator public class ConstantParameter implements Deobfuscator
{ {
private List<ConstantMethodParameter> parameters = new ArrayList<>(); private List<ConstantMethodParameter> parameters = new ArrayList<>();
private MultiValueMap<Method, ConstantMethodParameter> mparams = new MultiValueMap<>();
private MultiValueMap<Method, Integer> nonconst = new MultiValueMap<>(); // methods with non const parameters private MultiValueMap<Method, Integer> nonconst = new MultiValueMap<>(); // methods with non const parameters
private List<MethodGroup> methodGroups = new ArrayList<>(); private List<MethodGroup> methodGroups = new ArrayList<>();
@@ -105,8 +109,10 @@ public class ConstantParameter implements Deobfuscator
} }
} }
private void findConstantParameter(Execution execution, List<Method> methods, InstructionContext invokeCtx) private List<ConstantMethodParameter> findConstantParameter(List<Method> methods, InstructionContext invokeCtx)
{ {
List<ConstantMethodParameter> list = new ArrayList<>();
checkMethodsAreConsistent(methods); checkMethodsAreConsistent(methods);
Method method = methods.get(0); // all methods must have the same signature etc Method method = methods.get(0); // all methods must have the same signature etc
@@ -149,6 +155,10 @@ public class ConstantParameter implements Deobfuscator
continue outer; continue outer;
parameters.add(cmp); parameters.add(cmp);
for (Method m : methods)
mparams.put(m, cmp);
list.add(cmp);
} }
else else
{ {
@@ -162,28 +172,152 @@ public class ConstantParameter implements Deobfuscator
if (c.methods.equals(methods) && c.lvtIndex == lvtOffset) if (c.methods.equals(methods) && c.lvtIndex == lvtOffset)
{ {
it.remove(); it.remove();
list.remove(c);
} }
} }
} }
} }
return list;
} }
private void findParameters(Execution execution) private void findParameters(InstructionContext ins)
{ {
for (Frame frame : execution.processedFrames) if (!(ins.getInstruction() instanceof InvokeInstruction))
for (InstructionContext ins : frame.getInstructions()) return;
{
if (!(ins.getInstruction() instanceof InvokeInstruction)) List<Method> methods = ((InvokeInstruction) ins.getInstruction()).getMethods();
continue; if (methods.isEmpty())
return;
List<Method> methods = ((InvokeInstruction) ins.getInstruction()).getMethods();
if (methods.isEmpty()) findConstantParameter(methods, ins);
continue; //findDeadParameters(ins, c);
findConstantParameter(execution, methods, ins);
}
} }
private List<ConstantMethodParameter> findParametersForMethod(Method m)
{
Collection c = mparams.getCollection(m);
if (c == null) return new ArrayList();
return new ArrayList(c);
// List<ConstantMethodParameter> list = new ArrayList<>();
// for (ConstantMethodParameter c : parameters)
// if (c.methods.contains(m))
// list.add(c);
// return list;
}
private void findDeadParameters(InstructionContext ins)
{
List<ConstantMethodParameter> parameters = this.findParametersForMethod(ins.getFrame().getMethod());
for (ConstantMethodParameter parameter : parameters)
{
int lvtIndex = parameter.lvtIndex;
Object value = parameter.value;
if (ins.getInstruction() instanceof LVTInstruction)
{
LVTInstruction lvt = (LVTInstruction) ins.getInstruction();
if (lvt.getVariableIndex() == lvtIndex && lvt.store())
{
this.parameters.remove(parameter);
continue; // parameter is used
}
}
if (!(ins.getInstruction() instanceof ComparisonInstruction))
{
continue;
}
// assume that this will always be variable index #paramIndex comp with a constant.
ComparisonInstruction comp = (ComparisonInstruction) ins.getInstruction();
StackContext one, two = null;
if (comp instanceof If0)
{
one = ins.getPops().get(0);
}
else if (comp instanceof If)
{
one = ins.getPops().get(0);
two = ins.getPops().get(1);
}
else
{
throw new RuntimeException("Unknown comp ins");
}
// find if one is a lvt ins
LVTInstruction lvt = null;
StackContext other = null;
if (one.getPushed().getInstruction() instanceof LVTInstruction)
{
lvt = (LVTInstruction) one.getPushed().getInstruction();
other = two;
}
else if (two != null && two.getPushed().getInstruction() instanceof LVTInstruction)
{
lvt = (LVTInstruction) two.getPushed().getInstruction();
other = one;
}
assert lvt == null || !lvt.store();
if (lvt == null || lvt.getVariableIndex() != lvtIndex)
{
continue;
}
Object otherValue = null;
if (two != null) // two is null for if0
{
if (!(other.getPushed().getInstruction() instanceof PushConstantInstruction))
{
continue;
}
PushConstantInstruction pc = (PushConstantInstruction) other.getPushed().getInstruction();
otherValue = pc.getConstant().getObject();
}
// the result of the comparison doesn't matter, only that it always goes the same direction for every invocation
boolean result = doLogicalComparison(value, comp, otherValue);
if (parameter.result != null && parameter.result != result)
{
this.parameters.remove(parameter);
}
else
{
parameter.operations.add(ins);
parameter.result = result;
}
//
// Boolean b = deadOps.get(ins.getInstruction());
// if (b != null)
// {
// if (b != result)
// {
// //deadOps.remove(ins.getInstruction());
// this.parameters.remove(parameter);
// //invalidDeadOps.add(ins.getInstruction());
// }
// }
// else
// {
// deadOps.put(ins.getInstruction(), result);
// }
}
}
//private Map<Instruction, Boolean> deadOps = new HashMap<>();
// private Set<Instruction> invalidDeadOps = new HashSet<>();
private boolean doLogicalComparison(Object value, ComparisonInstruction comparison, Object otherValue) private boolean doLogicalComparison(Object value, ComparisonInstruction comparison, Object otherValue)
{ {
Instruction ins = (Instruction) comparison; Instruction ins = (Instruction) comparison;
@@ -252,93 +386,93 @@ public class ConstantParameter implements Deobfuscator
} }
// find all comparisons of lvtIndex in method and record branch taken // find all comparisons of lvtIndex in method and record branch taken
private List<LogicallyDeadOp> isLogicallyDead(Execution execution, Method method, int lvtIndex, Object value) // private List<LogicallyDeadOp> isLogicallyDead(Execution execution, Method method, int lvtIndex, Object value)
{ // {
List<LogicallyDeadOp> ops = new ArrayList<>(); // List<LogicallyDeadOp> ops = new ArrayList<>();
//
for (Frame frame : execution.processedFrames) // for (Frame frame : execution.processedFrames)
{ // {
if (frame.getMethod() != method) // if (frame.getMethod() != method)
continue; // continue;
//
for (InstructionContext ins : frame.getInstructions()) // for (InstructionContext ins : frame.getInstructions())
{ // {
if (ins.getInstruction() instanceof LVTInstruction) // if (ins.getInstruction() instanceof LVTInstruction)
{ // {
LVTInstruction lvt = (LVTInstruction) ins.getInstruction(); // LVTInstruction lvt = (LVTInstruction) ins.getInstruction();
//
if (lvt.getVariableIndex() == lvtIndex && lvt.store()) // if (lvt.getVariableIndex() == lvtIndex && lvt.store())
{ // {
return null; // return null;
} // }
} // }
//
if (!(ins.getInstruction() instanceof ComparisonInstruction)) // if (!(ins.getInstruction() instanceof ComparisonInstruction))
continue; // continue;
//
// assume that this will always be variable index #paramIndex comp with a constant. // // assume that this will always be variable index #paramIndex comp with a constant.
//
ComparisonInstruction comp = (ComparisonInstruction) ins.getInstruction(); // ComparisonInstruction comp = (ComparisonInstruction) ins.getInstruction();
//
StackContext one, two = null; // StackContext one, two = null;
//
if (comp instanceof If0) // if (comp instanceof If0)
{ // {
one = ins.getPops().get(0); // one = ins.getPops().get(0);
} // }
else if (comp instanceof If) // else if (comp instanceof If)
{ // {
one = ins.getPops().get(0); // one = ins.getPops().get(0);
two = ins.getPops().get(1); // two = ins.getPops().get(1);
} // }
else // else
{ // {
throw new RuntimeException("Unknown comp ins"); // throw new RuntimeException("Unknown comp ins");
} // }
//
// find if one is a lvt ins // // find if one is a lvt ins
LVTInstruction lvt = null; // LVTInstruction lvt = null;
StackContext other = null; // StackContext other = null;
//
if (one.getPushed().getInstruction() instanceof LVTInstruction) // if (one.getPushed().getInstruction() instanceof LVTInstruction)
{ // {
lvt = (LVTInstruction) one.getPushed().getInstruction(); // lvt = (LVTInstruction) one.getPushed().getInstruction();
other = two; // other = two;
} // }
else if (two != null && two.getPushed().getInstruction() instanceof LVTInstruction) // else if (two != null && two.getPushed().getInstruction() instanceof LVTInstruction)
{ // {
lvt = (LVTInstruction) two.getPushed().getInstruction(); // lvt = (LVTInstruction) two.getPushed().getInstruction();
other = one; // other = one;
} // }
//
assert lvt == null || !lvt.store(); // assert lvt == null || !lvt.store();
//
if (lvt == null || lvt.getVariableIndex() != lvtIndex) // if (lvt == null || lvt.getVariableIndex() != lvtIndex)
continue; // continue;
//
Object otherValue = null; // Object otherValue = null;
//
if (two != null) // two is null for if0 // if (two != null) // two is null for if0
{ // {
if (!(other.getPushed().getInstruction() instanceof PushConstantInstruction)) // if (!(other.getPushed().getInstruction() instanceof PushConstantInstruction))
continue; // continue;
//
PushConstantInstruction pc = (PushConstantInstruction) other.getPushed().getInstruction(); // PushConstantInstruction pc = (PushConstantInstruction) other.getPushed().getInstruction();
otherValue = pc.getConstant().getObject(); // otherValue = pc.getConstant().getObject();
} // }
//
// the result of the comparison doesn't matter, only that it always goes the same direction for every invocation // // the result of the comparison doesn't matter, only that it always goes the same direction for every invocation
boolean result = doLogicalComparison(value, comp, otherValue); // boolean result = doLogicalComparison(value, comp, otherValue);
//
LogicallyDeadOp deadOp = new LogicallyDeadOp(); // LogicallyDeadOp deadOp = new LogicallyDeadOp();
deadOp.compCtx = ins; // deadOp.compCtx = ins;
deadOp.branch = result; // deadOp.branch = result;
ops.add(deadOp); // ops.add(deadOp);
} // }
} // }
//
return ops; // return ops;
} // }
private static class MethodLvtPair private static class MethodLvtPair
{ {
@@ -368,20 +502,25 @@ public class ConstantParameter implements Deobfuscator
@Override @Override
public boolean equals(Object obj) public boolean equals(Object obj)
{ {
if (obj == null) { if (obj == null)
{
return false; return false;
} }
if (getClass() != obj.getClass()) { if (getClass() != obj.getClass())
{
return false; return false;
} }
final MethodLvtPair other = (MethodLvtPair) obj; final MethodLvtPair other = (MethodLvtPair) obj;
if (!Objects.equals(this.method, other.method)) { if (!Objects.equals(this.method, other.method))
{
return false; return false;
} }
if (this.lvtIndex != other.lvtIndex) { if (this.lvtIndex != other.lvtIndex)
{
return false; return false;
} }
if (this.paramIndex != other.paramIndex) { if (this.paramIndex != other.paramIndex)
{
return false; return false;
} }
return true; return true;
@@ -389,71 +528,67 @@ public class ConstantParameter implements Deobfuscator
} }
//
private Map<MethodLvtPair, List<LogicallyDeadOp> > deadops = new HashMap<>(); // private Map<MethodLvtPair, List<LogicallyDeadOp> > deadops = new HashMap<>();
private Set<MethodLvtPair> invalidDeadops = new HashSet<>(); // private Set<MethodLvtPair> invalidDeadops = new HashSet<>();
// check every method parameter that we've identified as being passed constants to see if it's logically dead // check every method parameter that we've identified as being passed constants to see if it's logically dead
private void findLogicallyDeadOperations(Execution execution) // private void findLogicallyDeadOperations(Execution execution)
{ // {
outer: // for (ConstantMethodParameter cmp : parameters)
for (ConstantMethodParameter cmp : parameters) // {
{ // for (Method method : cmp.methods)
for (Method method : cmp.methods) // {
{ // MethodLvtPair pair = new MethodLvtPair(method, cmp.lvtIndex, cmp.paramIndex, cmp.value);
MethodLvtPair pair = new MethodLvtPair(method, cmp.lvtIndex, cmp.paramIndex, cmp.value); //
// if (invalidDeadops.contains(pair))
if (invalidDeadops.contains(pair)) // continue;
continue; //
// // the dead comparisons must be the same and branch the same way for every call to this method.
// the dead comparisons must be the same and branch the same way for every call to this method. // List<LogicallyDeadOp> deadOps = isLogicallyDead(execution, method, cmp.lvtIndex, cmp.value);
List<LogicallyDeadOp> deadOps = isLogicallyDead(execution, method, cmp.lvtIndex, cmp.value); //
// if (deadOps == null)
if (deadOps == null) // {
{ // deadops.remove(pair);
deadops.remove(pair); // invalidDeadops.add(pair);
invalidDeadops.add(pair); // continue; // lvt store
continue; // lvt store // }
} //
// if (deadOps.isEmpty())
if (deadOps.isEmpty()) // continue; // no ops to compare
continue; // no ops to compare //
// // this must be per method,lvtindex
// this must be per method,lvtindex // List<LogicallyDeadOp> existing = deadops.get(pair);
List<LogicallyDeadOp> existing = deadops.get(pair); // if (existing != null)
if (existing != null) // if (!existing.equals(deadOps))
if (!existing.equals(deadOps)) // {
{ // // one of the branches taken differs because of the value, skip it
// one of the branches taken differs because of the value, skip it // deadops.remove(pair);
deadops.remove(pair); // invalidDeadops.add(pair);
invalidDeadops.add(pair); // continue;
continue; // }
} // else
else // {
{ // continue;
continue; // }
} //
// deadops.put(pair, deadOps);
deadops.put(pair, deadOps); // }
} // }
} // }
}
// remove logically dead comparisons // remove logically dead comparisons
private int removeDeadOperations() private int removeDeadOperations()
{ {
int count = 0; int count = 0;
for (MethodLvtPair mvp : deadops.keySet()) for (ConstantMethodParameter cmp : parameters)
{ {
List<LogicallyDeadOp> ops = deadops.get(mvp); annotateObfuscatedSignature(cmp);
annotateObfuscatedSignature(mvp); for (InstructionContext ctx : cmp.operations) // comparisons
for (LogicallyDeadOp op : ops)
{ {
InstructionContext ctx = op.compCtx; // comparison
Instruction ins = ctx.getInstruction(); Instruction ins = ctx.getInstruction();
boolean branch = op.branch; // branch that is always taken boolean branch = cmp.result; // branch that is always taken
if (ins.getInstructions() == null) if (ins.getInstructions() == null)
continue; // ins already removed? continue; // ins already removed?
@@ -512,23 +647,25 @@ public class ConstantParameter implements Deobfuscator
private static final Type OBFUSCATED_SIGNATURE = new Type("Lnet/runelite/mapping/ObfuscatedSignature;"); private static final Type OBFUSCATED_SIGNATURE = new Type("Lnet/runelite/mapping/ObfuscatedSignature;");
private void annotateObfuscatedSignature(MethodLvtPair mvp) private void annotateObfuscatedSignature(ConstantMethodParameter parameter)
{ {
Method m = mvp.method; for (Method m : parameter.methods)
Object value = mvp.value; {
Object value = parameter.value;
Attributes attributes = m.getAttributes(); Attributes attributes = m.getAttributes();
Annotations annotations = attributes.getAnnotations(); Annotations annotations = attributes.getAnnotations();
if (annotations != null && annotations.find(OBFUSCATED_SIGNATURE) != null) if (annotations != null && annotations.find(OBFUSCATED_SIGNATURE) != null)
return; return;
Annotation annotation = attributes.addAnnotation(OBFUSCATED_SIGNATURE, "signature", new net.runelite.asm.pool.UTF8(m.getDescriptor().toString())); Annotation annotation = attributes.addAnnotation(OBFUSCATED_SIGNATURE, "signature", new net.runelite.asm.pool.UTF8(m.getDescriptor().toString()));
Element element = new Element(annotation); Element element = new Element(annotation);
element.setType(new Type("garbageValue")); element.setType(new Type("garbageValue"));
element.setValue(new net.runelite.asm.pool.UTF8(value.toString())); element.setValue(new net.runelite.asm.pool.UTF8(value.toString()));
annotation.addElement(element); annotation.addElement(element);
}
} }
@Override @Override
@@ -537,11 +674,17 @@ public class ConstantParameter implements Deobfuscator
group.buildClassGraph(); // required for getMethods in the invoke stuff by execution... group.buildClassGraph(); // required for getMethods in the invoke stuff by execution...
Execution execution = new Execution(group); Execution execution = new Execution(group);
execution.addExecutionVisitor((i) -> findParameters(i));
execution.addExecutionVisitor((i) -> findDeadParameters(i));
execution.populateInitialMethods(); execution.populateInitialMethods();
execution.run(); execution.run();
// execution = new Execution(group);
// execution.addExecutionVisitor((i) -> findDeadParameters(i));
// execution.populateInitialMethods();
// execution.run();
findParameters(execution); //findLogicallyDeadOperations(execution);
findLogicallyDeadOperations(execution);
int count = removeDeadOperations(); int count = removeDeadOperations();
System.out.println("Removed " + count + " logically dead conditional jumps"); System.out.println("Removed " + count + " logically dead conditional jumps");