Add finer grained control of what register information is printed out

git-svn-id: https://smali.googlecode.com/svn/trunk@625 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
JesusFreke@JesusFreke.com 2010-02-14 20:04:59 +00:00
parent da69b22d6f
commit 1c56c7e750
6 changed files with 342 additions and 39 deletions

View File

@ -30,15 +30,14 @@ package org.jf.baksmali.Adaptors;
import org.jf.baksmali.Adaptors.Format.*;
import org.jf.baksmali.baksmali;
import org.jf.baksmali.main;
import org.jf.dexlib.*;
import org.jf.dexlib.Code.*;
import org.jf.dexlib.Code.Analysis.AnalyzedInstruction;
import org.jf.dexlib.Code.Analysis.MethodAnalyzer;
import org.jf.dexlib.Code.Analysis.RegisterType;
import org.jf.dexlib.Code.Analysis.ValidationException;
import org.jf.dexlib.Debug.DebugInstructionIterator;
import org.jf.dexlib.Code.Instruction;
import org.jf.dexlib.Code.Opcode;
import org.jf.dexlib.Code.OffsetInstruction;
import org.jf.dexlib.Util.AccessFlags;
import org.antlr.stringtemplate.StringTemplateGroup;
import org.antlr.stringtemplate.StringTemplate;
@ -57,6 +56,8 @@ public class MethodDefinition {
private final SparseIntArray sparseSwitchMap;
private final SparseIntArray instructionMap;
private final int registerCount;
public MethodDefinition(StringTemplateGroup stg, ClassDataItem.EncodedMethod encodedMethod) {
this.stg = stg;
this.encodedMethod = encodedMethod;
@ -71,6 +72,8 @@ public class MethodDefinition {
sparseSwitchMap = new SparseIntArray(1);
instructionMap = new SparseIntArray(instructions.length);
registerCount = encodedMethod.codeItem.getRegisterCount();
int currentCodeAddress = 0;
for (int i=0; i<instructions.length; i++) {
AnalyzedInstruction instruction = instructions[i];
@ -91,6 +94,7 @@ public class MethodDefinition {
sparseSwitchMap = null;
instructionMap = null;
methodAnalyzer = null;
registerCount = 0;
}
}
@ -243,18 +247,22 @@ public class MethodDefinition {
}
AnalyzedInstruction[] instructions;
if (baksmali.verboseRegisterInfo) {
if (baksmali.registerInfo != 0) {
instructions = methodAnalyzer.analyze();
ValidationException validationException = methodAnalyzer.getValidationException();
if (validationException != null) {
methodItems.add(new CommentMethodItem(stg, validationException.getMessage(),
methodItems.add(new CommentMethodItem(stg,
String.format("ValidationException: %s" ,validationException.getMessage()),
validationException.getCodeAddress(), Integer.MIN_VALUE));
}
} else {
instructions = methodAnalyzer.makeInstructionArray();
}
BitSet printPreRegister = new BitSet(registerCount);
BitSet printPostRegister = new BitSet(registerCount);
int currentCodeAddress = 0;
for (int i=0; i<instructions.length; i++) {
AnalyzedInstruction instruction = instructions[i];
@ -266,15 +274,51 @@ public class MethodDefinition {
methodItems.add(new BlankMethodItem(stg, currentCodeAddress));
}
if (baksmali.verboseRegisterInfo) {
if (instruction.getPredecessorCount() > 1 || i == 0) {
methodItems.add(new CommentMethodItem(stg, getPreInstructionRegisterString(instruction),
currentCodeAddress, Integer.MIN_VALUE));
}
methodItems.add(new CommentMethodItem(stg, getPostInstructionRegisterString(instruction),
currentCodeAddress, Integer.MAX_VALUE-1));
}
if (baksmali.registerInfo != 0) {
printPreRegister.clear();
printPostRegister.clear();
if ((baksmali.registerInfo & main.ALL) != 0) {
printPreRegister.set(0, registerCount);
printPostRegister.set(0, registerCount);
} else {
if ((baksmali.registerInfo & main.ALLPRE) != 0) {
printPreRegister.set(0, registerCount);
} else {
if ((baksmali.registerInfo & main.ARGS) != 0) {
addArgsRegs(printPreRegister, instruction);
}
if ((baksmali.registerInfo & main.MERGE) != 0) {
addMergeRegs(printPreRegister, instructions, i);
} else if ((baksmali.registerInfo & main.FULLMERGE) != 0 &&
(i == 0 || instruction.isBeginningInstruction())) {
addParamRegs(printPreRegister);
}
}
if ((baksmali.registerInfo & main.ALLPOST) != 0) {
printPostRegister.set(0, registerCount);
} else if ((baksmali.registerInfo & main.DEST) != 0) {
addDestRegs(printPostRegister, instruction);
}
}
if ((baksmali.registerInfo & main.FULLMERGE) != 0) {
addFullMergeRegs(printPreRegister, methodItems, methodAnalyzer, i, currentCodeAddress);
}
if (!printPreRegister.isEmpty()) {
methodItems.add(new CommentMethodItem(stg,
getPreInstructionRegisterString(instruction, printPreRegister),
currentCodeAddress, 99.9));
}
if (!printPostRegister.isEmpty()) {
methodItems.add(new CommentMethodItem(stg,
getPostInstructionRegisterString(instruction, printPostRegister),
currentCodeAddress, 100.1));
}
}
currentCodeAddress += instruction.instruction.getSize(currentCodeAddress);
}
@ -300,13 +344,168 @@ public class MethodDefinition {
return methodItems;
}
private String getPreInstructionRegisterString(AnalyzedInstruction instruction) {
private void addArgsRegs(BitSet printPreRegister, AnalyzedInstruction analyzedInstruction) {
if (analyzedInstruction.instruction instanceof RegisterRangeInstruction) {
RegisterRangeInstruction instruction = (RegisterRangeInstruction)analyzedInstruction.instruction;
printPreRegister.set(instruction.getStartRegister(),
instruction.getStartRegister() + instruction.getRegCount());
} else if (analyzedInstruction.instruction instanceof FiveRegisterInstruction) {
FiveRegisterInstruction instruction = (FiveRegisterInstruction)analyzedInstruction.instruction;
int regCount = instruction.getRegCount();
switch (regCount) {
case 5:
printPreRegister.set(instruction.getRegisterA());
//fall through
case 4:
printPreRegister.set(instruction.getRegisterG());
//fall through
case 3:
printPreRegister.set(instruction.getRegisterF());
//fall through
case 2:
printPreRegister.set(instruction.getRegisterE());
//fall through
case 1:
printPreRegister.set(instruction.getRegisterD());
}
} else if (analyzedInstruction.instruction instanceof ThreeRegisterInstruction) {
ThreeRegisterInstruction instruction = (ThreeRegisterInstruction)analyzedInstruction.instruction;
printPreRegister.set(instruction.getRegisterA());
printPreRegister.set(instruction.getRegisterB());
printPreRegister.set(instruction.getRegisterC());
} else if (analyzedInstruction.instruction instanceof TwoRegisterInstruction) {
TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.instruction;
printPreRegister.set(instruction.getRegisterA());
printPreRegister.set(instruction.getRegisterB());
} else if (analyzedInstruction.instruction instanceof SingleRegisterInstruction) {
SingleRegisterInstruction instruction = (SingleRegisterInstruction)analyzedInstruction.instruction;
printPreRegister.set(instruction.getRegisterA());
}
}
private void addFullMergeRegs(BitSet printPreRegister, List<MethodItem> methodItems, MethodAnalyzer methodAnalyzer,
int instructionNum, int currentCodeAddress) {
if (instructionNum == 0) {
return;
}
//MethodAnalyzer cached the analyzedInstructions, so we're not actually re-analyzing, just getting the
//instructions that have already been analyzed
AnalyzedInstruction[] instructions = methodAnalyzer.analyze();
AnalyzedInstruction instruction = instructions[instructionNum];
if (instruction.getPredecessorCount() <= 1) {
return;
}
StringBuffer sb = new StringBuffer();
for (int registerNum=0; registerNum<registerCount; registerNum++) {
sb.setLength(0);
sb.append(RegisterFormatter.formatRegister(encodedMethod.codeItem, registerNum));
sb.append('=');
sb.append(instruction.getPreInstructionRegisterType(registerNum));
sb.append(":merge{");
RegisterType mergedRegisterType = instruction.getPreInstructionRegisterType(registerNum);
boolean addRegister = false;
boolean first = true;
for (AnalyzedInstruction predecessor: instruction.getPredecessors()) {
RegisterType predecessorRegisterType = predecessor.getPostInstructionRegisterType(registerNum);
if (!first) {
sb.append(',');
if (!addRegister && mergedRegisterType != predecessorRegisterType) {
addRegister = true;
}
}
if (predecessor.getInstructionIndex() == -1) {
//the fake "StartOfMethod" instruction
sb.append("Start:");
} else {
sb.append("0x");
sb.append(Integer.toHexString(methodAnalyzer.getInstructionAddress(predecessor)));
sb.append(':');
}
sb.append(predecessor.getPostInstructionRegisterType(registerNum).toString());
first = false;
}
if (!addRegister) {
continue;
}
sb.append("}");
methodItems.add(new CommentMethodItem(stg, sb.toString(), currentCodeAddress, 99.8));
printPreRegister.clear(registerNum);
}
}
private void addMergeRegs(BitSet printPreRegister, AnalyzedInstruction instructions[], int instructionNum) {
//MethodAnalyzer cached the analyzedInstructions, so we're not actually re-analyzing, just getting the
//instructions that have already been analyzed
AnalyzedInstruction instruction = instructions[instructionNum];
if (instructionNum == 0) {
addParamRegs(printPreRegister);
} else {
if (instruction.isBeginningInstruction()) {
addParamRegs(printPreRegister);
}
if (instruction.getPredecessorCount() <= 1) {
//in the common case of an instruction that only has a single predecessor which is the previous
//instruction, the pre-instruction registers will always match the previous instruction's
//post-instruction registers
return;
}
for (int registerNum=0; registerNum<registerCount; registerNum++) {
RegisterType mergedRegisterType = instruction.getPreInstructionRegisterType(registerNum);
for (AnalyzedInstruction predecessor: instruction.getPredecessors()) {
if (predecessor.getPostInstructionRegisterType(registerNum) != mergedRegisterType) {
printPreRegister.set(registerNum);
continue;
}
}
}
}
}
private void addParamRegs(BitSet printPreRegister) {
int registerCount = encodedMethod.codeItem.getRegisterCount();
int parameterRegisterCount = encodedMethod.method.getPrototype().getParameterRegisterCount();
if ((encodedMethod.accessFlags & AccessFlags.STATIC.getValue()) == 0) {
parameterRegisterCount++;
}
printPreRegister.set(registerCount-parameterRegisterCount, registerCount);
}
private void addDestRegs(BitSet printPostRegister, AnalyzedInstruction analyzedInstruction) {
for (int registerNum=0; registerNum<registerCount; registerNum++) {
if (analyzedInstruction.getPreInstructionRegisterType(registerNum) !=
analyzedInstruction.getPostInstructionRegisterType(registerNum)) {
printPostRegister.set(registerNum);
}
}
}
private String getPreInstructionRegisterString(AnalyzedInstruction instruction, BitSet registers) {
StringBuilder sb = new StringBuilder();
for (int i=0; i<instruction.getRegisterCount(); i++) {
RegisterType registerType = instruction.getPreInstructionRegisterType(i);
sb.append("v");
sb.append(i);
for (int registerNum = registers.nextSetBit(0); registerNum >= 0;
registerNum = registers.nextSetBit(registerNum + 1)) {
RegisterType registerType = instruction.getPreInstructionRegisterType(registerNum);
sb.append(RegisterFormatter.formatRegister(encodedMethod.codeItem,registerNum));
sb.append("=");
if (registerType == null) {
sb.append("null");
@ -319,22 +518,26 @@ public class MethodDefinition {
return sb.toString();
}
private String getPostInstructionRegisterString(AnalyzedInstruction instruction) {
private String getPostInstructionRegisterString(AnalyzedInstruction instruction, BitSet registers) {
StringBuilder sb = new StringBuilder();
for (int i=0; i<instruction.getRegisterCount(); i++) {
RegisterType registerType = instruction.getPostInstructionRegisterType(i);
sb.append("v");
sb.append(i);
for (int registerNum = registers.nextSetBit(0); registerNum >= 0;
registerNum = registers.nextSetBit(registerNum + 1)) {
RegisterType registerType = instruction.getPostInstructionRegisterType(registerNum);
sb.append(RegisterFormatter.formatRegister(encodedMethod.codeItem,registerNum));
sb.append("=");
sb.append(registerType.toString());
if (registerType == null) {
sb.append("null");
} else {
sb.append(registerType.toString());
}
sb.append(";");
}
return sb.toString();
}
private void addTries(List<MethodItem> methodItems) {
if (encodedMethod.codeItem == null || encodedMethod.codeItem.getTries() == null) {
return;

View File

@ -45,23 +45,23 @@ public class baksmali {
public static boolean useLocalsDirective = false;
public static boolean useSequentialLabels = false;
public static boolean outputDebugInfo = true;
public static boolean verboseRegisterInfo = false;
public static int registerInfo = 0;
public static String bootClassPath;
public static DeodexUtil deodexUtil = null;
public static void disassembleDexFile(DexFile dexFile, Deodexerant deodexerant, String outputDirectory,
String bootClassPath, boolean noParameterRegisters,
boolean useLocalsDirective, boolean useSequentialLabels,
boolean outputDebugInfo, boolean verboseRegisterInfo)
boolean outputDebugInfo, int registerInfo)
{
baksmali.noParameterRegisters = noParameterRegisters;
baksmali.useLocalsDirective = useLocalsDirective;
baksmali.useSequentialLabels = useSequentialLabels;
baksmali.outputDebugInfo = outputDebugInfo;
baksmali.verboseRegisterInfo = verboseRegisterInfo;
baksmali.registerInfo = registerInfo;
baksmali.bootClassPath = bootClassPath;
if (verboseRegisterInfo) {
if (registerInfo != 0) {
ClassPath.InitializeClassPath(bootClassPath==null?null:bootClassPath.split(":"), dexFile);
}

View File

@ -31,6 +31,14 @@ public class main {
private static final Options options;
public static final int ALL = 1;
public static final int ALLPRE = 2;
public static final int ALLPOST = 4;
public static final int ARGS = 8;
public static final int DEST = 16;
public static final int MERGE = 32;
public static final int FULLMERGE = 64;
static {
options = new Options();
buildOptions();
@ -75,7 +83,8 @@ public class main {
boolean useLocalsDirective = false;
boolean useSequentialLabels = false;
boolean outputDebugInfo = true;
boolean verboseRegisterInfo = false;
int registerInfo = 0;
String outputDirectory = "out";
String dumpFileName = null;
@ -147,7 +156,36 @@ public class main {
}
if (commandLine.hasOption("r")) {
verboseRegisterInfo = true;
String[] values = commandLine.getOptionValues('r');
if (values == null || values.length == 0) {
registerInfo = ARGS | DEST | MERGE;
} else {
for (String value: values) {
if (value.equalsIgnoreCase("ALL")) {
registerInfo |= ALL;
} else if (value.equalsIgnoreCase("ALLPRE")) {
registerInfo |= ALLPRE;
} else if (value.equalsIgnoreCase("ALLPOST")) {
registerInfo |= ALLPOST;
} else if (value.equalsIgnoreCase("ARGS")) {
registerInfo |= ARGS;
} else if (value.equalsIgnoreCase("DEST")) {
registerInfo |= DEST;
} else if (value.equalsIgnoreCase("MERGE")) {
registerInfo |= MERGE;
} else if (value.equalsIgnoreCase("FULLMERGE")) {
registerInfo |= FULLMERGE;
} else {
usage();
return;
}
}
if ((registerInfo & FULLMERGE) != 0) {
registerInfo &= ~MERGE;
}
}
}
if (commandLine.hasOption("c")) {
@ -210,7 +248,7 @@ public class main {
if (disassemble) {
baksmali.disassembleDexFile(dexFile, deodexerant, outputDirectory, bootClassPath, noParameterRegisters,
useLocalsDirective, useSequentialLabels, outputDebugInfo, verboseRegisterInfo);
useLocalsDirective, useSequentialLabels, outputDebugInfo, registerInfo);
}
if ((doDump || write) && !dexFile.isOdex()) {
@ -238,6 +276,7 @@ public class main {
*/
private static void usage() {
HelpFormatter formatter = new HelpFormatter();
formatter.setWidth(100);
formatter.printHelp("java -jar baksmali.jar [options] <dex-file>",
"disassembles and/or dumps a dex file", options, "");
}
@ -319,8 +358,18 @@ public class main {
.withDescription("don't write out debug info (.local, .param, .line, etc.)")
.create("b");
Option verboseRegisterInfoOption = OptionBuilder.withLongOpt("verbose-registers")
.withDescription("print verbose register type information for each instruction")
Option registerInfoOption = OptionBuilder.withLongOpt("register-info")
.hasOptionalArgs()
.withArgName("REGISTER_INFO_TYPES")
.withValueSeparator(',')
.withDescription("print the specificed type(s) of register information for each instruction. " +
"\"ARGS,DEST,MERGE\" is the default if no types are specified.\nValid values are:\nALL: all " +
"pre- and post-instruction registers.\nALLPRE: all pre-instruction registers\nALLPOST: all " +
"post-instruction registers\nARGS: any pre-instruction registers used as arguments to the " +
"instruction\nDEST: the post-instruction destination register, if any\nMERGE: Any " +
"pre-instruction register has been merged from more than 1 different post-instruction " +
"register from its predecessors\nFULLMERGE: For each register that would be printed by " +
"MERGE, also show the incoming register types that were merged")
.create("r");
Option classPathOption = OptionBuilder.withLongOpt("bootclasspath")
@ -342,7 +391,7 @@ public class main {
options.addOption(useLocalsOption);
options.addOption(sequentialLabelsOption);
options.addOption(noDebugInfoOption);
options.addOption(verboseRegisterInfoOption);
options.addOption(registerInfoOption);
options.addOption(classPathOption);
}
}

View File

@ -6,7 +6,9 @@ import org.jf.dexlib.ItemType;
import org.jf.dexlib.MethodIdItem;
import org.jf.dexlib.Util.ExceptionWithContext;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class AnalyzedInstruction {
/**
@ -63,7 +65,24 @@ public class AnalyzedInstruction {
return predecessors.size();
}
public List<AnalyzedInstruction> getPredecessors() {
return Collections.unmodifiableList(predecessors);
}
private boolean checkPredecessorSorted(AnalyzedInstruction predecessor) {
if (predecessors.size() == 0) {
return true;
}
if (predecessor.getInstructionIndex() <= predecessors.getLast().getInstructionIndex()) {
return false;
}
return true;
}
protected void addPredecessor(AnalyzedInstruction predecessor) {
assert checkPredecessorSorted(predecessor);
predecessors.add(predecessor);
}
@ -80,6 +99,34 @@ public class AnalyzedInstruction {
return true;
}
public int getSuccessorCount() {
return successors.size();
}
public List<AnalyzedInstruction> getSuccesors() {
return Collections.unmodifiableList(successors);
}
/**
* Is this instruction a "beginning instruction". A beginning instruction is defined to be an instruction
* that can be the first successfully executed instruction in the method. The first instruction is always a
* beginning instruction. If the first instruction can throw an exception, and is covered by a try block, then
* the first instruction of any exception handler for that try block is also a beginning instruction. And likewise,
* if any of those instructions can throw an exception and are covered by try blocks, the first instruction of the
* corresponding exception handler is a beginning instruction, etc.
* @return a boolean value indicating whether this instruction is a beginning instruction
*/
public boolean isBeginningInstruction() {
if (predecessors.size() == 0) {
return false;
}
if (predecessors.getFirst().instructionIndex == -1) {
return true;
}
return false;
}
/*
* Sets the "post-instruction" register type as indicated. This should only be used to set
* the method parameter types for the "start of method" instruction, or to set the register

View File

@ -571,12 +571,12 @@ public class ClassPath {
if (interfaces != null) {
for (TypeIdItem interfaceType: interfaces.getTypes()) {
ClassDef interfaceDef = ClassPath.getClassDef(interfaceType.getTypeDescriptor());
assert interfaceDef.isInterface;
assert interfaceDef.isInterface();
implementedInterfaceSet.add(interfaceDef);
interfaceDef = interfaceDef.getSuperclass();
while (!interfaceDef.getClassType().equals("Ljava/lang/Object;")) {
assert interfaceDef.isInterface;
assert interfaceDef.isInterface();
implementedInterfaceSet.add(interfaceDef);
interfaceDef = interfaceDef.getSuperclass();
}

View File

@ -183,6 +183,10 @@ public class MethodAnalyzer {
(encodedMethod.accessFlags & AccessFlags.CONSTRUCTOR.getValue()) != 0;
}
public AnalyzedInstruction getStartOfMethod() {
return startOfMethod;
}
public AnalyzedInstruction[] makeInstructionArray() {
AnalyzedInstruction[] instructionArray = new AnalyzedInstruction[instructions.size()];
for (int i=0; i<instructions.size(); i++) {
@ -214,7 +218,7 @@ public class MethodAnalyzer {
return registerTypes;
}
private int getInstructionAddress(AnalyzedInstruction instruction) {
public int getInstructionAddress(AnalyzedInstruction instruction) {
return instructions.keyAt(instruction.instructionIndex);
}