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