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