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