1 /* 2 * Copyright (C) 2007 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.dx.dex.file; 18 19 import com.android.dx.dex.DexFormat; 20 import com.android.dx.dex.DexOptions; 21 import com.android.dx.rop.cst.Constant; 22 import com.android.dx.rop.cst.CstBaseMethodRef; 23 import com.android.dx.rop.cst.CstEnumRef; 24 import com.android.dx.rop.cst.CstFieldRef; 25 import com.android.dx.rop.cst.CstString; 26 import com.android.dx.rop.cst.CstType; 27 import com.android.dx.rop.type.Type; 28 import com.android.dx.util.ByteArrayAnnotatedOutput; 29 import com.android.dx.util.ExceptionWithContext; 30 31 import java.io.IOException; 32 import java.io.OutputStream; 33 import java.io.Writer; 34 import java.security.DigestException; 35 import java.security.MessageDigest; 36 import java.security.NoSuchAlgorithmException; 37 import java.util.zip.Adler32; 38 39 import static com.android.dx.dex.file.MixedItemSection.SortType; 40 41 /** 42 * Representation of an entire {@code .dex} (Dalvik EXecutable) 43 * file, which itself consists of a set of Dalvik classes. 44 */ 45 public final class DexFile { 46 /** options controlling the creation of the file */ 47 private DexOptions dexOptions; 48 49 /** {@code non-null;} word data section */ 50 private final MixedItemSection wordData; 51 52 /** 53 * {@code non-null;} type lists section. This is word data, but separating 54 * it from {@link #wordData} helps break what would otherwise be a 55 * circular dependency between the that and {@link #protoIds}. 56 */ 57 private final MixedItemSection typeLists; 58 59 /** 60 * {@code non-null;} map section. The map needs to be in a section by itself 61 * for the self-reference mechanics to work in a reasonably 62 * straightforward way. See {@link MapItem#addMap} for more detail. 63 */ 64 private final MixedItemSection map; 65 66 /** {@code non-null;} string data section */ 67 private final MixedItemSection stringData; 68 69 /** {@code non-null;} string identifiers section */ 70 private final StringIdsSection stringIds; 71 72 /** {@code non-null;} type identifiers section */ 73 private final TypeIdsSection typeIds; 74 75 /** {@code non-null;} prototype identifiers section */ 76 private final ProtoIdsSection protoIds; 77 78 /** {@code non-null;} field identifiers section */ 79 private final FieldIdsSection fieldIds; 80 81 /** {@code non-null;} method identifiers section */ 82 private final MethodIdsSection methodIds; 83 84 /** {@code non-null;} class definitions section */ 85 private final ClassDefsSection classDefs; 86 87 /** {@code non-null;} class data section */ 88 private final MixedItemSection classData; 89 90 /** {@code non-null;} byte data section */ 91 private final MixedItemSection byteData; 92 93 /** {@code non-null;} file header */ 94 private final HeaderSection header; 95 96 /** 97 * {@code non-null;} array of sections in the order they will appear in the 98 * final output file 99 */ 100 private final Section[] sections; 101 102 /** {@code >= -1;} total file size or {@code -1} if unknown */ 103 private int fileSize; 104 105 /** {@code >= 40;} maximum width of the file dump */ 106 private int dumpWidth; 107 108 /** 109 * Constructs an instance. It is initially empty. 110 */ DexFile(DexOptions dexOptions)111 public DexFile(DexOptions dexOptions) { 112 this.dexOptions = dexOptions; 113 114 header = new HeaderSection(this); 115 typeLists = new MixedItemSection(null, this, 4, SortType.NONE); 116 wordData = new MixedItemSection("word_data", this, 4, SortType.TYPE); 117 stringData = 118 new MixedItemSection("string_data", this, 1, SortType.INSTANCE); 119 classData = new MixedItemSection(null, this, 1, SortType.NONE); 120 byteData = new MixedItemSection("byte_data", this, 1, SortType.TYPE); 121 stringIds = new StringIdsSection(this); 122 typeIds = new TypeIdsSection(this); 123 protoIds = new ProtoIdsSection(this); 124 fieldIds = new FieldIdsSection(this); 125 methodIds = new MethodIdsSection(this); 126 classDefs = new ClassDefsSection(this); 127 map = new MixedItemSection("map", this, 4, SortType.NONE); 128 129 /* 130 * This is the list of sections in the order they appear in 131 * the final output. 132 */ 133 sections = new Section[] { 134 header, stringIds, typeIds, protoIds, fieldIds, methodIds, 135 classDefs, wordData, typeLists, stringData, byteData, 136 classData, map }; 137 138 fileSize = -1; 139 dumpWidth = 79; 140 } 141 142 /** 143 * Returns true if this dex doesn't contain any class defs. 144 */ isEmpty()145 public boolean isEmpty() { 146 return classDefs.items().isEmpty(); 147 } 148 149 /** 150 * Gets the dex-creation options object. 151 */ getDexOptions()152 public DexOptions getDexOptions() { 153 return dexOptions; 154 } 155 156 /** 157 * Adds a class to this instance. It is illegal to attempt to add more 158 * than one class with the same name. 159 * 160 * @param clazz {@code non-null;} the class to add 161 */ add(ClassDefItem clazz)162 public void add(ClassDefItem clazz) { 163 classDefs.add(clazz); 164 } 165 166 /** 167 * Gets the class definition with the given name, if any. 168 * 169 * @param name {@code non-null;} the class name to look for 170 * @return {@code null-ok;} the class with the given name, or {@code null} 171 * if there is no such class 172 */ getClassOrNull(String name)173 public ClassDefItem getClassOrNull(String name) { 174 try { 175 Type type = Type.internClassName(name); 176 return (ClassDefItem) classDefs.get(new CstType(type)); 177 } catch (IllegalArgumentException ex) { 178 // Translate exception, per contract. 179 return null; 180 } 181 } 182 183 /** 184 * Writes the contents of this instance as either a binary or a 185 * human-readable form, or both. 186 * 187 * @param out {@code null-ok;} where to write to 188 * @param humanOut {@code null-ok;} where to write human-oriented output to 189 * @param verbose whether to be verbose when writing human-oriented output 190 */ writeTo(OutputStream out, Writer humanOut, boolean verbose)191 public void writeTo(OutputStream out, Writer humanOut, boolean verbose) 192 throws IOException { 193 boolean annotate = (humanOut != null); 194 ByteArrayAnnotatedOutput result = toDex0(annotate, verbose); 195 196 if (out != null) { 197 out.write(result.getArray()); 198 } 199 200 if (annotate) { 201 result.writeAnnotationsTo(humanOut); 202 } 203 } 204 205 /** 206 * Returns the contents of this instance as a {@code .dex} file, 207 * in {@code byte[]} form. 208 * 209 * @param humanOut {@code null-ok;} where to write human-oriented output to 210 * @param verbose whether to be verbose when writing human-oriented output 211 * @return {@code non-null;} a {@code .dex} file for this instance 212 */ toDex(Writer humanOut, boolean verbose)213 public byte[] toDex(Writer humanOut, boolean verbose) 214 throws IOException { 215 boolean annotate = (humanOut != null); 216 ByteArrayAnnotatedOutput result = toDex0(annotate, verbose); 217 218 if (annotate) { 219 result.writeAnnotationsTo(humanOut); 220 } 221 222 return result.getArray(); 223 } 224 225 /** 226 * Sets the maximum width of the human-oriented dump of the instance. 227 * 228 * @param dumpWidth {@code >= 40;} the width 229 */ setDumpWidth(int dumpWidth)230 public void setDumpWidth(int dumpWidth) { 231 if (dumpWidth < 40) { 232 throw new IllegalArgumentException("dumpWidth < 40"); 233 } 234 235 this.dumpWidth = dumpWidth; 236 } 237 238 /** 239 * Gets the total file size, if known. 240 * 241 * <p>This is package-scope in order to allow 242 * the {@link HeaderSection} to set itself up properly.</p> 243 * 244 * @return {@code >= 0;} the total file size 245 * @throws RuntimeException thrown if the file size is not yet known 246 */ getFileSize()247 /*package*/ int getFileSize() { 248 if (fileSize < 0) { 249 throw new RuntimeException("file size not yet known"); 250 } 251 252 return fileSize; 253 } 254 255 /** 256 * Gets the string data section. 257 * 258 * <p>This is package-scope in order to allow 259 * the various {@link Item} instances to add items to the 260 * instance.</p> 261 * 262 * @return {@code non-null;} the string data section 263 */ getStringData()264 /*package*/ MixedItemSection getStringData() { 265 return stringData; 266 } 267 268 /** 269 * Gets the word data section. 270 * 271 * <p>This is package-scope in order to allow 272 * the various {@link Item} instances to add items to the 273 * instance.</p> 274 * 275 * @return {@code non-null;} the word data section 276 */ getWordData()277 /*package*/ MixedItemSection getWordData() { 278 return wordData; 279 } 280 281 /** 282 * Gets the type lists section. 283 * 284 * <p>This is package-scope in order to allow 285 * the various {@link Item} instances to add items to the 286 * instance.</p> 287 * 288 * @return {@code non-null;} the word data section 289 */ getTypeLists()290 /*package*/ MixedItemSection getTypeLists() { 291 return typeLists; 292 } 293 294 /** 295 * Gets the map section. 296 * 297 * <p>This is package-scope in order to allow the header section 298 * to query it.</p> 299 * 300 * @return {@code non-null;} the map section 301 */ getMap()302 /*package*/ MixedItemSection getMap() { 303 return map; 304 } 305 306 /** 307 * Gets the string identifiers section. 308 * 309 * <p>This is package-scope in order to allow 310 * the various {@link Item} instances to add items to the 311 * instance.</p> 312 * 313 * @return {@code non-null;} the string identifiers section 314 */ getStringIds()315 /*package*/ StringIdsSection getStringIds() { 316 return stringIds; 317 } 318 319 /** 320 * Gets the class definitions section. 321 * 322 * <p>This is package-scope in order to allow 323 * the various {@link Item} instances to add items to the 324 * instance.</p> 325 * 326 * @return {@code non-null;} the class definitions section 327 */ getClassDefs()328 /*package*/ ClassDefsSection getClassDefs() { 329 return classDefs; 330 } 331 332 /** 333 * Gets the class data section. 334 * 335 * <p>This is package-scope in order to allow 336 * the various {@link Item} instances to add items to the 337 * instance.</p> 338 * 339 * @return {@code non-null;} the class data section 340 */ getClassData()341 /*package*/ MixedItemSection getClassData() { 342 return classData; 343 } 344 345 /** 346 * Gets the type identifiers section. 347 * 348 * <p>This is package-scope in order to allow 349 * the various {@link Item} instances to add items to the 350 * instance.</p> 351 * 352 * @return {@code non-null;} the class identifiers section 353 */ getTypeIds()354 /*package*/ TypeIdsSection getTypeIds() { 355 return typeIds; 356 } 357 358 /** 359 * Gets the prototype identifiers section. 360 * 361 * <p>This is package-scope in order to allow 362 * the various {@link Item} instances to add items to the 363 * instance.</p> 364 * 365 * @return {@code non-null;} the prototype identifiers section 366 */ getProtoIds()367 /*package*/ ProtoIdsSection getProtoIds() { 368 return protoIds; 369 } 370 371 /** 372 * Gets the field identifiers section. 373 * 374 * <p>This is package-scope in order to allow 375 * the various {@link Item} instances to add items to the 376 * instance.</p> 377 * 378 * @return {@code non-null;} the field identifiers section 379 */ getFieldIds()380 /*package*/ FieldIdsSection getFieldIds() { 381 return fieldIds; 382 } 383 384 /** 385 * Gets the method identifiers section. 386 * 387 * <p>This is package-scope in order to allow 388 * the various {@link Item} instances to add items to the 389 * instance.</p> 390 * 391 * @return {@code non-null;} the method identifiers section 392 */ getMethodIds()393 /*package*/ MethodIdsSection getMethodIds() { 394 return methodIds; 395 } 396 397 /** 398 * Gets the byte data section. 399 * 400 * <p>This is package-scope in order to allow 401 * the various {@link Item} instances to add items to the 402 * instance.</p> 403 * 404 * @return {@code non-null;} the byte data section 405 */ getByteData()406 /*package*/ MixedItemSection getByteData() { 407 return byteData; 408 } 409 410 /** 411 * Gets the first section of the file that is to be considered 412 * part of the data section. 413 * 414 * <p>This is package-scope in order to allow the header section 415 * to query it.</p> 416 * 417 * @return {@code non-null;} the section 418 */ getFirstDataSection()419 /*package*/ Section getFirstDataSection() { 420 return wordData; 421 } 422 423 /** 424 * Gets the last section of the file that is to be considered 425 * part of the data section. 426 * 427 * <p>This is package-scope in order to allow the header section 428 * to query it.</p> 429 * 430 * @return {@code non-null;} the section 431 */ getLastDataSection()432 /*package*/ Section getLastDataSection() { 433 return map; 434 } 435 436 /** 437 * Interns the given constant in the appropriate section of this 438 * instance, or do nothing if the given constant isn't the sort 439 * that should be interned. 440 * 441 * @param cst {@code non-null;} constant to possibly intern 442 */ internIfAppropriate(Constant cst)443 /*package*/ void internIfAppropriate(Constant cst) { 444 if (cst instanceof CstString) { 445 stringIds.intern((CstString) cst); 446 } else if (cst instanceof CstType) { 447 typeIds.intern((CstType) cst); 448 } else if (cst instanceof CstBaseMethodRef) { 449 methodIds.intern((CstBaseMethodRef) cst); 450 } else if (cst instanceof CstFieldRef) { 451 fieldIds.intern((CstFieldRef) cst); 452 } else if (cst instanceof CstEnumRef) { 453 fieldIds.intern(((CstEnumRef) cst).getFieldRef()); 454 } else if (cst == null) { 455 throw new NullPointerException("cst == null"); 456 } 457 } 458 459 /** 460 * Gets the {@link IndexedItem} corresponding to the given constant, 461 * if it is a constant that has such a correspondence, or return 462 * {@code null} if it isn't such a constant. This will throw 463 * an exception if the given constant <i>should</i> have been found 464 * but wasn't. 465 * 466 * @param cst {@code non-null;} the constant to look up 467 * @return {@code null-ok;} its corresponding item, if it has a corresponding 468 * item, or {@code null} if it's not that sort of constant 469 */ findItemOrNull(Constant cst)470 /*package*/ IndexedItem findItemOrNull(Constant cst) { 471 IndexedItem item; 472 473 if (cst instanceof CstString) { 474 return stringIds.get(cst); 475 } else if (cst instanceof CstType) { 476 return typeIds.get(cst); 477 } else if (cst instanceof CstBaseMethodRef) { 478 return methodIds.get(cst); 479 } else if (cst instanceof CstFieldRef) { 480 return fieldIds.get(cst); 481 } else { 482 return null; 483 } 484 } 485 486 /** 487 * Returns the contents of this instance as a {@code .dex} file, 488 * in a {@link ByteArrayAnnotatedOutput} instance. 489 * 490 * @param annotate whether or not to keep annotations 491 * @param verbose if annotating, whether to be verbose 492 * @return {@code non-null;} a {@code .dex} file for this instance 493 */ toDex0(boolean annotate, boolean verbose)494 private ByteArrayAnnotatedOutput toDex0(boolean annotate, 495 boolean verbose) { 496 /* 497 * The following is ordered so that the prepare() calls which 498 * add items happen before the calls to the sections that get 499 * added to. 500 */ 501 502 classDefs.prepare(); 503 classData.prepare(); 504 wordData.prepare(); 505 byteData.prepare(); 506 methodIds.prepare(); 507 fieldIds.prepare(); 508 protoIds.prepare(); 509 typeLists.prepare(); 510 typeIds.prepare(); 511 stringIds.prepare(); 512 stringData.prepare(); 513 header.prepare(); 514 515 // Place the sections within the file. 516 517 int count = sections.length; 518 int offset = 0; 519 520 for (int i = 0; i < count; i++) { 521 Section one = sections[i]; 522 int placedAt = one.setFileOffset(offset); 523 if (placedAt < offset) { 524 throw new RuntimeException("bogus placement for section " + i); 525 } 526 527 try { 528 if (one == map) { 529 /* 530 * Inform the map of all the sections, and add it 531 * to the file. This can only be done after all 532 * the other items have been sorted and placed. 533 */ 534 MapItem.addMap(sections, map); 535 map.prepare(); 536 } 537 538 if (one instanceof MixedItemSection) { 539 /* 540 * Place the items of a MixedItemSection that just 541 * got placed. 542 */ 543 ((MixedItemSection) one).placeItems(); 544 } 545 546 offset = placedAt + one.writeSize(); 547 } catch (RuntimeException ex) { 548 throw ExceptionWithContext.withContext(ex, 549 "...while writing section " + i); 550 } 551 } 552 553 // Write out all the sections. 554 555 fileSize = offset; 556 byte[] barr = new byte[fileSize]; 557 ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(barr); 558 559 if (annotate) { 560 out.enableAnnotations(dumpWidth, verbose); 561 } 562 563 for (int i = 0; i < count; i++) { 564 try { 565 Section one = sections[i]; 566 int zeroCount = one.getFileOffset() - out.getCursor(); 567 if (zeroCount < 0) { 568 throw new ExceptionWithContext("excess write of " + 569 (-zeroCount)); 570 } 571 out.writeZeroes(one.getFileOffset() - out.getCursor()); 572 one.writeTo(out); 573 } catch (RuntimeException ex) { 574 ExceptionWithContext ec; 575 if (ex instanceof ExceptionWithContext) { 576 ec = (ExceptionWithContext) ex; 577 } else { 578 ec = new ExceptionWithContext(ex); 579 } 580 ec.addContext("...while writing section " + i); 581 throw ec; 582 } 583 } 584 585 if (out.getCursor() != fileSize) { 586 throw new RuntimeException("foreshortened write"); 587 } 588 589 // Perform final bookkeeping. 590 591 calcSignature(barr); 592 calcChecksum(barr); 593 594 if (annotate) { 595 wordData.writeIndexAnnotation(out, ItemType.TYPE_CODE_ITEM, 596 "\nmethod code index:\n\n"); 597 getStatistics().writeAnnotation(out); 598 out.finishAnnotating(); 599 } 600 601 return out; 602 } 603 604 /** 605 * Generates and returns statistics for all the items in the file. 606 * 607 * @return {@code non-null;} the statistics 608 */ getStatistics()609 public Statistics getStatistics() { 610 Statistics stats = new Statistics(); 611 612 for (Section s : sections) { 613 stats.addAll(s); 614 } 615 616 return stats; 617 } 618 619 /** 620 * Calculates the signature for the {@code .dex} file in the 621 * given array, and modify the array to contain it. 622 * 623 * @param bytes {@code non-null;} the bytes of the file 624 */ calcSignature(byte[] bytes)625 private static void calcSignature(byte[] bytes) { 626 MessageDigest md; 627 628 try { 629 md = MessageDigest.getInstance("SHA-1"); 630 } catch (NoSuchAlgorithmException ex) { 631 throw new RuntimeException(ex); 632 } 633 634 md.update(bytes, 32, bytes.length - 32); 635 636 try { 637 int amt = md.digest(bytes, 12, 20); 638 if (amt != 20) { 639 throw new RuntimeException("unexpected digest write: " + amt + 640 " bytes"); 641 } 642 } catch (DigestException ex) { 643 throw new RuntimeException(ex); 644 } 645 } 646 647 /** 648 * Calculates the checksum for the {@code .dex} file in the 649 * given array, and modify the array to contain it. 650 * 651 * @param bytes {@code non-null;} the bytes of the file 652 */ calcChecksum(byte[] bytes)653 private static void calcChecksum(byte[] bytes) { 654 Adler32 a32 = new Adler32(); 655 656 a32.update(bytes, 12, bytes.length - 12); 657 658 int sum = (int) a32.getValue(); 659 660 bytes[8] = (byte) sum; 661 bytes[9] = (byte) (sum >> 8); 662 bytes[10] = (byte) (sum >> 16); 663 bytes[11] = (byte) (sum >> 24); 664 } 665 } 666