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