BlockDumper.java revision 4b4413ab3d8de5805276cfcde3d7f535d9f64e85
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
17package com.android.dx.command.dump;
18
19import com.android.dx.cf.attrib.AttCode;
20import com.android.dx.cf.code.BasicBlocker;
21import com.android.dx.cf.code.ByteBlock;
22import com.android.dx.cf.code.ByteBlockList;
23import com.android.dx.cf.code.ByteCatchList;
24import com.android.dx.cf.code.BytecodeArray;
25import com.android.dx.cf.code.ConcreteMethod;
26import com.android.dx.cf.code.Ropper;
27import com.android.dx.cf.direct.CodeObserver;
28import com.android.dx.cf.direct.DirectClassFile;
29import com.android.dx.cf.direct.StdAttributeFactory;
30import com.android.dx.cf.iface.Member;
31import com.android.dx.cf.iface.Method;
32import com.android.dx.rop.code.BasicBlock;
33import com.android.dx.rop.code.BasicBlockList;
34import com.android.dx.rop.code.Insn;
35import com.android.dx.rop.code.InsnList;
36import com.android.dx.rop.code.RopMethod;
37import com.android.dx.rop.code.DexTranslationAdvice;
38import com.android.dx.rop.code.TranslationAdvice;
39import com.android.dx.rop.code.AccessFlags;
40import com.android.dx.rop.cst.CstType;
41import com.android.dx.ssa.Optimizer;
42import com.android.dx.util.ByteArray;
43import com.android.dx.util.Hex;
44import com.android.dx.util.IntList;
45
46import java.io.PrintStream;
47
48/**
49 * Utility to dump basic block info from methods in a human-friendly form.
50 */
51public 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