Home | History | Annotate | Download | only in dump
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.dx.command.dump;
     18 
     19 import com.android.dx.cf.attrib.AttCode;
     20 import com.android.dx.cf.code.BasicBlocker;
     21 import com.android.dx.cf.code.ByteBlock;
     22 import com.android.dx.cf.code.ByteBlockList;
     23 import com.android.dx.cf.code.ByteCatchList;
     24 import com.android.dx.cf.code.BytecodeArray;
     25 import com.android.dx.cf.code.ConcreteMethod;
     26 import com.android.dx.cf.code.Ropper;
     27 import com.android.dx.cf.direct.CodeObserver;
     28 import com.android.dx.cf.direct.DirectClassFile;
     29 import com.android.dx.cf.direct.StdAttributeFactory;
     30 import com.android.dx.cf.iface.Member;
     31 import com.android.dx.cf.iface.Method;
     32 import com.android.dx.rop.code.BasicBlock;
     33 import com.android.dx.rop.code.BasicBlockList;
     34 import com.android.dx.rop.code.Insn;
     35 import com.android.dx.rop.code.InsnList;
     36 import com.android.dx.rop.code.RopMethod;
     37 import com.android.dx.rop.code.DexTranslationAdvice;
     38 import com.android.dx.rop.code.TranslationAdvice;
     39 import com.android.dx.rop.code.AccessFlags;
     40 import com.android.dx.rop.cst.CstType;
     41 import com.android.dx.ssa.Optimizer;
     42 import com.android.dx.util.ByteArray;
     43 import com.android.dx.util.Hex;
     44 import com.android.dx.util.IntList;
     45 
     46 import java.io.PrintStream;
     47 
     48 /**
     49  * Utility to dump basic block info from methods in a human-friendly form.
     50  */
     51 public class BlockDumper
     52         extends BaseDumper {
     53     /** whether or not to registerize (make rop blocks) */
     54     private boolean rop;
     55 
     56     /**
     57      * {@code null-ok;} the class file object being constructed;
     58      * becomes non-null during {@link #dump}
     59      */
     60     protected DirectClassFile classFile;
     61 
     62     /** whether or not to suppress dumping */
     63     protected boolean suppressDump;
     64 
     65     /** whether this is the first method being dumped */
     66     private boolean first;
     67 
     68     /** whether or not to run the ssa optimziations */
     69     private boolean optimize;
     70 
     71     /**
     72      * Dumps the given array, interpreting it as a class file and dumping
     73      * methods with indications of block-level stuff.
     74      *
     75      * @param bytes {@code non-null;} bytes of the (alleged) class file
     76      * @param out {@code non-null;} where to dump to
     77      * @param filePath the file path for the class, excluding any base
     78      * directory specification
     79      * @param rop whether or not to registerize (make rop blocks)
     80      * @param args commandline parsedArgs
     81      */
     82     public static void dump(byte[] bytes, PrintStream out,
     83                             String filePath, boolean rop, Args args) {
     84         BlockDumper bd =
     85             new BlockDumper(bytes, out, filePath,
     86                             rop, args);
     87         bd.dump();
     88     }
     89 
     90     /**
     91      * Constructs an instance. This class is not publicly instantiable.
     92      * Use {@link #dump}.
     93      */
     94     BlockDumper(byte[] bytes, PrintStream out,
     95                         String filePath,
     96                         boolean rop, Args args) {
     97         super(bytes, out, filePath, args);
     98 
     99         this.rop = rop;
    100         this.classFile = null;
    101         this.suppressDump = true;
    102         this.first = true;
    103         this.optimize = args.optimize;
    104     }
    105 
    106     /**
    107      * Does the dumping.
    108      */
    109     public void dump() {
    110         byte[] bytes = getBytes();
    111         ByteArray ba = new ByteArray(bytes);
    112 
    113         /*
    114          * First, parse the file completely, so we can safely refer to
    115          * attributes, etc.
    116          */
    117         classFile = new DirectClassFile(ba, getFilePath(), getStrictParse());
    118         classFile.setAttributeFactory(StdAttributeFactory.THE_ONE);
    119         classFile.getMagic(); // Force parsing to happen.
    120 
    121         // Next, reparse it and observe the process.
    122         DirectClassFile liveCf =
    123             new DirectClassFile(ba, getFilePath(), getStrictParse());
    124         liveCf.setAttributeFactory(StdAttributeFactory.THE_ONE);
    125         liveCf.setObserver(this);
    126         liveCf.getMagic(); // Force parsing to happen.
    127     }
    128 
    129     /** {@inheritDoc} */
    130     @Override
    131     public void changeIndent(int indentDelta) {
    132         if (!suppressDump) {
    133             super.changeIndent(indentDelta);
    134         }
    135     }
    136 
    137     /** {@inheritDoc} */
    138     @Override
    139     public void parsed(ByteArray bytes, int offset, int len, String human) {
    140         if (!suppressDump) {
    141             super.parsed(bytes, offset, len, human);
    142         }
    143     }
    144 
    145     /**
    146      * @param name method name
    147      * @return true if this method should be dumped
    148      */
    149     protected boolean shouldDumpMethod(String name) {
    150         return args.method == null || args.method.equals(name);
    151     }
    152 
    153     /** {@inheritDoc} */
    154     @Override
    155     public void startParsingMember(ByteArray bytes, int offset, String name,
    156                                    String descriptor) {
    157         if (descriptor.indexOf('(') < 0) {
    158             // It's a field, not a method
    159             return;
    160         }
    161 
    162         if (!shouldDumpMethod(name)) {
    163             return;
    164         }
    165 
    166         // Reset the dump cursor to the start of the method.
    167         setAt(bytes, offset);
    168 
    169         suppressDump = false;
    170 
    171         if (first) {
    172             first = false;
    173         } else {
    174             parsed(bytes, offset, 0, "\n");
    175         }
    176 
    177         parsed(bytes, offset, 0, "method " + name + " " + descriptor);
    178         suppressDump = true;
    179     }
    180 
    181     /** {@inheritDoc} */
    182     @Override
    183     public void endParsingMember(ByteArray bytes, int offset, String name,
    184                                  String descriptor, Member member) {
    185         if (!(member instanceof Method)) {
    186             return;
    187         }
    188 
    189         if (!shouldDumpMethod(name)) {
    190             return;
    191         }
    192 
    193         ConcreteMethod meth = new ConcreteMethod((Method) member, classFile,
    194                                                  true, true);
    195 
    196         if (rop) {
    197             ropDump(meth);
    198         } else {
    199             regularDump(meth);
    200         }
    201     }
    202 
    203     /**
    204      * Does a regular basic block dump.
    205      *
    206      * @param meth {@code non-null;} method data to dump
    207      */
    208     private void regularDump(ConcreteMethod meth) {
    209         BytecodeArray code = meth.getCode();
    210         ByteArray bytes = code.getBytes();
    211         ByteBlockList list = BasicBlocker.identifyBlocks(meth);
    212         int sz = list.size();
    213         CodeObserver codeObserver = new CodeObserver(bytes, BlockDumper.this);
    214 
    215         // Reset the dump cursor to the start of the bytecode
    216         setAt(bytes, 0);
    217 
    218         suppressDump = false;
    219 
    220         int byteAt = 0;
    221         for (int i = 0; i < sz; i++) {
    222             ByteBlock bb = list.get(i);
    223             int start = bb.getStart();
    224             int end = bb.getEnd();
    225 
    226             if (byteAt < start) {
    227                 parsed(bytes, byteAt, start - byteAt,
    228                        "dead code " + Hex.u2(byteAt) + ".." + Hex.u2(start));
    229             }
    230 
    231             parsed(bytes, start, 0,
    232                    "block " + Hex.u2(bb.getLabel()) + ": " +
    233                    Hex.u2(start) + ".." + Hex.u2(end));
    234             changeIndent(1);
    235 
    236             int len;
    237             for (int j = start; j < end; j += len) {
    238                 len = code.parseInstruction(j, codeObserver);
    239                 codeObserver.setPreviousOffset(j);
    240             }
    241 
    242             IntList successors = bb.getSuccessors();
    243             int ssz = successors.size();
    244             if (ssz == 0) {
    245                 parsed(bytes, end, 0, "returns");
    246             } else {
    247                 for (int j = 0; j < ssz; j++) {
    248                     int succ = successors.get(j);
    249                     parsed(bytes, end, 0, "next " + Hex.u2(succ));
    250                 }
    251             }
    252 
    253             ByteCatchList catches = bb.getCatches();
    254             int csz = catches.size();
    255             for (int j = 0; j < csz; j++) {
    256                 ByteCatchList.Item one = catches.get(j);
    257                 CstType exceptionClass = one.getExceptionClass();
    258                 parsed(bytes, end, 0,
    259                        "catch " +
    260                        ((exceptionClass == CstType.OBJECT) ? "<any>" :
    261                         exceptionClass.toHuman()) + " -> " +
    262                        Hex.u2(one.getHandlerPc()));
    263             }
    264 
    265             changeIndent(-1);
    266             byteAt = end;
    267         }
    268 
    269         int end = bytes.size();
    270         if (byteAt < end) {
    271             parsed(bytes, byteAt, end - byteAt,
    272                    "dead code " + Hex.u2(byteAt) + ".." + Hex.u2(end));
    273         }
    274 
    275         suppressDump = true;
    276     }
    277 
    278     /**
    279      * Does a registerizing dump.
    280      *
    281      * @param meth {@code non-null;} method data to dump
    282      */
    283     private void ropDump(ConcreteMethod meth) {
    284         BytecodeArray code = meth.getCode();
    285         ByteArray bytes = code.getBytes();
    286 
    287         TranslationAdvice advice = DexTranslationAdvice.THE_ONE;
    288 
    289         RopMethod rmeth =
    290             Ropper.convert(meth, advice);
    291         StringBuffer sb = new StringBuffer(2000);
    292 
    293         if (optimize) {
    294             boolean isStatic = AccessFlags.isStatic(meth.getAccessFlags());
    295 
    296             int paramWidth = computeParamWidth(meth, isStatic);
    297             rmeth = Optimizer.optimize(rmeth, paramWidth, isStatic, true,
    298                     advice);
    299         }
    300 
    301         BasicBlockList blocks = rmeth.getBlocks();
    302 
    303         sb.append("first " + Hex.u2(rmeth.getFirstLabel()) + "\n");
    304 
    305         int sz = blocks.size();
    306         for (int i = 0; i < sz; i++) {
    307             BasicBlock bb = blocks.get(i);
    308             int label = bb.getLabel();
    309             sb.append("block ");
    310             sb.append(Hex.u2(label));
    311             sb.append("\n");
    312 
    313             IntList preds = rmeth.labelToPredecessors(label);
    314             int psz = preds.size();
    315             for (int j = 0; j < psz; j++) {
    316                 sb.append("  pred ");
    317                 sb.append(Hex.u2(preds.get(j)));
    318                 sb.append("\n");
    319             }
    320 
    321             InsnList il = bb.getInsns();
    322             int ilsz = il.size();
    323             for (int j = 0; j < ilsz; j++) {
    324                 Insn one = il.get(j);
    325                 sb.append("  ");
    326                 sb.append(il.get(j).toHuman());
    327                 sb.append("\n");
    328             }
    329 
    330             IntList successors = bb.getSuccessors();
    331             int ssz = successors.size();
    332             if (ssz == 0) {
    333                 sb.append("  returns\n");
    334             } else {
    335                 int primary = bb.getPrimarySuccessor();
    336                 for (int j = 0; j < ssz; j++) {
    337                     int succ = successors.get(j);
    338                     sb.append("  next ");
    339                     sb.append(Hex.u2(succ));
    340 
    341                     if ((ssz != 1) && (succ == primary)) {
    342                         sb.append(" *");
    343                     }
    344 
    345                     sb.append("\n");
    346                 }
    347             }
    348         }
    349 
    350         suppressDump = false;
    351         setAt(bytes, 0);
    352         parsed(bytes, 0, bytes.size(), sb.toString());
    353         suppressDump = true;
    354     }
    355 }
    356