mirror of
https://github.com/revanced/smali.git
synced 2025-05-05 00:54:25 +02:00

git-svn-id: https://smali.googlecode.com/svn/trunk@704 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
897 lines
35 KiB
Java
897 lines
35 KiB
Java
/*
|
|
* [The "BSD licence"]
|
|
* Copyright (c) 2010 Ben Gruver (JesusFreke)
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
package org.jf.dexlib;
|
|
|
|
import org.jf.dexlib.Util.*;
|
|
|
|
import java.io.*;
|
|
import java.security.DigestException;
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.zip.Adler32;
|
|
import java.util.zip.ZipEntry;
|
|
import java.util.zip.ZipFile;
|
|
|
|
/**
|
|
* <h3>Main use cases</h3>
|
|
*
|
|
* <p>These are the main use cases that drove the design of this library</p>
|
|
*
|
|
* <ol>
|
|
* <li><p><b>Annotate an existing dex file</b> - In this case, the intent is to document the structure of
|
|
* an existing dex file. We want to be able to read in the dex file, and then write out a dex file
|
|
* that is exactly the same (while adding annotation information to an AnnotatedOutput object)</p></li>
|
|
*
|
|
* <li><p><b>Canonicalize an existing dex file</b> - In this case, the intent is to rewrite an existing dex file
|
|
* so that it is in a canonical form. There is a certain amount of leeway in how various types of
|
|
* tems in a dex file are ordered or represented. It is sometimes useful to be able to easily
|
|
* compare a disassebled and reassembled dex file with the original dex file. If both dex-files are
|
|
* written canonically, they "should" match exactly, barring any explicit changes to the reassembled
|
|
* file.</p>
|
|
*
|
|
* <p>Currently, there are a couple of pieces of information that probably won't match exactly
|
|
* <ul>
|
|
* <li>the order of exception handlers in the <code>EncodedCatchHandlerList</code> for a method</li>
|
|
* <li>the ordering of some of the debug info in the <code>{@link org.jf.dexlib.DebugInfoItem}</code> for a method</li>
|
|
* </ul></p>
|
|
*
|
|
*
|
|
* <p>Note that the above discrepancies should typically only be "intra-item" differences. They
|
|
* shouldn't change the size of the item, or affect how anything else is placed or laid out</p></li>
|
|
*
|
|
* <li><p><b>Creating a dex file from scratch</b> - In this case, a blank dex file is created and then classes
|
|
* are added to it incrementally by calling the {@link org.jf.dexlib.Section#intern intern} method of
|
|
* {@link DexFile#ClassDefsSection}, which will add all the information necessary to represent the given
|
|
* class. For example, when assembling a dex file from a set of assembly text files.</p>
|
|
*
|
|
* <p>In this case, we can choose to write the dex file in a canonical form or not. It is somewhat
|
|
* slower to write it in a canonical format, due to the extra sorting and calculations that are
|
|
* required.</p></li>
|
|
*
|
|
*
|
|
* <li><p><b>Reading in the dex file</b> - In this case, the intent is to read in a dex file and expose all the
|
|
* data to the calling application. For example, when disassembling a dex file into a text based
|
|
* assembly format, or doing other misc processing of the dex file.</p></li>
|
|
*
|
|
*
|
|
* <h3>Other use cases</h3>
|
|
*
|
|
* <p>These are other use cases that are possible, but did not drive the design of the library.
|
|
* No effort was made to test these use cases or ensure that they work. Some of these could
|
|
* probably be better achieved with a disassemble - modify - reassemble type process, using
|
|
* smali/baksmali or another assembler/disassembler pair that are compatible with each other</p>
|
|
*
|
|
* <ul>
|
|
* <li>deleting classes/methods/etc. from a dex file</li>
|
|
* <li>merging 2 dex files</li>
|
|
* <li>splitting a dex file</li>
|
|
* <li>moving classes from 1 dex file to another</li>
|
|
* <li>removing the debug information from a dex file</li>
|
|
* <li>obfustication of a dex file</li>
|
|
* </ul>
|
|
*/
|
|
public class DexFile
|
|
{
|
|
/**
|
|
* A mapping from ItemType to the section that contains items of the given type
|
|
*/
|
|
private final Section[] sectionsByType;
|
|
|
|
/**
|
|
* Ordered lists of the indexed and offsetted sections. The order of these lists specifies the order
|
|
* that the sections will be written in
|
|
*/
|
|
private final IndexedSection[] indexedSections;
|
|
private final OffsettedSection[] offsettedSections;
|
|
|
|
/**
|
|
* dalvik had a bug where it wrote the registers for certain types of debug info in a signed leb
|
|
* format, instead of an unsigned leb format. There are no negative registers of course, but
|
|
* certain positive values have a different encoding depending on whether they are encoded as
|
|
* an unsigned leb128 or a signed leb128. Specifically, the signed leb128 is 1 byte longer in some cases.
|
|
*
|
|
* This determine whether we should keep any signed registers as signed, or force all register to
|
|
* unsigned. By default we don't keep track of whether they were signed or not, and write them back
|
|
* out as unsigned. This option only has an effect when reading an existing dex file. It has no
|
|
* effect when a dex file is created from scratch
|
|
*
|
|
* The 2 main use-cases in play are
|
|
* 1. Annotate an existing dex file - In this case, preserveSignedRegisters should be false, so that we keep
|
|
* track of any signed registers and write them back out as signed Leb128 values.
|
|
*
|
|
* 2. Canonicalize an existing dex file - In this case, fixRegisters should be true, so that all
|
|
* registers in the debug info are written as unsigned Leb128 values regardless of how they were
|
|
* originally encoded
|
|
*/
|
|
private final boolean preserveSignedRegisters;
|
|
|
|
/**
|
|
* When true, any instructions in a code item are skipped over instead of being read in. This is useful when
|
|
* you only need the information about the classes and their methods, for example, when loading the BOOTCLASSPATH
|
|
* jars in order to analyze a dex file
|
|
*/
|
|
private final boolean skipInstructions;
|
|
|
|
/**
|
|
* When true, this prevents any sorting of the items during placement of the dex file. This
|
|
* should *only* be set to true when this dex file was read in from an existing (valid) dex file,
|
|
* and no modifications were made (i.e. no items added or deleted). Otherwise it is likely that
|
|
* an invalid dex file will be generated.
|
|
*
|
|
* This is useful for the first use case (annotating an existing dex file). This ensures the items
|
|
* retain the same order as in the original dex file.
|
|
*/
|
|
private boolean inplace = false;
|
|
|
|
/**
|
|
* When true, this imposes an full ordering on all the items, to force them into a (possibly
|
|
* arbitrary) canonical order. When false, only the items that the dex format specifies
|
|
* an order for are sorted. The rest of the items are not ordered.
|
|
*
|
|
* This is useful for the second use case (canonicalizing an existing dex file) or possibly for
|
|
* the third use case (creating a dex file from scratch), if there is a need to write the new
|
|
* dex file in a canonical form.
|
|
*/
|
|
private boolean sortAllItems = false;
|
|
|
|
|
|
/**
|
|
* this is used to access the dex file from within inner classes, when they declare fields or
|
|
* variable that hide fields on this object
|
|
*/
|
|
private final DexFile dexFile = this;
|
|
|
|
/**
|
|
* Is this file an odex file? This is only set when reading in an odex file
|
|
*/
|
|
private boolean isOdex = false;
|
|
|
|
private OdexDependencies odexDependencies;
|
|
|
|
private int dataOffset;
|
|
private int dataSize;
|
|
private int fileSize;
|
|
|
|
/**
|
|
* A private constructor containing common code to initialize the section maps and lists
|
|
* @param preserveSignedRegisters If true, keep track of any registers in the debug information
|
|
* @param skipInstructions If true, skip the instructions in any code item.
|
|
* that are signed, so they will be written in the same format. See
|
|
* <code>getPreserveSignedRegisters()</code>
|
|
*/
|
|
private DexFile(boolean preserveSignedRegisters, boolean skipInstructions) {
|
|
this.preserveSignedRegisters = preserveSignedRegisters;
|
|
this.skipInstructions = skipInstructions;
|
|
|
|
sectionsByType = new Section[] {
|
|
StringIdsSection,
|
|
TypeIdsSection,
|
|
ProtoIdsSection,
|
|
FieldIdsSection,
|
|
MethodIdsSection,
|
|
ClassDefsSection,
|
|
TypeListsSection,
|
|
AnnotationSetRefListsSection,
|
|
AnnotationSetsSection,
|
|
ClassDataSection,
|
|
CodeItemsSection,
|
|
AnnotationDirectoriesSection,
|
|
StringDataSection,
|
|
DebugInfoItemsSection,
|
|
AnnotationsSection,
|
|
EncodedArraysSection,
|
|
null,
|
|
null
|
|
};
|
|
|
|
indexedSections = new IndexedSection[] {
|
|
StringIdsSection,
|
|
TypeIdsSection,
|
|
ProtoIdsSection,
|
|
FieldIdsSection,
|
|
MethodIdsSection,
|
|
ClassDefsSection
|
|
};
|
|
|
|
offsettedSections = new OffsettedSection[] {
|
|
AnnotationSetRefListsSection,
|
|
AnnotationSetsSection,
|
|
CodeItemsSection,
|
|
AnnotationDirectoriesSection,
|
|
TypeListsSection,
|
|
StringDataSection,
|
|
AnnotationsSection,
|
|
EncodedArraysSection,
|
|
ClassDataSection,
|
|
DebugInfoItemsSection
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* Construct a new DexFile instance by reading in the given dex file.
|
|
* @param file The dex file to read in
|
|
* @throws IOException if an IOException occurs
|
|
*/
|
|
public DexFile(String file)
|
|
throws IOException {
|
|
this(new File(file), true, false);
|
|
}
|
|
|
|
/**
|
|
* Construct a new DexFile instance by reading in the given dex file,
|
|
* and optionally keep track of any registers in the debug information that are signed,
|
|
* so they will be written in the same format.
|
|
* @param file The dex file to read in
|
|
* @param preserveSignedRegisters If true, keep track of any registers in the debug information
|
|
* that are signed, so they will be written in the same format. See
|
|
* @param skipInstructions If true, skip the instructions in any code item.
|
|
* <code>getPreserveSignedRegisters()</code>
|
|
* @throws IOException if an IOException occurs
|
|
*/
|
|
public DexFile(String file, boolean preserveSignedRegisters, boolean skipInstructions)
|
|
throws IOException {
|
|
this(new File(file), preserveSignedRegisters, skipInstructions);
|
|
}
|
|
|
|
/**
|
|
* Construct a new DexFile instance by reading in the given dex file.
|
|
* @param file The dex file to read in
|
|
* @throws IOException if an IOException occurs
|
|
*/
|
|
public DexFile(File file)
|
|
throws IOException {
|
|
this(file, true, false);
|
|
}
|
|
|
|
/**
|
|
* Construct a new DexFile instance by reading in the given dex file,
|
|
* and optionally keep track of any registers in the debug information that are signed,
|
|
* so they will be written in the same format.
|
|
* @param file The dex file to read in
|
|
* @param preserveSignedRegisters If true, keep track of any registers in the debug information
|
|
* that are signed, so they will be written in the same format.
|
|
* @param skipInstructions If true, skip the instructions in any code item.
|
|
* @see #getPreserveSignedRegisters
|
|
* @throws IOException if an IOException occurs
|
|
*/
|
|
public DexFile(File file, boolean preserveSignedRegisters, boolean skipInstructions)
|
|
throws IOException {
|
|
this(preserveSignedRegisters, skipInstructions);
|
|
|
|
long fileLength;
|
|
byte[] magic = FileUtils.readFile(file, 0, 8);
|
|
|
|
InputStream inputStream = null;
|
|
Input in = null;
|
|
ZipFile zipFile = null;
|
|
|
|
try {
|
|
//do we have a zip file?
|
|
if (magic[0] == 0x50 && magic[1] == 0x4B) {
|
|
zipFile = new ZipFile(file);
|
|
ZipEntry zipEntry = zipFile.getEntry("classes.dex");
|
|
if (zipEntry == null) {
|
|
throw new NoClassesDexException("zip file " + file.getName() + " does not contain a classes.dex " +
|
|
"file");
|
|
}
|
|
fileLength = zipEntry.getSize();
|
|
if (fileLength < 40) {
|
|
throw new RuntimeException("The classes.dex file in " + file.getName() + " is too small to be a" +
|
|
" valid dex file");
|
|
} else if (fileLength > Integer.MAX_VALUE) {
|
|
throw new RuntimeException("The classes.dex file in " + file.getName() + " is too large to read in");
|
|
}
|
|
inputStream = new BufferedInputStream(zipFile.getInputStream(zipEntry));
|
|
|
|
inputStream.mark(8);
|
|
for (int i=0; i<8; i++) {
|
|
magic[i] = (byte)inputStream.read();
|
|
}
|
|
inputStream.reset();
|
|
} else {
|
|
fileLength = file.length();
|
|
if (fileLength < 40) {
|
|
throw new RuntimeException(file.getName() + " is too small to be a valid dex file");
|
|
}
|
|
if (fileLength < 40) {
|
|
throw new RuntimeException(file.getName() + " is too small to be a valid dex file");
|
|
} else if (fileLength > Integer.MAX_VALUE) {
|
|
throw new RuntimeException(file.getName() + " is too large to read in");
|
|
}
|
|
inputStream = new FileInputStream(file);
|
|
}
|
|
|
|
byte[] dexMagic, odexMagic;
|
|
|
|
dexMagic = org.jf.dexlib.HeaderItem.MAGIC;
|
|
odexMagic = OdexHeader.MAGIC;
|
|
|
|
boolean isDex = true;
|
|
this.isOdex = true;
|
|
for (int i=0; i<8; i++) {
|
|
if (magic[i] != dexMagic[i]) {
|
|
isDex = false;
|
|
}
|
|
if (magic[i] != odexMagic[i]) {
|
|
isOdex = false;
|
|
}
|
|
}
|
|
|
|
if (isOdex) {
|
|
byte[] odexHeaderBytes = FileUtils.readStream(inputStream, 40);
|
|
Input odexHeaderIn = new ByteArrayInput(odexHeaderBytes);
|
|
OdexHeader odexHeader = new OdexHeader(odexHeaderIn);
|
|
|
|
int dependencySkip = odexHeader.depsOffset - odexHeader.dexOffset - odexHeader.dexLength;
|
|
if (dependencySkip < 0) {
|
|
throw new ExceptionWithContext("Unexpected placement of the odex dependency data");
|
|
}
|
|
|
|
if (odexHeader.dexOffset > 40) {
|
|
FileUtils.readStream(inputStream, odexHeader.dexOffset - 40);
|
|
}
|
|
|
|
in = new ByteArrayInput(FileUtils.readStream(inputStream, odexHeader.dexLength));
|
|
|
|
if (dependencySkip > 0) {
|
|
FileUtils.readStream(inputStream, dependencySkip);
|
|
}
|
|
|
|
odexDependencies = new OdexDependencies(
|
|
new ByteArrayInput(FileUtils.readStream(inputStream, odexHeader.depsLength)));
|
|
} else if (isDex) {
|
|
in = new ByteArrayInput(FileUtils.readStream(inputStream, (int)fileLength));
|
|
} else {
|
|
StringBuffer sb = new StringBuffer("bad magic value:");
|
|
for (int i=0; i<8; i++) {
|
|
sb.append(" ");
|
|
sb.append(Hex.u1(magic[i]));
|
|
}
|
|
throw new RuntimeException(sb.toString());
|
|
}
|
|
} finally {
|
|
if (inputStream != null) {
|
|
inputStream.close();
|
|
}
|
|
if (zipFile != null) {
|
|
zipFile.close();
|
|
}
|
|
}
|
|
|
|
ReadContext readContext = new ReadContext();
|
|
|
|
HeaderItem.readFrom(in, 0, readContext);
|
|
|
|
//the map offset was set while reading in the header item
|
|
int mapOffset = readContext.getSectionOffset(ItemType.TYPE_MAP_LIST);
|
|
|
|
in.setCursor(mapOffset);
|
|
MapItem.readFrom(in, 0, readContext);
|
|
|
|
//the sections are ordered in such a way that the item types
|
|
Section sections[] = new Section[] {
|
|
StringDataSection,
|
|
StringIdsSection,
|
|
TypeIdsSection,
|
|
TypeListsSection,
|
|
ProtoIdsSection,
|
|
FieldIdsSection,
|
|
MethodIdsSection,
|
|
AnnotationsSection,
|
|
AnnotationSetsSection,
|
|
AnnotationSetRefListsSection,
|
|
AnnotationDirectoriesSection,
|
|
DebugInfoItemsSection,
|
|
CodeItemsSection,
|
|
ClassDataSection,
|
|
EncodedArraysSection,
|
|
ClassDefsSection
|
|
};
|
|
|
|
for (Section section: sections) {
|
|
if (section == null) {
|
|
continue;
|
|
}
|
|
|
|
if (skipInstructions && (section == CodeItemsSection || section == DebugInfoItemsSection)) {
|
|
continue;
|
|
}
|
|
|
|
int sectionOffset = readContext.getSectionOffset(section.ItemType);
|
|
if (sectionOffset > 0) {
|
|
int sectionSize = readContext.getSectionSize(section.ItemType);
|
|
in.setCursor(sectionOffset);
|
|
section.readFrom(sectionSize, in, readContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructs a new, blank dex file. Classes can be added to this dex file by calling
|
|
* the <code>Section.intern()</code> method of <code>ClassDefsSection</code>
|
|
*/
|
|
public DexFile() {
|
|
this(true, false);
|
|
}
|
|
|
|
/**
|
|
* Get the <code>Section</code> containing items of the same type as the given item
|
|
* @param item Get the <code>Section</code> that contains items of this type
|
|
* @param <T> The specific item subclass - inferred from the passed item
|
|
* @return the <code>Section</code> containing items of the same type as the given item
|
|
*/
|
|
public <T extends Item> Section<T> getSectionForItem(T item) {
|
|
return (Section<T>)sectionsByType[item.getItemType().SectionIndex];
|
|
}
|
|
|
|
/**
|
|
* Get the <code>Section</code> containing items of the given type
|
|
* @param itemType the type of item
|
|
* @return the <code>Section</code> containing items of the given type
|
|
*/
|
|
public Section getSectionForType(ItemType itemType) {
|
|
return sectionsByType[itemType.SectionIndex];
|
|
}
|
|
|
|
/**
|
|
* Get a boolean value indicating whether this dex file preserved any signed
|
|
* registers in the debug info as it read the dex file in. By default, the dex file
|
|
* doesn't check whether the registers are encoded as unsigned or signed values.
|
|
*
|
|
* This does *not* affect the actual register value that is read in. The value is
|
|
* read correctly regardless
|
|
*
|
|
* This does affect whether any signed registers will retain the same encoding or be
|
|
* forced to the (correct) unsigned encoding when the dex file is written back out.
|
|
*
|
|
* See the discussion about signed register values in the documentation for
|
|
* <code>DexFile</code>
|
|
* @return a boolean indicating whether this dex file preserved any signed registers
|
|
* as it was read in
|
|
*/
|
|
public boolean getPreserveSignedRegisters() {
|
|
return preserveSignedRegisters;
|
|
}
|
|
|
|
/**
|
|
* Get a boolean value indicating whether to skip any instructions in a code item while reading in the dex file.
|
|
* This is useful when you only need the information about the classes and their methods, for example, when
|
|
* loading the BOOTCLASSPATH jars in order to analyze a dex file
|
|
* @return a boolean value indicating whether to skip any instructions in a code item
|
|
*/
|
|
public boolean skipInstructions() {
|
|
return skipInstructions;
|
|
}
|
|
|
|
/**
|
|
* Get a boolean value indicating whether all items should be placed into a
|
|
* (possibly arbitrary) "canonical" ordering. If false, then only the items
|
|
* that must be ordered per the dex specification are sorted.
|
|
*
|
|
* When true, writing the dex file involves somewhat more overhead
|
|
*
|
|
* If both SortAllItems and Inplace are true, Inplace takes precedence
|
|
* @return a boolean value indicating whether all items should be sorted
|
|
*/
|
|
public boolean getSortAllItems() {
|
|
return this.sortAllItems;
|
|
}
|
|
|
|
/**
|
|
* Set a boolean value indicating whether all items should be placed into a
|
|
* (possibly arbitrary) "canonical" ordering. If false, then only the items
|
|
* that must be ordered per the dex specification are sorted.
|
|
*
|
|
* When true, writing the dex file involves somewhat more overhead
|
|
*
|
|
* If both SortAllItems and Inplace are true, Inplace takes precedence
|
|
* @param value a boolean value indicating whether all items should be sorted
|
|
*/
|
|
public void setSortAllItems(boolean value) {
|
|
this.sortAllItems = value;
|
|
}
|
|
|
|
/**
|
|
* @return a boolean value indicating whether this dex file was created by reading in an odex file
|
|
*/
|
|
public boolean isOdex() {
|
|
return this.isOdex;
|
|
}
|
|
|
|
/**
|
|
* @return an OdexDependencies object that contains the dependencies for this odex, or null if this
|
|
* DexFile represents a dex file instead of an odex file
|
|
*/
|
|
public OdexDependencies getOdexDependencies() {
|
|
return odexDependencies;
|
|
}
|
|
|
|
/**
|
|
* Get a boolean value indicating whether items in this dex file should be
|
|
* written back out "in-place", or whether the normal layout logic should be
|
|
* applied.
|
|
*
|
|
* This should only be used for a dex file that has been read from an existing
|
|
* dex file, and no modifications have been made to the dex file. Otherwise,
|
|
* there is a good chance that the resulting dex file will be invalid due to
|
|
* items that aren't placed correctly
|
|
*
|
|
* If both SortAllItems and Inplace are true, Inplace takes precedence
|
|
* @return a boolean value indicating whether items in this dex file should be
|
|
* written back out in-place.
|
|
*/
|
|
public boolean getInplace() {
|
|
return this.inplace;
|
|
}
|
|
|
|
/**
|
|
* @return the size of the file, in bytes
|
|
*/
|
|
public int getFileSize() {
|
|
return fileSize;
|
|
}
|
|
|
|
/**
|
|
* @return the size of the data section, in bytes
|
|
*/
|
|
public int getDataSize() {
|
|
return dataSize;
|
|
}
|
|
|
|
/**
|
|
* @return the offset where the data section begins
|
|
*/
|
|
public int getDataOffset() {
|
|
return dataOffset;
|
|
}
|
|
|
|
/**
|
|
* Set a boolean value indicating whether items in this dex file should be
|
|
* written back out "in-place", or whether the normal layout logic should be
|
|
* applied.
|
|
*
|
|
* This should only be used for a dex file that has been read from an existing
|
|
* dex file, and no modifications have been made to the dex file. Otherwise,
|
|
* there is a good chance that the resulting dex file will be invalid due to
|
|
* items that aren't placed correctly
|
|
*
|
|
* If both SortAllItems and Inplace are true, Inplace takes precedence
|
|
* @param value a boolean value indicating whether items in this dex file should be
|
|
* written back out in-place.
|
|
*/
|
|
public void setInplace(boolean value) {
|
|
this.inplace = value;
|
|
}
|
|
|
|
/**
|
|
* Get an array of Section objects that are sorted by offset.
|
|
* @return an array of Section objects that are sorted by offset.
|
|
*/
|
|
protected Section[] getOrderedSections() {
|
|
int sectionCount = 0;
|
|
|
|
for (Section section: sectionsByType) {
|
|
if (section != null && section.getItems().size() > 0) {
|
|
sectionCount++;
|
|
}
|
|
}
|
|
|
|
Section[] sections = new Section[sectionCount];
|
|
sectionCount = 0;
|
|
for (Section section: sectionsByType) {
|
|
if (section != null && section.getItems().size() > 0) {
|
|
sections[sectionCount++] = section;
|
|
}
|
|
}
|
|
|
|
Arrays.sort(sections, new Comparator<Section>() {
|
|
public int compare(Section a, Section b) {
|
|
return a.getOffset() - b.getOffset();
|
|
}
|
|
});
|
|
|
|
return sections;
|
|
}
|
|
|
|
/**
|
|
* This method should be called before writing a dex file. It sorts the sections
|
|
* as needed or as indicated by <code>getSortAllItems()</code> and <code>getInplace()</code>,
|
|
* and then performs a pass through all of the items, finalizing the position (i.e.
|
|
* index and/or offset) of each item in the dex file.
|
|
*
|
|
* This step is needed primarily so that the indexes and offsets of all indexed and
|
|
* offsetted items are available when writing references to those items elsewhere.
|
|
*/
|
|
public void place() {
|
|
int offset = HeaderItem.placeAt(0, 0);
|
|
|
|
int sectionsPosition = 0;
|
|
Section[] sections;
|
|
if (this.inplace) {
|
|
sections = this.getOrderedSections();
|
|
} else {
|
|
sections = new Section[indexedSections.length + offsettedSections.length];
|
|
System.arraycopy(indexedSections, 0, sections, 0, indexedSections.length);
|
|
System.arraycopy(offsettedSections, 0, sections, indexedSections.length, offsettedSections.length);
|
|
}
|
|
|
|
while (sectionsPosition < sections.length && sections[sectionsPosition].ItemType.isIndexedItem()) {
|
|
Section section = sections[sectionsPosition];
|
|
if (!this.inplace) {
|
|
section.sortSection();
|
|
}
|
|
|
|
offset = section.placeAt(offset);
|
|
|
|
sectionsPosition++;
|
|
}
|
|
|
|
dataOffset = offset;
|
|
|
|
while (sectionsPosition < sections.length) {
|
|
Section section = sections[sectionsPosition];
|
|
if (this.sortAllItems && !this.inplace) {
|
|
section.sortSection();
|
|
}
|
|
offset = section.placeAt(offset);
|
|
|
|
sectionsPosition++;
|
|
}
|
|
|
|
offset = AlignmentUtils.alignOffset(offset, ItemType.TYPE_MAP_LIST.ItemAlignment);
|
|
offset = MapItem.placeAt(offset, 0);
|
|
|
|
fileSize = offset;
|
|
dataSize = offset - dataOffset;
|
|
}
|
|
|
|
/**
|
|
* Writes the dex file to the give <code>AnnotatedOutput</code> object. If
|
|
* <code>out.Annotates()</code> is true, then annotations that document the format
|
|
* of the dex file are written.
|
|
*
|
|
* You must call <code>place()</code> on this dex file, before calling this method
|
|
* @param out the AnnotatedOutput object to write the dex file and annotations to
|
|
*
|
|
* After calling this method, you should call <code>calcSignature()</code> and
|
|
* then <code>calcChecksum()</code> on the resulting byte array, to calculate the
|
|
* signature and checksum in the header
|
|
*/
|
|
public void writeTo(AnnotatedOutput out) {
|
|
|
|
out.annotate(0, "-----------------------------");
|
|
out.annotate(0, "header item");
|
|
out.annotate(0, "-----------------------------");
|
|
out.annotate(0, " ");
|
|
HeaderItem.writeTo(out);
|
|
|
|
out.annotate(0, " ");
|
|
|
|
int sectionsPosition = 0;
|
|
Section[] sections;
|
|
if (this.inplace) {
|
|
sections = this.getOrderedSections();
|
|
} else {
|
|
sections = new Section[indexedSections.length + offsettedSections.length];
|
|
System.arraycopy(indexedSections, 0, sections, 0, indexedSections.length);
|
|
System.arraycopy(offsettedSections, 0, sections, indexedSections.length, offsettedSections.length);
|
|
}
|
|
|
|
while (sectionsPosition < sections.length) {
|
|
sections[sectionsPosition].writeTo(out);
|
|
sectionsPosition++;
|
|
}
|
|
|
|
out.alignTo(MapItem.getItemType().ItemAlignment);
|
|
|
|
out.annotate(0, " ");
|
|
out.annotate(0, "-----------------------------");
|
|
out.annotate(0, "map item");
|
|
out.annotate(0, "-----------------------------");
|
|
out.annotate(0, " ");
|
|
MapItem.writeTo(out);
|
|
}
|
|
|
|
public final HeaderItem HeaderItem = new HeaderItem(this);
|
|
public final MapItem MapItem = new MapItem(this);
|
|
|
|
/**
|
|
* The <code>IndexedSection</code> containing <code>StringIdItem</code> items
|
|
*/
|
|
public final IndexedSection<StringIdItem> StringIdsSection =
|
|
new IndexedSection<StringIdItem>(this, ItemType.TYPE_STRING_ID_ITEM);
|
|
|
|
/**
|
|
* The <code>IndexedSection</code> containing <code>TypeIdItem</code> items
|
|
*/
|
|
public final IndexedSection<TypeIdItem> TypeIdsSection =
|
|
new IndexedSection<TypeIdItem>(this, ItemType.TYPE_TYPE_ID_ITEM);
|
|
|
|
/**
|
|
* The <code>IndexedSection</code> containing <code>ProtoIdItem</code> items
|
|
*/
|
|
public final IndexedSection<ProtoIdItem> ProtoIdsSection =
|
|
new IndexedSection<ProtoIdItem>(this, ItemType.TYPE_PROTO_ID_ITEM);
|
|
|
|
/**
|
|
* The <code>IndexedSection</code> containing <code>FieldIdItem</code> items
|
|
*/
|
|
public final IndexedSection<FieldIdItem> FieldIdsSection =
|
|
new IndexedSection<FieldIdItem>(this, ItemType.TYPE_FIELD_ID_ITEM);
|
|
|
|
/**
|
|
* The <code>IndexedSection</code> containing <code>MethodIdItem</code> items
|
|
*/
|
|
public final IndexedSection<MethodIdItem> MethodIdsSection =
|
|
new IndexedSection<MethodIdItem>(this, ItemType.TYPE_METHOD_ID_ITEM);
|
|
|
|
/**
|
|
* The <code>IndexedSection</code> containing <code>ClassDefItem</code> items
|
|
*/
|
|
public final IndexedSection<ClassDefItem> ClassDefsSection =
|
|
new IndexedSection<ClassDefItem>(this, ItemType.TYPE_CLASS_DEF_ITEM) {
|
|
|
|
public int placeAt(int offset) {
|
|
if (dexFile.getInplace()) {
|
|
return super.placeAt(offset);
|
|
}
|
|
|
|
int ret = ClassDefItem.placeClassDefItems(this, offset);
|
|
|
|
Collections.sort(this.items, new Comparator<ClassDefItem>() {
|
|
|
|
public int compare(ClassDefItem a, ClassDefItem b) {
|
|
return a.getOffset() - b.getOffset();
|
|
}
|
|
});
|
|
|
|
this.offset = items.get(0).getOffset();
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The <code>OffsettedSection</code> containing <code>TypeListItem</code> items
|
|
*/
|
|
public final OffsettedSection<TypeListItem> TypeListsSection =
|
|
new OffsettedSection<TypeListItem>(this, ItemType.TYPE_TYPE_LIST);
|
|
|
|
/**
|
|
* The <code>OffsettedSection</code> containing <code>AnnotationSetRefList</code> items
|
|
*/
|
|
public final OffsettedSection<AnnotationSetRefList> AnnotationSetRefListsSection =
|
|
new OffsettedSection<AnnotationSetRefList>(this, ItemType.TYPE_ANNOTATION_SET_REF_LIST);
|
|
|
|
/**
|
|
* The <code>OffsettedSection</code> containing <code>AnnotationSetItem</code> items
|
|
*/
|
|
public final OffsettedSection<AnnotationSetItem> AnnotationSetsSection =
|
|
new OffsettedSection<AnnotationSetItem>(this, ItemType.TYPE_ANNOTATION_SET_ITEM);
|
|
|
|
/**
|
|
* The <code>OffsettedSection</code> containing <code>ClassDataItem</code> items
|
|
*/
|
|
public final OffsettedSection<ClassDataItem> ClassDataSection =
|
|
new OffsettedSection<ClassDataItem>(this, ItemType.TYPE_CLASS_DATA_ITEM);
|
|
|
|
/**
|
|
* The <code>OffsettedSection</code> containing <code>CodeItem</code> items
|
|
*/
|
|
public final OffsettedSection<CodeItem> CodeItemsSection =
|
|
new OffsettedSection<CodeItem>(this, ItemType.TYPE_CODE_ITEM);
|
|
|
|
/**
|
|
* The <code>OffsettedSection</code> containing <code>StringDataItem</code> items
|
|
*/
|
|
public final OffsettedSection<StringDataItem> StringDataSection =
|
|
new OffsettedSection<StringDataItem>(this, ItemType.TYPE_STRING_DATA_ITEM);
|
|
|
|
/**
|
|
* The <code>OffsettedSection</code> containing <code>DebugInfoItem</code> items
|
|
*/
|
|
public final OffsettedSection<DebugInfoItem> DebugInfoItemsSection =
|
|
new OffsettedSection<DebugInfoItem>(this, ItemType.TYPE_DEBUG_INFO_ITEM);
|
|
|
|
/**
|
|
* The <code>OffsettedSection</code> containing <code>AnnotationItem</code> items
|
|
*/
|
|
public final OffsettedSection<AnnotationItem> AnnotationsSection =
|
|
new OffsettedSection<AnnotationItem>(this, ItemType.TYPE_ANNOTATION_ITEM);
|
|
|
|
/**
|
|
* The <code>OffsettedSection</code> containing <code>EncodedArrayItem</code> items
|
|
*/
|
|
public final OffsettedSection<EncodedArrayItem> EncodedArraysSection =
|
|
new OffsettedSection<EncodedArrayItem>(this, ItemType.TYPE_ENCODED_ARRAY_ITEM);
|
|
|
|
/**
|
|
* The <code>OffsettedSection</code> containing <code>AnnotationDirectoryItem</code> items
|
|
*/
|
|
public final OffsettedSection<AnnotationDirectoryItem> AnnotationDirectoriesSection =
|
|
new OffsettedSection<AnnotationDirectoryItem>(this, ItemType.TYPE_ANNOTATIONS_DIRECTORY_ITEM);
|
|
|
|
|
|
/**
|
|
* Calculates the signature for the dex file in the given byte array,
|
|
* and then writes the signature to the appropriate location in the header
|
|
* containing in the array
|
|
*
|
|
* @param bytes non-null; the bytes of the file
|
|
*/
|
|
public static void calcSignature(byte[] bytes) {
|
|
MessageDigest md;
|
|
|
|
try {
|
|
md = MessageDigest.getInstance("SHA-1");
|
|
} catch (NoSuchAlgorithmException ex) {
|
|
throw new RuntimeException(ex);
|
|
}
|
|
|
|
md.update(bytes, 32, bytes.length - 32);
|
|
|
|
try {
|
|
int amt = md.digest(bytes, 12, 20);
|
|
if (amt != 20) {
|
|
throw new RuntimeException("unexpected digest write: " + amt +
|
|
" bytes");
|
|
}
|
|
} catch (DigestException ex) {
|
|
throw new RuntimeException(ex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates the checksum for the <code>.dex</code> file in the
|
|
* given array, and modify the array to contain it.
|
|
*
|
|
* @param bytes non-null; the bytes of the file
|
|
*/
|
|
public static void calcChecksum(byte[] bytes) {
|
|
Adler32 a32 = new Adler32();
|
|
|
|
a32.update(bytes, 12, bytes.length - 12);
|
|
|
|
int sum = (int) a32.getValue();
|
|
|
|
bytes[8] = (byte) sum;
|
|
bytes[9] = (byte) (sum >> 8);
|
|
bytes[10] = (byte) (sum >> 16);
|
|
bytes[11] = (byte) (sum >> 24);
|
|
}
|
|
|
|
public static class NoClassesDexException extends ExceptionWithContext {
|
|
public NoClassesDexException(String message) {
|
|
super(message);
|
|
}
|
|
}
|
|
} |