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