/* * 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; } 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) { BytecodeArray code = meth.getCode(); ByteArray bytes = code.getBytes(); TranslationAdvice advice = DexTranslationAdvice.THE_ONE; 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(); sb.append("first " + Hex.u2(rmeth.getFirstLabel()) + "\n"); int sz = blocks.size(); for (int i = 0; i < sz; i++) { BasicBlock bb = blocks.get(i); int label = bb.getLabel(); sb.append("block "); sb.append(Hex.u2(label)); sb.append("\n"); IntList preds = rmeth.labelToPredecessors(label); int psz = preds.size(); for (int j = 0; j < psz; j++) { sb.append(" pred "); sb.append(Hex.u2(preds.get(j))); sb.append("\n"); } InsnList il = bb.getInsns(); int ilsz = il.size(); for (int j = 0; j < ilsz; j++) { Insn one = il.get(j); sb.append(" "); sb.append(il.get(j).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 j = 0; j < ssz; j++) { int succ = successors.get(j); 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; } }