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