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