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