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