/* Software Name : AsmDex * Version : 1.0 * * Copyright © 2012 France Télécom * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ package org.ow2.asmdex.lowLevelUtils; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.HashMap; import org.ow2.asmdex.structureReader.ClassDefinitionItem; import org.ow2.asmdex.structureReader.FieldIdItem; import org.ow2.asmdex.structureReader.MethodIdItem; /** * Reads a DEX file, builds a representation of this file, and implements * primitive Dalvik methods in order to conveniently parse the file structure. * * FIXME In order to optimize, the low level code is duplicated from DalvikValueReader * to avoid indirection. * * @author Julien Névo, based on the work by Pierre Crégut. */ public class DexFileReader extends BasicDexFileReader implements IDalvikValueReader { /** * Content of the Reader. */ private byte[] contents; /** * position */ protected int pos = 0; /** * Takes an input stream corresponding to a Dex file and populate the structure. * @param dexBytes bytes of the Dex file to read. * @throws IOException */ @Override public void parse(byte[] dexBytes) throws IOException { super.parse(dexBytes); contents = dexBytes; } // -------------------------------------- // Structural helper methods // -------------------------------------- /** * Fill the returned HashMap with the index of the fields found in one of the structures * which offset is given. These structures can be a field_annotation, a * method_annotation, or a parameter_annotation. This method works because they * all have the exact same structure. * The dex file reader must point on the beginning of the list of annotations * (field_annotation, method_annotation, or a parameter_annotation). * @param nbFields count of fields. * @return a map filled with the index linked to its offset. */ public HashMap<Integer, Integer> fillOffsetHashMap(int nbFields) { HashMap<Integer, Integer> map = new HashMap<Integer, Integer>(nbFields); for (int i = 0; i < nbFields; i++) { map.put(uint(), uint()); } return map; } /** * Gets the String related to string_id_item table, according to the string index given. * @param index index of the string to find. * @return the string linked to the string_id_item table, at the index given. */ public String getStringItemFromStringIndex(int index) { return constantPoolStrings[index]; } /** * Gets the String related to the type_id_item table, linked to the string_id_item * table, according to the type index given. * The dex file reader previous position <i>is</i> saved. * @param index index of the string to find. * @return the string linked to the type_id_item table, at the index given. */ public String getStringItemFromTypeIndex(int index) { return constantPoolTypes[index]; } /** * Gets the Name, third field of the method_id_item table, * according to the method index given. * The dex file reader previous position <i>is</i> saved. * @param index index of the name to find. * @return the name of the <i>index</i> element of the method_id_item table. */ public String getNameFromMethodIndex(int index) { int saveReaderPosition = getPos(); seek(getOffsetMethodIdItem(index) + 4); // + 4 to skip class_idx and type_idx. String result = getStringItemFromStringIndex(uint()); seek(saveReaderPosition); return result; } /** * Gets the String related to the shorty_idx item in the proto_id_item table, * according to the proto index given. * The dex file reader previous position <i>is</i> saved. * @param index index of the Prototype to find. * @return the string linked to the shorty_idx item in the proto_id_item table, at the index given. */ public String getShortyStringFromProtoIndex(int index) { int saveReaderPosition = getPos(); seek(getOffsetProtoIdItem(index)); String result = getStringItemFromStringIndex(uint()); seek(saveReaderPosition); return result; } /** * Gets the Name, third field of the field_it_item table, * according to the field index given. * The dex file reader previous position <i>is</i> saved. * @param index index of the name to find. * @return the name of the <i>index</i> element of the field_id_item table. */ public String getNameFromFieldIndex(int index) { int saveReaderPosition = getPos(); seek(getOffsetFieldIdItem(index) + 4); // + 4 to skip class_idx and type_idx. String result = getStringItemFromStringIndex(uint()); seek(saveReaderPosition); return result; } /** * Gets the Type Name, second field of the field_it_item table, * linked to the type_id list, and according to the field index given. * The dex file reader previous position <i>is</i> saved. * @param index index of the name to find. * @return the type name of the <i>index</i> element of the field_id_item table. */ public String getTypeNameFromFieldIndex(int index) { int saveReaderPosition = getPos(); seek(getOffsetFieldIdItem(index)); skipShort(); // Skip class_idx. String result = getStringItemFromTypeIndex(ushort()); seek(saveReaderPosition); // Also saved by the subroutine... return result; } /** * Returns all the type_ids indexes of a type_list structure. * The dex file reader <i>must</i> point at the beginning of a type_list structure. * The reader current position <i>will</i> be changed. * @return an array containing all the indexes to the type_id of the type_list structure. */ public int[] getTypeIdIndexesFromTypeList() { int nbEntries = uint(); int[] indexes = new int[nbEntries]; for (int i = 0; i < nbEntries; i++) { indexes[i] = ushort(); } return indexes; } /** * Returns all the annotation item offsets of an annotation_set_item structure. * The dex file reader <i>must</i> point at the beginning of an annotation_set_item * structure. The reader current position <i>will</i> be changed. * @return all the annotation item offsets of an annotation_set_item structure. */ public int[] getAnnotationItemOffsetsFromAnnotationSetItem() { int nbEntries = uint(); int[] offsets = new int[nbEntries]; for (int i = 0; i < nbEntries; i++) { offsets[i] = uint(); } return offsets; } /** * Returns the descriptor of a prototype which id is given. The descriptor * is constructed from the return type and parameters of the structure. * The dex file reader position <i>is</i> saved. * @param prototypeIndex index of the prototype. * @return a String containing the descriptor of the prototype. */ public String getDescriptorFromPrototypeIndex(int prototypeIndex) { int savedPosition = getPos(); // Get to the proto_id_item structure. seek(getOffsetProtoIdItem(prototypeIndex)); skipInt(); // Skip shorty_idx. // First, the return type. StringBuilder methodDescriptor = new StringBuilder(); methodDescriptor.append(getStringItemFromTypeIndex(uint())); // Then, the parameters, if any. int parametersListOffset = uint(); if (parametersListOffset != 0) { seek(parametersListOffset); // The offset points to the type_list structure. int[] parameterIndexes = getTypeIdIndexesFromTypeList(); for (int paramIndex = 0, parameterIndexesSize = parameterIndexes.length; paramIndex < parameterIndexesSize; paramIndex++) { methodDescriptor.append(getStringItemFromTypeIndex(parameterIndexes[paramIndex])); } } seek(savedPosition); return methodDescriptor.toString(); } /** * Returns a MethodIdItem structure containing the information related to * the method_id_item whose index is given. * The dex file reader position is <i>not</i> saved. * @param methodId index of the Method. * @return a MethodIdItem structure containing the information related to * the method_id_item whose index is given. */ public MethodIdItem getMethodIdItem(int methodId) { seek(getOffsetMethodIdItem(methodId)); return new MethodIdItem(ushort(), ushort(), uint()); } /** * Gets a FieldIdItem structure containing the class_idx, type_idx and name_idx of the * Field which index is given. * The dex file reader previous position is <i>not</i> saved. * @param index index of the Field to find. * @return a FieldIdItem structure. */ public FieldIdItem getFieldIdItem(int index) { seek(getOffsetFieldIdItem(index)); return new FieldIdItem(ushort(), ushort(), uint()); } /** * Returns a structure containing all the information of a class_def_item * structure, which Class index is given. * The dex file reader position <i>is</i> saved. * @param classIndex index of the Class. * @return a structure containing all the information of a class_def_item * structure. */ public ClassDefinitionItem getClassDefinitionItem(int classIndex) { return new ClassDefinitionItem(this, classIndex, getClassDefinitionOffset(classIndex)); } // ---------------------------------------- // Dalvik primitives // ---------------------------------------- /* @Override public byte[] getContents() { return reader.getContents(); } @Override final public int getPos() { return reader.pos; } @Override final public boolean hasMore() { return reader.hasMore(); } @Override final public byte sbyte() { return reader.sbyte(); } @Override final public void seek(int pos) { reader.pos = pos; } @Override final public int sint() { return reader.sint(); } @Override final public long sizedLong(int sz) { return reader.sizedLong(sz); } @Override final public long completeSignSizedLong(long l, int sz) { return reader.completeSignSizedLong(l, sz); } @Override final public int sleb128() { return reader.sleb128(); } @Override final public short sshort() { return reader.sshort(); } @Override final public short ubyte() { return reader.ubyte(); } @Override final public int uint() { return reader.uint(); } @Override final public int uleb128() { return reader.uleb128(); } @Override final public long uleb128_16() { return reader.uleb128_16(); } @Override final public int uleb128_p1() { return reader.uleb128_p1(); } @Override public String unicodeString(int strSize) { return reader.unicodeString(strSize); } @Override final public int ushort() { return reader.ushort(); } @Override public String utf8String() { return reader.utf8String(); } @Override final public void relativeSeek(int offset) { reader.pos += offset; } @Override final public void skipInt() { reader.pos += 4; } @Override final public void skipShort() { reader.pos += 2; } @Override final public void skipByte() { reader.pos += 1; } */ // What follows is the duplicate code from DalvikValueReader. // We may want to comment it and uncomment the code above to get a cleaner code, // but slightly slower. /** * Reads an integer directly from an input stream. Usually used * while builder the value reader. * @param stream * @return an integer signed * @throws IOException */ final public static int sint(InputStream stream) throws IOException { byte [] contents = new byte [4]; if (stream.read(contents) != 4) throw new RuntimeException("Cannot read integer"); return ((contents [0] & 0xFF) | ((contents[1] & 0xFF) << 8) | ((contents[2] & 0xFF) << 16) | ((contents[3] & 0xFF) << 24)); } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#sbyte() */ final public byte sbyte() { return contents[pos++]; } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#ubyte() */ final public short ubyte() { return (short) (contents[pos++] & 0xff); } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#sshort() */ final public short sshort() { short v = (short) ((contents [pos] & 0xff) | ((contents[pos + 1] & 0xff) << 8)); pos += 2; return v; } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#ushort() */ final public int ushort() { return ((contents [pos++] & 0xff) | ((contents[pos++] & 0xff) << 8)); } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#sint() */ final public int sint() { int v = ((contents [pos] & 0xff)| ((contents[pos + 1] & 0xff) << 8) | ((contents[pos + 2] & 0xff) << 16) | ((contents[pos + 3] & 0xff) << 24)); pos += 4; return v; } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#uint() */ //final public long uint() { final public int uint() { // WARNING : int return whereas a long *should* be used (but slower !). For now, no problem... int v = ((contents [pos] & 0xff) | ((contents[pos + 1] & 0xff) << 8) | ((contents[pos + 2] & 0xff) << 16)) | ((contents[pos + 3] & 0xff) << 24); pos += 4; return v; } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#sleb128() */ final public int sleb128() { int r, v; v = contents[pos++] & 0xff; r = v & 0x7f; if (v >= 0x80) { v = contents[pos++] & 0xff; r |= (v & 0x7f) << 7; if (v >= 0x80) { v = contents[pos++] & 0xff; r |= (v & 0x7f) << 14; if (v >= 0x80) { v = contents[pos++] & 0xff; r |= (v & 0x7f) << 21; if (v >= 0x80) { v = contents[pos++] & 0xff; r |= (v & 0x7f) << 28; if (v >= 0x80) { throw new RuntimeException("Bad sleb128"); } } else if ((v & 0x40) != 0) r |= 0xf0000000; } else if ((v & 0x40) != 0) r |= 0xffe00000; } else if ((v & 0x40) != 0) r |= 0xffffc000; } else if ((v & 0x40) != 0) r |= 0xffffff80; return r; } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#uleb128() */ //final public long uleb128(){ final public int uleb128(){ // WARNING : int returned whereas a long *should* be used (but slower !). For now, no problem... //long r; int r; int v; v = contents[pos++] & 0xff; r = v & 0x7f; if (v >= 0x80) { v = contents[pos++] & 0xff; r |= (v & 0x7f) << 7; if (v >= 0x80) { v = contents[pos++] & 0xff; r |= (v & 0x7f) << 14; if (v >= 0x80) { v = contents[pos++] & 0xff; r |= (v & 0x7f) << 21; if (v >= 0x80) { v = contents[pos++] & 0xff; r |= (v & 0x7f) << 28; if (v >= 0x80) { throw new RuntimeException("Bad uleb128"); } } } } } return r; } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#uleb128_p1() */ //final public long uleb128_p1() { final public int uleb128_p1() { return uleb128() - 1; } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#uleb128_16() */ final public long uleb128_16(){ long r; int v; v = ushort(); r = v & 0x7fff; if (v > 0x8000) { v = ushort(); r |= (v & 0x7fff) << 15; } return r; } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#sizedLong(int) */ final public long sizedLong(int sz) { long result = 0; int length = sz + 1; for(int i=0; i < length; i++) { short v = ubyte(); result = result | (long)v << (8*i); // v must be cast, else we may lose data ! } return result; } /** * Extends a long read with SizedLong of length sz according to its sign. * @param l * @param sz size-1 of the encoded number. * @return a long */ final public long completeSignSizedLong(long l, int sz) { sz++; int shift = (8 - sz) * 8; return l << shift >> shift; // Generates the sign. } /** * Reads a given number of bytes. * @param b */ public void bytes(byte [] b) { for(int i=0; i < b.length; i++) b[i] = contents[pos+i]; pos += b.length; } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#utf8String() */ public String utf8String() { StringBuilder buf = new StringBuilder(); int c; int v; while( (c = (contents[pos++] & 0xff)) != 0) { if ((c & 0x80) == 0x80) { if ((c & 0xe0) == 0xc0) { c &= 0x1f; v = contents[pos++] & 0x3f; c = c << 6 | v; } else if ((c & 0xf0) == 0xe0) { v = contents[pos++] & 0x3f; c = c << 6 | v; v = contents[pos++] & 0x3f; c = c << 6 | v; } else { System.out.println("Bad (point 4) UTF 8 " + Integer.toBinaryString(c)); } } buf.append((char) c); } return buf.toString(); } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#seek(int) */ final public void seek(int pos) { this.pos = pos; } public void relativeSeek(int offset) { pos += offset; } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#getPos() */ final public int getPos() { return pos; } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#unicodeString(int) */ public String unicodeString(int strSize) { char [] content = new char [strSize]; for(int i=0; i < strSize; i++) content[i] = (char) ushort(); int c; if ((c = ushort()) != 0) { System.out.println("Did not find the ending character\n " + Arrays.toString(content) + " " + c ); } return new String(content); } /* (non-Javadoc) * @see org.ow2.asmdex.IDalvikValueReader#hasMore() */ final public boolean hasMore() { return pos < contents.length; } // For debug only - not exposed. int peek(int i) { return ((int) contents[i]) & 0xff; } @Override final public void skipInt() { pos += 4; } @Override final public void skipShort() { pos += 2; } @Override final public void skipByte() { pos += 1; } @Override public byte[] getContents() { return contents; } }