1 /* 2 * Copyright 2012, Google Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package org.jf.dexlib2.dexbacked; 33 34 import com.google.common.io.ByteStreams; 35 import org.jf.dexlib2.Opcodes; 36 import org.jf.dexlib2.dexbacked.raw.*; 37 import org.jf.dexlib2.dexbacked.util.FixedSizeSet; 38 import org.jf.dexlib2.iface.DexFile; 39 import org.jf.util.ExceptionWithContext; 40 41 import javax.annotation.Nonnull; 42 import javax.annotation.Nullable; 43 import java.io.EOFException; 44 import java.io.IOException; 45 import java.io.InputStream; 46 import java.util.Set; 47 48 public class DexBackedDexFile extends BaseDexBuffer implements DexFile { 49 private final Opcodes opcodes; 50 51 private final int stringCount; 52 private final int stringStartOffset; 53 private final int typeCount; 54 private final int typeStartOffset; 55 private final int protoCount; 56 private final int protoStartOffset; 57 private final int fieldCount; 58 private final int fieldStartOffset; 59 private final int methodCount; 60 private final int methodStartOffset; 61 private final int classCount; 62 private final int classStartOffset; 63 64 private DexBackedDexFile(Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) { 65 super(buf); 66 67 this.opcodes = opcodes; 68 69 if (verifyMagic) { 70 verifyMagicAndByteOrder(buf, offset); 71 } 72 73 stringCount = readSmallUint(HeaderItem.STRING_COUNT_OFFSET); 74 stringStartOffset = readSmallUint(HeaderItem.STRING_START_OFFSET); 75 typeCount = readSmallUint(HeaderItem.TYPE_COUNT_OFFSET); 76 typeStartOffset = readSmallUint(HeaderItem.TYPE_START_OFFSET); 77 protoCount = readSmallUint(HeaderItem.PROTO_COUNT_OFFSET); 78 protoStartOffset = readSmallUint(HeaderItem.PROTO_START_OFFSET); 79 fieldCount = readSmallUint(HeaderItem.FIELD_COUNT_OFFSET); 80 fieldStartOffset = readSmallUint(HeaderItem.FIELD_START_OFFSET); 81 methodCount = readSmallUint(HeaderItem.METHOD_COUNT_OFFSET); 82 methodStartOffset = readSmallUint(HeaderItem.METHOD_START_OFFSET); 83 classCount = readSmallUint(HeaderItem.CLASS_COUNT_OFFSET); 84 classStartOffset = readSmallUint(HeaderItem.CLASS_START_OFFSET); 85 } 86 87 public DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull BaseDexBuffer buf) { 88 this(opcodes, buf.buf); 89 } 90 91 public DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, int offset) { 92 this(opcodes, buf, offset, false); 93 } 94 95 public DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf) { 96 this(opcodes, buf, 0, true); 97 } 98 99 public static DexBackedDexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is) 100 throws IOException { 101 if (!is.markSupported()) { 102 throw new IllegalArgumentException("InputStream must support mark"); 103 } 104 is.mark(44); 105 byte[] partialHeader = new byte[44]; 106 try { 107 ByteStreams.readFully(is, partialHeader); 108 } catch (EOFException ex) { 109 throw new NotADexFile("File is too short"); 110 } finally { 111 is.reset(); 112 } 113 114 verifyMagicAndByteOrder(partialHeader, 0); 115 116 byte[] buf = ByteStreams.toByteArray(is); 117 return new DexBackedDexFile(opcodes, buf, 0, false); 118 } 119 120 public Opcodes getOpcodes() { 121 return opcodes; 122 } 123 124 public boolean isOdexFile() { 125 return false; 126 } 127 128 @Nonnull 129 @Override 130 public Set<? extends DexBackedClassDef> getClasses() { 131 return new FixedSizeSet<DexBackedClassDef>() { 132 @Nonnull 133 @Override 134 public DexBackedClassDef readItem(int index) { 135 return new DexBackedClassDef(DexBackedDexFile.this, getClassDefItemOffset(index)); 136 } 137 138 @Override 139 public int size() { 140 return classCount; 141 } 142 }; 143 } 144 145 private static void verifyMagicAndByteOrder(@Nonnull byte[] buf, int offset) { 146 if (!HeaderItem.verifyMagic(buf, offset)) { 147 StringBuilder sb = new StringBuilder("Invalid magic value:"); 148 for (int i=0; i<8; i++) { 149 sb.append(String.format(" %02x", buf[i])); 150 } 151 throw new NotADexFile(sb.toString()); 152 } 153 154 int endian = HeaderItem.getEndian(buf, offset); 155 if (endian == HeaderItem.BIG_ENDIAN_TAG) { 156 throw new ExceptionWithContext("Big endian dex files are not currently supported"); 157 } 158 159 if (endian != HeaderItem.LITTLE_ENDIAN_TAG) { 160 throw new ExceptionWithContext("Invalid endian tag: 0x%x", endian); 161 } 162 } 163 164 public int getStringIdItemOffset(int stringIndex) { 165 if (stringIndex < 0 || stringIndex >= stringCount) { 166 throw new InvalidItemIndex(stringIndex, "String index out of bounds: %d", stringIndex); 167 } 168 return stringStartOffset + stringIndex*StringIdItem.ITEM_SIZE; 169 } 170 171 public int getTypeIdItemOffset(int typeIndex) { 172 if (typeIndex < 0 || typeIndex >= typeCount) { 173 throw new InvalidItemIndex(typeIndex, "Type index out of bounds: %d", typeIndex); 174 } 175 return typeStartOffset + typeIndex*TypeIdItem.ITEM_SIZE; 176 } 177 178 public int getFieldIdItemOffset(int fieldIndex) { 179 if (fieldIndex < 0 || fieldIndex >= fieldCount) { 180 throw new InvalidItemIndex(fieldIndex, "Field index out of bounds: %d", fieldIndex); 181 } 182 return fieldStartOffset + fieldIndex*FieldIdItem.ITEM_SIZE; 183 } 184 185 public int getMethodIdItemOffset(int methodIndex) { 186 if (methodIndex < 0 || methodIndex >= methodCount) { 187 throw new InvalidItemIndex(methodIndex, "Method index out of bounds: %d", methodIndex); 188 } 189 return methodStartOffset + methodIndex*MethodIdItem.ITEM_SIZE; 190 } 191 192 public int getProtoIdItemOffset(int protoIndex) { 193 if (protoIndex < 0 || protoIndex >= protoCount) { 194 throw new InvalidItemIndex(protoIndex, "Proto index out of bounds: %d", protoIndex); 195 } 196 return protoStartOffset + protoIndex*ProtoIdItem.ITEM_SIZE; 197 } 198 199 public int getClassDefItemOffset(int classIndex) { 200 if (classIndex < 0 || classIndex >= classCount) { 201 throw new InvalidItemIndex(classIndex, "Class index out of bounds: %d", classIndex); 202 } 203 return classStartOffset + classIndex*ClassDefItem.ITEM_SIZE; 204 } 205 206 public int getClassCount() { 207 return classCount; 208 } 209 210 @Nonnull 211 public String getString(int stringIndex) { 212 int stringOffset = getStringIdItemOffset(stringIndex); 213 int stringDataOffset = readSmallUint(stringOffset); 214 DexReader reader = readerAt(stringDataOffset); 215 int utf16Length = reader.readSmallUleb128(); 216 return reader.readString(utf16Length); 217 } 218 219 @Nullable 220 public String getOptionalString(int stringIndex) { 221 if (stringIndex == -1) { 222 return null; 223 } 224 return getString(stringIndex); 225 } 226 227 @Nonnull 228 public String getType(int typeIndex) { 229 int typeOffset = getTypeIdItemOffset(typeIndex); 230 int stringIndex = readSmallUint(typeOffset); 231 return getString(stringIndex); 232 } 233 234 @Nullable 235 public String getOptionalType(int typeIndex) { 236 if (typeIndex == -1) { 237 return null; 238 } 239 return getType(typeIndex); 240 } 241 242 @Override 243 @Nonnull 244 public DexReader readerAt(int offset) { 245 return new DexReader(this, offset); 246 } 247 248 public static class NotADexFile extends RuntimeException { 249 public NotADexFile() { 250 } 251 252 public NotADexFile(Throwable cause) { 253 super(cause); 254 } 255 256 public NotADexFile(String message) { 257 super(message); 258 } 259 260 public NotADexFile(String message, Throwable cause) { 261 super(message, cause); 262 } 263 } 264 265 public static class InvalidItemIndex extends ExceptionWithContext { 266 private final int itemIndex; 267 268 public InvalidItemIndex(int itemIndex) { 269 super(""); 270 this.itemIndex = itemIndex; 271 } 272 273 public InvalidItemIndex(int itemIndex, String message, Object... formatArgs) { 274 super(message, formatArgs); 275 this.itemIndex = itemIndex; 276 } 277 278 public int getInvalidIndex() { 279 return itemIndex; 280 } 281 } 282 } 283