1 /* 2 * Copyright (C) 2011 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.merge; 18 19 import com.android.dex.Annotation; 20 import com.android.dex.ClassData; 21 import com.android.dex.ClassDef; 22 import com.android.dex.Code; 23 import com.android.dex.Dex; 24 import com.android.dex.DexException; 25 import com.android.dex.FieldId; 26 import com.android.dex.MethodId; 27 import com.android.dex.ProtoId; 28 import com.android.dex.SizeOf; 29 import com.android.dex.TableOfContents; 30 import com.android.dex.TypeList; 31 import java.io.File; 32 import java.io.IOException; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Collections; 36 import java.util.List; 37 38 /** 39 * Combine two dex files into one. 40 */ 41 public final class DexMerger { 42 private final Dex dexA; 43 private final Dex dexB; 44 private final CollisionPolicy collisionPolicy; 45 private final WriterSizes writerSizes; 46 47 private final Dex dexOut; 48 49 private final Dex.Section headerOut; 50 51 /** All IDs and definitions sections */ 52 private final Dex.Section idsDefsOut; 53 54 private final Dex.Section mapListOut; 55 56 private final Dex.Section typeListOut; 57 58 private final Dex.Section classDataOut; 59 60 private final Dex.Section codeOut; 61 62 private final Dex.Section stringDataOut; 63 64 private final Dex.Section debugInfoOut; 65 66 private final Dex.Section encodedArrayOut; 67 68 /** annotations directory on a type */ 69 private final Dex.Section annotationsDirectoryOut; 70 71 /** sets of annotations on a member, parameter or type */ 72 private final Dex.Section annotationSetOut; 73 74 /** parameter lists */ 75 private final Dex.Section annotationSetRefListOut; 76 77 /** individual annotations, each containing zero or more fields */ 78 private final Dex.Section annotationOut; 79 80 private final TableOfContents contentsOut; 81 82 private final IndexMap aIndexMap; 83 private final IndexMap bIndexMap; 84 private final InstructionTransformer aInstructionTransformer; 85 private final InstructionTransformer bInstructionTransformer; 86 87 /** minimum number of wasted bytes before it's worthwhile to compact the result */ 88 private int compactWasteThreshold = 1024 * 1024; // 1MiB 89 90 public DexMerger(Dex dexA, Dex dexB, CollisionPolicy collisionPolicy) 91 throws IOException { 92 this(dexA, dexB, collisionPolicy, new WriterSizes(dexA, dexB)); 93 } 94 95 private DexMerger(Dex dexA, Dex dexB, CollisionPolicy collisionPolicy, 96 WriterSizes writerSizes) throws IOException { 97 this.dexA = dexA; 98 this.dexB = dexB; 99 this.collisionPolicy = collisionPolicy; 100 this.writerSizes = writerSizes; 101 102 dexOut = new Dex(writerSizes.size()); 103 104 TableOfContents aContents = dexA.getTableOfContents(); 105 TableOfContents bContents = dexB.getTableOfContents(); 106 aIndexMap = new IndexMap(dexOut, aContents); 107 bIndexMap = new IndexMap(dexOut, bContents); 108 aInstructionTransformer = new InstructionTransformer(aIndexMap); 109 bInstructionTransformer = new InstructionTransformer(bIndexMap); 110 111 headerOut = dexOut.appendSection(writerSizes.header, "header"); 112 idsDefsOut = dexOut.appendSection(writerSizes.idsDefs, "ids defs"); 113 114 contentsOut = dexOut.getTableOfContents(); 115 contentsOut.dataOff = dexOut.getNextSectionStart(); 116 117 contentsOut.mapList.off = dexOut.getNextSectionStart(); 118 contentsOut.mapList.size = 1; 119 mapListOut = dexOut.appendSection(writerSizes.mapList, "map list"); 120 121 contentsOut.typeLists.off = dexOut.getNextSectionStart(); 122 typeListOut = dexOut.appendSection(writerSizes.typeList, "type list"); 123 124 contentsOut.annotationSetRefLists.off = dexOut.getNextSectionStart(); 125 annotationSetRefListOut = dexOut.appendSection( 126 writerSizes.annotationsSetRefList, "annotation set ref list"); 127 128 contentsOut.annotationSets.off = dexOut.getNextSectionStart(); 129 annotationSetOut = dexOut.appendSection(writerSizes.annotationsSet, "annotation sets"); 130 131 contentsOut.classDatas.off = dexOut.getNextSectionStart(); 132 classDataOut = dexOut.appendSection(writerSizes.classData, "class data"); 133 134 contentsOut.codes.off = dexOut.getNextSectionStart(); 135 codeOut = dexOut.appendSection(writerSizes.code, "code"); 136 137 contentsOut.stringDatas.off = dexOut.getNextSectionStart(); 138 stringDataOut = dexOut.appendSection(writerSizes.stringData, "string data"); 139 140 contentsOut.debugInfos.off = dexOut.getNextSectionStart(); 141 debugInfoOut = dexOut.appendSection(writerSizes.debugInfo, "debug info"); 142 143 contentsOut.annotations.off = dexOut.getNextSectionStart(); 144 annotationOut = dexOut.appendSection(writerSizes.annotation, "annotation"); 145 146 contentsOut.encodedArrays.off = dexOut.getNextSectionStart(); 147 encodedArrayOut = dexOut.appendSection(writerSizes.encodedArray, "encoded array"); 148 149 contentsOut.annotationsDirectories.off = dexOut.getNextSectionStart(); 150 annotationsDirectoryOut = dexOut.appendSection( 151 writerSizes.annotationsDirectory, "annotations directory"); 152 153 contentsOut.dataSize = dexOut.getNextSectionStart() - contentsOut.dataOff; 154 } 155 156 public void setCompactWasteThreshold(int compactWasteThreshold) { 157 this.compactWasteThreshold = compactWasteThreshold; 158 } 159 160 private Dex mergeDexes() throws IOException { 161 mergeStringIds(); 162 mergeTypeIds(); 163 mergeTypeLists(); 164 mergeProtoIds(); 165 mergeFieldIds(); 166 mergeMethodIds(); 167 mergeAnnotations(); 168 unionAnnotationSetsAndDirectories(); 169 mergeClassDefs(); 170 171 // write the header 172 contentsOut.header.off = 0; 173 contentsOut.header.size = 1; 174 contentsOut.fileSize = dexOut.getLength(); 175 contentsOut.computeSizesFromOffsets(); 176 contentsOut.writeHeader(headerOut); 177 contentsOut.writeMap(mapListOut); 178 179 // generate and write the hashes 180 dexOut.writeHashes(); 181 182 return dexOut; 183 } 184 185 public Dex merge() throws IOException { 186 long start = System.nanoTime(); 187 Dex result = mergeDexes(); 188 189 /* 190 * We use pessimistic sizes when merging dex files. If those sizes 191 * result in too many bytes wasted, compact the result. To compact, 192 * simply merge the result with itself. 193 */ 194 WriterSizes compactedSizes = new WriterSizes(this); 195 int wastedByteCount = writerSizes.size() - compactedSizes.size(); 196 if (wastedByteCount > + compactWasteThreshold) { 197 DexMerger compacter = new DexMerger( 198 dexOut, new Dex(0), CollisionPolicy.FAIL, compactedSizes); 199 result = compacter.mergeDexes(); 200 System.out.printf("Result compacted from %.1fKiB to %.1fKiB to save %.1fKiB%n", 201 dexOut.getLength() / 1024f, 202 result.getLength() / 1024f, 203 wastedByteCount / 1024f); 204 } 205 206 long elapsed = System.nanoTime() - start; 207 System.out.printf("Merged dex A (%d defs/%.1fKiB) with dex B " 208 + "(%d defs/%.1fKiB). Result is %d defs/%.1fKiB. Took %.1fs%n", 209 dexA.getTableOfContents().classDefs.size, 210 dexA.getLength() / 1024f, 211 dexB.getTableOfContents().classDefs.size, 212 dexB.getLength() / 1024f, 213 result.getTableOfContents().classDefs.size, 214 result.getLength() / 1024f, 215 elapsed / 1000000000f); 216 217 return result; 218 } 219 220 /** 221 * Reads an IDs section of two dex files and writes an IDs section of a 222 * merged dex file. Populates maps from old to new indices in the process. 223 */ 224 abstract class IdMerger<T extends Comparable<T>> { 225 private final Dex.Section out; 226 227 protected IdMerger(Dex.Section out) { 228 this.out = out; 229 } 230 231 /** 232 * Merges already-sorted sections, reading only two values into memory 233 * at a time. 234 */ 235 public final void mergeSorted() { 236 TableOfContents.Section aSection = getSection(dexA.getTableOfContents()); 237 TableOfContents.Section bSection = getSection(dexB.getTableOfContents()); 238 getSection(contentsOut).off = out.getPosition(); 239 240 Dex.Section inA = aSection.exists() ? dexA.open(aSection.off) : null; 241 Dex.Section inB = bSection.exists() ? dexB.open(bSection.off) : null; 242 int aOffset = -1; 243 int bOffset = -1; 244 int aIndex = 0; 245 int bIndex = 0; 246 int outCount = 0; 247 T a = null; 248 T b = null; 249 250 while (true) { 251 if (a == null && aIndex < aSection.size) { 252 aOffset = inA.getPosition(); 253 a = read(inA, aIndexMap, aIndex); 254 } 255 if (b == null && bIndex < bSection.size) { 256 bOffset = inB.getPosition(); 257 b = read(inB, bIndexMap, bIndex); 258 } 259 260 // Write the smaller of a and b. If they're equal, write only once 261 boolean advanceA; 262 boolean advanceB; 263 if (a != null && b != null) { 264 int compare = a.compareTo(b); 265 advanceA = compare <= 0; 266 advanceB = compare >= 0; 267 } else { 268 advanceA = (a != null); 269 advanceB = (b != null); 270 } 271 272 T toWrite = null; 273 if (advanceA) { 274 toWrite = a; 275 updateIndex(aOffset, aIndexMap, aIndex++, outCount); 276 a = null; 277 aOffset = -1; 278 } 279 if (advanceB) { 280 toWrite = b; 281 updateIndex(bOffset, bIndexMap, bIndex++, outCount); 282 b = null; 283 bOffset = -1; 284 } 285 if (toWrite == null) { 286 break; // advanceA == false && advanceB == false 287 } 288 write(toWrite); 289 outCount++; 290 } 291 292 getSection(contentsOut).size = outCount; 293 } 294 295 /** 296 * Merges unsorted sections by reading them completely into memory and 297 * sorting in memory. 298 */ 299 public final void mergeUnsorted() { 300 getSection(contentsOut).off = out.getPosition(); 301 302 List<UnsortedValue> all = new ArrayList<UnsortedValue>(); 303 all.addAll(readUnsortedValues(dexA, aIndexMap)); 304 all.addAll(readUnsortedValues(dexB, bIndexMap)); 305 Collections.sort(all); 306 307 int outCount = 0; 308 for (int i = 0; i < all.size(); ) { 309 UnsortedValue e1 = all.get(i++); 310 updateIndex(e1.offset, getIndexMap(e1.source), e1.index, outCount - 1); 311 312 while (i < all.size() && e1.compareTo(all.get(i)) == 0) { 313 UnsortedValue e2 = all.get(i++); 314 updateIndex(e2.offset, getIndexMap(e2.source), e2.index, outCount - 1); 315 } 316 317 write(e1.value); 318 outCount++; 319 } 320 321 getSection(contentsOut).size = outCount; 322 } 323 324 private List<UnsortedValue> readUnsortedValues(Dex source, IndexMap indexMap) { 325 TableOfContents.Section section = getSection(source.getTableOfContents()); 326 if (!section.exists()) { 327 return Collections.emptyList(); 328 } 329 330 List<UnsortedValue> result = new ArrayList<UnsortedValue>(); 331 Dex.Section in = source.open(section.off); 332 for (int i = 0; i < section.size; i++) { 333 int offset = in.getPosition(); 334 T value = read(in, indexMap, 0); 335 result.add(new UnsortedValue(source, indexMap, value, i, offset)); 336 } 337 return result; 338 } 339 340 abstract TableOfContents.Section getSection(TableOfContents tableOfContents); 341 abstract T read(Dex.Section in, IndexMap indexMap, int index); 342 abstract void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex); 343 abstract void write(T value); 344 345 class UnsortedValue implements Comparable<UnsortedValue> { 346 final Dex source; 347 final IndexMap indexMap; 348 final T value; 349 final int index; 350 final int offset; 351 352 UnsortedValue(Dex source, IndexMap indexMap, T value, int index, int offset) { 353 this.source = source; 354 this.indexMap = indexMap; 355 this.value = value; 356 this.index = index; 357 this.offset = offset; 358 } 359 360 public int compareTo(UnsortedValue unsortedValue) { 361 return value.compareTo(unsortedValue.value); 362 } 363 } 364 } 365 366 private IndexMap getIndexMap(Dex dex) { 367 if (dex == dexA) { 368 return aIndexMap; 369 } else if (dex == dexB) { 370 return bIndexMap; 371 } else { 372 throw new IllegalArgumentException(); 373 } 374 } 375 376 private void mergeStringIds() { 377 new IdMerger<String>(idsDefsOut) { 378 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 379 return tableOfContents.stringIds; 380 } 381 382 @Override String read(Dex.Section in, IndexMap indexMap, int index) { 383 return in.readString(); 384 } 385 386 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 387 indexMap.stringIds[oldIndex] = newIndex; 388 } 389 390 @Override void write(String value) { 391 contentsOut.stringDatas.size++; 392 idsDefsOut.writeInt(stringDataOut.getPosition()); 393 stringDataOut.writeStringData(value); 394 } 395 }.mergeSorted(); 396 } 397 398 private void mergeTypeIds() { 399 new IdMerger<Integer>(idsDefsOut) { 400 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 401 return tableOfContents.typeIds; 402 } 403 404 @Override Integer read(Dex.Section in, IndexMap indexMap, int index) { 405 int stringIndex = in.readInt(); 406 return indexMap.adjustString(stringIndex); 407 } 408 409 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 410 if (newIndex < 0 || newIndex > 0xffff) { 411 throw new IllegalArgumentException("type ID not in [0, 0xffff]: " + newIndex); 412 } 413 indexMap.typeIds[oldIndex] = (short) newIndex; 414 } 415 416 @Override void write(Integer value) { 417 idsDefsOut.writeInt(value); 418 } 419 }.mergeSorted(); 420 } 421 422 private void mergeTypeLists() { 423 new IdMerger<TypeList>(typeListOut) { 424 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 425 return tableOfContents.typeLists; 426 } 427 428 @Override TypeList read(Dex.Section in, IndexMap indexMap, int index) { 429 return indexMap.adjustTypeList(in.readTypeList()); 430 } 431 432 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 433 indexMap.putTypeListOffset(offset, typeListOut.getPosition()); 434 } 435 436 @Override void write(TypeList value) { 437 typeListOut.writeTypeList(value); 438 } 439 }.mergeUnsorted(); 440 } 441 442 private void mergeProtoIds() { 443 new IdMerger<ProtoId>(idsDefsOut) { 444 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 445 return tableOfContents.protoIds; 446 } 447 448 @Override ProtoId read(Dex.Section in, IndexMap indexMap, int index) { 449 return indexMap.adjust(in.readProtoId()); 450 } 451 452 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 453 if (newIndex < 0 || newIndex > 0xffff) { 454 throw new IllegalArgumentException("proto ID not in [0, 0xffff]: " + newIndex); 455 } 456 indexMap.protoIds[oldIndex] = (short) newIndex; 457 } 458 459 @Override void write(ProtoId value) { 460 value.writeTo(idsDefsOut); 461 } 462 }.mergeSorted(); 463 } 464 465 private void mergeFieldIds() { 466 new IdMerger<FieldId>(idsDefsOut) { 467 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 468 return tableOfContents.fieldIds; 469 } 470 471 @Override FieldId read(Dex.Section in, IndexMap indexMap, int index) { 472 return indexMap.adjust(in.readFieldId()); 473 } 474 475 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 476 if (newIndex < 0 || newIndex > 0xffff) { 477 throw new IllegalArgumentException("field ID not in [0, 0xffff]: " + newIndex); 478 } 479 indexMap.fieldIds[oldIndex] = (short) newIndex; 480 } 481 482 @Override void write(FieldId value) { 483 value.writeTo(idsDefsOut); 484 } 485 }.mergeSorted(); 486 } 487 488 private void mergeMethodIds() { 489 new IdMerger<MethodId>(idsDefsOut) { 490 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 491 return tableOfContents.methodIds; 492 } 493 494 @Override MethodId read(Dex.Section in, IndexMap indexMap, int index) { 495 return indexMap.adjust(in.readMethodId()); 496 } 497 498 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 499 if (newIndex < 0 || newIndex > 0xffff) { 500 throw new IllegalArgumentException("method ID not in [0, 0xffff]: " + newIndex); 501 } 502 indexMap.methodIds[oldIndex] = (short) newIndex; 503 } 504 505 @Override void write(MethodId methodId) { 506 methodId.writeTo(idsDefsOut); 507 } 508 }.mergeSorted(); 509 } 510 511 private void mergeAnnotations() { 512 new IdMerger<Annotation>(annotationOut) { 513 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 514 return tableOfContents.annotations; 515 } 516 517 @Override Annotation read(Dex.Section in, IndexMap indexMap, int index) { 518 return indexMap.adjust(in.readAnnotation()); 519 } 520 521 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 522 indexMap.putAnnotationOffset(offset, annotationOut.getPosition()); 523 } 524 525 @Override void write(Annotation value) { 526 value.writeTo(annotationOut); 527 } 528 }.mergeUnsorted(); 529 } 530 531 private void mergeClassDefs() { 532 SortableType[] types = getSortedTypes(); 533 contentsOut.classDefs.off = idsDefsOut.getPosition(); 534 contentsOut.classDefs.size = types.length; 535 536 for (SortableType type : types) { 537 Dex in = type.getDex(); 538 IndexMap indexMap = (in == dexA) ? aIndexMap : bIndexMap; 539 transformClassDef(in, type.getClassDef(), indexMap); 540 } 541 } 542 543 /** 544 * Returns the union of classes from both files, sorted in order such that 545 * a class is always preceded by its supertype and implemented interfaces. 546 */ 547 private SortableType[] getSortedTypes() { 548 // size is pessimistic; doesn't include arrays 549 SortableType[] sortableTypes = new SortableType[contentsOut.typeIds.size]; 550 readSortableTypes(sortableTypes, dexA, aIndexMap); 551 readSortableTypes(sortableTypes, dexB, bIndexMap); 552 553 /* 554 * Populate the depths of each sortable type. This makes D iterations 555 * through all N types, where 'D' is the depth of the deepest type. For 556 * example, the deepest class in libcore is Xalan's KeyIterator, which 557 * is 11 types deep. 558 */ 559 while (true) { 560 boolean allDone = true; 561 for (SortableType sortableType : sortableTypes) { 562 if (sortableType != null && !sortableType.isDepthAssigned()) { 563 allDone &= sortableType.tryAssignDepth(sortableTypes); 564 } 565 } 566 if (allDone) { 567 break; 568 } 569 } 570 571 // Now that all types have depth information, the result can be sorted 572 Arrays.sort(sortableTypes, SortableType.NULLS_LAST_ORDER); 573 574 // Strip nulls from the end 575 int firstNull = Arrays.asList(sortableTypes).indexOf(null); 576 return firstNull != -1 577 ? Arrays.copyOfRange(sortableTypes, 0, firstNull) 578 : sortableTypes; 579 } 580 581 /** 582 * Reads just enough data on each class so that we can sort it and then find 583 * it later. 584 */ 585 private void readSortableTypes(SortableType[] sortableTypes, Dex buffer, 586 IndexMap indexMap) { 587 for (ClassDef classDef : buffer.classDefs()) { 588 SortableType sortableType = indexMap.adjust(new SortableType(buffer, classDef)); 589 int t = sortableType.getTypeIndex(); 590 if (sortableTypes[t] == null) { 591 sortableTypes[t] = sortableType; 592 } else if (collisionPolicy != CollisionPolicy.KEEP_FIRST) { 593 throw new DexException("Multiple dex files define " 594 + buffer.typeNames().get(classDef.getTypeIndex())); 595 } 596 } 597 } 598 599 /** 600 * Copy annotation sets from each input to the output. 601 * 602 * TODO: this may write multiple copies of the same annotation set. 603 * We should shrink the output by merging rather than unioning 604 */ 605 private void unionAnnotationSetsAndDirectories() { 606 transformAnnotationSets(dexA, aIndexMap); 607 transformAnnotationSets(dexB, bIndexMap); 608 transformAnnotationSetRefLists(dexA, aIndexMap); 609 transformAnnotationSetRefLists(dexB, bIndexMap); 610 transformAnnotationDirectories(dexA, aIndexMap); 611 transformAnnotationDirectories(dexB, bIndexMap); 612 transformStaticValues(dexA, aIndexMap); 613 transformStaticValues(dexB, bIndexMap); 614 } 615 616 private void transformAnnotationSets(Dex in, IndexMap indexMap) { 617 TableOfContents.Section section = in.getTableOfContents().annotationSets; 618 if (section.exists()) { 619 Dex.Section setIn = in.open(section.off); 620 for (int i = 0; i < section.size; i++) { 621 transformAnnotationSet(indexMap, setIn); 622 } 623 } 624 } 625 626 private void transformAnnotationSetRefLists(Dex in, IndexMap indexMap) { 627 TableOfContents.Section section = in.getTableOfContents().annotationSetRefLists; 628 if (section.exists()) { 629 Dex.Section setIn = in.open(section.off); 630 for (int i = 0; i < section.size; i++) { 631 transformAnnotationSetRefList(indexMap, setIn); 632 } 633 } 634 } 635 636 private void transformAnnotationDirectories(Dex in, IndexMap indexMap) { 637 TableOfContents.Section section = in.getTableOfContents().annotationsDirectories; 638 if (section.exists()) { 639 Dex.Section directoryIn = in.open(section.off); 640 for (int i = 0; i < section.size; i++) { 641 transformAnnotationDirectory(directoryIn, indexMap); 642 } 643 } 644 } 645 646 private void transformStaticValues(Dex in, IndexMap indexMap) { 647 TableOfContents.Section section = in.getTableOfContents().encodedArrays; 648 if (section.exists()) { 649 Dex.Section staticValuesIn = in.open(section.off); 650 for (int i = 0; i < section.size; i++) { 651 transformStaticValues(staticValuesIn, indexMap); 652 } 653 } 654 } 655 656 /** 657 * Reads a class_def_item beginning at {@code in} and writes the index and 658 * data. 659 */ 660 private void transformClassDef(Dex in, ClassDef classDef, IndexMap indexMap) { 661 idsDefsOut.assertFourByteAligned(); 662 idsDefsOut.writeInt(classDef.getTypeIndex()); 663 idsDefsOut.writeInt(classDef.getAccessFlags()); 664 idsDefsOut.writeInt(classDef.getSupertypeIndex()); 665 idsDefsOut.writeInt(classDef.getInterfacesOffset()); 666 667 int sourceFileIndex = indexMap.adjustString(classDef.getSourceFileIndex()); 668 idsDefsOut.writeInt(sourceFileIndex); 669 670 int annotationsOff = classDef.getAnnotationsOffset(); 671 idsDefsOut.writeInt(indexMap.adjustAnnotationDirectory(annotationsOff)); 672 673 int classDataOff = classDef.getClassDataOffset(); 674 if (classDataOff == 0) { 675 idsDefsOut.writeInt(0); 676 } else { 677 idsDefsOut.writeInt(classDataOut.getPosition()); 678 ClassData classData = in.readClassData(classDef); 679 transformClassData(in, classData, indexMap); 680 } 681 682 int staticValuesOff = classDef.getStaticValuesOffset(); 683 idsDefsOut.writeInt(indexMap.adjustStaticValues(staticValuesOff)); 684 } 685 686 /** 687 * Transform all annotations on a class. 688 */ 689 private void transformAnnotationDirectory( 690 Dex.Section directoryIn, IndexMap indexMap) { 691 contentsOut.annotationsDirectories.size++; 692 annotationsDirectoryOut.assertFourByteAligned(); 693 indexMap.putAnnotationDirectoryOffset( 694 directoryIn.getPosition(), annotationsDirectoryOut.getPosition()); 695 696 int classAnnotationsOffset = indexMap.adjustAnnotationSet(directoryIn.readInt()); 697 annotationsDirectoryOut.writeInt(classAnnotationsOffset); 698 699 int fieldsSize = directoryIn.readInt(); 700 annotationsDirectoryOut.writeInt(fieldsSize); 701 702 int methodsSize = directoryIn.readInt(); 703 annotationsDirectoryOut.writeInt(methodsSize); 704 705 int parameterListSize = directoryIn.readInt(); 706 annotationsDirectoryOut.writeInt(parameterListSize); 707 708 for (int i = 0; i < fieldsSize; i++) { 709 // field index 710 annotationsDirectoryOut.writeInt(indexMap.adjustField(directoryIn.readInt())); 711 712 // annotations offset 713 annotationsDirectoryOut.writeInt(indexMap.adjustAnnotationSet(directoryIn.readInt())); 714 } 715 716 for (int i = 0; i < methodsSize; i++) { 717 // method index 718 annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt())); 719 720 // annotation set offset 721 annotationsDirectoryOut.writeInt( 722 indexMap.adjustAnnotationSet(directoryIn.readInt())); 723 } 724 725 for (int i = 0; i < parameterListSize; i++) { 726 // method index 727 annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt())); 728 729 // annotations offset 730 annotationsDirectoryOut.writeInt( 731 indexMap.adjustAnnotationSetRefList(directoryIn.readInt())); 732 } 733 } 734 735 /** 736 * Transform all annotations on a single type, member or parameter. 737 */ 738 private void transformAnnotationSet(IndexMap indexMap, Dex.Section setIn) { 739 contentsOut.annotationSets.size++; 740 annotationSetOut.assertFourByteAligned(); 741 indexMap.putAnnotationSetOffset(setIn.getPosition(), annotationSetOut.getPosition()); 742 743 int size = setIn.readInt(); 744 annotationSetOut.writeInt(size); 745 746 for (int j = 0; j < size; j++) { 747 annotationSetOut.writeInt(indexMap.adjustAnnotation(setIn.readInt())); 748 } 749 } 750 751 /** 752 * Transform all annotation set ref lists. 753 */ 754 private void transformAnnotationSetRefList(IndexMap indexMap, Dex.Section refListIn) { 755 contentsOut.annotationSetRefLists.size++; 756 annotationSetRefListOut.assertFourByteAligned(); 757 indexMap.putAnnotationSetRefListOffset( 758 refListIn.getPosition(), annotationSetRefListOut.getPosition()); 759 760 int parameterCount = refListIn.readInt(); 761 annotationSetRefListOut.writeInt(parameterCount); 762 for (int p = 0; p < parameterCount; p++) { 763 annotationSetRefListOut.writeInt(indexMap.adjustAnnotationSet(refListIn.readInt())); 764 } 765 } 766 767 private void transformClassData(Dex in, ClassData classData, IndexMap indexMap) { 768 contentsOut.classDatas.size++; 769 770 ClassData.Field[] staticFields = classData.getStaticFields(); 771 ClassData.Field[] instanceFields = classData.getInstanceFields(); 772 ClassData.Method[] directMethods = classData.getDirectMethods(); 773 ClassData.Method[] virtualMethods = classData.getVirtualMethods(); 774 775 classDataOut.writeUleb128(staticFields.length); 776 classDataOut.writeUleb128(instanceFields.length); 777 classDataOut.writeUleb128(directMethods.length); 778 classDataOut.writeUleb128(virtualMethods.length); 779 780 transformFields(indexMap, staticFields); 781 transformFields(indexMap, instanceFields); 782 transformMethods(in, indexMap, directMethods); 783 transformMethods(in, indexMap, virtualMethods); 784 } 785 786 private void transformFields(IndexMap indexMap, ClassData.Field[] fields) { 787 int lastOutFieldIndex = 0; 788 for (ClassData.Field field : fields) { 789 int outFieldIndex = indexMap.adjustField(field.getFieldIndex()); 790 classDataOut.writeUleb128(outFieldIndex - lastOutFieldIndex); 791 lastOutFieldIndex = outFieldIndex; 792 classDataOut.writeUleb128(field.getAccessFlags()); 793 } 794 } 795 796 private void transformMethods(Dex in, IndexMap indexMap, ClassData.Method[] methods) { 797 int lastOutMethodIndex = 0; 798 for (ClassData.Method method : methods) { 799 int outMethodIndex = indexMap.adjustMethod(method.getMethodIndex()); 800 classDataOut.writeUleb128(outMethodIndex - lastOutMethodIndex); 801 lastOutMethodIndex = outMethodIndex; 802 803 classDataOut.writeUleb128(method.getAccessFlags()); 804 805 if (method.getCodeOffset() == 0) { 806 classDataOut.writeUleb128(0); 807 } else { 808 codeOut.alignToFourBytesWithZeroFill(); 809 classDataOut.writeUleb128(codeOut.getPosition()); 810 transformCode(in, in.readCode(method), indexMap); 811 } 812 } 813 } 814 815 private void transformCode(Dex in, Code code, IndexMap indexMap) { 816 contentsOut.codes.size++; 817 codeOut.assertFourByteAligned(); 818 819 codeOut.writeUnsignedShort(code.getRegistersSize()); 820 codeOut.writeUnsignedShort(code.getInsSize()); 821 codeOut.writeUnsignedShort(code.getOutsSize()); 822 823 Code.Try[] tries = code.getTries(); 824 Code.CatchHandler[] catchHandlers = code.getCatchHandlers(); 825 codeOut.writeUnsignedShort(tries.length); 826 827 int debugInfoOffset = code.getDebugInfoOffset(); 828 if (debugInfoOffset != 0) { 829 codeOut.writeInt(debugInfoOut.getPosition()); 830 transformDebugInfoItem(in.open(debugInfoOffset), indexMap); 831 } else { 832 codeOut.writeInt(0); 833 } 834 835 short[] instructions = code.getInstructions(); 836 InstructionTransformer transformer = (in == dexA) 837 ? aInstructionTransformer 838 : bInstructionTransformer; 839 short[] newInstructions = transformer.transform(instructions); 840 codeOut.writeInt(newInstructions.length); 841 codeOut.write(newInstructions); 842 843 if (tries.length > 0) { 844 if (newInstructions.length % 2 == 1) { 845 codeOut.writeShort((short) 0); // padding 846 } 847 848 /* 849 * We can't write the tries until we've written the catch handlers. 850 * Unfortunately they're in the opposite order in the dex file so we 851 * need to transform them out-of-order. 852 */ 853 Dex.Section triesSection = dexOut.open(codeOut.getPosition()); 854 codeOut.skip(tries.length * SizeOf.TRY_ITEM); 855 int[] offsets = transformCatchHandlers(indexMap, catchHandlers); 856 transformTries(triesSection, tries, offsets); 857 } 858 } 859 860 /** 861 * Writes the catch handlers to {@code codeOut} and returns their indices. 862 */ 863 private int[] transformCatchHandlers(IndexMap indexMap, Code.CatchHandler[] catchHandlers) { 864 int baseOffset = codeOut.getPosition(); 865 codeOut.writeUleb128(catchHandlers.length); 866 int[] offsets = new int[catchHandlers.length]; 867 for (int i = 0; i < catchHandlers.length; i++) { 868 offsets[i] = codeOut.getPosition() - baseOffset; 869 transformEncodedCatchHandler(catchHandlers[i], indexMap); 870 } 871 return offsets; 872 } 873 874 private void transformTries(Dex.Section out, Code.Try[] tries, 875 int[] catchHandlerOffsets) { 876 for (Code.Try tryItem : tries) { 877 out.writeInt(tryItem.getStartAddress()); 878 out.writeUnsignedShort(tryItem.getInstructionCount()); 879 out.writeUnsignedShort(catchHandlerOffsets[tryItem.getCatchHandlerIndex()]); 880 } 881 } 882 883 private static final byte DBG_END_SEQUENCE = 0x00; 884 private static final byte DBG_ADVANCE_PC = 0x01; 885 private static final byte DBG_ADVANCE_LINE = 0x02; 886 private static final byte DBG_START_LOCAL = 0x03; 887 private static final byte DBG_START_LOCAL_EXTENDED = 0x04; 888 private static final byte DBG_END_LOCAL = 0x05; 889 private static final byte DBG_RESTART_LOCAL = 0x06; 890 private static final byte DBG_SET_PROLOGUE_END = 0x07; 891 private static final byte DBG_SET_EPILOGUE_BEGIN = 0x08; 892 private static final byte DBG_SET_FILE = 0x09; 893 894 private void transformDebugInfoItem(Dex.Section in, IndexMap indexMap) { 895 contentsOut.debugInfos.size++; 896 int lineStart = in.readUleb128(); 897 debugInfoOut.writeUleb128(lineStart); 898 899 int parametersSize = in.readUleb128(); 900 debugInfoOut.writeUleb128(parametersSize); 901 902 for (int p = 0; p < parametersSize; p++) { 903 int parameterName = in.readUleb128p1(); 904 debugInfoOut.writeUleb128p1(indexMap.adjustString(parameterName)); 905 } 906 907 int addrDiff; // uleb128 address delta. 908 int lineDiff; // sleb128 line delta. 909 int registerNum; // uleb128 register number. 910 int nameIndex; // uleb128p1 string index. Needs indexMap adjustment. 911 int typeIndex; // uleb128p1 type index. Needs indexMap adjustment. 912 int sigIndex; // uleb128p1 string index. Needs indexMap adjustment. 913 914 while (true) { 915 int opcode = in.readByte(); 916 debugInfoOut.writeByte(opcode); 917 918 switch (opcode) { 919 case DBG_END_SEQUENCE: 920 return; 921 922 case DBG_ADVANCE_PC: 923 addrDiff = in.readUleb128(); 924 debugInfoOut.writeUleb128(addrDiff); 925 break; 926 927 case DBG_ADVANCE_LINE: 928 lineDiff = in.readSleb128(); 929 debugInfoOut.writeSleb128(lineDiff); 930 break; 931 932 case DBG_START_LOCAL: 933 case DBG_START_LOCAL_EXTENDED: 934 registerNum = in.readUleb128(); 935 debugInfoOut.writeUleb128(registerNum); 936 nameIndex = in.readUleb128p1(); 937 debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex)); 938 typeIndex = in.readUleb128p1(); 939 debugInfoOut.writeUleb128p1(indexMap.adjustType(typeIndex)); 940 if (opcode == DBG_START_LOCAL_EXTENDED) { 941 sigIndex = in.readUleb128p1(); 942 debugInfoOut.writeUleb128p1(indexMap.adjustString(sigIndex)); 943 } 944 break; 945 946 case DBG_END_LOCAL: 947 case DBG_RESTART_LOCAL: 948 registerNum = in.readUleb128(); 949 debugInfoOut.writeUleb128(registerNum); 950 break; 951 952 case DBG_SET_FILE: 953 nameIndex = in.readUleb128p1(); 954 debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex)); 955 break; 956 957 case DBG_SET_PROLOGUE_END: 958 case DBG_SET_EPILOGUE_BEGIN: 959 default: 960 break; 961 } 962 } 963 } 964 965 private void transformEncodedCatchHandler(Code.CatchHandler catchHandler, IndexMap indexMap) { 966 int catchAllAddress = catchHandler.getCatchAllAddress(); 967 int[] typeIndexes = catchHandler.getTypeIndexes(); 968 int[] addresses = catchHandler.getAddresses(); 969 970 if (catchAllAddress != -1) { 971 codeOut.writeSleb128(-typeIndexes.length); 972 } else { 973 codeOut.writeSleb128(typeIndexes.length); 974 } 975 976 for (int i = 0; i < typeIndexes.length; i++) { 977 codeOut.writeUleb128(indexMap.adjustType(typeIndexes[i])); 978 codeOut.writeUleb128(addresses[i]); 979 } 980 981 if (catchAllAddress != -1) { 982 codeOut.writeUleb128(catchAllAddress); 983 } 984 } 985 986 private void transformStaticValues(Dex.Section in, IndexMap indexMap) { 987 contentsOut.encodedArrays.size++; 988 indexMap.putStaticValuesOffset(in.getPosition(), encodedArrayOut.getPosition()); 989 indexMap.adjustEncodedArray(in.readEncodedArray()).writeTo(encodedArrayOut); 990 } 991 992 /** 993 * Byte counts for the sections written when creating a dex. Target sizes 994 * are defined in one of two ways: 995 * <ul> 996 * <li>By pessimistically guessing how large the union of dex files will be. 997 * We're pessimistic because we can't predict the amount of duplication 998 * between dex files, nor can we predict the length of ULEB-encoded 999 * offsets or indices. 1000 * <li>By exactly measuring an existing dex. 1001 * </ul> 1002 */ 1003 private static class WriterSizes { 1004 private int header = SizeOf.HEADER_ITEM; 1005 private int idsDefs; 1006 private int mapList; 1007 private int typeList; 1008 private int classData; 1009 private int code; 1010 private int stringData; 1011 private int debugInfo; 1012 private int encodedArray; 1013 private int annotationsDirectory; 1014 private int annotationsSet; 1015 private int annotationsSetRefList; 1016 private int annotation; 1017 1018 /** 1019 * Compute sizes for merging a and b. 1020 */ 1021 public WriterSizes(Dex a, Dex b) { 1022 plus(a.getTableOfContents(), false); 1023 plus(b.getTableOfContents(), false); 1024 fourByteAlign(); 1025 } 1026 1027 public WriterSizes(DexMerger dexMerger) { 1028 header = dexMerger.headerOut.used(); 1029 idsDefs = dexMerger.idsDefsOut.used(); 1030 mapList = dexMerger.mapListOut.used(); 1031 typeList = dexMerger.typeListOut.used(); 1032 classData = dexMerger.classDataOut.used(); 1033 code = dexMerger.codeOut.used(); 1034 stringData = dexMerger.stringDataOut.used(); 1035 debugInfo = dexMerger.debugInfoOut.used(); 1036 encodedArray = dexMerger.encodedArrayOut.used(); 1037 annotationsDirectory = dexMerger.annotationsDirectoryOut.used(); 1038 annotationsSet = dexMerger.annotationSetOut.used(); 1039 annotationsSetRefList = dexMerger.annotationSetRefListOut.used(); 1040 annotation = dexMerger.annotationOut.used(); 1041 fourByteAlign(); 1042 } 1043 1044 private void plus(TableOfContents contents, boolean exact) { 1045 idsDefs += contents.stringIds.size * SizeOf.STRING_ID_ITEM 1046 + contents.typeIds.size * SizeOf.TYPE_ID_ITEM 1047 + contents.protoIds.size * SizeOf.PROTO_ID_ITEM 1048 + contents.fieldIds.size * SizeOf.MEMBER_ID_ITEM 1049 + contents.methodIds.size * SizeOf.MEMBER_ID_ITEM 1050 + contents.classDefs.size * SizeOf.CLASS_DEF_ITEM; 1051 mapList = SizeOf.UINT + (contents.sections.length * SizeOf.MAP_ITEM); 1052 typeList += contents.typeLists.byteCount; 1053 stringData += contents.stringDatas.byteCount; 1054 annotationsDirectory += contents.annotationsDirectories.byteCount; 1055 annotationsSet += contents.annotationSets.byteCount; 1056 annotationsSetRefList += contents.annotationSetRefLists.byteCount; 1057 1058 if (exact) { 1059 code += contents.codes.byteCount; 1060 classData += contents.classDatas.byteCount; 1061 encodedArray += contents.encodedArrays.byteCount; 1062 annotation += contents.annotations.byteCount; 1063 debugInfo += contents.debugInfos.byteCount; 1064 } else { 1065 // at most 1/4 of the bytes in a code section are uleb/sleb 1066 code += (int) Math.ceil(contents.codes.byteCount * 1.25); 1067 // at most 1/3 of the bytes in a class data section are uleb/sleb 1068 classData += (int) Math.ceil(contents.classDatas.byteCount * 1.34); 1069 // all of the bytes in an encoding arrays section may be uleb/sleb 1070 encodedArray += contents.encodedArrays.byteCount * 2; 1071 // all of the bytes in an annotations section may be uleb/sleb 1072 annotation += (int) Math.ceil(contents.annotations.byteCount * 2); 1073 // all of the bytes in a debug info section may be uleb/sleb 1074 debugInfo += contents.debugInfos.byteCount * 2; 1075 } 1076 } 1077 1078 private void fourByteAlign() { 1079 header = fourByteAlign(header); 1080 idsDefs = fourByteAlign(idsDefs); 1081 mapList = fourByteAlign(mapList); 1082 typeList = fourByteAlign(typeList); 1083 classData = fourByteAlign(classData); 1084 code = fourByteAlign(code); 1085 stringData = fourByteAlign(stringData); 1086 debugInfo = fourByteAlign(debugInfo); 1087 encodedArray = fourByteAlign(encodedArray); 1088 annotationsDirectory = fourByteAlign(annotationsDirectory); 1089 annotationsSet = fourByteAlign(annotationsSet); 1090 annotationsSetRefList = fourByteAlign(annotationsSetRefList); 1091 annotation = fourByteAlign(annotation); 1092 } 1093 1094 private static int fourByteAlign(int position) { 1095 return (position + 3) & ~3; 1096 } 1097 1098 public int size() { 1099 return header + idsDefs + mapList + typeList + classData + code + stringData + debugInfo 1100 + encodedArray + annotationsDirectory + annotationsSet + annotationsSetRefList 1101 + annotation; 1102 } 1103 } 1104 1105 public static void main(String[] args) throws IOException { 1106 if (args.length < 2) { 1107 printUsage(); 1108 return; 1109 } 1110 1111 Dex merged = new Dex(new File(args[1])); 1112 for (int i = 2; i < args.length; i++) { 1113 Dex toMerge = new Dex(new File(args[i])); 1114 merged = new DexMerger(merged, toMerge, CollisionPolicy.KEEP_FIRST).merge(); 1115 } 1116 merged.writeTo(new File(args[0])); 1117 } 1118 1119 private static void printUsage() { 1120 System.out.println("Usage: DexMerger <out.dex> <a.dex> <b.dex> ..."); 1121 System.out.println(); 1122 System.out.println( 1123 "If a class is defined in several dex, the class found in the first dex will be used."); 1124 } 1125 } 1126