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