blob: 89dff182a82876d1ba47e3ff2ab1c3d0890ffda8 [file] [log] [blame]
/*
* 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.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 boolean isBigEndian = false;
/**
* 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) ||
Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_API_13);
}
/**
* 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 */
isBigEndian = true;
} else {
System.err.println("Endian constant has unexpected value " +
Integer.toHexString(mHeaderItem.endianTag));
throw new DexDataException();
}
seek(8+4+20); // magic, checksum, signature
mHeaderItem.fileSize = readInt();
mHeaderItem.headerSize = readInt();
/*mHeaderItem.endianTag =*/ readInt();
/*mHeaderItem.linkSize =*/ readInt();
/*mHeaderItem.linkOff =*/ readInt();
/*mHeaderItem.mapOff =*/ readInt();
mHeaderItem.stringIdsSize = readInt();
mHeaderItem.stringIdsOff = readInt();
mHeaderItem.typeIdsSize = readInt();
mHeaderItem.typeIdsOff = readInt();
mHeaderItem.protoIdsSize = readInt();
mHeaderItem.protoIdsOff = readInt();
mHeaderItem.fieldIdsSize = readInt();
mHeaderItem.fieldIdsOff = readInt();
mHeaderItem.methodIdsSize = readInt();
mHeaderItem.methodIdsOff = readInt();
mHeaderItem.classDefsSize = readInt();
mHeaderItem.classDefsOff = readInt();
/*mHeaderItem.dataSize =*/ readInt();
/*mHeaderItem.dataOff =*/ readInt();
}
/**
* Loads the string table out of the DEX.
*
* 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);
for (int i = 0; i < count; i++) {
stringOffsets[i] = readInt();
}
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);
for (int i = 0; i < count; i++) {
mTypeIds[i] = new TypeIdItem();
mTypeIds[i].descriptorIdx = readInt();
//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);
/*
* Read the proto ID items.
*/
for (int i = 0; i < count; i++) {
mProtoIds[i] = new ProtoIdItem();
mProtoIds[i].shortyIdx = readInt();
mProtoIds[i].returnTypeIdx = readInt();
mProtoIds[i].parametersOff = readInt();
//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
protoId.types = new int[size];
for (int j = 0; j < size; j++) {
protoId.types[j] = readShort() & 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);
for (int i = 0; i < count; i++) {
mFieldIds[i] = new FieldIdItem();
mFieldIds[i].classIdx = readShort() & 0xffff;
mFieldIds[i].typeIdx = readShort() & 0xffff;
mFieldIds[i].nameIdx = readInt();
//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);
for (int i = 0; i < count; i++) {
mMethodIds[i] = new MethodIdItem();
mMethodIds[i].classIdx = readShort() & 0xffff;
mMethodIds[i].protoIdx = readShort() & 0xffff;
mMethodIds[i].nameIdx = readInt();
//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);
for (int i = 0; i < count; i++) {
mClassDefs[i] = new ClassDefItem();
mClassDefs[i].classIdx = readInt();
/* access_flags = */ readInt();
/* superclass_idx = */ readInt();
/* interfaces_off = */ readInt();
/* source_file_idx = */ readInt();
/* annotations_off = */ readInt();
/* class_data_off = */ readInt();
/* static_values_off = */ readInt();
//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 16-bit integer, byte-swapping if necessary.
*/
short readShort() throws IOException {
mDexFile.readFully(tmpBuf, 0, 2);
if (isBigEndian) {
return (short) ((tmpBuf[1] & 0xff) | ((tmpBuf[0] & 0xff) << 8));
} else {
return (short) ((tmpBuf[0] & 0xff) | ((tmpBuf[1] & 0xff) << 8));
}
}
/**
* Reads a signed 32-bit integer, byte-swapping if necessary.
*/
int readInt() throws IOException {
mDexFile.readFully(tmpBuf, 0, 4);
if (isBigEndian) {
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 a UTF-8 string.
*
* We don't know how long the UTF-8 string is, so we have to read one
* byte at a time. We could make an educated guess based on the
* utf16_size and seek back if we get it wrong, but seeking backward
* may cause the underlying implementation to reload I/O buffers.
*/
String readString() throws IOException {
int utf16len = readUnsignedLeb128();
byte inBuf[] = new byte[utf16len * 3]; // worst case
int idx;
for (idx = 0; idx < inBuf.length; idx++) {
byte val = readByte();
if (val == 0)
break;
inBuf[idx] = val;
}
return new String(inBuf, 0, idx, "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 = {
0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x36, 0x00 };
public static final byte[] DEX_FILE_MAGIC_API_13 = {
0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00 };
public static final int ENDIAN_CONSTANT = 0x12345678;
public static final int REVERSE_ENDIAN_CONSTANT = 0x78563412;
}
/**
* Holds the contents of a type_id_item.
*
* 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.
*
* 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
}
}