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.dx.dex.file;
     18 
     19 import com.android.dex.util.ExceptionWithContext;
     20 import com.android.dx.dex.DexOptions;
     21 import com.android.dx.dex.file.MixedItemSection.SortType;
     22 import com.android.dx.rop.cst.Constant;
     23 import com.android.dx.rop.cst.CstBaseMethodRef;
     24 import com.android.dx.rop.cst.CstEnumRef;
     25 import com.android.dx.rop.cst.CstFieldRef;
     26 import com.android.dx.rop.cst.CstString;
     27 import com.android.dx.rop.cst.CstType;
     28 import com.android.dx.rop.type.Type;
     29 import com.android.dx.util.ByteArrayAnnotatedOutput;
     30 
     31 import java.io.IOException;
     32 import java.io.OutputStream;
     33 import java.io.Writer;
     34 import java.security.DigestException;
     35 import java.security.MessageDigest;
     36 import java.security.NoSuchAlgorithmException;
     37 import java.util.zip.Adler32;
     38 
     39 /**
     40  * Representation of an entire {@code .dex} (Dalvik EXecutable)
     41  * file, which itself consists of a set of Dalvik classes.
     42  */
     43 public final class DexFile {
     44     /** options controlling the creation of the file */
     45     private DexOptions dexOptions;
     46 
     47     /** {@code non-null;} word data section */
     48     private final MixedItemSection wordData;
     49 
     50     /**
     51      * {@code non-null;} type lists section. This is word data, but separating
     52      * it from {@link #wordData} helps break what would otherwise be a
     53      * circular dependency between the that and {@link #protoIds}.
     54      */
     55     private final MixedItemSection typeLists;
     56 
     57     /**
     58      * {@code non-null;} map section. The map needs to be in a section by itself
     59      * for the self-reference mechanics to work in a reasonably
     60      * straightforward way. See {@link MapItem#addMap} for more detail.
     61      */
     62     private final MixedItemSection map;
     63 
     64     /** {@code non-null;} string data section */
     65     private final MixedItemSection stringData;
     66 
     67     /** {@code non-null;} string identifiers section */
     68     private final StringIdsSection stringIds;
     69 
     70     /** {@code non-null;} type identifiers section */
     71     private final TypeIdsSection typeIds;
     72 
     73     /** {@code non-null;} prototype identifiers section */
     74     private final ProtoIdsSection protoIds;
     75 
     76     /** {@code non-null;} field identifiers section */
     77     private final FieldIdsSection fieldIds;
     78 
     79     /** {@code non-null;} method identifiers section */
     80     private final MethodIdsSection methodIds;
     81 
     82     /** {@code non-null;} class definitions section */
     83     private final ClassDefsSection classDefs;
     84 
     85     /** {@code non-null;} class data section */
     86     private final MixedItemSection classData;
     87 
     88     /** {@code non-null;} byte data section */
     89     private final MixedItemSection byteData;
     90 
     91     /** {@code non-null;} file header */
     92     private final HeaderSection header;
     93 
     94     /**
     95      * {@code non-null;} array of sections in the order they will appear in the
     96      * final output file
     97      */
     98     private final Section[] sections;
     99 
    100     /** {@code >= -1;} total file size or {@code -1} if unknown */
    101     private int fileSize;
    102 
    103     /** {@code >= 40;} maximum width of the file dump */
    104     private int dumpWidth;
    105 
    106     /**
    107      * Constructs an instance. It is initially empty.
    108      */
    109     public DexFile(DexOptions dexOptions) {
    110         this.dexOptions = dexOptions;
    111 
    112         header = new HeaderSection(this);
    113         typeLists = new MixedItemSection(null, this, 4, SortType.NONE);
    114         wordData = new MixedItemSection("word_data", this, 4, SortType.TYPE);
    115         stringData =
    116             new MixedItemSection("string_data", this, 1, SortType.INSTANCE);
    117         classData = new MixedItemSection(null, this, 1, SortType.NONE);
    118         byteData = new MixedItemSection("byte_data", this, 1, SortType.TYPE);
    119         stringIds = new StringIdsSection(this);
    120         typeIds = new TypeIdsSection(this);
    121         protoIds = new ProtoIdsSection(this);
    122         fieldIds = new FieldIdsSection(this);
    123         methodIds = new MethodIdsSection(this);
    124         classDefs = new ClassDefsSection(this);
    125         map = new MixedItemSection("map", this, 4, SortType.NONE);
    126 
    127         /*
    128          * This is the list of sections in the order they appear in
    129          * the final output.
    130          */
    131         sections = new Section[] {
    132             header, stringIds, typeIds, protoIds, fieldIds, methodIds,
    133             classDefs, wordData, typeLists, stringData, byteData,
    134             classData, map };
    135 
    136         fileSize = -1;
    137         dumpWidth = 79;
    138     }
    139 
    140     /**
    141      * Returns true if this dex doesn't contain any class defs.
    142      */
    143     public boolean isEmpty() {
    144         return classDefs.items().isEmpty();
    145     }
    146 
    147     /**
    148      * Gets the dex-creation options object.
    149      */
    150     public DexOptions getDexOptions() {
    151         return dexOptions;
    152     }
    153 
    154     /**
    155      * Adds a class to this instance. It is illegal to attempt to add more
    156      * than one class with the same name.
    157      *
    158      * @param clazz {@code non-null;} the class to add
    159      */
    160     public void add(ClassDefItem clazz) {
    161         classDefs.add(clazz);
    162     }
    163 
    164     /**
    165      * Gets the class definition with the given name, if any.
    166      *
    167      * @param name {@code non-null;} the class name to look for
    168      * @return {@code null-ok;} the class with the given name, or {@code null}
    169      * if there is no such class
    170      */
    171     public ClassDefItem getClassOrNull(String name) {
    172         try {
    173             Type type = Type.internClassName(name);
    174             return (ClassDefItem) classDefs.get(new CstType(type));
    175         } catch (IllegalArgumentException ex) {
    176             // Translate exception, per contract.
    177             return null;
    178         }
    179     }
    180 
    181     /**
    182      * Writes the contents of this instance as either a binary or a
    183      * human-readable form, or both.
    184      *
    185      * @param out {@code null-ok;} where to write to
    186      * @param humanOut {@code null-ok;} where to write human-oriented output to
    187      * @param verbose whether to be verbose when writing human-oriented output
    188      */
    189     public void writeTo(OutputStream out, Writer humanOut, boolean verbose)
    190         throws IOException {
    191         boolean annotate = (humanOut != null);
    192         ByteArrayAnnotatedOutput result = toDex0(annotate, verbose);
    193 
    194         if (out != null) {
    195             out.write(result.getArray());
    196         }
    197 
    198         if (annotate) {
    199             result.writeAnnotationsTo(humanOut);
    200         }
    201     }
    202 
    203     /**
    204      * Returns the contents of this instance as a {@code .dex} file,
    205      * in {@code byte[]} form.
    206      *
    207      * @param humanOut {@code null-ok;} where to write human-oriented output to
    208      * @param verbose whether to be verbose when writing human-oriented output
    209      * @return {@code non-null;} a {@code .dex} file for this instance
    210      */
    211     public byte[] toDex(Writer humanOut, boolean verbose)
    212         throws IOException {
    213         boolean annotate = (humanOut != null);
    214         ByteArrayAnnotatedOutput result = toDex0(annotate, verbose);
    215 
    216         if (annotate) {
    217             result.writeAnnotationsTo(humanOut);
    218         }
    219 
    220         return result.getArray();
    221     }
    222 
    223     /**
    224      * Sets the maximum width of the human-oriented dump of the instance.
    225      *
    226      * @param dumpWidth {@code >= 40;} the width
    227      */
    228     public void setDumpWidth(int dumpWidth) {
    229         if (dumpWidth < 40) {
    230             throw new IllegalArgumentException("dumpWidth < 40");
    231         }
    232 
    233         this.dumpWidth = dumpWidth;
    234     }
    235 
    236     /**
    237      * Gets the total file size, if known.
    238      *
    239      * <p>This is package-scope in order to allow
    240      * the {@link HeaderSection} to set itself up properly.</p>
    241      *
    242      * @return {@code >= 0;} the total file size
    243      * @throws RuntimeException thrown if the file size is not yet known
    244      */
    245     public int getFileSize() {
    246         if (fileSize < 0) {
    247             throw new RuntimeException("file size not yet known");
    248         }
    249 
    250         return fileSize;
    251     }
    252 
    253     /**
    254      * Gets the string data section.
    255      *
    256      * <p>This is package-scope in order to allow
    257      * the various {@link Item} instances to add items to the
    258      * instance.</p>
    259      *
    260      * @return {@code non-null;} the string data section
    261      */
    262     /*package*/ MixedItemSection getStringData() {
    263         return stringData;
    264     }
    265 
    266     /**
    267      * Gets the word data section.
    268      *
    269      * <p>This is package-scope in order to allow
    270      * the various {@link Item} instances to add items to the
    271      * instance.</p>
    272      *
    273      * @return {@code non-null;} the word data section
    274      */
    275     /*package*/ MixedItemSection getWordData() {
    276         return wordData;
    277     }
    278 
    279     /**
    280      * Gets the type lists section.
    281      *
    282      * <p>This is package-scope in order to allow
    283      * the various {@link Item} instances to add items to the
    284      * instance.</p>
    285      *
    286      * @return {@code non-null;} the word data section
    287      */
    288     /*package*/ MixedItemSection getTypeLists() {
    289         return typeLists;
    290     }
    291 
    292     /**
    293      * Gets the map section.
    294      *
    295      * <p>This is package-scope in order to allow the header section
    296      * to query it.</p>
    297      *
    298      * @return {@code non-null;} the map section
    299      */
    300     /*package*/ MixedItemSection getMap() {
    301         return map;
    302     }
    303 
    304     /**
    305      * Gets the string identifiers section.
    306      *
    307      * <p>This is package-scope in order to allow
    308      * the various {@link Item} instances to add items to the
    309      * instance.</p>
    310      *
    311      * @return {@code non-null;} the string identifiers section
    312      */
    313     /*package*/ StringIdsSection getStringIds() {
    314         return stringIds;
    315     }
    316 
    317     /**
    318      * Gets the class definitions section.
    319      *
    320      * <p>This is package-scope in order to allow
    321      * the various {@link Item} instances to add items to the
    322      * instance.</p>
    323      *
    324      * @return {@code non-null;} the class definitions section
    325      */
    326     /*package*/ ClassDefsSection getClassDefs() {
    327         return classDefs;
    328     }
    329 
    330     /**
    331      * Gets the class data section.
    332      *
    333      * <p>This is package-scope in order to allow
    334      * the various {@link Item} instances to add items to the
    335      * instance.</p>
    336      *
    337      * @return {@code non-null;} the class data section
    338      */
    339     /*package*/ MixedItemSection getClassData() {
    340         return classData;
    341     }
    342 
    343     /**
    344      * Gets the type identifiers section.
    345      *
    346      * <p>This is public in order to allow
    347      * the various {@link Item} instances to add items to the
    348      * instance and help early counting of type ids.</p>
    349      *
    350      * @return {@code non-null;} the class identifiers section
    351      */
    352     public TypeIdsSection getTypeIds() {
    353         return typeIds;
    354     }
    355 
    356     /**
    357      * Gets the prototype identifiers section.
    358      *
    359      * <p>This is package-scope in order to allow
    360      * the various {@link Item} instances to add items to the
    361      * instance.</p>
    362      *
    363      * @return {@code non-null;} the prototype identifiers section
    364      */
    365     /*package*/ ProtoIdsSection getProtoIds() {
    366         return protoIds;
    367     }
    368 
    369     /**
    370      * Gets the field identifiers section.
    371      *
    372      * <p>This is public in order to allow
    373      * the various {@link Item} instances to add items to the
    374      * instance and help early counting of field ids.</p>
    375      *
    376      * @return {@code non-null;} the field identifiers section
    377      */
    378     public FieldIdsSection getFieldIds() {
    379         return fieldIds;
    380     }
    381 
    382     /**
    383      * Gets the method identifiers section.
    384      *
    385      * <p>This is public in order to allow
    386      * the various {@link Item} instances to add items to the
    387      * instance and help early counting of method ids.</p>
    388      *
    389      * @return {@code non-null;} the method identifiers section
    390      */
    391     public MethodIdsSection getMethodIds() {
    392         return methodIds;
    393     }
    394 
    395     /**
    396      * Gets the byte data section.
    397      *
    398      * <p>This is package-scope in order to allow
    399      * the various {@link Item} instances to add items to the
    400      * instance.</p>
    401      *
    402      * @return {@code non-null;} the byte data section
    403      */
    404     /*package*/ MixedItemSection getByteData() {
    405         return byteData;
    406     }
    407 
    408     /**
    409      * Gets the first section of the file that is to be considered
    410      * part of the data section.
    411      *
    412      * <p>This is package-scope in order to allow the header section
    413      * to query it.</p>
    414      *
    415      * @return {@code non-null;} the section
    416      */
    417     /*package*/ Section getFirstDataSection() {
    418         return wordData;
    419     }
    420 
    421     /**
    422      * Gets the last section of the file that is to be considered
    423      * part of the data section.
    424      *
    425      * <p>This is package-scope in order to allow the header section
    426      * to query it.</p>
    427      *
    428      * @return {@code non-null;} the section
    429      */
    430     /*package*/ Section getLastDataSection() {
    431         return map;
    432     }
    433 
    434     /**
    435      * Interns the given constant in the appropriate section of this
    436      * instance, or do nothing if the given constant isn't the sort
    437      * that should be interned.
    438      *
    439      * @param cst {@code non-null;} constant to possibly intern
    440      */
    441     /*package*/ void internIfAppropriate(Constant cst) {
    442         if (cst instanceof CstString) {
    443             stringIds.intern((CstString) cst);
    444         } else if (cst instanceof CstType) {
    445             typeIds.intern((CstType) cst);
    446         } else if (cst instanceof CstBaseMethodRef) {
    447             methodIds.intern((CstBaseMethodRef) cst);
    448         } else if (cst instanceof CstFieldRef) {
    449             fieldIds.intern((CstFieldRef) cst);
    450         } else if (cst instanceof CstEnumRef) {
    451             fieldIds.intern(((CstEnumRef) cst).getFieldRef());
    452         } else if (cst == null) {
    453             throw new NullPointerException("cst == null");
    454         }
    455     }
    456 
    457     /**
    458      * Gets the {@link IndexedItem} corresponding to the given constant,
    459      * if it is a constant that has such a correspondence, or return
    460      * {@code null} if it isn't such a constant. This will throw
    461      * an exception if the given constant <i>should</i> have been found
    462      * but wasn't.
    463      *
    464      * @param cst {@code non-null;} the constant to look up
    465      * @return {@code null-ok;} its corresponding item, if it has a corresponding
    466      * item, or {@code null} if it's not that sort of constant
    467      */
    468     /*package*/ IndexedItem findItemOrNull(Constant cst) {
    469         IndexedItem item;
    470 
    471         if (cst instanceof CstString) {
    472             return stringIds.get(cst);
    473         } else if (cst instanceof CstType) {
    474             return typeIds.get(cst);
    475         } else if (cst instanceof CstBaseMethodRef) {
    476             return methodIds.get(cst);
    477         } else if (cst instanceof CstFieldRef) {
    478             return fieldIds.get(cst);
    479         } else {
    480             return null;
    481         }
    482     }
    483 
    484     /**
    485      * Returns the contents of this instance as a {@code .dex} file,
    486      * in a {@link ByteArrayAnnotatedOutput} instance.
    487      *
    488      * @param annotate whether or not to keep annotations
    489      * @param verbose if annotating, whether to be verbose
    490      * @return {@code non-null;} a {@code .dex} file for this instance
    491      */
    492     private ByteArrayAnnotatedOutput toDex0(boolean annotate,
    493             boolean verbose) {
    494         /*
    495          * The following is ordered so that the prepare() calls which
    496          * add items happen before the calls to the sections that get
    497          * added to.
    498          */
    499 
    500         classDefs.prepare();
    501         classData.prepare();
    502         wordData.prepare();
    503         byteData.prepare();
    504         methodIds.prepare();
    505         fieldIds.prepare();
    506         protoIds.prepare();
    507         typeLists.prepare();
    508         typeIds.prepare();
    509         stringIds.prepare();
    510         stringData.prepare();
    511         header.prepare();
    512 
    513         // Place the sections within the file.
    514 
    515         int count = sections.length;
    516         int offset = 0;
    517 
    518         for (int i = 0; i < count; i++) {
    519             Section one = sections[i];
    520             int placedAt = one.setFileOffset(offset);
    521             if (placedAt < offset) {
    522                 throw new RuntimeException("bogus placement for section " + i);
    523             }
    524 
    525             try {
    526                 if (one == map) {
    527                     /*
    528                      * Inform the map of all the sections, and add it
    529                      * to the file. This can only be done after all
    530                      * the other items have been sorted and placed.
    531                      */
    532                     MapItem.addMap(sections, map);
    533                     map.prepare();
    534                 }
    535 
    536                 if (one instanceof MixedItemSection) {
    537                     /*
    538                      * Place the items of a MixedItemSection that just
    539                      * got placed.
    540                      */
    541                     ((MixedItemSection) one).placeItems();
    542                 }
    543 
    544                 offset = placedAt + one.writeSize();
    545             } catch (RuntimeException ex) {
    546                 throw ExceptionWithContext.withContext(ex,
    547                         "...while writing section " + i);
    548             }
    549         }
    550 
    551         // Write out all the sections.
    552 
    553         fileSize = offset;
    554         byte[] barr = new byte[fileSize];
    555         ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(barr);
    556 
    557         if (annotate) {
    558             out.enableAnnotations(dumpWidth, verbose);
    559         }
    560 
    561         for (int i = 0; i < count; i++) {
    562             try {
    563                 Section one = sections[i];
    564                 int zeroCount = one.getFileOffset() - out.getCursor();
    565                 if (zeroCount < 0) {
    566                     throw new ExceptionWithContext("excess write of " +
    567                             (-zeroCount));
    568                 }
    569                 out.writeZeroes(one.getFileOffset() - out.getCursor());
    570                 one.writeTo(out);
    571             } catch (RuntimeException ex) {
    572                 ExceptionWithContext ec;
    573                 if (ex instanceof ExceptionWithContext) {
    574                     ec = (ExceptionWithContext) ex;
    575                 } else {
    576                     ec = new ExceptionWithContext(ex);
    577                 }
    578                 ec.addContext("...while writing section " + i);
    579                 throw ec;
    580             }
    581         }
    582 
    583         if (out.getCursor() != fileSize) {
    584             throw new RuntimeException("foreshortened write");
    585         }
    586 
    587         // Perform final bookkeeping.
    588 
    589         calcSignature(barr);
    590         calcChecksum(barr);
    591 
    592         if (annotate) {
    593             wordData.writeIndexAnnotation(out, ItemType.TYPE_CODE_ITEM,
    594                     "\nmethod code index:\n\n");
    595             getStatistics().writeAnnotation(out);
    596             out.finishAnnotating();
    597         }
    598 
    599         return out;
    600     }
    601 
    602     /**
    603      * Generates and returns statistics for all the items in the file.
    604      *
    605      * @return {@code non-null;} the statistics
    606      */
    607     public Statistics getStatistics() {
    608         Statistics stats = new Statistics();
    609 
    610         for (Section s : sections) {
    611             stats.addAll(s);
    612         }
    613 
    614         return stats;
    615     }
    616 
    617     /**
    618      * Calculates the signature for the {@code .dex} file in the
    619      * given array, and modify the array to contain it.
    620      *
    621      * @param bytes {@code non-null;} the bytes of the file
    622      */
    623     private static void calcSignature(byte[] bytes) {
    624         MessageDigest md;
    625 
    626         try {
    627             md = MessageDigest.getInstance("SHA-1");
    628         } catch (NoSuchAlgorithmException ex) {
    629             throw new RuntimeException(ex);
    630         }
    631 
    632         md.update(bytes, 32, bytes.length - 32);
    633 
    634         try {
    635             int amt = md.digest(bytes, 12, 20);
    636             if (amt != 20) {
    637                 throw new RuntimeException("unexpected digest write: " + amt +
    638                                            " bytes");
    639             }
    640         } catch (DigestException ex) {
    641             throw new RuntimeException(ex);
    642         }
    643     }
    644 
    645     /**
    646      * Calculates the checksum for the {@code .dex} file in the
    647      * given array, and modify the array to contain it.
    648      *
    649      * @param bytes {@code non-null;} the bytes of the file
    650      */
    651     private static void calcChecksum(byte[] bytes) {
    652         Adler32 a32 = new Adler32();
    653 
    654         a32.update(bytes, 12, bytes.length - 12);
    655 
    656         int sum = (int) a32.getValue();
    657 
    658         bytes[8]  = (byte) sum;
    659         bytes[9]  = (byte) (sum >> 8);
    660         bytes[10] = (byte) (sum >> 16);
    661         bytes[11] = (byte) (sum >> 24);
    662     }
    663 }
    664