Merge branch 'v2.2_WIP'

This commit is contained in:
Ben Gruver 2016-10-02 16:41:52 -07:00
commit 2d0f6254b1
118 changed files with 6793 additions and 2627 deletions

View File

@ -41,8 +41,8 @@ buildscript {
dependencies {
compile project(':util')
compile project(':dexlib2')
compile depends.commons_cli
compile depends.guava
compile depends.jcommander
testCompile depends.junit
testCompile project(':smali')
@ -59,7 +59,7 @@ task fatJar(type: Jar) {
classifier = 'fat'
manifest {
attributes('Main-Class': 'org.jf.baksmali.main')
attributes('Main-Class': 'org.jf.baksmali.Main')
}
doLast {
@ -92,7 +92,7 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
dontobfuscate
dontoptimize
keep 'public class org.jf.baksmali.main { public static void main(java.lang.String[]); }'
keep 'public class org.jf.baksmali.Main { public static void main(java.lang.String[]); }'
keepclassmembers 'enum * { public static **[] values(); public static ** valueOf(java.lang.String); }'
dontwarn 'com.google.common.**'
@ -100,3 +100,17 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
}
tasks.getByPath(':release').dependsOn(proguard)
task fastbuild(dependsOn: build) {
}
task fb(dependsOn: fastbuild) {
}
tasks.getByPath('javadoc').onlyIf({
!gradle.taskGraph.hasTask(fastbuild)
})
tasks.getByPath('test').onlyIf({
!gradle.taskGraph.hasTask(fastbuild)
})

View File

@ -28,7 +28,7 @@
package org.jf.baksmali.Adaptors;
import org.jf.baksmali.baksmaliOptions;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.util.IndentingWriter;
import javax.annotation.Nonnull;
@ -42,7 +42,7 @@ public class CatchMethodItem extends MethodItem {
private final LabelMethodItem tryEndLabel;
private final LabelMethodItem handlerLabel;
public CatchMethodItem(@Nonnull baksmaliOptions options, @Nonnull MethodDefinition.LabelCache labelCache,
public CatchMethodItem(@Nonnull BaksmaliOptions options, @Nonnull MethodDefinition.LabelCache labelCache,
int codeAddress, @Nullable String exceptionType, int startAddress, int endAddress,
int handlerAddress) {
super(codeAddress);

View File

@ -28,8 +28,7 @@
package org.jf.baksmali.Adaptors;
import com.google.common.collect.Lists;
import org.jf.baksmali.baksmaliOptions;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.dexbacked.DexBackedClassDef;
import org.jf.dexlib2.dexbacked.DexBackedDexFile.InvalidItemIndex;
@ -46,16 +45,16 @@ import java.io.IOException;
import java.util.*;
public class ClassDefinition {
@Nonnull public final baksmaliOptions options;
@Nonnull public final BaksmaliOptions options;
@Nonnull public final ClassDef classDef;
@Nonnull private final HashSet<String> fieldsSetInStaticConstructor;
protected boolean validationErrors;
public ClassDefinition(@Nonnull baksmaliOptions options, @Nonnull ClassDef classDef) {
public ClassDefinition(@Nonnull BaksmaliOptions options, @Nonnull ClassDef classDef) {
this.options = options;
this.classDef = classDef;
fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor();
fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor(classDef);
}
public boolean hadValidationErrors() {
@ -63,7 +62,7 @@ public class ClassDefinition {
}
@Nonnull
private HashSet<String> findFieldsSetInStaticConstructor() {
private static HashSet<String> findFieldsSetInStaticConstructor(@Nonnull ClassDef classDef) {
HashSet<String> fieldsSetInStaticConstructor = new HashSet<String>();
for (Method method: classDef.getDirectMethods()) {
@ -166,7 +165,7 @@ public class ClassDefinition {
writer.write("# annotations\n");
String containingClass = null;
if (options.useImplicitReferences) {
if (options.implicitReferences) {
containingClass = classDef.getType();
}

View File

@ -28,14 +28,14 @@
package org.jf.baksmali.Adaptors;
import org.jf.baksmali.baksmaliOptions;
import org.jf.baksmali.BaksmaliOptions;
import javax.annotation.Nonnull;
public class EndTryLabelMethodItem extends LabelMethodItem {
private int endTryAddress;
public EndTryLabelMethodItem(@Nonnull baksmaliOptions options, int codeAddress, int endTryAddress) {
public EndTryLabelMethodItem(@Nonnull BaksmaliOptions options, int codeAddress, int endTryAddress) {
super(options, codeAddress, "try_end_");
this.endTryAddress = endTryAddress;
}

View File

@ -29,7 +29,7 @@
package org.jf.baksmali.Adaptors;
import org.jf.baksmali.Adaptors.EncodedValue.EncodedValueAdaptor;
import org.jf.baksmali.baksmaliOptions;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.iface.Annotation;
import org.jf.dexlib2.iface.Field;
@ -41,7 +41,7 @@ import java.io.IOException;
import java.util.Collection;
public class FieldDefinition {
public static void writeTo(baksmaliOptions options, IndentingWriter writer, Field field,
public static void writeTo(BaksmaliOptions options, IndentingWriter writer, Field field,
boolean setInStaticConstructor) throws IOException {
EncodedValue initialValue = field.getInitialValue();
int accessFlags = field.getAccessFlags();
@ -68,7 +68,7 @@ public class FieldDefinition {
writer.write(" = ");
String containingClass = null;
if (options.useImplicitReferences) {
if (options.implicitReferences) {
containingClass = field.getDefiningClass();
}
@ -82,7 +82,7 @@ public class FieldDefinition {
writer.indent(4);
String containingClass = null;
if (options.useImplicitReferences) {
if (options.implicitReferences) {
containingClass = field.getDefiningClass();
}

View File

@ -32,7 +32,7 @@ import org.jf.baksmali.Adaptors.MethodDefinition;
import org.jf.baksmali.Adaptors.MethodDefinition.InvalidSwitchPayload;
import org.jf.baksmali.Adaptors.MethodItem;
import org.jf.baksmali.Renderers.LongRenderer;
import org.jf.baksmali.baksmaliOptions;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.Opcode;
import org.jf.dexlib2.ReferenceType;
import org.jf.dexlib2.VerificationError;
@ -67,7 +67,7 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
}
private boolean isAllowedOdex(@Nonnull Opcode opcode) {
baksmaliOptions options = methodDef.classDef.options;
BaksmaliOptions options = methodDef.classDef.options;
if (options.allowOdex) {
return true;
}
@ -110,7 +110,7 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
if (instruction instanceof ReferenceInstruction) {
ReferenceInstruction referenceInstruction = (ReferenceInstruction)instruction;
String classContext = null;
if (methodDef.classDef.options.useImplicitReferences) {
if (methodDef.classDef.options.implicitReferences) {
classContext = methodDef.method.getDefiningClass();
}

View File

@ -30,7 +30,7 @@ package org.jf.baksmali.Adaptors.Format;
import org.jf.baksmali.Adaptors.LabelMethodItem;
import org.jf.baksmali.Adaptors.MethodDefinition;
import org.jf.baksmali.baksmaliOptions;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.Opcode;
import org.jf.dexlib2.iface.instruction.OffsetInstruction;
import org.jf.util.IndentingWriter;
@ -41,7 +41,7 @@ import java.io.IOException;
public class OffsetInstructionFormatMethodItem extends InstructionMethodItem<OffsetInstruction> {
protected LabelMethodItem label;
public OffsetInstructionFormatMethodItem(@Nonnull baksmaliOptions options, @Nonnull MethodDefinition methodDef,
public OffsetInstructionFormatMethodItem(@Nonnull BaksmaliOptions options, @Nonnull MethodDefinition methodDef,
int codeAddress, OffsetInstruction instruction) {
super(methodDef, codeAddress, instruction);

View File

@ -28,18 +28,18 @@
package org.jf.baksmali.Adaptors;
import org.jf.baksmali.baksmaliOptions;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.util.IndentingWriter;
import javax.annotation.Nonnull;
import java.io.IOException;
public class LabelMethodItem extends MethodItem {
private final baksmaliOptions options;
private final BaksmaliOptions options;
private final String labelPrefix;
private int labelSequence;
public LabelMethodItem(@Nonnull baksmaliOptions options, int codeAddress, @Nonnull String labelPrefix) {
public LabelMethodItem(@Nonnull BaksmaliOptions options, int codeAddress, @Nonnull String labelPrefix) {
super(codeAddress);
this.options = options;
this.labelPrefix = labelPrefix;
@ -76,7 +76,7 @@ public class LabelMethodItem extends MethodItem {
public boolean writeTo(IndentingWriter writer) throws IOException {
writer.write(':');
writer.write(labelPrefix);
if (options.useSequentialLabels) {
if (options.sequentialLabels) {
writer.printUnsignedLongAsHex(labelSequence);
} else {
writer.printUnsignedLongAsHex(this.getLabelAddress());

View File

@ -32,7 +32,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.jf.baksmali.Adaptors.Debug.DebugMethodItem;
import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory;
import org.jf.baksmali.baksmaliOptions;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.Format;
import org.jf.dexlib2.Opcode;
@ -163,7 +163,7 @@ public class MethodDefinition {
}
public static void writeEmptyMethodTo(IndentingWriter writer, Method method,
baksmaliOptions options) throws IOException {
BaksmaliOptions options) throws IOException {
writer.write(".method ");
writeAccessFlags(writer, method.getAccessFlags());
writer.write(method.getName());
@ -180,7 +180,7 @@ public class MethodDefinition {
writeParameters(writer, method, methodParameters, options);
String containingClass = null;
if (options.useImplicitReferences) {
if (options.implicitReferences) {
containingClass = method.getDefiningClass();
}
AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass);
@ -212,7 +212,7 @@ public class MethodDefinition {
writer.write('\n');
writer.indent(4);
if (classDef.options.useLocalsDirective) {
if (classDef.options.localsDirective) {
writer.write(".locals ");
writer.printSignedIntAsDec(methodImpl.getRegisterCount() - parameterRegisterCount);
} else {
@ -228,7 +228,7 @@ public class MethodDefinition {
}
String containingClass = null;
if (classDef.options.useImplicitReferences) {
if (classDef.options.implicitReferences) {
containingClass = method.getDefiningClass();
}
AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass);
@ -313,18 +313,18 @@ public class MethodDefinition {
private static void writeParameters(IndentingWriter writer, Method method,
List<? extends MethodParameter> parameters,
baksmaliOptions options) throws IOException {
BaksmaliOptions options) throws IOException {
boolean isStatic = AccessFlags.STATIC.isSet(method.getAccessFlags());
int registerNumber = isStatic?0:1;
for (MethodParameter parameter: parameters) {
String parameterType = parameter.getType();
String parameterName = parameter.getName();
Collection<? extends Annotation> annotations = parameter.getAnnotations();
if ((options.outputDebugInfo && parameterName != null) || annotations.size() != 0) {
if ((options.debugInfo && parameterName != null) || annotations.size() != 0) {
writer.write(".param p");
writer.printSignedIntAsDec(registerNumber);
if (parameterName != null && options.outputDebugInfo) {
if (parameterName != null && options.debugInfo) {
writer.write(", ");
ReferenceFormatter.writeStringReference(writer, parameterName);
}
@ -335,7 +335,7 @@ public class MethodDefinition {
writer.indent(4);
String containingClass = null;
if (options.useImplicitReferences) {
if (options.implicitReferences) {
containingClass = method.getDefiningClass();
}
AnnotationFormatter.writeTo(writer, annotations, containingClass);
@ -374,11 +374,11 @@ public class MethodDefinition {
}
addTries(methodItems);
if (classDef.options.outputDebugInfo) {
if (classDef.options.debugInfo) {
addDebugInfo(methodItems);
}
if (classDef.options.useSequentialLabels) {
if (classDef.options.sequentialLabels) {
setLabelSequentialNumbers();
}
@ -415,7 +415,7 @@ public class MethodDefinition {
methodItems.add(new BlankMethodItem(currentCodeAddress));
}
if (classDef.options.addCodeOffsets) {
if (classDef.options.codeOffsets) {
methodItems.add(new MethodItem(currentCodeAddress) {
@Override
@ -432,7 +432,7 @@ public class MethodDefinition {
});
}
if (!classDef.options.noAccessorComments && (instruction instanceof ReferenceInstruction)) {
if (classDef.options.accessorComments && (instruction instanceof ReferenceInstruction)) {
Opcode opcode = instruction.getOpcode();
if (opcode.referenceType == ReferenceType.METHOD) {
@ -493,7 +493,7 @@ public class MethodDefinition {
methodItems.add(new BlankMethodItem(currentCodeAddress));
}
if (classDef.options.addCodeOffsets) {
if (classDef.options.codeOffsets) {
methodItems.add(new MethodItem(currentCodeAddress) {
@Override
@ -597,7 +597,7 @@ public class MethodDefinition {
@Nullable
private String getContainingClassForImplicitReference() {
if (classDef.options.useImplicitReferences) {
if (classDef.options.implicitReferences) {
return classDef.classDef.getType();
}
return null;

View File

@ -28,7 +28,7 @@
package org.jf.baksmali.Adaptors;
import org.jf.baksmali.baksmaliOptions;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.analysis.AnalyzedInstruction;
import org.jf.dexlib2.analysis.RegisterType;
import org.jf.util.IndentingWriter;
@ -60,12 +60,12 @@ public class PostInstructionRegisterInfoMethodItem extends MethodItem {
int registerCount = analyzedInstruction.getRegisterCount();
BitSet registers = new BitSet(registerCount);
if ((registerInfo & baksmaliOptions.ALL) != 0) {
if ((registerInfo & BaksmaliOptions.ALL) != 0) {
registers.set(0, registerCount);
} else {
if ((registerInfo & baksmaliOptions.ALLPOST) != 0) {
if ((registerInfo & BaksmaliOptions.ALLPOST) != 0) {
registers.set(0, registerCount);
} else if ((registerInfo & baksmaliOptions.DEST) != 0) {
} else if ((registerInfo & BaksmaliOptions.DEST) != 0) {
addDestRegs(registers, registerCount);
}
}

View File

@ -28,7 +28,7 @@
package org.jf.baksmali.Adaptors;
import org.jf.baksmali.baksmaliOptions;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.analysis.AnalyzedInstruction;
import org.jf.dexlib2.analysis.MethodAnalyzer;
import org.jf.dexlib2.analysis.RegisterType;
@ -68,29 +68,29 @@ public class PreInstructionRegisterInfoMethodItem extends MethodItem {
BitSet registers = new BitSet(registerCount);
BitSet mergeRegisters = null;
if ((registerInfo & baksmaliOptions.ALL) != 0) {
if ((registerInfo & BaksmaliOptions.ALL) != 0) {
registers.set(0, registerCount);
} else {
if ((registerInfo & baksmaliOptions.ALLPRE) != 0) {
if ((registerInfo & BaksmaliOptions.ALLPRE) != 0) {
registers.set(0, registerCount);
} else {
if ((registerInfo & baksmaliOptions.ARGS) != 0) {
if ((registerInfo & BaksmaliOptions.ARGS) != 0) {
addArgsRegs(registers);
}
if ((registerInfo & baksmaliOptions.MERGE) != 0) {
if ((registerInfo & BaksmaliOptions.MERGE) != 0) {
if (analyzedInstruction.isBeginningInstruction()) {
addParamRegs(registers, registerCount);
}
mergeRegisters = new BitSet(registerCount);
addMergeRegs(mergeRegisters, registerCount);
} else if ((registerInfo & baksmaliOptions.FULLMERGE) != 0 &&
} else if ((registerInfo & BaksmaliOptions.FULLMERGE) != 0 &&
(analyzedInstruction.isBeginningInstruction())) {
addParamRegs(registers, registerCount);
}
}
}
if ((registerInfo & baksmaliOptions.FULLMERGE) != 0) {
if ((registerInfo & BaksmaliOptions.FULLMERGE) != 0) {
if (mergeRegisters == null) {
mergeRegisters = new BitSet(registerCount);
addMergeRegs(mergeRegisters, registerCount);

View File

@ -28,7 +28,7 @@
package org.jf.baksmali.Adaptors;
import org.jf.baksmali.baksmaliOptions;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.util.IndentingWriter;
import javax.annotation.Nonnull;
@ -38,11 +38,11 @@ import java.io.IOException;
* This class contains the logic used for formatting registers
*/
public class RegisterFormatter {
@Nonnull public final baksmaliOptions options;
@Nonnull public final BaksmaliOptions options;
public final int registerCount;
public final int parameterRegisterCount;
public RegisterFormatter(@Nonnull baksmaliOptions options, int registerCount, int parameterRegisterCount) {
public RegisterFormatter(@Nonnull BaksmaliOptions options, int registerCount, int parameterRegisterCount) {
this.options = options;
this.registerCount = registerCount;
this.parameterRegisterCount = parameterRegisterCount;
@ -58,7 +58,7 @@ public class RegisterFormatter {
* @param lastRegister the last register in the range
*/
public void writeRegisterRange(IndentingWriter writer, int startRegister, int lastRegister) throws IOException {
if (!options.noParameterRegisters) {
if (options.parameterRegisters) {
assert startRegister <= lastRegister;
if (startRegister >= registerCount - parameterRegisterCount) {
@ -86,7 +86,7 @@ public class RegisterFormatter {
* @param register the register number
*/
public void writeTo(IndentingWriter writer, int register) throws IOException {
if (!options.noParameterRegisters) {
if (options.parameterRegisters) {
if (register >= registerCount - parameterRegisterCount) {
writer.write('p');
writer.printSignedIntAsDec((register - (registerCount - parameterRegisterCount)));

View File

@ -0,0 +1,137 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.Parameter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.analysis.ClassPathResolver;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.iface.DexFile;
import org.jf.util.jcommander.ColonParameterSplitter;
import org.jf.util.jcommander.ExtendedParameter;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class AnalysisArguments {
@Parameter(names = {"-a", "--api"},
description = "The numeric api level of the file being disassembled.")
@ExtendedParameter(argumentNames = "api")
public int apiLevel = 15;
@Parameter(names = {"-b", "--bootclasspath", "--bcp"},
description = "A colon separated list of the files to include in the bootclasspath when analyzing the " +
"dex file. If not specified, baksmali will attempt to choose an " +
"appropriate default. When analyzing oat files, this can simply be the path to the device's " +
"boot.oat file. A single empty string can be used to specify that an empty bootclasspath should " +
"be used. (e.g. --bootclasspath \"\") See baksmali help classpath for more information.",
splitter = ColonParameterSplitter.class)
@ExtendedParameter(argumentNames = "classpath")
public List<String> bootClassPath = null;
@Parameter(names = {"-c", "--classpath", "--cp"},
description = "A colon separated list of additional files to include in the classpath when analyzing the " +
"dex file. These will be added to the classpath after any bootclasspath entries.",
splitter = ColonParameterSplitter.class)
@ExtendedParameter(argumentNames = "classpath")
public List<String> classPath = Lists.newArrayList();
@Parameter(names = {"-d", "--classpath-dir", "--cpd", "--dir"},
description = "A directory to search for classpath files. This option can be used multiple times to " +
"specify multiple directories to search. They will be searched in the order they are provided.")
@ExtendedParameter(argumentNames = "dir")
public List<String> classPathDirectories = null;
public static class CheckPackagePrivateArgument {
@Parameter(names = {"--check-package-private-access", "--package-private", "--checkpp", "--pp"},
description = "Use the package-private access check when calculating vtable indexes. This is enabled " +
"by default for oat files. For odex files, this is only needed for odexes from 4.2.0. It " +
"was reverted in 4.2.1.")
public boolean checkPackagePrivateAccess = false;
}
@Nonnull
public ClassPath loadClassPathForDexFile(@Nonnull File dexFileDir, @Nonnull DexFile dexFile,
boolean checkPackagePrivateAccess) throws IOException {
return loadClassPathForDexFile(dexFileDir, dexFile, checkPackagePrivateAccess, 0);
}
@Nonnull
public ClassPath loadClassPathForDexFile(@Nonnull File dexFileDir, @Nonnull DexFile dexFile,
boolean checkPackagePrivateAccess, int oatVersion)
throws IOException {
ClassPathResolver resolver;
if (dexFile instanceof OatDexFile) {
checkPackagePrivateAccess = true;
}
if (classPathDirectories == null || classPathDirectories.size() == 0) {
classPathDirectories = Lists.newArrayList(dexFileDir.getPath());
}
List<String> filteredClassPathDirectories = Lists.newArrayList();
if (classPathDirectories != null) {
for (String dir: classPathDirectories) {
File file = new File(dir);
if (!file.exists()) {
System.err.println(String.format("Warning: directory %s does not exist. Ignoring.", dir));
} else if (!file.isDirectory()) {
System.err.println(String.format("Warning: %s is not a directory. Ignoring.", dir));
} else {
filteredClassPathDirectories.add(dir);
}
}
}
if (bootClassPath == null) {
// TODO: we should be able to get the api from the Opcodes object associated with the dexFile..
// except that the oat version -> api mapping doesn't fully work yet
resolver = new ClassPathResolver(filteredClassPathDirectories, classPath, dexFile, apiLevel);
} else if (bootClassPath.size() == 1 && bootClassPath.get(0).length() == 0) {
// --bootclasspath "" is a special case, denoting that no bootclasspath should be used
resolver = new ClassPathResolver(
ImmutableList.<String>of(), ImmutableList.<String>of(), classPath, dexFile);
} else {
resolver = new ClassPathResolver(filteredClassPathDirectories, bootClassPath, classPath, dexFile);
}
if (oatVersion == 0 && dexFile instanceof OatDexFile) {
oatVersion = ((OatDexFile)dexFile).getContainer().getOatVersion();
}
return new ClassPath(resolver.getResolvedClassProviders(), checkPackagePrivateAccess, oatVersion);
}
}

View File

@ -28,105 +28,20 @@
package org.jf.baksmali;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.analysis.CustomInlineMethodResolver;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.util.SyntheticAccessorResolver;
import org.jf.util.ClassFileNameHandler;
import org.jf.util.IndentingWriter;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.*;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.*;
public class baksmali {
public static boolean disassembleDexFile(DexFile dexFile, final baksmaliOptions options) {
if (options.registerInfo != 0 || options.deodex || options.normalizeVirtualMethods) {
try {
Iterable<String> extraClassPathEntries;
if (options.extraClassPathEntries != null) {
extraClassPathEntries = options.extraClassPathEntries;
} else {
extraClassPathEntries = ImmutableList.of();
}
options.classPath = ClassPath.fromClassPath(options.bootClassPathDirs,
Iterables.concat(options.bootClassPathEntries, extraClassPathEntries), dexFile,
options.apiLevel, options.checkPackagePrivateAccess, options.experimental);
if (options.customInlineDefinitions != null) {
options.inlineResolver = new CustomInlineMethodResolver(options.classPath,
options.customInlineDefinitions);
}
} catch (Exception ex) {
System.err.println("\n\nError occurred while loading boot class path files. Aborting.");
ex.printStackTrace(System.err);
return false;
}
}
if (options.resourceIdFileEntries != null) {
class PublicHandler extends DefaultHandler {
String prefix = null;
public PublicHandler(String prefix) {
super();
this.prefix = prefix;
}
public void startElement(String uri, String localName,
String qName, Attributes attr) throws SAXException {
if (qName.equals("public")) {
String type = attr.getValue("type");
String name = attr.getValue("name").replace('.', '_');
Integer public_key = Integer.decode(attr.getValue("id"));
String public_val = new StringBuffer()
.append(prefix)
.append(".")
.append(type)
.append(".")
.append(name)
.toString();
options.resourceIds.put(public_key, public_val);
}
}
};
for (Entry<String,String> entry: options.resourceIdFileEntries.entrySet()) {
try {
SAXParser saxp = SAXParserFactory.newInstance().newSAXParser();
String prefix = entry.getValue();
saxp.parse(entry.getKey(), new PublicHandler(prefix));
} catch (ParserConfigurationException e) {
continue;
} catch (SAXException e) {
continue;
} catch (IOException e) {
continue;
}
}
}
File outputDirectoryFile = new File(options.outputDirectory);
if (!outputDirectoryFile.exists()) {
if (!outputDirectoryFile.mkdirs()) {
System.err.println("Can't create the output directory " + options.outputDirectory);
return false;
}
}
public class Baksmali {
public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options) {
//sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file
//name collisions, then we'll use the same name for each class, if the dex file goes through multiple
@ -134,13 +49,9 @@ public class baksmali {
//may still change of course
List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());
if (!options.noAccessorComments) {
options.syntheticAccessorResolver = new SyntheticAccessorResolver(dexFile.getOpcodes(), classDefs);
}
final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDir, ".smali");
final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali");
ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
ExecutorService executor = Executors.newFixedThreadPool(jobs);
List<Future<Boolean>> tasks = Lists.newArrayList();
for (final ClassDef classDef: classDefs) {
@ -174,7 +85,7 @@ public class baksmali {
}
private static boolean disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler,
baksmaliOptions options) {
BaksmaliOptions options) {
/**
* The path for the disassembly file is based on the package name
* The class descriptor will look something like:

View File

@ -31,19 +31,35 @@
package org.jf.baksmali;
import com.google.common.collect.Lists;
import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.analysis.InlineMethodResolver;
import org.jf.dexlib2.util.SyntheticAccessorResolver;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.annotation.Nullable;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;
import java.util.Arrays;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class baksmaliOptions {
public class BaksmaliOptions {
public int apiLevel = 15;
public boolean parameterRegisters = true;
public boolean localsDirective = false;
public boolean sequentialLabels = false;
public boolean debugInfo = true;
public boolean codeOffsets = false;
public boolean accessorComments = true;
public boolean allowOdex = false;
public boolean deodex = false;
public boolean implicitReferences = false;
public boolean normalizeVirtualMethods = false;
// register info values
public static final int ALL = 1;
public static final int ALLPRE = 2;
@ -53,56 +69,40 @@ public class baksmaliOptions {
public static final int MERGE = 32;
public static final int FULLMERGE = 64;
public int apiLevel = 15;
public String outputDirectory = "out";
@Nullable public String dexEntry = null;
public List<String> bootClassPathDirs = Lists.newArrayList();
public List<String> bootClassPathEntries = Lists.newArrayList();
public List<String> extraClassPathEntries = Lists.newArrayList();
public Map<String,String> resourceIdFileEntries = new HashMap<String,String>();
public Map<Integer,String> resourceIds = new HashMap<Integer,String>();
public boolean noParameterRegisters = false;
public boolean useLocalsDirective = false;
public boolean useSequentialLabels = false;
public boolean outputDebugInfo = true;
public boolean addCodeOffsets = false;
public boolean noAccessorComments = false;
public boolean allowOdex = false;
public boolean deodex = false;
public boolean experimental = false;
public boolean ignoreErrors = false;
public boolean checkPackagePrivateAccess = false;
public boolean useImplicitReferences = false;
public boolean normalizeVirtualMethods = false;
public File customInlineDefinitions = null;
public InlineMethodResolver inlineResolver = null;
public int registerInfo = 0;
public ClassPath classPath = null;
public int jobs = Runtime.getRuntime().availableProcessors();
public boolean disassemble = true;
public boolean dump = false;
public String dumpFileName = null;
public Map<Integer,String> resourceIds = new HashMap<Integer,String>();
public InlineMethodResolver inlineResolver = null;
public ClassPath classPath = null;
public SyntheticAccessorResolver syntheticAccessorResolver = null;
public void setBootClassPath(String bootClassPath) {
bootClassPathEntries = Lists.newArrayList(bootClassPath.split(":"));
}
public void addExtraClassPath(String extraClassPath) {
if (extraClassPath.startsWith(":")) {
extraClassPath = extraClassPath.substring(1);
}
extraClassPathEntries.addAll(Arrays.asList(extraClassPath.split(":")));
}
public void setResourceIdFiles(String resourceIdFiles) {
for (String resourceIdFile: resourceIdFiles.split(":")) {
String[] entry = resourceIdFile.split("=");
resourceIdFileEntries.put(entry[1], entry[0]);
/**
* Load the resource ids from a set of public.xml files.
*
* @param resourceFiles A map of resource prefixes -> public.xml files
*/
public void loadResourceIds(Map<String, File> resourceFiles) throws SAXException, IOException {
for (Map.Entry<String, File> entry: resourceFiles.entrySet()) {
try {
SAXParser saxp = SAXParserFactory.newInstance().newSAXParser();
final String prefix = entry.getKey();
saxp.parse(entry.getValue(), new DefaultHandler() {
@Override
public void startElement(String uri, String localName, String qName,
Attributes attr) throws SAXException {
if (qName.equals("public")) {
String resourceType = attr.getValue("type");
String resourceName = attr.getValue("name").replace('.', '_');
Integer resourceId = Integer.decode(attr.getValue("id"));
String qualifiedResourceName =
String.format("%s.%s.%s", prefix, resourceType, resourceName);
resourceIds.put(resourceId, qualifiedResourceName);
}
}
});
} catch (ParserConfigurationException ex) {
throw new RuntimeException(ex);
}
}
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.ParametersDelegate;
import org.jf.baksmali.AnalysisArguments.CheckPackagePrivateArgument;
import org.jf.dexlib2.analysis.CustomInlineMethodResolver;
import org.jf.dexlib2.analysis.InlineMethodResolver;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.util.List;
@Parameters(commandDescription = "Deodexes an odex/oat file")
@ExtendedParameters(
commandName = "deodex",
commandAliases = { "de", "x" })
public class DeodexCommand extends DisassembleCommand {
@ParametersDelegate
protected CheckPackagePrivateArgument checkPackagePrivateArgument = new CheckPackagePrivateArgument();
@Parameter(names = {"--inline-table", "--inline", "--it"},
description = "Specify a file containing a custom inline method table to use. See the " +
"\"deodexerant\" tool in the smali github repository to dump the inline method table from a " +
"device that uses dalvik.")
@ExtendedParameter(argumentNames = "file")
private String inlineTable;
public DeodexCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
@Override protected BaksmaliOptions getOptions() {
BaksmaliOptions options = super.getOptions();
options.deodex = true;
if (dexFile instanceof DexBackedOdexFile) {
if (inlineTable == null) {
options.inlineResolver = InlineMethodResolver.createInlineMethodResolver(
((DexBackedOdexFile)dexFile).getOdexVersion());
} else {
File inlineTableFile = new File(inlineTable);
if (!inlineTableFile.exists()) {
System.err.println(String.format("Could not find file: %s", inlineTable));
System.exit(-1);
}
try {
options.inlineResolver = new CustomInlineMethodResolver(options.classPath, inlineTableFile);
} catch (IOException ex) {
System.err.println(String.format("Error while reading file: %s", inlineTableFile));
ex.printStackTrace(System.err);
System.exit(-1);
}
}
}
return options;
}
@Override protected boolean shouldCheckPackagePrivateAccess() {
return checkPackagePrivateArgument.checkPackagePrivateAccess;
}
@Override protected boolean needsClassPath() {
return true;
}
@Override protected boolean showDeodexWarning() {
return false;
}
}

View File

@ -0,0 +1,146 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedParameter;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.util.List;
/**
* This class implements common functionality for commands that need to load a dex file based on
* command line input
*/
public abstract class DexInputCommand extends Command {
@Parameter(description = "A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
"files, you can specify the specific entry to use as if the apk/oat file was a directory. " +
"e.g. \"app.apk/classes2.dex\". For more information, see \"baksmali help input\".")
@ExtendedParameter(argumentNames = "file")
protected List<String> inputList = Lists.newArrayList();
protected File inputFile;
protected String inputEntry;
protected DexBackedDexFile dexFile;
public DexInputCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
/**
* Parses a dex file input from the user and loads the given dex file.
*
* In some cases, the input file can contain multiple dex files. If this is the case, you can refer to a specific
* dex file with a slash, followed by the entry name, optionally in quotes.
*
* If the entry name is enclosed in quotes, then it will strip the first and last quote and look for an entry with
* exactly that name. Otherwise, it will perform a partial filename match against the entry to find any candidates.
* If there is a single matching candidate, it will be used. Otherwise, an error will be generated.
*
* For example, to refer to the "/system/framework/framework.jar:classes2.dex" entry within the
* "framework/arm/framework.oat" oat file, you could use any of:
*
* framework/arm/framework.oat/"/system/framework/framework.jar:classes2.dex"
* framework/arm/framework.oat/system/framework/framework.jar:classes2.dex
* framework/arm/framework.oat/framework/framework.jar:classes2.dex
* framework/arm/framework.oat/framework.jar:classes2.dex
* framework/arm/framework.oat/classes2.dex
*
* The last option is the easiest, but only works if the oat file doesn't contain another entry with the
* "classes2.dex" name. e.g. "/system/framework/blah.jar:classes2.dex"
*
* It's technically possible (although unlikely) for an oat file to contain 2 entries like:
* /system/framework/framework.jar:classes2.dex
* system/framework/framework.jar:classes2.dex
*
* In this case, the "framework/arm/framework.oat/system/framework/framework.jar:classes2.dex" syntax will generate
* an error because both entries match the partial entry name. Instead, you could use the following for the
* first and second entry respectively:
*
* framework/arm/framework.oat/"/system/framework/framework.jar:classes2.dex"
* framework/arm/framework.oat/"system/framework/framework.jar:classes2.dex"
*
* @param input The name of a dex, apk, odex or oat file/entry.
* @param opcodes The set of opcodes to load the dex file with.
*/
protected void loadDexFile(@Nonnull String input, Opcodes opcodes) {
File file = new File(input);
while (file != null && !file.exists()) {
file = file.getParentFile();
}
if (file == null || !file.exists() || file.isDirectory()) {
System.err.println("Can't find file: " + input);
System.exit(1);
}
inputFile = file;
String dexEntry = null;
if (file.getPath().length() < input.length()) {
dexEntry = input.substring(file.getPath().length() + 1);
}
if (!Strings.isNullOrEmpty(dexEntry)) {
boolean exactMatch = false;
if (dexEntry.length() > 2 && dexEntry.charAt(0) == '"' && dexEntry.charAt(dexEntry.length() - 1) == '"') {
dexEntry = dexEntry.substring(1, dexEntry.length() - 1);
exactMatch = true;
}
inputEntry = dexEntry;
try {
dexFile = DexFileFactory.loadDexEntry(file, dexEntry, exactMatch, opcodes);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
} else {
try {
dexFile = DexFileFactory.loadDexFile(file, opcodes);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
}

View File

@ -0,0 +1,283 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.ParametersDelegate;
import com.beust.jcommander.validators.PositiveInteger;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.util.SyntheticAccessorResolver;
import org.jf.util.StringWrapper;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import org.xml.sax.SAXException;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@Parameters(commandDescription = "Disassembles a dex file.")
@ExtendedParameters(
commandName = "disassemble",
commandAliases = { "dis", "d" })
public class DisassembleCommand extends DexInputCommand {
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information for this command.")
private boolean help;
@ParametersDelegate
protected AnalysisArguments analysisArguments = new AnalysisArguments();
@Parameter(names = {"--debug-info", "--di"}, arity = 1,
description = "Whether to include debug information in the output (.local, .param, .line, etc.). True " +
"by default, use --debug-info=false to disable.")
@ExtendedParameter(argumentNames = "boolean")
private boolean debugInfo = true;
@Parameter(names = {"--code-offsets", "--offsets", "--off"},
description = "Add a comment before each instruction with it's code offset within the method.")
private boolean codeOffsets = false;
@Parameter(names = {"--resolve-resources", "--rr"}, arity = 2,
description = "This will attempt to find any resource id references within the bytecode and add a " +
"comment with the name of the resource being referenced. The parameter accepts 2 values:" +
"an arbitrary resource prefix and the path to a public.xml file. For example: " +
"--resolve-resources android.R framework/res/values/public.xml. This option can be specified " +
"multiple times to provide resources from multiple packages.")
@ExtendedParameter(argumentNames = {"resource prefix", "public.xml file"})
private List<String> resourceIdFiles = Lists.newArrayList();
@Parameter(names = {"-j", "--jobs"},
description = "The number of threads to use. Defaults to the number of cores available.",
validateWith = PositiveInteger.class)
@ExtendedParameter(argumentNames = "n")
private int jobs = Runtime.getRuntime().availableProcessors();
@Parameter(names = {"-l", "--use-locals"},
description = "When disassembling, output the .locals directive with the number of non-parameter " +
"registers instead of the .registers directive with the total number of registers.")
private boolean localsDirective = false;
@Parameter(names = {"--accessor-comments", "--ac"}, arity = 1,
description = "Generate helper comments for synthetic accessors. True by default, use " +
"--accessor-comments=false to disable.")
@ExtendedParameter(argumentNames = "boolean")
private boolean accessorComments = true;
@Parameter(names = {"--normalize-virtual-methods", "--norm", "--nvm"},
description = "Normalize virtual method references to use the base class where the method is " +
"originally declared.")
private boolean normalizeVirtualMethods = false;
@Parameter(names = {"-o", "--output"},
description = "The directory to write the disassembled files to.")
@ExtendedParameter(argumentNames = "dir")
private String outputDir = "out";
@Parameter(names = {"--parameter-registers", "--preg", "--pr"}, arity = 1,
description = "Use the pNN syntax for registers that refer to a method parameter on method entry. True " +
"by default, use --parameter-registers=false to disable.")
@ExtendedParameter(argumentNames = "boolean")
private boolean parameterRegisters = true;
@Parameter(names = {"-r", "--register-info"},
description = "Add comments before/after each instruction with information about register types. " +
"The value is a comma-separated list of any of ALL, ALLPRE, ALLPOST, ARGS, DEST, MERGE and " +
"FULLMERGE. See \"baksmali help register-info\" for more information.")
@ExtendedParameter(argumentNames = "register info specifier")
private List<String> registerInfoTypes = Lists.newArrayList();
@Parameter(names = {"--sequential-labels", "--seq", "--sl"},
description = "Create label names using a sequential numbering scheme per label type, rather than " +
"using the bytecode address.")
private boolean sequentialLabels = false;
@Parameter(names = {"--implicit-references", "--implicit", "--ir"},
description = "Use implicit method and field references (without the class name) for methods and " +
"fields from the current class.")
private boolean implicitReferences = false;
public DisassembleCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
public void run() {
if (help || inputList == null || inputList.isEmpty()) {
usage();
return;
}
if (inputList.size() > 1) {
System.err.println("Too many files specified");
usage();
return;
}
String input = inputList.get(0);
loadDexFile(input, Opcodes.getDefault());
if (showDeodexWarning() && dexFile.hasOdexOpcodes()) {
StringWrapper.printWrappedString(System.err,
"Warning: You are disassembling an odex/oat file without deodexing it. You won't be able to " +
"re-assemble the results unless you deodex it. See \"baksmali help deodex\"");
}
File outputDirectoryFile = new File(outputDir);
if (!outputDirectoryFile.exists()) {
if (!outputDirectoryFile.mkdirs()) {
System.err.println("Can't create the output directory " + outputDir);
System.exit(-1);
}
}
if (analysisArguments.classPathDirectories == null || analysisArguments.classPathDirectories.isEmpty()) {
analysisArguments.classPathDirectories = Lists.newArrayList(inputFile.getAbsoluteFile().getParent());
}
if (!Baksmali.disassembleDexFile(dexFile, outputDirectoryFile, jobs, getOptions())) {
System.exit(-1);
}
}
protected boolean needsClassPath() {
return !registerInfoTypes.isEmpty() || normalizeVirtualMethods;
}
protected boolean shouldCheckPackagePrivateAccess() {
return false;
}
protected boolean showDeodexWarning() {
return true;
}
protected BaksmaliOptions getOptions() {
if (dexFile == null) {
throw new IllegalStateException("You must call loadDexFile first");
}
final BaksmaliOptions options = new BaksmaliOptions();
if (needsClassPath()) {
try {
options.classPath = analysisArguments.loadClassPathForDexFile(
inputFile.getAbsoluteFile().getParentFile(), dexFile, shouldCheckPackagePrivateAccess());
} catch (Exception ex) {
System.err.println("\n\nError occurred while loading class path files. Aborting.");
ex.printStackTrace(System.err);
System.exit(-1);
}
}
if (!resourceIdFiles.isEmpty()) {
Map<String, File> resourceFiles = Maps.newHashMap();
assert (resourceIdFiles.size() % 2) == 0;
for (int i=0; i<resourceIdFiles.size(); i+=2) {
String resourcePrefix = resourceIdFiles.get(i);
String publicXml = resourceIdFiles.get(i+1);
File publicXmlFile = new File(publicXml);
if (!publicXmlFile.exists()) {
System.err.println(String.format("Can't find file: %s", publicXmlFile));
System.exit(-1);
}
resourceFiles.put(resourcePrefix, publicXmlFile);
}
try {
options.loadResourceIds(resourceFiles);
} catch (IOException ex) {
System.err.println("Error while loading resource files:");
ex.printStackTrace(System.err);
System.exit(-1);
} catch (SAXException ex) {
System.err.println("Error while loading resource files:");
ex.printStackTrace(System.err);
System.exit(-1);
}
}
options.parameterRegisters = parameterRegisters;
options.localsDirective = localsDirective;
options.sequentialLabels = sequentialLabels;
options.debugInfo = debugInfo;
options.codeOffsets = codeOffsets;
options.accessorComments = accessorComments;
options.implicitReferences = implicitReferences;
options.normalizeVirtualMethods = normalizeVirtualMethods;
options.registerInfo = 0;
for (String registerInfoType: registerInfoTypes) {
if (registerInfoType.equalsIgnoreCase("ALL")) {
options.registerInfo |= BaksmaliOptions.ALL;
} else if (registerInfoType.equalsIgnoreCase("ALLPRE")) {
options.registerInfo |= BaksmaliOptions.ALLPRE;
} else if (registerInfoType.equalsIgnoreCase("ALLPOST")) {
options.registerInfo |= BaksmaliOptions.ALLPOST;
} else if (registerInfoType.equalsIgnoreCase("ARGS")) {
options.registerInfo |= BaksmaliOptions.ARGS;
} else if (registerInfoType.equalsIgnoreCase("DEST")) {
options.registerInfo |= BaksmaliOptions.DEST;
} else if (registerInfoType.equalsIgnoreCase("MERGE")) {
options.registerInfo |= BaksmaliOptions.MERGE;
} else if (registerInfoType.equalsIgnoreCase("FULLMERGE")) {
options.registerInfo |= BaksmaliOptions.FULLMERGE;
} else {
System.err.println(String.format("Invalid register info type: %s", registerInfoType));
usage();
System.exit(-1);
}
if ((options.registerInfo & BaksmaliOptions.FULLMERGE) != 0) {
options.registerInfo &= ~BaksmaliOptions.MERGE;
}
}
if (accessorComments) {
options.syntheticAccessorResolver = new SyntheticAccessorResolver(dexFile.getOpcodes(),
dexFile.getClasses());
}
return options;
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.raw.RawDexFile;
import org.jf.dexlib2.dexbacked.raw.util.DexAnnotator;
import org.jf.util.ConsoleUtil;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.io.*;
import java.util.List;
@Parameters(commandDescription = "Prints an annotated hex dump for the given dex file")
@ExtendedParameters(
commandName = "dump",
commandAliases = "du")
public class DumpCommand extends DexInputCommand {
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information for this command.")
private boolean help;
@Parameter(names = {"-a", "--api"},
description = "The numeric api level of the file being disassembled.")
@ExtendedParameter(argumentNames = "api")
private int apiLevel = 15;
public DumpCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
public void run() {
if (help || inputList == null || inputList.isEmpty()) {
usage();
return;
}
if (inputList.size() > 1) {
System.err.println("Too many files specified");
usage();
return;
}
String input = inputList.get(0);
loadDexFile(input, Opcodes.getDefault());
try {
dump(dexFile, System.out, apiLevel);
} catch (IOException ex) {
System.err.println("There was an error while dumping the dex file");
ex.printStackTrace(System.err);
}
}
/**
* Writes an annotated hex dump of the given dex file to output.
*
* @param dexFile The dex file to dump
* @param output An OutputStream to write the annotated hex dump to. The caller is responsible for closing this
* when needed.
* @param apiLevel The api level to use when dumping the dex file
*
* @throws IOException
*/
public static void dump(@Nonnull DexBackedDexFile dexFile, @Nonnull OutputStream output, int apiLevel)
throws IOException {
Writer writer = new BufferedWriter(new OutputStreamWriter(output));
int consoleWidth = ConsoleUtil.getConsoleWidth();
if (consoleWidth <= 0) {
consoleWidth = 120;
}
RawDexFile rawDexFile = new RawDexFile(dexFile.getOpcodes(), dexFile);
DexAnnotator annotator = new DexAnnotator(rawDexFile, consoleWidth);
annotator.writeAnnotations(writer);
}
}

View File

@ -0,0 +1,204 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.Lists;
import org.jf.util.ConsoleUtil;
import org.jf.util.StringWrapper;
import org.jf.util.jcommander.*;
import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Shows usage information")
@ExtendedParameters(
commandName = "help",
commandAliases = "h")
public class HelpCommand extends Command {
public HelpCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
@Parameter(description = "If specified, show the detailed usage information for the given commands")
@ExtendedParameter(argumentNames = "commands")
private List<String> commands = Lists.newArrayList();
public void run() {
JCommander parentJc = commandAncestors.get(commandAncestors.size() - 1);
if (commands == null || commands.isEmpty()) {
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(commandAncestors));
} else {
boolean printedHelp = false;
for (String cmd : commands) {
if (cmd.equals("register-info")) {
printedHelp = true;
String registerInfoHelp = "The --register-info parameter will cause baksmali to generate " +
"comments before and after every instruction containing register type " +
"information about some subset of registers. This parameter accepts a comma-separated list" +
"of values specifying which registers and how much information to include.\n" +
" ALL: all pre- and post-instruction registers\n" +
" ALLPRE: all pre-instruction registers\n" +
" ALLPOST: all post-instruction registers\n" +
" ARGS: any pre-instruction registers used as arguments to the instruction\n" +
" DEST: the post-instruction register used as the output of the instruction\n" +
" MERGE: any pre-instruction register that has been merged from multiple " +
"incoming code paths\n" +
" FULLMERGE: an extended version of MERGE that also includes a list of all " +
"the register types from incoming code paths that were merged";
Iterable<String> lines = StringWrapper.wrapStringOnBreaks(registerInfoHelp,
ConsoleUtil.getConsoleWidth());
for (String line : lines) {
System.out.println(line);
}
} else if (cmd.equals("input")) {
printedHelp = true;
String registerInfoHelp = "Apks and oat files can contain multiple dex files. In order to " +
"specify a particular dex file, the basic syntax is to treat the apk/oat file as a " +
"directory. For example, to load the \"classes2.dex\" entry from \"app.apk\", you can " +
"use \"app.apk/classes2.dex\".\n" +
"\n" +
"For ease of use, you can also specify a partial path to the dex file to load. For " +
"example, to load a entry named \"/system/framework/framework.jar:classes2.dex\" from " +
"\"framework.oat\", you can use any of the following:\n" +
"\"framework.oat/classes2.dex\"\n" +
"\"framework.oat/framework.jar:classes2.dex\"\n" +
"\"framework.oat/framework/framework.jar:classes2.dex\"\n" +
"\"framework.oat/system/framework/framework.jar:classes2.dex\"\n" +
"\n" +
"In some rare cases, an oat file could have entries that can't be differentiated with " +
"the above syntax. For example \"/blah/blah.dex\" and \"blah/blah.dex\". In this case, " +
"the \"blah.oat/blah/blah.dex\" would match both entries and generate an error. To get " +
"around this, you can add double quotes around the entry name to specify an exact entry " +
"name. E.g. blah.oat/\"/blah/blah.dex\" or blah.oat/\"blah/blah.dex\" respectively.";
Iterable<String> lines = StringWrapper.wrapStringOnBreaks(registerInfoHelp,
ConsoleUtil.getConsoleWidth());
for (String line : lines) {
System.out.println(line);
}
} else if (cmd.equals("classpath")) {
printedHelp = true;
String registerInfoHelp = "When deodexing odex/oat files or when using the --register-info " +
"option, baksmali needs to load all classes from the framework files on the device " +
"in order to fully understand the class hierarchy. There are several options that " +
"control how baksmali finds and loads the classpath entries.\n" +
"\n"+
"L+ devices (ART):\n" +
"When deodexing or disassembling a file from an L+ device using ART, you generally " +
"just need to specify the path to the boot.oat file via the --bootclasspath/-b " +
"parameter. On pre-N devices, the boot.oat file is self-contained and no other files are " +
"needed. In N, boot.oat was split into multiple files. In this case, the other " +
"files should be in the same directory as the boot.oat file, but you still only need to " +
"specify the boot.oat file in the --bootclasspath/-b option. The other files will be " +
"automatically loaded from the same directory.\n" +
"\n" +
"Pre-L devices (dalvik):\n" +
"When deodexing odex files from a pre-L device using dalvik, you " +
"generally just need to specify the path to a directory containing the framework files " +
"from the device via the --classpath-dir/-d option. odex files contain a list of " +
"framework files they depend on and baksmali will search for these dependencies in the " +
"directory that you specify.\n" +
"\n" +
"Dex files don't contain a list of dependencies like odex files, so when disassembling a " +
"dex file using the --register-info option, and using the framework files from a " +
"pre-L device, baksmali will attempt to use a reasonable default list of classpath files " +
"based on the api level set via the -a option. If this default list is incorrect, you " +
"can override the classpath using the --bootclasspath/-b option. This option accepts a " +
"colon separated list of classpath entries. Each entry can be specified in a few " +
"different ways.\n" +
" - A simple filename like \"framework.jar\"\n" +
" - A device path like \"/system/framework/framework.jar\"\n" +
" - A local relative or absolute path like \"/tmp/framework/framework.jar\"\n" +
"When using the first or second formats, you should also specify the directory " +
"containing the framework files via the --classpath-dir/-d option. When using the third " +
"format, this option is not needed.\n" +
"It's worth noting that the second format matches the format used by Android for the " +
"BOOTCLASSPATH environment variable, so you can simply grab the value of that variable " +
"from the device and use it as-is.\n" +
"\n" +
"Examples:\n" +
" For an M device:\n" +
" adb pull /system/framework/arm/boot.oat /tmp/boot.oat\n" +
" baksmali deodex blah.oat -b /tmp/boot.oat\n" +
" For an N+ device:\n" +
" adb pull /system/framework/arm /tmp/framework\n" +
" baksmali deodex blah.oat -b /tmp/framework/boot.oat\n" +
" For a pre-L device:\n" +
" adb pull /system/framework /tmp/framework\n" +
" baksmali deodex blah.odex -d /tmp/framework\n" +
" Using the BOOTCLASSPATH on a pre-L device:\n" +
" adb pull /system/framework /tmp/framework\n" +
" export BOOTCLASSPATH=`adb shell \"echo \\\\$BOOTCLASPATH\"`\n" +
" baksmali disassemble --register-info ARGS,DEST blah.apk -b $BOOTCLASSPATH -d " +
"/tmp/framework";
Iterable<String> lines = StringWrapper.wrapStringOnBreaks(registerInfoHelp,
ConsoleUtil.getConsoleWidth());
for (String line : lines) {
System.out.println(line);
}
} else {
JCommander command = ExtendedCommands.getSubcommand(parentJc, cmd);
if (command == null) {
System.err.println("No such command: " + cmd);
} else {
printedHelp = true;
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(((Command)command.getObjects().get(0)).getCommandHierarchy()));
}
}
}
if (!printedHelp) {
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(commandAncestors));
}
}
}
@Parameters(hidden = true)
@ExtendedParameters(commandName = "hlep")
public static class HlepCommand extends HelpCommand {
public HlepCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Lists the classes in a dex file.")
@ExtendedParameters(
commandName = "classes",
commandAliases = { "class", "c" })
public class ListClassesCommand extends DexInputCommand {
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information")
private boolean help;
public ListClassesCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
@Override public void run() {
if (help || inputList == null || inputList.isEmpty()) {
usage();
return;
}
if (inputList.size() > 1) {
System.err.println("Too many files specified");
usage();
return;
}
String input = inputList.get(0);
loadDexFile(input, Opcodes.getDefault());
for (ClassDef classDef: dexFile.getClasses()) {
System.out.println(classDef.getType());
}
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.jf.baksmali.ListHelpCommand.ListHlepCommand;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedCommands;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Lists various objects in a dex file.")
@ExtendedParameters(
commandName = "list",
commandAliases = "l")
public class ListCommand extends Command {
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information")
private boolean help;
public ListCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
@Override protected void setupCommand(JCommander jc) {
List<JCommander> hierarchy = getCommandHierarchy();
ExtendedCommands.addExtendedCommand(jc, new ListStringsCommand(hierarchy));
ExtendedCommands.addExtendedCommand(jc, new ListMethodsCommand(hierarchy));
ExtendedCommands.addExtendedCommand(jc, new ListFieldsCommand(hierarchy));
ExtendedCommands.addExtendedCommand(jc, new ListTypesCommand(hierarchy));
ExtendedCommands.addExtendedCommand(jc, new ListClassesCommand(hierarchy));
ExtendedCommands.addExtendedCommand(jc, new ListDexCommand(hierarchy));
ExtendedCommands.addExtendedCommand(jc, new ListVtablesCommand(hierarchy));
ExtendedCommands.addExtendedCommand(jc, new ListFieldOffsetsCommand(hierarchy));
ExtendedCommands.addExtendedCommand(jc, new ListDependenciesCommand(hierarchy));
ExtendedCommands.addExtendedCommand(jc, new ListHelpCommand(hierarchy));
ExtendedCommands.addExtendedCommand(jc, new ListHlepCommand(hierarchy));
}
@Override public void run() {
JCommander jc = getJCommander();
if (help || jc.getParsedCommand() == null) {
usage();
return;
}
Command command = (Command)jc.getCommands().get(jc.getParsedCommand()).getObjects().get(0);
command.run();
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.Lists;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.io.*;
import java.util.List;
@Parameters(commandDescription = "Lists the stored dependencies in an odex/oat file.")
@ExtendedParameters(
commandName = "dependencies",
commandAliases = { "deps", "dep" })
public class ListDependenciesCommand extends Command {
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information")
private boolean help;
@Parameter(description = "An oat/odex file")
@ExtendedParameter(argumentNames = "file")
private List<String> inputList = Lists.newArrayList();
public ListDependenciesCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
@Override public void run() {
if (help || inputList == null || inputList.isEmpty()) {
usage();
return;
}
if (inputList.size() > 1) {
System.err.println("Too many files specified");
usage();
return;
}
String input = inputList.get(0);
InputStream inputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream(input));
} catch (FileNotFoundException ex) {
System.err.println("Could not find file: " + input);
System.exit(-1);
}
try {
OatFile oatFile = OatFile.fromInputStream(inputStream);
for (String entry: oatFile.getBootClassPath()) {
System.out.println(entry);
}
return;
} catch (OatFile.NotAnOatFileException ex) {
// ignore
} catch (IOException ex) {
throw new RuntimeException(ex);
}
try {
DexBackedOdexFile odexFile = DexBackedOdexFile.fromInputStream(Opcodes.getDefault(), inputStream);
for (String entry: odexFile.getDependencies()) {
System.out.println(entry);
}
return;
} catch (IOException ex) {
throw new RuntimeException(ex);
} catch (DexBackedOdexFile.NotAnOdexFile ex) {
// handled below
} catch (DexBackedDexFile.NotADexFile ex) {
// handled below
}
System.err.println(input + " is not an odex or oat file.");
System.exit(-1);
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.Lists;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.iface.MultiDexContainer;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.util.List;
@Parameters(commandDescription = "Lists the dex files in an apk/oat file.")
@ExtendedParameters(
commandName = "dex",
commandAliases = "d")
public class ListDexCommand extends Command {
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information")
private boolean help;
@Parameter(description = "An apk or oat file.")
@ExtendedParameter(argumentNames = "file")
private List<String> inputList = Lists.newArrayList();
public ListDexCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
@Override public void run() {
if (help || inputList == null || inputList.isEmpty()) {
usage();
return;
}
if (inputList.size() > 1) {
System.err.println("Too many files specified");
usage();
return;
}
String input = inputList.get(0);
File file = new File(input);
if (!file.exists()) {
System.err.println(String.format("Could not find the file: %s", input));
System.exit(-1);
}
List<String> entries;
try {
MultiDexContainer<? extends DexBackedDexFile> container =
DexFileFactory.loadDexContainer(file, Opcodes.getDefault());
entries = container.getDexEntryNames();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
for (String entry: entries) {
System.out.println(entry);
}
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.ParametersDelegate;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.analysis.ClassProto;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.util.SparseArray;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.List;
@Parameters(commandDescription = "Lists the instance field offsets for classes in a dex file.")
@ExtendedParameters(
commandName = "fieldoffsets",
commandAliases = { "fieldoffset", "fo" })
public class ListFieldOffsetsCommand extends DexInputCommand {
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information")
private boolean help;
@ParametersDelegate
private AnalysisArguments analysisArguments = new AnalysisArguments();
public ListFieldOffsetsCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
@Override public void run() {
if (help || inputList == null || inputList.isEmpty()) {
usage();
return;
}
if (inputList.size() > 1) {
System.err.println("Too many files specified");
usage();
return;
}
String input = inputList.get(0);
loadDexFile(input, Opcodes.getDefault());
BaksmaliOptions options = getOptions();
try {
for (ClassDef classDef: dexFile.getClasses()) {
ClassProto classProto = (ClassProto) options.classPath.getClass(classDef);
SparseArray<FieldReference> fields = classProto.getInstanceFields();
String className = "Class " + classDef.getType() + " : " + fields.size() + " instance fields\n";
System.out.write(className.getBytes());
for (int i=0;i<fields.size();i++) {
String field = fields.keyAt(i) + ":" + fields.valueAt(i).getType() + " " + fields.valueAt(i).getName() + "\n";
System.out.write(field.getBytes());
}
System.out.write("\n".getBytes());
}
System.out.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
@Nonnull
private BaksmaliOptions getOptions() {
if (dexFile == null) {
throw new IllegalStateException("You must call loadDexFile first");
}
final BaksmaliOptions options = new BaksmaliOptions();
options.apiLevel = analysisArguments.apiLevel;
try {
options.classPath = analysisArguments.loadClassPathForDexFile(
inputFile.getAbsoluteFile().getParentFile(), dexFile, false);
} catch (Exception ex) {
System.err.println("Error occurred while loading class path files.");
ex.printStackTrace(System.err);
System.exit(-1);
}
return options;
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameters;
import org.jf.dexlib2.ReferenceType;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Lists the fields in a dex file's field table.")
@ExtendedParameters(
commandName = "fields",
commandAliases = { "field", "f" })
public class ListFieldsCommand extends ListReferencesCommand {
public ListFieldsCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors, ReferenceType.FIELD);
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.Iterables;
import org.jf.util.ConsoleUtil;
import org.jf.util.jcommander.*;
import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Shows usage information")
@ExtendedParameters(
commandName = "help",
commandAliases = "h")
public class ListHelpCommand extends Command {
@Parameter(description = "If specified, show the detailed usage information for the given commands")
@ExtendedParameter(argumentNames = "commands")
private List<String> commands;
public ListHelpCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
public void run() {
if (commands == null || commands.isEmpty()) {
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(commandAncestors));
} else {
boolean printedHelp = false;
JCommander parentJc = Iterables.getLast(commandAncestors);
for (String cmd : commands) {
JCommander command = ExtendedCommands.getSubcommand(parentJc, cmd);
if (command == null) {
System.err.println("No such command: " + cmd);
} else {
printedHelp = true;
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(((Command)command.getObjects().get(0)).getCommandHierarchy()));
}
}
if (!printedHelp) {
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(commandAncestors));
}
}
}
@Parameters(hidden = true)
@ExtendedParameters(commandName = "hlep")
public static class ListHlepCommand extends ListHelpCommand {
public ListHlepCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameters;
import org.jf.dexlib2.ReferenceType;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Lists the methods in a dex file's method table.")
@ExtendedParameters(
commandName = "methods",
commandAliases = { "method", "m" })
public class ListMethodsCommand extends ListReferencesCommand {
public ListMethodsCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors, ReferenceType.METHOD);
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.iface.reference.Reference;
import org.jf.dexlib2.util.ReferenceUtil;
import javax.annotation.Nonnull;
import java.util.List;
public abstract class ListReferencesCommand extends DexInputCommand {
private final int referenceType;
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information")
private boolean help;
public ListReferencesCommand(@Nonnull List<JCommander> commandAncestors, int referenceType) {
super(commandAncestors);
this.referenceType = referenceType;
}
@Override public void run() {
if (help || inputList == null || inputList.isEmpty()) {
usage();
return;
}
if (inputList.size() > 1) {
System.err.println("Too many files specified");
usage();
return;
}
String input = inputList.get(0);
loadDexFile(input, Opcodes.getDefault());
for (Reference reference: dexFile.getReferences(referenceType)) {
System.out.println(ReferenceUtil.getReferenceString(reference));
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameters;
import org.jf.dexlib2.ReferenceType;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Lists the strings in a dex file's string table.")
@ExtendedParameters(
commandName = "strings",
commandAliases = { "string", "str", "s" })
public class ListStringsCommand extends ListReferencesCommand {
public ListStringsCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors, ReferenceType.STRING);
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameters;
import org.jf.dexlib2.ReferenceType;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Lists the type ids in a dex file's type table.")
@ExtendedParameters(
commandName = "types",
commandAliases = { "type", "t" })
public class ListTypesCommand extends ListReferencesCommand {
public ListTypesCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors, ReferenceType.TYPE);
}
}

View File

@ -0,0 +1,158 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.ParametersDelegate;
import org.jf.baksmali.AnalysisArguments.CheckPackagePrivateArgument;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.analysis.ClassProto;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.Method;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.List;
@Parameters(commandDescription = "Lists the virtual method tables for classes in a dex file.")
@ExtendedParameters(
commandName = "vtables",
commandAliases = { "vtable", "v" })
public class ListVtablesCommand extends DexInputCommand {
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information")
private boolean help;
@ParametersDelegate
private AnalysisArguments analysisArguments = new AnalysisArguments();
@ParametersDelegate
private CheckPackagePrivateArgument checkPackagePrivateArgument = new CheckPackagePrivateArgument();
@Parameter(names = "--classes",
description = "A comma separated list of classes. Only print the vtable for these classes")
@ExtendedParameter(argumentNames = "classes")
private List<String> classes = null;
@Parameter(names = "--override-oat-version",
description = "Uses a classpath for the given oat version, regardless of the actual oat version. This " +
"can be used, e.g. to list vtables from a dex file, as if they were in an oat file of the given " +
"version.")
private int oatVersion = 0;
public ListVtablesCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
@Override public void run() {
if (help || inputList == null || inputList.isEmpty()) {
usage();
return;
}
if (inputList.size() > 1) {
System.err.println("Too many files specified");
usage();
return;
}
String input = inputList.get(0);
loadDexFile(input, Opcodes.getDefault());
BaksmaliOptions options = getOptions();
if (options == null) {
return;
}
try {
if (classes != null && !classes.isEmpty()) {
for (String cls: classes) {
listClassVtable((ClassProto)options.classPath.getClass(cls));
}
return;
}
for (ClassDef classDef : dexFile.getClasses()) {
if (!AccessFlags.INTERFACE.isSet(classDef.getAccessFlags())) {
listClassVtable((ClassProto)options.classPath.getClass(classDef));
}
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private void listClassVtable(ClassProto classProto) throws IOException {
List<Method> methods = classProto.getVtable();
String className = "Class " + classProto.getType() + " extends " + classProto.getSuperclass() +
" : " + methods.size() + " methods\n";
System.out.write(className.getBytes());
for (int i = 0; i < methods.size(); i++) {
Method method = methods.get(i);
String methodString = i + ":" + method.getDefiningClass() + "->" + method.getName() + "(";
for (CharSequence parameter : method.getParameterTypes()) {
methodString += parameter;
}
methodString += ")" + method.getReturnType() + "\n";
System.out.write(methodString.getBytes());
}
System.out.write("\n".getBytes());
}
protected BaksmaliOptions getOptions() {
if (dexFile == null) {
throw new IllegalStateException("You must call loadDexFile first");
}
final BaksmaliOptions options = new BaksmaliOptions();
options.apiLevel = analysisArguments.apiLevel;
try {
options.classPath = analysisArguments.loadClassPathForDexFile(inputFile.getAbsoluteFile().getParentFile(),
dexFile, checkPackagePrivateArgument.checkPackagePrivateAccess, oatVersion);
} catch (Exception ex) {
System.err.println("Error occurred while loading class path files.");
ex.printStackTrace(System.err);
return null;
}
return options;
}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.google.common.collect.Lists;
import org.jf.baksmali.HelpCommand.HlepCommand;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedCommands;
import org.jf.util.jcommander.ExtendedParameters;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
@ExtendedParameters(
includeParametersInUsage = true,
commandName = "baksmali",
postfixDescription = "See baksmali help <command> for more information about a specific command")
public class Main extends Command {
public static final String VERSION = loadVersion();
@Parameter(names = {"--help", "-h", "-?"}, help = true,
description = "Show usage information")
private boolean help;
@Parameter(names = {"--version", "-v"}, help = true,
description = "Print the version of baksmali and then exit")
public boolean version;
private JCommander jc;
public Main() {
super(Lists.<JCommander>newArrayList());
}
@Override public void run() {
}
@Override protected JCommander getJCommander() {
return jc;
}
public static void main(String[] args) {
Main main = new Main();
JCommander jc = new JCommander(main);
main.jc = jc;
jc.setProgramName("baksmali");
List<JCommander> commandHierarchy = main.getCommandHierarchy();
ExtendedCommands.addExtendedCommand(jc, new DisassembleCommand(commandHierarchy));
ExtendedCommands.addExtendedCommand(jc, new DeodexCommand(commandHierarchy));
ExtendedCommands.addExtendedCommand(jc, new DumpCommand(commandHierarchy));
ExtendedCommands.addExtendedCommand(jc, new HelpCommand(commandHierarchy));
ExtendedCommands.addExtendedCommand(jc, new HlepCommand(commandHierarchy));
ExtendedCommands.addExtendedCommand(jc, new ListCommand(commandHierarchy));
jc.parse(args);
if (main.version) {
version();
}
if (jc.getParsedCommand() == null || main.help) {
main.usage();
return;
}
Command command = (Command)jc.getCommands().get(jc.getParsedCommand()).getObjects().get(0);
command.run();
}
protected static void version() {
System.out.println("baksmali " + VERSION + " (http://smali.org)");
System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
System.exit(0);
}
private static String loadVersion() {
InputStream propertiesStream = Baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
String version = "[unknown version]";
if (propertiesStream != null) {
Properties properties = new Properties();
try {
properties.load(propertiesStream);
version = properties.getProperty("application.version");
} catch (IOException ex) {
// ignore
}
}
return version;
}
}

View File

@ -1,73 +0,0 @@
/*
* [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.baksmali;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.raw.RawDexFile;
import org.jf.dexlib2.dexbacked.raw.util.DexAnnotator;
import org.jf.util.ConsoleUtil;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class dump {
public static void dump(DexBackedDexFile dexFile, String dumpFileName, int apiLevel) throws IOException {
if (dumpFileName != null) {
Writer writer = null;
try {
writer = new BufferedWriter(new FileWriter(dumpFileName));
int consoleWidth = ConsoleUtil.getConsoleWidth();
if (consoleWidth <= 0) {
consoleWidth = 120;
}
RawDexFile rawDexFile = new RawDexFile(Opcodes.forApi(apiLevel), dexFile);
DexAnnotator annotator = new DexAnnotator(rawDexFile, consoleWidth);
annotator.writeAnnotations(writer);
} catch (IOException ex) {
System.err.println("There was an error while dumping the dex file to " + dumpFileName);
ex.printStackTrace(System.err);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException ex) {
System.err.println("There was an error while closing the dump file " + dumpFileName);
ex.printStackTrace(System.err);
}
}
}
}
}
}

View File

@ -1,612 +0,0 @@
/*
* [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.baksmali;
import com.google.common.collect.Lists;
import org.apache.commons.cli.*;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
import org.jf.dexlib2.analysis.InlineMethodResolver;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.iface.DexFile;
import org.jf.util.ConsoleUtil;
import org.jf.util.SmaliHelpFormatter;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
public class main {
public static final String VERSION;
private static final Options basicOptions;
private static final Options debugOptions;
private static final Options options;
static {
options = new Options();
basicOptions = new Options();
debugOptions = new Options();
buildOptions();
InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
if (templateStream != null) {
Properties properties = new Properties();
String version = "(unknown)";
try {
properties.load(templateStream);
version = properties.getProperty("application.version");
} catch (IOException ex) {
// ignore
}
VERSION = version;
} else {
VERSION = "[unknown version]";
}
}
/**
* This class is uninstantiable.
*/
private main() {
}
/**
* A more programmatic-friendly entry point for baksmali
*
* @param options a baksmaliOptions object with the options to run baksmali with
* @param inputDexFile The DexFile to disassemble
* @return true if disassembly completed with no errors, or false if errors were encountered
*/
public static boolean run(@Nonnull baksmaliOptions options, @Nonnull DexFile inputDexFile) throws IOException {
if (options.bootClassPathEntries.isEmpty() &&
(options.deodex || options.registerInfo != 0 || options.normalizeVirtualMethods)) {
if (inputDexFile instanceof DexBackedOdexFile) {
options.bootClassPathEntries = ((DexBackedOdexFile)inputDexFile).getDependencies();
} else {
options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel,
options.experimental);
}
}
if (options.customInlineDefinitions == null && inputDexFile instanceof DexBackedOdexFile) {
options.inlineResolver =
InlineMethodResolver.createInlineMethodResolver(
((DexBackedOdexFile)inputDexFile).getOdexVersion());
}
boolean errorOccurred = false;
if (options.disassemble) {
errorOccurred = !baksmali.disassembleDexFile(inputDexFile, options);
}
if (options.dump) {
if (!(inputDexFile instanceof DexBackedDexFile)) {
throw new IllegalArgumentException("Annotated hex-dumps require a DexBackedDexFile");
}
dump.dump((DexBackedDexFile)inputDexFile, options.dumpFileName, options.apiLevel);
}
return !errorOccurred;
}
/**
* Run!
*/
public static void main(String[] args) throws IOException {
Locale locale = new Locale("en", "US");
Locale.setDefault(locale);
CommandLineParser parser = new PosixParser();
CommandLine commandLine;
try {
commandLine = parser.parse(options, args);
} catch (ParseException ex) {
usage();
return;
}
baksmaliOptions options = new baksmaliOptions();
String[] remainingArgs = commandLine.getArgs();
Option[] clOptions = commandLine.getOptions();
for (int i=0; i<clOptions.length; i++) {
Option option = clOptions[i];
String opt = option.getOpt();
switch (opt.charAt(0)) {
case 'v':
version();
return;
case '?':
while (++i < clOptions.length) {
if (clOptions[i].getOpt().charAt(0) == '?') {
usage(true);
return;
}
}
usage(false);
return;
case 'o':
options.outputDirectory = commandLine.getOptionValue("o");
break;
case 'p':
options.noParameterRegisters = true;
break;
case 'l':
options.useLocalsDirective = true;
break;
case 's':
options.useSequentialLabels = true;
break;
case 'b':
options.outputDebugInfo = false;
break;
case 'd':
options.bootClassPathDirs.add(option.getValue());
break;
case 'f':
options.addCodeOffsets = true;
break;
case 'r':
String[] values = commandLine.getOptionValues('r');
int registerInfo = 0;
if (values == null || values.length == 0) {
registerInfo = baksmaliOptions.ARGS | baksmaliOptions.DEST;
} else {
for (String value: values) {
if (value.equalsIgnoreCase("ALL")) {
registerInfo |= baksmaliOptions.ALL;
} else if (value.equalsIgnoreCase("ALLPRE")) {
registerInfo |= baksmaliOptions.ALLPRE;
} else if (value.equalsIgnoreCase("ALLPOST")) {
registerInfo |= baksmaliOptions.ALLPOST;
} else if (value.equalsIgnoreCase("ARGS")) {
registerInfo |= baksmaliOptions.ARGS;
} else if (value.equalsIgnoreCase("DEST")) {
registerInfo |= baksmaliOptions.DEST;
} else if (value.equalsIgnoreCase("MERGE")) {
registerInfo |= baksmaliOptions.MERGE;
} else if (value.equalsIgnoreCase("FULLMERGE")) {
registerInfo |= baksmaliOptions.FULLMERGE;
} else {
usage();
return;
}
}
if ((registerInfo & baksmaliOptions.FULLMERGE) != 0) {
registerInfo &= ~baksmaliOptions.MERGE;
}
}
options.registerInfo = registerInfo;
break;
case 'c':
String bcp = commandLine.getOptionValue("c");
if (bcp != null && bcp.charAt(0) == ':') {
options.addExtraClassPath(bcp);
} else {
options.setBootClassPath(bcp);
}
break;
case 'x':
options.deodex = true;
break;
case 'X':
options.experimental = true;
break;
case 'm':
options.noAccessorComments = true;
break;
case 'a':
options.apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
break;
case 'j':
options.jobs = Integer.parseInt(commandLine.getOptionValue("j"));
break;
case 'i':
String rif = commandLine.getOptionValue("i");
options.setResourceIdFiles(rif);
break;
case 't':
options.useImplicitReferences = true;
break;
case 'e':
options.dexEntry = commandLine.getOptionValue("e");
break;
case 'k':
options.checkPackagePrivateAccess = true;
break;
case 'n':
options.normalizeVirtualMethods = true;
break;
case 'N':
options.disassemble = false;
break;
case 'D':
options.dump = true;
options.dumpFileName = commandLine.getOptionValue("D");
break;
case 'I':
options.ignoreErrors = true;
break;
case 'T':
options.customInlineDefinitions = new File(commandLine.getOptionValue("T"));
break;
default:
assert false;
}
}
if (remainingArgs.length != 1) {
usage();
return;
}
String inputDexPath = remainingArgs[0];
File dexFileFile = new File(inputDexPath);
if (!dexFileFile.exists()) {
System.err.println("Can't find the file " + inputDexPath);
System.exit(1);
}
//Read in and parse the dex file
DexBackedDexFile dexFile = null;
try {
dexFile = DexFileFactory.loadDexFile(dexFileFile, options.dexEntry, options.apiLevel, options.experimental);
} catch (MultipleDexFilesException ex) {
System.err.println(String.format("%s contains multiple dex files. You must specify which one to " +
"disassemble with the -e option", dexFileFile.getName()));
System.err.println("Valid entries include:");
for (OatDexFile oatDexFile: ex.oatFile.getDexFiles()) {
System.err.println(oatDexFile.filename);
}
System.exit(1);
}
if (dexFile.hasOdexOpcodes()) {
if (!options.deodex) {
System.err.println("Warning: You are disassembling an odex file without deodexing it. You");
System.err.println("won't be able to re-assemble the results unless you deodex it with the -x");
System.err.println("option");
options.allowOdex = true;
}
} else {
options.deodex = false;
}
if (options.dump) {
if (options.dumpFileName == null) {
options.dumpFileName = inputDexPath + ".dump";
}
}
try {
if (!run(options, dexFile)) {
System.exit(1);
}
} catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage());
System.exit(1);
}
}
/**
* Prints the usage message.
*/
private static void usage(boolean printDebugOptions) {
SmaliHelpFormatter formatter = new SmaliHelpFormatter();
int consoleWidth = ConsoleUtil.getConsoleWidth();
if (consoleWidth <= 0) {
consoleWidth = 80;
}
formatter.setWidth(consoleWidth);
formatter.printHelp("java -jar baksmali.jar [options] <dex-file>",
"disassembles and/or dumps a dex file", basicOptions, printDebugOptions?debugOptions:null);
}
private static void usage() {
usage(false);
}
/**
* Prints the version message.
*/
protected static void version() {
System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)");
System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
System.exit(0);
}
@SuppressWarnings("AccessStaticViaInstance")
private static void buildOptions() {
Option versionOption = OptionBuilder.withLongOpt("version")
.withDescription("prints the version then exits")
.create("v");
Option helpOption = OptionBuilder.withLongOpt("help")
.withDescription("prints the help message then exits. Specify twice for debug options")
.create("?");
Option outputDirOption = OptionBuilder.withLongOpt("output")
.withDescription("the directory where the disassembled files will be placed. The default is out")
.hasArg()
.withArgName("DIR")
.create("o");
Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers")
.withDescription("use the v<n> syntax instead of the p<n> syntax for registers mapped to method " +
"parameters")
.create("p");
Option deodexerantOption = OptionBuilder.withLongOpt("deodex")
.withDescription("deodex the given odex file. This option is ignored if the input file is not an " +
"odex file")
.create("x");
Option experimentalOption = OptionBuilder.withLongOpt("experimental")
.withDescription("enable experimental opcodes to be disassembled, even if they aren't necessarily supported in the Android runtime yet")
.create("X");
Option useLocalsOption = OptionBuilder.withLongOpt("use-locals")
.withDescription("output the .locals directive with the number of non-parameter registers, rather" +
" than the .register directive with the total number of register")
.create("l");
Option sequentialLabelsOption = OptionBuilder.withLongOpt("sequential-labels")
.withDescription("create label names using a sequential numbering scheme per label type, rather than " +
"using the bytecode address")
.create("s");
Option noDebugInfoOption = OptionBuilder.withLongOpt("no-debug-info")
.withDescription("don't write out debug info (.local, .param, .line, etc.)")
.create("b");
Option registerInfoOption = OptionBuilder.withLongOpt("register-info")
.hasOptionalArgs()
.withArgName("REGISTER_INFO_TYPES")
.withValueSeparator(',')
.withDescription("print the specificed type(s) of register information for each instruction. " +
"\"ARGS,DEST\" is the default if no types are specified.\nValid values are:\nALL: all " +
"pre- and post-instruction registers.\nALLPRE: all pre-instruction registers\nALLPOST: all " +
"post-instruction registers\nARGS: any pre-instruction registers used as arguments to the " +
"instruction\nDEST: the post-instruction destination register, if any\nMERGE: Any " +
"pre-instruction register has been merged from more than 1 different post-instruction " +
"register from its predecessors\nFULLMERGE: For each register that would be printed by " +
"MERGE, also show the incoming register types that were merged")
.create("r");
Option classPathOption = OptionBuilder.withLongOpt("bootclasspath")
.withDescription("A colon-separated list of bootclasspath jar/oat files to use for analysis. Add an " +
"initial colon to specify that the jars/oats should be appended to the default bootclasspath " +
"instead of replacing it")
.hasOptionalArg()
.withArgName("BOOTCLASSPATH")
.create("c");
Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir")
.withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " +
"directory")
.hasArg()
.withArgName("DIR")
.create("d");
Option codeOffsetOption = OptionBuilder.withLongOpt("code-offsets")
.withDescription("add comments to the disassembly containing the code offset for each address")
.create("f");
Option noAccessorCommentsOption = OptionBuilder.withLongOpt("no-accessor-comments")
.withDescription("don't output helper comments for synthetic accessors")
.create("m");
Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
.withDescription("The numeric api-level of the file being disassembled. If not " +
"specified, it defaults to 15 (ICS).")
.hasArg()
.withArgName("API_LEVEL")
.create("a");
Option jobsOption = OptionBuilder.withLongOpt("jobs")
.withDescription("The number of threads to use. Defaults to the number of cores available, up to a " +
"maximum of 6")
.hasArg()
.withArgName("NUM_THREADS")
.create("j");
Option resourceIdFilesOption = OptionBuilder.withLongOpt("resource-id-files")
.withDescription("the resource ID files to use, for analysis. A colon-separated list of prefix=file " +
"pairs. For example R=res/values/public.xml:" +
"android.R=$ANDROID_HOME/platforms/android-19/data/res/values/public.xml")
.hasArg()
.withArgName("FILES")
.create("i");
Option noImplicitReferencesOption = OptionBuilder.withLongOpt("implicit-references")
.withDescription("Use implicit (type-less) method and field references")
.create("t");
Option checkPackagePrivateAccessOption = OptionBuilder.withLongOpt("check-package-private-access")
.withDescription("When deodexing, use the package-private access check when calculating vtable " +
"indexes. It should only be needed for 4.2.0 odexes. The functionality was reverted for " +
"4.2.1.")
.create("k");
Option normalizeVirtualMethods = OptionBuilder.withLongOpt("normalize-virtual-methods")
.withDescription("Normalize virtual method references to the reference the base method.")
.create("n");
Option dumpOption = OptionBuilder.withLongOpt("dump-to")
.withDescription("dumps the given dex file into a single annotated dump file named FILE" +
" (<dexfile>.dump by default), along with the normal disassembly")
.hasOptionalArg()
.withArgName("FILE")
.create("D");
Option ignoreErrorsOption = OptionBuilder.withLongOpt("ignore-errors")
.withDescription("ignores any non-fatal errors that occur while disassembling/deodexing," +
" ignoring the class if needed, and continuing with the next class. The default" +
" behavior is to stop disassembling and exit once an error is encountered")
.create("I");
Option noDisassemblyOption = OptionBuilder.withLongOpt("no-disassembly")
.withDescription("suppresses the output of the disassembly")
.create("N");
Option inlineTableOption = OptionBuilder.withLongOpt("inline-table")
.withDescription("specify a file containing a custom inline method table to use for deodexing")
.hasArg()
.withArgName("FILE")
.create("T");
Option dexEntryOption = OptionBuilder.withLongOpt("dex-file")
.withDescription("looks for dex file named DEX_FILE, defaults to classes.dex")
.withArgName("DEX_FILE")
.hasArg()
.create("e");
basicOptions.addOption(versionOption);
basicOptions.addOption(helpOption);
basicOptions.addOption(outputDirOption);
basicOptions.addOption(noParameterRegistersOption);
basicOptions.addOption(deodexerantOption);
basicOptions.addOption(experimentalOption);
basicOptions.addOption(useLocalsOption);
basicOptions.addOption(sequentialLabelsOption);
basicOptions.addOption(noDebugInfoOption);
basicOptions.addOption(registerInfoOption);
basicOptions.addOption(classPathOption);
basicOptions.addOption(classPathDirOption);
basicOptions.addOption(codeOffsetOption);
basicOptions.addOption(noAccessorCommentsOption);
basicOptions.addOption(apiLevelOption);
basicOptions.addOption(jobsOption);
basicOptions.addOption(resourceIdFilesOption);
basicOptions.addOption(noImplicitReferencesOption);
basicOptions.addOption(dexEntryOption);
basicOptions.addOption(checkPackagePrivateAccessOption);
basicOptions.addOption(normalizeVirtualMethods);
debugOptions.addOption(dumpOption);
debugOptions.addOption(ignoreErrorsOption);
debugOptions.addOption(noDisassemblyOption);
debugOptions.addOption(inlineTableOption);
for (Object option: basicOptions.getOptions()) {
options.addOption((Option)option);
}
for (Object option: debugOptions.getOptions()) {
options.addOption((Option)option);
}
}
@Nonnull
private static List<String> getDefaultBootClassPathForApi(int apiLevel, boolean experimental) {
if (apiLevel < 9) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar");
} else if (apiLevel < 12) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel < 14) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/apache-xml.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel < 16) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar",
"/system/framework/filterfw.jar");
} else if (apiLevel < 21) {
// this is correct as of api 17/4.2.2
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar");
} else { // api >= 21
// TODO: verify, add new ones?
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/apache-xml.jar");
}
}
}

View File

@ -36,6 +36,7 @@ import com.google.common.io.Resources;
import junit.framework.Assert;
import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
@ -85,14 +86,14 @@ public class AnalysisTest {
public void runTest(String test, boolean registerInfo) throws IOException, URISyntaxException {
String dexFilePath = String.format("%s%sclasses.dex", test, File.separatorChar);
DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), 15, false);
DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), Opcodes.getDefault());
baksmaliOptions options = new baksmaliOptions();
BaksmaliOptions options = new BaksmaliOptions();
if (registerInfo) {
options.registerInfo = baksmaliOptions.ALL | baksmaliOptions.FULLMERGE;
options.registerInfo = BaksmaliOptions.ALL | BaksmaliOptions.FULLMERGE;
options.classPath = new ClassPath();
}
options.useImplicitReferences = false;
options.implicitReferences = false;
for (ClassDef classDef: dexFile.getClasses()) {
StringWriter stringWriter = new StringWriter();

View File

@ -48,10 +48,9 @@ import java.io.StringWriter;
public class BaksmaliTestUtils {
public static void assertSmaliCompiledEquals(String source, String expected,
baksmaliOptions options, boolean stripComments) throws IOException,
BaksmaliOptions options, boolean stripComments) throws IOException,
RecognitionException {
ClassDef classDef = SmaliTestUtils.compileSmali(source, options.apiLevel,
options.experimental);
ClassDef classDef = SmaliTestUtils.compileSmali(source, options.apiLevel);
// Remove unnecessary whitespace and optionally strip all comments from smali file
String normalizedActual = getNormalizedSmali(classDef, options, stripComments);
@ -62,13 +61,13 @@ public class BaksmaliTestUtils {
}
public static void assertSmaliCompiledEquals(String source, String expected,
baksmaliOptions options) throws IOException, RecognitionException {
BaksmaliOptions options) throws IOException, RecognitionException {
assertSmaliCompiledEquals(source, expected, options, false);
}
public static void assertSmaliCompiledEquals(String source, String expected)
throws IOException, RecognitionException {
baksmaliOptions options = new baksmaliOptions();
BaksmaliOptions options = new BaksmaliOptions();
assertSmaliCompiledEquals(source, expected, options);
}
@ -81,7 +80,7 @@ public class BaksmaliTestUtils {
}
@Nonnull
public static String getNormalizedSmali(@Nonnull ClassDef classDef, @Nonnull baksmaliOptions options,
public static String getNormalizedSmali(@Nonnull ClassDef classDef, @Nonnull BaksmaliOptions options,
boolean stripComments)
throws IOException {
StringWriter stringWriter = new StringWriter();

View File

@ -65,7 +65,7 @@ public abstract class DexTest {
}
@Nonnull
protected DexBackedDexFile getInputDexFile(@Nonnull String testName, @Nonnull baksmaliOptions options) {
protected DexBackedDexFile getInputDexFile(@Nonnull String testName, @Nonnull BaksmaliOptions options) {
try {
// Load file from resources as a stream
byte[] inputBytes = BaksmaliTestUtils.readResourceBytesFully(getInputFilename(testName));

View File

@ -57,10 +57,10 @@ public class DisassemblyTest extends DexTest {
}
protected void runTest(@Nonnull String testName) {
runTest(testName, new baksmaliOptions());
runTest(testName, new BaksmaliOptions());
}
protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) {
protected void runTest(@Nonnull String testName, @Nonnull BaksmaliOptions options) {
try {
DexBackedDexFile inputDex = getInputDexFile(testName, options);
Assert.assertEquals(1, inputDex.getClassCount());

View File

@ -42,7 +42,7 @@ import org.junit.Test;
public class FieldGapOrderTest extends DexTest {
@Test
public void testOldOrder() {
DexFile dexFile = getInputDexFile("FieldGapOrder", new baksmaliOptions());
DexFile dexFile = getInputDexFile("FieldGapOrder", new BaksmaliOptions());
Assert.assertEquals(3, dexFile.getClasses().size());
ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), false, 66);
@ -56,7 +56,7 @@ public class FieldGapOrderTest extends DexTest {
@Test
public void testNewOrder() {
DexFile dexFile = getInputDexFile("FieldGapOrder", new baksmaliOptions());
DexFile dexFile = getInputDexFile("FieldGapOrder", new BaksmaliOptions());
Assert.assertEquals(3, dexFile.getClasses().size());
ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), false, 67);

View File

@ -62,8 +62,8 @@ public class ImplicitReferenceTest {
"return-void\n" +
".end method\n";
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = true;
BaksmaliOptions options = new BaksmaliOptions();
options.implicitReferences = true;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@ -93,8 +93,8 @@ public class ImplicitReferenceTest {
" return-void\n" +
".end method\n";
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = false;
BaksmaliOptions options = new BaksmaliOptions();
options.implicitReferences = false;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@ -118,8 +118,8 @@ public class ImplicitReferenceTest {
".field public static field3:Ljava/lang/reflect/Method; = I()V\n" +
".field public static field4:Ljava/lang/Class; = I\n";
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = true;
BaksmaliOptions options = new BaksmaliOptions();
options.implicitReferences = true;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@ -143,8 +143,8 @@ public class ImplicitReferenceTest {
".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" +
".field public static field4:Ljava/lang/Class; = I\n";
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = false;
BaksmaliOptions options = new BaksmaliOptions();
options.implicitReferences = false;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@ -174,8 +174,8 @@ public class ImplicitReferenceTest {
" return-void\n" +
".end method\n";
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = true;
BaksmaliOptions options = new BaksmaliOptions();
options.implicitReferences = true;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@ -205,8 +205,8 @@ public class ImplicitReferenceTest {
" return-void\n" +
".end method\n";
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = false;
BaksmaliOptions options = new BaksmaliOptions();
options.implicitReferences = false;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@ -228,8 +228,8 @@ public class ImplicitReferenceTest {
".field public static field2:Ljava/lang/reflect/Field; = V:I\n" +
".field public static field3:Ljava/lang/reflect/Field; = I:I\n";
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = true;
BaksmaliOptions options = new BaksmaliOptions();
options.implicitReferences = true;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@ -251,8 +251,8 @@ public class ImplicitReferenceTest {
".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" +
".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I\n";
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = false;
BaksmaliOptions options = new BaksmaliOptions();
options.implicitReferences = false;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}

View File

@ -36,6 +36,6 @@ import org.junit.Test;
public class InterfaceOrderTest extends IdenticalRoundtripTest {
@Test
public void testInterfaceOrder() {
runTest("InterfaceOrder", new baksmaliOptions());
runTest("InterfaceOrder", new BaksmaliOptions());
}
}

View File

@ -69,10 +69,10 @@ public abstract class RoundtripTest {
}
protected void runTest(@Nonnull String testName) {
runTest(testName, new baksmaliOptions());
runTest(testName, new BaksmaliOptions());
}
protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) {
protected void runTest(@Nonnull String testName, @Nonnull BaksmaliOptions options) {
try {
// Load file from resources as a stream
String inputFilename = getInputFilename(testName);

View File

@ -101,14 +101,15 @@ subprojects {
guava: 'com.google.guava:guava:18.0',
findbugs: 'com.google.code.findbugs:jsr305:1.3.9',
junit: 'junit:junit:4.6',
mockito: 'org.mockito:mockito-core:1.+',
antlr_runtime: 'org.antlr:antlr-runtime:3.5.2',
antlr: 'org.antlr:antlr:3.5.2',
stringtemplate: 'org.antlr:stringtemplate:3.2.1',
commons_cli: 'commons-cli:commons-cli:1.2',
jflex_plugin: 'org.xbib.gradle.plugin:gradle-plugin-jflex:1.1.0',
proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1',
dx: 'com.google.android.tools:dx:1.7',
gson: 'com.google.code.gson:gson:2.3.1'
gson: 'com.google.code.gson:gson:2.3.1',
jcommander: 'com.beust:jcommander:1.48'
]
}

View File

@ -8,6 +8,7 @@ d7cbf8a6629942e7bd315ffae7e1c77b082f3e11 - 60
- return-void-barrier -> return-void-no-barrier
1412dfa4adcd511902e510fa0c948b168ab5840c - 61 (re-commit of f3251d12)
9d6bf69ad3012a9d843268fdd5325b6719b6d5f2 - 62
- classpath list was added
0de1133ba600f299b3d67938f650720d9f859eb2 - 63
07785bb98dc8bbe192970e0f4c2cafd338a8dc68 - 64
fa2c054b28d4b540c1b3651401a7a091282a015f - 65
@ -21,4 +22,27 @@ fab6788358dfb64e5c370611ddbbbffab0ed0553 - 67
6e2d5747d00697a25251d25dd33b953e54709507 - 68 (revert of 54b62480)
0747466fca310eedea5fc49e37d54f240a0b3c0f - 69 (re-commit of 54b62480)
501fd635a557645ab05f893c56e1f358e21bab82 - 70
99170c636dfae4908b102347cfe9f92bad1881cc - 71
99170c636dfae4908b102347cfe9f92bad1881cc - 71
3cfa4d05afa76e19ca99ec964b535a15c73683f0 - 72
- default methods
d9786b0e5be23ea0258405165098b4216579209c - 73
- fast class lookup table
a4f1220c1518074db18ca1044e9201492975750b - 74
625a64aad13905d8a2454bf3cc0e874487b110d5 - 75
- bootclasspath list was added
- class offsets moved out to a separate table
919f5536182890d2e03f59b961acf8f7c836ff61 - 74 (revert of 625a64aa)
9bdf108885a27ba05fae8501725649574d7c491b - 75 (re-commit of 625a64aa)
a62d2f04a6ecf804f8a78e722a6ca8ccb2dfa931 - 76
845e5064580bd37ad5014f7aa0d078be7265464d - 75 (revert of a62d2f04)
29d38e77c553c6cf71fc4dafe2d22b4e3f814872 - 76 (re-commit of 845e5064)
d1537b569b6cd18297c5e02d13cdd588c4366c51 - 77
61b28a17d9b6e8e998103646e98e4a9772e11927 - 78
9d07e3d128ccfa0ef7670feadd424a825e447d1d - 79
952e1e3710158982941fc70326e9fddc3021235d - 80
013e3f33495dcc31dba19c9de128d23ed441d7d3 - 81
87f3fcbd0db352157fc59148e94647ef21b73bce - 82
02b75806a80f8b75c3d6ba2ff97c995117630f36 - 83
4359e61927866c254bc2d701e3ea4c48de10b79c - 84
d549c28cfbddba945cb88857bcca3dce1414fb29 - 85
952dbb19cd094b8bfb01dbb33e0878db429e499a - 86

View File

@ -51,6 +51,7 @@ dependencies {
compile depends.guava
testCompile depends.junit
testCompile depends.mockito
accessorTestGenerator project('accessorTestGenerator')

View File

@ -31,14 +31,19 @@
package org.jf.dexlib2;
import com.google.common.base.MoreObjects;
import com.google.common.io.ByteStreams;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.dexbacked.ZipDexContainer;
import org.jf.dexlib2.dexbacked.ZipDexContainer.NotAZipFileException;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.iface.MultiDexContainer;
import org.jf.dexlib2.writer.pool.DexPool;
import org.jf.util.ExceptionWithContext;
@ -46,80 +51,45 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public final class DexFileFactory {
@Nonnull
public static DexBackedDexFile loadDexFile(@Nonnull String path, int api) throws IOException {
return loadDexFile(path, api, false);
public static DexBackedDexFile loadDexFile(@Nonnull String path, @Nonnull Opcodes opcodes) throws IOException {
return loadDexFile(new File(path), opcodes);
}
/**
* Loads a dex/apk/odex/oat file.
*
* For oat files with multiple dex files, the first will be opened. For zip/apk files, the "classes.dex" entry
* will be opened.
*
* @param file The file to open
* @param opcodes The set of opcodes to use
* @return A DexBackedDexFile for the given file
*
* @throws UnsupportedOatVersionException If file refers to an unsupported oat file
* @throws DexFileNotFoundException If file does not exist, if file is a zip file but does not have a "classes.dex"
* entry, or if file is an oat file that has no dex entries.
* @throws UnsupportedFileTypeException If file is not a valid dex/zip/odex/oat file, or if the "classes.dex" entry
* in a zip file is not a valid dex file
*/
@Nonnull
public static DexBackedDexFile loadDexFile(@Nonnull String path, int api, boolean experimental)
throws IOException {
return loadDexFile(new File(path), "classes.dex", Opcodes.forApi(api, experimental));
}
@Nonnull
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api) throws IOException {
return loadDexFile(dexFile, api, false);
}
@Nonnull
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api, boolean experimental)
throws IOException {
return loadDexFile(dexFile, null, Opcodes.forApi(api, experimental));
}
@Nonnull
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, @Nullable String dexEntry, int api,
boolean experimental) throws IOException {
return loadDexFile(dexFile, dexEntry, Opcodes.forApi(api, experimental));
}
@Nonnull
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, @Nullable String dexEntry,
@Nonnull Opcodes opcodes) throws IOException {
ZipFile zipFile = null;
boolean isZipFile = false;
try {
zipFile = new ZipFile(dexFile);
// if we get here, it's safe to assume we have a zip file
isZipFile = true;
String zipEntryName = MoreObjects.firstNonNull(dexEntry, "classes.dex");
ZipEntry zipEntry = zipFile.getEntry(zipEntryName);
if (zipEntry == null) {
throw new DexFileNotFound("zip file %s does not contain a %s file", dexFile.getName(), zipEntryName);
}
long fileLength = zipEntry.getSize();
if (fileLength < 40) {
throw new ExceptionWithContext("The %s file in %s is too small to be a valid dex file",
zipEntryName, dexFile.getName());
} else if (fileLength > Integer.MAX_VALUE) {
throw new ExceptionWithContext("The %s file in %s is too large to read in",
zipEntryName, dexFile.getName());
}
byte[] dexBytes = new byte[(int)fileLength];
ByteStreams.readFully(zipFile.getInputStream(zipEntry), dexBytes);
return new DexBackedDexFile(opcodes, dexBytes);
} catch (IOException ex) {
// don't continue on if we know it's a zip file
if (isZipFile) {
throw ex;
}
} finally {
if (zipFile != null) {
try {
zipFile.close();
} catch (IOException ex) {
// just eat it
}
}
public static DexBackedDexFile loadDexFile(@Nonnull File file, @Nonnull Opcodes opcodes) throws IOException {
if (!file.exists()) {
throw new DexFileNotFoundException("%s does not exist", file.getName());
}
InputStream inputStream = new BufferedInputStream(new FileInputStream(dexFile));
try {
ZipDexContainer container = new ZipDexContainer(file, opcodes);
return new DexEntryFinder(file.getPath(), container).findEntry("classes.dex", true);
} catch (NotAZipFileException ex) {
// eat it and continue
}
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
try {
try {
return DexBackedDexFile.fromInputStream(opcodes, inputStream);
@ -127,14 +97,15 @@ public final class DexFileFactory {
// just eat it
}
// Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails
try {
return DexBackedOdexFile.fromInputStream(opcodes, inputStream);
} catch (DexBackedOdexFile.NotAnOdexFile ex) {
// just eat it
}
// Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream
// back to the same position, if they fails
OatFile oatFile = null;
try {
oatFile = OatFile.fromInputStream(inputStream);
@ -150,71 +121,181 @@ public final class DexFileFactory {
List<OatDexFile> oatDexFiles = oatFile.getDexFiles();
if (oatDexFiles.size() == 0) {
throw new DexFileNotFound("Oat file %s contains no dex files", dexFile.getName());
throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName());
}
if (dexEntry == null) {
if (oatDexFiles.size() > 1) {
throw new MultipleDexFilesException(oatFile);
}
return oatDexFiles.get(0);
} else {
// first check for an exact match
for (OatDexFile oatDexFile : oatFile.getDexFiles()) {
if (oatDexFile.filename.equals(dexEntry)) {
return oatDexFile;
}
}
if (!dexEntry.contains("/")) {
for (OatDexFile oatDexFile : oatFile.getDexFiles()) {
File oatEntryFile = new File(oatDexFile.filename);
if (oatEntryFile.getName().equals(dexEntry)) {
return oatDexFile;
}
}
}
throw new DexFileNotFound("oat file %s does not contain a dex file named %s",
dexFile.getName(), dexEntry);
}
return oatDexFiles.get(0);
}
} finally {
inputStream.close();
}
throw new ExceptionWithContext("%s is not an apk, dex, odex or oat file.", dexFile.getPath());
throw new UnsupportedFileTypeException("%s is not an apk, dex, odex or oat file.", file.getPath());
}
/**
* Loads a dex entry from a container format (zip/oat)
*
* This has two modes of operation, depending on the exactMatch parameter. When exactMatch is true, it will only
* load an entry whose name exactly matches that provided by the dexEntry parameter.
*
* When exactMatch is false, then it will search for any entry that dexEntry is a path suffix of. "path suffix"
* meaning all the path components in dexEntry must fully match the corresponding path components in the entry name,
* but some path components at the beginning of entry name can be missing.
*
* For example, if an oat file contains a "/system/framework/framework.jar:classes2.dex" entry, then the following
* will match (not an exhaustive list):
*
* "/system/framework/framework.jar:classes2.dex"
* "system/framework/framework.jar:classes2.dex"
* "framework/framework.jar:classes2.dex"
* "framework.jar:classes2.dex"
* "classes2.dex"
*
* Note that partial path components specifically don't match. So something like "work/framework.jar:classes2.dex"
* would not match.
*
* If dexEntry contains an initial slash, it will be ignored for purposes of this suffix match -- but not when
* performing an exact match.
*
* If multiple entries match the given dexEntry, a MultipleMatchingDexEntriesException will be thrown
*
* @param file The container file. This must be either a zip (apk) file or an oat file.
* @param dexEntry The name of the entry to load. This can either be the exact entry name, if exactMatch is true,
* or it can be a path suffix.
* @param exactMatch If true, dexE
* @param opcodes The set of opcodes to use
* @return A DexBackedDexFile for the given entry
*
* @throws UnsupportedOatVersionException If file refers to an unsupported oat file
* @throws DexFileNotFoundException If the file does not exist, or if no matching entry could be found
* @throws UnsupportedFileTypeException If file is not a valid zip/oat file, or if the matching entry is not a
* valid dex file
* @throws MultipleMatchingDexEntriesException If multiple entries match the given dexEntry
*/
public static DexBackedDexFile loadDexEntry(@Nonnull File file, @Nonnull String dexEntry,
boolean exactMatch, @Nonnull Opcodes opcodes) throws IOException {
if (!file.exists()) {
throw new DexFileNotFoundException("Container file %s does not exist", file.getName());
}
try {
ZipDexContainer container = new ZipDexContainer(file, opcodes);
return new DexEntryFinder(file.getPath(), container).findEntry(dexEntry, exactMatch);
} catch (NotAZipFileException ex) {
// eat it and continue
}
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
try {
OatFile oatFile = null;
try {
oatFile = OatFile.fromInputStream(inputStream);
} catch (NotAnOatFileException ex) {
// just eat it
}
if (oatFile != null) {
if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) {
throw new UnsupportedOatVersionException(oatFile);
}
List<OatDexFile> oatDexFiles = oatFile.getDexFiles();
if (oatDexFiles.size() == 0) {
throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName());
}
return new DexEntryFinder(file.getPath(), oatFile).findEntry(dexEntry, exactMatch);
}
} finally {
inputStream.close();
}
throw new UnsupportedFileTypeException("%s is not an apk or oat file.", file.getPath());
}
/**
* Loads a file containing 1 or more dex files
*
* If the given file is a dex or odex file, it will return a MultiDexContainer containing that single entry.
* Otherwise, for an oat or zip file, it will return an OatFile or ZipDexContainer respectively.
*
* @param file The file to open
* @param opcodes The set of opcodes to use
* @return A MultiDexContainer
* @throws DexFileNotFoundException If the given file does not exist
* @throws UnsupportedFileTypeException If the given file is not a valid dex/zip/odex/oat file
*/
public static MultiDexContainer<? extends DexBackedDexFile> loadDexContainer(
@Nonnull File file, @Nonnull final Opcodes opcodes) throws IOException {
if (!file.exists()) {
throw new DexFileNotFoundException("%s does not exist", file.getName());
}
ZipDexContainer zipDexContainer = new ZipDexContainer(file, opcodes);
if (zipDexContainer.isZipFile()) {
return zipDexContainer;
}
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
try {
try {
DexBackedDexFile dexFile = DexBackedDexFile.fromInputStream(opcodes, inputStream);
return new SingletonMultiDexContainer(file.getPath(), dexFile);
} catch (DexBackedDexFile.NotADexFile ex) {
// just eat it
}
try {
DexBackedOdexFile odexFile = DexBackedOdexFile.fromInputStream(opcodes, inputStream);
return new SingletonMultiDexContainer(file.getPath(), odexFile);
} catch (DexBackedOdexFile.NotAnOdexFile ex) {
// just eat it
}
// Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream
// back to the same position, if they fails
OatFile oatFile = null;
try {
oatFile = OatFile.fromInputStream(inputStream);
} catch (NotAnOatFileException ex) {
// just eat it
}
if (oatFile != null) {
// TODO: we should support loading earlier oat files, just not deodexing them
if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) {
throw new UnsupportedOatVersionException(oatFile);
}
return oatFile;
}
} finally {
inputStream.close();
}
throw new UnsupportedFileTypeException("%s is not an apk, dex, odex or oat file.", file.getPath());
}
/**
* Writes a DexFile out to disk
*
* @param path The path to write the dex file to
* @param dexFile a DexFile to write
*/
public static void writeDexFile(@Nonnull String path, @Nonnull DexFile dexFile) throws IOException {
DexPool.writeTo(path, dexFile);
}
private DexFileFactory() {}
public static class DexFileNotFound extends ExceptionWithContext {
public DexFileNotFound(@Nullable Throwable cause) {
super(cause);
}
public DexFileNotFound(@Nullable Throwable cause, @Nullable String message, Object... formatArgs) {
super(cause, message, formatArgs);
}
public DexFileNotFound(@Nullable String message, Object... formatArgs) {
public static class DexFileNotFoundException extends ExceptionWithContext {
public DexFileNotFoundException(@Nullable String message, Object... formatArgs) {
super(message, formatArgs);
}
}
public static class MultipleDexFilesException extends ExceptionWithContext {
@Nonnull public final OatFile oatFile;
public MultipleDexFilesException(@Nonnull OatFile oatFile) {
super("Oat file has multiple dex files.");
this.oatFile = oatFile;
}
}
public static class UnsupportedOatVersionException extends ExceptionWithContext {
@Nonnull public final OatFile oatFile;
@ -223,4 +304,155 @@ public final class DexFileFactory {
this.oatFile = oatFile;
}
}
public static class MultipleMatchingDexEntriesException extends ExceptionWithContext {
public MultipleMatchingDexEntriesException(@Nonnull String message, Object... formatArgs) {
super(String.format(message, formatArgs));
}
}
public static class UnsupportedFileTypeException extends ExceptionWithContext {
public UnsupportedFileTypeException(@Nonnull String message, Object... formatArgs) {
super(String.format(message, formatArgs));
}
}
/**
* Matches two entries fully, ignoring any initial slash, if any
*/
private static boolean fullEntryMatch(@Nonnull String entry, @Nonnull String targetEntry) {
if (entry.equals(targetEntry)) {
return true;
}
if (entry.charAt(0) == '/') {
entry = entry.substring(1);
}
if (targetEntry.charAt(0) == '/') {
targetEntry = targetEntry.substring(1);
}
return entry.equals(targetEntry);
}
/**
* Performs a partial match against entry and targetEntry.
*
* This is considered a partial match if targetEntry is a suffix of entry, and if the suffix starts
* on a path "part" (ignoring the initial separator, if any). Both '/' and ':' are considered separators for this.
*
* So entry="/blah/blah/something.dex" and targetEntry="lah/something.dex" shouldn't match, but
* both targetEntry="blah/something.dex" and "/blah/something.dex" should match.
*/
private static boolean partialEntryMatch(String entry, String targetEntry) {
if (entry.equals(targetEntry)) {
return true;
}
if (!entry.endsWith(targetEntry)) {
return false;
}
// Make sure the first matching part is a full entry. We don't want to match "/blah/blah/something.dex" with
// "lah/something.dex", but both "/blah/something.dex" and "blah/something.dex" should match
char precedingChar = entry.charAt(entry.length() - targetEntry.length() - 1);
char firstTargetChar = targetEntry.charAt(0);
// This is a device path, so we should always use the linux separator '/', rather than the current platform's
// separator
return firstTargetChar == ':' || firstTargetChar == '/' || precedingChar == ':' || precedingChar == '/';
}
protected static class DexEntryFinder {
private final String filename;
private final MultiDexContainer<? extends DexBackedDexFile> dexContainer;
public DexEntryFinder(@Nonnull String filename,
@Nonnull MultiDexContainer<? extends DexBackedDexFile> dexContainer) {
this.filename = filename;
this.dexContainer = dexContainer;
}
@Nonnull
public DexBackedDexFile findEntry(@Nonnull String targetEntry, boolean exactMatch) throws IOException {
if (exactMatch) {
try {
DexBackedDexFile dexFile = dexContainer.getEntry(targetEntry);
if (dexFile == null) {
throw new DexFileNotFoundException("Could not find entry %s in %s.", targetEntry, filename);
}
return dexFile;
} catch (NotADexFile ex) {
throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file", targetEntry, filename);
}
}
// find all full and partial matches
List<String> fullMatches = Lists.newArrayList();
List<DexBackedDexFile> fullEntries = Lists.newArrayList();
List<String> partialMatches = Lists.newArrayList();
List<DexBackedDexFile> partialEntries = Lists.newArrayList();
for (String entry: dexContainer.getDexEntryNames()) {
if (fullEntryMatch(entry, targetEntry)) {
// We want to grab all full matches, regardless of whether they're actually a dex file.
fullMatches.add(entry);
fullEntries.add(dexContainer.getEntry(entry));
} else if (partialEntryMatch(entry, targetEntry)) {
partialMatches.add(entry);
partialEntries.add(dexContainer.getEntry(entry));
}
}
// full matches always take priority
if (fullEntries.size() == 1) {
try {
DexBackedDexFile dexFile = fullEntries.get(0);
assert dexFile != null;
return dexFile;
} catch (NotADexFile ex) {
throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file",
fullMatches.get(0), filename);
}
}
if (fullEntries.size() > 1) {
// This should be quite rare. This would only happen if an oat file has two entries that differ
// only by an initial path separator. e.g. "/blah/blah.dex" and "blah/blah.dex"
throw new MultipleMatchingDexEntriesException(String.format(
"Multiple entries in %s match %s: %s", filename, targetEntry,
Joiner.on(", ").join(fullMatches)));
}
if (partialEntries.size() == 0) {
throw new DexFileNotFoundException("Could not find a dex entry in %s matching %s",
filename, targetEntry);
}
if (partialEntries.size() > 1) {
throw new MultipleMatchingDexEntriesException(String.format(
"Multiple dex entries in %s match %s: %s", filename, targetEntry,
Joiner.on(", ").join(partialMatches)));
}
return partialEntries.get(0);
}
}
private static class SingletonMultiDexContainer implements MultiDexContainer<DexBackedDexFile> {
private final String entryName;
private final DexBackedDexFile dexFile;
public SingletonMultiDexContainer(@Nonnull String entryName, @Nonnull DexBackedDexFile dexFile) {
this.entryName = entryName;
this.dexFile = dexFile;
}
@Nonnull @Override public List<String> getDexEntryNames() throws IOException {
return ImmutableList.of(entryName);
}
@Nullable @Override public DexBackedDexFile getEntry(@Nonnull String entryName) throws IOException {
if (entryName.equals(this.entryName)) {
return dexFile;
}
return null;
}
}
}

View File

@ -330,8 +330,6 @@ public enum Opcode
public static final int JUMBO_OPCODE = 0x200;
//if the instruction can initialize an uninitialized object reference
public static final int CAN_INITIALIZE_REFERENCE = 0x400;
//if the instruction is experimental (not potentially supported by Android runtime yet)
public static final int EXPERIMENTAL = 0x800;
private static final int ALL_APIS = 0xFFFF0000;
@ -471,10 +469,6 @@ public enum Opcode
return (flags & CAN_INITIALIZE_REFERENCE) != 0;
}
public final boolean isExperimental() {
return (flags & EXPERIMENTAL) != 0;
}
private static class VersionConstraint {
@Nonnull public final Range<Integer> apiRange;
@Nonnull public final Range<Integer> artVersionRange;

View File

@ -39,6 +39,10 @@ import javax.annotation.Nullable;
import java.util.EnumMap;
import java.util.HashMap;
import static org.jf.dexlib2.VersionMap.NO_VERSION;
import static org.jf.dexlib2.VersionMap.mapApiToArtVersion;
import static org.jf.dexlib2.VersionMap.mapArtVersionToApi;
public class Opcodes {
/**
@ -52,37 +56,36 @@ public class Opcodes {
@Nonnull
public static Opcodes forApi(int api) {
return new Opcodes(api, VersionMap.mapApiToArtVersion(api), false);
}
@Nonnull
public static Opcodes forApi(int api, boolean experimental) {
return new Opcodes(api, VersionMap.mapApiToArtVersion(api), experimental);
return new Opcodes(api, NO_VERSION);
}
@Nonnull
public static Opcodes forArtVersion(int artVersion) {
return forArtVersion(artVersion, false);
return new Opcodes(NO_VERSION, artVersion);
}
/**
* @return a default Opcodes instance for when the exact Opcodes to use doesn't matter or isn't known
*/
@Nonnull
public static Opcodes forArtVersion(int artVersion, boolean experimental) {
return new Opcodes(VersionMap.mapArtVersionToApi(artVersion), artVersion, experimental);
public static Opcodes getDefault() {
// The last pre-art api
return forApi(20);
}
@Deprecated
public Opcodes(int api) {
this(api, false);
}
private Opcodes(int api, int artVersion) {
@Deprecated
public Opcodes(int api, boolean experimental) {
this(api, VersionMap.mapApiToArtVersion(api), experimental);
}
private Opcodes(int api, int artVersion, boolean experimental) {
this.api = api;
this.artVersion = artVersion;
if (api >= 21) {
this.api = api;
this.artVersion = mapApiToArtVersion(api);
} else if (artVersion >= 0 && artVersion < 39) {
this.api = mapArtVersionToApi(artVersion);
this.artVersion = artVersion;
} else {
this.api = api;
this.artVersion = artVersion;
}
opcodeValues = new EnumMap<Opcode, Short>(Opcode.class);
opcodesByName = Maps.newHashMap();
@ -104,7 +107,7 @@ public class Opcodes {
}
Short opcodeValue = versionToValueMap.get(version);
if (opcodeValue != null && (!opcode.isExperimental() || experimental)) {
if (opcodeValue != null) {
if (!opcode.format.isPayloadFormat) {
opcodesByValue[opcodeValue] = opcode;
}
@ -142,6 +145,6 @@ public class Opcodes {
}
public boolean isArt() {
return artVersion != VersionMap.NO_VERSION;
return artVersion != NO_VERSION;
}
}

View File

@ -35,16 +35,38 @@ public class VersionMap {
public static final int NO_VERSION = -1;
public static int mapArtVersionToApi(int artVersion) {
// TODO: implement this
return 20;
if (artVersion >= 79) {
return 24;
}
if (artVersion >= 64) {
return 23;
}
if (artVersion >= 45) {
return 22;
}
if (artVersion >= 39) {
return 21;
}
return 19;
}
public static int mapApiToArtVersion(int api) {
// TODO: implement this
if (api < 20) {
return NO_VERSION;
} else {
return 56;
switch (api) {
case 19:
case 20:
return 7;
case 21:
return 39;
case 22:
return 45;
case 23:
return 64;
case 24:
return 79;
}
if (api > 24) {
return 79;
}
return NO_VERSION;
}
}

View File

@ -54,7 +54,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
/**
* The actual instruction
*/
@Nullable
@Nonnull
protected Instruction instruction;
/**
@ -65,21 +65,25 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
/**
* Instructions that can pass on execution to this one during normal execution
*/
@Nonnull
protected final TreeSet<AnalyzedInstruction> predecessors = new TreeSet<AnalyzedInstruction>();
/**
* Instructions that can execution could pass on to next during normal execution
*/
@Nonnull
protected final LinkedList<AnalyzedInstruction> successors = new LinkedList<AnalyzedInstruction>();
/**
* This contains the register types *before* the instruction has executed
*/
@Nonnull
protected final RegisterType[] preRegisterMap;
/**
* This contains the register types *after* the instruction has executed
*/
@Nonnull
protected final RegisterType[] postRegisterMap;
/**
@ -94,8 +98,8 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
*/
protected final Instruction originalInstruction;
public AnalyzedInstruction(MethodAnalyzer methodAnalyzer, Instruction instruction, int instructionIndex,
int registerCount) {
public AnalyzedInstruction(@Nonnull MethodAnalyzer methodAnalyzer, @Nonnull Instruction instruction,
int instructionIndex, int registerCount) {
this.methodAnalyzer = methodAnalyzer;
this.instruction = instruction;
this.originalInstruction = instruction;
@ -150,18 +154,17 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
instruction = originalInstruction;
}
public int getSuccessorCount() {
return successors.size();
}
public List<AnalyzedInstruction> getSuccesors() {
@Nonnull
public List<AnalyzedInstruction> getSuccessors() {
return Collections.unmodifiableList(successors);
}
@Nonnull
public Instruction getInstruction() {
return instruction;
}
@Nonnull
public Instruction getOriginalInstruction() {
return originalInstruction;
}
@ -184,11 +187,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
if (predecessors.size() == 0) {
return false;
}
if (predecessors.first().instructionIndex == -1) {
return true;
}
return false;
return predecessors.first().instructionIndex == -1;
}
/*
@ -237,6 +236,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
* @param registerNumber the register number
* @return The register type resulting from merging the post-instruction register types from all predecessors
*/
@Nonnull
protected RegisterType getMergedPreRegisterTypeFromPredecessors(int registerNumber) {
RegisterType mergedRegisterType = null;
for (AnalyzedInstruction predecessor: predecessors) {
@ -249,6 +249,10 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
}
}
}
if (mergedRegisterType == null) {
// This is a start-of-method or unreachable instruction.
throw new IllegalStateException();
}
return mergedRegisterType;
}
/**
@ -275,10 +279,10 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
*
* This is used to set the register type for only one branch from a conditional jump.
*
* @param predecessor Which predecessor is being overriden
* @param registerNumber The register number of the register being overriden
* @param predecessor Which predecessor is being overridden
* @param registerNumber The register number of the register being overridden
* @param registerType The overridden register type
* @param verifiedInstructions
* @param verifiedInstructions A bit vector of instructions that have been verified
*
* @return true if the post-instruction register type for this instruction changed as a result of this override
*/
@ -309,7 +313,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
}
protected boolean isInvokeInit() {
if (instruction == null || !instruction.getOpcode().canInitializeReference()) {
if (!instruction.getOpcode().canInitializeReference()) {
return false;
}
@ -364,10 +368,10 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
return false;
}
if (instruction.getOpcode() == Opcode.IF_EQZ || instruction.getOpcode() == Opcode.IF_NEZ) {
if (getPredecessorCount() == 1 && (instruction.getOpcode() == Opcode.IF_EQZ ||
instruction.getOpcode() == Opcode.IF_NEZ)) {
AnalyzedInstruction previousInstruction = getPreviousInstruction();
if (previousInstruction != null &&
previousInstruction.instruction != null &&
previousInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF &&
registerNumber == ((Instruction22c)previousInstruction.instruction).getRegisterB() &&
MethodAnalyzer.canNarrowAfterInstanceOf(previousInstruction, this, methodAnalyzer.getClassPath())) {
@ -421,7 +425,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
return preRegisterMap[registerNumber];
}
public int compareTo(AnalyzedInstruction analyzedInstruction) {
public int compareTo(@Nonnull AnalyzedInstruction analyzedInstruction) {
if (instructionIndex < analyzedInstruction.instructionIndex) {
return -1;
} else if (instructionIndex == analyzedInstruction.instructionIndex) {

View File

@ -36,28 +36,18 @@ import com.google.common.base.Suppliers;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.DexFileFactory.DexFileNotFound;
import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.immutable.ImmutableDexFile;
import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ClassPath {
@Nonnull private final TypeProto unknownClass;
@ -114,7 +104,7 @@ public class ClassPath {
private static ClassProvider getBasicClasses() {
// fallbacks for some special classes that we assume are present
return new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), ImmutableSet.of(
return new DexClassProvider(new ImmutableDexFile(Opcodes.getDefault(), ImmutableSet.of(
new ReflectionClassDef(Class.class),
new ReflectionClassDef(Cloneable.class),
new ReflectionClassDef(Object.class),
@ -164,119 +154,6 @@ public class ClassPath {
return checkPackagePrivateAccess;
}
@Nonnull
public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
int api, boolean experimental) {
return fromClassPath(classPathDirs, classPath, dexFile, api, api == 17, experimental);
}
@Nonnull
public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
int api, boolean checkPackagePrivateAccess, boolean experimental) {
List<ClassProvider> providers = Lists.newArrayList();
int oatVersion = NOT_ART;
for (String classPathEntry: classPath) {
List<? extends DexFile> classPathDexFiles =
loadClassPathEntry(classPathDirs, classPathEntry, api, experimental);
if (oatVersion == NOT_ART) {
for (DexFile classPathDexFile: classPathDexFiles) {
if (classPathDexFile instanceof OatDexFile) {
oatVersion = ((OatDexFile)classPathDexFile).getOatVersion();
break;
}
}
}
for (DexFile classPathDexFile: classPathDexFiles) {
providers.add(new DexClassProvider(classPathDexFile));
}
}
providers.add(new DexClassProvider(dexFile));
return new ClassPath(providers, checkPackagePrivateAccess, oatVersion);
}
@Nonnull
public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
int api, boolean checkPackagePrivateAccess, boolean experimental,
int oatVersion) {
List<ClassProvider> providers = Lists.newArrayList();
for (String classPathEntry: classPath) {
List<? extends DexFile> classPathDexFiles =
loadClassPathEntry(classPathDirs, classPathEntry, api, experimental);
for (DexFile classPathDexFile: classPathDexFiles) {
providers.add(new DexClassProvider(classPathDexFile));
}
}
providers.add(new DexClassProvider(dexFile));
return new ClassPath(providers, checkPackagePrivateAccess, oatVersion);
}
private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
@Nonnull
private static List<? extends DexFile> loadClassPathEntry(@Nonnull Iterable<String> classPathDirs,
@Nonnull String bootClassPathEntry, int api,
boolean experimental) {
File rawEntry = new File(bootClassPathEntry);
// strip off the path - we only care about the filename
String entryName = rawEntry.getName();
// if it's a dalvik-cache entry, grab the name of the jar/apk
if (entryName.endsWith("@classes.dex")) {
Matcher m = dalvikCacheOdexPattern.matcher(entryName);
if (!m.find()) {
throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", bootClassPathEntry));
}
entryName = m.group(1);
}
int extIndex = entryName.lastIndexOf(".");
String baseEntryName;
if (extIndex == -1) {
baseEntryName = entryName;
} else {
baseEntryName = entryName.substring(0, extIndex);
}
for (String classPathDir: classPathDirs) {
String[] extensions;
if (entryName.endsWith(".oat")) {
extensions = new String[] { ".oat" };
} else {
extensions = new String[] { "", ".odex", ".jar", ".apk", ".zip" };
}
for (String ext: extensions) {
File file = new File(classPathDir, baseEntryName + ext);
if (file.exists() && file.isFile()) {
if (!file.canRead()) {
System.err.println(String.format(
"warning: cannot open %s for reading. Will continue looking.", file.getPath()));
} else {
try {
return ImmutableList.of(DexFileFactory.loadDexFile(file, api, experimental));
} catch (DexFileNotFound ex) {
// ignore and continue
} catch (MultipleDexFilesException ex) {
return ex.oatFile.getDexFiles();
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex,
"Error while reading boot class path entry \"%s\"", bootClassPathEntry);
}
}
}
}
}
throw new ExceptionWithContext("Cannot locate boot class path file %s", bootClassPathEntry);
}
private final Supplier<OdexedFieldInstructionMapper> fieldInstructionMapperSupplier = Suppliers.memoize(
new Supplier<OdexedFieldInstructionMapper>() {
@Override public OdexedFieldInstructionMapper get() {

View File

@ -0,0 +1,442 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.dexlib2.analysis;
import com.beust.jcommander.internal.Sets;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.DexFileFactory.UnsupportedFileTypeException;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.iface.MultiDexContainer;
import org.jf.dexlib2.iface.MultiDexContainer.MultiDexFile;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;
public class ClassPathResolver {
private final Iterable<String> classPathDirs;
private final Opcodes opcodes;
private final Set<File> loadedFiles = Sets.newHashSet();
private final List<ClassProvider> classProviders = Lists.newArrayList();
/**
* Constructs a new ClassPathResolver using a specified list of bootclasspath entries
*
* @param bootClassPathDirs A list of directories to search for boot classpath entries. Can be empty if all boot
* classpath entries are specified as local paths
* @param bootClassPathEntries A list of boot classpath entries to load. These can either be local paths, or
* device paths (e.g. "/system/framework/framework.jar"). The entry will be interpreted
* first as a local path. If not found as a local path, it will be interpreted as a
* partial or absolute device path, and will be searched for in bootClassPathDirs
* @param extraClassPathEntries A list of additional classpath entries to load. Can be empty. All entries must be
* local paths. Device paths are not supported.
* @param dexFile The dex file that the classpath will be used to analyze
* @throws IOException If any IOException occurs
* @throws ResolveException If any classpath entries cannot be loaded for some reason
*
* If null, a default bootclasspath is used,
* depending on the the file type of dexFile and the api level. If empty, no boot
* classpath entries will be loaded
*/
public ClassPathResolver(@Nonnull List<String> bootClassPathDirs, @Nonnull List<String> bootClassPathEntries,
@Nonnull List<String> extraClassPathEntries, @Nonnull DexFile dexFile)
throws IOException {
this(bootClassPathDirs, bootClassPathEntries, extraClassPathEntries, dexFile, dexFile.getOpcodes().api);
}
/**
* Constructs a new ClassPathResolver using a default list of bootclasspath entries
*
* @param bootClassPathDirs A list of directories to search for boot classpath entries
* @param extraClassPathEntries A list of additional classpath entries to load. Can be empty. All entries must be
* local paths. Device paths are not supported.
* @param dexFile The dex file that the classpath will be used to analyze
* @param apiLevel The api level of the device. This is used to select an appropriate set of boot classpath entries.
* @throws IOException If any IOException occurs
* @throws ResolveException If any classpath entries cannot be loaded for some reason
*
* If null, a default bootclasspath is used,
* depending on the the file type of dexFile and the api level. If empty, no boot
* classpath entries will be loaded
*/
public ClassPathResolver(@Nonnull List<String> bootClassPathDirs, @Nonnull List<String> extraClassPathEntries,
@Nonnull DexFile dexFile, int apiLevel)
throws IOException {
this(bootClassPathDirs, null, extraClassPathEntries, dexFile, apiLevel);
}
private ClassPathResolver(@Nonnull List<String> bootClassPathDirs, @Nullable List<String> bootClassPathEntries,
@Nonnull List<String> extraClassPathEntries, @Nonnull DexFile dexFile, int apiLevel)
throws IOException {
this.classPathDirs = bootClassPathDirs;
opcodes = dexFile.getOpcodes();
if (bootClassPathEntries == null) {
bootClassPathEntries = getDefaultBootClassPath(dexFile, apiLevel);
}
for (String entry : bootClassPathEntries) {
try {
loadLocalOrDeviceBootClassPathEntry(entry);
} catch (NoDexException ex) {
if (entry.endsWith(".jar")) {
String odexEntry = entry.substring(0, entry.length() - 4) + ".odex";
try {
loadLocalOrDeviceBootClassPathEntry(odexEntry);
} catch (NoDexException ex2) {
throw new ResolveException("Neither %s nor %s contain a dex file", entry, odexEntry);
} catch (NotFoundException ex2) {
throw new ResolveException(ex);
}
} else {
throw new ResolveException(ex);
}
} catch (NotFoundException ex) {
throw new ResolveException(ex);
}
}
for (String entry: extraClassPathEntries) {
// extra classpath entries must be specified using a local path, so we don't need to do the search through
// bootClassPathDirs
try {
loadLocalClassPathEntry(entry);
} catch (NoDexException ex) {
throw new ResolveException(ex);
}
}
if (dexFile instanceof MultiDexContainer.MultiDexFile) {
MultiDexContainer<? extends MultiDexFile> container = ((MultiDexFile)dexFile).getContainer();
for (String entry: container.getDexEntryNames()) {
classProviders.add(new DexClassProvider(container.getEntry(entry)));
}
} else {
classProviders.add(new DexClassProvider(dexFile));
}
}
@Nonnull
public List<ClassProvider> getResolvedClassProviders() {
return classProviders;
}
private boolean loadLocalClassPathEntry(@Nonnull String entry) throws NoDexException, IOException {
File entryFile = new File(entry);
if (entryFile.exists() && entryFile.isFile()) {
try {
loadEntry(entryFile, true);
return true;
} catch (UnsupportedFileTypeException ex) {
throw new ResolveException(ex, "Couldn't load classpath entry %s", entry);
}
}
return false;
}
private void loadLocalOrDeviceBootClassPathEntry(@Nonnull String entry)
throws IOException, NoDexException, NotFoundException {
// first, see if the entry is a valid local path
if (loadLocalClassPathEntry(entry)) {
return;
}
// It's not a local path, so let's try to resolve it as a device path, relative to one of the provided
// directories
List<String> pathComponents = splitDevicePath(entry);
Joiner pathJoiner = Joiner.on(File.pathSeparatorChar);
for (String directory: classPathDirs) {
File directoryFile = new File(directory);
if (!directoryFile.exists()) {
// TODO: print a warning in the baksmali frontend before we get here
continue;
}
for (int i=0; i<pathComponents.size(); i++) {
String partialPath = pathJoiner.join(pathComponents.subList(i, pathComponents.size()));
File entryFile = new File(directoryFile, partialPath);
if (entryFile.exists() && entryFile.isFile()) {
loadEntry(entryFile, true);
return;
}
}
}
throw new NotFoundException("Could not find classpath entry %s", entry);
}
private void loadEntry(@Nonnull File entryFile, boolean loadOatDependencies)
throws IOException, NoDexException {
if (loadedFiles.contains(entryFile)) {
return;
}
MultiDexContainer<? extends DexBackedDexFile> container;
try {
container = DexFileFactory.loadDexContainer(entryFile, opcodes);
} catch (UnsupportedFileTypeException ex) {
throw new ResolveException(ex);
}
List<String> entryNames = container.getDexEntryNames();
if (entryNames.size() == 0) {
throw new NoDexException("%s contains no dex file");
}
loadedFiles.add(entryFile);
for (String entryName: entryNames) {
classProviders.add(new DexClassProvider(container.getEntry(entryName)));
}
if (loadOatDependencies && container instanceof OatFile) {
List<String> oatDependencies = ((OatFile)container).getBootClassPath();
if (!oatDependencies.isEmpty()) {
try {
loadOatDependencies(entryFile.getParentFile(), oatDependencies);
} catch (NotFoundException ex) {
throw new ResolveException(ex, "Error while loading oat file %s", entryFile);
} catch (NoDexException ex) {
throw new ResolveException(ex, "Error while loading dependencies for oat file %s", entryFile);
}
}
}
}
@Nonnull
private static List<String> splitDevicePath(@Nonnull String path) {
return Lists.newArrayList(Splitter.on('/').split(path));
}
private void loadOatDependencies(@Nonnull File directory, @Nonnull List<String> oatDependencies)
throws IOException, NoDexException, NotFoundException {
// We assume that all oat dependencies are located in the same directory as the oat file
for (String oatDependency: oatDependencies) {
String oatDependencyName = getFilenameForOatDependency(oatDependency);
File file = new File(directory, oatDependencyName);
if (!file.exists()) {
throw new NotFoundException("Cannot find dependency %s in %s", oatDependencyName, directory);
}
loadEntry(file, false);
}
}
@Nonnull
private String getFilenameForOatDependency(String oatDependency) {
int index = oatDependency.lastIndexOf('/');
String dependencyLeaf = oatDependency.substring(index+1);
if (dependencyLeaf.endsWith(".art")) {
return dependencyLeaf.substring(0, dependencyLeaf.length() - 4) + ".oat";
}
return dependencyLeaf;
}
private static class NotFoundException extends Exception {
public NotFoundException(String message, Object... formatArgs) {
super(String.format(message, formatArgs));
}
}
private static class NoDexException extends Exception {
public NoDexException(String message, Object... formatArgs) {
super(String.format(message, formatArgs));
}
}
/**
* An error that occurred while resolving the classpath
*/
public static class ResolveException extends RuntimeException {
public ResolveException (String message, Object... formatArgs) {
super(String.format(message, formatArgs));
}
public ResolveException (Throwable cause) {
super(cause);
}
public ResolveException (Throwable cause, String message, Object... formatArgs) {
super(String.format(message, formatArgs), cause);
}
}
/**
* Returns the default boot class path for the given dex file and api level.
*/
@Nonnull
private static List<String> getDefaultBootClassPath(@Nonnull DexFile dexFile, int apiLevel) {
if (dexFile instanceof OatFile.OatDexFile) {
List<String> bcp = ((OatDexFile)dexFile).getContainer().getBootClassPath();
if (!bcp.isEmpty()) {
for (int i=0; i<bcp.size(); i++) {
String entry = bcp.get(i);
if (entry.endsWith(".art")) {
bcp.set(i, entry.substring(0, entry.length() - 4) + ".oat");
}
}
return bcp;
}
return Lists.newArrayList("boot.oat");
}
if (dexFile instanceof DexBackedOdexFile) {
return ((DexBackedOdexFile)dexFile).getDependencies();
}
if (apiLevel <= 8) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar");
} else if (apiLevel <= 11) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel <= 13) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/apache-xml.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel <= 15) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar",
"/system/framework/filterfw.jar");
} else if (apiLevel <= 17) {
// this is correct as of api 17/4.2.2
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar");
} else if (apiLevel <= 18) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar");
} else if (apiLevel <= 19) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/conscrypt.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/framework2.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar",
"/system/framework/webviewchromium.jar");
} else if (apiLevel <= 22) {
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/apache-xml.jar");
} else /*if (apiLevel <= 23)*/ {
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/apache-xml.jar",
"/system/framework/org.apache.http.legacy.boot.jar");
}
// TODO: update for N
}
}

View File

@ -39,12 +39,10 @@ import com.google.common.collect.*;
import com.google.common.primitives.Ints;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.analysis.util.TypeProtoUtils;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.Field;
import org.jf.dexlib2.iface.Method;
import org.jf.dexlib2.base.reference.BaseMethodReference;
import org.jf.dexlib2.iface.*;
import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.dexlib2.iface.reference.MethodReference;
import org.jf.dexlib2.immutable.ImmutableMethod;
import org.jf.dexlib2.util.MethodUtil;
import org.jf.util.AlignmentUtils;
import org.jf.util.ExceptionWithContext;
@ -53,6 +51,7 @@ import org.jf.util.SparseArray;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.Map.Entry;
/**
* A class "prototype". This contains things like the interfaces, the superclass, the vtable and the instance fields
@ -123,11 +122,18 @@ public class ClassProto implements TypeProto {
*/
@Nonnull
protected LinkedHashMap<String, ClassDef> getInterfaces() {
return interfacesSupplier.get();
if (!classPath.isArt() || classPath.oatVersion < 72) {
return preDefaultMethodInterfaceSupplier.get();
} else {
return postDefaultMethodInterfaceSupplier.get();
}
}
/**
* This calculates the interfaces in the order required for vtable generation for dalvik and pre-default method ART
*/
@Nonnull
private final Supplier<LinkedHashMap<String, ClassDef>> interfacesSupplier =
private final Supplier<LinkedHashMap<String, ClassDef>> preDefaultMethodInterfaceSupplier =
Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
@Override public LinkedHashMap<String, ClassDef> get() {
Set<String> unresolvedInterfaces = new HashSet<String>(0);
@ -149,7 +155,8 @@ public class ClassProto implements TypeProto {
ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
for (String superInterface: interfaceProto.getInterfaces().keySet()) {
if (!interfaces.containsKey(superInterface)) {
interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface));
interfaces.put(superInterface,
interfaceProto.getInterfaces().get(superInterface));
}
}
if (!interfaceProto.interfacesFullyResolved) {
@ -159,6 +166,7 @@ public class ClassProto implements TypeProto {
}
}
} catch (UnresolvedClassException ex) {
interfaces.put(type, null);
unresolvedInterfaces.add(type);
interfacesFullyResolved = false;
}
@ -197,6 +205,71 @@ public class ClassProto implements TypeProto {
}
});
/**
* This calculates the interfaces in the order required for vtable generation for post-default method ART
*/
@Nonnull
private final Supplier<LinkedHashMap<String, ClassDef>> postDefaultMethodInterfaceSupplier =
Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
@Override public LinkedHashMap<String, ClassDef> get() {
Set<String> unresolvedInterfaces = new HashSet<String>(0);
LinkedHashMap<String, ClassDef> interfaces = Maps.newLinkedHashMap();
String superclass = getSuperclass();
if (superclass != null) {
ClassProto superclassProto = (ClassProto) classPath.getClass(superclass);
for (String superclassInterface: superclassProto.getInterfaces().keySet()) {
interfaces.put(superclassInterface, null);
}
if (!superclassProto.interfacesFullyResolved) {
unresolvedInterfaces.addAll(superclassProto.getUnresolvedInterfaces());
interfacesFullyResolved = false;
}
}
try {
for (String interfaceType: getClassDef().getInterfaces()) {
if (!interfaces.containsKey(interfaceType)) {
ClassProto interfaceProto = (ClassProto)classPath.getClass(interfaceType);
try {
for (Entry<String, ClassDef> entry: interfaceProto.getInterfaces().entrySet()) {
if (!interfaces.containsKey(entry.getKey())) {
interfaces.put(entry.getKey(), entry.getValue());
}
}
} catch (UnresolvedClassException ex) {
interfaces.put(interfaceType, null);
unresolvedInterfaces.add(interfaceType);
interfacesFullyResolved = false;
}
if (!interfaceProto.interfacesFullyResolved) {
unresolvedInterfaces.addAll(interfaceProto.getUnresolvedInterfaces());
interfacesFullyResolved = false;
}
try {
ClassDef interfaceDef = classPath.getClassDef(interfaceType);
interfaces.put(interfaceType, interfaceDef);
} catch (UnresolvedClassException ex) {
interfaces.put(interfaceType, null);
unresolvedInterfaces.add(interfaceType);
interfacesFullyResolved = false;
}
}
}
} catch (UnresolvedClassException ex) {
interfaces.put(type, null);
unresolvedInterfaces.add(type);
interfacesFullyResolved = false;
}
if (unresolvedInterfaces.size() > 0) {
ClassProto.this.unresolvedInterfaces = unresolvedInterfaces;
}
return interfaces;
}
});
@Nonnull
protected Set<String> getUnresolvedInterfaces() {
if (unresolvedInterfaces == null) {
@ -379,7 +452,10 @@ public class ClassProto implements TypeProto {
}
public int findMethodIndexInVtable(@Nonnull MethodReference method) {
List<Method> vtable = getVtable();
return findMethodIndexInVtable(getVtable(), method);
}
private int findMethodIndexInVtable(@Nonnull List<Method> vtable, MethodReference method) {
for (int i=0; i<vtable.size(); i++) {
Method candidate = vtable.get(i);
if (MethodUtil.methodSignaturesMatch(candidate, method)) {
@ -392,7 +468,20 @@ public class ClassProto implements TypeProto {
return -1;
}
@Nonnull SparseArray<FieldReference> getInstanceFields() {
private int findMethodIndexInVtableReverse(@Nonnull List<Method> vtable, MethodReference method) {
for (int i=vtable.size() - 1; i>=0; i--) {
Method candidate = vtable.get(i);
if (MethodUtil.methodSignaturesMatch(candidate, method)) {
if (!classPath.shouldCheckPackagePrivateAccess() ||
AnalyzedMethodUtil.canAccess(this, candidate, true, false, false)) {
return i;
}
}
}
return -1;
}
@Nonnull public SparseArray<FieldReference> getInstanceFields() {
if (classPath.isArt()) {
return artInstanceFieldsSupplier.get();
} else {
@ -440,9 +529,7 @@ public class ClassProto implements TypeProto {
ClassProto superclass = null;
if (superclassType != null) {
superclass = (ClassProto) classPath.getClass(superclassType);
if (superclass != null) {
startFieldOffset = superclass.getNextFieldOffset();
}
startFieldOffset = superclass.getNextFieldOffset();
}
int fieldIndexMod;
@ -530,13 +617,11 @@ public class ClassProto implements TypeProto {
//add padding to align the wide fields, if needed
if (fieldTypes[i] == WIDE && !gotDouble) {
if (!gotDouble) {
if (fieldOffset % 8 != 0) {
assert fieldOffset % 8 == 4;
fieldOffset += 4;
}
gotDouble = true;
if (fieldOffset % 8 != 0) {
assert fieldOffset % 8 == 4;
fieldOffset += 4;
}
gotDouble = true;
}
instanceFields.append(fieldOffset, field);
@ -574,7 +659,7 @@ public class ClassProto implements TypeProto {
public static FieldGap newFieldGap(int offset, int size, int oatVersion) {
if (oatVersion >= 67) {
return new FieldGap(offset, size) {
@Override public int compareTo(FieldGap o) {
@Override public int compareTo(@Nonnull FieldGap o) {
int result = Ints.compare(o.size, size);
if (result != 0) {
return result;
@ -584,7 +669,7 @@ public class ClassProto implements TypeProto {
};
} else {
return new FieldGap(offset, size) {
@Override public int compareTo(FieldGap o) {
@Override public int compareTo(@Nonnull FieldGap o) {
int result = Ints.compare(size, o.size);
if (result != 0) {
return result;
@ -778,12 +863,18 @@ public class ClassProto implements TypeProto {
throw new ExceptionWithContext("Invalid type: %s", type);
}
@Nonnull List<Method> getVtable() {
return vtableSupplier.get();
@Nonnull public List<Method> getVtable() {
if (!classPath.isArt() || classPath.oatVersion < 72) {
return preDefaultMethodVtableSupplier.get();
} else if (classPath.oatVersion < 87) {
return buggyPostDefaultMethodVtableSupplier.get();
} else {
return postDefaultMethodVtableSupplier.get();
}
}
//TODO: check the case when we have a package private method that overrides an interface method
@Nonnull private final Supplier<List<Method>> vtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
@Nonnull private final Supplier<List<Method>> preDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
@Override public List<Method> get() {
List<Method> vtable = Lists.newArrayList();
@ -812,52 +903,315 @@ public class ClassProto implements TypeProto {
//iterate over the virtual methods in the current class, and only add them when we don't already have the
//method (i.e. if it was implemented by the superclass)
if (!isInterface()) {
addToVtable(getClassDef().getVirtualMethods(), vtable, true);
addToVtable(getClassDef().getVirtualMethods(), vtable, true, true);
// assume that interface method is implemented in the current class, when adding it to vtable
// otherwise it looks like that method is invoked on an interface, which fails Dalvik's optimization checks
for (ClassDef interfaceDef: getDirectInterfaces()) {
// We use the current class for any vtable method references that we add, rather than the interface, so
// we don't end up trying to call invoke-virtual using an interface, which will fail verification
Iterable<ClassDef> interfaces = getDirectInterfaces();
for (ClassDef interfaceDef: interfaces) {
List<Method> interfaceMethods = Lists.newArrayList();
for (Method interfaceMethod: interfaceDef.getVirtualMethods()) {
ImmutableMethod method = new ImmutableMethod(
type,
interfaceMethod.getName(),
interfaceMethod.getParameters(),
interfaceMethod.getReturnType(),
interfaceMethod.getAccessFlags(),
interfaceMethod.getAnnotations(),
interfaceMethod.getImplementation());
interfaceMethods.add(method);
interfaceMethods.add(new ReparentedMethod(interfaceMethod, type));
}
addToVtable(interfaceMethods, vtable, false);
addToVtable(interfaceMethods, vtable, false, true);
}
}
return vtable;
}
});
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods,
@Nonnull List<Method> vtable, boolean replaceExisting) {
List<? extends Method> methods = Lists.newArrayList(localMethods);
Collections.sort(methods);
/**
* This is the vtable supplier for a version of art that had buggy vtable calculation logic. In some cases it can
* produce multiple vtable entries for a given virtual method. This supplier duplicates this buggy logic in order to
* generate an identical vtable
*/
@Nonnull private final Supplier<List<Method>> buggyPostDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
@Override public List<Method> get() {
List<Method> vtable = Lists.newArrayList();
outer: for (Method virtualMethod: methods) {
for (int i=0; i<vtable.size(); i++) {
Method superMethod = vtable.get(i);
if (MethodUtil.methodSignaturesMatch(superMethod, virtualMethod)) {
if (!classPath.shouldCheckPackagePrivateAccess() ||
AnalyzedMethodUtil.canAccess(ClassProto.this, superMethod, true, false, false)) {
if (replaceExisting) {
vtable.set(i, virtualMethod);
//copy the virtual methods from the superclass
String superclassType;
try {
superclassType = getSuperclass();
} catch (UnresolvedClassException ex) {
vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
vtableFullyResolved = false;
return vtable;
}
if (superclassType != null) {
ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
vtable.addAll(superclass.getVtable());
// if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by
// this class should start, so we just propagate what we can from the parent and hope for the best.
if (!superclass.vtableFullyResolved) {
vtableFullyResolved = false;
return vtable;
}
}
//iterate over the virtual methods in the current class, and only add them when we don't already have the
//method (i.e. if it was implemented by the superclass)
if (!isInterface()) {
addToVtable(getClassDef().getVirtualMethods(), vtable, true, true);
List<String> interfaces = Lists.newArrayList(getInterfaces().keySet());
List<Method> defaultMethods = Lists.newArrayList();
List<Method> defaultConflictMethods = Lists.newArrayList();
List<Method> mirandaMethods = Lists.newArrayList();
final HashMap<MethodReference, Integer> methodOrder = Maps.newHashMap();
for (int i=interfaces.size()-1; i>=0; i--) {
String interfaceType = interfaces.get(i);
ClassDef interfaceDef = classPath.getClassDef(interfaceType);
for (Method interfaceMethod : interfaceDef.getVirtualMethods()) {
int vtableIndex = findMethodIndexInVtableReverse(vtable, interfaceMethod);
Method oldVtableMethod = null;
if (vtableIndex >= 0) {
oldVtableMethod = vtable.get(vtableIndex);
}
for (int j=0; j<vtable.size(); j++) {
Method candidate = vtable.get(j);
if (MethodUtil.methodSignaturesMatch(candidate, interfaceMethod)) {
if (!classPath.shouldCheckPackagePrivateAccess() ||
AnalyzedMethodUtil.canAccess(ClassProto.this, candidate, true, false, false)) {
if (interfaceMethodOverrides(interfaceMethod, candidate)) {
vtable.set(j, interfaceMethod);
}
}
}
}
if (vtableIndex >= 0) {
if (!isOverridableByDefaultMethod(vtable.get(vtableIndex))) {
continue;
}
}
int defaultMethodIndex = findMethodIndexInVtable(defaultMethods, interfaceMethod);
if (defaultMethodIndex >= 0) {
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
ClassProto existingInterface = (ClassProto)classPath.getClass(
defaultMethods.get(defaultMethodIndex).getDefiningClass());
if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
Method removedMethod = defaultMethods.remove(defaultMethodIndex);
defaultConflictMethods.add(removedMethod);
}
}
continue;
}
int defaultConflictMethodIndex = findMethodIndexInVtable(
defaultConflictMethods, interfaceMethod);
if (defaultConflictMethodIndex >= 0) {
// There's already a matching method in the conflict list, we don't need to do
// anything else
continue;
}
int mirandaMethodIndex = findMethodIndexInVtable(mirandaMethods, interfaceMethod);
if (mirandaMethodIndex >= 0) {
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
ClassProto existingInterface = (ClassProto)classPath.getClass(
mirandaMethods.get(mirandaMethodIndex).getDefiningClass());
if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
Method oldMethod = mirandaMethods.remove(mirandaMethodIndex);
int methodOrderValue = methodOrder.get(oldMethod);
methodOrder.put(interfaceMethod, methodOrderValue);
defaultMethods.add(interfaceMethod);
}
}
continue;
}
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
if (oldVtableMethod != null) {
if (!interfaceMethodOverrides(interfaceMethod, oldVtableMethod)) {
continue;
}
}
defaultMethods.add(interfaceMethod);
methodOrder.put(interfaceMethod, methodOrder.size());
} else {
// TODO: do we need to check interfaceMethodOverrides here?
if (oldVtableMethod == null) {
mirandaMethods.add(interfaceMethod);
methodOrder.put(interfaceMethod, methodOrder.size());
}
continue outer;
}
}
}
Comparator<MethodReference> comparator = new Comparator<MethodReference>() {
@Override public int compare(MethodReference o1, MethodReference o2) {
return Ints.compare(methodOrder.get(o1), methodOrder.get(o2));
}
};
// The methods should be in the same order within each list as they were iterated over.
// They can be misordered if, e.g. a method was originally added to the default list, but then moved
// to the conflict list.
Collections.sort(mirandaMethods, comparator);
Collections.sort(defaultMethods, comparator);
Collections.sort(defaultConflictMethods, comparator);
vtable.addAll(mirandaMethods);
vtable.addAll(defaultMethods);
vtable.addAll(defaultConflictMethods);
}
return vtable;
}
});
@Nonnull private final Supplier<List<Method>> postDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
@Override public List<Method> get() {
List<Method> vtable = Lists.newArrayList();
//copy the virtual methods from the superclass
String superclassType;
try {
superclassType = getSuperclass();
} catch (UnresolvedClassException ex) {
vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
vtableFullyResolved = false;
return vtable;
}
if (superclassType != null) {
ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
vtable.addAll(superclass.getVtable());
// if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by
// this class should start, so we just propagate what we can from the parent and hope for the best.
if (!superclass.vtableFullyResolved) {
vtableFullyResolved = false;
return vtable;
}
}
//iterate over the virtual methods in the current class, and only add them when we don't already have the
//method (i.e. if it was implemented by the superclass)
if (!isInterface()) {
addToVtable(getClassDef().getVirtualMethods(), vtable, true, true);
Iterable<ClassDef> interfaces = Lists.reverse(Lists.newArrayList(getDirectInterfaces()));
List<Method> defaultMethods = Lists.newArrayList();
List<Method> defaultConflictMethods = Lists.newArrayList();
List<Method> mirandaMethods = Lists.newArrayList();
final HashMap<MethodReference, Integer> methodOrder = Maps.newHashMap();
for (ClassDef interfaceDef: interfaces) {
for (Method interfaceMethod : interfaceDef.getVirtualMethods()) {
int vtableIndex = findMethodIndexInVtable(vtable, interfaceMethod);
if (vtableIndex >= 0) {
if (interfaceMethodOverrides(interfaceMethod, vtable.get(vtableIndex))) {
vtable.set(vtableIndex, interfaceMethod);
}
} else {
int defaultMethodIndex = findMethodIndexInVtable(defaultMethods, interfaceMethod);
if (defaultMethodIndex >= 0) {
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
ClassProto existingInterface = (ClassProto)classPath.getClass(
defaultMethods.get(defaultMethodIndex).getDefiningClass());
if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
Method removedMethod = defaultMethods.remove(defaultMethodIndex);
defaultConflictMethods.add(removedMethod);
}
}
continue;
}
int defaultConflictMethodIndex = findMethodIndexInVtable(
defaultConflictMethods, interfaceMethod);
if (defaultConflictMethodIndex >= 0) {
// There's already a matching method in the conflict list, we don't need to do
// anything else
continue;
}
int mirandaMethodIndex = findMethodIndexInVtable(mirandaMethods, interfaceMethod);
if (mirandaMethodIndex >= 0) {
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
ClassProto existingInterface = (ClassProto)classPath.getClass(
mirandaMethods.get(mirandaMethodIndex).getDefiningClass());
if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
Method oldMethod = mirandaMethods.remove(mirandaMethodIndex);
int methodOrderValue = methodOrder.get(oldMethod);
methodOrder.put(interfaceMethod, methodOrderValue);
defaultMethods.add(interfaceMethod);
}
}
continue;
}
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
defaultMethods.add(interfaceMethod);
methodOrder.put(interfaceMethod, methodOrder.size());
} else {
mirandaMethods.add(interfaceMethod);
methodOrder.put(interfaceMethod, methodOrder.size());
}
}
}
}
Comparator<MethodReference> comparator = new Comparator<MethodReference>() {
@Override public int compare(MethodReference o1, MethodReference o2) {
return Ints.compare(methodOrder.get(o1), methodOrder.get(o2));
}
};
// The methods should be in the same order within each list as they were iterated over.
// They can be misordered if, e.g. a method was originally added to the default list, but then moved
// to the conflict list.
Collections.sort(defaultMethods, comparator);
Collections.sort(defaultConflictMethods, comparator);
Collections.sort(mirandaMethods, comparator);
addToVtable(defaultMethods, vtable, false, false);
addToVtable(defaultConflictMethods, vtable, false, false);
addToVtable(mirandaMethods, vtable, false, false);
}
return vtable;
}
});
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods, @Nonnull List<Method> vtable,
boolean replaceExisting, boolean sort) {
if (sort) {
ArrayList<Method> methods = Lists.newArrayList(localMethods);
Collections.sort(methods);
localMethods = methods;
}
for (Method virtualMethod: localMethods) {
int vtableIndex = findMethodIndexInVtable(vtable, virtualMethod);
if (vtableIndex >= 0) {
if (replaceExisting) {
vtable.set(vtableIndex, virtualMethod);
}
} else {
// we didn't find an equivalent method, so add it as a new entry
vtable.add(virtualMethod);
}
}
});
}
private static byte getFieldType(@Nonnull FieldReference field) {
switch (field.getType().charAt(0)) {
@ -871,4 +1225,68 @@ public class ClassProto implements TypeProto {
return 2; //OTHER
}
}
private boolean isOverridableByDefaultMethod(@Nonnull Method method) {
ClassProto classProto = (ClassProto)classPath.getClass(method.getDefiningClass());
return classProto.isInterface();
}
/**
* Checks if the interface method overrides the virtual or interface method2
* @param method A Method from an interface
* @param method2 A Method from an interface or a class
* @return true if the interface method overrides the virtual or interface method2
*/
private boolean interfaceMethodOverrides(@Nonnull Method method, @Nonnull Method method2) {
ClassProto classProto = (ClassProto)classPath.getClass(method2.getDefiningClass());
if (classProto.isInterface()) {
ClassProto targetClassProto = (ClassProto)classPath.getClass(method.getDefiningClass());
return targetClassProto.implementsInterface(method2.getDefiningClass());
} else {
return false;
}
}
static class ReparentedMethod extends BaseMethodReference implements Method {
private final Method method;
private final String definingClass;
public ReparentedMethod(Method method, String definingClass) {
this.method = method;
this.definingClass = definingClass;
}
@Nonnull @Override public String getDefiningClass() {
return definingClass;
}
@Nonnull @Override public String getName() {
return method.getName();
}
@Nonnull @Override public List<? extends CharSequence> getParameterTypes() {
return method.getParameterTypes();
}
@Nonnull @Override public String getReturnType() {
return method.getReturnType();
}
@Nonnull @Override public List<? extends MethodParameter> getParameters() {
return method.getParameters();
}
@Override public int getAccessFlags() {
return method.getAccessFlags();
}
@Nonnull @Override public Set<? extends Annotation> getAnnotations() {
return method.getAnnotations();
}
@Nullable @Override public MethodImplementation getImplementation() {
return method.getImplementation();
}
}
}

View File

@ -1,180 +0,0 @@
/*
* Copyright 2013, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.dexlib2.analysis;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import org.apache.commons.cli.*;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.Field;
import org.jf.dexlib2.iface.Method;
import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.util.ConsoleUtil;
import org.jf.util.SparseArray;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
public class DumpFields {
private static final Options options;
static {
options = new Options();
buildOptions();
}
public static void main(String[] args) {
CommandLineParser parser = new PosixParser();
CommandLine commandLine;
try {
commandLine = parser.parse(options, args);
} catch (ParseException ex) {
usage();
return;
}
String[] remainingArgs = commandLine.getArgs();
Option[] parsedOptions = commandLine.getOptions();
ArrayList<String> bootClassPathDirs = Lists.newArrayList();
String outFile = "fields.txt";
int apiLevel = 15;
boolean experimental = false;
for (int i=0; i<parsedOptions.length; i++) {
Option option = parsedOptions[i];
String opt = option.getOpt();
switch (opt.charAt(0)) {
case 'd':
bootClassPathDirs.add(option.getValue());
break;
case 'o':
outFile = option.getValue();
break;
case 'a':
apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
break;
case 'X':
experimental = true;
break;
default:
assert false;
}
}
if (remainingArgs.length != 1) {
usage();
return;
}
String inputDexFileName = remainingArgs[0];
File dexFileFile = new File(inputDexFileName);
if (!dexFileFile.exists()) {
System.err.println("Can't find the file " + inputDexFileName);
System.exit(1);
}
try {
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, apiLevel, experimental);
Iterable<String> bootClassPaths = Splitter.on(":").split("core.jar:ext.jar:framework.jar:android.policy.jar:services.jar");
ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel, experimental);
FileOutputStream outStream = new FileOutputStream(outFile);
for (ClassDef classDef: dexFile.getClasses()) {
ClassProto classProto = (ClassProto) classPath.getClass(classDef);
SparseArray<FieldReference> fields = classProto.getInstanceFields();
String className = "Class " + classDef.getType() + " : " + fields.size() + " instance fields\n";
outStream.write(className.getBytes());
for (int i=0;i<fields.size();i++) {
String field = fields.keyAt(i) + ":" + fields.valueAt(i).getType() + " " + fields.valueAt(i).getName() + "\n";
outStream.write(field.getBytes());
}
outStream.write("\n".getBytes());
}
outStream.close();
} catch (IOException ex) {
System.out.println("IOException thrown when trying to open a dex file or write out vtables: " + ex);
}
}
/**
* Prints the usage message.
*/
private static void usage() {
int consoleWidth = ConsoleUtil.getConsoleWidth();
if (consoleWidth <= 0) {
consoleWidth = 80;
}
System.out.println("java -cp baksmali.jar org.jf.dexlib2.analysis.DumpFields -d path/to/framework/jar/files <dex-file>");
}
private static void buildOptions() {
Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir")
.withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " +
"directory")
.hasArg()
.withArgName("DIR")
.create("d");
Option outputFileOption = OptionBuilder.withLongOpt("out-file")
.withDescription("output file")
.hasArg()
.withArgName("FILE")
.create("o");
Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
.withDescription("The numeric api-level of the file being disassembled. If not " +
"specified, it defaults to 15 (ICS).")
.hasArg()
.withArgName("API_LEVEL")
.create("a");
Option experimentalOption = OptionBuilder.withLongOpt("experimental")
.withDescription("Enable dumping experimental opcodes, that aren't necessarily " +
"supported by the android runtime yet.")
.create("X");
options.addOption(classPathDirOption);
options.addOption(outputFileOption);
options.addOption(apiLevelOption);
options.addOption(experimentalOption);
}
}

View File

@ -1,184 +0,0 @@
/*
* Copyright 2013, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.dexlib2.analysis;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import org.apache.commons.cli.*;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.Method;
import org.jf.util.ConsoleUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class DumpVtables {
private static final Options options;
static {
options = new Options();
buildOptions();
}
public static void main(String[] args) {
CommandLineParser parser = new PosixParser();
CommandLine commandLine;
try {
commandLine = parser.parse(options, args);
} catch (ParseException ex) {
usage();
return;
}
String[] remainingArgs = commandLine.getArgs();
Option[] parsedOptions = commandLine.getOptions();
ArrayList<String> bootClassPathDirs = Lists.newArrayList();
String outFile = "vtables.txt";
int apiLevel = 15;
boolean experimental = false;
for (int i=0; i<parsedOptions.length; i++) {
Option option = parsedOptions[i];
String opt = option.getOpt();
switch (opt.charAt(0)) {
case 'd':
bootClassPathDirs.add(option.getValue());
break;
case 'o':
outFile = option.getValue();
break;
case 'a':
apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
break;
case 'X':
experimental = true;
break;
default:
assert false;
}
}
if (remainingArgs.length != 1) {
usage();
return;
}
String inputDexFileName = remainingArgs[0];
File dexFileFile = new File(inputDexFileName);
if (!dexFileFile.exists()) {
System.err.println("Can't find the file " + inputDexFileName);
System.exit(1);
}
try {
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, apiLevel, experimental);
Iterable<String> bootClassPaths = Splitter.on(":").split("core.jar:ext.jar:framework.jar:android.policy.jar:services.jar");
ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel, experimental);
FileOutputStream outStream = new FileOutputStream(outFile);
for (ClassDef classDef: dexFile.getClasses()) {
ClassProto classProto = (ClassProto) classPath.getClass(classDef);
List<Method> methods = classProto.getVtable();
String className = "Class " + classDef.getType() + " extends " + classDef.getSuperclass() + " : " + methods.size() + " methods\n";
outStream.write(className.getBytes());
for (int i=0;i<methods.size();i++) {
Method method = methods.get(i);
String methodString = i + ":" + method.getDefiningClass() + "->" + method.getName() + "(";
for (CharSequence parameter: method.getParameterTypes()) {
methodString += parameter;
}
methodString += ")" + method.getReturnType() + "\n";
outStream.write(methodString.getBytes());
}
outStream.write("\n".getBytes());
}
outStream.close();
} catch (IOException ex) {
System.out.println("IOException thrown when trying to open a dex file or write out vtables: " + ex);
}
}
/**
* Prints the usage message.
*/
private static void usage() {
int consoleWidth = ConsoleUtil.getConsoleWidth();
if (consoleWidth <= 0) {
consoleWidth = 80;
}
System.out.println("java -cp baksmali.jar org.jf.dexlib2.analysis.DumpVtables -d path/to/framework/jar/files <dex-file>");
}
private static void buildOptions() {
Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir")
.withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " +
"directory")
.hasArg()
.withArgName("DIR")
.create("d");
Option outputFileOption = OptionBuilder.withLongOpt("out-file")
.withDescription("output file")
.hasArg()
.withArgName("FILE")
.create("o");
Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
.withDescription("The numeric api-level of the file being disassembled. If not " +
"specified, it defaults to 15 (ICS).")
.hasArg()
.withArgName("API_LEVEL")
.create("a");
Option experimentalOption = OptionBuilder.withLongOpt("experimental")
.withDescription("Enable dumping experimental opcodes, that aren't necessarily " +
"supported by the android runtime yet.")
.create("X");
options.addOption(classPathDirOption);
options.addOption(outputFileOption);
options.addOption(apiLevelOption);
options.addOption(experimentalOption);
}
}

View File

@ -36,6 +36,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.Opcode;
import org.jf.dexlib2.base.reference.BaseMethodReference;
import org.jf.dexlib2.iface.*;
import org.jf.dexlib2.iface.instruction.*;
import org.jf.dexlib2.iface.instruction.formats.*;
@ -89,10 +90,10 @@ public class MethodAnalyzer {
@Nullable private AnalysisException analysisException = null;
//This is a dummy instruction that occurs immediately before the first real instruction. We can initialize the
//register types for this instruction to the parameter types, in order to have them propagate to all of its
//successors, e.g. the first real instruction, the first instructions in any exception handlers covering the first
//instruction, etc.
// This is a dummy instruction that occurs immediately before the first real instruction. We can initialize the
// register types for this instruction to the parameter types, in order to have them propagate to all of its
// successors, e.g. the first real instruction, the first instructions in any exception handlers covering the first
// instruction, etc.
private final AnalyzedInstruction startOfMethod;
public MethodAnalyzer(@Nonnull ClassPath classPath, @Nonnull Method method,
@ -110,27 +111,16 @@ public class MethodAnalyzer {
this.methodImpl = methodImpl;
//override AnalyzedInstruction and provide custom implementations of some of the methods, so that we don't
//have to handle the case this special case of instruction being null, in the main class
startOfMethod = new AnalyzedInstruction(this, null, -1, methodImpl.getRegisterCount()) {
public boolean setsRegister() {
return false;
// Override AnalyzedInstruction and provide custom implementations of some of the methods, so that we don't
// have to handle the case this special case of instruction being null, in the main class
startOfMethod = new AnalyzedInstruction(this, new ImmutableInstruction10x(Opcode.NOP), -1, methodImpl.getRegisterCount()) {
@Override protected boolean addPredecessor(AnalyzedInstruction predecessor) {
throw new UnsupportedOperationException();
}
@Override
public boolean setsWideRegister() {
return false;
}
@Override
public boolean setsRegister(int registerNumber) {
return false;
}
@Override
public int getDestinationRegister() {
assert false;
return -1;
@Override @Nonnull
public RegisterType getPredecessorRegisterType(@Nonnull AnalyzedInstruction predecessor, int registerNumber) {
throw new UnsupportedOperationException();
}
};
@ -141,6 +131,7 @@ public class MethodAnalyzer {
analyze();
}
@Nonnull
public ClassPath getClassPath() {
return classPath;
}
@ -1176,32 +1167,36 @@ public class MethodAnalyzer {
setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, castRegisterType);
}
private static boolean isNarrowingConversion(RegisterType originalType, RegisterType newType) {
if (originalType.type == null || newType.type == null) {
return false;
}
if (originalType.type.isInterface()) {
return newType.type.implementsInterface(originalType.type.getType());
} else {
TypeProto commonSuperclass = newType.type.getCommonSuperclass(originalType.type);
return commonSuperclass.getType().equals(originalType.type.getType());
}
}
static boolean canNarrowAfterInstanceOf(AnalyzedInstruction analyzedInstanceOfInstruction,
AnalyzedInstruction analyzedIfInstruction, ClassPath classPath) {
Instruction ifInstruction = analyzedIfInstruction.instruction;
assert analyzedIfInstruction.instruction != null;
if (((Instruction21t)ifInstruction).getRegisterA() == analyzedInstanceOfInstruction.getDestinationRegister()) {
Reference reference = ((Instruction22c)analyzedInstanceOfInstruction.getInstruction()).getReference();
RegisterType registerType = RegisterType.getRegisterType(classPath, (TypeReference)reference);
if (registerType.type != null && !registerType.type.isInterface()) {
int objectRegister = ((TwoRegisterInstruction)analyzedInstanceOfInstruction.getInstruction())
.getRegisterB();
try {
if (registerType.type != null && !registerType.type.isInterface()) {
int objectRegister = ((TwoRegisterInstruction)analyzedInstanceOfInstruction.getInstruction())
.getRegisterB();
RegisterType originalType = analyzedIfInstruction.getPreInstructionRegisterType(objectRegister);
RegisterType originalType = analyzedIfInstruction.getPreInstructionRegisterType(objectRegister);
if (originalType.type != null) {
// Only override if we're going from an interface to a class, or are going to a narrower class
if (originalType.type.isInterface()) {
return true;
} else {
TypeProto commonSuperclass = registerType.type.getCommonSuperclass(originalType.type);
// only if it's a narrowing conversion
if (commonSuperclass.getType().equals(originalType.type.getType())) {
return true;
}
}
return isNarrowingConversion(originalType, registerType);
}
} catch (UnresolvedClassException ex) {
return false;
}
}
return false;
@ -1216,16 +1211,47 @@ public class MethodAnalyzer {
private void analyzeIfEqzNez(@Nonnull AnalyzedInstruction analyzedInstruction) {
int instructionIndex = analyzedInstruction.getInstructionIndex();
if (instructionIndex > 0) {
AnalyzedInstruction prevAnalyzedInstruction = analyzedInstructions.valueAt(instructionIndex - 1);
if (prevAnalyzedInstruction.instruction != null &&
prevAnalyzedInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF) {
if (analyzedInstruction.getPredecessorCount() != 1) {
return;
}
AnalyzedInstruction prevAnalyzedInstruction = analyzedInstruction.getPredecessors().first();
if (prevAnalyzedInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF) {
if (canNarrowAfterInstanceOf(prevAnalyzedInstruction, analyzedInstruction, classPath)) {
// Propagate the original type to the failing branch, and the new type to the successful branch
int narrowingRegister = ((Instruction22c)prevAnalyzedInstruction.instruction).getRegisterB();
RegisterType originalType = analyzedInstruction.getPreInstructionRegisterType(narrowingRegister);
List<Integer> narrowingRegisters = Lists.newArrayList();
RegisterType newType = RegisterType.getRegisterType(classPath,
(TypeReference)((Instruction22c)prevAnalyzedInstruction.instruction).getReference());
if (instructionIndex > 1) {
// If we have something like:
// move-object/from16 v0, p1
// instance-of v2, v0, Lblah;
// if-eqz v2, :target
// Then we need to narrow both v0 AND p1
AnalyzedInstruction prevPrevAnalyzedInstruction =
analyzedInstructions.valueAt(instructionIndex - 2);
Opcode opcode = prevPrevAnalyzedInstruction.instruction.getOpcode();
if (opcode == Opcode.MOVE_OBJECT || opcode == Opcode.MOVE_OBJECT_16 ||
opcode == Opcode.MOVE_OBJECT_FROM16) {
TwoRegisterInstruction moveInstruction =
((TwoRegisterInstruction)prevPrevAnalyzedInstruction.instruction);
RegisterType originalType =
prevPrevAnalyzedInstruction.getPostInstructionRegisterType(
moveInstruction.getRegisterB());
if (originalType.type != null) {
if (isNarrowingConversion(originalType, newType)) {
narrowingRegisters.add(
((TwoRegisterInstruction)prevPrevAnalyzedInstruction.instruction).getRegisterB());
}
}
}
}
// Propagate the original type to the failing branch, and the new type to the successful branch
int narrowingRegister = ((Instruction22c)prevAnalyzedInstruction.instruction).getRegisterB();
narrowingRegisters.add(narrowingRegister);
RegisterType originalType = analyzedInstruction.getPreInstructionRegisterType(narrowingRegister);
AnalyzedInstruction fallthroughInstruction = analyzedInstructions.valueAt(
analyzedInstruction.getInstructionIndex() + 1);
@ -1233,16 +1259,18 @@ public class MethodAnalyzer {
((Instruction21t)analyzedInstruction.instruction).getCodeOffset();
AnalyzedInstruction branchInstruction = analyzedInstructions.get(nextAddress);
if (analyzedInstruction.instruction.getOpcode() == Opcode.IF_EQZ) {
overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction,
narrowingRegister, newType);
overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
narrowingRegister, originalType);
} else {
overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction,
narrowingRegister, originalType);
overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
narrowingRegister, newType);
for (int register: narrowingRegisters) {
if (analyzedInstruction.instruction.getOpcode() == Opcode.IF_EQZ) {
overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction,
register, newType);
overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
register, originalType);
} else {
overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction,
register, originalType);
overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
register, newType);
}
}
}
}
@ -1695,13 +1723,13 @@ public class MethodAnalyzer {
// fieldClass is now the first accessible class found. Now. we need to make sure that the field is
// actually valid for this class
resolvedField = classPath.getClass(fieldClass.getType()).getFieldByOffset(fieldOffset);
if (resolvedField == null) {
FieldReference newResolvedField = classPath.getClass(fieldClass.getType()).getFieldByOffset(fieldOffset);
if (newResolvedField == null) {
throw new ExceptionWithContext("Couldn't find accessible class while resolving field %s",
ReferenceUtil.getShortFieldDescriptor(resolvedField));
}
resolvedField = new ImmutableFieldReference(fieldClass.getType(), resolvedField.getName(),
resolvedField.getType());
resolvedField = new ImmutableFieldReference(fieldClass.getType(), newResolvedField.getName(),
newResolvedField.getType());
}
String fieldType = resolvedField.getType();
@ -1733,41 +1761,9 @@ public class MethodAnalyzer {
targetMethod = (MethodReference)instruction.getReference();
}
TypeProto typeProto = classPath.getClass(targetMethod.getDefiningClass());
int methodIndex;
try {
methodIndex = typeProto.findMethodIndexInVtable(targetMethod);
} catch (UnresolvedClassException ex) {
return true;
}
MethodReference replacementMethod = normalizeMethodReference(targetMethod);
if (methodIndex < 0) {
return true;
}
Method replacementMethod = typeProto.getMethodByVtableIndex(methodIndex);
assert replacementMethod != null;
while (true) {
String superType = typeProto.getSuperclass();
if (superType == null) {
break;
}
typeProto = classPath.getClass(superType);
Method resolvedMethod = typeProto.getMethodByVtableIndex(methodIndex);
if (resolvedMethod == null) {
break;
}
if (!resolvedMethod.equals(replacementMethod)) {
if (!AnalyzedMethodUtil.canAccess(typeProto, replacementMethod, true, true, true)) {
continue;
}
replacementMethod = resolvedMethod;
}
}
if (replacementMethod.equals(method)) {
if (replacementMethod == null || replacementMethod.equals(targetMethod)) {
return true;
}
@ -1839,7 +1835,9 @@ public class MethodAnalyzer {
// no need to check class access for invoke-super. A class can obviously access its superclass.
ClassDef thisClass = classPath.getClassDef(method.getDefiningClass());
if (!isSuper && !TypeUtils.canAccessClass(
if (classPath.getClass(resolvedMethod.getDefiningClass()).isInterface()) {
resolvedMethod = new ReparentedMethodReference(resolvedMethod, objectRegisterTypeProto.getType());
} else if (!isSuper && !TypeUtils.canAccessClass(
thisClass.getType(), classPath.getClassDef(resolvedMethod.getDefiningClass()))) {
// the class is not accessible. So we start looking at objectRegisterTypeProto (which may be different
@ -1860,13 +1858,20 @@ public class MethodAnalyzer {
MethodReference newResolvedMethod =
classPath.getClass(methodClass.getType()).getMethodByVtableIndex(methodIndex);
if (newResolvedMethod == null) {
// TODO: fix NPE here
throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s",
ReferenceUtil.getMethodDescriptor(resolvedMethod, true));
}
resolvedMethod = newResolvedMethod;
resolvedMethod = new ImmutableMethodReference(methodClass.getType(), resolvedMethod.getName(),
resolvedMethod.getParameterTypes(), resolvedMethod.getReturnType());
}
if (normalizeVirtualMethods) {
MethodReference replacementMethod = normalizeMethodReference(resolvedMethod);
if (replacementMethod != null) {
resolvedMethod = replacementMethod;
}
}
Instruction deodexedInstruction;
@ -1967,4 +1972,70 @@ public class MethodAnalyzer {
"pair because it is the last register.", registerNumber));
}
}
@Nullable
private MethodReference normalizeMethodReference(@Nonnull MethodReference methodRef) {
TypeProto typeProto = classPath.getClass(methodRef.getDefiningClass());
int methodIndex;
try {
methodIndex = typeProto.findMethodIndexInVtable(methodRef);
} catch (UnresolvedClassException ex) {
return null;
}
if (methodIndex < 0) {
return null;
}
ClassProto thisClass = (ClassProto)classPath.getClass(method.getDefiningClass());
Method replacementMethod = typeProto.getMethodByVtableIndex(methodIndex);
assert replacementMethod != null;
while (true) {
String superType = typeProto.getSuperclass();
if (superType == null) {
break;
}
typeProto = classPath.getClass(superType);
Method resolvedMethod = typeProto.getMethodByVtableIndex(methodIndex);
if (resolvedMethod == null) {
break;
}
if (!resolvedMethod.equals(replacementMethod)) {
if (!AnalyzedMethodUtil.canAccess(thisClass, resolvedMethod, false, false, true)) {
continue;
}
replacementMethod = resolvedMethod;
}
}
return replacementMethod;
}
private static class ReparentedMethodReference extends BaseMethodReference {
private final MethodReference baseReference;
private final String definingClass;
public ReparentedMethodReference(MethodReference baseReference, String definingClass) {
this.baseReference = baseReference;
this.definingClass = definingClass;
}
@Override @Nonnull public String getName() {
return baseReference.getName();
}
@Override @Nonnull public List<? extends CharSequence> getParameterTypes() {
return baseReference.getParameterTypes();
}
@Override @Nonnull public String getReturnType() {
return baseReference.getReturnType();
}
@Nonnull @Override public String getDefiningClass() {
return definingClass;
}
}
}

View File

@ -235,7 +235,7 @@ public class RegisterType {
case '[':
return getRegisterType(REFERENCE, classPath.getClass(type));
default:
throw new ExceptionWithContext("Invalid type: " + type);
throw new AnalysisException("Invalid type: " + type);
}
}

View File

@ -31,12 +31,43 @@
package org.jf.dexlib2.analysis.reflection.util;
import com.google.common.collect.ImmutableBiMap;
public class ReflectionUtils {
private static ImmutableBiMap<String, String> primitiveMap = ImmutableBiMap.<String, String>builder()
.put("boolean", "Z")
.put("int", "I")
.put("long", "J")
.put("double", "D")
.put("void", "V")
.put("float", "F")
.put("char", "C")
.put("short", "S")
.put("byte", "B")
.build();
public static String javaToDexName(String javaName) {
javaName = javaName.replace('.', '/');
if (javaName.length() > 1 && javaName.charAt(javaName.length()-1) != ';') {
javaName = 'L' + javaName + ';';
if (javaName.charAt(0) == '[') {
return javaName.replace('.', '/');
}
return javaName;
if (primitiveMap.containsKey(javaName)) {
return primitiveMap.get(javaName);
}
return 'L' + javaName.replace('.', '/') + ';';
}
public static String dexToJavaName(String dexName) {
if (dexName.charAt(0) == '[') {
return dexName.replace('/', '.');
}
if (primitiveMap.inverse().containsKey(dexName)) {
return primitiveMap.inverse().get(dexName);
}
return dexName.replace('/', '.').substring(1, dexName.length()-2);
}
}

View File

@ -32,6 +32,7 @@
package org.jf.dexlib2.base.reference;
import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.dexlib2.util.ReferenceUtil;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -64,4 +65,8 @@ public abstract class BaseFieldReference implements FieldReference {
if (res != 0) return res;
return getType().compareTo(o.getType());
}
@Override public String toString() {
return ReferenceUtil.getFieldDescriptor(this);
}
}

View File

@ -33,6 +33,7 @@ package org.jf.dexlib2.base.reference;
import com.google.common.collect.Ordering;
import org.jf.dexlib2.iface.reference.MethodProtoReference;
import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.util.CharSequenceUtils;
import org.jf.util.CollectionUtils;
@ -63,4 +64,8 @@ public abstract class BaseMethodProtoReference implements MethodProtoReference {
if (res != 0) return res;
return CollectionUtils.compareAsIterable(Ordering.usingToString(), getParameterTypes(), o.getParameterTypes());
}
@Override public String toString() {
return ReferenceUtil.getMethodProtoDescriptor(this);
}
}

View File

@ -33,6 +33,7 @@ package org.jf.dexlib2.base.reference;
import com.google.common.collect.Ordering;
import org.jf.dexlib2.iface.reference.MethodReference;
import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.util.CharSequenceUtils;
import org.jf.util.CollectionUtils;
@ -70,4 +71,8 @@ public abstract class BaseMethodReference implements MethodReference {
if (res != 0) return res;
return CollectionUtils.compareAsIterable(Ordering.usingToString(), getParameterTypes(), o.getParameterTypes());
}
@Override public String toString() {
return ReferenceUtil.getMethodDescriptor(this);
}
}

View File

@ -58,5 +58,5 @@ public abstract class BaseStringReference implements StringReference {
@Override public int length() { return getString().length(); }
@Override public char charAt(int index) { return getString().charAt(index); }
@Override public CharSequence subSequence(int start, int end) { return getString().subSequence(start, end); }
@Override public String toString() { return getString(); }
@Override @Nonnull public String toString() { return getString(); }
}

View File

@ -33,9 +33,15 @@ package org.jf.dexlib2.dexbacked;
import com.google.common.io.ByteStreams;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.ReferenceType;
import org.jf.dexlib2.dexbacked.raw.*;
import org.jf.dexlib2.dexbacked.reference.DexBackedFieldReference;
import org.jf.dexlib2.dexbacked.reference.DexBackedMethodReference;
import org.jf.dexlib2.dexbacked.reference.DexBackedStringReference;
import org.jf.dexlib2.dexbacked.reference.DexBackedTypeReference;
import org.jf.dexlib2.dexbacked.util.FixedSizeSet;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.iface.reference.Reference;
import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
@ -43,6 +49,8 @@ import javax.annotation.Nullable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.AbstractList;
import java.util.List;
import java.util.Set;
public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
@ -61,7 +69,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
private final int classCount;
private final int classStartOffset;
private DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
protected DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
super(buf, offset);
this.opcodes = opcodes;
@ -85,7 +93,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
}
public DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull BaseDexBuffer buf) {
this(opcodes, buf.buf);
this(opcodes, buf.buf, buf.baseOffset);
}
public DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, int offset) {
@ -96,6 +104,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
this(opcodes, buf, 0, true);
}
@Nonnull
public static DexBackedDexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is)
throws IOException {
if (!is.markSupported()) {
@ -148,7 +157,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
};
}
private static void verifyMagicAndByteOrder(@Nonnull byte[] buf, int offset) {
protected static void verifyMagicAndByteOrder(@Nonnull byte[] buf, int offset) {
if (!HeaderItem.verifyMagic(buf, offset)) {
StringBuilder sb = new StringBuilder("Invalid magic value:");
for (int i=0; i<8; i++) {
@ -265,6 +274,81 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
return getType(typeIndex);
}
public List<DexBackedStringReference> getStrings() {
return new AbstractList<DexBackedStringReference>() {
@Override public DexBackedStringReference get(int index) {
if (index < 0 || index >= getStringCount()) {
throw new IndexOutOfBoundsException();
}
return new DexBackedStringReference(DexBackedDexFile.this, index);
}
@Override public int size() {
return getStringCount();
}
};
}
public List<DexBackedTypeReference> getTypes() {
return new AbstractList<DexBackedTypeReference>() {
@Override public DexBackedTypeReference get(int index) {
if (index < 0 || index >= getTypeCount()) {
throw new IndexOutOfBoundsException();
}
return new DexBackedTypeReference(DexBackedDexFile.this, index);
}
@Override public int size() {
return getTypeCount();
}
};
}
public List<DexBackedMethodReference> getMethods() {
return new AbstractList<DexBackedMethodReference>() {
@Override public DexBackedMethodReference get(int index) {
if (index < 0 || index >= getMethodCount()) {
throw new IndexOutOfBoundsException();
}
return new DexBackedMethodReference(DexBackedDexFile.this, index);
}
@Override public int size() {
return getMethodCount();
}
};
}
public List<DexBackedFieldReference> getFields() {
return new AbstractList<DexBackedFieldReference>() {
@Override public DexBackedFieldReference get(int index) {
if (index < 0 || index >= getFieldCount()) {
throw new IndexOutOfBoundsException();
}
return new DexBackedFieldReference(DexBackedDexFile.this, index);
}
@Override public int size() {
return getFieldCount();
}
};
}
public List<? extends Reference> getReferences(int referenceType) {
switch (referenceType) {
case ReferenceType.STRING:
return getStrings();
case ReferenceType.TYPE:
return getTypes();
case ReferenceType.METHOD:
return getMethods();
case ReferenceType.FIELD:
return getFields();
default:
throw new IllegalArgumentException(String.format("Invalid reference type: %d", referenceType));
}
}
@Override
@Nonnull
public DexReader readerAt(int offset) {

View File

@ -129,7 +129,11 @@ public class DexBackedMethodImplementation implements MethodImplementation {
return DebugInfo.newOrEmpty(dexFile, 0, this);
}
if (debugOffset < 0) {
System.err.println("%s: Invalid debug offset");
System.err.println(String.format("%s: Invalid debug offset", method));
return DebugInfo.newOrEmpty(dexFile, 0, this);
}
if (debugOffset >= dexFile.buf.length) {
System.err.println(String.format("%s: Invalid debug offset", method));
return DebugInfo.newOrEmpty(dexFile, 0, this);
}
return DebugInfo.newOrEmpty(dexFile, debugOffset, this);

View File

@ -31,22 +31,29 @@
package org.jf.dexlib2.dexbacked;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.io.ByteStreams;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.dexbacked.OatFile.SymbolTable.Symbol;
import org.jf.dexlib2.dexbacked.raw.HeaderItem;
import org.jf.dexlib2.iface.MultiDexContainer;
import org.jf.util.AbstractForwardSequentialList;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class OatFile extends BaseDexBuffer {
public class OatFile extends BaseDexBuffer implements MultiDexContainer<OatDexFile> {
private static final byte[] ELF_MAGIC = new byte[] { 0x7f, 'E', 'L', 'F' };
private static final byte[] OAT_MAGIC = new byte[] { 'o', 'a', 't', '\n' };
private static final int MIN_ELF_HEADER_SIZE = 52;
@ -54,7 +61,7 @@ public class OatFile extends BaseDexBuffer {
// These are the "known working" versions that I have manually inspected the source for.
// Later version may or may not work, depending on what changed.
private static final int MIN_OAT_VERSION = 56;
private static final int MAX_OAT_VERSION = 71;
private static final int MAX_OAT_VERSION = 86;
public static final int UNSUPPORTED = 0;
public static final int SUPPORTED = 1;
@ -148,6 +155,18 @@ public class OatFile extends BaseDexBuffer {
return UNKNOWN;
}
@Nonnull
public List<String> getBootClassPath() {
if (getOatVersion() < 75) {
return ImmutableList.of();
}
String bcp = oatHeader.getKeyValue("bootclasspath");
if (bcp == null) {
return ImmutableList.of();
}
return Arrays.asList(bcp.split(":"));
}
@Nonnull
public List<OatDexFile> getDexFiles() {
return new AbstractForwardSequentialList<OatDexFile>() {
@ -156,44 +175,44 @@ public class OatFile extends BaseDexBuffer {
}
@Nonnull @Override public Iterator<OatDexFile> iterator() {
return new Iterator<OatDexFile>() {
int index = 0;
int offset = oatHeader.getDexListStart();
@Override public boolean hasNext() {
return index < size();
return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, OatDexFile>() {
@Nullable @Override public OatDexFile apply(DexEntry dexEntry) {
return dexEntry.getDexFile();
}
@Override public OatDexFile next() {
int filenameLength = readSmallUint(offset);
offset += 4;
// TODO: what is the correct character encoding?
String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII"));
offset += filenameLength;
offset += 4; // checksum
int dexOffset = readSmallUint(offset) + oatHeader.offset;
offset += 4;
int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET);
offset += 4 * classCount;
index++;
return new OatDexFile(dexOffset, filename);
}
@Override public void remove() {
throw new UnsupportedOperationException();
}
};
});
}
};
}
public class OatDexFile extends DexBackedDexFile {
@Nonnull @Override public List<String> getDexEntryNames() throws IOException {
return new AbstractForwardSequentialList<String>() {
@Override public int size() {
return oatHeader.getDexFileCount();
}
@Nonnull @Override public Iterator<String> iterator() {
return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, String>() {
@Nullable @Override public String apply(DexEntry dexEntry) {
return dexEntry.entryName;
}
});
}
};
}
@Nullable @Override public OatDexFile getEntry(@Nonnull String entryName) throws IOException {
DexEntryIterator iterator = new DexEntryIterator();
while (iterator.hasNext()) {
DexEntry entry = iterator.next();
if (entry.entryName.equals(entryName)) {
return entry.getDexFile();
}
}
return null;
}
public class OatDexFile extends DexBackedDexFile implements MultiDexContainer.MultiDexFile {
@Nonnull public final String filename;
public OatDexFile(int offset, @Nonnull String filename) {
@ -201,8 +220,12 @@ public class OatFile extends BaseDexBuffer {
this.filename = filename;
}
public int getOatVersion() {
return OatFile.this.getOatVersion();
@Nonnull @Override public String getEntryName() {
return filename;
}
@Nonnull @Override public OatFile getContainer() {
return OatFile.this;
}
@Override public boolean hasOdexOpcodes() {
@ -211,57 +234,87 @@ public class OatFile extends BaseDexBuffer {
}
private class OatHeader {
private final int offset;
private final int headerOffset;
public OatHeader(int offset) {
this.offset = offset;
this.headerOffset = offset;
}
public boolean isValid() {
for (int i=0; i<OAT_MAGIC.length; i++) {
if (buf[offset + i] != OAT_MAGIC[i]) {
if (buf[headerOffset + i] != OAT_MAGIC[i]) {
return false;
}
}
for (int i=4; i<7; i++) {
if (buf[offset + i] < '0' || buf[offset + i] > '9') {
if (buf[headerOffset + i] < '0' || buf[headerOffset + i] > '9') {
return false;
}
}
return buf[offset + 7] == 0;
return buf[headerOffset + 7] == 0;
}
public int getVersion() {
return Integer.valueOf(new String(buf, offset + 4, 3));
return Integer.valueOf(new String(buf, headerOffset + 4, 3));
}
public int getDexFileCount() {
return readSmallUint(offset + 20);
return readSmallUint(headerOffset + 20);
}
public int getKeyValueStoreSize() {
int version = getVersion();
if (version < 56) {
if (getVersion() < MIN_OAT_VERSION) {
throw new IllegalStateException("Unsupported oat version");
}
int fieldOffset = 17 * 4;
return readSmallUint(offset + fieldOffset);
return readSmallUint(headerOffset + fieldOffset);
}
public int getHeaderSize() {
int version = getVersion();
if (version >= 56) {
return 18*4 + getKeyValueStoreSize();
} else {
if (getVersion() < MIN_OAT_VERSION) {
throw new IllegalStateException("Unsupported oat version");
}
return 18*4 + getKeyValueStoreSize();
}
@Nullable
public String getKeyValue(@Nonnull String key) {
int size = getKeyValueStoreSize();
int offset = headerOffset + 18 * 4;
int endOffset = offset + size;
while (offset < endOffset) {
int keyStartOffset = offset;
while (offset < endOffset && buf[offset] != '\0') {
offset++;
}
if (offset >= endOffset) {
throw new InvalidOatFileException("Oat file contains truncated key value store");
}
int keyEndOffset = offset;
String k = new String(buf, keyStartOffset, keyEndOffset - keyStartOffset);
if (k.equals(key)) {
int valueStartOffset = ++offset;
while (offset < endOffset && buf[offset] != '\0') {
offset++;
}
if (offset >= endOffset) {
throw new InvalidOatFileException("Oat file contains truncated key value store");
}
int valueEndOffset = offset;
return new String(buf, valueStartOffset, valueEndOffset - valueStartOffset);
}
offset++;
}
return null;
}
public int getDexListStart() {
return offset + getHeaderSize();
return headerOffset + getHeaderSize();
}
}
@ -481,7 +534,64 @@ public class OatFile extends BaseDexBuffer {
return new String(buf, start, end-start, Charset.forName("US-ASCII"));
}
}
private class DexEntry {
public final String entryName;
public final int dexOffset;
public DexEntry(String entryName, int dexOffset) {
this.entryName = entryName;
this.dexOffset = dexOffset;
}
public OatDexFile getDexFile() {
return new OatDexFile(dexOffset, entryName);
}
}
private class DexEntryIterator implements Iterator<DexEntry> {
int index = 0;
int offset = oatHeader.getDexListStart();
@Override public boolean hasNext() {
return index < oatHeader.getDexFileCount();
}
@Override public DexEntry next() {
int filenameLength = readSmallUint(offset);
offset += 4;
// TODO: what is the correct character encoding?
String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII"));
offset += filenameLength;
offset += 4; // checksum
int dexOffset = readSmallUint(offset) + oatHeader.headerOffset;
offset += 4;
if (getOatVersion() >= 75) {
offset += 4; // offset to class offsets table
}
if (getOatVersion() >= 73) {
offset += 4; // lookup table offset
}
if (getOatVersion() < 75) {
// prior to 75, the class offsets are included here directly
int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET);
offset += 4 * classCount;
}
index++;
return new DexEntry(filename, dexOffset);
}
@Override public void remove() {
throw new UnsupportedOperationException();
}
}
public static class InvalidOatFileException extends RuntimeException {
@ -493,4 +603,5 @@ public class OatFile extends BaseDexBuffer {
public static class NotAnOatFileException extends RuntimeException {
public NotAnOatFileException() {}
}
}

View File

@ -0,0 +1,193 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.dexlib2.dexbacked;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
import org.jf.dexlib2.dexbacked.ZipDexContainer.ZipDexFile;
import org.jf.dexlib2.iface.MultiDexContainer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static org.jf.dexlib2.dexbacked.DexBackedDexFile.verifyMagicAndByteOrder;
/**
* Represents a zip file that contains dex files (i.e. an apk or jar file)
*/
public class ZipDexContainer implements MultiDexContainer<ZipDexFile> {
private final File zipFilePath;
private final Opcodes opcodes;
/**
* Constructs a new ZipDexContainer for the given zip file
*
* @param zipFilePath The path to the zip file
* @param opcodes The Opcodes instance to use when loading dex files from this container
*/
public ZipDexContainer(@Nonnull File zipFilePath, @Nonnull Opcodes opcodes) {
this.zipFilePath = zipFilePath;
this.opcodes = opcodes;
}
/**
* Gets a list of the names of dex files in this zip file.
*
* @return A list of the names of dex files in this zip file
*/
@Nonnull @Override public List<String> getDexEntryNames() throws IOException {
List<String> entryNames = Lists.newArrayList();
ZipFile zipFile = getZipFile();
try {
Enumeration<? extends ZipEntry> entriesEnumeration = zipFile.entries();
while (entriesEnumeration.hasMoreElements()) {
ZipEntry entry = entriesEnumeration.nextElement();
if (!isDex(zipFile, entry)) {
continue;
}
entryNames.add(entry.getName());
}
return entryNames;
} finally {
zipFile.close();
}
}
/**
* Loads a dex file from a specific named entry.
*
* @param entryName The name of the entry
* @return A ZipDexFile, or null if there is no entry with the given name
* @throws NotADexFile If the entry isn't a dex file
*/
@Nullable @Override public ZipDexFile getEntry(@Nonnull String entryName) throws IOException {
ZipFile zipFile = getZipFile();
try {
ZipEntry entry = zipFile.getEntry(entryName);
if (entry == null) {
return null;
}
return loadEntry(zipFile, entry);
} finally {
zipFile.close();
}
}
public boolean isZipFile() {
try {
getZipFile();
return true;
} catch (IOException ex) {
return false;
} catch (NotAZipFileException ex) {
return false;
}
}
public class ZipDexFile extends DexBackedDexFile implements MultiDexContainer.MultiDexFile {
private final String entryName;
protected ZipDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, @Nonnull String entryName) {
super(opcodes, buf, 0);
this.entryName = entryName;
}
@Nonnull @Override public String getEntryName() {
return entryName;
}
@Nonnull @Override public MultiDexContainer getContainer() {
return ZipDexContainer.this;
}
}
private boolean isDex(@Nonnull ZipFile zipFile, @Nonnull ZipEntry zipEntry) throws IOException {
InputStream inputStream = zipFile.getInputStream(zipEntry);
try {
inputStream.mark(44);
byte[] partialHeader = new byte[44];
try {
ByteStreams.readFully(inputStream, partialHeader);
} catch (EOFException ex) {
return false;
}
try {
verifyMagicAndByteOrder(partialHeader, 0);
} catch (NotADexFile ex) {
return false;
}
return true;
} finally {
inputStream.close();
}
}
private ZipFile getZipFile() throws IOException {
try {
return new ZipFile(zipFilePath);
} catch (IOException ex) {
throw new NotAZipFileException();
}
}
@Nonnull
private ZipDexFile loadEntry(@Nonnull ZipFile zipFile, @Nonnull ZipEntry zipEntry) throws IOException {
InputStream inputStream = zipFile.getInputStream(zipEntry);
try {
byte[] buf = ByteStreams.toByteArray(inputStream);
return new ZipDexFile(opcodes, buf, zipEntry.getName());
} finally {
inputStream.close();
}
}
public static class NotAZipFileException extends RuntimeException {
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.dexlib2.iface;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.List;
/**
* This class represents a dex container that can contain multiple, named dex files
*/
public interface MultiDexContainer<T extends DexFile> {
/**
* @return A list of the names of dex entries in this container
*/
@Nonnull List<String> getDexEntryNames() throws IOException;
/**
* Gets the dex entry with the given name
*
* @param entryName The name of the entry
* @return A DexFile, or null if no entry with that name is found
*/
@Nullable T getEntry(@Nonnull String entryName) throws IOException;
/**
* This class represents a dex file that is contained in a MultiDexContainer
*/
interface MultiDexFile extends DexFile {
/**
* @return The name of this entry within its container
*/
@Nonnull String getEntryName();
/**
* @return The MultiDexContainer that contains this dex file
*/
@Nonnull MultiDexContainer<? extends MultiDexFile> getContainer();
}
}

View File

@ -45,18 +45,6 @@ public class ImmutableDexFile implements DexFile {
@Nonnull protected final ImmutableSet<? extends ImmutableClassDef> classes;
@Nonnull private final Opcodes opcodes;
@Deprecated
public ImmutableDexFile(@Nullable Collection<? extends ClassDef> classes) {
this.classes = ImmutableClassDef.immutableSetOf(classes);
this.opcodes = Opcodes.forApi(19);
}
@Deprecated
public ImmutableDexFile(@Nullable ImmutableSet<? extends ImmutableClassDef> classes) {
this.classes = ImmutableUtils.nullToEmptySet(classes);
this.opcodes = Opcodes.forApi(19);
}
public ImmutableDexFile(@Nonnull Opcodes opcodes, @Nullable Collection<? extends ClassDef> classes) {
this.classes = ImmutableClassDef.immutableSetOf(classes);
this.opcodes = opcodes;

View File

@ -192,12 +192,12 @@ public abstract class DexWriter<
private int getDataSectionOffset() {
return HeaderItem.ITEM_SIZE +
stringSection.getItems().size() * StringIdItem.ITEM_SIZE +
typeSection.getItems().size() * TypeIdItem.ITEM_SIZE +
protoSection.getItems().size() * ProtoIdItem.ITEM_SIZE +
fieldSection.getItems().size() * FieldIdItem.ITEM_SIZE +
methodSection.getItems().size() * MethodIdItem.ITEM_SIZE +
classSection.getItems().size() * ClassDefItem.ITEM_SIZE;
stringSection.getItemCount() * StringIdItem.ITEM_SIZE +
typeSection.getItemCount() * TypeIdItem.ITEM_SIZE +
protoSection.getItemCount() * ProtoIdItem.ITEM_SIZE +
fieldSection.getItemCount() * FieldIdItem.ITEM_SIZE +
methodSection.getItemCount() * MethodIdItem.ITEM_SIZE +
classSection.getItemCount() * ClassDefItem.ITEM_SIZE;
}
@Nonnull
@ -227,6 +227,22 @@ public abstract class DexWriter<
return classReferences;
}
/**
* Checks whether any of the size-sensitive constant pools have overflowed.
*
* This checks whether the type, method, field pools are larger than 64k entries.
*
* Note that even if this returns true, it may still be possible to successfully write the dex file, if the
* overflowed items are not referenced anywhere that uses a 16-bit index
*
* @return true if any of the size-sensitive constant pools have overflowed
*/
public boolean hasOverflowed() {
return methodSection.getItemCount() > (1 << 16) ||
typeSection.getItemCount() > (1 << 16) ||
fieldSection.getItemCount() > (1 << 16);
}
public void writeTo(@Nonnull DexDataStore dest) throws IOException {
this.writeTo(dest, MemoryDeferredOutputStream.getFactory());
}

View File

@ -38,4 +38,5 @@ import java.util.Map;
public interface IndexSection<Key> {
int getItemIndex(@Nonnull Key key);
@Nonnull Collection<? extends Map.Entry<? extends Key, Integer>> getItems();
int getItemCount();
}

View File

@ -443,4 +443,8 @@ public class BuilderClassPool implements ClassSection<BuilderStringReference, Bu
}
};
}
@Override public int getItemCount() {
return internedItems.size();
}
}

View File

@ -104,4 +104,8 @@ public class BuilderFieldPool
}
};
}
@Override public int getItemCount() {
return internedItems.size();
}
}

View File

@ -112,6 +112,10 @@ class BuilderMethodPool implements MethodSection<BuilderStringReference, Builder
};
}
@Override public int getItemCount() {
return internedItems.size();
}
private static class MethodKey extends BaseMethodReference implements MethodReference {
@Nonnull private final String definingClass;
@Nonnull private final String name;

View File

@ -103,4 +103,8 @@ class BuilderProtoPool
}
};
}
@Override public int getItemCount() {
return internedItems.size();
}
}

View File

@ -86,4 +86,8 @@ class BuilderStringPool implements StringSection<BuilderStringReference, Builder
}
};
}
@Override public int getItemCount() {
return internedItems.size();
}
}

View File

@ -92,4 +92,8 @@ class BuilderTypePool implements TypeSection<BuilderStringReference, BuilderType
}
};
}
@Override public int getItemCount() {
return internedItems.size();
}
}

View File

@ -60,18 +60,6 @@ public class DexBuilder extends DexWriter<BuilderStringReference, BuilderStringR
@Nonnull private final BuilderContext context;
@Nonnull public static DexBuilder makeDexBuilder() {
BuilderContext context = new BuilderContext();
return new DexBuilder(Opcodes.forApi(20), context);
}
@Deprecated
@Nonnull
public static DexBuilder makeDexBuilder(int api) {
BuilderContext context = new BuilderContext();
return new DexBuilder(Opcodes.forApi(api), context);
}
@Nonnull public static DexBuilder makeDexBuilder(@Nonnull Opcodes opcodes) {
BuilderContext context = new BuilderContext();
return new DexBuilder(opcodes, context);

View File

@ -31,7 +31,6 @@
package org.jf.dexlib2.writer.pool;
import com.google.common.collect.Maps;
import org.jf.dexlib2.writer.IndexSection;
import org.jf.util.ExceptionWithContext;
@ -39,9 +38,7 @@ import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Map;
public abstract class BaseIndexPool<Key> implements IndexSection<Key> {
@Nonnull protected final Map<Key, Integer> internedItems = Maps.newHashMap();
public abstract class BaseIndexPool<Key> extends BasePool<Key, Integer> implements IndexSection<Key> {
@Nonnull @Override public Collection<? extends Map.Entry<? extends Key, Integer>> getItems() {
return internedItems.entrySet();
}

View File

@ -31,7 +31,6 @@
package org.jf.dexlib2.writer.pool;
import com.google.common.collect.Maps;
import org.jf.dexlib2.writer.OffsetSection;
import org.jf.util.ExceptionWithContext;
@ -39,9 +38,7 @@ import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Map;
public abstract class BaseOffsetPool<Key> implements OffsetSection<Key> {
@Nonnull protected final Map<Key, Integer> internedItems = Maps.newHashMap();
public abstract class BaseOffsetPool<Key> extends BasePool<Key, Integer> implements OffsetSection<Key> {
@Nonnull @Override public Collection<? extends Map.Entry<? extends Key, Integer>> getItems() {
return internedItems.entrySet();
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.dexlib2.writer.pool;
import com.google.common.collect.Maps;
import javax.annotation.Nonnull;
import java.util.Iterator;
import java.util.Map;
public class BasePool<Key, Value> implements Markable {
@Nonnull protected final Map<Key, Value> internedItems = Maps.newLinkedHashMap();
private int markedItemCount = -1;
public void mark() {
markedItemCount = internedItems.size();
}
public void reset() {
if (markedItemCount < 0) {
throw new IllegalStateException("mark() must be called before calling reset()");
}
if (markedItemCount == internedItems.size()) {
return;
}
Iterator<Key> keys = internedItems.keySet().iterator();
for (int i=0; i<markedItemCount; i++) {
keys.next();
}
while (keys.hasNext()) {
keys.next();
keys.remove();
}
}
public int getItemCount() {
return internedItems.size();
}
}

View File

@ -58,11 +58,9 @@ import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
public class ClassPool implements ClassSection<CharSequence, CharSequence,
public class ClassPool extends BasePool<String, PoolClassDef> implements ClassSection<CharSequence, CharSequence,
TypeListPool.Key<? extends Collection<? extends CharSequence>>, PoolClassDef, Field, PoolMethod,
Set<? extends Annotation>, EncodedValue> {
@Nonnull private HashMap<String, PoolClassDef> internedItems = Maps.newHashMap();
@Nonnull private final StringPool stringPool;
@Nonnull private final TypePool typePool;
@Nonnull private final FieldPool fieldPool;

View File

@ -56,16 +56,11 @@ public class DexPool extends DexWriter<CharSequence, StringReference, CharSequen
TypeListPool.Key<? extends Collection<? extends CharSequence>>, Field, PoolMethod,
EncodedValue, AnnotationElement> {
@Nonnull
public static DexPool makeDexPool() {
return makeDexPool(Opcodes.forApi(20));
}
@Deprecated
@Nonnull
public static DexPool makeDexPool(int api) {
return makeDexPool(Opcodes.forApi(api));
}
private final Markable[] sections = new Markable[] {
(Markable)stringSection, (Markable)typeSection, (Markable)protoSection, (Markable)fieldSection,
(Markable)methodSection, (Markable)classSection, (Markable)typeListSection, (Markable)annotationSection,
(Markable)annotationSetSection
};
@Nonnull
public static DexPool makeDexPool(@Nonnull Opcodes opcodes) {
@ -84,29 +79,60 @@ public class DexPool extends DexWriter<CharSequence, StringReference, CharSequen
annotationPool, annotationSetPool);
}
private DexPool(Opcodes opcodes, StringPool stringPool, TypePool typePool, ProtoPool protoPool, FieldPool fieldPool,
MethodPool methodPool, ClassPool classPool, TypeListPool typeListPool,
AnnotationPool annotationPool, AnnotationSetPool annotationSetPool) {
protected DexPool(Opcodes opcodes, StringPool stringPool, TypePool typePool, ProtoPool protoPool,
FieldPool fieldPool, MethodPool methodPool, ClassPool classPool, TypeListPool typeListPool,
AnnotationPool annotationPool, AnnotationSetPool annotationSetPool) {
super(opcodes, stringPool, typePool, protoPool, fieldPool, methodPool,
classPool, typeListPool, annotationPool, annotationSetPool);
}
public static void writeTo(@Nonnull DexDataStore dataStore, @Nonnull org.jf.dexlib2.iface.DexFile input) throws IOException {
DexPool dexPool = makeDexPool();
public static void writeTo(@Nonnull DexDataStore dataStore, @Nonnull org.jf.dexlib2.iface.DexFile input)
throws IOException {
DexPool dexPool = makeDexPool(input.getOpcodes());
for (ClassDef classDef: input.getClasses()) {
((ClassPool)dexPool.classSection).intern(classDef);
dexPool.internClass(classDef);
}
dexPool.writeTo(dataStore);
}
public static void writeTo(@Nonnull String path, @Nonnull org.jf.dexlib2.iface.DexFile input) throws IOException {
DexPool dexPool = makeDexPool();
DexPool dexPool = makeDexPool(input.getOpcodes());
for (ClassDef classDef: input.getClasses()) {
((ClassPool)dexPool.classSection).intern(classDef);
dexPool.internClass(classDef);
}
dexPool.writeTo(new FileDataStore(new File(path)));
}
/**
* Interns a class into this DexPool
* @param classDef The class to intern
*/
public void internClass(ClassDef classDef) {
((ClassPool)classSection).intern(classDef);
}
/**
* Creates a marked state that can be returned to by calling reset()
*
* This is useful to rollback the last added class if it causes a method/field/type overflow
*/
public void mark() {
for (Markable section: sections) {
section.mark();
}
}
/**
* Resets to the last marked state
*
* This is useful to rollback the last added class if it causes a method/field/type overflow
*/
public void reset() {
for (Markable section: sections) {
section.reset();
}
}
@Override protected void writeEncodedValue(@Nonnull InternalEncodedValueWriter writer,
@Nonnull EncodedValue encodedValue) throws IOException {
switch (encodedValue.getValueType()) {
@ -165,7 +191,7 @@ public class DexPool extends DexWriter<CharSequence, StringReference, CharSequen
}
}
public static void internEncodedValue(@Nonnull EncodedValue encodedValue,
static void internEncodedValue(@Nonnull EncodedValue encodedValue,
@Nonnull StringPool stringPool,
@Nonnull TypePool typePool,
@Nonnull FieldPool fieldPool,

View File

@ -0,0 +1,37 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.dexlib2.writer.pool;
public interface Markable {
void mark();
void reset();
}

View File

@ -31,7 +31,6 @@
package org.jf.dexlib2.writer.pool;
import com.google.common.collect.Maps;
import org.jf.dexlib2.writer.DexWriter;
import org.jf.dexlib2.writer.NullableIndexSection;
import org.jf.util.ExceptionWithContext;
@ -41,9 +40,8 @@ import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Map;
public abstract class StringTypeBasePool implements NullableIndexSection<CharSequence> {
@Nonnull protected final Map<String, Integer> internedItems = Maps.newHashMap();
public abstract class StringTypeBasePool extends BasePool<String, Integer>
implements NullableIndexSection<CharSequence>, Markable {
@Nonnull @Override public Collection<Map.Entry<String, Integer>> getItems() {
return internedItems.entrySet();
}

View File

@ -79,7 +79,7 @@ public class AccessorTest {
public void testAccessors() throws IOException {
URL url = AccessorTest.class.getClassLoader().getResource("accessorTest.dex");
Assert.assertNotNull(url);
DexFile f = DexFileFactory.loadDexFile(url.getFile(), 15, false);
DexFile f = DexFileFactory.loadDexFile(url.getFile(), Opcodes.getDefault());
SyntheticAccessorResolver sar = new SyntheticAccessorResolver(f.getOpcodes(), f.getClasses());

View File

@ -0,0 +1,227 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.dexlib2;
import com.beust.jcommander.internal.Maps;
import com.google.common.collect.Lists;
import org.jf.dexlib2.DexFileFactory.DexEntryFinder;
import org.jf.dexlib2.DexFileFactory.DexFileNotFoundException;
import org.jf.dexlib2.DexFileFactory.MultipleMatchingDexEntriesException;
import org.jf.dexlib2.DexFileFactory.UnsupportedFileTypeException;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
import org.jf.dexlib2.iface.MultiDexContainer;
import org.junit.Assert;
import org.junit.Test;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import static org.mockito.Mockito.mock;
public class DexEntryFinderTest {
@Test
public void testNormalStuff() throws Exception {
Map<String, DexBackedDexFile> entries = Maps.newHashMap();
DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
entries.put("/system/framework/framework.jar", dexFile1);
DexBackedDexFile dexFile2 = mock(DexBackedDexFile.class);
entries.put("/system/framework/framework.jar:classes2.dex", dexFile2);
DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
assertEntryNotFound(testFinder, "system/framework/framework.jar", true);
assertEntryNotFound(testFinder, "/framework/framework.jar", true);
assertEntryNotFound(testFinder, "framework/framework.jar", true);
assertEntryNotFound(testFinder, "/framework.jar", true);
assertEntryNotFound(testFinder, "framework.jar", true);
Assert.assertEquals(dexFile1, testFinder.findEntry("system/framework/framework.jar", false));
Assert.assertEquals(dexFile1, testFinder.findEntry("/framework/framework.jar", false));
Assert.assertEquals(dexFile1, testFinder.findEntry("framework/framework.jar", false));
Assert.assertEquals(dexFile1, testFinder.findEntry("/framework.jar", false));
Assert.assertEquals(dexFile1, testFinder.findEntry("framework.jar", false));
assertEntryNotFound(testFinder, "ystem/framework/framework.jar", false);
assertEntryNotFound(testFinder, "ssystem/framework/framework.jar", false);
assertEntryNotFound(testFinder, "ramework/framework.jar", false);
assertEntryNotFound(testFinder, "ramework.jar", false);
assertEntryNotFound(testFinder, "framework", false);
Assert.assertEquals(dexFile2, testFinder.findEntry("/system/framework/framework.jar:classes2.dex", true));
assertEntryNotFound(testFinder, "system/framework/framework.jar:classes2.dex", true);
assertEntryNotFound(testFinder, "framework.jar:classes2.dex", true);
assertEntryNotFound(testFinder, "classes2.dex", true);
Assert.assertEquals(dexFile2, testFinder.findEntry("system/framework/framework.jar:classes2.dex", false));
Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar:classes2.dex", false));
Assert.assertEquals(dexFile2, testFinder.findEntry("framework/framework.jar:classes2.dex", false));
Assert.assertEquals(dexFile2, testFinder.findEntry("/framework.jar:classes2.dex", false));
Assert.assertEquals(dexFile2, testFinder.findEntry("framework.jar:classes2.dex", false));
Assert.assertEquals(dexFile2, testFinder.findEntry(":classes2.dex", false));
Assert.assertEquals(dexFile2, testFinder.findEntry("classes2.dex", false));
assertEntryNotFound(testFinder, "ystem/framework/framework.jar:classes2.dex", false);
assertEntryNotFound(testFinder, "ramework.jar:classes2.dex", false);
assertEntryNotFound(testFinder, "lasses2.dex", false);
assertEntryNotFound(testFinder, "classes2", false);
}
@Test
public void testSimilarEntries() throws Exception {
Map<String, DexBackedDexFile> entries = Maps.newHashMap();
DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
entries.put("/system/framework/framework.jar", dexFile1);
DexBackedDexFile dexFile2 = mock(DexBackedDexFile.class);
entries.put("system/framework/framework.jar", dexFile2);
DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
Assert.assertEquals(dexFile2, testFinder.findEntry("system/framework/framework.jar", true));
assertMultipleMatchingEntries(testFinder, "/system/framework/framework.jar");
assertMultipleMatchingEntries(testFinder, "system/framework/framework.jar");
assertMultipleMatchingEntries(testFinder, "/framework/framework.jar");
assertMultipleMatchingEntries(testFinder, "framework/framework.jar");
assertMultipleMatchingEntries(testFinder, "/framework.jar");
assertMultipleMatchingEntries(testFinder, "framework.jar");
}
@Test
public void testMatchingSuffix() throws Exception {
Map<String, DexBackedDexFile> entries = Maps.newHashMap();
DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
entries.put("/system/framework/framework.jar", dexFile1);
DexBackedDexFile dexFile2 = mock(DexBackedDexFile.class);
entries.put("/framework/framework.jar", dexFile2);
DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar", true));
Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar", false));
Assert.assertEquals(dexFile2, testFinder.findEntry("framework/framework.jar", false));
assertMultipleMatchingEntries(testFinder, "/framework.jar");
assertMultipleMatchingEntries(testFinder, "framework.jar");
}
@Test
public void testNonDexEntries() throws Exception {
Map<String, DexBackedDexFile> entries = Maps.newHashMap();
DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
entries.put("classes.dex", dexFile1);
entries.put("/blah/classes.dex", null);
DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
Assert.assertEquals(dexFile1, testFinder.findEntry("classes.dex", true));
Assert.assertEquals(dexFile1, testFinder.findEntry("classes.dex", false));
assertUnsupportedFileType(testFinder, "/blah/classes.dex", true);
assertDexFileNotFound(testFinder, "/blah/classes.dex", false);
}
private void assertEntryNotFound(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
try {
finder.findEntry(entry, exactMatch);
Assert.fail();
} catch (DexFileNotFoundException ex) {
// expected exception
}
}
private void assertMultipleMatchingEntries(DexEntryFinder finder, String entry) throws IOException {
try {
finder.findEntry(entry, false);
Assert.fail();
} catch (MultipleMatchingDexEntriesException ex) {
// expected exception
}
}
private void assertUnsupportedFileType(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
try {
finder.findEntry(entry, exactMatch);
Assert.fail();
} catch (UnsupportedFileTypeException ex) {
// expected exception
}
}
private void assertDexFileNotFound(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
try {
finder.findEntry(entry, exactMatch);
Assert.fail();
} catch (DexFileNotFoundException ex) {
// expected exception
}
}
public static class TestMultiDexContainer implements MultiDexContainer<DexBackedDexFile> {
@Nonnull private final Map<String, DexBackedDexFile> entries;
public TestMultiDexContainer(@Nonnull Map<String, DexBackedDexFile> entries) {
this.entries = entries;
}
@Nonnull @Override public List<String> getDexEntryNames() throws IOException {
List<String> entryNames = Lists.newArrayList();
for (Entry<String, DexBackedDexFile> entry: entries.entrySet()) {
if (entry.getValue() != null) {
entryNames.add(entry.getKey());
}
}
return entryNames;
}
@Nullable @Override public DexBackedDexFile getEntry(@Nonnull String entryName) throws IOException {
if (entries.containsKey(entryName)) {
DexBackedDexFile entry = entries.get(entryName);
if (entry == null) {
throw new NotADexFile();
}
return entry;
}
return null;
}
}
}

View File

@ -32,8 +32,10 @@
package org.jf.dexlib2.analysis;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import junit.framework.Assert;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.immutable.ImmutableDexFile;
import org.junit.Test;
@ -51,49 +53,53 @@ public class CommonSuperclassTest {
// fivetwothree
// fivethree
private final ClassPath classPath;
private final ClassPath oldClassPath;
private final ClassPath newClassPath;
public CommonSuperclassTest() throws IOException {
classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19),
ImmutableSet.of(
TestUtils.makeClassDef("Ljava/lang/Object;", null),
TestUtils.makeClassDef("Ltest/one;", "Ljava/lang/Object;"),
TestUtils.makeClassDef("Ltest/two;", "Ljava/lang/Object;"),
TestUtils.makeClassDef("Ltest/onetwo;", "Ltest/one;"),
TestUtils.makeClassDef("Ltest/onetwothree;", "Ltest/onetwo;"),
TestUtils.makeClassDef("Ltest/onethree;", "Ltest/one;"),
TestUtils.makeClassDef("Ltest/fivetwo;", "Ltest/five;"),
TestUtils.makeClassDef("Ltest/fivetwothree;", "Ltest/fivetwo;"),
TestUtils.makeClassDef("Ltest/fivethree;", "Ltest/five;"),
TestUtils.makeInterfaceDef("Ljava/lang/Cloneable;"),
TestUtils.makeInterfaceDef("Ljava/io/Serializable;"),
ImmutableSet<ClassDef> classes = ImmutableSet.of(
TestUtils.makeClassDef("Ljava/lang/Object;", null),
TestUtils.makeClassDef("Ltest/one;", "Ljava/lang/Object;"),
TestUtils.makeClassDef("Ltest/two;", "Ljava/lang/Object;"),
TestUtils.makeClassDef("Ltest/onetwo;", "Ltest/one;"),
TestUtils.makeClassDef("Ltest/onetwothree;", "Ltest/onetwo;"),
TestUtils.makeClassDef("Ltest/onethree;", "Ltest/one;"),
TestUtils.makeClassDef("Ltest/fivetwo;", "Ltest/five;"),
TestUtils.makeClassDef("Ltest/fivetwothree;", "Ltest/fivetwo;"),
TestUtils.makeClassDef("Ltest/fivethree;", "Ltest/five;"),
TestUtils.makeInterfaceDef("Ljava/lang/Cloneable;"),
TestUtils.makeInterfaceDef("Ljava/io/Serializable;"),
// basic class and interface
TestUtils.makeClassDef("Liface/classiface1;", "Ljava/lang/Object;", "Liface/iface1;"),
TestUtils.makeInterfaceDef("Liface/iface1;"),
// basic class and interface
TestUtils.makeClassDef("Liface/classiface1;", "Ljava/lang/Object;", "Liface/iface1;"),
TestUtils.makeInterfaceDef("Liface/iface1;"),
// a more complex interface tree
TestUtils.makeInterfaceDef("Liface/base1;"),
// implements undefined interface
TestUtils.makeInterfaceDef("Liface/sub1;", "Liface/base1;", "Liface/base2;"),
// this implements sub1, so that its interfaces can't be fully resolved either
TestUtils.makeInterfaceDef("Liface/sub2;", "Liface/base1;", "Liface/sub1;"),
TestUtils.makeInterfaceDef("Liface/sub3;", "Liface/base1;"),
TestUtils.makeInterfaceDef("Liface/sub4;", "Liface/base1;", "Liface/sub3;"),
TestUtils.makeClassDef("Liface/classsub1;", "Ljava/lang/Object;", "Liface/sub1;"),
TestUtils.makeClassDef("Liface/classsub2;", "Ljava/lang/Object;", "Liface/sub2;"),
TestUtils.makeClassDef("Liface/classsub3;", "Ljava/lang/Object;", "Liface/sub3;",
"Liface/base;"),
TestUtils.makeClassDef("Liface/classsub4;", "Ljava/lang/Object;", "Liface/sub3;",
"Liface/sub4;"),
TestUtils.makeClassDef("Liface/classsubsub4;", "Liface/classsub4;"),
TestUtils.makeClassDef("Liface/classsub1234;", "Ljava/lang/Object;", "Liface/sub1;",
"Liface/sub2;", "Liface/sub3;", "Liface/sub4;")
))));
// a more complex interface tree
TestUtils.makeInterfaceDef("Liface/base1;"),
// implements undefined interface
TestUtils.makeInterfaceDef("Liface/sub1;", "Liface/base1;", "Liface/base2;"),
// this implements sub1, so that its interfaces can't be fully resolved either
TestUtils.makeInterfaceDef("Liface/sub2;", "Liface/base1;", "Liface/sub1;"),
TestUtils.makeInterfaceDef("Liface/sub3;", "Liface/base1;"),
TestUtils.makeInterfaceDef("Liface/sub4;", "Liface/base1;", "Liface/sub3;"),
TestUtils.makeClassDef("Liface/classsub1;", "Ljava/lang/Object;", "Liface/sub1;"),
TestUtils.makeClassDef("Liface/classsub2;", "Ljava/lang/Object;", "Liface/sub2;"),
TestUtils.makeClassDef("Liface/classsub3;", "Ljava/lang/Object;", "Liface/sub3;",
"Liface/base;"),
TestUtils.makeClassDef("Liface/classsub4;", "Ljava/lang/Object;", "Liface/sub3;",
"Liface/sub4;"),
TestUtils.makeClassDef("Liface/classsubsub4;", "Liface/classsub4;"),
TestUtils.makeClassDef("Liface/classsub1234;", "Ljava/lang/Object;", "Liface/sub1;",
"Liface/sub2;", "Liface/sub3;", "Liface/sub4;"));
oldClassPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.getDefault(), classes)));
newClassPath = new ClassPath(Lists.newArrayList(new DexClassProvider(
new ImmutableDexFile(Opcodes.forArtVersion(72), classes))), true, 72);
}
public void superclassTest(String commonSuperclass,
String type1, String type2) {
public void superclassTest(ClassPath classPath, String commonSuperclass,
String type1, String type2) {
TypeProto commonSuperclassProto = classPath.getClass(commonSuperclass);
TypeProto type1Proto = classPath.getClass(type1);
TypeProto type2Proto = classPath.getClass(type2);
@ -102,6 +108,11 @@ public class CommonSuperclassTest {
Assert.assertSame(commonSuperclassProto, type2Proto.getCommonSuperclass(type1Proto));
}
public void superclassTest(String commonSuperclass, String type1, String type2) {
superclassTest(oldClassPath, commonSuperclass, type1, type2);
superclassTest(newClassPath, commonSuperclass, type1, type2);
}
@Test
public void testGetCommonSuperclass() throws IOException {
String object = "Ljava/lang/Object;";
@ -131,7 +142,11 @@ public class CommonSuperclassTest {
// same value, but different object
Assert.assertEquals(
onetwo,
classPath.getClass(onetwo).getCommonSuperclass(new ClassProto(classPath, onetwo)).getType());
oldClassPath.getClass(onetwo).getCommonSuperclass(new ClassProto(oldClassPath, onetwo)).getType());
Assert.assertEquals(
onetwo,
newClassPath.getClass(onetwo).getCommonSuperclass(new ClassProto(newClassPath, onetwo)).getType());
// other object is superclass
superclassTest(object, object, one);

View File

@ -51,11 +51,12 @@ import org.jf.dexlib2.immutable.instruction.ImmutableInstruction35mi;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
public class CustomMethodInlineTableTest {
@Test
public void testCustomMethodInlineTable_Virtual() {
public void testCustomMethodInlineTable_Virtual() throws IOException {
List<ImmutableInstruction> instructions = Lists.newArrayList(
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
new ImmutableInstruction10x(Opcode.RETURN_VOID));
@ -67,10 +68,12 @@ public class CustomMethodInlineTableTest {
ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
null, null, null, null, null, ImmutableList.of(method));
DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), ImmutableList.of(classDef));
ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
15, false);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
@ -82,7 +85,7 @@ public class CustomMethodInlineTableTest {
}
@Test
public void testCustomMethodInlineTable_Static() {
public void testCustomMethodInlineTable_Static() throws IOException {
List<ImmutableInstruction> instructions = Lists.newArrayList(
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
new ImmutableInstruction10x(Opcode.RETURN_VOID));
@ -94,10 +97,12 @@ public class CustomMethodInlineTableTest {
ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
null, null, null, null, ImmutableList.of(method), null);
DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), ImmutableList.of(classDef));
ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
15, false);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
@ -109,7 +114,7 @@ public class CustomMethodInlineTableTest {
}
@Test
public void testCustomMethodInlineTable_Direct() {
public void testCustomMethodInlineTable_Direct() throws IOException {
List<ImmutableInstruction> instructions = Lists.newArrayList(
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
new ImmutableInstruction10x(Opcode.RETURN_VOID));
@ -121,10 +126,12 @@ public class CustomMethodInlineTableTest {
ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
null, null, null, null, ImmutableList.of(method), null);
DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), ImmutableList.of(classDef));
ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
15, false);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);

View File

@ -0,0 +1,156 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.dexlib2.analysis;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.Opcode;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.builder.MethodImplementationBuilder;
import org.jf.dexlib2.builder.instruction.BuilderInstruction10x;
import org.jf.dexlib2.builder.instruction.BuilderInstruction12x;
import org.jf.dexlib2.builder.instruction.BuilderInstruction21t;
import org.jf.dexlib2.builder.instruction.BuilderInstruction22c;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.iface.Method;
import org.jf.dexlib2.iface.MethodImplementation;
import org.jf.dexlib2.immutable.ImmutableClassDef;
import org.jf.dexlib2.immutable.ImmutableDexFile;
import org.jf.dexlib2.immutable.ImmutableMethod;
import org.jf.dexlib2.immutable.ImmutableMethodParameter;
import org.jf.dexlib2.immutable.reference.ImmutableTypeReference;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
public class MethodAnalyzerTest {
@Test
public void testInstanceOfNarrowingEqz() throws IOException {
MethodImplementationBuilder builder = new MethodImplementationBuilder(2);
builder.addInstruction(new BuilderInstruction22c(Opcode.INSTANCE_OF, 0, 1,
new ImmutableTypeReference("Lmain;")));
builder.addInstruction(new BuilderInstruction21t(Opcode.IF_EQZ, 0, builder.getLabel("not_instance_of")));
builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
builder.addLabel("not_instance_of");
builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
MethodImplementation methodImplementation = builder.getMethodImplementation();
Method method = new ImmutableMethod("Lmain;", "narrowing",
Collections.singletonList(new ImmutableMethodParameter("Ljava/lang/Object;", null, null)), "V",
AccessFlags.PUBLIC.getValue(), null, methodImplementation);
ClassDef classDef = new ImmutableClassDef("Lmain;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
null, null, null, Collections.singletonList(method));
DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), Collections.singletonList(classDef));
ClassPath classPath = new ClassPath(new DexClassProvider(dexFile));
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, null, false);
List<AnalyzedInstruction> analyzedInstructions = methodAnalyzer.getAnalyzedInstructions();
Assert.assertEquals("Lmain;", analyzedInstructions.get(2).getPreInstructionRegisterType(1).type.getType());
Assert.assertEquals("Ljava/lang/Object;",
analyzedInstructions.get(3).getPreInstructionRegisterType(1).type.getType());
}
@Test
public void testInstanceOfNarrowingNez() throws IOException {
MethodImplementationBuilder builder = new MethodImplementationBuilder(2);
builder.addInstruction(new BuilderInstruction22c(Opcode.INSTANCE_OF, 0, 1,
new ImmutableTypeReference("Lmain;")));
builder.addInstruction(new BuilderInstruction21t(Opcode.IF_NEZ, 0, builder.getLabel("instance_of")));
builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
builder.addLabel("instance_of");
builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
MethodImplementation methodImplementation = builder.getMethodImplementation();
Method method = new ImmutableMethod("Lmain;", "narrowing",
Collections.singletonList(new ImmutableMethodParameter("Ljava/lang/Object;", null, null)), "V",
AccessFlags.PUBLIC.getValue(), null, methodImplementation);
ClassDef classDef = new ImmutableClassDef("Lmain;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
null, null, null, Collections.singletonList(method));
DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), Collections.singletonList(classDef));
ClassPath classPath = new ClassPath(new DexClassProvider(dexFile));
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, null, false);
List<AnalyzedInstruction> analyzedInstructions = methodAnalyzer.getAnalyzedInstructions();
Assert.assertEquals("Ljava/lang/Object;",
analyzedInstructions.get(2).getPreInstructionRegisterType(1).type.getType());
Assert.assertEquals("Lmain;", analyzedInstructions.get(3).getPreInstructionRegisterType(1).type.getType());
}
@Test
public void testInstanceOfNarrowingAfterMove() throws IOException {
MethodImplementationBuilder builder = new MethodImplementationBuilder(3);
builder.addInstruction(new BuilderInstruction12x(Opcode.MOVE_OBJECT, 1, 2));
builder.addInstruction(new BuilderInstruction22c(Opcode.INSTANCE_OF, 0, 1,
new ImmutableTypeReference("Lmain;")));
builder.addInstruction(new BuilderInstruction21t(Opcode.IF_EQZ, 0, builder.getLabel("not_instance_of")));
builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
builder.addLabel("not_instance_of");
builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
MethodImplementation methodImplementation = builder.getMethodImplementation();
Method method = new ImmutableMethod("Lmain;", "narrowing",
Collections.singletonList(new ImmutableMethodParameter("Ljava/lang/Object;", null, null)), "V",
AccessFlags.PUBLIC.getValue(), null, methodImplementation);
ClassDef classDef = new ImmutableClassDef("Lmain;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
null, null, null, Collections.singletonList(method));
DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), Collections.singletonList(classDef));
ClassPath classPath = new ClassPath(new DexClassProvider(dexFile));
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, null, false);
List<AnalyzedInstruction> analyzedInstructions = methodAnalyzer.getAnalyzedInstructions();
Assert.assertEquals("Lmain;", analyzedInstructions.get(3).getPreInstructionRegisterType(1).type.getType());
Assert.assertEquals("Lmain;", analyzedInstructions.get(3).getPreInstructionRegisterType(2).type.getType());
Assert.assertEquals("Ljava/lang/Object;",
analyzedInstructions.get(4).getPreInstructionRegisterType(1).type.getType());
Assert.assertEquals("Ljava/lang/Object;",
analyzedInstructions.get(4).getPreInstructionRegisterType(2).type.getType());
}
}

View File

@ -57,7 +57,7 @@ public class SuperclassChainTest {
ImmutableSet<ClassDef> classes = ImmutableSet.<ClassDef>of(
objectClassDef, oneClassDef, twoClassDef, threeClassDef);
ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), classes)));
ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.getDefault(), classes)));
TypeProto objectClassProto = classPath.getClass("Ljava/lang/Object;");
TypeProto oneClassProto = classPath.getClass("Ltest/one;");
@ -88,7 +88,7 @@ public class SuperclassChainTest {
ClassDef twoClassDef = TestUtils.makeClassDef("Ltest/two;", "Ltest/one;");
ClassDef threeClassDef = TestUtils.makeClassDef("Ltest/three;", "Ltest/two;");
ImmutableSet<ClassDef> classes = ImmutableSet.<ClassDef>of(twoClassDef, threeClassDef);
ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), classes)));
ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.getDefault(), classes)));
TypeProto unknownClassProto = classPath.getUnknownClass();
TypeProto oneClassProto = classPath.getClass("Ltest/one;");

View File

@ -0,0 +1,106 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.dexlib2.pool;
import com.google.common.collect.Lists;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.AnnotationVisibility;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.raw.MapItem;
import org.jf.dexlib2.dexbacked.raw.RawDexFile;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.Field;
import org.jf.dexlib2.iface.Method;
import org.jf.dexlib2.iface.MethodParameter;
import org.jf.dexlib2.immutable.*;
import org.jf.dexlib2.writer.io.MemoryDataStore;
import org.jf.dexlib2.writer.pool.DexPool;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
public class RollbackTest {
@Test
public void testRollback() throws IOException {
ClassDef class1 = new ImmutableClassDef("Lcls1;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null, null,
Lists.newArrayList(new ImmutableAnnotation(AnnotationVisibility.RUNTIME, "Lannotation;", null)),
Lists.<Field>newArrayList(
new ImmutableField("Lcls1;", "field1", "I", AccessFlags.PUBLIC.getValue(), null, null)
),
Lists.<Method>newArrayList(
new ImmutableMethod("Lcls1", "method1",
Lists.<MethodParameter>newArrayList(new ImmutableMethodParameter("L", null, null)), "V",
AccessFlags.PUBLIC.getValue(), null, null))
);
ClassDef class2 = new ImmutableClassDef("Lcls2;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null, null,
Lists.newArrayList(new ImmutableAnnotation(AnnotationVisibility.RUNTIME, "Lannotation2;", null)),
Lists.<Field>newArrayList(
new ImmutableField("Lcls2;", "field2", "D", AccessFlags.PUBLIC.getValue(), null, null)
),
Lists.<Method>newArrayList(
new ImmutableMethod("Lcls2;", "method2",
Lists.<MethodParameter>newArrayList(new ImmutableMethodParameter("D", null, null)), "V",
AccessFlags.PUBLIC.getValue(), null, null))
);
RawDexFile dexFile1;
{
MemoryDataStore dataStore = new MemoryDataStore();
DexPool dexPool = DexPool.makeDexPool(Opcodes.getDefault());
dexPool.internClass(class1);
dexPool.mark();
dexPool.internClass(class2);
dexPool.reset();
dexPool.writeTo(dataStore);
dexFile1 = new RawDexFile(Opcodes.getDefault(), dataStore.getData());
}
RawDexFile dexFile2;
{
MemoryDataStore dataStore = new MemoryDataStore();
DexPool dexPool = DexPool.makeDexPool(Opcodes.getDefault());
dexPool.internClass(class1);
dexPool.writeTo(dataStore);
dexFile2 = new RawDexFile(Opcodes.getDefault(), dataStore.getData());
}
List<MapItem> mapItems1 = dexFile1.getMapItems();
List<MapItem> mapItems2 = dexFile2.getMapItems();
for (int i=0; i<mapItems1.size(); i++) {
Assert.assertEquals(mapItems1.get(i).getType(), mapItems2.get(i).getType());
Assert.assertEquals(mapItems1.get(i).getItemCount(), mapItems2.get(i).getItemCount());
}
}
}

View File

@ -72,12 +72,12 @@ public class DexWriterTest {
MemoryDataStore dataStore = new MemoryDataStore();
try {
DexPool.writeTo(dataStore, new ImmutableDexFile(Opcodes.forApi(19), ImmutableSet.of(classDef)));
DexPool.writeTo(dataStore, new ImmutableDexFile(Opcodes.getDefault(), ImmutableSet.of(classDef)));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(15), dataStore.getData());
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dataStore.getData());
ClassDef dbClassDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(dbClassDef);
Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null);
@ -112,12 +112,12 @@ public class DexWriterTest {
MemoryDataStore dataStore = new MemoryDataStore();
try {
DexPool.writeTo(dataStore, new ImmutableDexFile(Opcodes.forApi(19), ImmutableSet.of(classDef)));
DexPool.writeTo(dataStore, new ImmutableDexFile(Opcodes.getDefault(), ImmutableSet.of(classDef)));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(15), dataStore.getData());
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dataStore.getData());
ClassDef dbClassDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(dbClassDef);
Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null);

View File

@ -62,7 +62,7 @@ import java.util.List;
public class JumboStringConversionTest {
@Test
public void testJumboStringConversion() throws IOException {
DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.forApi(15));
DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.getDefault());
MethodImplementationBuilder methodBuilder = new MethodImplementationBuilder(1);
for (int i=0; i<66000; i++) {
@ -92,7 +92,7 @@ public class JumboStringConversionTest {
MemoryDataStore dexStore = new MemoryDataStore();
dexBuilder.writeTo(dexStore);
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(15), dexStore.getData());
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dexStore.getData());
ClassDef classDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(classDef);
@ -122,7 +122,7 @@ public class JumboStringConversionTest {
@Test
public void testJumboStringConversion_NonMethodBuilder() throws IOException {
DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.forApi(15));
DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.getDefault());
final List<Instruction> instructions = Lists.newArrayList();
for (int i=0; i<66000; i++) {
@ -189,7 +189,7 @@ public class JumboStringConversionTest {
MemoryDataStore dexStore = new MemoryDataStore();
dexBuilder.writeTo(dexStore);
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(15), dexStore.getData());
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dexStore.getData());
ClassDef classDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(classDef);

View File

@ -76,8 +76,8 @@ dependencies {
compile project(':util')
compile project(':dexlib2')
compile depends.antlr_runtime
compile depends.jcommander
compile depends.stringtemplate
compile depends.commons_cli
testCompile depends.junit
@ -95,7 +95,7 @@ task fatJar(type: Jar, dependsOn: jar) {
classifier = 'fat'
manifest {
attributes('Main-Class': 'org.jf.smali.main')
attributes('Main-Class': 'org.jf.smali.Main')
}
doLast {
@ -141,7 +141,7 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
dontobfuscate
dontoptimize
keep 'public class org.jf.smali.main { public static void main(java.lang.String[]); }'
keep 'public class org.jf.smali.Main { public static void main(java.lang.String[]); }'
keepclassmembers 'enum * { public static **[] values(); public static ** valueOf(java.lang.String); }'
dontwarn 'com.google.common.**'

View File

@ -263,8 +263,8 @@ import org.jf.dexlib2.Opcodes;
this.allowOdex = allowOdex;
}
public void setApiLevel(int apiLevel, boolean experimental) {
this.opcodes = new Opcodes(apiLevel, experimental);
public void setApiLevel(int apiLevel) {
this.opcodes = Opcodes.forApi(apiLevel);
this.apiLevel = apiLevel;
}

View File

@ -85,8 +85,8 @@ import java.util.*;
this.dexBuilder = dexBuilder;
}
public void setApiLevel(int apiLevel, boolean experimental) {
this.opcodes = new Opcodes(apiLevel, experimental);
public void setApiLevel(int apiLevel) {
this.opcodes = Opcodes.forApi(apiLevel);
this.apiLevel = apiLevel;
}

View File

@ -0,0 +1,113 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.smali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.validators.PositiveInteger;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.List;
@Parameters(commandDescription = "Assembles smali files into a dex file.")
@ExtendedParameters(
commandName = "assemble",
commandAliases = { "ass", "as", "a" })
public class AssembleCommand extends Command {
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information for this command.")
private boolean help;
@Parameter(names = {"-j", "--jobs"},
description = "The number of threads to use. Defaults to the number of cores available.",
validateWith = PositiveInteger.class)
@ExtendedParameter(argumentNames = "n")
private int jobs = Runtime.getRuntime().availableProcessors();
@Parameter(names = {"-a", "--api"},
description = "The numeric api level to use while assembling.")
@ExtendedParameter(argumentNames = "api")
private int apiLevel = 15;
@Parameter(names = {"-o", "--output"},
description = "The name/path of the dex file to write.")
@ExtendedParameter(argumentNames = "file")
private String output = "out.dex";
@Parameter(names = "--verbose",
description = "Generate verbose error messages.")
private boolean verbose = false;
@Parameter(names = {"--allow-odex-opcodes", "--allow-odex", "--ao"},
description = "Allows the odex opcodes that dalvik doesn't reject to be assembled.")
private boolean allowOdexOpcodes;
@Parameter(description = "Assembles the given files. If a directory is specified, it will be " +
"recursively searched for any files with a .smali prefix")
@ExtendedParameter(argumentNames = "[<file>|<dir>]+")
private List<String> input;
public AssembleCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
@Override public void run() {
if (help || input == null || input.isEmpty()) {
usage();
return;
}
try {
Smali.assemble(getOptions(), input);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
protected SmaliOptions getOptions() {
SmaliOptions options = new SmaliOptions();
options.jobs = jobs;
options.apiLevel = apiLevel;
options.outputDexFile = output;
options.allowOdexOpcodes = allowOdexOpcodes;
options.verboseErrors = verbose;
return options;
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.smali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.jf.util.ConsoleUtil;
import org.jf.util.jcommander.*;
import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Shows usage information")
@ExtendedParameters(
commandName = "help",
commandAliases = "h")
public class HelpCommand extends Command {
@Parameter(description = "If specified, show the detailed usage information for the given commands")
@ExtendedParameter(argumentNames = "commands")
private List<String> commands;
public HelpCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
public void run() {
JCommander parentJc = commandAncestors.get(commandAncestors.size() - 1);
if (commands == null || commands.isEmpty()) {
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(commandAncestors));
} else {
boolean printedHelp = false;
for (String cmd : commands) {
JCommander command = ExtendedCommands.getSubcommand(parentJc, cmd);
if (command == null) {
System.err.println("No such command: " + cmd);
} else {
printedHelp = true;
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(((Command)command.getObjects().get(0)).getCommandHierarchy()));
}
}
if (!printedHelp) {
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(commandAncestors));
}
}
}
@Parameters(hidden = true)
@ExtendedParameters(commandName = "hlep")
public static class HlepCommand extends HelpCommand {
public HlepCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.smali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.google.common.collect.Lists;
import org.jf.smali.HelpCommand.HlepCommand;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedCommands;
import org.jf.util.jcommander.ExtendedParameters;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
@ExtendedParameters(
includeParametersInUsage = true,
commandName = "smali",
postfixDescription = "See smali help <command> for more information about a specific command")
public class Main extends Command {
public static final String VERSION = loadVersion();
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information")
private boolean help;
@Parameter(names = {"-v", "--version"}, help = true,
description = "Print the version of baksmali and then exit")
public boolean version;
private JCommander jc;
@Override public void run() {
}
@Override protected JCommander getJCommander() {
return jc;
}
public Main() {
super(Lists.<JCommander>newArrayList());
}
public static void main(String[] args) {
Main main = new Main();
JCommander jc = new JCommander(main);
main.jc = jc;
jc.setProgramName("smali");
List<JCommander> commandHierarchy = main.getCommandHierarchy();
ExtendedCommands.addExtendedCommand(jc, new AssembleCommand(commandHierarchy));
ExtendedCommands.addExtendedCommand(jc, new HelpCommand(commandHierarchy));
ExtendedCommands.addExtendedCommand(jc, new HlepCommand(commandHierarchy));
jc.parse(args);
if (main.version) {
version();
}
if (jc.getParsedCommand() == null || main.help) {
main.usage();
return;
}
Command command = (Command)jc.getCommands().get(jc.getParsedCommand()).getObjects().get(0);
command.run();
}
protected static void version() {
System.out.println("smali " + VERSION + " (http://smali.org)");
System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
System.exit(0);
}
private static String loadVersion() {
InputStream propertiesStream = Main.class.getClassLoader().getResourceAsStream("smali.properties");
String version = "[unknown version]";
if (propertiesStream != null) {
Properties properties = new Properties();
try {
properties.load(propertiesStream);
version = properties.getProperty("application.version");
} catch (IOException ex) {
// ignore
}
}
return version;
}
}

Some files were not shown because too many files have changed in this diff Show More