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