Home | History | Annotate | Download | only in file
      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      */
     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 
    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 
    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 
    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      */
    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      */
    197     public List<LocalEntry> getLocals() {
    198         return locals;
    199     }
    200 
    201     /**
    202      * Decodes the debug info sequence.
    203      */
    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      */
    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      */
    234     private int getParamBase() {
    235         return regSize
    236                 - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1);
    237     }
    238 
    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      */
    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 
    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      */
    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      */
    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