DexFile.java revision db385ec3fd0c4f0de00ec3a17b6565d2a6c60e61
1/* 2 * [The "BSD licence"] 3 * Copyright (c) 2009 Ben Gruver 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29package org.jf.dexlib; 30 31import org.jf.dexlib.Util.*; 32import org.jf.dexlib.*; 33import org.jf.dexlib.Item; 34import org.jf.dexlib.StringDataItem; 35 36import java.io.*; 37import java.security.DigestException; 38import java.security.MessageDigest; 39import java.security.NoSuchAlgorithmException; 40import java.util.HashMap; 41import java.util.Arrays; 42import java.util.Comparator; 43import java.util.Collections; 44import java.util.zip.Adler32; 45import java.util.zip.ZipFile; 46import java.util.zip.ZipException; 47import java.util.zip.ZipEntry; 48 49/** 50 * <h3>Main use cases</h3> 51 * 52 * <p>These are the main use cases that drove the design of this library</p> 53 * 54 * <ol> 55 * <li><p><b>Annotate an existing dex file</b> - In this case, the intent is to document the structure of 56 * an existing dex file. We want to be able to read in the dex file, and then write out a dex file 57 * that is exactly the same (while adding annotation information to an AnnotatedOutput object)</p></li> 58 * 59 * <li><p><b>Canonicalize an existing dex file</b> - In this case, the intent is to rewrite an existing dex file 60 * so that it is in a canonical form. There is a certain amount of leeway in how various types of 61 * tems in a dex file are ordered or represented. It is sometimes useful to be able to easily 62 * compare a disassebled and reassembled dex file with the original dex file. If both dex-files are 63 * written canonically, they "should" match exactly, barring any explicit changes to the reassembled 64 * file.</p> 65 * 66 * <p>Currently, there are a couple of pieces of information that probably won't match exactly 67 * <ul> 68 * <li>the order of exception handlers in the <code>EncodedCatchHandlerList</code> for a method</li> 69 * <li>the ordering of some of the debug info in the <code>{@link org.jf.dexlib.DebugInfoItem}</code> for a method</li> 70 * </ul></p> 71 * 72 * 73 * <p>Note that the above discrepancies should typically only be "intra-item" differences. They 74 * shouldn't change the size of the item, or affect how anything else is placed or laid out</p></li> 75 * 76 * <li><p><b>Creating a dex file from scratch</b> - In this case, a blank dex file is created and then classes 77 * are added to it incrementally by calling the {@link org.jf.dexlib.Section#intern intern} method of 78 * {@link DexFile#ClassDefsSection}, which will add all the information necessary to represent the given 79 * class. For example, when assembling a dex file from a set of assembly text files.</p> 80 * 81 * <p>In this case, we can choose to write the dex file in a canonical form or not. It is somewhat 82 * slower to write it in a canonical format, due to the extra sorting and calculations that are 83 * required.</p></li> 84 * 85 * 86 * <li><p><b>Reading in the dex file</b> - In this case, the intent is to read in a dex file and expose all the 87 * data to the calling application. For example, when disassembling a dex file into a text based 88 * assembly format, or doing other misc processing of the dex file.</p></li> 89 * 90 * 91 * <h3>Other use cases</h3> 92 * 93 * <p>These are other use cases that are possible, but did not drive the design of the library. 94 * No effort was made to test these use cases or ensure that they work. Some of these could 95 * probably be better achieved with a disassemble - modify - reassemble type process, using 96 * smali/baksmali or another assembler/disassembler pair that are compatible with each other</p> 97 * 98 * <ul> 99 * <li>deleting classes/methods/etc. from a dex file</li> 100 * <li>merging 2 dex files</li> 101 * <li>splitting a dex file</li> 102 * <li>moving classes from 1 dex file to another</li> 103 * <li>removing the debug information from a dex file</li> 104 * <li>obfustication of a dex file</li> 105 * </ul> 106 */ 107public class DexFile 108{ 109 /** 110 * A mapping from ItemType to the section that contains items of the given type 111 */ 112 private final Section[] sectionsByType; 113 114 /** 115 * Ordered lists of the indexed and offsetted sections. The order of these lists specifies the order 116 * that the sections will be written in 117 */ 118 private final IndexedSection[] indexedSections; 119 private final OffsettedSection[] offsettedSections; 120 121 /** 122 * dalvik had a bug where it wrote the registers for certain types of debug info in a signed leb 123 * format, instead of an unsigned leb format. There are no negative registers of course, but 124 * certain positive values have a different encoding depending on whether they are encoded as 125 * an unsigned leb128 or a signed leb128. Specifically, the signed leb128 is 1 byte longer in some cases. 126 * 127 * This determine whether we should keep any signed registers as signed, or force all register to 128 * unsigned. By default we don't keep track of whether they were signed or not, and write them back 129 * out as unsigned. This option only has an effect when reading an existing dex file. It has no 130 * effect when a dex file is created from scratch 131 * 132 * The 2 main use-cases in play are 133 * 1. Annotate an existing dex file - In this case, preserveSignedRegisters should be false, so that we keep 134 * track of any signed registers and write them back out as signed Leb128 values. 135 * 136 * 2. Canonicalize an existing dex file - In this case, fixRegisters should be true, so that all 137 * registers in the debug info are written as unsigned Leb128 values regardless of how they were 138 * originally encoded 139 */ 140 private final boolean preserveSignedRegisters; 141 142 /** 143 * When true, any instructions in a code item are skipped over instead of being read in. This is useful when 144 * you only need the information about the classes and their methods, for example, when loading the BOOTCLASSPATH 145 * jars in order to analyze a dex file 146 */ 147 private final boolean skipInstructions; 148 149 /** 150 * When true, this prevents any sorting of the items during placement of the dex file. This 151 * should *only* be set to true when this dex file was read in from an existing (valid) dex file, 152 * and no modifications were made (i.e. no items added or deleted). Otherwise it is likely that 153 * an invalid dex file will be generated. 154 * 155 * This is useful for the first use case (annotating an existing dex file). This ensures the items 156 * retain the same order as in the original dex file. 157 */ 158 private boolean inplace = false; 159 160 /** 161 * When true, this imposes an full ordering on all the items, to force them into a (possibly 162 * arbitrary) canonical order. When false, only the items that the dex format specifies 163 * an order for are sorted. The rest of the items are not ordered. 164 * 165 * This is useful for the second use case (canonicalizing an existing dex file) or possibly for 166 * the third use case (creating a dex file from scratch), if there is a need to write the new 167 * dex file in a canonical form. 168 */ 169 private boolean sortAllItems = false; 170 171 172 /** 173 * this is used to access the dex file from within inner classes, when they declare fields or 174 * variable that hide fields on this object 175 */ 176 private final DexFile dexFile = this; 177 178 /** 179 * Is this file an odex file? This is only set when reading in an odex file 180 */ 181 private boolean isOdex = false; 182 183 184 private int dataOffset; 185 private int dataSize; 186 private int fileSize; 187 188 private boolean disableInterning = false; 189 190 191 /** 192 * A private constructor containing common code to initialize the section maps and lists 193 * @param preserveSignedRegisters If true, keep track of any registers in the debug information 194 * @param skipInstructions If true, skip the instructions in any code item. 195 * that are signed, so they will be written in the same format. See 196 * <code>getPreserveSignedRegisters()</code> 197 */ 198 private DexFile(boolean preserveSignedRegisters, boolean skipInstructions) { 199 this.preserveSignedRegisters = preserveSignedRegisters; 200 this.skipInstructions = skipInstructions; 201 202 sectionsByType = new Section[] { 203 StringIdsSection, 204 TypeIdsSection, 205 ProtoIdsSection, 206 FieldIdsSection, 207 MethodIdsSection, 208 ClassDefsSection, 209 TypeListsSection, 210 AnnotationSetRefListsSection, 211 AnnotationSetsSection, 212 ClassDataSection, 213 CodeItemsSection, 214 AnnotationDirectoriesSection, 215 StringDataSection, 216 DebugInfoItemsSection, 217 AnnotationsSection, 218 EncodedArraysSection, 219 null, 220 null 221 }; 222 223 indexedSections = new IndexedSection[] { 224 StringIdsSection, 225 TypeIdsSection, 226 ProtoIdsSection, 227 FieldIdsSection, 228 MethodIdsSection, 229 ClassDefsSection 230 }; 231 232 offsettedSections = new OffsettedSection[] { 233 AnnotationSetRefListsSection, 234 AnnotationSetsSection, 235 CodeItemsSection, 236 AnnotationDirectoriesSection, 237 TypeListsSection, 238 StringDataSection, 239 AnnotationsSection, 240 EncodedArraysSection, 241 ClassDataSection, 242 DebugInfoItemsSection 243 }; 244 } 245 246 247 /** 248 * Construct a new DexFile instance by reading in the given dex file. 249 * @param file The dex file to read in 250 * @throws IOException if an IOException occurs 251 */ 252 public DexFile(String file) 253 throws IOException { 254 this(new File(file), true, false); 255 } 256 257 /** 258 * Construct a new DexFile instance by reading in the given dex file, 259 * and optionally keep track of any registers in the debug information that are signed, 260 * so they will be written in the same format. 261 * @param file The dex file to read in 262 * @param preserveSignedRegisters If true, keep track of any registers in the debug information 263 * that are signed, so they will be written in the same format. See 264 * @param skipInstructions If true, skip the instructions in any code item. 265 * <code>getPreserveSignedRegisters()</code> 266 * @throws IOException if an IOException occurs 267 */ 268 public DexFile(String file, boolean preserveSignedRegisters, boolean skipInstructions) 269 throws IOException { 270 this(new File(file), preserveSignedRegisters, skipInstructions); 271 } 272 273 /** 274 * Construct a new DexFile instance by reading in the given dex file. 275 * @param file The dex file to read in 276 * @throws IOException if an IOException occurs 277 */ 278 public DexFile(File file) 279 throws IOException { 280 this(file, true, false); 281 } 282 283 /** 284 * Construct a new DexFile instance by reading in the given dex file, 285 * and optionally keep track of any registers in the debug information that are signed, 286 * so they will be written in the same format. 287 * @param file The dex file to read in 288 * @param preserveSignedRegisters If true, keep track of any registers in the debug information 289 * that are signed, so they will be written in the same format. 290 * @param skipInstructions If true, skip the instructions in any code item. 291 * @see #getPreserveSignedRegisters 292 * @throws IOException if an IOException occurs 293 */ 294 public DexFile(File file, boolean preserveSignedRegisters, boolean skipInstructions) 295 throws IOException { 296 this(preserveSignedRegisters, skipInstructions); 297 298 long fileLength; 299 byte[] magic = FileUtils.readFile(file, 0, 8); 300 301 InputStream inputStream = null; 302 Input in = null; 303 ZipFile zipFile = null; 304 305 try { 306 //do we have a zip file? 307 if (magic[0] == 0x50 && magic[1] == 0x4B) { 308 zipFile = new ZipFile(file); 309 ZipEntry zipEntry = zipFile.getEntry("classes.dex"); 310 if (zipEntry == null) { 311 throw new RuntimeException("zip file " + file.getName() + " does not contain a classes.dex file"); 312 } 313 fileLength = zipEntry.getSize(); 314 if (fileLength < 40) { 315 throw new RuntimeException("The classes.dex file in " + file.getName() + " is too small to be a" + 316 " valid dex file"); 317 } else if (fileLength > Integer.MAX_VALUE) { 318 throw new RuntimeException("The classes.dex file in " + file.getName() + " is too large to read in"); 319 } 320 inputStream = new BufferedInputStream(zipFile.getInputStream(zipEntry)); 321 322 inputStream.mark(8); 323 for (int i=0; i<8; i++) { 324 magic[i] = (byte)inputStream.read(); 325 } 326 inputStream.reset(); 327 } else { 328 fileLength = file.length(); 329 if (fileLength < 40) { 330 throw new RuntimeException(file.getName() + " is too small to be a valid dex file"); 331 } 332 if (fileLength < 40) { 333 throw new RuntimeException(file.getName() + " is too small to be a valid dex file"); 334 } else if (fileLength > Integer.MAX_VALUE) { 335 throw new RuntimeException(file.getName() + " is too large to read in"); 336 } 337 inputStream = new FileInputStream(file); 338 } 339 340 byte[] dexMagic, odexMagic; 341 342 dexMagic = org.jf.dexlib.HeaderItem.MAGIC; 343 odexMagic = OdexHeaderItem.MAGIC; 344 345 boolean isDex = true; 346 this.isOdex = true; 347 for (int i=0; i<8; i++) { 348 if (magic[i] != dexMagic[i]) { 349 isDex = false; 350 } 351 if (magic[i] != odexMagic[i]) { 352 isOdex = false; 353 } 354 } 355 356 if (isOdex) { 357 byte[] odexHeaderBytes = FileUtils.readStream(inputStream, 40); 358 Input odexHeaderIn = new ByteArrayInput(odexHeaderBytes); 359 OdexHeaderItem odexHeader = new OdexHeaderItem(odexHeaderIn); 360 361 in = new ByteArrayInput(FileUtils.readStream(inputStream, odexHeader.dexLength)); 362 } else if (isDex) { 363 in = new ByteArrayInput(FileUtils.readStream(inputStream, (int)fileLength)); 364 } else { 365 StringBuffer sb = new StringBuffer("bad magic value:"); 366 for (int i=0; i<8; i++) { 367 sb.append(" "); 368 sb.append(Hex.u1(magic[i])); 369 } 370 throw new RuntimeException(sb.toString()); 371 } 372 } finally { 373 if (inputStream != null) { 374 inputStream.close(); 375 } 376 if (zipFile != null) { 377 zipFile.close(); 378 } 379 } 380 381 ReadContext readContext = new ReadContext(this); 382 383 HeaderItem.readFrom(in, 0, readContext); 384 385 //the map offset was set while reading in the header item 386 int mapOffset = readContext.getSectionOffset(ItemType.TYPE_MAP_LIST); 387 388 in.setCursor(mapOffset); 389 MapItem.readFrom(in, 0, readContext); 390 391 Section sections[] = new Section[] { 392 StringIdsSection, 393 TypeIdsSection, 394 ProtoIdsSection, 395 FieldIdsSection, 396 MethodIdsSection, 397 ClassDefsSection, 398 StringDataSection, 399 TypeListsSection, 400 AnnotationSetRefListsSection, 401 AnnotationSetsSection, 402 ClassDataSection, 403 CodeItemsSection, 404 AnnotationDirectoriesSection, 405 DebugInfoItemsSection, 406 AnnotationsSection, 407 EncodedArraysSection, 408 }; 409 410 for (Section section: sections) { 411 if (section == null) { 412 continue; 413 } 414 415 int sectionOffset = readContext.getSectionOffset(section.ItemType); 416 if (sectionOffset > 0) { 417 int sectionSize = readContext.getSectionSize(section.ItemType); 418 in.setCursor(sectionOffset); 419 section.readFrom(sectionSize, in, readContext); 420 } 421 } 422 } 423 424 /** 425 * Constructs a new, blank dex file. Classes can be added to this dex file by calling 426 * the <code>Section.intern()</code> method of <code>ClassDefsSection</code> 427 */ 428 public DexFile() { 429 this(true, false); 430 } 431 432 /** 433 * Get the <code>Section</code> containing items of the same type as the given item 434 * @param item Get the <code>Section</code> that contains items of this type 435 * @param <T> The specific item subclass - inferred from the passed item 436 * @return the <code>Section</code> containing items of the same type as the given item 437 */ 438 public <T extends Item> Section<T> getSectionForItem(T item) { 439 return (Section<T>)sectionsByType[item.getItemType().SectionIndex]; 440 } 441 442 /** 443 * Get the <code>Section</code> containing items of the given type 444 * @param itemType the type of item 445 * @return the <code>Section</code> containing items of the given type 446 */ 447 public Section getSectionForType(ItemType itemType) { 448 return sectionsByType[itemType.SectionIndex]; 449 } 450 451 /** 452 * Get a boolean value indicating whether this dex file preserved any signed 453 * registers in the debug info as it read the dex file in. By default, the dex file 454 * doesn't check whether the registers are encoded as unsigned or signed values. 455 * 456 * This does *not* affect the actual register value that is read in. The value is 457 * read correctly regardless 458 * 459 * This does affect whether any signed registers will retain the same encoding or be 460 * forced to the (correct) unsigned encoding when the dex file is written back out. 461 * 462 * See the discussion about signed register values in the documentation for 463 * <code>DexFile</code> 464 * @return a boolean indicating whether this dex file preserved any signed registers 465 * as it was read in 466 */ 467 public boolean getPreserveSignedRegisters() { 468 return preserveSignedRegisters; 469 } 470 471 /** 472 * Get a boolean value indicating whether to skip any instructions in a code item while reading in the dex file. 473 * This is useful when you only need the information about the classes and their methods, for example, when 474 * loading the BOOTCLASSPATH jars in order to analyze a dex file 475 * @return a boolean value indicating whether to skip any instructions in a code item 476 */ 477 public boolean skipInstructions() { 478 return skipInstructions; 479 } 480 481 /** 482 * Get a boolean value indicating whether all items should be placed into a 483 * (possibly arbitrary) "canonical" ordering. If false, then only the items 484 * that must be ordered per the dex specification are sorted. 485 * 486 * When true, writing the dex file involves somewhat more overhead 487 * 488 * If both SortAllItems and Inplace are true, Inplace takes precedence 489 * @return a boolean value indicating whether all items should be sorted 490 */ 491 public boolean getSortAllItems() { 492 return this.sortAllItems; 493 } 494 495 /** 496 * Set a boolean value indicating whether all items should be placed into a 497 * (possibly arbitrary) "canonical" ordering. If false, then only the items 498 * that must be ordered per the dex specification are sorted. 499 * 500 * When true, writing the dex file involves somewhat more overhead 501 * 502 * If both SortAllItems and Inplace are true, Inplace takes precedence 503 * @param value a boolean value indicating whether all items should be sorted 504 */ 505 public void setSortAllItems(boolean value) { 506 this.sortAllItems = value; 507 } 508 509 /** 510 * Disables adding new items to this dex file. The various getInterned*() type 511 * methods on individual items will return null if there isn't an existing item 512 * that matches 513 */ 514 public void disableInterning() { 515 this.disableInterning = true; 516 } 517 518 /** 519 * @return a boolean value indicating whether interning new items has been disabled 520 * for this dex file 521 */ 522 public boolean getInterningDisabled() { 523 return disableInterning; 524 } 525 526 /** 527 * @return a boolean value indicating whether this dex file was created by reading in an odex file 528 */ 529 public boolean isOdex() { 530 return this.isOdex; 531 } 532 533 /** 534 * Get a boolean value indicating whether items in this dex file should be 535 * written back out "in-place", or whether the normal layout logic should be 536 * applied. 537 * 538 * This should only be used for a dex file that has been read from an existing 539 * dex file, and no modifications have been made to the dex file. Otherwise, 540 * there is a good chance that the resulting dex file will be invalid due to 541 * items that aren't placed correctly 542 * 543 * If both SortAllItems and Inplace are true, Inplace takes precedence 544 * @return a boolean value indicating whether items in this dex file should be 545 * written back out in-place. 546 */ 547 public boolean getInplace() { 548 return this.inplace; 549 } 550 551 /** 552 * @return the size of the file, in bytes 553 */ 554 public int getFileSize() { 555 return fileSize; 556 } 557 558 /** 559 * @return the size of the data section, in bytes 560 */ 561 public int getDataSize() { 562 return dataSize; 563 } 564 565 /** 566 * @return the offset where the data section begins 567 */ 568 public int getDataOffset() { 569 return dataOffset; 570 } 571 572 /** 573 * Set a boolean value indicating whether items in this dex file should be 574 * written back out "in-place", or whether the normal layout logic should be 575 * applied. 576 * 577 * This should only be used for a dex file that has been read from an existing 578 * dex file, and no modifications have been made to the dex file. Otherwise, 579 * there is a good chance that the resulting dex file will be invalid due to 580 * items that aren't placed correctly 581 * 582 * If both SortAllItems and Inplace are true, Inplace takes precedence 583 * @param value a boolean value indicating whether items in this dex file should be 584 * written back out in-place. 585 */ 586 public void setInplace(boolean value) { 587 this.inplace = value; 588 } 589 590 /** 591 * Get an array of Section objects that are sorted by offset. 592 * @return an array of Section objects that are sorted by offset. 593 */ 594 protected Section[] getOrderedSections() { 595 int sectionCount = 0; 596 597 for (Section section: sectionsByType) { 598 if (section != null && section.getItems().size() > 0) { 599 sectionCount++; 600 } 601 } 602 603 Section[] sections = new Section[sectionCount]; 604 sectionCount = 0; 605 for (Section section: sectionsByType) { 606 if (section != null && section.getItems().size() > 0) { 607 sections[sectionCount++] = section; 608 } 609 } 610 611 Arrays.sort(sections, new Comparator<Section>() { 612 public int compare(Section a, Section b) { 613 return a.getOffset() - b.getOffset(); 614 } 615 }); 616 617 return sections; 618 } 619 620 /** 621 * This method should be called before writing a dex file. It sorts the sections 622 * as needed or as indicated by <code>getSortAllItems()</code> and <code>getInplace()</code>, 623 * and then performs a pass through all of the items, finalizing the position (i.e. 624 * index and/or offset) of each item in the dex file. 625 * 626 * This step is needed primarily so that the indexes and offsets of all indexed and 627 * offsetted items are available when writing references to those items elsewhere. 628 */ 629 public void place() { 630 int offset = HeaderItem.placeAt(0, 0); 631 632 int sectionsPosition = 0; 633 Section[] sections; 634 if (this.inplace) { 635 sections = this.getOrderedSections(); 636 } else { 637 sections = new Section[indexedSections.length + offsettedSections.length]; 638 System.arraycopy(indexedSections, 0, sections, 0, indexedSections.length); 639 System.arraycopy(offsettedSections, 0, sections, indexedSections.length, offsettedSections.length); 640 } 641 642 while (sectionsPosition < sections.length && sections[sectionsPosition].ItemType.isIndexedItem()) { 643 Section section = sections[sectionsPosition]; 644 if (!this.inplace) { 645 section.sortSection(); 646 } 647 648 offset = section.placeAt(offset); 649 650 sectionsPosition++; 651 } 652 653 dataOffset = offset; 654 655 while (sectionsPosition < sections.length) { 656 Section section = sections[sectionsPosition]; 657 if (this.sortAllItems && !this.inplace) { 658 section.sortSection(); 659 } 660 offset = section.placeAt(offset); 661 662 sectionsPosition++; 663 } 664 665 offset = AlignmentUtils.alignOffset(offset, ItemType.TYPE_MAP_LIST.ItemAlignment); 666 offset = MapItem.placeAt(offset, 0); 667 668 fileSize = offset; 669 dataSize = offset - dataOffset; 670 } 671 672 /** 673 * Writes the dex file to the give <code>AnnotatedOutput</code> object. If 674 * <code>out.Annotates()</code> is true, then annotations that document the format 675 * of the dex file are written. 676 * 677 * You must call <code>place()</code> on this dex file, before calling this method 678 * @param out the AnnotatedOutput object to write the dex file and annotations to 679 * 680 * After calling this method, you should call <code>calcSignature()</code> and 681 * then <code>calcChecksum()</code> on the resulting byte array, to calculate the 682 * signature and checksum in the header 683 */ 684 public void writeTo(AnnotatedOutput out) { 685 686 out.annotate(0, "-----------------------------"); 687 out.annotate(0, "header item"); 688 out.annotate(0, "-----------------------------"); 689 out.annotate(0, " "); 690 HeaderItem.writeTo(out); 691 692 out.annotate(0, " "); 693 694 int sectionsPosition = 0; 695 Section[] sections; 696 if (this.inplace) { 697 sections = this.getOrderedSections(); 698 } else { 699 sections = new Section[indexedSections.length + offsettedSections.length]; 700 System.arraycopy(indexedSections, 0, sections, 0, indexedSections.length); 701 System.arraycopy(offsettedSections, 0, sections, indexedSections.length, offsettedSections.length); 702 } 703 704 while (sectionsPosition < sections.length) { 705 sections[sectionsPosition].writeTo(out); 706 sectionsPosition++; 707 } 708 709 out.alignTo(MapItem.getItemType().ItemAlignment); 710 711 out.annotate(0, " "); 712 out.annotate(0, "-----------------------------"); 713 out.annotate(0, "map item"); 714 out.annotate(0, "-----------------------------"); 715 out.annotate(0, " "); 716 MapItem.writeTo(out); 717 } 718 719 public final HeaderItem HeaderItem = new HeaderItem(this); 720 public final MapItem MapItem = new MapItem(this); 721 722 /** 723 * The <code>IndexedSection</code> containing <code>StringIdItem</code> items 724 */ 725 public final IndexedSection<StringIdItem> StringIdsSection = 726 new IndexedSection<StringIdItem>(this, ItemType.TYPE_STRING_ID_ITEM); 727 728 /** 729 * The <code>IndexedSection</code> containing <code>TypeIdItem</code> items 730 */ 731 public final IndexedSection<TypeIdItem> TypeIdsSection = 732 new IndexedSection<TypeIdItem>(this, ItemType.TYPE_TYPE_ID_ITEM); 733 734 /** 735 * The <code>IndexedSection</code> containing <code>ProtoIdItem</code> items 736 */ 737 public final IndexedSection<ProtoIdItem> ProtoIdsSection = 738 new IndexedSection<ProtoIdItem>(this, ItemType.TYPE_PROTO_ID_ITEM); 739 740 /** 741 * The <code>IndexedSection</code> containing <code>FieldIdItem</code> items 742 */ 743 public final IndexedSection<FieldIdItem> FieldIdsSection = 744 new IndexedSection<FieldIdItem>(this, ItemType.TYPE_FIELD_ID_ITEM); 745 746 /** 747 * The <code>IndexedSection</code> containing <code>MethodIdItem</code> items 748 */ 749 public final IndexedSection<MethodIdItem> MethodIdsSection = 750 new IndexedSection<MethodIdItem>(this, ItemType.TYPE_METHOD_ID_ITEM); 751 752 /** 753 * The <code>IndexedSection</code> containing <code>ClassDefItem</code> items 754 */ 755 public final IndexedSection<ClassDefItem> ClassDefsSection = 756 new IndexedSection<ClassDefItem>(this, ItemType.TYPE_CLASS_DEF_ITEM) { 757 758 public int placeAt(int offset) { 759 if (dexFile.getInplace()) { 760 return super.placeAt(offset); 761 } 762 763 int ret = ClassDefItem.placeClassDefItems(this, offset); 764 765 Collections.sort(this.items, new Comparator<ClassDefItem>() { 766 767 public int compare(ClassDefItem a, ClassDefItem b) { 768 return a.getOffset() - b.getOffset(); 769 } 770 }); 771 772 this.offset = items.get(0).getOffset(); 773 return ret; 774 } 775 }; 776 777 /** 778 * The <code>OffsettedSection</code> containing <code>TypeListItem</code> items 779 */ 780 public final OffsettedSection<TypeListItem> TypeListsSection = 781 new OffsettedSection<TypeListItem>(this, ItemType.TYPE_TYPE_LIST); 782 783 /** 784 * The <code>OffsettedSection</code> containing <code>AnnotationSetRefList</code> items 785 */ 786 public final OffsettedSection<AnnotationSetRefList> AnnotationSetRefListsSection = 787 new OffsettedSection<AnnotationSetRefList>(this, ItemType.TYPE_ANNOTATION_SET_REF_LIST); 788 789 /** 790 * The <code>OffsettedSection</code> containing <code>AnnotationSetItem</code> items 791 */ 792 public final OffsettedSection<AnnotationSetItem> AnnotationSetsSection = 793 new OffsettedSection<AnnotationSetItem>(this, ItemType.TYPE_ANNOTATION_SET_ITEM); 794 795 /** 796 * The <code>OffsettedSection</code> containing <code>ClassDataItem</code> items 797 */ 798 public final OffsettedSection<ClassDataItem> ClassDataSection = 799 new OffsettedSection<ClassDataItem>(this, ItemType.TYPE_CLASS_DATA_ITEM); 800 801 /** 802 * The <code>OffsettedSection</code> containing <code>CodeItem</code> items 803 */ 804 public final OffsettedSection<CodeItem> CodeItemsSection = 805 new OffsettedSection<CodeItem>(this, ItemType.TYPE_CODE_ITEM); 806 807 /** 808 * The <code>OffsettedSection</code> containing <code>StringDataItem</code> items 809 */ 810 public final OffsettedSection<StringDataItem> StringDataSection = 811 new OffsettedSection<StringDataItem>(this, ItemType.TYPE_STRING_DATA_ITEM); 812 813 /** 814 * The <code>OffsettedSection</code> containing <code>DebugInfoItem</code> items 815 */ 816 public final OffsettedSection<DebugInfoItem> DebugInfoItemsSection = 817 new OffsettedSection<DebugInfoItem>(this, ItemType.TYPE_DEBUG_INFO_ITEM); 818 819 /** 820 * The <code>OffsettedSection</code> containing <code>AnnotationItem</code> items 821 */ 822 public final OffsettedSection<AnnotationItem> AnnotationsSection = 823 new OffsettedSection<AnnotationItem>(this, ItemType.TYPE_ANNOTATION_ITEM); 824 825 /** 826 * The <code>OffsettedSection</code> containing <code>EncodedArrayItem</code> items 827 */ 828 public final OffsettedSection<EncodedArrayItem> EncodedArraysSection = 829 new OffsettedSection<EncodedArrayItem>(this, ItemType.TYPE_ENCODED_ARRAY_ITEM); 830 831 /** 832 * The <code>OffsettedSection</code> containing <code>AnnotationDirectoryItem</code> items 833 */ 834 public final OffsettedSection<AnnotationDirectoryItem> AnnotationDirectoriesSection = 835 new OffsettedSection<AnnotationDirectoryItem>(this, ItemType.TYPE_ANNOTATIONS_DIRECTORY_ITEM); 836 837 838 /** 839 * Calculates the signature for the dex file in the given byte array, 840 * and then writes the signature to the appropriate location in the header 841 * containing in the array 842 * 843 * @param bytes non-null; the bytes of the file 844 */ 845 public static void calcSignature(byte[] bytes) { 846 MessageDigest md; 847 848 try { 849 md = MessageDigest.getInstance("SHA-1"); 850 } catch (NoSuchAlgorithmException ex) { 851 throw new RuntimeException(ex); 852 } 853 854 md.update(bytes, 32, bytes.length - 32); 855 856 try { 857 int amt = md.digest(bytes, 12, 20); 858 if (amt != 20) { 859 throw new RuntimeException("unexpected digest write: " + amt + 860 " bytes"); 861 } 862 } catch (DigestException ex) { 863 throw new RuntimeException(ex); 864 } 865 } 866 867 /** 868 * Calculates the checksum for the <code>.dex</code> file in the 869 * given array, and modify the array to contain it. 870 * 871 * @param bytes non-null; the bytes of the file 872 */ 873 public static void calcChecksum(byte[] bytes) { 874 Adler32 a32 = new Adler32(); 875 876 a32.update(bytes, 12, bytes.length - 12); 877 878 int sum = (int) a32.getValue(); 879 880 bytes[8] = (byte) sum; 881 bytes[9] = (byte) (sum >> 8); 882 bytes[10] = (byte) (sum >> 16); 883 bytes[11] = (byte) (sum >> 24); 884 } 885}