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