blob: 6919b0967080b8212f3372f900a2eacdf7a4ea58 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.dx.command.dump;
import com.android.dx.cf.attrib.AttCode;
import com.android.dx.cf.code.BasicBlocker;
import com.android.dx.cf.code.ByteBlock;
import com.android.dx.cf.code.ByteBlockList;
import com.android.dx.cf.code.ByteCatchList;
import com.android.dx.cf.code.BytecodeArray;
import com.android.dx.cf.code.ConcreteMethod;
import com.android.dx.cf.code.Ropper;
import com.android.dx.cf.direct.CodeObserver;
import com.android.dx.cf.direct.DirectClassFile;
import com.android.dx.cf.direct.StdAttributeFactory;
import com.android.dx.cf.iface.Member;
import com.android.dx.cf.iface.Method;
import com.android.dx.rop.code.BasicBlock;
import com.android.dx.rop.code.BasicBlockList;
import com.android.dx.rop.code.Insn;
import com.android.dx.rop.code.InsnList;
import com.android.dx.rop.code.RopMethod;
import com.android.dx.rop.code.DexTranslationAdvice;
import com.android.dx.rop.code.TranslationAdvice;
import com.android.dx.rop.code.AccessFlags;
import com.android.dx.rop.cst.CstType;
import com.android.dx.ssa.Optimizer;
import com.android.dx.util.ByteArray;
import com.android.dx.util.Hex;
import com.android.dx.util.IntList;
import java.io.PrintStream;
/**
* Utility to dump basic block info from methods in a human-friendly form.
*/
public class BlockDumper
extends BaseDumper {
/** whether or not to registerize (make rop blocks) */
private boolean rop;
/**
* {@code null-ok;} the class file object being constructed;
* becomes non-null during {@link #dump}
*/
protected DirectClassFile classFile;
/** whether or not to suppress dumping */
protected boolean suppressDump;
/** whether this is the first method being dumped */
private boolean first;
/** whether or not to run the ssa optimziations */
private boolean optimize;
/**
* Dumps the given array, interpreting it as a class file and dumping
* methods with indications of block-level stuff.
*
* @param bytes {@code non-null;} bytes of the (alleged) class file
* @param out {@code non-null;} where to dump to
* @param filePath the file path for the class, excluding any base
* directory specification
* @param rop whether or not to registerize (make rop blocks)
* @param args commandline parsedArgs
*/
public static void dump(byte[] bytes, PrintStream out,
String filePath, boolean rop, Args args) {
BlockDumper bd = new BlockDumper(bytes, out, filePath,
rop, args);
bd.dump();
}
/**
* Constructs an instance. This class is not publicly instantiable.
* Use {@link #dump}.
*/
BlockDumper(byte[] bytes, PrintStream out, String filePath,
boolean rop, Args args) {
super(bytes, out, filePath, args);
this.rop = rop;
this.classFile = null;
this.suppressDump = true;
this.first = true;
this.optimize = args.optimize;
}
/**
* Does the dumping.
*/
public void dump() {
byte[] bytes = getBytes();
ByteArray ba = new ByteArray(bytes);
/*
* First, parse the file completely, so we can safely refer to
* attributes, etc.
*/
classFile = new DirectClassFile(ba, getFilePath(), getStrictParse());
classFile.setAttributeFactory(StdAttributeFactory.THE_ONE);
classFile.getMagic(); // Force parsing to happen.
// Next, reparse it and observe the process.
DirectClassFile liveCf =
new DirectClassFile(ba, getFilePath(), getStrictParse());
liveCf.setAttributeFactory(StdAttributeFactory.THE_ONE);
liveCf.setObserver(this);
liveCf.getMagic(); // Force parsing to happen.
}
/** {@inheritDoc} */
@Override
public void changeIndent(int indentDelta) {
if (!suppressDump) {
super.changeIndent(indentDelta);
}
}
/** {@inheritDoc} */
@Override
public void parsed(ByteArray bytes, int offset, int len, String human) {
if (!suppressDump) {
super.parsed(bytes, offset, len, human);
}
}
/**
* @param name method name
* @return true if this method should be dumped
*/
protected boolean shouldDumpMethod(String name) {
return args.method == null || args.method.equals(name);
}
/** {@inheritDoc} */
@Override
public void startParsingMember(ByteArray bytes, int offset, String name,
String descriptor) {
if (descriptor.indexOf('(') < 0) {
// It's a field, not a method
return;
}
if (!shouldDumpMethod(name)) {
return;
}
// Reset the dump cursor to the start of the method.
setAt(bytes, offset);
suppressDump = false;
if (first) {
first = false;
} else {
parsed(bytes, offset, 0, "\n");
}
parsed(bytes, offset, 0, "method " + name + " " + descriptor);
suppressDump = true;
}
/** {@inheritDoc} */
@Override
public void endParsingMember(ByteArray bytes, int offset, String name,
String descriptor, Member member) {
if (!(member instanceof Method)) {
return;
}
if (!shouldDumpMethod(name)) {
return;
}
if ((member.getAccessFlags() & (AccessFlags.ACC_ABSTRACT |
AccessFlags.ACC_NATIVE)) != 0) {
return;
}
ConcreteMethod meth =
new ConcreteMethod((Method) member, classFile, true, true);
if (rop) {
ropDump(meth);
} else {
regularDump(meth);
}
}
/**
* Does a regular basic block dump.
*
* @param meth {@code non-null;} method data to dump
*/
private void regularDump(ConcreteMethod meth) {
BytecodeArray code = meth.getCode();
ByteArray bytes = code.getBytes();
ByteBlockList list = BasicBlocker.identifyBlocks(meth);
int sz = list.size();
CodeObserver codeObserver = new CodeObserver(bytes, BlockDumper.this);
// Reset the dump cursor to the start of the bytecode.
setAt(bytes, 0);
suppressDump = false;
int byteAt = 0;
for (int i = 0; i < sz; i++) {
ByteBlock bb = list.get(i);
int start = bb.getStart();
int end = bb.getEnd();
if (byteAt < start) {
parsed(bytes, byteAt, start - byteAt,
"dead code " + Hex.u2(byteAt) + ".." + Hex.u2(start));
}
parsed(bytes, start, 0,
"block " + Hex.u2(bb.getLabel()) + ": " +
Hex.u2(start) + ".." + Hex.u2(end));
changeIndent(1);
int len;
for (int j = start; j < end; j += len) {
len = code.parseInstruction(j, codeObserver);
codeObserver.setPreviousOffset(j);
}
IntList successors = bb.getSuccessors();
int ssz = successors.size();
if (ssz == 0) {
parsed(bytes, end, 0, "returns");
} else {
for (int j = 0; j < ssz; j++) {
int succ = successors.get(j);
parsed(bytes, end, 0, "next " + Hex.u2(succ));
}
}
ByteCatchList catches = bb.getCatches();
int csz = catches.size();
for (int j = 0; j < csz; j++) {
ByteCatchList.Item one = catches.get(j);
CstType exceptionClass = one.getExceptionClass();
parsed(bytes, end, 0,
"catch " +
((exceptionClass == CstType.OBJECT) ? "<any>" :
exceptionClass.toHuman()) + " -> " +
Hex.u2(one.getHandlerPc()));
}
changeIndent(-1);
byteAt = end;
}
int end = bytes.size();
if (byteAt < end) {
parsed(bytes, byteAt, end - byteAt,
"dead code " + Hex.u2(byteAt) + ".." + Hex.u2(end));
}
suppressDump = true;
}
/**
* Does a registerizing dump.
*
* @param meth {@code non-null;} method data to dump
*/
private void ropDump(ConcreteMethod meth) {
TranslationAdvice advice = DexTranslationAdvice.THE_ONE;
BytecodeArray code = meth.getCode();
ByteArray bytes = code.getBytes();
RopMethod rmeth = Ropper.convert(meth, advice);
StringBuffer sb = new StringBuffer(2000);
if (optimize) {
boolean isStatic = AccessFlags.isStatic(meth.getAccessFlags());
int paramWidth = computeParamWidth(meth, isStatic);
rmeth =
Optimizer.optimize(rmeth, paramWidth, isStatic, true, advice);
}
BasicBlockList blocks = rmeth.getBlocks();
int[] order = blocks.getLabelsInOrder();
sb.append("first " + Hex.u2(rmeth.getFirstLabel()) + "\n");
for (int label : order) {
BasicBlock bb = blocks.get(blocks.indexOfLabel(label));
sb.append("block ");
sb.append(Hex.u2(label));
sb.append("\n");
IntList preds = rmeth.labelToPredecessors(label);
int psz = preds.size();
for (int i = 0; i < psz; i++) {
sb.append(" pred ");
sb.append(Hex.u2(preds.get(i)));
sb.append("\n");
}
InsnList il = bb.getInsns();
int ilsz = il.size();
for (int i = 0; i < ilsz; i++) {
Insn one = il.get(i);
sb.append(" ");
sb.append(il.get(i).toHuman());
sb.append("\n");
}
IntList successors = bb.getSuccessors();
int ssz = successors.size();
if (ssz == 0) {
sb.append(" returns\n");
} else {
int primary = bb.getPrimarySuccessor();
for (int i = 0; i < ssz; i++) {
int succ = successors.get(i);
sb.append(" next ");
sb.append(Hex.u2(succ));
if ((ssz != 1) && (succ == primary)) {
sb.append(" *");
}
sb.append("\n");
}
}
}
suppressDump = false;
setAt(bytes, 0);
parsed(bytes, 0, bytes.size(), sb.toString());
suppressDump = true;
}
}