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