Home | History | Annotate | Download | only in dexdeps
      1 /*
      2  * Copyright (C) 2009 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.dexdeps;
     18 
     19 import java.io.IOException;
     20 import java.io.RandomAccessFile;
     21 import java.nio.charset.StandardCharsets;
     22 import java.util.Arrays;
     23 
     24 /**
     25  * Data extracted from a DEX file.
     26  */
     27 public class DexData {
     28     private RandomAccessFile mDexFile;
     29     private HeaderItem mHeaderItem;
     30     private String[] mStrings;              // strings from string_data_*
     31     private TypeIdItem[] mTypeIds;
     32     private ProtoIdItem[] mProtoIds;
     33     private FieldIdItem[] mFieldIds;
     34     private MethodIdItem[] mMethodIds;
     35     private ClassDefItem[] mClassDefs;
     36 
     37     private byte tmpBuf[] = new byte[4];
     38     private boolean isBigEndian = false;
     39 
     40     /**
     41      * Constructs a new DexData for this file.
     42      */
     43     public DexData(RandomAccessFile raf) {
     44         mDexFile = raf;
     45     }
     46 
     47     /**
     48      * Loads the contents of the DEX file into our data structures.
     49      *
     50      * @throws IOException if we encounter a problem while reading
     51      * @throws DexDataException if the DEX contents look bad
     52      */
     53     public void load() throws IOException {
     54         parseHeaderItem();
     55 
     56         loadStrings();
     57         loadTypeIds();
     58         loadProtoIds();
     59         loadFieldIds();
     60         loadMethodIds();
     61         loadClassDefs();
     62 
     63         markInternalClasses();
     64     }
     65 
     66     /**
     67      * Verifies the given magic number.
     68      */
     69     private static boolean verifyMagic(byte[] magic) {
     70         return Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_v035) ||
     71             Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_v037) ||
     72             Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_v038);
     73     }
     74 
     75     /**
     76      * Parses the interesting bits out of the header.
     77      */
     78     void parseHeaderItem() throws IOException {
     79         mHeaderItem = new HeaderItem();
     80 
     81         seek(0);
     82 
     83         byte[] magic = new byte[8];
     84         readBytes(magic);
     85         if (!verifyMagic(magic)) {
     86             System.err.println("Magic number is wrong -- are you sure " +
     87                 "this is a DEX file?");
     88             throw new DexDataException();
     89         }
     90 
     91         /*
     92          * Read the endian tag, so we properly swap things as we read
     93          * them from here on.
     94          */
     95         seek(8+4+20+4+4);
     96         mHeaderItem.endianTag = readInt();
     97         if (mHeaderItem.endianTag == HeaderItem.ENDIAN_CONSTANT) {
     98             /* do nothing */
     99         } else if (mHeaderItem.endianTag == HeaderItem.REVERSE_ENDIAN_CONSTANT){
    100             /* file is big-endian (!), reverse future reads */
    101             isBigEndian = true;
    102         } else {
    103             System.err.println("Endian constant has unexpected value " +
    104                 Integer.toHexString(mHeaderItem.endianTag));
    105             throw new DexDataException();
    106         }
    107 
    108         seek(8+4+20);  // magic, checksum, signature
    109         mHeaderItem.fileSize = readInt();
    110         mHeaderItem.headerSize = readInt();
    111         /*mHeaderItem.endianTag =*/ readInt();
    112         /*mHeaderItem.linkSize =*/ readInt();
    113         /*mHeaderItem.linkOff =*/ readInt();
    114         /*mHeaderItem.mapOff =*/ readInt();
    115         mHeaderItem.stringIdsSize = readInt();
    116         mHeaderItem.stringIdsOff = readInt();
    117         mHeaderItem.typeIdsSize = readInt();
    118         mHeaderItem.typeIdsOff = readInt();
    119         mHeaderItem.protoIdsSize = readInt();
    120         mHeaderItem.protoIdsOff = readInt();
    121         mHeaderItem.fieldIdsSize = readInt();
    122         mHeaderItem.fieldIdsOff = readInt();
    123         mHeaderItem.methodIdsSize = readInt();
    124         mHeaderItem.methodIdsOff = readInt();
    125         mHeaderItem.classDefsSize = readInt();
    126         mHeaderItem.classDefsOff = readInt();
    127         /*mHeaderItem.dataSize =*/ readInt();
    128         /*mHeaderItem.dataOff =*/ readInt();
    129     }
    130 
    131     /**
    132      * Loads the string table out of the DEX.
    133      *
    134      * First we read all of the string_id_items, then we read all of the
    135      * string_data_item.  Doing it this way should allow us to avoid
    136      * seeking around in the file.
    137      */
    138     void loadStrings() throws IOException {
    139         int count = mHeaderItem.stringIdsSize;
    140         int stringOffsets[] = new int[count];
    141 
    142         //System.out.println("reading " + count + " strings");
    143 
    144         seek(mHeaderItem.stringIdsOff);
    145         for (int i = 0; i < count; i++) {
    146             stringOffsets[i] = readInt();
    147         }
    148 
    149         mStrings = new String[count];
    150 
    151         seek(stringOffsets[0]);
    152         for (int i = 0; i < count; i++) {
    153             seek(stringOffsets[i]);         // should be a no-op
    154             mStrings[i] = readString();
    155             //System.out.println("STR: " + i + ": " + mStrings[i]);
    156         }
    157     }
    158 
    159     /**
    160      * Loads the type ID list.
    161      */
    162     void loadTypeIds() throws IOException {
    163         int count = mHeaderItem.typeIdsSize;
    164         mTypeIds = new TypeIdItem[count];
    165 
    166         //System.out.println("reading " + count + " typeIds");
    167         seek(mHeaderItem.typeIdsOff);
    168         for (int i = 0; i < count; i++) {
    169             mTypeIds[i] = new TypeIdItem();
    170             mTypeIds[i].descriptorIdx = readInt();
    171 
    172             //System.out.println(i + ": " + mTypeIds[i].descriptorIdx +
    173             //    " " + mStrings[mTypeIds[i].descriptorIdx]);
    174         }
    175     }
    176 
    177     /**
    178      * Loads the proto ID list.
    179      */
    180     void loadProtoIds() throws IOException {
    181         int count = mHeaderItem.protoIdsSize;
    182         mProtoIds = new ProtoIdItem[count];
    183 
    184         //System.out.println("reading " + count + " protoIds");
    185         seek(mHeaderItem.protoIdsOff);
    186 
    187         /*
    188          * Read the proto ID items.
    189          */
    190         for (int i = 0; i < count; i++) {
    191             mProtoIds[i] = new ProtoIdItem();
    192             mProtoIds[i].shortyIdx = readInt();
    193             mProtoIds[i].returnTypeIdx = readInt();
    194             mProtoIds[i].parametersOff = readInt();
    195 
    196             //System.out.println(i + ": " + mProtoIds[i].shortyIdx +
    197             //    " " + mStrings[mProtoIds[i].shortyIdx]);
    198         }
    199 
    200         /*
    201          * Go back through and read the type lists.
    202          */
    203         for (int i = 0; i < count; i++) {
    204             ProtoIdItem protoId = mProtoIds[i];
    205 
    206             int offset = protoId.parametersOff;
    207 
    208             if (offset == 0) {
    209                 protoId.types = new int[0];
    210                 continue;
    211             } else {
    212                 seek(offset);
    213                 int size = readInt();       // #of entries in list
    214                 protoId.types = new int[size];
    215 
    216                 for (int j = 0; j < size; j++) {
    217                     protoId.types[j] = readShort() & 0xffff;
    218                 }
    219             }
    220         }
    221     }
    222 
    223     /**
    224      * Loads the field ID list.
    225      */
    226     void loadFieldIds() throws IOException {
    227         int count = mHeaderItem.fieldIdsSize;
    228         mFieldIds = new FieldIdItem[count];
    229 
    230         //System.out.println("reading " + count + " fieldIds");
    231         seek(mHeaderItem.fieldIdsOff);
    232         for (int i = 0; i < count; i++) {
    233             mFieldIds[i] = new FieldIdItem();
    234             mFieldIds[i].classIdx = readShort() & 0xffff;
    235             mFieldIds[i].typeIdx = readShort() & 0xffff;
    236             mFieldIds[i].nameIdx = readInt();
    237 
    238             //System.out.println(i + ": " + mFieldIds[i].nameIdx +
    239             //    " " + mStrings[mFieldIds[i].nameIdx]);
    240         }
    241     }
    242 
    243     /**
    244      * Loads the method ID list.
    245      */
    246     void loadMethodIds() throws IOException {
    247         int count = mHeaderItem.methodIdsSize;
    248         mMethodIds = new MethodIdItem[count];
    249 
    250         //System.out.println("reading " + count + " methodIds");
    251         seek(mHeaderItem.methodIdsOff);
    252         for (int i = 0; i < count; i++) {
    253             mMethodIds[i] = new MethodIdItem();
    254             mMethodIds[i].classIdx = readShort() & 0xffff;
    255             mMethodIds[i].protoIdx = readShort() & 0xffff;
    256             mMethodIds[i].nameIdx = readInt();
    257 
    258             //System.out.println(i + ": " + mMethodIds[i].nameIdx +
    259             //    " " + mStrings[mMethodIds[i].nameIdx]);
    260         }
    261     }
    262 
    263     /**
    264      * Loads the class defs list.
    265      */
    266     void loadClassDefs() throws IOException {
    267         int count = mHeaderItem.classDefsSize;
    268         mClassDefs = new ClassDefItem[count];
    269 
    270         //System.out.println("reading " + count + " classDefs");
    271         seek(mHeaderItem.classDefsOff);
    272         for (int i = 0; i < count; i++) {
    273             mClassDefs[i] = new ClassDefItem();
    274             mClassDefs[i].classIdx = readInt();
    275 
    276             /* access_flags = */ readInt();
    277             /* superclass_idx = */ readInt();
    278             /* interfaces_off = */ readInt();
    279             /* source_file_idx = */ readInt();
    280             /* annotations_off = */ readInt();
    281             /* class_data_off = */ readInt();
    282             /* static_values_off = */ readInt();
    283 
    284             //System.out.println(i + ": " + mClassDefs[i].classIdx + " " +
    285             //    mStrings[mTypeIds[mClassDefs[i].classIdx].descriptorIdx]);
    286         }
    287     }
    288 
    289     /**
    290      * Sets the "internal" flag on type IDs which are defined in the
    291      * DEX file or within the VM (e.g. primitive classes and arrays).
    292      */
    293     void markInternalClasses() {
    294         for (int i = mClassDefs.length -1; i >= 0; i--) {
    295             mTypeIds[mClassDefs[i].classIdx].internal = true;
    296         }
    297 
    298         for (int i = 0; i < mTypeIds.length; i++) {
    299             String className = mStrings[mTypeIds[i].descriptorIdx];
    300 
    301             if (className.length() == 1) {
    302                 // primitive class
    303                 mTypeIds[i].internal = true;
    304             } else if (className.charAt(0) == '[') {
    305                 mTypeIds[i].internal = true;
    306             }
    307 
    308             //System.out.println(i + " " +
    309             //    (mTypeIds[i].internal ? "INTERNAL" : "external") + " - " +
    310             //    mStrings[mTypeIds[i].descriptorIdx]);
    311         }
    312     }
    313 
    314 
    315     /*
    316      * =======================================================================
    317      *      Queries
    318      * =======================================================================
    319      */
    320 
    321     /**
    322      * Returns the class name, given an index into the type_ids table.
    323      */
    324     private String classNameFromTypeIndex(int idx) {
    325         return mStrings[mTypeIds[idx].descriptorIdx];
    326     }
    327 
    328     /**
    329      * Returns an array of method argument type strings, given an index
    330      * into the proto_ids table.
    331      */
    332     private String[] argArrayFromProtoIndex(int idx) {
    333         ProtoIdItem protoId = mProtoIds[idx];
    334         String[] result = new String[protoId.types.length];
    335 
    336         for (int i = 0; i < protoId.types.length; i++) {
    337             result[i] = mStrings[mTypeIds[protoId.types[i]].descriptorIdx];
    338         }
    339 
    340         return result;
    341     }
    342 
    343     /**
    344      * Returns a string representing the method's return type, given an
    345      * index into the proto_ids table.
    346      */
    347     private String returnTypeFromProtoIndex(int idx) {
    348         ProtoIdItem protoId = mProtoIds[idx];
    349         return mStrings[mTypeIds[protoId.returnTypeIdx].descriptorIdx];
    350     }
    351 
    352     /**
    353      * Returns an array with all of the class references that don't
    354      * correspond to classes in the DEX file.  Each class reference has
    355      * a list of the referenced fields and methods associated with
    356      * that class.
    357      */
    358     public ClassRef[] getExternalReferences() {
    359         // create a sparse array of ClassRef that parallels mTypeIds
    360         ClassRef[] sparseRefs = new ClassRef[mTypeIds.length];
    361 
    362         // create entries for all externally-referenced classes
    363         int count = 0;
    364         for (int i = 0; i < mTypeIds.length; i++) {
    365             if (!mTypeIds[i].internal) {
    366                 sparseRefs[i] =
    367                     new ClassRef(mStrings[mTypeIds[i].descriptorIdx]);
    368                 count++;
    369             }
    370         }
    371 
    372         // add fields and methods to the appropriate class entry
    373         addExternalFieldReferences(sparseRefs);
    374         addExternalMethodReferences(sparseRefs);
    375 
    376         // crunch out the sparseness
    377         ClassRef[] classRefs = new ClassRef[count];
    378         int idx = 0;
    379         for (int i = 0; i < mTypeIds.length; i++) {
    380             if (sparseRefs[i] != null)
    381                 classRefs[idx++] = sparseRefs[i];
    382         }
    383 
    384         assert idx == count;
    385 
    386         return classRefs;
    387     }
    388 
    389     /**
    390      * Runs through the list of field references, inserting external
    391      * references into the appropriate ClassRef.
    392      */
    393     private void addExternalFieldReferences(ClassRef[] sparseRefs) {
    394         for (int i = 0; i < mFieldIds.length; i++) {
    395             if (!mTypeIds[mFieldIds[i].classIdx].internal) {
    396                 FieldIdItem fieldId = mFieldIds[i];
    397                 FieldRef newFieldRef = new FieldRef(
    398                         classNameFromTypeIndex(fieldId.classIdx),
    399                         classNameFromTypeIndex(fieldId.typeIdx),
    400                         mStrings[fieldId.nameIdx]);
    401                 sparseRefs[mFieldIds[i].classIdx].addField(newFieldRef);
    402             }
    403         }
    404     }
    405 
    406     /**
    407      * Runs through the list of method references, inserting external
    408      * references into the appropriate ClassRef.
    409      */
    410     private void addExternalMethodReferences(ClassRef[] sparseRefs) {
    411         for (int i = 0; i < mMethodIds.length; i++) {
    412             if (!mTypeIds[mMethodIds[i].classIdx].internal) {
    413                 MethodIdItem methodId = mMethodIds[i];
    414                 MethodRef newMethodRef = new MethodRef(
    415                         classNameFromTypeIndex(methodId.classIdx),
    416                         argArrayFromProtoIndex(methodId.protoIdx),
    417                         returnTypeFromProtoIndex(methodId.protoIdx),
    418                         mStrings[methodId.nameIdx]);
    419                 sparseRefs[mMethodIds[i].classIdx].addMethod(newMethodRef);
    420             }
    421         }
    422     }
    423 
    424 
    425     /*
    426      * =======================================================================
    427      *      Basic I/O functions
    428      * =======================================================================
    429      */
    430 
    431     /**
    432      * Seeks the DEX file to the specified absolute position.
    433      */
    434     void seek(int position) throws IOException {
    435         mDexFile.seek(position);
    436     }
    437 
    438     /**
    439      * Fills the buffer by reading bytes from the DEX file.
    440      */
    441     void readBytes(byte[] buffer) throws IOException {
    442         mDexFile.readFully(buffer);
    443     }
    444 
    445     /**
    446      * Reads a single signed byte value.
    447      */
    448     byte readByte() throws IOException {
    449         mDexFile.readFully(tmpBuf, 0, 1);
    450         return tmpBuf[0];
    451     }
    452 
    453     /**
    454      * Reads a signed 16-bit integer, byte-swapping if necessary.
    455      */
    456     short readShort() throws IOException {
    457         mDexFile.readFully(tmpBuf, 0, 2);
    458         if (isBigEndian) {
    459             return (short) ((tmpBuf[1] & 0xff) | ((tmpBuf[0] & 0xff) << 8));
    460         } else {
    461             return (short) ((tmpBuf[0] & 0xff) | ((tmpBuf[1] & 0xff) << 8));
    462         }
    463     }
    464 
    465     /**
    466      * Reads a signed 32-bit integer, byte-swapping if necessary.
    467      */
    468     int readInt() throws IOException {
    469         mDexFile.readFully(tmpBuf, 0, 4);
    470 
    471         if (isBigEndian) {
    472             return (tmpBuf[3] & 0xff) | ((tmpBuf[2] & 0xff) << 8) |
    473                    ((tmpBuf[1] & 0xff) << 16) | ((tmpBuf[0] & 0xff) << 24);
    474         } else {
    475             return (tmpBuf[0] & 0xff) | ((tmpBuf[1] & 0xff) << 8) |
    476                    ((tmpBuf[2] & 0xff) << 16) | ((tmpBuf[3] & 0xff) << 24);
    477         }
    478     }
    479 
    480     /**
    481      * Reads a variable-length unsigned LEB128 value.  Does not attempt to
    482      * verify that the value is valid.
    483      *
    484      * @throws EOFException if we run off the end of the file
    485      */
    486     int readUnsignedLeb128() throws IOException {
    487         int result = 0;
    488         byte val;
    489 
    490         do {
    491             val = readByte();
    492             result = (result << 7) | (val & 0x7f);
    493         } while (val < 0);
    494 
    495         return result;
    496     }
    497 
    498     /**
    499      * Reads a UTF-8 string.
    500      *
    501      * We don't know how long the UTF-8 string is, so we have to read one
    502      * byte at a time.  We could make an educated guess based on the
    503      * utf16_size and seek back if we get it wrong, but seeking backward
    504      * may cause the underlying implementation to reload I/O buffers.
    505      */
    506     String readString() throws IOException {
    507         int utf16len = readUnsignedLeb128();
    508         byte inBuf[] = new byte[utf16len * 3];      // worst case
    509         int idx;
    510 
    511         for (idx = 0; idx < inBuf.length; idx++) {
    512             byte val = readByte();
    513             if (val == 0)
    514                 break;
    515             inBuf[idx] = val;
    516         }
    517 
    518         return new String(inBuf, 0, idx, "UTF-8");
    519     }
    520 
    521 
    522     /*
    523      * =======================================================================
    524      *      Internal "structure" declarations
    525      * =======================================================================
    526      */
    527 
    528     /**
    529      * Holds the contents of a header_item.
    530      */
    531     static class HeaderItem {
    532         public int fileSize;
    533         public int headerSize;
    534         public int endianTag;
    535         public int stringIdsSize, stringIdsOff;
    536         public int typeIdsSize, typeIdsOff;
    537         public int protoIdsSize, protoIdsOff;
    538         public int fieldIdsSize, fieldIdsOff;
    539         public int methodIdsSize, methodIdsOff;
    540         public int classDefsSize, classDefsOff;
    541 
    542         /* expected magic values */
    543         public static final byte[] DEX_FILE_MAGIC_v035 =
    544             "dex\n035\0".getBytes(StandardCharsets.US_ASCII);
    545 
    546         // Dex version 036 skipped because of an old dalvik bug on some versions
    547         // of android where dex files with that version number would erroneously
    548         // be accepted and run. See: art/runtime/dex_file.cc
    549 
    550         // V037 was introduced in API LEVEL 24
    551         public static final byte[] DEX_FILE_MAGIC_v037 =
    552             "dex\n037\0".getBytes(StandardCharsets.US_ASCII);
    553 
    554         // V038 was introduced in API LEVEL 26
    555         public static final byte[] DEX_FILE_MAGIC_v038 =
    556             "dex\n038\0".getBytes(StandardCharsets.US_ASCII);
    557 
    558         public static final int ENDIAN_CONSTANT = 0x12345678;
    559         public static final int REVERSE_ENDIAN_CONSTANT = 0x78563412;
    560     }
    561 
    562     /**
    563      * Holds the contents of a type_id_item.
    564      *
    565      * This is chiefly a list of indices into the string table.  We need
    566      * some additional bits of data, such as whether or not the type ID
    567      * represents a class defined in this DEX, so we use an object for
    568      * each instead of a simple integer.  (Could use a parallel array, but
    569      * since this is a desktop app it's not essential.)
    570      */
    571     static class TypeIdItem {
    572         public int descriptorIdx;       // index into string_ids
    573 
    574         public boolean internal;        // defined within this DEX file?
    575     }
    576 
    577     /**
    578      * Holds the contents of a proto_id_item.
    579      */
    580     static class ProtoIdItem {
    581         public int shortyIdx;           // index into string_ids
    582         public int returnTypeIdx;       // index into type_ids
    583         public int parametersOff;       // file offset to a type_list
    584 
    585         public int types[];             // contents of type list
    586     }
    587 
    588     /**
    589      * Holds the contents of a field_id_item.
    590      */
    591     static class FieldIdItem {
    592         public int classIdx;            // index into type_ids (defining class)
    593         public int typeIdx;             // index into type_ids (field type)
    594         public int nameIdx;             // index into string_ids
    595     }
    596 
    597     /**
    598      * Holds the contents of a method_id_item.
    599      */
    600     static class MethodIdItem {
    601         public int classIdx;            // index into type_ids
    602         public int protoIdx;            // index into proto_ids
    603         public int nameIdx;             // index into string_ids
    604     }
    605 
    606     /**
    607      * Holds the contents of a class_def_item.
    608      *
    609      * We don't really need a class for this, but there's some stuff in
    610      * the class_def_item that we might want later.
    611      */
    612     static class ClassDefItem {
    613         public int classIdx;            // index into type_ids
    614     }
    615 }
    616