| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dexdeps; |
| |
| import java.io.IOException; |
| import java.io.RandomAccessFile; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Arrays; |
| |
| /** Data extracted from a DEX file. */ |
| public class DexData { |
| private RandomAccessFile mDexFile; |
| private HeaderItem mHeaderItem; |
| private String[] mStrings; // strings from string_data_* |
| private TypeIdItem[] mTypeIds; |
| private ProtoIdItem[] mProtoIds; |
| private FieldIdItem[] mFieldIds; |
| private MethodIdItem[] mMethodIds; |
| private ClassDefItem[] mClassDefs; |
| |
| private byte tmpBuf[] = new byte[4]; |
| private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; |
| |
| /** Constructs a new DexData for this file. */ |
| public DexData(RandomAccessFile raf) { |
| mDexFile = raf; |
| } |
| |
| /** |
| * Loads the contents of the DEX file into our data structures. |
| * |
| * @throws IOException if we encounter a problem while reading |
| * @throws DexDataException if the DEX contents look bad |
| */ |
| public void load() throws IOException { |
| parseHeaderItem(); |
| |
| loadStrings(); |
| loadTypeIds(); |
| loadProtoIds(); |
| loadFieldIds(); |
| loadMethodIds(); |
| loadClassDefs(); |
| |
| markInternalClasses(); |
| } |
| |
| /** Verifies the given magic number. */ |
| private static boolean verifyMagic(byte[] magic) { |
| return Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_v035) |
| || Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_v037) |
| || Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_v038) |
| || Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_v039) |
| || Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_v041); |
| } |
| |
| /** Parses the interesting bits out of the header. */ |
| void parseHeaderItem() throws IOException { |
| mHeaderItem = new HeaderItem(); |
| |
| seek(0); |
| |
| byte[] magic = new byte[8]; |
| readBytes(magic); |
| if (!verifyMagic(magic)) { |
| System.err.println("Magic number is wrong -- are you sure " + "this is a DEX file?"); |
| throw new DexDataException(); |
| } |
| |
| /* |
| * Read the endian tag, so we properly swap things as we read |
| * them from here on. |
| */ |
| seek(8 + 4 + 20 + 4 + 4); |
| mHeaderItem.endianTag = readInt(); |
| if (mHeaderItem.endianTag == HeaderItem.ENDIAN_CONSTANT) { |
| /* do nothing */ |
| } else if (mHeaderItem.endianTag == HeaderItem.REVERSE_ENDIAN_CONSTANT) { |
| /* file is big-endian (!), reverse future reads */ |
| mByteOrder = ByteOrder.BIG_ENDIAN; |
| } else { |
| System.err.println( |
| "Endian constant has unexpected value " |
| + Integer.toHexString(mHeaderItem.endianTag)); |
| throw new DexDataException(); |
| } |
| |
| seek(8 + 4 + 20); // magic, checksum, signature |
| ByteBuffer buffer = readByteBuffer(Integer.BYTES * 20); |
| mHeaderItem.fileSize = buffer.getInt(); |
| mHeaderItem.headerSize = buffer.getInt(); |
| /*mHeaderItem.endianTag =*/ buffer.getInt(); |
| /*mHeaderItem.linkSize =*/ buffer.getInt(); |
| /*mHeaderItem.linkOff =*/ buffer.getInt(); |
| /*mHeaderItem.mapOff =*/ buffer.getInt(); |
| mHeaderItem.stringIdsSize = buffer.getInt(); |
| mHeaderItem.stringIdsOff = buffer.getInt(); |
| mHeaderItem.typeIdsSize = buffer.getInt(); |
| mHeaderItem.typeIdsOff = buffer.getInt(); |
| mHeaderItem.protoIdsSize = buffer.getInt(); |
| mHeaderItem.protoIdsOff = buffer.getInt(); |
| mHeaderItem.fieldIdsSize = buffer.getInt(); |
| mHeaderItem.fieldIdsOff = buffer.getInt(); |
| mHeaderItem.methodIdsSize = buffer.getInt(); |
| mHeaderItem.methodIdsOff = buffer.getInt(); |
| mHeaderItem.classDefsSize = buffer.getInt(); |
| mHeaderItem.classDefsOff = buffer.getInt(); |
| /*mHeaderItem.dataSize =*/ buffer.getInt(); |
| /*mHeaderItem.dataOff =*/ buffer.getInt(); |
| } |
| |
| /** |
| * Loads the string table out of the DEX. |
| * |
| * <p>First we read all of the string_id_items, then we read all of the string_data_item. Doing |
| * it this way should allow us to avoid seeking around in the file. |
| */ |
| void loadStrings() throws IOException { |
| int count = mHeaderItem.stringIdsSize; |
| int stringOffsets[] = new int[count]; |
| |
| // System.out.println("reading " + count + " strings"); |
| |
| seek(mHeaderItem.stringIdsOff); |
| readByteBuffer(Integer.BYTES * count).asIntBuffer().get(stringOffsets); |
| |
| mStrings = new String[count]; |
| |
| seek(stringOffsets[0]); |
| for (int i = 0; i < count; i++) { |
| seek(stringOffsets[i]); // should be a no-op |
| mStrings[i] = readString(); |
| // System.out.println("STR: " + i + ": " + mStrings[i]); |
| } |
| } |
| |
| /** Loads the type ID list. */ |
| void loadTypeIds() throws IOException { |
| int count = mHeaderItem.typeIdsSize; |
| mTypeIds = new TypeIdItem[count]; |
| |
| // System.out.println("reading " + count + " typeIds"); |
| seek(mHeaderItem.typeIdsOff); |
| ByteBuffer buffer = readByteBuffer(Integer.BYTES * count); |
| for (int i = 0; i < count; i++) { |
| mTypeIds[i] = new TypeIdItem(); |
| mTypeIds[i].descriptorIdx = buffer.getInt(); |
| |
| // System.out.println(i + ": " + mTypeIds[i].descriptorIdx + |
| // " " + mStrings[mTypeIds[i].descriptorIdx]); |
| } |
| } |
| |
| /** Loads the proto ID list. */ |
| void loadProtoIds() throws IOException { |
| int count = mHeaderItem.protoIdsSize; |
| mProtoIds = new ProtoIdItem[count]; |
| |
| // System.out.println("reading " + count + " protoIds"); |
| seek(mHeaderItem.protoIdsOff); |
| ByteBuffer buffer = readByteBuffer(Integer.BYTES * 3 * count); |
| |
| /* |
| * Read the proto ID items. |
| */ |
| for (int i = 0; i < count; i++) { |
| mProtoIds[i] = new ProtoIdItem(); |
| mProtoIds[i].shortyIdx = buffer.getInt(); |
| mProtoIds[i].returnTypeIdx = buffer.getInt(); |
| mProtoIds[i].parametersOff = buffer.getInt(); |
| |
| // System.out.println(i + ": " + mProtoIds[i].shortyIdx + |
| // " " + mStrings[mProtoIds[i].shortyIdx]); |
| } |
| |
| /* |
| * Go back through and read the type lists. |
| */ |
| for (int i = 0; i < count; i++) { |
| ProtoIdItem protoId = mProtoIds[i]; |
| |
| int offset = protoId.parametersOff; |
| |
| if (offset == 0) { |
| protoId.types = new int[0]; |
| continue; |
| } else { |
| seek(offset); |
| int size = readInt(); // #of entries in list |
| buffer = readByteBuffer(Short.BYTES * size); |
| protoId.types = new int[size]; |
| |
| for (int j = 0; j < size; j++) { |
| protoId.types[j] = buffer.getShort() & 0xffff; |
| } |
| } |
| } |
| } |
| |
| /** Loads the field ID list. */ |
| void loadFieldIds() throws IOException { |
| int count = mHeaderItem.fieldIdsSize; |
| mFieldIds = new FieldIdItem[count]; |
| |
| // System.out.println("reading " + count + " fieldIds"); |
| seek(mHeaderItem.fieldIdsOff); |
| ByteBuffer buffer = readByteBuffer((Integer.BYTES + Short.BYTES * 2) * count); |
| for (int i = 0; i < count; i++) { |
| mFieldIds[i] = new FieldIdItem(); |
| mFieldIds[i].classIdx = buffer.getShort() & 0xffff; |
| mFieldIds[i].typeIdx = buffer.getShort() & 0xffff; |
| mFieldIds[i].nameIdx = buffer.getInt(); |
| |
| // System.out.println(i + ": " + mFieldIds[i].nameIdx + |
| // " " + mStrings[mFieldIds[i].nameIdx]); |
| } |
| } |
| |
| /** Loads the method ID list. */ |
| void loadMethodIds() throws IOException { |
| int count = mHeaderItem.methodIdsSize; |
| mMethodIds = new MethodIdItem[count]; |
| |
| // System.out.println("reading " + count + " methodIds"); |
| seek(mHeaderItem.methodIdsOff); |
| ByteBuffer buffer = readByteBuffer((Integer.BYTES + Short.BYTES * 2) * count); |
| for (int i = 0; i < count; i++) { |
| mMethodIds[i] = new MethodIdItem(); |
| mMethodIds[i].classIdx = buffer.getShort() & 0xffff; |
| mMethodIds[i].protoIdx = buffer.getShort() & 0xffff; |
| mMethodIds[i].nameIdx = buffer.getInt(); |
| |
| // System.out.println(i + ": " + mMethodIds[i].nameIdx + |
| // " " + mStrings[mMethodIds[i].nameIdx]); |
| } |
| } |
| |
| /** Loads the class defs list. */ |
| void loadClassDefs() throws IOException { |
| int count = mHeaderItem.classDefsSize; |
| mClassDefs = new ClassDefItem[count]; |
| |
| // System.out.println("reading " + count + " classDefs"); |
| seek(mHeaderItem.classDefsOff); |
| ByteBuffer buffer = readByteBuffer(Integer.BYTES * 8 * count); |
| for (int i = 0; i < count; i++) { |
| mClassDefs[i] = new ClassDefItem(); |
| mClassDefs[i].classIdx = buffer.getInt(); |
| |
| /* access_flags= */ buffer.getInt(); |
| /* superclass_idx= */ buffer.getInt(); |
| /* interfaces_off= */ buffer.getInt(); |
| /* source_file_idx= */ buffer.getInt(); |
| /* annotations_off= */ buffer.getInt(); |
| /* class_data_off= */ buffer.getInt(); |
| /* static_values_off= */ buffer.getInt(); |
| |
| // System.out.println(i + ": " + mClassDefs[i].classIdx + " " + |
| // mStrings[mTypeIds[mClassDefs[i].classIdx].descriptorIdx]); |
| } |
| } |
| |
| /** |
| * Sets the "internal" flag on type IDs which are defined in the DEX file or within the VM (e.g. |
| * primitive classes and arrays). |
| */ |
| void markInternalClasses() { |
| for (int i = mClassDefs.length - 1; i >= 0; i--) { |
| mTypeIds[mClassDefs[i].classIdx].internal = true; |
| } |
| |
| for (int i = 0; i < mTypeIds.length; i++) { |
| String className = mStrings[mTypeIds[i].descriptorIdx]; |
| |
| if (className.length() == 1) { |
| // primitive class |
| mTypeIds[i].internal = true; |
| } else if (className.charAt(0) == '[') { |
| mTypeIds[i].internal = true; |
| } |
| |
| // System.out.println(i + " " + |
| // (mTypeIds[i].internal ? "INTERNAL" : "external") + " - " + |
| // mStrings[mTypeIds[i].descriptorIdx]); |
| } |
| } |
| |
| /* |
| * ======================================================================= |
| * Queries |
| * ======================================================================= |
| */ |
| |
| /** Returns the class name, given an index into the type_ids table. */ |
| private String classNameFromTypeIndex(int idx) { |
| return mStrings[mTypeIds[idx].descriptorIdx]; |
| } |
| |
| /** |
| * Returns an array of method argument type strings, given an index into the proto_ids table. |
| */ |
| private String[] argArrayFromProtoIndex(int idx) { |
| ProtoIdItem protoId = mProtoIds[idx]; |
| String[] result = new String[protoId.types.length]; |
| |
| for (int i = 0; i < protoId.types.length; i++) { |
| result[i] = mStrings[mTypeIds[protoId.types[i]].descriptorIdx]; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Returns a string representing the method's return type, given an index into the proto_ids |
| * table. |
| */ |
| private String returnTypeFromProtoIndex(int idx) { |
| ProtoIdItem protoId = mProtoIds[idx]; |
| return mStrings[mTypeIds[protoId.returnTypeIdx].descriptorIdx]; |
| } |
| |
| /** |
| * Returns an array with all of the class references that don't correspond to classes in the DEX |
| * file. Each class reference has a list of the referenced fields and methods associated with |
| * that class. |
| */ |
| public ClassRef[] getExternalReferences() { |
| // create a sparse array of ClassRef that parallels mTypeIds |
| ClassRef[] sparseRefs = new ClassRef[mTypeIds.length]; |
| |
| // create entries for all externally-referenced classes |
| int count = 0; |
| for (int i = 0; i < mTypeIds.length; i++) { |
| if (!mTypeIds[i].internal) { |
| sparseRefs[i] = new ClassRef(mStrings[mTypeIds[i].descriptorIdx]); |
| count++; |
| } |
| } |
| |
| // add fields and methods to the appropriate class entry |
| addExternalFieldReferences(sparseRefs); |
| addExternalMethodReferences(sparseRefs); |
| |
| // crunch out the sparseness |
| ClassRef[] classRefs = new ClassRef[count]; |
| int idx = 0; |
| for (int i = 0; i < mTypeIds.length; i++) { |
| if (sparseRefs[i] != null) classRefs[idx++] = sparseRefs[i]; |
| } |
| |
| assert idx == count; |
| |
| return classRefs; |
| } |
| |
| /** |
| * Runs through the list of field references, inserting external references into the appropriate |
| * ClassRef. |
| */ |
| private void addExternalFieldReferences(ClassRef[] sparseRefs) { |
| for (int i = 0; i < mFieldIds.length; i++) { |
| if (!mTypeIds[mFieldIds[i].classIdx].internal) { |
| FieldIdItem fieldId = mFieldIds[i]; |
| FieldRef newFieldRef = |
| new FieldRef( |
| classNameFromTypeIndex(fieldId.classIdx), |
| classNameFromTypeIndex(fieldId.typeIdx), |
| mStrings[fieldId.nameIdx]); |
| sparseRefs[mFieldIds[i].classIdx].addField(newFieldRef); |
| } |
| } |
| } |
| |
| /** |
| * Runs through the list of method references, inserting external references into the |
| * appropriate ClassRef. |
| */ |
| private void addExternalMethodReferences(ClassRef[] sparseRefs) { |
| for (int i = 0; i < mMethodIds.length; i++) { |
| if (!mTypeIds[mMethodIds[i].classIdx].internal) { |
| MethodIdItem methodId = mMethodIds[i]; |
| MethodRef newMethodRef = |
| new MethodRef( |
| classNameFromTypeIndex(methodId.classIdx), |
| argArrayFromProtoIndex(methodId.protoIdx), |
| returnTypeFromProtoIndex(methodId.protoIdx), |
| mStrings[methodId.nameIdx]); |
| sparseRefs[mMethodIds[i].classIdx].addMethod(newMethodRef); |
| } |
| } |
| } |
| |
| /* |
| * ======================================================================= |
| * Basic I/O functions |
| * ======================================================================= |
| */ |
| |
| /** Seeks the DEX file to the specified absolute position. */ |
| void seek(int position) throws IOException { |
| mDexFile.seek(position); |
| } |
| |
| /** Fills the buffer by reading bytes from the DEX file. */ |
| void readBytes(byte[] buffer) throws IOException { |
| mDexFile.readFully(buffer); |
| } |
| |
| /** Reads a single signed byte value. */ |
| byte readByte() throws IOException { |
| mDexFile.readFully(tmpBuf, 0, 1); |
| return tmpBuf[0]; |
| } |
| |
| /** Reads a signed 32-bit integer, byte-swapping if necessary. */ |
| int readInt() throws IOException { |
| mDexFile.readFully(tmpBuf, 0, 4); |
| |
| if (mByteOrder == ByteOrder.BIG_ENDIAN) { |
| return (tmpBuf[3] & 0xff) |
| | ((tmpBuf[2] & 0xff) << 8) |
| | ((tmpBuf[1] & 0xff) << 16) |
| | ((tmpBuf[0] & 0xff) << 24); |
| } else { |
| return (tmpBuf[0] & 0xff) |
| | ((tmpBuf[1] & 0xff) << 8) |
| | ((tmpBuf[2] & 0xff) << 16) |
| | ((tmpBuf[3] & 0xff) << 24); |
| } |
| } |
| |
| /** |
| * Reads a variable-length unsigned LEB128 value. Does not attempt to verify that the value is |
| * valid. |
| * |
| * @throws EOFException if we run off the end of the file |
| */ |
| int readUnsignedLeb128() throws IOException { |
| int result = 0; |
| byte val; |
| |
| do { |
| val = readByte(); |
| result = (result << 7) | (val & 0x7f); |
| } while (val < 0); |
| |
| return result; |
| } |
| |
| /** |
| * Reads bytes and transforms them into a ByteBuffer with the desired byte order set, from which |
| * primitive values can be read. |
| */ |
| ByteBuffer readByteBuffer(int size) throws IOException { |
| byte bytes[] = new byte[size]; |
| mDexFile.read(bytes); |
| return ByteBuffer.wrap(bytes).order(mByteOrder); |
| } |
| |
| /** |
| * Reads a UTF-8 string. |
| * |
| * <p>We don't know how long the UTF-8 string is, so we try to read the worst case amount of |
| * bytes. |
| * |
| * <p>Note that the dex file pointer will likely be at a wrong location after this operation, |
| * which means it can't be used in the middle of sequential reads. |
| */ |
| String readString() throws IOException { |
| int utf16len = readUnsignedLeb128(); |
| byte inBuf[] = new byte[utf16len * 3]; // worst case |
| |
| int bytesRead = mDexFile.read(inBuf); |
| for (int i = 0; i < bytesRead; i++) { |
| if (inBuf[i] == 0) { |
| bytesRead = i; |
| break; |
| } |
| } |
| |
| return new String(inBuf, 0, bytesRead, "UTF-8"); |
| } |
| |
| /* |
| * ======================================================================= |
| * Internal "structure" declarations |
| * ======================================================================= |
| */ |
| |
| /** Holds the contents of a header_item. */ |
| static class HeaderItem { |
| public int fileSize; |
| public int headerSize; |
| public int endianTag; |
| public int stringIdsSize, stringIdsOff; |
| public int typeIdsSize, typeIdsOff; |
| public int protoIdsSize, protoIdsOff; |
| public int fieldIdsSize, fieldIdsOff; |
| public int methodIdsSize, methodIdsOff; |
| public int classDefsSize, classDefsOff; |
| |
| /* expected magic values */ |
| public static final byte[] DEX_FILE_MAGIC_v035 = |
| "dex\n035\0".getBytes(StandardCharsets.US_ASCII); |
| |
| // Dex version 036 skipped because of an old dalvik bug on some versions |
| // of android where dex files with that version number would erroneously |
| // be accepted and run. See: art/runtime/dex_file.cc |
| |
| // V037 was introduced in API LEVEL 24 |
| public static final byte[] DEX_FILE_MAGIC_v037 = |
| "dex\n037\0".getBytes(StandardCharsets.US_ASCII); |
| |
| // V038 was introduced in API LEVEL 26 |
| public static final byte[] DEX_FILE_MAGIC_v038 = |
| "dex\n038\0".getBytes(StandardCharsets.US_ASCII); |
| |
| // V039 was introduced in API LEVEL 28 |
| public static final byte[] DEX_FILE_MAGIC_v039 = |
| "dex\n039\0".getBytes(StandardCharsets.US_ASCII); |
| |
| // V041 will be introduced in API LEVEL 35 |
| public static final byte[] DEX_FILE_MAGIC_v041 = |
| "dex\n041\0".getBytes(StandardCharsets.US_ASCII); |
| |
| public static final int ENDIAN_CONSTANT = 0x12345678; |
| public static final int REVERSE_ENDIAN_CONSTANT = 0x78563412; |
| } |
| |
| /** |
| * Holds the contents of a type_id_item. |
| * |
| * <p>This is chiefly a list of indices into the string table. We need some additional bits of |
| * data, such as whether or not the type ID represents a class defined in this DEX, so we use an |
| * object for each instead of a simple integer. (Could use a parallel array, but since this is a |
| * desktop app it's not essential.) |
| */ |
| static class TypeIdItem { |
| public int descriptorIdx; // index into string_ids |
| |
| public boolean internal; // defined within this DEX file? |
| } |
| |
| /** Holds the contents of a proto_id_item. */ |
| static class ProtoIdItem { |
| public int shortyIdx; // index into string_ids |
| public int returnTypeIdx; // index into type_ids |
| public int parametersOff; // file offset to a type_list |
| |
| public int types[]; // contents of type list |
| } |
| |
| /** Holds the contents of a field_id_item. */ |
| static class FieldIdItem { |
| public int classIdx; // index into type_ids (defining class) |
| public int typeIdx; // index into type_ids (field type) |
| public int nameIdx; // index into string_ids |
| } |
| |
| /** Holds the contents of a method_id_item. */ |
| static class MethodIdItem { |
| public int classIdx; // index into type_ids |
| public int protoIdx; // index into proto_ids |
| public int nameIdx; // index into string_ids |
| } |
| |
| /** |
| * Holds the contents of a class_def_item. |
| * |
| * <p>We don't really need a class for this, but there's some stuff in the class_def_item that |
| * we might want later. |
| */ |
| static class ClassDefItem { |
| public int classIdx; // index into type_ids |
| } |
| } |