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