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