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