1 /* 2 * Copyright 2012, Google LLC 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google LLC nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 package com.android.tools.smali.dexlib2.dexbacked; 32 33 import com.android.tools.smali.dexlib2.Opcodes; 34 import com.android.tools.smali.dexlib2.ReferenceType; 35 import com.android.tools.smali.dexlib2.dexbacked.raw.CallSiteIdItem; 36 import com.android.tools.smali.dexlib2.dexbacked.raw.ClassDefItem; 37 import com.android.tools.smali.dexlib2.dexbacked.raw.FieldIdItem; 38 import com.android.tools.smali.dexlib2.dexbacked.raw.HeaderItem; 39 import com.android.tools.smali.dexlib2.dexbacked.raw.HiddenApiClassDataItem; 40 import com.android.tools.smali.dexlib2.dexbacked.raw.ItemType; 41 import com.android.tools.smali.dexlib2.dexbacked.raw.MapItem; 42 import com.android.tools.smali.dexlib2.dexbacked.raw.MethodHandleItem; 43 import com.android.tools.smali.dexlib2.dexbacked.raw.MethodIdItem; 44 import com.android.tools.smali.dexlib2.dexbacked.raw.ProtoIdItem; 45 import com.android.tools.smali.dexlib2.dexbacked.raw.StringIdItem; 46 import com.android.tools.smali.dexlib2.dexbacked.raw.TypeIdItem; 47 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedCallSiteReference; 48 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedFieldReference; 49 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedMethodHandleReference; 50 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedMethodProtoReference; 51 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedMethodReference; 52 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedStringReference; 53 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedTypeReference; 54 import com.android.tools.smali.dexlib2.dexbacked.util.FixedSizeList; 55 import com.android.tools.smali.dexlib2.dexbacked.util.FixedSizeSet; 56 import com.android.tools.smali.dexlib2.iface.DexFile; 57 import com.android.tools.smali.dexlib2.iface.reference.Reference; 58 import com.android.tools.smali.dexlib2.util.DexUtil; 59 import com.android.tools.smali.dexlib2.writer.DexWriter; 60 import com.android.tools.smali.util.InputStreamUtil; 61 62 import javax.annotation.Nonnull; 63 import javax.annotation.Nullable; 64 65 import java.io.IOException; 66 import java.io.InputStream; 67 import java.util.AbstractList; 68 import java.util.List; 69 import java.util.Set; 70 71 public class DexBackedDexFile implements DexFile { 72 73 private final DexBuffer dexBuffer; 74 private final DexBuffer dataBuffer; 75 76 @Nonnull private final Opcodes opcodes; 77 78 private final int fileSize; 79 private final int stringCount; 80 private final int stringStartOffset; 81 private final int typeCount; 82 private final int typeStartOffset; 83 private final int protoCount; 84 private final int protoStartOffset; 85 private final int fieldCount; 86 private final int fieldStartOffset; 87 private final int methodCount; 88 private final int methodStartOffset; 89 private final int classCount; 90 private final int classStartOffset; 91 private final int mapOffset; 92 private final int hiddenApiRestrictionsOffset; 93 DexBackedDexFile(@ullable Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic)94 protected DexBackedDexFile(@Nullable Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) { 95 this(opcodes, buf, offset, verifyMagic, 0); 96 } 97 DexBackedDexFile(@ullable Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic, int header_offset)98 protected DexBackedDexFile(@Nullable Opcodes opcodes, 99 @Nonnull byte[] buf, 100 int offset, 101 boolean verifyMagic, 102 int header_offset) { 103 dexBuffer = new DexBuffer(buf, offset); 104 dataBuffer = new DexBuffer(buf, offset + getBaseDataOffset()); 105 106 int dexVersion = getVersion(buf, offset, verifyMagic); 107 108 if (opcodes == null) { 109 this.opcodes = getDefaultOpcodes(dexVersion); 110 } else { 111 this.opcodes = opcodes; 112 } 113 114 fileSize = dexBuffer.readSmallUint(header_offset + HeaderItem.FILE_SIZE_OFFSET); 115 stringCount = dexBuffer.readSmallUint(header_offset + HeaderItem.STRING_COUNT_OFFSET); 116 stringStartOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.STRING_START_OFFSET); 117 typeCount = dexBuffer.readSmallUint(header_offset + HeaderItem.TYPE_COUNT_OFFSET); 118 typeStartOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.TYPE_START_OFFSET); 119 protoCount = dexBuffer.readSmallUint(header_offset + HeaderItem.PROTO_COUNT_OFFSET); 120 protoStartOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.PROTO_START_OFFSET); 121 fieldCount = dexBuffer.readSmallUint(header_offset + HeaderItem.FIELD_COUNT_OFFSET); 122 fieldStartOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.FIELD_START_OFFSET); 123 methodCount = dexBuffer.readSmallUint(header_offset + HeaderItem.METHOD_COUNT_OFFSET); 124 methodStartOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.METHOD_START_OFFSET); 125 classCount = dexBuffer.readSmallUint(header_offset + HeaderItem.CLASS_COUNT_OFFSET); 126 classStartOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.CLASS_START_OFFSET); 127 mapOffset = dexBuffer.readSmallUint(header_offset + HeaderItem.MAP_OFFSET); 128 129 MapItem mapItem = getMapItemForSection(ItemType.HIDDENAPI_CLASS_DATA_ITEM); 130 if (mapItem != null) { 131 hiddenApiRestrictionsOffset = mapItem.getOffset(); 132 } else { 133 hiddenApiRestrictionsOffset = DexWriter.NO_OFFSET; 134 } 135 136 int container_off = 0; 137 if (dexVersion >= 41) { 138 container_off = dexBuffer.readSmallUint(header_offset + HeaderItem.CONTAINER_OFF_OFFSET); 139 } 140 if (container_off != header_offset) { 141 throw new DexUtil.InvalidFile(String.format("Unexpected container offset in header")); 142 } 143 } 144 145 /** 146 * @return The offset that various data offsets are relative to. This is always 0 for a dex file, but may be 147 * different for other related formats (e.g. cdex). 148 */ getBaseDataOffset()149 public int getBaseDataOffset() { 150 return 0; 151 } 152 153 /** 154 * @return Size of single dex file (out of potentially several dex files within a container). 155 */ getFileSize()156 public int getFileSize() { 157 return fileSize; 158 } 159 getVersion(byte[] buf, int offset, boolean verifyMagic)160 protected int getVersion(byte[] buf, int offset, boolean verifyMagic) { 161 if (verifyMagic) { 162 return DexUtil.verifyDexHeader(buf, offset); 163 } else { 164 return HeaderItem.getVersion(buf, offset); 165 } 166 } 167 getDefaultOpcodes(int version)168 protected Opcodes getDefaultOpcodes(int version) { 169 return Opcodes.forDexVersion(version); 170 } 171 getBuffer()172 public DexBuffer getBuffer() { 173 return dexBuffer; 174 } 175 getDataBuffer()176 public DexBuffer getDataBuffer() { 177 return dataBuffer; 178 } 179 DexBackedDexFile(@ullable Opcodes opcodes, @Nonnull DexBuffer buf)180 public DexBackedDexFile(@Nullable Opcodes opcodes, @Nonnull DexBuffer buf) { 181 this(opcodes, buf.buf, buf.baseOffset); 182 } 183 DexBackedDexFile(@ullable Opcodes opcodes, @Nonnull byte[] buf, int offset)184 public DexBackedDexFile(@Nullable Opcodes opcodes, @Nonnull byte[] buf, int offset) { 185 this(opcodes, buf, offset, false); 186 } 187 DexBackedDexFile(@ullable Opcodes opcodes, @Nonnull byte[] buf)188 public DexBackedDexFile(@Nullable Opcodes opcodes, @Nonnull byte[] buf) { 189 this(opcodes, buf, 0, true); 190 } 191 192 @Nonnull fromInputStream(@ullable Opcodes opcodes, @Nonnull InputStream is)193 public static DexBackedDexFile fromInputStream(@Nullable Opcodes opcodes, @Nonnull InputStream is) 194 throws IOException { 195 DexUtil.verifyDexHeader(is); 196 197 byte[] buf = InputStreamUtil.toByteArray(is); 198 return new DexBackedDexFile(opcodes, buf, 0, false); 199 } 200 getOpcodes()201 @Nonnull public Opcodes getOpcodes() { 202 return opcodes; 203 } 204 supportsOptimizedOpcodes()205 public boolean supportsOptimizedOpcodes() { 206 return false; 207 } 208 209 @Nonnull getClasses()210 public Set<? extends DexBackedClassDef> getClasses() { 211 return new FixedSizeSet<DexBackedClassDef>() { 212 @Nonnull 213 @Override 214 public DexBackedClassDef readItem(int index) { 215 return getClassSection().get(index); 216 } 217 218 @Override 219 public int size() { 220 return classCount; 221 } 222 }; 223 } 224 225 public List<DexBackedStringReference> getStringReferences() { 226 return new AbstractList<DexBackedStringReference>() { 227 @Override public DexBackedStringReference get(int index) { 228 if (index < 0 || index >= getStringSection().size()) { 229 throw new IndexOutOfBoundsException(); 230 } 231 return new DexBackedStringReference(DexBackedDexFile.this, index); 232 } 233 234 @Override public int size() { 235 return getStringSection().size(); 236 } 237 }; 238 } 239 240 public List<DexBackedTypeReference> getTypeReferences() { 241 return new AbstractList<DexBackedTypeReference>() { 242 @Override public DexBackedTypeReference get(int index) { 243 if (index < 0 || index >= getTypeSection().size()) { 244 throw new IndexOutOfBoundsException(); 245 } 246 return new DexBackedTypeReference(DexBackedDexFile.this, index); 247 } 248 249 @Override public int size() { 250 return getTypeSection().size(); 251 } 252 }; 253 } 254 255 public List<? extends Reference> getReferences(int referenceType) { 256 switch (referenceType) { 257 case ReferenceType.STRING: 258 return getStringReferences(); 259 case ReferenceType.TYPE: 260 return getTypeReferences(); 261 case ReferenceType.METHOD: 262 return getMethodSection(); 263 case ReferenceType.FIELD: 264 return getFieldSection(); 265 case ReferenceType.METHOD_PROTO: 266 return getMethodSection(); 267 case ReferenceType.METHOD_HANDLE: 268 return getMethodHandleSection(); 269 case ReferenceType.CALL_SITE: 270 return getCallSiteSection(); 271 default: 272 throw new IllegalArgumentException(String.format("Invalid reference type: %d", referenceType)); 273 } 274 } 275 276 public List<MapItem> getMapItems() { 277 final int mapSize = dataBuffer.readSmallUint(mapOffset); 278 279 return new FixedSizeList<MapItem>() { 280 @Override 281 public MapItem readItem(int index) { 282 int mapItemOffset = mapOffset + 4 + index * MapItem.ITEM_SIZE; 283 return new MapItem(DexBackedDexFile.this, mapItemOffset); 284 } 285 286 @Override public int size() { 287 return mapSize; 288 } 289 }; 290 } 291 292 @Nullable 293 public MapItem getMapItemForSection(int itemType) { 294 for (MapItem mapItem: getMapItems()) { 295 if (mapItem.getType() == itemType) { 296 return mapItem; 297 } 298 } 299 return null; 300 } 301 302 public static class NotADexFile extends RuntimeException { 303 public NotADexFile() { 304 } 305 306 public NotADexFile(Throwable cause) { 307 super(cause); 308 } 309 310 public NotADexFile(String message) { 311 super(message); 312 } 313 314 public NotADexFile(String message, Throwable cause) { 315 super(message, cause); 316 } 317 } 318 319 private OptionalIndexedSection<String> stringSection = new OptionalIndexedSection<String>() { 320 @Override 321 public String get(int index) { 322 int stringOffset = getOffset(index); 323 int stringDataOffset = dexBuffer.readSmallUint(stringOffset); 324 DexReader<? extends DexBuffer> reader = dataBuffer.readerAt(stringDataOffset); 325 int utf16Length = reader.readSmallUleb128(); 326 return reader.readString(utf16Length); 327 } 328 329 @Override 330 public int size() { 331 return stringCount; 332 } 333 334 @Nullable 335 @Override 336 public String getOptional(int index) { 337 if (index == -1) { 338 return null; 339 } 340 return get(index); 341 } 342 343 @Override 344 public int getOffset(int index) { 345 if (index < 0 || index >= size()) { 346 throw new IndexOutOfBoundsException( 347 String.format("Invalid string index %d, not in [0, %d)", index, size())); 348 } 349 return stringStartOffset + index* StringIdItem.ITEM_SIZE; 350 } 351 }; 352 353 public OptionalIndexedSection<String> getStringSection() { 354 return stringSection; 355 } 356 357 private OptionalIndexedSection<String> typeSection = new OptionalIndexedSection<String>() { 358 @Override 359 public String get(int index) { 360 int typeOffset = getOffset(index); 361 int stringIndex = dexBuffer.readSmallUint(typeOffset); 362 return getStringSection().get(stringIndex); 363 } 364 365 @Override 366 public int size() { 367 return typeCount; 368 } 369 370 @Nullable 371 @Override 372 public String getOptional(int index) { 373 if (index == -1) { 374 return null; 375 } 376 return get(index); 377 } 378 379 @Override 380 public int getOffset(int index) { 381 if (index < 0 || index >= size()) { 382 throw new IndexOutOfBoundsException( 383 String.format("Invalid type index %d, not in [0, %d)", index, size())); 384 } 385 return typeStartOffset + index * TypeIdItem.ITEM_SIZE; 386 } 387 }; 388 389 public OptionalIndexedSection<String> getTypeSection() { 390 return typeSection; 391 } 392 393 private IndexedSection<DexBackedFieldReference> fieldSection = new IndexedSection<DexBackedFieldReference>() { 394 @Override 395 public DexBackedFieldReference get(int index) { 396 return new DexBackedFieldReference(DexBackedDexFile.this, index); 397 } 398 399 @Override 400 public int size() { 401 return fieldCount; 402 } 403 404 @Override 405 public int getOffset(int index) { 406 if (index < 0 || index >= size()) { 407 throw new IndexOutOfBoundsException( 408 String.format("Invalid field index %d, not in [0, %d)", index, size())); 409 } 410 411 return fieldStartOffset + index * FieldIdItem.ITEM_SIZE; 412 } 413 }; 414 415 public IndexedSection<DexBackedFieldReference> getFieldSection() { 416 return fieldSection; 417 } 418 419 private IndexedSection<DexBackedMethodReference> methodSection = new IndexedSection<DexBackedMethodReference>() { 420 @Override 421 public DexBackedMethodReference get(int index) { 422 return new DexBackedMethodReference(DexBackedDexFile.this, index); 423 } 424 425 @Override 426 public int size() { 427 return methodCount; 428 } 429 430 @Override 431 public int getOffset(int index) { 432 if (index < 0 || index >= size()) { 433 throw new IndexOutOfBoundsException( 434 String.format("Invalid method index %d, not in [0, %d)", index, size())); 435 } 436 437 return methodStartOffset + index * MethodIdItem.ITEM_SIZE; 438 } 439 }; 440 441 public IndexedSection<DexBackedMethodReference> getMethodSection() { 442 return methodSection; 443 } 444 445 private IndexedSection<DexBackedMethodProtoReference> protoSection = 446 new IndexedSection<DexBackedMethodProtoReference>() { 447 @Override 448 public DexBackedMethodProtoReference get(int index) { 449 return new DexBackedMethodProtoReference(DexBackedDexFile.this, index); 450 } 451 452 @Override 453 public int size() { 454 return protoCount; 455 } 456 457 @Override 458 public int getOffset(int index) { 459 if (index < 0 || index >= size()) { 460 throw new IndexOutOfBoundsException( 461 String.format("Invalid proto index %d, not in [0, %d)", index, size())); 462 } 463 464 return protoStartOffset + index * ProtoIdItem.ITEM_SIZE; 465 } 466 }; 467 468 public IndexedSection<DexBackedMethodProtoReference> getProtoSection() { 469 return protoSection; 470 } 471 472 private IndexedSection<DexBackedClassDef> classSection = new IndexedSection<DexBackedClassDef>() { 473 @Override 474 public DexBackedClassDef get(int index) { 475 return new DexBackedClassDef(DexBackedDexFile.this, getOffset(index), 476 readHiddenApiRestrictionsOffset(index)); 477 } 478 479 @Override 480 public int size() { 481 return classCount; 482 } 483 484 @Override 485 public int getOffset(int index) { 486 if (index < 0 || index >= size()) { 487 throw new IndexOutOfBoundsException( 488 String.format("Invalid class index %d, not in [0, %d)", index, size())); 489 } 490 491 return classStartOffset + index * ClassDefItem.ITEM_SIZE; 492 } 493 }; 494 495 public IndexedSection<DexBackedClassDef> getClassSection() { 496 return classSection; 497 } 498 499 private IndexedSection<DexBackedCallSiteReference> callSiteSection = 500 new IndexedSection<DexBackedCallSiteReference>() { 501 @Override 502 public DexBackedCallSiteReference get(int index) { 503 return new DexBackedCallSiteReference(DexBackedDexFile.this, index); 504 } 505 506 @Override 507 public int size() { 508 MapItem mapItem = getMapItemForSection(ItemType.CALL_SITE_ID_ITEM); 509 if (mapItem == null) { 510 return 0; 511 } 512 return mapItem.getItemCount(); 513 } 514 515 @Override 516 public int getOffset(int index) { 517 MapItem mapItem = getMapItemForSection(ItemType.CALL_SITE_ID_ITEM); 518 if (index < 0 || index >= size()) { 519 throw new IndexOutOfBoundsException( 520 String.format("Invalid callsite index %d, not in [0, %d)", index, size())); 521 } 522 return mapItem.getOffset() + index * CallSiteIdItem.ITEM_SIZE; 523 } 524 }; 525 526 public IndexedSection<DexBackedCallSiteReference> getCallSiteSection() { 527 return callSiteSection; 528 } 529 530 private IndexedSection<DexBackedMethodHandleReference> methodHandleSection = 531 new IndexedSection<DexBackedMethodHandleReference>() { 532 @Override 533 public DexBackedMethodHandleReference get(int index) { 534 return new DexBackedMethodHandleReference(DexBackedDexFile.this, index); 535 } 536 537 @Override 538 public int size() { 539 MapItem mapItem = getMapItemForSection(ItemType.METHOD_HANDLE_ITEM); 540 if (mapItem == null) { 541 return 0; 542 } 543 return mapItem.getItemCount(); 544 } 545 546 @Override 547 public int getOffset(int index) { 548 MapItem mapItem = getMapItemForSection(ItemType.METHOD_HANDLE_ITEM); 549 if (index < 0 || index >= size()) { 550 throw new IndexOutOfBoundsException( 551 String.format("Invalid method handle index %d, not in [0, %d)", index, size())); 552 } 553 return mapItem.getOffset() + index * MethodHandleItem.ITEM_SIZE; 554 } 555 }; 556 557 public IndexedSection<DexBackedMethodHandleReference> getMethodHandleSection() { 558 return methodHandleSection; 559 } 560 561 protected DexBackedMethodImplementation createMethodImplementation( 562 @Nonnull DexBackedDexFile dexFile, @Nonnull DexBackedMethod method, int codeOffset) { 563 return new DexBackedMethodImplementation(dexFile, method, codeOffset); 564 } 565 566 private int readHiddenApiRestrictionsOffset(int classIndex) { 567 if (hiddenApiRestrictionsOffset == DexWriter.NO_OFFSET) { 568 return DexWriter.NO_OFFSET; 569 } 570 571 int offset = dexBuffer.readInt( 572 hiddenApiRestrictionsOffset + 573 HiddenApiClassDataItem.OFFSETS_LIST_OFFSET + 574 classIndex * HiddenApiClassDataItem.OFFSET_ITEM_SIZE); 575 if (offset == DexWriter.NO_OFFSET) { 576 return DexWriter.NO_OFFSET; 577 } 578 579 return hiddenApiRestrictionsOffset + offset; 580 } 581 582 public static abstract class OptionalIndexedSection<T> extends IndexedSection<T> { 583 /** 584 * @param index The index of the item, or -1 for a null item. 585 * @return The value at the given index, or null if index is -1. 586 * @throws IndexOutOfBoundsException if the index is out of bounds and is not -1. 587 */ 588 @Nullable public abstract T getOptional(int index); 589 } 590 591 public static abstract class IndexedSection<T> extends AbstractList<T> { 592 /** 593 * @param index The index of the item to get the offset for. 594 * @return The offset from the beginning of the dex file to the specified item. 595 * @throws IndexOutOfBoundsException if the index is out of bounds. 596 */ 597 public abstract int getOffset(int index); 598 } 599 } 600