diff options
author | Andy McFadden <> | 2009-03-26 16:02:55 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-26 16:02:55 -0700 |
commit | 12d6d4c0ea192b6a924df0df1e3b14ce1ed5793b (patch) | |
tree | 207f93a0e689132f1faab540004274e62b1d4906 /tools | |
parent | 642d9646f0399648e377ed8e1b36eba9b12f84b2 (diff) | |
download | android_dalvik-12d6d4c0ea192b6a924df0df1e3b14ce1ed5793b.tar.gz android_dalvik-12d6d4c0ea192b6a924df0df1e3b14ce1ed5793b.tar.bz2 android_dalvik-12d6d4c0ea192b6a924df0df1e3b14ce1ed5793b.zip |
Automated import from //branches/master/...@143003,143003
Diffstat (limited to 'tools')
-rw-r--r-- | tools/dexdeps/Android.mk | 44 | ||||
-rw-r--r-- | tools/dexdeps/etc/dexdeps | 69 | ||||
-rw-r--r-- | tools/dexdeps/etc/manifest.txt | 1 | ||||
-rw-r--r-- | tools/dexdeps/src/Android.mk | 32 | ||||
-rw-r--r-- | tools/dexdeps/src/com/android/dexdeps/DexData.java | 656 | ||||
-rw-r--r-- | tools/dexdeps/src/com/android/dexdeps/DexDataException.java | 24 | ||||
-rw-r--r-- | tools/dexdeps/src/com/android/dexdeps/FieldRef.java | 43 | ||||
-rw-r--r-- | tools/dexdeps/src/com/android/dexdeps/Main.java | 200 | ||||
-rw-r--r-- | tools/dexdeps/src/com/android/dexdeps/MethodRef.java | 69 | ||||
-rw-r--r-- | tools/dexdeps/src/com/android/dexdeps/Output.java | 61 | ||||
-rw-r--r-- | tools/dexdeps/src/com/android/dexdeps/UsageException.java | 24 |
11 files changed, 1223 insertions, 0 deletions
diff --git a/tools/dexdeps/Android.mk b/tools/dexdeps/Android.mk new file mode 100644 index 000000000..9c2cec700 --- /dev/null +++ b/tools/dexdeps/Android.mk @@ -0,0 +1,44 @@ +# 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. + +LOCAL_PATH := $(call my-dir) + +# We use copy-file-to-new-target so that the installed +# script files' timestamps are at least as new as the +# .jar files they wrap. + +# the dexdeps script +# ============================================================ +include $(CLEAR_VARS) +LOCAL_IS_HOST_MODULE := true +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE := dexdeps + +include $(BUILD_SYSTEM)/base_rules.mk + +$(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/dexdeps$(COMMON_JAVA_PACKAGE_SUFFIX) +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/dexdeps | $(ACP) + @echo "Copy: $(PRIVATE_MODULE) ($@)" + $(copy-file-to-new-target) + $(hide) chmod 755 $@ + +INTERNAL_DALVIK_MODULES += $(LOCAL_INSTALLED_MODULE) + +# the other stuff +# ============================================================ +subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \ + src \ + )) + +include $(subdirs) diff --git a/tools/dexdeps/etc/dexdeps b/tools/dexdeps/etc/dexdeps new file mode 100644 index 000000000..dc628bd14 --- /dev/null +++ b/tools/dexdeps/etc/dexdeps @@ -0,0 +1,69 @@ +#!/bin/bash +# +# 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. + +# Set up prog to be the path of this script, including following symlinks, +# and set up progdir to be the fully-qualified pathname of its directory. +prog="$0" +while [ -h "${prog}" ]; do + newProg=`/bin/ls -ld "${prog}"` + newProg=`expr "${newProg}" : ".* -> \(.*\)$"` + if expr "x${newProg}" : 'x/' >/dev/null; then + prog="${newProg}" + else + progdir=`dirname "${prog}"` + prog="${progdir}/${newProg}" + fi +done +oldwd=`pwd` +progdir=`dirname "${prog}"` +cd "${progdir}" +progdir=`pwd` +prog="${progdir}"/`basename "${prog}"` +cd "${oldwd}" + +jarfile=dexdeps.jar +libdir="$progdir" +if [ ! -r "$libdir/$jarfile" ] +then + libdir=`dirname "$progdir"`/tools/lib +fi +if [ ! -r "$libdir/$jarfile" ] +then + libdir=`dirname "$progdir"`/framework +fi +if [ ! -r "$libdir/$jarfile" ] +then + echo `basename "$prog"`": can't find $jarfile" + exit 1 +fi + +javaOpts="" + +# Alternatively, this will extract any parameter "-Jxxx" from the command line +# and pass them to Java (instead of to dexdeps). +while expr "x$1" : 'x-J' >/dev/null; do + opt=`expr "$1" : '-J\(.*\)'` + javaOpts="${javaOpts} -${opt}" + shift +done + +if [ "$OSTYPE" = "cygwin" ] ; then + jarpath=`cygpath -w "$libdir/$jarfile"` +else + jarpath="$libdir/$jarfile" +fi + +exec java $javaOpts -jar "$jarpath" "$@" diff --git a/tools/dexdeps/etc/manifest.txt b/tools/dexdeps/etc/manifest.txt new file mode 100644 index 000000000..760674402 --- /dev/null +++ b/tools/dexdeps/etc/manifest.txt @@ -0,0 +1 @@ +Main-Class: com.android.dexdeps.Main diff --git a/tools/dexdeps/src/Android.mk b/tools/dexdeps/src/Android.mk new file mode 100644 index 000000000..756a0b370 --- /dev/null +++ b/tools/dexdeps/src/Android.mk @@ -0,0 +1,32 @@ +# 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. + +LOCAL_PATH := $(call my-dir) + + +# dexdeps java library +# ============================================================ +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_JAR_MANIFEST := ../etc/manifest.txt + +LOCAL_MODULE:= dexdeps + +include $(BUILD_HOST_JAVA_LIBRARY) + +INTERNAL_DALVIK_MODULES += $(LOCAL_INSTALLED_MODULE) + +include $(BUILD_DROIDDOC) + diff --git a/tools/dexdeps/src/com/android/dexdeps/DexData.java b/tools/dexdeps/src/com/android/dexdeps/DexData.java new file mode 100644 index 000000000..0bfd20253 --- /dev/null +++ b/tools/dexdeps/src/com/android/dexdeps/DexData.java @@ -0,0 +1,656 @@ +/* + * 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(); + } + + + /** + * 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 (!Arrays.equals(magic, HeaderItem.DEX_FILE_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 + * ======================================================================= + */ + + /** + * Converts a single-character primitive type into its human-readable + * equivalent. + */ + private String primitiveTypeLabel(char typeChar) { + /* primitive type; substitute human-readable name in */ + switch (typeChar) { + case 'B': return "byte"; + case 'C': return "char"; + case 'D': return "double"; + case 'F': return "float"; + case 'I': return "int"; + case 'J': return "long"; + case 'S': return "short"; + case 'V': return "void"; + case 'Z': return "boolean"; + default: + /* huh? */ + System.err.println("Unexpected class char " + typeChar); + assert false; + return "UNKNOWN"; + } + } + + /** + * Converts a descriptor to dotted form. For example, + * "Ljava/lang/String;" becomes "java.lang.String", and "[I" becomes + * "int[]. + */ + private String descriptorToDot(String descr) { + int targetLen = descr.length(); + int offset = 0; + int arrayDepth = 0; + + /* strip leading [s; will be added to end */ + while (targetLen > 1 && descr.charAt(offset) == '[') { + offset++; + targetLen--; + } + arrayDepth = offset; + + if (targetLen == 1) { + descr = primitiveTypeLabel(descr.charAt(offset)); + offset = 0; + targetLen = descr.length(); + } else { + /* account for leading 'L' and trailing ';' */ + if (targetLen >= 2 && descr.charAt(offset) == 'L' && + descr.charAt(offset+targetLen-1) == ';') + { + targetLen -= 2; /* two fewer chars to copy */ + offset++; /* skip the 'L' */ + } + } + + char[] buf = new char[targetLen + arrayDepth * 2]; + + /* copy class name over */ + int i; + for (i = 0; i < targetLen; i++) { + char ch = descr.charAt(offset + i); + buf[i] = (ch == '/') ? '.' : ch; + } + + /* add the appopriate number of brackets for arrays */ + while (arrayDepth-- > 0) { + buf[i++] = '['; + buf[i++] = ']'; + } + assert i == buf.length; + + return new String(buf); + } + + /** + * Returns the dot-form class name, given an index into the type_ids + * table. + */ + private String classNameFromTypeIndex(int idx) { + String descriptor = mStrings[mTypeIds[idx].descriptorIdx]; + return descriptorToDot(descriptor); + } + + /** + * Returns the method prototype descriptor, given an index into the + * proto_ids table. + */ + private String protoStringFromProtoIndex(int idx) { + StringBuilder builder = new StringBuilder(); + ProtoIdItem protoId = mProtoIds[idx]; + + builder.append("("); + for (int i = 0; i < protoId.types.length; i++) { + String elem = mStrings[mTypeIds[protoId.types[i]].descriptorIdx]; + builder.append(elem); + } + + builder.append(")"); + String ret = mStrings[mTypeIds[protoId.returnTypeIdx].descriptorIdx]; + builder.append(ret); + + return builder.toString(); + } + + /** + * Returns an array with all of the field references that don't + * correspond to classes in the DEX file. + */ + public FieldRef[] getExternalFieldReferences() { + // get a count + int count = 0; + for (int i = 0; i < mFieldIds.length; i++) { + if (!mTypeIds[mFieldIds[i].classIdx].internal) + count++; + } + + //System.out.println("count is " + count + " of " + mFieldIds.length); + + FieldRef[] fieldRefs = new FieldRef[count]; + count = 0; + for (int i = 0; i < mFieldIds.length; i++) { + if (!mTypeIds[mFieldIds[i].classIdx].internal) { + FieldIdItem fieldId = mFieldIds[i]; + fieldRefs[count++] = + new FieldRef(classNameFromTypeIndex(fieldId.classIdx), + classNameFromTypeIndex(fieldId.typeIdx), + mStrings[fieldId.nameIdx]); + } + } + + assert count == fieldRefs.length; + + return fieldRefs; + } + + /** + * Returns an array with all of the method references that don't + * correspond to classes in the DEX file. + */ + public MethodRef[] getExternalMethodReferences() { + // get a count + int count = 0; + for (int i = 0; i < mMethodIds.length; i++) { + if (!mTypeIds[mMethodIds[i].classIdx].internal) + count++; + } + + //System.out.println("count is " + count + " of " + mMethodIds.length); + + MethodRef[] methodRefs = new MethodRef[count]; + count = 0; + for (int i = 0; i < mMethodIds.length; i++) { + if (!mTypeIds[mMethodIds[i].classIdx].internal) { + MethodIdItem methodId = mMethodIds[i]; + methodRefs[count++] = + new MethodRef(classNameFromTypeIndex(methodId.classIdx), + protoStringFromProtoIndex(methodId.protoIdx), + mStrings[methodId.nameIdx]); + } + } + + assert count == methodRefs.length; + + return methodRefs; + } + + /* + * ======================================================================= + * 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, 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 + } +} + diff --git a/tools/dexdeps/src/com/android/dexdeps/DexDataException.java b/tools/dexdeps/src/com/android/dexdeps/DexDataException.java new file mode 100644 index 000000000..e51853f53 --- /dev/null +++ b/tools/dexdeps/src/com/android/dexdeps/DexDataException.java @@ -0,0 +1,24 @@ +/* + * 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; + +/** + * Bad data found inside a DEX file. + */ +public class DexDataException extends RuntimeException { +} + diff --git a/tools/dexdeps/src/com/android/dexdeps/FieldRef.java b/tools/dexdeps/src/com/android/dexdeps/FieldRef.java new file mode 100644 index 000000000..96e0ef01f --- /dev/null +++ b/tools/dexdeps/src/com/android/dexdeps/FieldRef.java @@ -0,0 +1,43 @@ +/* + * 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; + +public class FieldRef { + private String mDeclClass, mFieldType, mFieldName; + + /** + * Initializes a new field reference. + */ + public FieldRef(String declClass, String fieldType, String fieldName) { + mDeclClass = declClass; + mFieldType = fieldType; + mFieldName = fieldName; + } + + public String getDeclClassName() { + return mDeclClass; + } + + public String getTypeName() { + return mFieldType; + } + + public String getName() { + return mFieldName; + } +} + diff --git a/tools/dexdeps/src/com/android/dexdeps/Main.java b/tools/dexdeps/src/com/android/dexdeps/Main.java new file mode 100644 index 000000000..43698458f --- /dev/null +++ b/tools/dexdeps/src/com/android/dexdeps/Main.java @@ -0,0 +1,200 @@ +/* + * 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.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +public class Main { + private static final String CLASSES_DEX = "classes.dex"; + + private String mInputFileName; + private String mOutputFormat = "brief"; + + /** + * Entry point. + */ + public static void main(String[] args) { + Main main = new Main(); + main.run(args); + } + + /** + * Start things up. + */ + void run(String[] args) { + try { + parseArgs(args); + RandomAccessFile raf = openInputFile(); + DexData dexData = new DexData(raf); + dexData.load(); + + Output.generate(dexData, mOutputFormat); + } catch (UsageException ue) { + usage(); + System.exit(2); + } catch (IOException ioe) { + /* a message was already reported, just bail quietly */ + System.exit(1); + } catch (DexDataException dde) { + /* a message was already reported, just bail quietly */ + System.exit(1); + } + } + + /** + * Opens the input file, which could be a .dex or a .jar/.apk with a + * classes.dex inside. If the latter, we extract the contents to a + * temporary file. + */ + RandomAccessFile openInputFile() throws IOException { + RandomAccessFile raf; + + raf = openInputFileAsZip(); + if (raf == null) { + File inputFile = new File(mInputFileName); + raf = new RandomAccessFile(inputFile, "r"); + } + + return raf; + } + + /** + * Tries to open the input file as a Zip archive (jar/apk) with a + * "classes.dex" inside. + * + * @return a RandomAccessFile for classes.dex, or null if the input file + * is not a zip archive + * @throws IOException if the file isn't found, or it's a zip and + * classes.dex isn't found inside + */ + RandomAccessFile openInputFileAsZip() throws IOException { + ZipFile zipFile; + + /* + * Try it as a zip file. + */ + try { + zipFile = new ZipFile(mInputFileName); + } catch (FileNotFoundException fnfe) { + /* not found, no point in retrying as non-zip */ + System.err.println("Unable to open '" + mInputFileName + "': " + + fnfe.getMessage()); + throw fnfe; + } catch (ZipException ze) { + /* not a zip */ + return null; + } + + /* + * We know it's a zip; see if there's anything useful inside. A + * failure here results in some type of IOException (of which + * ZipException is a subclass). + */ + ZipEntry entry = zipFile.getEntry(CLASSES_DEX); + if (entry == null) { + System.err.println("Unable to find '" + CLASSES_DEX + + "' in '" + mInputFileName + "'"); + zipFile.close(); + throw new ZipException(); + } + + InputStream zis = zipFile.getInputStream(entry); + + /* + * Create a temp file to hold the DEX data, open it, and delete it + * to ensure it doesn't hang around if we fail. + */ + File tempFile = File.createTempFile("dexdeps", ".dex"); + System.out.println("+++ using temp " + tempFile); + RandomAccessFile raf = new RandomAccessFile(tempFile, "rw"); + tempFile.delete(); + + /* + * Copy all data from input stream to output file. + */ + byte copyBuf[] = new byte[32768]; + int actual; + + while (true) { + actual = zis.read(copyBuf); + if (actual == -1) + break; + + raf.write(copyBuf, 0, actual); + } + + zis.close(); + raf.seek(0); + + return raf; + } + + + /** + * Parses command-line arguments. + * + * @throws UsageException if arguments are missing or poorly formed + */ + void parseArgs(String[] args) { + int idx; + + for (idx = 0; idx < args.length; idx++) { + String arg = args[idx]; + + if (arg.equals("--") || !arg.startsWith("--")) { + break; + } else if (arg.startsWith("--format=")) { + mOutputFormat = arg.substring(arg.indexOf('=') + 1); + if (!mOutputFormat.equals("brief") && + !mOutputFormat.equals("xml")) + { + System.err.println("Unknown format '" + mOutputFormat +"'"); + throw new UsageException(); + } + System.out.println("Using format " + mOutputFormat); + } else { + System.err.println("Unknown option '" + arg + "'"); + throw new UsageException(); + } + } + + // expecting one argument left + if (idx != args.length - 1) { + throw new UsageException(); + } + + mInputFileName = args[idx]; + } + + /** + * Prints command-line usage info. + */ + void usage() { + System.err.println("\nUsage: dexdeps [options] <file.{dex,apk,jar}>"); + System.err.println("Options:"); + System.err.println(" --format={brief,xml}"); + } +} + diff --git a/tools/dexdeps/src/com/android/dexdeps/MethodRef.java b/tools/dexdeps/src/com/android/dexdeps/MethodRef.java new file mode 100644 index 000000000..1ca68528d --- /dev/null +++ b/tools/dexdeps/src/com/android/dexdeps/MethodRef.java @@ -0,0 +1,69 @@ +/* + * 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; + +public class MethodRef { + private String mDeclClass, mDescriptor, mMethodName; + + /** + * Initializes a new field reference. + */ + public MethodRef(String declClass, String descriptor, String methodName) { + mDeclClass = declClass; + mDescriptor = descriptor; + mMethodName = methodName; + } + + /** + * Gets the name of the method's declaring class. + */ + public String getDeclClassName() { + return mDeclClass; + } + + /** + * Gets the method's descriptor. + */ + public String getDescriptor() { + return mDescriptor; + } + + /** + * Gets the method's name. + */ + public String getName() { + return mMethodName; + } + + /** + * Gets the method arguments as an array of type strings. + */ + public String[] getArguments() { + // TODO + return null; + } + + /** + * Gets the method's return type. + */ + public String getReturnType() { + // TODO + return null; + } +} + + diff --git a/tools/dexdeps/src/com/android/dexdeps/Output.java b/tools/dexdeps/src/com/android/dexdeps/Output.java new file mode 100644 index 000000000..0c350b68a --- /dev/null +++ b/tools/dexdeps/src/com/android/dexdeps/Output.java @@ -0,0 +1,61 @@ +/* + * 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; + +/** + * Generate fancy output. + */ +public class Output { + public static void generate(DexData dexData, String format) { + FieldRef[] externFieldRefs; + MethodRef[] externMethodRefs; + + externFieldRefs = dexData.getExternalFieldReferences(); + externMethodRefs = dexData.getExternalMethodReferences(); + + if (format.equals("brief")) { + printFieldRefs(externFieldRefs); + printMethodRefs(externMethodRefs); + } else if (format.equals("xml")) { + // ... + } else { + /* should've been trapped in arg handler */ + throw new RuntimeException("unknown output format"); + } + } + + static void printFieldRefs(FieldRef[] fields) { + System.out.println("Fields:"); + for (int i = 0; i < fields.length; i++) { + FieldRef ref = fields[i]; + + System.out.println(ref.getDeclClassName() + "." + + ref.getName() + " : " + ref.getTypeName()); + } + } + + static void printMethodRefs(MethodRef[] methods) { + System.out.println("Methods:"); + for (int i = 0; i < methods.length; i++) { + MethodRef ref = methods[i]; + + System.out.println(ref.getDeclClassName() + "." + + ref.getName() + " : " + ref.getDescriptor()); + } + } +} + diff --git a/tools/dexdeps/src/com/android/dexdeps/UsageException.java b/tools/dexdeps/src/com/android/dexdeps/UsageException.java new file mode 100644 index 000000000..f9f971b78 --- /dev/null +++ b/tools/dexdeps/src/com/android/dexdeps/UsageException.java @@ -0,0 +1,24 @@ +/* + * 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; + +/** + * Tells the main entry point to show the usage information and bail. + */ +public class UsageException extends RuntimeException { +} + |