1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dexdeps; 18 19 import java.io.IOException; 20 import java.io.RandomAccessFile; 21 import java.nio.ByteBuffer; 22 import java.nio.ByteOrder; 23 import java.nio.charset.StandardCharsets; 24 import java.util.Arrays; 25 26 /** 27 * Data extracted from a DEX file. 28 */ 29 public class DexData { 30 private RandomAccessFile mDexFile; 31 private HeaderItem mHeaderItem; 32 private String[] mStrings; // strings from string_data_* 33 private TypeIdItem[] mTypeIds; 34 private ProtoIdItem[] mProtoIds; 35 private FieldIdItem[] mFieldIds; 36 private MethodIdItem[] mMethodIds; 37 private ClassDefItem[] mClassDefs; 38 39 private byte tmpBuf[] = new byte[4]; 40 private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; 41 42 /** 43 * Constructs a new DexData for this file. 44 */ DexData(RandomAccessFile raf)45 public DexData(RandomAccessFile raf) { 46 mDexFile = raf; 47 } 48 49 /** 50 * Loads the contents of the DEX file into our data structures. 51 * 52 * @throws IOException if we encounter a problem while reading 53 * @throws DexDataException if the DEX contents look bad 54 */ load()55 public void load() throws IOException { 56 parseHeaderItem(); 57 58 loadStrings(); 59 loadTypeIds(); 60 loadProtoIds(); 61 loadFieldIds(); 62 loadMethodIds(); 63 loadClassDefs(); 64 65 markInternalClasses(); 66 } 67 68 /** 69 * Verifies the given magic number. 70 */ verifyMagic(byte[] magic)71 private static boolean verifyMagic(byte[] magic) { 72 return Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_v035) || 73 Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_v037) || 74 Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_v038) || 75 Arrays.equals(magic, HeaderItem.DEX_FILE_MAGIC_v039); 76 } 77 78 /** 79 * Parses the interesting bits out of the header. 80 */ parseHeaderItem()81 void parseHeaderItem() throws IOException { 82 mHeaderItem = new HeaderItem(); 83 84 seek(0); 85 86 byte[] magic = new byte[8]; 87 readBytes(magic); 88 if (!verifyMagic(magic)) { 89 System.err.println("Magic number is wrong -- are you sure " + 90 "this is a DEX file?"); 91 throw new DexDataException(); 92 } 93 94 /* 95 * Read the endian tag, so we properly swap things as we read 96 * them from here on. 97 */ 98 seek(8+4+20+4+4); 99 mHeaderItem.endianTag = readInt(); 100 if (mHeaderItem.endianTag == HeaderItem.ENDIAN_CONSTANT) { 101 /* do nothing */ 102 } else if (mHeaderItem.endianTag == HeaderItem.REVERSE_ENDIAN_CONSTANT){ 103 /* file is big-endian (!), reverse future reads */ 104 mByteOrder = ByteOrder.BIG_ENDIAN; 105 } else { 106 System.err.println("Endian constant has unexpected value " + 107 Integer.toHexString(mHeaderItem.endianTag)); 108 throw new DexDataException(); 109 } 110 111 seek(8+4+20); // magic, checksum, signature 112 ByteBuffer buffer = readByteBuffer(Integer.BYTES * 20); 113 mHeaderItem.fileSize = buffer.getInt(); 114 mHeaderItem.headerSize = buffer.getInt(); 115 /*mHeaderItem.endianTag =*/ buffer.getInt(); 116 /*mHeaderItem.linkSize =*/ buffer.getInt(); 117 /*mHeaderItem.linkOff =*/ buffer.getInt(); 118 /*mHeaderItem.mapOff =*/ buffer.getInt(); 119 mHeaderItem.stringIdsSize = buffer.getInt(); 120 mHeaderItem.stringIdsOff = buffer.getInt(); 121 mHeaderItem.typeIdsSize = buffer.getInt(); 122 mHeaderItem.typeIdsOff = buffer.getInt(); 123 mHeaderItem.protoIdsSize = buffer.getInt(); 124 mHeaderItem.protoIdsOff = buffer.getInt(); 125 mHeaderItem.fieldIdsSize = buffer.getInt(); 126 mHeaderItem.fieldIdsOff = buffer.getInt(); 127 mHeaderItem.methodIdsSize = buffer.getInt(); 128 mHeaderItem.methodIdsOff = buffer.getInt(); 129 mHeaderItem.classDefsSize = buffer.getInt(); 130 mHeaderItem.classDefsOff = buffer.getInt(); 131 /*mHeaderItem.dataSize =*/ buffer.getInt(); 132 /*mHeaderItem.dataOff =*/ buffer.getInt(); 133 } 134 135 /** 136 * Loads the string table out of the DEX. 137 * 138 * First we read all of the string_id_items, then we read all of the 139 * string_data_item. Doing it this way should allow us to avoid 140 * seeking around in the file. 141 */ loadStrings()142 void loadStrings() throws IOException { 143 int count = mHeaderItem.stringIdsSize; 144 int stringOffsets[] = new int[count]; 145 146 //System.out.println("reading " + count + " strings"); 147 148 seek(mHeaderItem.stringIdsOff); 149 readByteBuffer(Integer.BYTES * count).asIntBuffer().get(stringOffsets); 150 151 mStrings = new String[count]; 152 153 seek(stringOffsets[0]); 154 for (int i = 0; i < count; i++) { 155 seek(stringOffsets[i]); // should be a no-op 156 mStrings[i] = readString(); 157 //System.out.println("STR: " + i + ": " + mStrings[i]); 158 } 159 } 160 161 /** 162 * Loads the type ID list. 163 */ loadTypeIds()164 void loadTypeIds() throws IOException { 165 int count = mHeaderItem.typeIdsSize; 166 mTypeIds = new TypeIdItem[count]; 167 168 //System.out.println("reading " + count + " typeIds"); 169 seek(mHeaderItem.typeIdsOff); 170 ByteBuffer buffer = readByteBuffer(Integer.BYTES * count); 171 for (int i = 0; i < count; i++) { 172 mTypeIds[i] = new TypeIdItem(); 173 mTypeIds[i].descriptorIdx = buffer.getInt(); 174 175 //System.out.println(i + ": " + mTypeIds[i].descriptorIdx + 176 // " " + mStrings[mTypeIds[i].descriptorIdx]); 177 } 178 } 179 180 /** 181 * Loads the proto ID list. 182 */ loadProtoIds()183 void loadProtoIds() throws IOException { 184 int count = mHeaderItem.protoIdsSize; 185 mProtoIds = new ProtoIdItem[count]; 186 187 //System.out.println("reading " + count + " protoIds"); 188 seek(mHeaderItem.protoIdsOff); 189 ByteBuffer buffer = readByteBuffer(Integer.BYTES * 3 * count); 190 191 /* 192 * Read the proto ID items. 193 */ 194 for (int i = 0; i < count; i++) { 195 mProtoIds[i] = new ProtoIdItem(); 196 mProtoIds[i].shortyIdx = buffer.getInt(); 197 mProtoIds[i].returnTypeIdx = buffer.getInt(); 198 mProtoIds[i].parametersOff = buffer.getInt(); 199 200 //System.out.println(i + ": " + mProtoIds[i].shortyIdx + 201 // " " + mStrings[mProtoIds[i].shortyIdx]); 202 } 203 204 /* 205 * Go back through and read the type lists. 206 */ 207 for (int i = 0; i < count; i++) { 208 ProtoIdItem protoId = mProtoIds[i]; 209 210 int offset = protoId.parametersOff; 211 212 if (offset == 0) { 213 protoId.types = new int[0]; 214 continue; 215 } else { 216 seek(offset); 217 int size = readInt(); // #of entries in list 218 buffer = readByteBuffer(Short.BYTES * size); 219 protoId.types = new int[size]; 220 221 for (int j = 0; j < size; j++) { 222 protoId.types[j] = buffer.getShort() & 0xffff; 223 } 224 } 225 } 226 } 227 228 /** 229 * Loads the field ID list. 230 */ loadFieldIds()231 void loadFieldIds() throws IOException { 232 int count = mHeaderItem.fieldIdsSize; 233 mFieldIds = new FieldIdItem[count]; 234 235 //System.out.println("reading " + count + " fieldIds"); 236 seek(mHeaderItem.fieldIdsOff); 237 ByteBuffer buffer = readByteBuffer((Integer.BYTES + Short.BYTES * 2) * count); 238 for (int i = 0; i < count; i++) { 239 mFieldIds[i] = new FieldIdItem(); 240 mFieldIds[i].classIdx = buffer.getShort() & 0xffff; 241 mFieldIds[i].typeIdx = buffer.getShort() & 0xffff; 242 mFieldIds[i].nameIdx = buffer.getInt(); 243 244 //System.out.println(i + ": " + mFieldIds[i].nameIdx + 245 // " " + mStrings[mFieldIds[i].nameIdx]); 246 } 247 } 248 249 /** 250 * Loads the method ID list. 251 */ loadMethodIds()252 void loadMethodIds() throws IOException { 253 int count = mHeaderItem.methodIdsSize; 254 mMethodIds = new MethodIdItem[count]; 255 256 //System.out.println("reading " + count + " methodIds"); 257 seek(mHeaderItem.methodIdsOff); 258 ByteBuffer buffer = readByteBuffer((Integer.BYTES + Short.BYTES * 2) * count); 259 for (int i = 0; i < count; i++) { 260 mMethodIds[i] = new MethodIdItem(); 261 mMethodIds[i].classIdx = buffer.getShort() & 0xffff; 262 mMethodIds[i].protoIdx = buffer.getShort() & 0xffff; 263 mMethodIds[i].nameIdx = buffer.getInt(); 264 265 //System.out.println(i + ": " + mMethodIds[i].nameIdx + 266 // " " + mStrings[mMethodIds[i].nameIdx]); 267 } 268 } 269 270 /** 271 * Loads the class defs list. 272 */ loadClassDefs()273 void loadClassDefs() throws IOException { 274 int count = mHeaderItem.classDefsSize; 275 mClassDefs = new ClassDefItem[count]; 276 277 //System.out.println("reading " + count + " classDefs"); 278 seek(mHeaderItem.classDefsOff); 279 ByteBuffer buffer = readByteBuffer(Integer.BYTES * 8 * count); 280 for (int i = 0; i < count; i++) { 281 mClassDefs[i] = new ClassDefItem(); 282 mClassDefs[i].classIdx = buffer.getInt(); 283 284 /* access_flags = */ buffer.getInt(); 285 /* superclass_idx = */ buffer.getInt(); 286 /* interfaces_off = */ buffer.getInt(); 287 /* source_file_idx = */ buffer.getInt(); 288 /* annotations_off = */ buffer.getInt(); 289 /* class_data_off = */ buffer.getInt(); 290 /* static_values_off = */ buffer.getInt(); 291 292 //System.out.println(i + ": " + mClassDefs[i].classIdx + " " + 293 // mStrings[mTypeIds[mClassDefs[i].classIdx].descriptorIdx]); 294 } 295 } 296 297 /** 298 * Sets the "internal" flag on type IDs which are defined in the 299 * DEX file or within the VM (e.g. primitive classes and arrays). 300 */ markInternalClasses()301 void markInternalClasses() { 302 for (int i = mClassDefs.length -1; i >= 0; i--) { 303 mTypeIds[mClassDefs[i].classIdx].internal = true; 304 } 305 306 for (int i = 0; i < mTypeIds.length; i++) { 307 String className = mStrings[mTypeIds[i].descriptorIdx]; 308 309 if (className.length() == 1) { 310 // primitive class 311 mTypeIds[i].internal = true; 312 } else if (className.charAt(0) == '[') { 313 mTypeIds[i].internal = true; 314 } 315 316 //System.out.println(i + " " + 317 // (mTypeIds[i].internal ? "INTERNAL" : "external") + " - " + 318 // mStrings[mTypeIds[i].descriptorIdx]); 319 } 320 } 321 322 323 /* 324 * ======================================================================= 325 * Queries 326 * ======================================================================= 327 */ 328 329 /** 330 * Returns the class name, given an index into the type_ids table. 331 */ classNameFromTypeIndex(int idx)332 private String classNameFromTypeIndex(int idx) { 333 return mStrings[mTypeIds[idx].descriptorIdx]; 334 } 335 336 /** 337 * Returns an array of method argument type strings, given an index 338 * into the proto_ids table. 339 */ argArrayFromProtoIndex(int idx)340 private String[] argArrayFromProtoIndex(int idx) { 341 ProtoIdItem protoId = mProtoIds[idx]; 342 String[] result = new String[protoId.types.length]; 343 344 for (int i = 0; i < protoId.types.length; i++) { 345 result[i] = mStrings[mTypeIds[protoId.types[i]].descriptorIdx]; 346 } 347 348 return result; 349 } 350 351 /** 352 * Returns a string representing the method's return type, given an 353 * index into the proto_ids table. 354 */ returnTypeFromProtoIndex(int idx)355 private String returnTypeFromProtoIndex(int idx) { 356 ProtoIdItem protoId = mProtoIds[idx]; 357 return mStrings[mTypeIds[protoId.returnTypeIdx].descriptorIdx]; 358 } 359 360 /** 361 * Returns an array with all of the class references that don't 362 * correspond to classes in the DEX file. Each class reference has 363 * a list of the referenced fields and methods associated with 364 * that class. 365 */ getExternalReferences()366 public ClassRef[] getExternalReferences() { 367 // create a sparse array of ClassRef that parallels mTypeIds 368 ClassRef[] sparseRefs = new ClassRef[mTypeIds.length]; 369 370 // create entries for all externally-referenced classes 371 int count = 0; 372 for (int i = 0; i < mTypeIds.length; i++) { 373 if (!mTypeIds[i].internal) { 374 sparseRefs[i] = 375 new ClassRef(mStrings[mTypeIds[i].descriptorIdx]); 376 count++; 377 } 378 } 379 380 // add fields and methods to the appropriate class entry 381 addExternalFieldReferences(sparseRefs); 382 addExternalMethodReferences(sparseRefs); 383 384 // crunch out the sparseness 385 ClassRef[] classRefs = new ClassRef[count]; 386 int idx = 0; 387 for (int i = 0; i < mTypeIds.length; i++) { 388 if (sparseRefs[i] != null) 389 classRefs[idx++] = sparseRefs[i]; 390 } 391 392 assert idx == count; 393 394 return classRefs; 395 } 396 397 /** 398 * Runs through the list of field references, inserting external 399 * references into the appropriate ClassRef. 400 */ addExternalFieldReferences(ClassRef[] sparseRefs)401 private void addExternalFieldReferences(ClassRef[] sparseRefs) { 402 for (int i = 0; i < mFieldIds.length; i++) { 403 if (!mTypeIds[mFieldIds[i].classIdx].internal) { 404 FieldIdItem fieldId = mFieldIds[i]; 405 FieldRef newFieldRef = new FieldRef( 406 classNameFromTypeIndex(fieldId.classIdx), 407 classNameFromTypeIndex(fieldId.typeIdx), 408 mStrings[fieldId.nameIdx]); 409 sparseRefs[mFieldIds[i].classIdx].addField(newFieldRef); 410 } 411 } 412 } 413 414 /** 415 * Runs through the list of method references, inserting external 416 * references into the appropriate ClassRef. 417 */ addExternalMethodReferences(ClassRef[] sparseRefs)418 private void addExternalMethodReferences(ClassRef[] sparseRefs) { 419 for (int i = 0; i < mMethodIds.length; i++) { 420 if (!mTypeIds[mMethodIds[i].classIdx].internal) { 421 MethodIdItem methodId = mMethodIds[i]; 422 MethodRef newMethodRef = new MethodRef( 423 classNameFromTypeIndex(methodId.classIdx), 424 argArrayFromProtoIndex(methodId.protoIdx), 425 returnTypeFromProtoIndex(methodId.protoIdx), 426 mStrings[methodId.nameIdx]); 427 sparseRefs[mMethodIds[i].classIdx].addMethod(newMethodRef); 428 } 429 } 430 } 431 432 433 /* 434 * ======================================================================= 435 * Basic I/O functions 436 * ======================================================================= 437 */ 438 439 /** 440 * Seeks the DEX file to the specified absolute position. 441 */ seek(int position)442 void seek(int position) throws IOException { 443 mDexFile.seek(position); 444 } 445 446 /** 447 * Fills the buffer by reading bytes from the DEX file. 448 */ readBytes(byte[] buffer)449 void readBytes(byte[] buffer) throws IOException { 450 mDexFile.readFully(buffer); 451 } 452 453 /** 454 * Reads a single signed byte value. 455 */ readByte()456 byte readByte() throws IOException { 457 mDexFile.readFully(tmpBuf, 0, 1); 458 return tmpBuf[0]; 459 } 460 461 /** 462 * Reads a signed 32-bit integer, byte-swapping if necessary. 463 */ readInt()464 int readInt() throws IOException { 465 mDexFile.readFully(tmpBuf, 0, 4); 466 467 if (mByteOrder == ByteOrder.BIG_ENDIAN) { 468 return (tmpBuf[3] & 0xff) | ((tmpBuf[2] & 0xff) << 8) | 469 ((tmpBuf[1] & 0xff) << 16) | ((tmpBuf[0] & 0xff) << 24); 470 } else { 471 return (tmpBuf[0] & 0xff) | ((tmpBuf[1] & 0xff) << 8) | 472 ((tmpBuf[2] & 0xff) << 16) | ((tmpBuf[3] & 0xff) << 24); 473 } 474 } 475 476 /** 477 * Reads a variable-length unsigned LEB128 value. Does not attempt to 478 * verify that the value is valid. 479 * 480 * @throws EOFException if we run off the end of the file 481 */ readUnsignedLeb128()482 int readUnsignedLeb128() throws IOException { 483 int result = 0; 484 byte val; 485 486 do { 487 val = readByte(); 488 result = (result << 7) | (val & 0x7f); 489 } while (val < 0); 490 491 return result; 492 } 493 494 /** 495 * Reads bytes and transforms them into a ByteBuffer with the desired byte order set, from which 496 * primitive values can be read. 497 */ readByteBuffer(int size)498 ByteBuffer readByteBuffer(int size) throws IOException { 499 byte bytes[] = new byte[size]; 500 mDexFile.read(bytes); 501 return ByteBuffer.wrap(bytes).order(mByteOrder); 502 } 503 504 /** 505 * Reads a UTF-8 string. 506 * 507 * We don't know how long the UTF-8 string is, so we try to read the worst case amount of bytes. 508 * 509 * Note that the dex file pointer will likely be at a wrong location after this operation, which 510 * means it can't be used in the middle of sequential reads. 511 */ readString()512 String readString() throws IOException { 513 int utf16len = readUnsignedLeb128(); 514 byte inBuf[] = new byte[utf16len * 3]; // worst case 515 516 int bytesRead = mDexFile.read(inBuf); 517 for (int i = 0; i < bytesRead; i++) { 518 if (inBuf[i] == 0) { 519 bytesRead = i; 520 break; 521 } 522 } 523 524 return new String(inBuf, 0, bytesRead, "UTF-8"); 525 } 526 527 /* 528 * ======================================================================= 529 * Internal "structure" declarations 530 * ======================================================================= 531 */ 532 533 /** 534 * Holds the contents of a header_item. 535 */ 536 static class HeaderItem { 537 public int fileSize; 538 public int headerSize; 539 public int endianTag; 540 public int stringIdsSize, stringIdsOff; 541 public int typeIdsSize, typeIdsOff; 542 public int protoIdsSize, protoIdsOff; 543 public int fieldIdsSize, fieldIdsOff; 544 public int methodIdsSize, methodIdsOff; 545 public int classDefsSize, classDefsOff; 546 547 /* expected magic values */ 548 public static final byte[] DEX_FILE_MAGIC_v035 = 549 "dex\n035\0".getBytes(StandardCharsets.US_ASCII); 550 551 // Dex version 036 skipped because of an old dalvik bug on some versions 552 // of android where dex files with that version number would erroneously 553 // be accepted and run. See: art/runtime/dex_file.cc 554 555 // V037 was introduced in API LEVEL 24 556 public static final byte[] DEX_FILE_MAGIC_v037 = 557 "dex\n037\0".getBytes(StandardCharsets.US_ASCII); 558 559 // V038 was introduced in API LEVEL 26 560 public static final byte[] DEX_FILE_MAGIC_v038 = 561 "dex\n038\0".getBytes(StandardCharsets.US_ASCII); 562 563 // V039 was introduced in API LEVEL 28 564 public static final byte[] DEX_FILE_MAGIC_v039 = 565 "dex\n039\0".getBytes(StandardCharsets.US_ASCII); 566 567 public static final int ENDIAN_CONSTANT = 0x12345678; 568 public static final int REVERSE_ENDIAN_CONSTANT = 0x78563412; 569 } 570 571 /** 572 * Holds the contents of a type_id_item. 573 * 574 * This is chiefly a list of indices into the string table. We need 575 * some additional bits of data, such as whether or not the type ID 576 * represents a class defined in this DEX, so we use an object for 577 * each instead of a simple integer. (Could use a parallel array, but 578 * since this is a desktop app it's not essential.) 579 */ 580 static class TypeIdItem { 581 public int descriptorIdx; // index into string_ids 582 583 public boolean internal; // defined within this DEX file? 584 } 585 586 /** 587 * Holds the contents of a proto_id_item. 588 */ 589 static class ProtoIdItem { 590 public int shortyIdx; // index into string_ids 591 public int returnTypeIdx; // index into type_ids 592 public int parametersOff; // file offset to a type_list 593 594 public int types[]; // contents of type list 595 } 596 597 /** 598 * Holds the contents of a field_id_item. 599 */ 600 static class FieldIdItem { 601 public int classIdx; // index into type_ids (defining class) 602 public int typeIdx; // index into type_ids (field type) 603 public int nameIdx; // index into string_ids 604 } 605 606 /** 607 * Holds the contents of a method_id_item. 608 */ 609 static class MethodIdItem { 610 public int classIdx; // index into type_ids 611 public int protoIdx; // index into proto_ids 612 public int nameIdx; // index into string_ids 613 } 614 615 /** 616 * Holds the contents of a class_def_item. 617 * 618 * We don't really need a class for this, but there's some stuff in 619 * the class_def_item that we might want later. 620 */ 621 static class ClassDefItem { 622 public int classIdx; // index into type_ids 623 } 624 } 625