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