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.dex.file;
18
19import com.android.dex.util.ByteArrayByteInput;
20import com.android.dex.util.ByteInput;
21import com.android.dex.util.ExceptionWithContext;
22import com.android.dex.Leb128;
23import com.android.dx.dex.code.DalvCode;
24import com.android.dx.dex.code.DalvInsnList;
25import com.android.dx.dex.code.LocalList;
26import com.android.dx.dex.code.PositionList;
27import static com.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_LINE;
28import static com.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_PC;
29import static com.android.dx.dex.file.DebugInfoConstants.DBG_END_LOCAL;
30import static com.android.dx.dex.file.DebugInfoConstants.DBG_END_SEQUENCE;
31import static com.android.dx.dex.file.DebugInfoConstants.DBG_FIRST_SPECIAL;
32import static com.android.dx.dex.file.DebugInfoConstants.DBG_LINE_BASE;
33import static com.android.dx.dex.file.DebugInfoConstants.DBG_LINE_RANGE;
34import static com.android.dx.dex.file.DebugInfoConstants.DBG_RESTART_LOCAL;
35import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_EPILOGUE_BEGIN;
36import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_FILE;
37import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_PROLOGUE_END;
38import static com.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL;
39import static com.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL_EXTENDED;
40import com.android.dx.rop.cst.CstMethodRef;
41import com.android.dx.rop.cst.CstString;
42import com.android.dx.rop.type.Prototype;
43import com.android.dx.rop.type.StdTypeList;
44import com.android.dx.rop.type.Type;
45import java.io.IOException;
46import java.util.ArrayList;
47import java.util.List;
48
49/**
50 * A decoder for the dex debug info state machine format.
51 * This code exists mostly as a reference implementation and test for
52 * for the {@code DebugInfoEncoder}
53 */
54public class DebugInfoDecoder {
55    /** encoded debug info */
56    private final byte[] encoded;
57
58    /** positions decoded */
59    private final ArrayList<PositionEntry> positions;
60
61    /** locals decoded */
62    private final ArrayList<LocalEntry> locals;
63
64    /** size of code block in code units */
65    private final int codesize;
66
67    /** indexed by register, the last local variable live in a reg */
68    private final LocalEntry[] lastEntryForReg;
69
70    /** method descriptor of method this debug info is for */
71    private final Prototype desc;
72
73    /** true if method is static */
74    private final boolean isStatic;
75
76    /** dex file this debug info will be stored in */
77    private final DexFile file;
78
79    /**
80     * register size, in register units, of the register space
81     * used by this method
82     */
83    private final int regSize;
84
85    /** current decoding state: line number */
86    private int line = 1;
87
88    /** current decoding state: bytecode address */
89    private int address = 0;
90
91    /** string index of the string "this" */
92    private final int thisStringIdx;
93
94    /**
95     * Constructs an instance.
96     *
97     * @param encoded encoded debug info
98     * @param codesize size of code block in code units
99     * @param regSize register size, in register units, of the register space
100     * used by this method
101     * @param isStatic true if method is static
102     * @param ref method descriptor of method this debug info is for
103     * @param file dex file this debug info will be stored in
104     */
105    DebugInfoDecoder(byte[] encoded, int codesize, int regSize,
106            boolean isStatic, CstMethodRef ref, DexFile file) {
107        if (encoded == null) {
108            throw new NullPointerException("encoded == null");
109        }
110
111        this.encoded = encoded;
112        this.isStatic = isStatic;
113        this.desc = ref.getPrototype();
114        this.file = file;
115        this.regSize = regSize;
116
117        positions = new ArrayList<PositionEntry>();
118        locals = new ArrayList<LocalEntry>();
119        this.codesize = codesize;
120        lastEntryForReg = new LocalEntry[regSize];
121
122        int idx = -1;
123
124        try {
125            idx = file.getStringIds().indexOf(new CstString("this"));
126        } catch (IllegalArgumentException ex) {
127            /*
128             * Silently tolerate not finding "this". It just means that
129             * no method has local variable info that looks like
130             * a standard instance method.
131             */
132        }
133
134        thisStringIdx = idx;
135    }
136
137    /**
138     * An entry in the resulting postions table
139     */
140    static private class PositionEntry {
141        /** bytecode address */
142        public int address;
143
144        /** line number */
145        public int line;
146
147        public PositionEntry(int address, int line) {
148            this.address = address;
149            this.line = line;
150        }
151    }
152
153    /**
154     * An entry in the resulting locals table
155     */
156    static private class LocalEntry {
157        /** address of event */
158        public int address;
159
160        /** {@code true} iff it's a local start */
161        public boolean isStart;
162
163        /** register number */
164        public int reg;
165
166        /** index of name in strings table */
167        public int nameIndex;
168
169        /** index of type in types table */
170        public int typeIndex;
171
172        /** index of type signature in strings table */
173        public int signatureIndex;
174
175        public LocalEntry(int address, boolean isStart, int reg, int nameIndex,
176                int typeIndex, int signatureIndex) {
177            this.address        = address;
178            this.isStart        = isStart;
179            this.reg            = reg;
180            this.nameIndex      = nameIndex;
181            this.typeIndex      = typeIndex;
182            this.signatureIndex = signatureIndex;
183        }
184
185        public String toString() {
186            return String.format("[%x %s v%d %04x %04x %04x]",
187                    address, isStart ? "start" : "end", reg,
188                    nameIndex, typeIndex, signatureIndex);
189        }
190    }
191
192    /**
193     * Gets the decoded positions list.
194     * Valid after calling {@code decode}.
195     *
196     * @return positions list in ascending address order.
197     */
198    public List<PositionEntry> getPositionList() {
199        return positions;
200    }
201
202    /**
203     * Gets the decoded locals list, in ascending start-address order.
204     * Valid after calling {@code decode}.
205     *
206     * @return locals list in ascending address order.
207     */
208    public List<LocalEntry> getLocals() {
209        return locals;
210    }
211
212    /**
213     * Decodes the debug info sequence.
214     */
215    public void decode() {
216        try {
217            decode0();
218        } catch (Exception ex) {
219            throw ExceptionWithContext.withContext(ex,
220                    "...while decoding debug info");
221        }
222    }
223
224    /**
225     * Reads a string index. String indicies are offset by 1, and a 0 value
226     * in the stream (-1 as returned by this method) means "null"
227     *
228     * @return index into file's string ids table, -1 means null
229     * @throws IOException
230     */
231    private int readStringIndex(ByteInput bs) throws IOException {
232        int offsetIndex = Leb128.readUnsignedLeb128(bs);
233
234        return offsetIndex - 1;
235    }
236
237    /**
238     * Gets the register that begins the method's parameter range (including
239     * the 'this' parameter for non-static methods). The range continues until
240     * {@code regSize}
241     *
242     * @return register as noted above.
243     */
244    private int getParamBase() {
245        return regSize
246                - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1);
247    }
248
249    private void decode0() throws IOException {
250        ByteInput bs = new ByteArrayByteInput(encoded);
251
252        line = Leb128.readUnsignedLeb128(bs);
253        int szParams = Leb128.readUnsignedLeb128(bs);
254        StdTypeList params = desc.getParameterTypes();
255        int curReg = getParamBase();
256
257        if (szParams != params.size()) {
258            throw new RuntimeException(
259                    "Mismatch between parameters_size and prototype");
260        }
261
262        if (!isStatic) {
263            // Start off with implicit 'this' entry
264            LocalEntry thisEntry =
265                new LocalEntry(0, true, curReg, thisStringIdx, 0, 0);
266            locals.add(thisEntry);
267            lastEntryForReg[curReg] = thisEntry;
268            curReg++;
269        }
270
271        for (int i = 0; i < szParams; i++) {
272            Type paramType = params.getType(i);
273            LocalEntry le;
274
275            int nameIdx = readStringIndex(bs);
276
277            if (nameIdx == -1) {
278                /*
279                 * Unnamed parameter; often but not always filled in by an
280                 * extended start op after the prologue
281                 */
282                le = new LocalEntry(0, true, curReg, -1, 0, 0);
283            } else {
284                // TODO: Final 0 should be idx of paramType.getDescriptor().
285                le = new LocalEntry(0, true, curReg, nameIdx, 0, 0);
286            }
287
288            locals.add(le);
289            lastEntryForReg[curReg] = le;
290            curReg += paramType.getCategory();
291        }
292
293        for (;;) {
294            int opcode = bs.readByte() & 0xff;
295
296            switch (opcode) {
297                case DBG_START_LOCAL: {
298                    int reg = Leb128.readUnsignedLeb128(bs);
299                    int nameIdx = readStringIndex(bs);
300                    int typeIdx = readStringIndex(bs);
301                    LocalEntry le = new LocalEntry(
302                            address, true, reg, nameIdx, typeIdx, 0);
303
304                    locals.add(le);
305                    lastEntryForReg[reg] = le;
306                }
307                break;
308
309                case DBG_START_LOCAL_EXTENDED: {
310                    int reg = Leb128.readUnsignedLeb128(bs);
311                    int nameIdx = readStringIndex(bs);
312                    int typeIdx = readStringIndex(bs);
313                    int sigIdx = readStringIndex(bs);
314                    LocalEntry le = new LocalEntry(
315                            address, true, reg, nameIdx, typeIdx, sigIdx);
316
317                    locals.add(le);
318                    lastEntryForReg[reg] = le;
319                }
320                break;
321
322                case DBG_RESTART_LOCAL: {
323                    int reg = Leb128.readUnsignedLeb128(bs);
324                    LocalEntry prevle;
325                    LocalEntry le;
326
327                    try {
328                        prevle = lastEntryForReg[reg];
329
330                        if (prevle.isStart) {
331                            throw new RuntimeException("nonsensical "
332                                    + "RESTART_LOCAL on live register v"
333                                    + reg);
334                        }
335
336                        le = new LocalEntry(address, true, reg,
337                                prevle.nameIndex, prevle.typeIndex, 0);
338                    } catch (NullPointerException ex) {
339                        throw new RuntimeException(
340                                "Encountered RESTART_LOCAL on new v" + reg);
341                    }
342
343                    locals.add(le);
344                    lastEntryForReg[reg] = le;
345                }
346                break;
347
348                case DBG_END_LOCAL: {
349                    int reg = Leb128.readUnsignedLeb128(bs);
350                    LocalEntry prevle;
351                    LocalEntry le;
352
353                    try {
354                        prevle = lastEntryForReg[reg];
355
356                        if (!prevle.isStart) {
357                            throw new RuntimeException("nonsensical "
358                                    + "END_LOCAL on dead register v" + reg);
359                        }
360
361                        le = new LocalEntry(address, false, reg,
362                                prevle.nameIndex, prevle.typeIndex,
363                                prevle.signatureIndex);
364                    } catch (NullPointerException ex) {
365                        throw new RuntimeException(
366                                "Encountered END_LOCAL on new v" + reg);
367                    }
368
369                    locals.add(le);
370                    lastEntryForReg[reg] = le;
371                }
372                break;
373
374                case DBG_END_SEQUENCE:
375                    // all done
376                return;
377
378                case DBG_ADVANCE_PC:
379                    address += Leb128.readUnsignedLeb128(bs);
380                break;
381
382                case DBG_ADVANCE_LINE:
383                    line += Leb128.readSignedLeb128(bs);
384                break;
385
386                case DBG_SET_PROLOGUE_END:
387                    //TODO do something with this.
388                break;
389
390                case DBG_SET_EPILOGUE_BEGIN:
391                    //TODO do something with this.
392                break;
393
394                case DBG_SET_FILE:
395                    //TODO do something with this.
396                break;
397
398                default:
399                    if (opcode < DBG_FIRST_SPECIAL) {
400                        throw new RuntimeException(
401                                "Invalid extended opcode encountered "
402                                        + opcode);
403                    }
404
405                    int adjopcode = opcode - DBG_FIRST_SPECIAL;
406
407                    address += adjopcode / DBG_LINE_RANGE;
408                    line += DBG_LINE_BASE + (adjopcode % DBG_LINE_RANGE);
409
410                    positions.add(new PositionEntry(address, line));
411                break;
412
413            }
414        }
415    }
416
417    /**
418     * Validates an encoded debug info stream against data used to encode it,
419     * throwing an exception if they do not match. Used to validate the
420     * encoder.
421     *
422     * @param info encoded debug info
423     * @param file {@code non-null;} file to refer to during decoding
424     * @param ref {@code non-null;} method whose info is being decoded
425     * @param code {@code non-null;} original code object that was encoded
426     * @param isStatic whether the method is static
427     */
428    public static void validateEncode(byte[] info, DexFile file,
429            CstMethodRef ref, DalvCode code, boolean isStatic) {
430        PositionList pl = code.getPositions();
431        LocalList ll = code.getLocals();
432        DalvInsnList insns = code.getInsns();
433        int codeSize = insns.codeSize();
434        int countRegisters = insns.getRegistersSize();
435
436        try {
437            validateEncode0(info, codeSize, countRegisters,
438                    isStatic, ref, file, pl, ll);
439        } catch (RuntimeException ex) {
440            System.err.println("instructions:");
441            insns.debugPrint(System.err, "  ", true);
442            System.err.println("local list:");
443            ll.debugPrint(System.err, "  ");
444            throw ExceptionWithContext.withContext(ex,
445                    "while processing " + ref.toHuman());
446        }
447    }
448
449    private static void validateEncode0(byte[] info, int codeSize,
450            int countRegisters, boolean isStatic, CstMethodRef ref,
451            DexFile file, PositionList pl, LocalList ll) {
452        DebugInfoDecoder decoder
453                = new DebugInfoDecoder(info, codeSize, countRegisters,
454                    isStatic, ref, file);
455
456        decoder.decode();
457
458        /*
459         * Go through the decoded position entries, matching up
460         * with original entries.
461         */
462
463        List<PositionEntry> decodedEntries = decoder.getPositionList();
464
465        if (decodedEntries.size() != pl.size()) {
466            throw new RuntimeException(
467                    "Decoded positions table not same size was "
468                    + decodedEntries.size() + " expected " + pl.size());
469        }
470
471        for (PositionEntry entry : decodedEntries) {
472            boolean found = false;
473            for (int i = pl.size() - 1; i >= 0; i--) {
474                PositionList.Entry ple = pl.get(i);
475
476                if (entry.line == ple.getPosition().getLine()
477                        && entry.address == ple.getAddress()) {
478                    found = true;
479                    break;
480                }
481            }
482
483            if (!found) {
484                throw new RuntimeException ("Could not match position entry: "
485                        + entry.address + ", " + entry.line);
486            }
487        }
488
489        /*
490         * Go through the original local list, in order, matching up
491         * with decoded entries.
492         */
493
494        List<LocalEntry> decodedLocals = decoder.getLocals();
495        int thisStringIdx = decoder.thisStringIdx;
496        int decodedSz = decodedLocals.size();
497        int paramBase = decoder.getParamBase();
498
499        /*
500         * Preflight to fill in any parameters that were skipped in
501         * the prologue (including an implied "this") but then
502         * identified by full signature.
503         */
504        for (int i = 0; i < decodedSz; i++) {
505            LocalEntry entry = decodedLocals.get(i);
506            int idx = entry.nameIndex;
507
508            if ((idx < 0) || (idx == thisStringIdx)) {
509                for (int j = i + 1; j < decodedSz; j++) {
510                    LocalEntry e2 = decodedLocals.get(j);
511                    if (e2.address != 0) {
512                        break;
513                    }
514                    if ((entry.reg == e2.reg) && e2.isStart) {
515                        decodedLocals.set(i, e2);
516                        decodedLocals.remove(j);
517                        decodedSz--;
518                        break;
519                    }
520                }
521            }
522        }
523
524        int origSz = ll.size();
525        int decodeAt = 0;
526        boolean problem = false;
527
528        for (int i = 0; i < origSz; i++) {
529            LocalList.Entry origEntry = ll.get(i);
530
531            if (origEntry.getDisposition()
532                    == LocalList.Disposition.END_REPLACED) {
533                /*
534                 * The encoded list doesn't represent replacements, so
535                 * ignore them for the sake of comparison.
536                 */
537                continue;
538            }
539
540            LocalEntry decodedEntry;
541
542            do {
543                decodedEntry = decodedLocals.get(decodeAt);
544                if (decodedEntry.nameIndex >= 0) {
545                    break;
546                }
547                /*
548                 * A negative name index means this is an anonymous
549                 * parameter, and we shouldn't expect to see it in the
550                 * original list. So, skip it.
551                 */
552                decodeAt++;
553            } while (decodeAt < decodedSz);
554
555            int decodedAddress = decodedEntry.address;
556
557            if (decodedEntry.reg != origEntry.getRegister()) {
558                System.err.println("local register mismatch at orig " + i +
559                        " / decoded " + decodeAt);
560                problem = true;
561                break;
562            }
563
564            if (decodedEntry.isStart != origEntry.isStart()) {
565                System.err.println("local start/end mismatch at orig " + i +
566                        " / decoded " + decodeAt);
567                problem = true;
568                break;
569            }
570
571            /*
572             * The secondary check here accounts for the fact that a
573             * parameter might not be marked as starting at 0 in the
574             * original list.
575             */
576            if ((decodedAddress != origEntry.getAddress())
577                    && !((decodedAddress == 0)
578                            && (decodedEntry.reg >= paramBase))) {
579                System.err.println("local address mismatch at orig " + i +
580                        " / decoded " + decodeAt);
581                problem = true;
582                break;
583            }
584
585            decodeAt++;
586        }
587
588        if (problem) {
589            System.err.println("decoded locals:");
590            for (LocalEntry e : decodedLocals) {
591                System.err.println("  " + e);
592            }
593            throw new RuntimeException("local table problem");
594        }
595    }
596}
597