| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dx.merge; |
| |
| import com.android.dx.dex.SizeOf; |
| import com.android.dx.dex.TableOfContents; |
| import com.android.dx.io.Annotation; |
| import com.android.dx.io.ClassData; |
| import com.android.dx.io.ClassDef; |
| import com.android.dx.io.Code; |
| import com.android.dx.io.DexBuffer; |
| import com.android.dx.io.DexHasher; |
| import com.android.dx.io.FieldId; |
| import com.android.dx.io.MethodId; |
| import com.android.dx.io.ProtoId; |
| import com.android.dx.util.DexException; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * Combine two dex files into one. |
| */ |
| public final class DexMerger { |
| private final DexBuffer dexA; |
| private final DexBuffer dexB; |
| private final CollisionPolicy collisionPolicy; |
| private final WriterSizes writerSizes; |
| |
| private final DexBuffer dexOut = new DexBuffer(); |
| |
| private final DexBuffer.Section headerOut; |
| |
| /** All IDs and definitions sections */ |
| private final DexBuffer.Section idsDefsOut; |
| |
| private final DexBuffer.Section mapListOut; |
| |
| private final DexBuffer.Section typeListOut; |
| |
| private final DexBuffer.Section classDataOut; |
| |
| private final DexBuffer.Section codeOut; |
| |
| private final DexBuffer.Section stringDataOut; |
| |
| private final DexBuffer.Section debugInfoOut; |
| |
| private final DexBuffer.Section encodedArrayOut; |
| |
| /** annotations directory on a type */ |
| private final DexBuffer.Section annotationsDirectoryOut; |
| |
| /** sets of annotations on a member, parameter or type */ |
| private final DexBuffer.Section annotationSetOut; |
| |
| /** parameter lists */ |
| private final DexBuffer.Section annotationSetRefListOut; |
| |
| /** individual annotations, each containing zero or more fields */ |
| private final DexBuffer.Section annotationOut; |
| |
| private final TableOfContents contentsOut; |
| |
| private final IndexMap aIndexMap; |
| private final IndexMap bIndexMap; |
| private final InstructionTransformer aInstructionTransformer; |
| private final InstructionTransformer bInstructionTransformer; |
| |
| /** minimum number of wasted bytes before it's worthwhile to compact the result */ |
| private int compactWasteThreshold = 1024 * 1024; // 1MiB |
| |
| public DexMerger(DexBuffer dexA, DexBuffer dexB, CollisionPolicy collisionPolicy) |
| throws IOException { |
| this(dexA, dexB, collisionPolicy, new WriterSizes(dexA, dexB)); |
| } |
| |
| private DexMerger(DexBuffer dexA, DexBuffer dexB, CollisionPolicy collisionPolicy, |
| WriterSizes writerSizes) throws IOException { |
| this.dexA = dexA; |
| this.dexB = dexB; |
| this.collisionPolicy = collisionPolicy; |
| this.writerSizes = writerSizes; |
| |
| TableOfContents aContents = dexA.getTableOfContents(); |
| TableOfContents bContents = dexB.getTableOfContents(); |
| aIndexMap = new IndexMap(dexOut, aContents); |
| bIndexMap = new IndexMap(dexOut, bContents); |
| aInstructionTransformer = new InstructionTransformer(aIndexMap); |
| bInstructionTransformer = new InstructionTransformer(bIndexMap); |
| |
| headerOut = dexOut.appendSection(writerSizes.header, "header"); |
| idsDefsOut = dexOut.appendSection(writerSizes.idsDefs, "ids defs"); |
| |
| contentsOut = dexOut.getTableOfContents(); |
| contentsOut.dataOff = dexOut.getLength(); |
| |
| contentsOut.mapList.off = dexOut.getLength(); |
| contentsOut.mapList.size = 1; |
| mapListOut = dexOut.appendSection(writerSizes.mapList, "map list"); |
| |
| contentsOut.typeLists.off = dexOut.getLength(); |
| typeListOut = dexOut.appendSection(writerSizes.typeList, "type list"); |
| |
| contentsOut.annotationSetRefLists.off = dexOut.getLength(); |
| annotationSetRefListOut = dexOut.appendSection( |
| writerSizes.annotationsSetRefList, "annotation set ref list"); |
| |
| contentsOut.annotationSets.off = dexOut.getLength(); |
| annotationSetOut = dexOut.appendSection(writerSizes.annotationsSet, "annotation sets"); |
| |
| contentsOut.classDatas.off = dexOut.getLength(); |
| classDataOut = dexOut.appendSection(writerSizes.classData, "class data"); |
| |
| contentsOut.codes.off = dexOut.getLength(); |
| codeOut = dexOut.appendSection(writerSizes.code, "code"); |
| |
| contentsOut.stringDatas.off = dexOut.getLength(); |
| stringDataOut = dexOut.appendSection(writerSizes.stringData, "string data"); |
| |
| contentsOut.debugInfos.off = dexOut.getLength(); |
| debugInfoOut = dexOut.appendSection(writerSizes.debugInfo, "debug info"); |
| |
| contentsOut.annotations.off = dexOut.getLength(); |
| annotationOut = dexOut.appendSection(writerSizes.annotation, "annotation"); |
| |
| contentsOut.encodedArrays.off = dexOut.getLength(); |
| encodedArrayOut = dexOut.appendSection(writerSizes.encodedArray, "encoded array"); |
| |
| contentsOut.annotationsDirectories.off = dexOut.getLength(); |
| annotationsDirectoryOut = dexOut.appendSection( |
| writerSizes.annotationsDirectory, "annotations directory"); |
| |
| dexOut.noMoreSections(); |
| contentsOut.dataSize = dexOut.getLength() - contentsOut.dataOff; |
| } |
| |
| public void setCompactWasteThreshold(int compactWasteThreshold) { |
| this.compactWasteThreshold = compactWasteThreshold; |
| } |
| |
| private DexBuffer mergeDexBuffers() throws IOException { |
| mergeStringIds(); |
| mergeTypeIds(); |
| mergeTypeLists(); |
| mergeProtoIds(); |
| mergeFieldIds(); |
| mergeMethodIds(); |
| mergeAnnotations(); |
| unionAnnotationSetsAndDirectories(); |
| mergeClassDefs(); |
| |
| // write the header |
| contentsOut.header.off = 0; |
| contentsOut.header.size = 1; |
| contentsOut.fileSize = dexOut.getLength(); |
| contentsOut.computeSizesFromOffsets(); |
| contentsOut.writeHeader(headerOut); |
| contentsOut.writeMap(mapListOut); |
| |
| // generate and write the hashes |
| new DexHasher().writeHashes(dexOut); |
| |
| return dexOut; |
| } |
| |
| public DexBuffer merge() throws IOException { |
| long start = System.nanoTime(); |
| DexBuffer result = mergeDexBuffers(); |
| |
| /* |
| * We use pessimistic sizes when merging dex files. If those sizes |
| * result in too many bytes wasted, compact the result. To compact, |
| * simply merge the result with itself. |
| */ |
| WriterSizes compactedSizes = new WriterSizes(this); |
| int wastedByteCount = writerSizes.size() - compactedSizes.size(); |
| if (wastedByteCount > + compactWasteThreshold) { |
| DexMerger compacter = new DexMerger( |
| dexOut, new DexBuffer(), CollisionPolicy.FAIL, compactedSizes); |
| result = compacter.mergeDexBuffers(); |
| System.out.printf("Result compacted from %.1fKiB to %.1fKiB to save %.1fKiB%n", |
| dexOut.getLength() / 1024f, |
| result.getLength() / 1024f, |
| wastedByteCount / 1024f); |
| } |
| |
| long elapsed = System.nanoTime() - start; |
| System.out.printf("Merged dex A (%d defs/%.1fKiB) with dex B " |
| + "(%d defs/%.1fKiB). Result is %d defs/%.1fKiB. Took %.1fs%n", |
| dexA.getTableOfContents().classDefs.size, |
| dexA.getLength() / 1024f, |
| dexB.getTableOfContents().classDefs.size, |
| dexB.getLength() / 1024f, |
| result.getTableOfContents().classDefs.size, |
| result.getLength() / 1024f, |
| elapsed / 1000000000f); |
| |
| return result; |
| } |
| |
| /** |
| * Reads an IDs section of two dex files and writes an IDs section of a |
| * merged dex file. Populates maps from old to new indices in the process. |
| */ |
| abstract class IdMerger<T extends Comparable<T>> { |
| private final DexBuffer.Section out; |
| |
| protected IdMerger(DexBuffer.Section out) { |
| this.out = out; |
| } |
| |
| /** |
| * Merges already-sorted sections, reading only two values into memory |
| * at a time. |
| */ |
| public final void mergeSorted() { |
| TableOfContents.Section aSection = getSection(dexA.getTableOfContents()); |
| TableOfContents.Section bSection = getSection(dexB.getTableOfContents()); |
| getSection(contentsOut).off = out.getPosition(); |
| |
| DexBuffer.Section inA = aSection.exists() ? dexA.open(aSection.off) : null; |
| DexBuffer.Section inB = bSection.exists() ? dexB.open(bSection.off) : null; |
| int aOffset = -1; |
| int bOffset = -1; |
| int aIndex = 0; |
| int bIndex = 0; |
| int outCount = 0; |
| T a = null; |
| T b = null; |
| |
| while (true) { |
| if (a == null && aIndex < aSection.size) { |
| aOffset = inA.getPosition(); |
| a = read(inA, aIndexMap, aIndex); |
| } |
| if (b == null && bIndex < bSection.size) { |
| bOffset = inB.getPosition(); |
| b = read(inB, bIndexMap, bIndex); |
| } |
| |
| // Write the smaller of a and b. If they're equal, write only once |
| boolean advanceA; |
| boolean advanceB; |
| if (a != null && b != null) { |
| int compare = a.compareTo(b); |
| advanceA = compare <= 0; |
| advanceB = compare >= 0; |
| } else { |
| advanceA = (a != null); |
| advanceB = (b != null); |
| } |
| |
| T toWrite = null; |
| if (advanceA) { |
| toWrite = a; |
| updateIndex(aOffset, aIndexMap, aIndex++, outCount); |
| a = null; |
| aOffset = -1; |
| } |
| if (advanceB) { |
| toWrite = b; |
| updateIndex(bOffset, bIndexMap, bIndex++, outCount); |
| b = null; |
| bOffset = -1; |
| } |
| if (toWrite == null) { |
| break; // advanceA == false && advanceB == false |
| } |
| write(toWrite); |
| outCount++; |
| } |
| |
| getSection(contentsOut).size = outCount; |
| } |
| |
| /** |
| * Merges unsorted sections by reading them completely into memory and |
| * sorting in memory. |
| */ |
| public final void mergeUnsorted() { |
| getSection(contentsOut).off = out.getPosition(); |
| |
| List<UnsortedValue> all = new ArrayList<UnsortedValue>(); |
| all.addAll(readUnsortedValues(dexA, aIndexMap)); |
| all.addAll(readUnsortedValues(dexB, bIndexMap)); |
| Collections.sort(all); |
| |
| int outCount = 0; |
| for (int i = 0; i < all.size(); ) { |
| UnsortedValue e1 = all.get(i++); |
| updateIndex(e1.offset, getIndexMap(e1.source), e1.index, outCount - 1); |
| |
| while (i < all.size() && e1.compareTo(all.get(i)) == 0) { |
| UnsortedValue e2 = all.get(i++); |
| updateIndex(e2.offset, getIndexMap(e2.source), e2.index, outCount - 1); |
| } |
| |
| write(e1.value); |
| outCount++; |
| } |
| |
| getSection(contentsOut).size = outCount; |
| } |
| |
| private List<UnsortedValue> readUnsortedValues(DexBuffer source, IndexMap indexMap) { |
| TableOfContents.Section section = getSection(source.getTableOfContents()); |
| if (!section.exists()) { |
| return Collections.emptyList(); |
| } |
| |
| List<UnsortedValue> result = new ArrayList<UnsortedValue>(); |
| DexBuffer.Section in = source.open(section.off); |
| for (int i = 0; i < section.size; i++) { |
| int offset = in.getPosition(); |
| T value = read(in, indexMap, 0); |
| result.add(new UnsortedValue(source, indexMap, value, i, offset)); |
| } |
| return result; |
| } |
| |
| abstract TableOfContents.Section getSection(TableOfContents tableOfContents); |
| abstract T read(DexBuffer.Section in, IndexMap indexMap, int index); |
| abstract void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex); |
| abstract void write(T value); |
| |
| class UnsortedValue implements Comparable<UnsortedValue> { |
| final DexBuffer source; |
| final IndexMap indexMap; |
| final T value; |
| final int index; |
| final int offset; |
| |
| UnsortedValue(DexBuffer source, IndexMap indexMap, T value, int index, int offset) { |
| this.source = source; |
| this.indexMap = indexMap; |
| this.value = value; |
| this.index = index; |
| this.offset = offset; |
| } |
| |
| public int compareTo(UnsortedValue unsortedValue) { |
| return value.compareTo(unsortedValue.value); |
| } |
| } |
| } |
| |
| private IndexMap getIndexMap(DexBuffer dexBuffer) { |
| if (dexBuffer == dexA) { |
| return aIndexMap; |
| } else if (dexBuffer == dexB) { |
| return bIndexMap; |
| } else { |
| throw new IllegalArgumentException(); |
| } |
| } |
| |
| private void mergeStringIds() { |
| new IdMerger<String>(idsDefsOut) { |
| @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { |
| return tableOfContents.stringIds; |
| } |
| |
| @Override String read(DexBuffer.Section in, IndexMap indexMap, int index) { |
| return in.readString(); |
| } |
| |
| @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { |
| indexMap.stringIds[oldIndex] = newIndex; |
| } |
| |
| @Override void write(String value) { |
| contentsOut.stringDatas.size++; |
| idsDefsOut.writeInt(stringDataOut.getPosition()); |
| stringDataOut.writeStringData(value); |
| } |
| }.mergeSorted(); |
| } |
| |
| private void mergeTypeIds() { |
| new IdMerger<Integer>(idsDefsOut) { |
| @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { |
| return tableOfContents.typeIds; |
| } |
| |
| @Override Integer read(DexBuffer.Section in, IndexMap indexMap, int index) { |
| int stringIndex = in.readInt(); |
| return indexMap.adjustString(stringIndex); |
| } |
| |
| @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { |
| indexMap.typeIds[oldIndex] = (short) newIndex; |
| } |
| |
| @Override void write(Integer value) { |
| idsDefsOut.writeInt(value); |
| } |
| }.mergeSorted(); |
| } |
| |
| private void mergeTypeLists() { |
| new IdMerger<TypeList>(typeListOut) { |
| @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { |
| return tableOfContents.typeLists; |
| } |
| |
| @Override TypeList read(DexBuffer.Section in, IndexMap indexMap, int index) { |
| return indexMap.adjustTypeList(in.readTypeList()); |
| } |
| |
| @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { |
| indexMap.putTypeListOffset(offset, typeListOut.getPosition()); |
| } |
| |
| @Override void write(TypeList value) { |
| typeListOut.writeTypeList(value); |
| } |
| }.mergeUnsorted(); |
| } |
| |
| private void mergeProtoIds() { |
| new IdMerger<ProtoId>(idsDefsOut) { |
| @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { |
| return tableOfContents.protoIds; |
| } |
| |
| @Override ProtoId read(DexBuffer.Section in, IndexMap indexMap, int index) { |
| return indexMap.adjust(in.readProtoId()); |
| } |
| |
| @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { |
| indexMap.protoIds[oldIndex] = (short) newIndex; |
| } |
| |
| @Override void write(ProtoId value) { |
| value.writeTo(idsDefsOut); |
| } |
| }.mergeSorted(); |
| } |
| |
| private void mergeFieldIds() { |
| new IdMerger<FieldId>(idsDefsOut) { |
| @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { |
| return tableOfContents.fieldIds; |
| } |
| |
| @Override FieldId read(DexBuffer.Section in, IndexMap indexMap, int index) { |
| return indexMap.adjust(in.readFieldId()); |
| } |
| |
| @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { |
| indexMap.fieldIds[oldIndex] = (short) newIndex; |
| } |
| |
| @Override void write(FieldId value) { |
| value.writeTo(idsDefsOut); |
| } |
| }.mergeSorted(); |
| } |
| |
| private void mergeMethodIds() { |
| new IdMerger<MethodId>(idsDefsOut) { |
| @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { |
| return tableOfContents.methodIds; |
| } |
| |
| @Override MethodId read(DexBuffer.Section in, IndexMap indexMap, int index) { |
| return indexMap.adjust(in.readMethodId()); |
| } |
| |
| @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { |
| indexMap.methodIds[oldIndex] = (short) newIndex; |
| } |
| |
| @Override void write(MethodId methodId) { |
| methodId.writeTo(idsDefsOut); |
| } |
| }.mergeSorted(); |
| } |
| |
| private void mergeAnnotations() { |
| new IdMerger<Annotation>(annotationOut) { |
| @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { |
| return tableOfContents.annotations; |
| } |
| |
| @Override Annotation read(DexBuffer.Section in, IndexMap indexMap, int index) { |
| return indexMap.adjust(in.readAnnotation()); |
| } |
| |
| @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { |
| indexMap.putAnnotationOffset(offset, annotationOut.getPosition()); |
| } |
| |
| @Override void write(Annotation value) { |
| value.writeTo(annotationOut); |
| } |
| }.mergeUnsorted(); |
| } |
| |
| private void mergeClassDefs() { |
| SortableType[] types = getSortedTypes(); |
| contentsOut.classDefs.off = idsDefsOut.getPosition(); |
| contentsOut.classDefs.size = types.length; |
| |
| for (SortableType type : types) { |
| DexBuffer in = type.getBuffer(); |
| IndexMap indexMap = (in == dexA) ? aIndexMap : bIndexMap; |
| transformClassDef(in, type.getClassDef(), indexMap); |
| } |
| } |
| |
| /** |
| * Returns the union of classes from both files, sorted in order such that |
| * a class is always preceded by its supertype and implemented interfaces. |
| */ |
| private SortableType[] getSortedTypes() { |
| // size is pessimistic; doesn't include arrays |
| SortableType[] sortableTypes = new SortableType[contentsOut.typeIds.size]; |
| readSortableTypes(sortableTypes, dexA, aIndexMap); |
| readSortableTypes(sortableTypes, dexB, bIndexMap); |
| |
| /* |
| * Populate the depths of each sortable type. This makes D iterations |
| * through all N types, where 'D' is the depth of the deepest type. For |
| * example, the deepest class in libcore is Xalan's KeyIterator, which |
| * is 11 types deep. |
| */ |
| while (true) { |
| boolean allDone = true; |
| for (SortableType sortableType : sortableTypes) { |
| if (sortableType != null && !sortableType.isDepthAssigned()) { |
| allDone &= sortableType.tryAssignDepth(sortableTypes); |
| } |
| } |
| if (allDone) { |
| break; |
| } |
| } |
| |
| // Now that all types have depth information, the result can be sorted |
| Arrays.sort(sortableTypes, SortableType.NULLS_LAST_ORDER); |
| |
| // Strip nulls from the end |
| int firstNull = Arrays.asList(sortableTypes).indexOf(null); |
| return firstNull != -1 |
| ? Arrays.copyOfRange(sortableTypes, 0, firstNull) |
| : sortableTypes; |
| } |
| |
| /** |
| * Reads just enough data on each class so that we can sort it and then find |
| * it later. |
| */ |
| private void readSortableTypes(SortableType[] sortableTypes, DexBuffer buffer, |
| IndexMap indexMap) { |
| for (ClassDef classDef : buffer.classDefs()) { |
| SortableType sortableType = indexMap.adjust(new SortableType(buffer, classDef)); |
| int t = sortableType.getTypeIndex(); |
| if (sortableTypes[t] == null) { |
| sortableTypes[t] = sortableType; |
| } else if (collisionPolicy != CollisionPolicy.KEEP_FIRST) { |
| throw new DexException("Multiple dex files define " |
| + buffer.typeNames().get(classDef.getTypeIndex())); |
| } |
| } |
| } |
| |
| /** |
| * Copy annotation sets from each input to the output. |
| * |
| * TODO: this may write multiple copies of the same annotation set. |
| * We should shrink the output by merging rather than unioning |
| */ |
| private void unionAnnotationSetsAndDirectories() { |
| transformAnnotationSets(dexA, aIndexMap); |
| transformAnnotationSets(dexB, bIndexMap); |
| transformAnnotationDirectories(dexA, aIndexMap); |
| transformAnnotationDirectories(dexB, bIndexMap); |
| transformStaticValues(dexA, aIndexMap); |
| transformStaticValues(dexB, bIndexMap); |
| } |
| |
| private void transformAnnotationSets(DexBuffer in, IndexMap indexMap) { |
| TableOfContents.Section section = in.getTableOfContents().annotationSets; |
| if (section.exists()) { |
| DexBuffer.Section setIn = in.open(section.off); |
| for (int i = 0; i < section.size; i++) { |
| transformAnnotationSet(indexMap, setIn); |
| } |
| } |
| } |
| |
| private void transformAnnotationDirectories(DexBuffer in, IndexMap indexMap) { |
| TableOfContents.Section section = in.getTableOfContents().annotationsDirectories; |
| if (section.exists()) { |
| DexBuffer.Section directoryIn = in.open(section.off); |
| for (int i = 0; i < section.size; i++) { |
| transformAnnotationDirectory(in, directoryIn, indexMap); |
| } |
| } |
| } |
| |
| private void transformStaticValues(DexBuffer in, IndexMap indexMap) { |
| TableOfContents.Section section = in.getTableOfContents().encodedArrays; |
| if (section.exists()) { |
| DexBuffer.Section staticValuesIn = in.open(section.off); |
| for (int i = 0; i < section.size; i++) { |
| transformStaticValues(staticValuesIn, indexMap); |
| } |
| } |
| } |
| |
| /** |
| * Reads a class_def_item beginning at {@code in} and writes the index and |
| * data. |
| */ |
| private void transformClassDef(DexBuffer in, ClassDef classDef, IndexMap indexMap) { |
| idsDefsOut.assertFourByteAligned(); |
| idsDefsOut.writeInt(classDef.getTypeIndex()); |
| idsDefsOut.writeInt(classDef.getAccessFlags()); |
| idsDefsOut.writeInt(classDef.getSupertypeIndex()); |
| idsDefsOut.writeInt(classDef.getInterfacesOffset()); |
| |
| int sourceFileIndex = indexMap.adjustString(classDef.getSourceFileIndex()); |
| idsDefsOut.writeInt(sourceFileIndex); |
| |
| int annotationsOff = classDef.getAnnotationsOffset(); |
| idsDefsOut.writeInt(indexMap.adjustAnnotationDirectory(annotationsOff)); |
| |
| int classDataOff = classDef.getClassDataOffset(); |
| if (classDataOff == 0) { |
| idsDefsOut.writeInt(0); |
| } else { |
| idsDefsOut.writeInt(classDataOut.getPosition()); |
| ClassData classData = in.readClassData(classDef); |
| transformClassData(in, classData, indexMap); |
| } |
| |
| int staticValuesOff = classDef.getStaticValuesOffset(); |
| idsDefsOut.writeInt(indexMap.adjustStaticValues(staticValuesOff)); |
| } |
| |
| /** |
| * Transform all annotations on a class. |
| */ |
| private void transformAnnotationDirectory( |
| DexBuffer in, DexBuffer.Section directoryIn, IndexMap indexMap) { |
| contentsOut.annotationsDirectories.size++; |
| annotationsDirectoryOut.assertFourByteAligned(); |
| indexMap.putAnnotationDirectoryOffset( |
| directoryIn.getPosition(), annotationsDirectoryOut.getPosition()); |
| |
| int classAnnotationsOffset = indexMap.adjustAnnotationSet(directoryIn.readInt()); |
| annotationsDirectoryOut.writeInt(classAnnotationsOffset); |
| |
| int fieldsSize = directoryIn.readInt(); |
| annotationsDirectoryOut.writeInt(fieldsSize); |
| |
| int methodsSize = directoryIn.readInt(); |
| annotationsDirectoryOut.writeInt(methodsSize); |
| |
| int parameterListSize = directoryIn.readInt(); |
| annotationsDirectoryOut.writeInt(parameterListSize); |
| |
| for (int i = 0; i < fieldsSize; i++) { |
| // field index |
| annotationsDirectoryOut.writeInt(indexMap.adjustField(directoryIn.readInt())); |
| |
| // annotations offset |
| annotationsDirectoryOut.writeInt(indexMap.adjustAnnotationSet(directoryIn.readInt())); |
| } |
| |
| for (int i = 0; i < methodsSize; i++) { |
| // method index |
| annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt())); |
| |
| // annotation set offset |
| annotationsDirectoryOut.writeInt( |
| indexMap.adjustAnnotationSet(directoryIn.readInt())); |
| } |
| |
| for (int i = 0; i < parameterListSize; i++) { |
| contentsOut.annotationSetRefLists.size++; |
| annotationSetRefListOut.assertFourByteAligned(); |
| |
| // method index |
| annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt())); |
| |
| // annotations offset |
| annotationsDirectoryOut.writeInt(annotationSetRefListOut.getPosition()); |
| DexBuffer.Section refListIn = in.open(directoryIn.readInt()); |
| |
| // parameters |
| int parameterCount = refListIn.readInt(); |
| annotationSetRefListOut.writeInt(parameterCount); |
| for (int p = 0; p < parameterCount; p++) { |
| annotationSetRefListOut.writeInt(indexMap.adjustAnnotationSet(refListIn.readInt())); |
| } |
| } |
| } |
| |
| /** |
| * Transform all annotations on a single type, member or parameter. |
| */ |
| private void transformAnnotationSet(IndexMap indexMap, DexBuffer.Section setIn) { |
| contentsOut.annotationSets.size++; |
| annotationSetOut.assertFourByteAligned(); |
| indexMap.putAnnotationSetOffset(setIn.getPosition(), annotationSetOut.getPosition()); |
| |
| int size = setIn.readInt(); |
| annotationSetOut.writeInt(size); |
| |
| for (int j = 0; j < size; j++) { |
| annotationSetOut.writeInt(indexMap.adjustAnnotation(setIn.readInt())); |
| } |
| } |
| |
| private void transformClassData(DexBuffer in, ClassData classData, IndexMap indexMap) { |
| contentsOut.classDatas.size++; |
| |
| ClassData.Field[] staticFields = classData.getStaticFields(); |
| ClassData.Field[] instanceFields = classData.getInstanceFields(); |
| ClassData.Method[] directMethods = classData.getDirectMethods(); |
| ClassData.Method[] virtualMethods = classData.getVirtualMethods(); |
| |
| classDataOut.writeUleb128(staticFields.length); |
| classDataOut.writeUleb128(instanceFields.length); |
| classDataOut.writeUleb128(directMethods.length); |
| classDataOut.writeUleb128(virtualMethods.length); |
| |
| transformFields(indexMap, staticFields); |
| transformFields(indexMap, instanceFields); |
| transformMethods(in, indexMap, directMethods); |
| transformMethods(in, indexMap, virtualMethods); |
| } |
| |
| private void transformFields(IndexMap indexMap, ClassData.Field[] fields) { |
| int lastOutFieldIndex = 0; |
| for (ClassData.Field field : fields) { |
| int outFieldIndex = indexMap.adjustField(field.getFieldIndex()); |
| classDataOut.writeUleb128(outFieldIndex - lastOutFieldIndex); |
| lastOutFieldIndex = outFieldIndex; |
| classDataOut.writeUleb128(field.getAccessFlags()); |
| } |
| } |
| |
| private void transformMethods(DexBuffer in, IndexMap indexMap, ClassData.Method[] methods) { |
| int lastOutMethodIndex = 0; |
| for (ClassData.Method method : methods) { |
| int outMethodIndex = indexMap.adjustMethod(method.getMethodIndex()); |
| classDataOut.writeUleb128(outMethodIndex - lastOutMethodIndex); |
| lastOutMethodIndex = outMethodIndex; |
| |
| classDataOut.writeUleb128(method.getAccessFlags()); |
| |
| if (method.getCodeOffset() == 0) { |
| classDataOut.writeUleb128(0); |
| } else { |
| codeOut.alignToFourBytes(); |
| classDataOut.writeUleb128(codeOut.getPosition()); |
| transformCode(in, in.readCode(method), indexMap); |
| } |
| } |
| } |
| |
| private void transformCode(DexBuffer in, Code code, IndexMap indexMap) { |
| contentsOut.codes.size++; |
| codeOut.assertFourByteAligned(); |
| |
| codeOut.writeUnsignedShort(code.getRegistersSize()); |
| codeOut.writeUnsignedShort(code.getInsSize()); |
| codeOut.writeUnsignedShort(code.getOutsSize()); |
| |
| Code.Try[] tries = code.getTries(); |
| codeOut.writeUnsignedShort(tries.length); |
| |
| int debugInfoOffset = code.getDebugInfoOffset(); |
| if (debugInfoOffset != 0) { |
| codeOut.writeInt(debugInfoOut.getPosition()); |
| transformDebugInfoItem(in.open(debugInfoOffset), indexMap); |
| } else { |
| codeOut.writeInt(0); |
| } |
| |
| short[] instructions = code.getInstructions(); |
| InstructionTransformer transformer = (in == dexA) |
| ? aInstructionTransformer |
| : bInstructionTransformer; |
| short[] newInstructions = transformer.transform(instructions); |
| codeOut.writeInt(newInstructions.length); |
| codeOut.write(newInstructions); |
| |
| if (tries.length > 0) { |
| if (newInstructions.length % 2 == 1) { |
| codeOut.writeShort((short) 0); // padding |
| } |
| for (Code.Try tryItem : tries) { |
| codeOut.writeInt(tryItem.getStartAddress()); |
| codeOut.writeUnsignedShort(tryItem.getInstructionCount()); |
| codeOut.writeUnsignedShort(tryItem.getHandlerOffset()); |
| } |
| Code.CatchHandler[] catchHandlers = code.getCatchHandlers(); |
| codeOut.writeUleb128(catchHandlers.length); |
| for (Code.CatchHandler catchHandler : catchHandlers) { |
| transformEncodedCatchHandler(catchHandler, indexMap); |
| } |
| } |
| } |
| |
| private static final byte DBG_END_SEQUENCE = 0x00; |
| private static final byte DBG_ADVANCE_PC = 0x01; |
| private static final byte DBG_ADVANCE_LINE = 0x02; |
| private static final byte DBG_START_LOCAL = 0x03; |
| private static final byte DBG_START_LOCAL_EXTENDED = 0x04; |
| private static final byte DBG_END_LOCAL = 0x05; |
| private static final byte DBG_RESTART_LOCAL = 0x06; |
| private static final byte DBG_SET_PROLOGUE_END = 0x07; |
| private static final byte DBG_SET_EPILOGUE_BEGIN = 0x08; |
| private static final byte DBG_SET_FILE = 0x09; |
| |
| private void transformDebugInfoItem(DexBuffer.Section in, IndexMap indexMap) { |
| contentsOut.debugInfos.size++; |
| int lineStart = in.readUleb128(); |
| debugInfoOut.writeUleb128(lineStart); |
| |
| int parametersSize = in.readUleb128(); |
| debugInfoOut.writeUleb128(parametersSize); |
| |
| for (int p = 0; p < parametersSize; p++) { |
| int parameterName = in.readUleb128p1(); |
| debugInfoOut.writeUleb128p1(indexMap.adjustString(parameterName)); |
| } |
| |
| int addrDiff; // uleb128 address delta. |
| int lineDiff; // sleb128 line delta. |
| int registerNum; // uleb128 register number. |
| int nameIndex; // uleb128p1 string index. Needs indexMap adjustment. |
| int typeIndex; // uleb128p1 type index. Needs indexMap adjustment. |
| int sigIndex; // uleb128p1 string index. Needs indexMap adjustment. |
| |
| while (true) { |
| int opcode = in.readByte(); |
| debugInfoOut.writeByte(opcode); |
| |
| switch (opcode) { |
| case DBG_END_SEQUENCE: |
| return; |
| |
| case DBG_ADVANCE_PC: |
| addrDiff = in.readUleb128(); |
| debugInfoOut.writeUleb128(addrDiff); |
| break; |
| |
| case DBG_ADVANCE_LINE: |
| lineDiff = in.readSleb128(); |
| debugInfoOut.writeSleb128(lineDiff); |
| break; |
| |
| case DBG_START_LOCAL: |
| case DBG_START_LOCAL_EXTENDED: |
| registerNum = in.readUleb128(); |
| debugInfoOut.writeUleb128(registerNum); |
| nameIndex = in.readUleb128p1(); |
| debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex)); |
| typeIndex = in.readUleb128p1(); |
| debugInfoOut.writeUleb128p1(indexMap.adjustType(typeIndex)); |
| if (opcode == DBG_START_LOCAL_EXTENDED) { |
| sigIndex = in.readUleb128p1(); |
| debugInfoOut.writeUleb128p1(indexMap.adjustString(sigIndex)); |
| } |
| break; |
| |
| case DBG_END_LOCAL: |
| case DBG_RESTART_LOCAL: |
| registerNum = in.readUleb128(); |
| debugInfoOut.writeUleb128(registerNum); |
| break; |
| |
| case DBG_SET_FILE: |
| nameIndex = in.readUleb128p1(); |
| debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex)); |
| break; |
| |
| case DBG_SET_PROLOGUE_END: |
| case DBG_SET_EPILOGUE_BEGIN: |
| default: |
| break; |
| } |
| } |
| } |
| |
| private void transformEncodedCatchHandler(Code.CatchHandler catchHandler, IndexMap indexMap) { |
| int catchAllAddress = catchHandler.getCatchAllAddress(); |
| int[] typeIndexes = catchHandler.getTypeIndexes(); |
| int[] addresses = catchHandler.getAddresses(); |
| |
| if (catchAllAddress != -1) { |
| codeOut.writeSleb128(-typeIndexes.length); |
| } else { |
| codeOut.writeSleb128(typeIndexes.length); |
| } |
| |
| for (int i = 0; i < typeIndexes.length; i++) { |
| codeOut.writeUleb128(indexMap.adjustType(typeIndexes[i])); |
| codeOut.writeUleb128(addresses[i]); |
| } |
| |
| if (catchAllAddress != -1) { |
| codeOut.writeUleb128(catchAllAddress); |
| } |
| } |
| |
| private void transformStaticValues(DexBuffer.Section in, IndexMap indexMap) { |
| contentsOut.encodedArrays.size++; |
| indexMap.putStaticValuesOffset(in.getPosition(), encodedArrayOut.getPosition()); |
| indexMap.adjustEncodedArray(in.readEncodedArray()).writeTo(encodedArrayOut); |
| } |
| |
| /** |
| * Byte counts for the sections written when creating a dex. Target sizes |
| * are defined in one of two ways: |
| * <ul> |
| * <li>By pessimistically guessing how large the union of dex files will be. |
| * We're pessimistic because we can't predict the amount of duplication |
| * between dex files, nor can we predict the length of ULEB-encoded |
| * offsets or indices. |
| * <li>By exactly measuring an existing dex. |
| * </ul> |
| */ |
| private static class WriterSizes { |
| private int header = SizeOf.HEADER_ITEM; |
| private int idsDefs; |
| private int mapList; |
| private int typeList; |
| private int classData; |
| private int code; |
| private int stringData; |
| private int debugInfo; |
| private int encodedArray; |
| private int annotationsDirectory; |
| private int annotationsSet; |
| private int annotationsSetRefList; |
| private int annotation; |
| |
| /** |
| * Compute sizes for merging a and b. |
| */ |
| public WriterSizes(DexBuffer a, DexBuffer b) { |
| plus(a.getTableOfContents(), false); |
| plus(b.getTableOfContents(), false); |
| } |
| |
| public WriterSizes(DexMerger dexMerger) { |
| header = dexMerger.headerOut.used(); |
| idsDefs = dexMerger.idsDefsOut.used(); |
| mapList = dexMerger.mapListOut.used(); |
| typeList = dexMerger.typeListOut.used(); |
| classData = dexMerger.classDataOut.used(); |
| code = dexMerger.codeOut.used(); |
| stringData = dexMerger.stringDataOut.used(); |
| debugInfo = dexMerger.debugInfoOut.used(); |
| encodedArray = dexMerger.encodedArrayOut.used(); |
| annotationsDirectory = dexMerger.annotationsDirectoryOut.used(); |
| annotationsSet = dexMerger.annotationSetOut.used(); |
| annotationsSetRefList = dexMerger.annotationSetRefListOut.used(); |
| annotation = dexMerger.annotationOut.used(); |
| } |
| |
| public void plus(TableOfContents contents, boolean exact) { |
| idsDefs += contents.stringIds.size * SizeOf.STRING_ID_ITEM |
| + contents.typeIds.size * SizeOf.TYPE_ID_ITEM |
| + contents.protoIds.size * SizeOf.PROTO_ID_ITEM |
| + contents.fieldIds.size * SizeOf.MEMBER_ID_ITEM |
| + contents.methodIds.size * SizeOf.MEMBER_ID_ITEM |
| + contents.classDefs.size * SizeOf.CLASS_DEF_ITEM; |
| mapList = SizeOf.UINT + (contents.sections.length * SizeOf.MAP_ITEM); |
| typeList += contents.typeLists.byteCount; |
| stringData += contents.stringDatas.byteCount; |
| annotationsDirectory += contents.annotationsDirectories.byteCount; |
| annotationsSet += contents.annotationSets.byteCount; |
| annotationsSetRefList += contents.annotationSetRefLists.byteCount; |
| |
| if (exact) { |
| code += contents.codes.byteCount; |
| classData += contents.classDatas.byteCount; |
| encodedArray += contents.encodedArrays.byteCount; |
| annotation += contents.annotations.byteCount; |
| debugInfo += contents.debugInfos.byteCount; |
| } else { |
| // at most 1/4 of the bytes in a code section are uleb/sleb |
| code += (int) Math.ceil(contents.codes.byteCount * 1.25); |
| // at most 1/3 of the bytes in a class data section are uleb/sleb |
| classData += (int) Math.ceil(contents.classDatas.byteCount * 1.34); |
| // all of the bytes in an encoding arrays section may be uleb/sleb |
| encodedArray += contents.encodedArrays.byteCount * 2; |
| // all of the bytes in an annotations section may be uleb/sleb |
| annotation += (int) Math.ceil(contents.annotations.byteCount * 2); |
| // all of the bytes in a debug info section may be uleb/sleb |
| debugInfo += contents.debugInfos.byteCount * 2; |
| } |
| |
| typeList = DexBuffer.fourByteAlign(typeList); |
| code = DexBuffer.fourByteAlign(code); |
| } |
| |
| public int size() { |
| return header + idsDefs + mapList + typeList + classData + code + stringData + debugInfo |
| + encodedArray + annotationsDirectory + annotationsSet + annotationsSetRefList |
| + annotation; |
| } |
| } |
| |
| public static void main(String[] args) throws IOException { |
| if (args.length != 3) { |
| printUsage(); |
| return; |
| } |
| |
| DexBuffer dexA = new DexBuffer(new File(args[1])); |
| DexBuffer dexB = new DexBuffer(new File(args[2])); |
| DexBuffer merged = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge(); |
| merged.writeTo(new File(args[0])); |
| } |
| |
| private static void printUsage() { |
| System.out.println("Usage: DexMerger <out.dex> <a.dex> <b.dex>"); |
| System.out.println(); |
| System.out.println("If both a and b define the same classes, a's copy will be used."); |
| } |
| } |