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 { dependencies {
compile project(':util') compile project(':util')
compile project(':dexlib2') compile project(':dexlib2')
compile depends.commons_cli
compile depends.guava compile depends.guava
compile depends.jcommander
testCompile depends.junit testCompile depends.junit
testCompile project(':smali') testCompile project(':smali')
@ -59,7 +59,7 @@ task fatJar(type: Jar) {
classifier = 'fat' classifier = 'fat'
manifest { manifest {
attributes('Main-Class': 'org.jf.baksmali.main') attributes('Main-Class': 'org.jf.baksmali.Main')
} }
doLast { doLast {
@ -92,7 +92,7 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
dontobfuscate dontobfuscate
dontoptimize 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); }' keepclassmembers 'enum * { public static **[] values(); public static ** valueOf(java.lang.String); }'
dontwarn 'com.google.common.**' dontwarn 'com.google.common.**'
@ -100,3 +100,17 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
} }
tasks.getByPath(':release').dependsOn(proguard) 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; package org.jf.baksmali.Adaptors;
import org.jf.baksmali.baksmaliOptions; import org.jf.baksmali.BaksmaliOptions;
import org.jf.util.IndentingWriter; import org.jf.util.IndentingWriter;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -42,7 +42,7 @@ public class CatchMethodItem extends MethodItem {
private final LabelMethodItem tryEndLabel; private final LabelMethodItem tryEndLabel;
private final LabelMethodItem handlerLabel; 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 codeAddress, @Nullable String exceptionType, int startAddress, int endAddress,
int handlerAddress) { int handlerAddress) {
super(codeAddress); super(codeAddress);

View File

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

View File

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

View File

@ -29,7 +29,7 @@
package org.jf.baksmali.Adaptors; package org.jf.baksmali.Adaptors;
import org.jf.baksmali.Adaptors.EncodedValue.EncodedValueAdaptor; 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.AccessFlags;
import org.jf.dexlib2.iface.Annotation; import org.jf.dexlib2.iface.Annotation;
import org.jf.dexlib2.iface.Field; import org.jf.dexlib2.iface.Field;
@ -41,7 +41,7 @@ import java.io.IOException;
import java.util.Collection; import java.util.Collection;
public class FieldDefinition { 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 { boolean setInStaticConstructor) throws IOException {
EncodedValue initialValue = field.getInitialValue(); EncodedValue initialValue = field.getInitialValue();
int accessFlags = field.getAccessFlags(); int accessFlags = field.getAccessFlags();
@ -68,7 +68,7 @@ public class FieldDefinition {
writer.write(" = "); writer.write(" = ");
String containingClass = null; String containingClass = null;
if (options.useImplicitReferences) { if (options.implicitReferences) {
containingClass = field.getDefiningClass(); containingClass = field.getDefiningClass();
} }
@ -82,7 +82,7 @@ public class FieldDefinition {
writer.indent(4); writer.indent(4);
String containingClass = null; String containingClass = null;
if (options.useImplicitReferences) { if (options.implicitReferences) {
containingClass = field.getDefiningClass(); 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.MethodDefinition.InvalidSwitchPayload;
import org.jf.baksmali.Adaptors.MethodItem; import org.jf.baksmali.Adaptors.MethodItem;
import org.jf.baksmali.Renderers.LongRenderer; 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.Opcode;
import org.jf.dexlib2.ReferenceType; import org.jf.dexlib2.ReferenceType;
import org.jf.dexlib2.VerificationError; import org.jf.dexlib2.VerificationError;
@ -67,7 +67,7 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
} }
private boolean isAllowedOdex(@Nonnull Opcode opcode) { private boolean isAllowedOdex(@Nonnull Opcode opcode) {
baksmaliOptions options = methodDef.classDef.options; BaksmaliOptions options = methodDef.classDef.options;
if (options.allowOdex) { if (options.allowOdex) {
return true; return true;
} }
@ -110,7 +110,7 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
if (instruction instanceof ReferenceInstruction) { if (instruction instanceof ReferenceInstruction) {
ReferenceInstruction referenceInstruction = (ReferenceInstruction)instruction; ReferenceInstruction referenceInstruction = (ReferenceInstruction)instruction;
String classContext = null; String classContext = null;
if (methodDef.classDef.options.useImplicitReferences) { if (methodDef.classDef.options.implicitReferences) {
classContext = methodDef.method.getDefiningClass(); 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.LabelMethodItem;
import org.jf.baksmali.Adaptors.MethodDefinition; 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.Opcode;
import org.jf.dexlib2.iface.instruction.OffsetInstruction; import org.jf.dexlib2.iface.instruction.OffsetInstruction;
import org.jf.util.IndentingWriter; import org.jf.util.IndentingWriter;
@ -41,7 +41,7 @@ import java.io.IOException;
public class OffsetInstructionFormatMethodItem extends InstructionMethodItem<OffsetInstruction> { public class OffsetInstructionFormatMethodItem extends InstructionMethodItem<OffsetInstruction> {
protected LabelMethodItem label; protected LabelMethodItem label;
public OffsetInstructionFormatMethodItem(@Nonnull baksmaliOptions options, @Nonnull MethodDefinition methodDef, public OffsetInstructionFormatMethodItem(@Nonnull BaksmaliOptions options, @Nonnull MethodDefinition methodDef,
int codeAddress, OffsetInstruction instruction) { int codeAddress, OffsetInstruction instruction) {
super(methodDef, codeAddress, instruction); super(methodDef, codeAddress, instruction);

View File

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

View File

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

View File

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

View File

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

View File

@ -28,7 +28,7 @@
package org.jf.baksmali.Adaptors; package org.jf.baksmali.Adaptors;
import org.jf.baksmali.baksmaliOptions; import org.jf.baksmali.BaksmaliOptions;
import org.jf.util.IndentingWriter; import org.jf.util.IndentingWriter;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -38,11 +38,11 @@ import java.io.IOException;
* This class contains the logic used for formatting registers * This class contains the logic used for formatting registers
*/ */
public class RegisterFormatter { public class RegisterFormatter {
@Nonnull public final baksmaliOptions options; @Nonnull public final BaksmaliOptions options;
public final int registerCount; public final int registerCount;
public final int parameterRegisterCount; 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.options = options;
this.registerCount = registerCount; this.registerCount = registerCount;
this.parameterRegisterCount = parameterRegisterCount; this.parameterRegisterCount = parameterRegisterCount;
@ -58,7 +58,7 @@ public class RegisterFormatter {
* @param lastRegister the last register in the range * @param lastRegister the last register in the range
*/ */
public void writeRegisterRange(IndentingWriter writer, int startRegister, int lastRegister) throws IOException { public void writeRegisterRange(IndentingWriter writer, int startRegister, int lastRegister) throws IOException {
if (!options.noParameterRegisters) { if (options.parameterRegisters) {
assert startRegister <= lastRegister; assert startRegister <= lastRegister;
if (startRegister >= registerCount - parameterRegisterCount) { if (startRegister >= registerCount - parameterRegisterCount) {
@ -86,7 +86,7 @@ public class RegisterFormatter {
* @param register the register number * @param register the register number
*/ */
public void writeTo(IndentingWriter writer, int register) throws IOException { public void writeTo(IndentingWriter writer, int register) throws IOException {
if (!options.noParameterRegisters) { if (options.parameterRegisters) {
if (register >= registerCount - parameterRegisterCount) { if (register >= registerCount - parameterRegisterCount) {
writer.write('p'); writer.write('p');
writer.printSignedIntAsDec((register - (registerCount - parameterRegisterCount))); 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; 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.Lists;
import com.google.common.collect.Ordering; import com.google.common.collect.Ordering;
import org.jf.baksmali.Adaptors.ClassDefinition; 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.ClassDef;
import org.jf.dexlib2.iface.DexFile; import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.util.SyntheticAccessorResolver;
import org.jf.util.ClassFileNameHandler; import org.jf.util.ClassFileNameHandler;
import org.jf.util.IndentingWriter; 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.io.*;
import java.util.List; import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.*; import java.util.concurrent.*;
public class baksmali { public class Baksmali {
public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options) {
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;
}
}
//sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file //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 //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 //may still change of course
List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses()); List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());
if (!options.noAccessorComments) { final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDir, ".smali");
options.syntheticAccessorResolver = new SyntheticAccessorResolver(dexFile.getOpcodes(), classDefs);
}
final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali"); ExecutorService executor = Executors.newFixedThreadPool(jobs);
ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
List<Future<Boolean>> tasks = Lists.newArrayList(); List<Future<Boolean>> tasks = Lists.newArrayList();
for (final ClassDef classDef: classDefs) { for (final ClassDef classDef: classDefs) {
@ -174,7 +85,7 @@ public class baksmali {
} }
private static boolean disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler, 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 path for the disassembly file is based on the package name
* The class descriptor will look something like: * The class descriptor will look something like:

View File

@ -31,19 +31,35 @@
package org.jf.baksmali; package org.jf.baksmali;
import com.google.common.collect.Lists;
import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.analysis.InlineMethodResolver; import org.jf.dexlib2.analysis.InlineMethodResolver;
import org.jf.dexlib2.util.SyntheticAccessorResolver; 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.io.File;
import java.util.Arrays; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; 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 // register info values
public static final int ALL = 1; public static final int ALL = 1;
public static final int ALLPRE = 2; public static final int ALLPRE = 2;
@ -53,56 +69,40 @@ public class baksmaliOptions {
public static final int MERGE = 32; public static final int MERGE = 32;
public static final int FULLMERGE = 64; 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 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 SyntheticAccessorResolver syntheticAccessorResolver = null;
public void setBootClassPath(String bootClassPath) { /**
bootClassPathEntries = Lists.newArrayList(bootClassPath.split(":")); * Load the resource ids from a set of public.xml files.
} *
* @param resourceFiles A map of resource prefixes -> public.xml files
public void addExtraClassPath(String extraClassPath) { */
if (extraClassPath.startsWith(":")) { public void loadResourceIds(Map<String, File> resourceFiles) throws SAXException, IOException {
extraClassPath = extraClassPath.substring(1); for (Map.Entry<String, File> entry: resourceFiles.entrySet()) {
} try {
extraClassPathEntries.addAll(Arrays.asList(extraClassPath.split(":"))); SAXParser saxp = SAXParserFactory.newInstance().newSAXParser();
} final String prefix = entry.getKey();
saxp.parse(entry.getValue(), new DefaultHandler() {
public void setResourceIdFiles(String resourceIdFiles) { @Override
for (String resourceIdFile: resourceIdFiles.split(":")) { public void startElement(String uri, String localName, String qName,
String[] entry = resourceIdFile.split("="); Attributes attr) throws SAXException {
resourceIdFileEntries.put(entry[1], entry[0]); 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 junit.framework.Assert;
import org.jf.baksmali.Adaptors.ClassDefinition; import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.dexlib2.DexFileFactory; import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile; import org.jf.dexlib2.iface.DexFile;
@ -85,14 +86,14 @@ public class AnalysisTest {
public void runTest(String test, boolean registerInfo) throws IOException, URISyntaxException { public void runTest(String test, boolean registerInfo) throws IOException, URISyntaxException {
String dexFilePath = String.format("%s%sclasses.dex", test, File.separatorChar); 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) { if (registerInfo) {
options.registerInfo = baksmaliOptions.ALL | baksmaliOptions.FULLMERGE; options.registerInfo = BaksmaliOptions.ALL | BaksmaliOptions.FULLMERGE;
options.classPath = new ClassPath(); options.classPath = new ClassPath();
} }
options.useImplicitReferences = false; options.implicitReferences = false;
for (ClassDef classDef: dexFile.getClasses()) { for (ClassDef classDef: dexFile.getClasses()) {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();

View File

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

View File

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

View File

@ -57,10 +57,10 @@ public class DisassemblyTest extends DexTest {
} }
protected void runTest(@Nonnull String testName) { 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 { try {
DexBackedDexFile inputDex = getInputDexFile(testName, options); DexBackedDexFile inputDex = getInputDexFile(testName, options);
Assert.assertEquals(1, inputDex.getClassCount()); Assert.assertEquals(1, inputDex.getClassCount());

View File

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

View File

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

View File

@ -36,6 +36,6 @@ import org.junit.Test;
public class InterfaceOrderTest extends IdenticalRoundtripTest { public class InterfaceOrderTest extends IdenticalRoundtripTest {
@Test @Test
public void testInterfaceOrder() { 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) { 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 { try {
// Load file from resources as a stream // Load file from resources as a stream
String inputFilename = getInputFilename(testName); String inputFilename = getInputFilename(testName);

View File

@ -101,14 +101,15 @@ subprojects {
guava: 'com.google.guava:guava:18.0', guava: 'com.google.guava:guava:18.0',
findbugs: 'com.google.code.findbugs:jsr305:1.3.9', findbugs: 'com.google.code.findbugs:jsr305:1.3.9',
junit: 'junit:junit:4.6', junit: 'junit:junit:4.6',
mockito: 'org.mockito:mockito-core:1.+',
antlr_runtime: 'org.antlr:antlr-runtime:3.5.2', antlr_runtime: 'org.antlr:antlr-runtime:3.5.2',
antlr: 'org.antlr:antlr:3.5.2', antlr: 'org.antlr:antlr:3.5.2',
stringtemplate: 'org.antlr:stringtemplate:3.2.1', 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', jflex_plugin: 'org.xbib.gradle.plugin:gradle-plugin-jflex:1.1.0',
proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1', proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1',
dx: 'com.google.android.tools:dx:1.7', 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 - return-void-barrier -> return-void-no-barrier
1412dfa4adcd511902e510fa0c948b168ab5840c - 61 (re-commit of f3251d12) 1412dfa4adcd511902e510fa0c948b168ab5840c - 61 (re-commit of f3251d12)
9d6bf69ad3012a9d843268fdd5325b6719b6d5f2 - 62 9d6bf69ad3012a9d843268fdd5325b6719b6d5f2 - 62
- classpath list was added
0de1133ba600f299b3d67938f650720d9f859eb2 - 63 0de1133ba600f299b3d67938f650720d9f859eb2 - 63
07785bb98dc8bbe192970e0f4c2cafd338a8dc68 - 64 07785bb98dc8bbe192970e0f4c2cafd338a8dc68 - 64
fa2c054b28d4b540c1b3651401a7a091282a015f - 65 fa2c054b28d4b540c1b3651401a7a091282a015f - 65
@ -21,4 +22,27 @@ fab6788358dfb64e5c370611ddbbbffab0ed0553 - 67
6e2d5747d00697a25251d25dd33b953e54709507 - 68 (revert of 54b62480) 6e2d5747d00697a25251d25dd33b953e54709507 - 68 (revert of 54b62480)
0747466fca310eedea5fc49e37d54f240a0b3c0f - 69 (re-commit of 54b62480) 0747466fca310eedea5fc49e37d54f240a0b3c0f - 69 (re-commit of 54b62480)
501fd635a557645ab05f893c56e1f358e21bab82 - 70 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 compile depends.guava
testCompile depends.junit testCompile depends.junit
testCompile depends.mockito
accessorTestGenerator project('accessorTestGenerator') accessorTestGenerator project('accessorTestGenerator')

View File

@ -31,14 +31,19 @@
package org.jf.dexlib2; package org.jf.dexlib2;
import com.google.common.base.MoreObjects; import com.google.common.base.Joiner;
import com.google.common.io.ByteStreams; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile; import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile; import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException; import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile; 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.DexFile;
import org.jf.dexlib2.iface.MultiDexContainer;
import org.jf.dexlib2.writer.pool.DexPool; import org.jf.dexlib2.writer.pool.DexPool;
import org.jf.util.ExceptionWithContext; import org.jf.util.ExceptionWithContext;
@ -46,80 +51,45 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.*; import java.io.*;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public final class DexFileFactory { public final class DexFileFactory {
@Nonnull @Nonnull
public static DexBackedDexFile loadDexFile(@Nonnull String path, int api) throws IOException { public static DexBackedDexFile loadDexFile(@Nonnull String path, @Nonnull Opcodes opcodes) throws IOException {
return loadDexFile(path, api, false); 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 @Nonnull
public static DexBackedDexFile loadDexFile(@Nonnull String path, int api, boolean experimental) public static DexBackedDexFile loadDexFile(@Nonnull File file, @Nonnull Opcodes opcodes) throws IOException {
throws IOException { if (!file.exists()) {
return loadDexFile(new File(path), "classes.dex", Opcodes.forApi(api, experimental)); throw new DexFileNotFoundException("%s does not exist", file.getName());
}
@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
}
}
} }
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 {
try { try {
return DexBackedDexFile.fromInputStream(opcodes, inputStream); return DexBackedDexFile.fromInputStream(opcodes, inputStream);
@ -127,14 +97,15 @@ public final class DexFileFactory {
// just eat it // just eat it
} }
// Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails
try { try {
return DexBackedOdexFile.fromInputStream(opcodes, inputStream); return DexBackedOdexFile.fromInputStream(opcodes, inputStream);
} catch (DexBackedOdexFile.NotAnOdexFile ex) { } catch (DexBackedOdexFile.NotAnOdexFile ex) {
// just eat it // just eat it
} }
// Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream
// back to the same position, if they fails
OatFile oatFile = null; OatFile oatFile = null;
try { try {
oatFile = OatFile.fromInputStream(inputStream); oatFile = OatFile.fromInputStream(inputStream);
@ -150,71 +121,181 @@ public final class DexFileFactory {
List<OatDexFile> oatDexFiles = oatFile.getDexFiles(); List<OatDexFile> oatDexFiles = oatFile.getDexFiles();
if (oatDexFiles.size() == 0) { 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) { return oatDexFiles.get(0);
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);
}
} }
} finally { } finally {
inputStream.close(); 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 { public static void writeDexFile(@Nonnull String path, @Nonnull DexFile dexFile) throws IOException {
DexPool.writeTo(path, dexFile); DexPool.writeTo(path, dexFile);
} }
private DexFileFactory() {} private DexFileFactory() {}
public static class DexFileNotFound extends ExceptionWithContext { public static class DexFileNotFoundException extends ExceptionWithContext {
public DexFileNotFound(@Nullable Throwable cause) { public DexFileNotFoundException(@Nullable String message, Object... formatArgs) {
super(cause);
}
public DexFileNotFound(@Nullable Throwable cause, @Nullable String message, Object... formatArgs) {
super(cause, message, formatArgs);
}
public DexFileNotFound(@Nullable String message, Object... formatArgs) {
super(message, 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 { public static class UnsupportedOatVersionException extends ExceptionWithContext {
@Nonnull public final OatFile oatFile; @Nonnull public final OatFile oatFile;
@ -223,4 +304,155 @@ public final class DexFileFactory {
this.oatFile = oatFile; 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; public static final int JUMBO_OPCODE = 0x200;
//if the instruction can initialize an uninitialized object reference //if the instruction can initialize an uninitialized object reference
public static final int CAN_INITIALIZE_REFERENCE = 0x400; 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; private static final int ALL_APIS = 0xFFFF0000;
@ -471,10 +469,6 @@ public enum Opcode
return (flags & CAN_INITIALIZE_REFERENCE) != 0; return (flags & CAN_INITIALIZE_REFERENCE) != 0;
} }
public final boolean isExperimental() {
return (flags & EXPERIMENTAL) != 0;
}
private static class VersionConstraint { private static class VersionConstraint {
@Nonnull public final Range<Integer> apiRange; @Nonnull public final Range<Integer> apiRange;
@Nonnull public final Range<Integer> artVersionRange; @Nonnull public final Range<Integer> artVersionRange;

View File

@ -39,6 +39,10 @@ import javax.annotation.Nullable;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; 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 { public class Opcodes {
/** /**
@ -52,37 +56,36 @@ public class Opcodes {
@Nonnull @Nonnull
public static Opcodes forApi(int api) { public static Opcodes forApi(int api) {
return new Opcodes(api, VersionMap.mapApiToArtVersion(api), false); return new Opcodes(api, NO_VERSION);
}
@Nonnull
public static Opcodes forApi(int api, boolean experimental) {
return new Opcodes(api, VersionMap.mapApiToArtVersion(api), experimental);
} }
@Nonnull @Nonnull
public static Opcodes forArtVersion(int artVersion) { 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 @Nonnull
public static Opcodes forArtVersion(int artVersion, boolean experimental) { public static Opcodes getDefault() {
return new Opcodes(VersionMap.mapArtVersionToApi(artVersion), artVersion, experimental); // The last pre-art api
return forApi(20);
} }
@Deprecated private Opcodes(int api, int artVersion) {
public Opcodes(int api) {
this(api, false);
}
@Deprecated
public Opcodes(int api, boolean experimental) {
this(api, VersionMap.mapApiToArtVersion(api), experimental);
}
private Opcodes(int api, int artVersion, boolean experimental) { if (api >= 21) {
this.api = api; this.api = api;
this.artVersion = artVersion; 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); opcodeValues = new EnumMap<Opcode, Short>(Opcode.class);
opcodesByName = Maps.newHashMap(); opcodesByName = Maps.newHashMap();
@ -104,7 +107,7 @@ public class Opcodes {
} }
Short opcodeValue = versionToValueMap.get(version); Short opcodeValue = versionToValueMap.get(version);
if (opcodeValue != null && (!opcode.isExperimental() || experimental)) { if (opcodeValue != null) {
if (!opcode.format.isPayloadFormat) { if (!opcode.format.isPayloadFormat) {
opcodesByValue[opcodeValue] = opcode; opcodesByValue[opcodeValue] = opcode;
} }
@ -142,6 +145,6 @@ public class Opcodes {
} }
public boolean isArt() { 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 final int NO_VERSION = -1;
public static int mapArtVersionToApi(int artVersion) { public static int mapArtVersionToApi(int artVersion) {
// TODO: implement this if (artVersion >= 79) {
return 20; 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) { public static int mapApiToArtVersion(int api) {
// TODO: implement this switch (api) {
if (api < 20) { case 19:
return NO_VERSION; case 20:
} else { return 7;
return 56; 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 * The actual instruction
*/ */
@Nullable @Nonnull
protected Instruction instruction; 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 * Instructions that can pass on execution to this one during normal execution
*/ */
@Nonnull
protected final TreeSet<AnalyzedInstruction> predecessors = new TreeSet<AnalyzedInstruction>(); protected final TreeSet<AnalyzedInstruction> predecessors = new TreeSet<AnalyzedInstruction>();
/** /**
* Instructions that can execution could pass on to next during normal execution * Instructions that can execution could pass on to next during normal execution
*/ */
@Nonnull
protected final LinkedList<AnalyzedInstruction> successors = new LinkedList<AnalyzedInstruction>(); protected final LinkedList<AnalyzedInstruction> successors = new LinkedList<AnalyzedInstruction>();
/** /**
* This contains the register types *before* the instruction has executed * This contains the register types *before* the instruction has executed
*/ */
@Nonnull
protected final RegisterType[] preRegisterMap; protected final RegisterType[] preRegisterMap;
/** /**
* This contains the register types *after* the instruction has executed * This contains the register types *after* the instruction has executed
*/ */
@Nonnull
protected final RegisterType[] postRegisterMap; protected final RegisterType[] postRegisterMap;
/** /**
@ -94,8 +98,8 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
*/ */
protected final Instruction originalInstruction; protected final Instruction originalInstruction;
public AnalyzedInstruction(MethodAnalyzer methodAnalyzer, Instruction instruction, int instructionIndex, public AnalyzedInstruction(@Nonnull MethodAnalyzer methodAnalyzer, @Nonnull Instruction instruction,
int registerCount) { int instructionIndex, int registerCount) {
this.methodAnalyzer = methodAnalyzer; this.methodAnalyzer = methodAnalyzer;
this.instruction = instruction; this.instruction = instruction;
this.originalInstruction = instruction; this.originalInstruction = instruction;
@ -150,18 +154,17 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
instruction = originalInstruction; instruction = originalInstruction;
} }
public int getSuccessorCount() { @Nonnull
return successors.size(); public List<AnalyzedInstruction> getSuccessors() {
}
public List<AnalyzedInstruction> getSuccesors() {
return Collections.unmodifiableList(successors); return Collections.unmodifiableList(successors);
} }
@Nonnull
public Instruction getInstruction() { public Instruction getInstruction() {
return instruction; return instruction;
} }
@Nonnull
public Instruction getOriginalInstruction() { public Instruction getOriginalInstruction() {
return originalInstruction; return originalInstruction;
} }
@ -184,11 +187,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
if (predecessors.size() == 0) { if (predecessors.size() == 0) {
return false; return false;
} }
return predecessors.first().instructionIndex == -1;
if (predecessors.first().instructionIndex == -1) {
return true;
}
return false;
} }
/* /*
@ -237,6 +236,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
* @param registerNumber the register number * @param registerNumber the register number
* @return The register type resulting from merging the post-instruction register types from all predecessors * @return The register type resulting from merging the post-instruction register types from all predecessors
*/ */
@Nonnull
protected RegisterType getMergedPreRegisterTypeFromPredecessors(int registerNumber) { protected RegisterType getMergedPreRegisterTypeFromPredecessors(int registerNumber) {
RegisterType mergedRegisterType = null; RegisterType mergedRegisterType = null;
for (AnalyzedInstruction predecessor: predecessors) { 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; 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. * This is used to set the register type for only one branch from a conditional jump.
* *
* @param predecessor Which predecessor is being overriden * @param predecessor Which predecessor is being overridden
* @param registerNumber The register number of the register being overriden * @param registerNumber The register number of the register being overridden
* @param registerType The overridden register type * @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 * @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() { protected boolean isInvokeInit() {
if (instruction == null || !instruction.getOpcode().canInitializeReference()) { if (!instruction.getOpcode().canInitializeReference()) {
return false; return false;
} }
@ -364,10 +368,10 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
return false; 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(); AnalyzedInstruction previousInstruction = getPreviousInstruction();
if (previousInstruction != null && if (previousInstruction != null &&
previousInstruction.instruction != null &&
previousInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF && previousInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF &&
registerNumber == ((Instruction22c)previousInstruction.instruction).getRegisterB() && registerNumber == ((Instruction22c)previousInstruction.instruction).getRegisterB() &&
MethodAnalyzer.canNarrowAfterInstanceOf(previousInstruction, this, methodAnalyzer.getClassPath())) { MethodAnalyzer.canNarrowAfterInstanceOf(previousInstruction, this, methodAnalyzer.getClassPath())) {
@ -421,7 +425,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
return preRegisterMap[registerNumber]; return preRegisterMap[registerNumber];
} }
public int compareTo(AnalyzedInstruction analyzedInstruction) { public int compareTo(@Nonnull AnalyzedInstruction analyzedInstruction) {
if (instructionIndex < analyzedInstruction.instructionIndex) { if (instructionIndex < analyzedInstruction.instructionIndex) {
return -1; return -1;
} else if (instructionIndex == analyzedInstruction.instructionIndex) { } 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.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; 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.Opcodes;
import org.jf.dexlib2.analysis.reflection.ReflectionClassDef; 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.ClassDef;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.immutable.ImmutableDexFile; import org.jf.dexlib2.immutable.ImmutableDexFile;
import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ClassPath { public class ClassPath {
@Nonnull private final TypeProto unknownClass; @Nonnull private final TypeProto unknownClass;
@ -114,7 +104,7 @@ public class ClassPath {
private static ClassProvider getBasicClasses() { private static ClassProvider getBasicClasses() {
// fallbacks for some special classes that we assume are present // 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(Class.class),
new ReflectionClassDef(Cloneable.class), new ReflectionClassDef(Cloneable.class),
new ReflectionClassDef(Object.class), new ReflectionClassDef(Object.class),
@ -164,119 +154,6 @@ public class ClassPath {
return checkPackagePrivateAccess; 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( private final Supplier<OdexedFieldInstructionMapper> fieldInstructionMapperSupplier = Suppliers.memoize(
new Supplier<OdexedFieldInstructionMapper>() { new Supplier<OdexedFieldInstructionMapper>() {
@Override public OdexedFieldInstructionMapper get() { @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 com.google.common.primitives.Ints;
import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.analysis.util.TypeProtoUtils; import org.jf.dexlib2.analysis.util.TypeProtoUtils;
import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.base.reference.BaseMethodReference;
import org.jf.dexlib2.iface.Field; import org.jf.dexlib2.iface.*;
import org.jf.dexlib2.iface.Method;
import org.jf.dexlib2.iface.reference.FieldReference; import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.dexlib2.iface.reference.MethodReference; import org.jf.dexlib2.iface.reference.MethodReference;
import org.jf.dexlib2.immutable.ImmutableMethod;
import org.jf.dexlib2.util.MethodUtil; import org.jf.dexlib2.util.MethodUtil;
import org.jf.util.AlignmentUtils; import org.jf.util.AlignmentUtils;
import org.jf.util.ExceptionWithContext; import org.jf.util.ExceptionWithContext;
@ -53,6 +51,7 @@ import org.jf.util.SparseArray;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.*; 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 * 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 @Nonnull
protected LinkedHashMap<String, ClassDef> getInterfaces() { 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 @Nonnull
private final Supplier<LinkedHashMap<String, ClassDef>> interfacesSupplier = private final Supplier<LinkedHashMap<String, ClassDef>> preDefaultMethodInterfaceSupplier =
Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() { Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
@Override public LinkedHashMap<String, ClassDef> get() { @Override public LinkedHashMap<String, ClassDef> get() {
Set<String> unresolvedInterfaces = new HashSet<String>(0); Set<String> unresolvedInterfaces = new HashSet<String>(0);
@ -149,7 +155,8 @@ public class ClassProto implements TypeProto {
ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType); ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
for (String superInterface: interfaceProto.getInterfaces().keySet()) { for (String superInterface: interfaceProto.getInterfaces().keySet()) {
if (!interfaces.containsKey(superInterface)) { if (!interfaces.containsKey(superInterface)) {
interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface)); interfaces.put(superInterface,
interfaceProto.getInterfaces().get(superInterface));
} }
} }
if (!interfaceProto.interfacesFullyResolved) { if (!interfaceProto.interfacesFullyResolved) {
@ -159,6 +166,7 @@ public class ClassProto implements TypeProto {
} }
} }
} catch (UnresolvedClassException ex) { } catch (UnresolvedClassException ex) {
interfaces.put(type, null);
unresolvedInterfaces.add(type); unresolvedInterfaces.add(type);
interfacesFullyResolved = false; 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 @Nonnull
protected Set<String> getUnresolvedInterfaces() { protected Set<String> getUnresolvedInterfaces() {
if (unresolvedInterfaces == null) { if (unresolvedInterfaces == null) {
@ -379,7 +452,10 @@ public class ClassProto implements TypeProto {
} }
public int findMethodIndexInVtable(@Nonnull MethodReference method) { 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++) { for (int i=0; i<vtable.size(); i++) {
Method candidate = vtable.get(i); Method candidate = vtable.get(i);
if (MethodUtil.methodSignaturesMatch(candidate, method)) { if (MethodUtil.methodSignaturesMatch(candidate, method)) {
@ -392,7 +468,20 @@ public class ClassProto implements TypeProto {
return -1; 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()) { if (classPath.isArt()) {
return artInstanceFieldsSupplier.get(); return artInstanceFieldsSupplier.get();
} else { } else {
@ -440,9 +529,7 @@ public class ClassProto implements TypeProto {
ClassProto superclass = null; ClassProto superclass = null;
if (superclassType != null) { if (superclassType != null) {
superclass = (ClassProto) classPath.getClass(superclassType); superclass = (ClassProto) classPath.getClass(superclassType);
if (superclass != null) { startFieldOffset = superclass.getNextFieldOffset();
startFieldOffset = superclass.getNextFieldOffset();
}
} }
int fieldIndexMod; int fieldIndexMod;
@ -530,13 +617,11 @@ public class ClassProto implements TypeProto {
//add padding to align the wide fields, if needed //add padding to align the wide fields, if needed
if (fieldTypes[i] == WIDE && !gotDouble) { if (fieldTypes[i] == WIDE && !gotDouble) {
if (!gotDouble) { if (fieldOffset % 8 != 0) {
if (fieldOffset % 8 != 0) { assert fieldOffset % 8 == 4;
assert fieldOffset % 8 == 4; fieldOffset += 4;
fieldOffset += 4;
}
gotDouble = true;
} }
gotDouble = true;
} }
instanceFields.append(fieldOffset, field); instanceFields.append(fieldOffset, field);
@ -574,7 +659,7 @@ public class ClassProto implements TypeProto {
public static FieldGap newFieldGap(int offset, int size, int oatVersion) { public static FieldGap newFieldGap(int offset, int size, int oatVersion) {
if (oatVersion >= 67) { if (oatVersion >= 67) {
return new FieldGap(offset, size) { 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); int result = Ints.compare(o.size, size);
if (result != 0) { if (result != 0) {
return result; return result;
@ -584,7 +669,7 @@ public class ClassProto implements TypeProto {
}; };
} else { } else {
return new FieldGap(offset, size) { 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); int result = Ints.compare(size, o.size);
if (result != 0) { if (result != 0) {
return result; return result;
@ -778,12 +863,18 @@ public class ClassProto implements TypeProto {
throw new ExceptionWithContext("Invalid type: %s", type); throw new ExceptionWithContext("Invalid type: %s", type);
} }
@Nonnull List<Method> getVtable() { @Nonnull public List<Method> getVtable() {
return vtableSupplier.get(); 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 //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() { @Override public List<Method> get() {
List<Method> vtable = Lists.newArrayList(); 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 //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) //method (i.e. if it was implemented by the superclass)
if (!isInterface()) { 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 // We use the current class for any vtable method references that we add, rather than the interface, so
// otherwise it looks like that method is invoked on an interface, which fails Dalvik's optimization checks // we don't end up trying to call invoke-virtual using an interface, which will fail verification
for (ClassDef interfaceDef: getDirectInterfaces()) { Iterable<ClassDef> interfaces = getDirectInterfaces();
for (ClassDef interfaceDef: interfaces) {
List<Method> interfaceMethods = Lists.newArrayList(); List<Method> interfaceMethods = Lists.newArrayList();
for (Method interfaceMethod: interfaceDef.getVirtualMethods()) { for (Method interfaceMethod: interfaceDef.getVirtualMethods()) {
ImmutableMethod method = new ImmutableMethod( interfaceMethods.add(new ReparentedMethod(interfaceMethod, type));
type,
interfaceMethod.getName(),
interfaceMethod.getParameters(),
interfaceMethod.getReturnType(),
interfaceMethod.getAccessFlags(),
interfaceMethod.getAnnotations(),
interfaceMethod.getImplementation());
interfaceMethods.add(method);
} }
addToVtable(interfaceMethods, vtable, false); addToVtable(interfaceMethods, vtable, false, true);
} }
} }
return vtable; return vtable;
} }
});
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods, /**
@Nonnull List<Method> vtable, boolean replaceExisting) { * This is the vtable supplier for a version of art that had buggy vtable calculation logic. In some cases it can
List<? extends Method> methods = Lists.newArrayList(localMethods); * produce multiple vtable entries for a given virtual method. This supplier duplicates this buggy logic in order to
Collections.sort(methods); * 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) { //copy the virtual methods from the superclass
for (int i=0; i<vtable.size(); i++) { String superclassType;
Method superMethod = vtable.get(i); try {
if (MethodUtil.methodSignaturesMatch(superMethod, virtualMethod)) { superclassType = getSuperclass();
if (!classPath.shouldCheckPackagePrivateAccess() || } catch (UnresolvedClassException ex) {
AnalyzedMethodUtil.canAccess(ClassProto.this, superMethod, true, false, false)) { vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
if (replaceExisting) { vtableFullyResolved = false;
vtable.set(i, virtualMethod); 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 // we didn't find an equivalent method, so add it as a new entry
vtable.add(virtualMethod); vtable.add(virtualMethod);
} }
} }
}); }
private static byte getFieldType(@Nonnull FieldReference field) { private static byte getFieldType(@Nonnull FieldReference field) {
switch (field.getType().charAt(0)) { switch (field.getType().charAt(0)) {
@ -871,4 +1225,68 @@ public class ClassProto implements TypeProto {
return 2; //OTHER 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 com.google.common.collect.Lists;
import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.Opcode; import org.jf.dexlib2.Opcode;
import org.jf.dexlib2.base.reference.BaseMethodReference;
import org.jf.dexlib2.iface.*; import org.jf.dexlib2.iface.*;
import org.jf.dexlib2.iface.instruction.*; import org.jf.dexlib2.iface.instruction.*;
import org.jf.dexlib2.iface.instruction.formats.*; import org.jf.dexlib2.iface.instruction.formats.*;
@ -89,10 +90,10 @@ public class MethodAnalyzer {
@Nullable private AnalysisException analysisException = null; @Nullable private AnalysisException analysisException = null;
//This is a dummy instruction that occurs immediately before the first real instruction. We can initialize the // 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 // 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 // successors, e.g. the first real instruction, the first instructions in any exception handlers covering the first
//instruction, etc. // instruction, etc.
private final AnalyzedInstruction startOfMethod; private final AnalyzedInstruction startOfMethod;
public MethodAnalyzer(@Nonnull ClassPath classPath, @Nonnull Method method, public MethodAnalyzer(@Nonnull ClassPath classPath, @Nonnull Method method,
@ -110,27 +111,16 @@ public class MethodAnalyzer {
this.methodImpl = methodImpl; this.methodImpl = methodImpl;
//override AnalyzedInstruction and provide custom implementations of some of the methods, so that we don't // 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 // have to handle the case this special case of instruction being null, in the main class
startOfMethod = new AnalyzedInstruction(this, null, -1, methodImpl.getRegisterCount()) { startOfMethod = new AnalyzedInstruction(this, new ImmutableInstruction10x(Opcode.NOP), -1, methodImpl.getRegisterCount()) {
public boolean setsRegister() { @Override protected boolean addPredecessor(AnalyzedInstruction predecessor) {
return false; throw new UnsupportedOperationException();
} }
@Override @Override @Nonnull
public boolean setsWideRegister() { public RegisterType getPredecessorRegisterType(@Nonnull AnalyzedInstruction predecessor, int registerNumber) {
return false; throw new UnsupportedOperationException();
}
@Override
public boolean setsRegister(int registerNumber) {
return false;
}
@Override
public int getDestinationRegister() {
assert false;
return -1;
} }
}; };
@ -141,6 +131,7 @@ public class MethodAnalyzer {
analyze(); analyze();
} }
@Nonnull
public ClassPath getClassPath() { public ClassPath getClassPath() {
return classPath; return classPath;
} }
@ -1176,32 +1167,36 @@ public class MethodAnalyzer {
setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, castRegisterType); 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, static boolean canNarrowAfterInstanceOf(AnalyzedInstruction analyzedInstanceOfInstruction,
AnalyzedInstruction analyzedIfInstruction, ClassPath classPath) { AnalyzedInstruction analyzedIfInstruction, ClassPath classPath) {
Instruction ifInstruction = analyzedIfInstruction.instruction; Instruction ifInstruction = analyzedIfInstruction.instruction;
assert analyzedIfInstruction.instruction != null;
if (((Instruction21t)ifInstruction).getRegisterA() == analyzedInstanceOfInstruction.getDestinationRegister()) { if (((Instruction21t)ifInstruction).getRegisterA() == analyzedInstanceOfInstruction.getDestinationRegister()) {
Reference reference = ((Instruction22c)analyzedInstanceOfInstruction.getInstruction()).getReference(); Reference reference = ((Instruction22c)analyzedInstanceOfInstruction.getInstruction()).getReference();
RegisterType registerType = RegisterType.getRegisterType(classPath, (TypeReference)reference); RegisterType registerType = RegisterType.getRegisterType(classPath, (TypeReference)reference);
if (registerType.type != null && !registerType.type.isInterface()) { try {
int objectRegister = ((TwoRegisterInstruction)analyzedInstanceOfInstruction.getInstruction()) if (registerType.type != null && !registerType.type.isInterface()) {
.getRegisterB(); int objectRegister = ((TwoRegisterInstruction)analyzedInstanceOfInstruction.getInstruction())
.getRegisterB();
RegisterType originalType = analyzedIfInstruction.getPreInstructionRegisterType(objectRegister); RegisterType originalType = analyzedIfInstruction.getPreInstructionRegisterType(objectRegister);
if (originalType.type != null) { return isNarrowingConversion(originalType, registerType);
// 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;
}
}
} }
} catch (UnresolvedClassException ex) {
return false;
} }
} }
return false; return false;
@ -1216,16 +1211,47 @@ public class MethodAnalyzer {
private void analyzeIfEqzNez(@Nonnull AnalyzedInstruction analyzedInstruction) { private void analyzeIfEqzNez(@Nonnull AnalyzedInstruction analyzedInstruction) {
int instructionIndex = analyzedInstruction.getInstructionIndex(); int instructionIndex = analyzedInstruction.getInstructionIndex();
if (instructionIndex > 0) { if (instructionIndex > 0) {
AnalyzedInstruction prevAnalyzedInstruction = analyzedInstructions.valueAt(instructionIndex - 1); if (analyzedInstruction.getPredecessorCount() != 1) {
if (prevAnalyzedInstruction.instruction != null && return;
prevAnalyzedInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF) { }
AnalyzedInstruction prevAnalyzedInstruction = analyzedInstruction.getPredecessors().first();
if (prevAnalyzedInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF) {
if (canNarrowAfterInstanceOf(prevAnalyzedInstruction, analyzedInstruction, classPath)) { if (canNarrowAfterInstanceOf(prevAnalyzedInstruction, analyzedInstruction, classPath)) {
// Propagate the original type to the failing branch, and the new type to the successful branch List<Integer> narrowingRegisters = Lists.newArrayList();
int narrowingRegister = ((Instruction22c)prevAnalyzedInstruction.instruction).getRegisterB();
RegisterType originalType = analyzedInstruction.getPreInstructionRegisterType(narrowingRegister);
RegisterType newType = RegisterType.getRegisterType(classPath, RegisterType newType = RegisterType.getRegisterType(classPath,
(TypeReference)((Instruction22c)prevAnalyzedInstruction.instruction).getReference()); (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 fallthroughInstruction = analyzedInstructions.valueAt(
analyzedInstruction.getInstructionIndex() + 1); analyzedInstruction.getInstructionIndex() + 1);
@ -1233,16 +1259,18 @@ public class MethodAnalyzer {
((Instruction21t)analyzedInstruction.instruction).getCodeOffset(); ((Instruction21t)analyzedInstruction.instruction).getCodeOffset();
AnalyzedInstruction branchInstruction = analyzedInstructions.get(nextAddress); AnalyzedInstruction branchInstruction = analyzedInstructions.get(nextAddress);
if (analyzedInstruction.instruction.getOpcode() == Opcode.IF_EQZ) { for (int register: narrowingRegisters) {
overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction, if (analyzedInstruction.instruction.getOpcode() == Opcode.IF_EQZ) {
narrowingRegister, newType); overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction,
overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction, register, newType);
narrowingRegister, originalType); overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
} else { register, originalType);
overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction, } else {
narrowingRegister, originalType); overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction,
overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction, register, originalType);
narrowingRegister, newType); 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 // fieldClass is now the first accessible class found. Now. we need to make sure that the field is
// actually valid for this class // actually valid for this class
resolvedField = classPath.getClass(fieldClass.getType()).getFieldByOffset(fieldOffset); FieldReference newResolvedField = classPath.getClass(fieldClass.getType()).getFieldByOffset(fieldOffset);
if (resolvedField == null) { if (newResolvedField == null) {
throw new ExceptionWithContext("Couldn't find accessible class while resolving field %s", throw new ExceptionWithContext("Couldn't find accessible class while resolving field %s",
ReferenceUtil.getShortFieldDescriptor(resolvedField)); ReferenceUtil.getShortFieldDescriptor(resolvedField));
} }
resolvedField = new ImmutableFieldReference(fieldClass.getType(), resolvedField.getName(), resolvedField = new ImmutableFieldReference(fieldClass.getType(), newResolvedField.getName(),
resolvedField.getType()); newResolvedField.getType());
} }
String fieldType = resolvedField.getType(); String fieldType = resolvedField.getType();
@ -1733,41 +1761,9 @@ public class MethodAnalyzer {
targetMethod = (MethodReference)instruction.getReference(); targetMethod = (MethodReference)instruction.getReference();
} }
TypeProto typeProto = classPath.getClass(targetMethod.getDefiningClass()); MethodReference replacementMethod = normalizeMethodReference(targetMethod);
int methodIndex;
try {
methodIndex = typeProto.findMethodIndexInVtable(targetMethod);
} catch (UnresolvedClassException ex) {
return true;
}
if (methodIndex < 0) { if (replacementMethod == null || replacementMethod.equals(targetMethod)) {
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)) {
return true; 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. // no need to check class access for invoke-super. A class can obviously access its superclass.
ClassDef thisClass = classPath.getClassDef(method.getDefiningClass()); 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()))) { thisClass.getType(), classPath.getClassDef(resolvedMethod.getDefiningClass()))) {
// the class is not accessible. So we start looking at objectRegisterTypeProto (which may be different // the class is not accessible. So we start looking at objectRegisterTypeProto (which may be different
@ -1860,13 +1858,20 @@ public class MethodAnalyzer {
MethodReference newResolvedMethod = MethodReference newResolvedMethod =
classPath.getClass(methodClass.getType()).getMethodByVtableIndex(methodIndex); classPath.getClass(methodClass.getType()).getMethodByVtableIndex(methodIndex);
if (newResolvedMethod == null) { if (newResolvedMethod == null) {
// TODO: fix NPE here
throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s", throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s",
ReferenceUtil.getMethodDescriptor(resolvedMethod, true)); ReferenceUtil.getMethodDescriptor(resolvedMethod, true));
} }
resolvedMethod = newResolvedMethod; resolvedMethod = newResolvedMethod;
resolvedMethod = new ImmutableMethodReference(methodClass.getType(), resolvedMethod.getName(), resolvedMethod = new ImmutableMethodReference(methodClass.getType(), resolvedMethod.getName(),
resolvedMethod.getParameterTypes(), resolvedMethod.getReturnType()); resolvedMethod.getParameterTypes(), resolvedMethod.getReturnType());
}
if (normalizeVirtualMethods) {
MethodReference replacementMethod = normalizeMethodReference(resolvedMethod);
if (replacementMethod != null) {
resolvedMethod = replacementMethod;
}
} }
Instruction deodexedInstruction; Instruction deodexedInstruction;
@ -1967,4 +1972,70 @@ public class MethodAnalyzer {
"pair because it is the last register.", registerNumber)); "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 '[': case '[':
return getRegisterType(REFERENCE, classPath.getClass(type)); return getRegisterType(REFERENCE, classPath.getClass(type));
default: 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; package org.jf.dexlib2.analysis.reflection.util;
import com.google.common.collect.ImmutableBiMap;
public class ReflectionUtils { 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) { public static String javaToDexName(String javaName) {
javaName = javaName.replace('.', '/'); if (javaName.charAt(0) == '[') {
if (javaName.length() > 1 && javaName.charAt(javaName.length()-1) != ';') { return javaName.replace('.', '/');
javaName = 'L' + javaName + ';';
} }
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; package org.jf.dexlib2.base.reference;
import org.jf.dexlib2.iface.reference.FieldReference; import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.dexlib2.util.ReferenceUtil;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -64,4 +65,8 @@ public abstract class BaseFieldReference implements FieldReference {
if (res != 0) return res; if (res != 0) return res;
return getType().compareTo(o.getType()); 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 com.google.common.collect.Ordering;
import org.jf.dexlib2.iface.reference.MethodProtoReference; import org.jf.dexlib2.iface.reference.MethodProtoReference;
import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.util.CharSequenceUtils; import org.jf.util.CharSequenceUtils;
import org.jf.util.CollectionUtils; import org.jf.util.CollectionUtils;
@ -63,4 +64,8 @@ public abstract class BaseMethodProtoReference implements MethodProtoReference {
if (res != 0) return res; if (res != 0) return res;
return CollectionUtils.compareAsIterable(Ordering.usingToString(), getParameterTypes(), o.getParameterTypes()); 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 com.google.common.collect.Ordering;
import org.jf.dexlib2.iface.reference.MethodReference; import org.jf.dexlib2.iface.reference.MethodReference;
import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.util.CharSequenceUtils; import org.jf.util.CharSequenceUtils;
import org.jf.util.CollectionUtils; import org.jf.util.CollectionUtils;
@ -70,4 +71,8 @@ public abstract class BaseMethodReference implements MethodReference {
if (res != 0) return res; if (res != 0) return res;
return CollectionUtils.compareAsIterable(Ordering.usingToString(), getParameterTypes(), o.getParameterTypes()); 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 int length() { return getString().length(); }
@Override public char charAt(int index) { return getString().charAt(index); } @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 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 com.google.common.io.ByteStreams;
import org.jf.dexlib2.Opcodes; import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.ReferenceType;
import org.jf.dexlib2.dexbacked.raw.*; 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.dexbacked.util.FixedSizeSet;
import org.jf.dexlib2.iface.DexFile; import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.iface.reference.Reference;
import org.jf.util.ExceptionWithContext; import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -43,6 +49,8 @@ import javax.annotation.Nullable;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.AbstractList;
import java.util.List;
import java.util.Set; import java.util.Set;
public class DexBackedDexFile extends BaseDexBuffer implements DexFile { 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 classCount;
private final int classStartOffset; 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); super(buf, offset);
this.opcodes = opcodes; this.opcodes = opcodes;
@ -85,7 +93,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
} }
public DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull BaseDexBuffer buf) { 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) { 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); this(opcodes, buf, 0, true);
} }
@Nonnull
public static DexBackedDexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is) public static DexBackedDexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is)
throws IOException { throws IOException {
if (!is.markSupported()) { 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)) { if (!HeaderItem.verifyMagic(buf, offset)) {
StringBuilder sb = new StringBuilder("Invalid magic value:"); StringBuilder sb = new StringBuilder("Invalid magic value:");
for (int i=0; i<8; i++) { for (int i=0; i<8; i++) {
@ -265,6 +274,81 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
return getType(typeIndex); 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 @Override
@Nonnull @Nonnull
public DexReader readerAt(int offset) { public DexReader readerAt(int offset) {

View File

@ -129,7 +129,11 @@ public class DexBackedMethodImplementation implements MethodImplementation {
return DebugInfo.newOrEmpty(dexFile, 0, this); return DebugInfo.newOrEmpty(dexFile, 0, this);
} }
if (debugOffset < 0) { 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, 0, this);
} }
return DebugInfo.newOrEmpty(dexFile, debugOffset, this); return DebugInfo.newOrEmpty(dexFile, debugOffset, this);

View File

@ -31,22 +31,29 @@
package org.jf.dexlib2.dexbacked; 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 com.google.common.io.ByteStreams;
import org.jf.dexlib2.Opcodes; 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.OatFile.SymbolTable.Symbol;
import org.jf.dexlib2.dexbacked.raw.HeaderItem; import org.jf.dexlib2.dexbacked.raw.HeaderItem;
import org.jf.dexlib2.iface.MultiDexContainer;
import org.jf.util.AbstractForwardSequentialList; import org.jf.util.AbstractForwardSequentialList;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.AbstractList; import java.util.AbstractList;
import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; 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[] ELF_MAGIC = new byte[] { 0x7f, 'E', 'L', 'F' };
private static final byte[] OAT_MAGIC = new byte[] { 'o', 'a', 't', '\n' }; private static final byte[] OAT_MAGIC = new byte[] { 'o', 'a', 't', '\n' };
private static final int MIN_ELF_HEADER_SIZE = 52; 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. // 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. // Later version may or may not work, depending on what changed.
private static final int MIN_OAT_VERSION = 56; 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 UNSUPPORTED = 0;
public static final int SUPPORTED = 1; public static final int SUPPORTED = 1;
@ -148,6 +155,18 @@ public class OatFile extends BaseDexBuffer {
return UNKNOWN; 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 @Nonnull
public List<OatDexFile> getDexFiles() { public List<OatDexFile> getDexFiles() {
return new AbstractForwardSequentialList<OatDexFile>() { return new AbstractForwardSequentialList<OatDexFile>() {
@ -156,44 +175,44 @@ public class OatFile extends BaseDexBuffer {
} }
@Nonnull @Override public Iterator<OatDexFile> iterator() { @Nonnull @Override public Iterator<OatDexFile> iterator() {
return new Iterator<OatDexFile>() { return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, OatDexFile>() {
int index = 0; @Nullable @Override public OatDexFile apply(DexEntry dexEntry) {
int offset = oatHeader.getDexListStart(); return dexEntry.getDexFile();
@Override public boolean hasNext() {
return index < size();
} }
});
@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; @Nonnull public final String filename;
public OatDexFile(int offset, @Nonnull String filename) { public OatDexFile(int offset, @Nonnull String filename) {
@ -201,8 +220,12 @@ public class OatFile extends BaseDexBuffer {
this.filename = filename; this.filename = filename;
} }
public int getOatVersion() { @Nonnull @Override public String getEntryName() {
return OatFile.this.getOatVersion(); return filename;
}
@Nonnull @Override public OatFile getContainer() {
return OatFile.this;
} }
@Override public boolean hasOdexOpcodes() { @Override public boolean hasOdexOpcodes() {
@ -211,57 +234,87 @@ public class OatFile extends BaseDexBuffer {
} }
private class OatHeader { private class OatHeader {
private final int offset; private final int headerOffset;
public OatHeader(int offset) { public OatHeader(int offset) {
this.offset = offset; this.headerOffset = offset;
} }
public boolean isValid() { public boolean isValid() {
for (int i=0; i<OAT_MAGIC.length; i++) { 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; return false;
} }
} }
for (int i=4; i<7; i++) { 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 false;
} }
} }
return buf[offset + 7] == 0; return buf[headerOffset + 7] == 0;
} }
public int getVersion() { public int getVersion() {
return Integer.valueOf(new String(buf, offset + 4, 3)); return Integer.valueOf(new String(buf, headerOffset + 4, 3));
} }
public int getDexFileCount() { public int getDexFileCount() {
return readSmallUint(offset + 20); return readSmallUint(headerOffset + 20);
} }
public int getKeyValueStoreSize() { public int getKeyValueStoreSize() {
int version = getVersion(); if (getVersion() < MIN_OAT_VERSION) {
if (version < 56) {
throw new IllegalStateException("Unsupported oat version"); throw new IllegalStateException("Unsupported oat version");
} }
int fieldOffset = 17 * 4; int fieldOffset = 17 * 4;
return readSmallUint(offset + fieldOffset); return readSmallUint(headerOffset + fieldOffset);
} }
public int getHeaderSize() { public int getHeaderSize() {
int version = getVersion(); if (getVersion() < MIN_OAT_VERSION) {
if (version >= 56) {
return 18*4 + getKeyValueStoreSize();
} else {
throw new IllegalStateException("Unsupported 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() { 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")); 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 { public static class InvalidOatFileException extends RuntimeException {
@ -493,4 +603,5 @@ public class OatFile extends BaseDexBuffer {
public static class NotAnOatFileException extends RuntimeException { public static class NotAnOatFileException extends RuntimeException {
public NotAnOatFileException() {} 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 protected final ImmutableSet<? extends ImmutableClassDef> classes;
@Nonnull private final Opcodes opcodes; @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) { public ImmutableDexFile(@Nonnull Opcodes opcodes, @Nullable Collection<? extends ClassDef> classes) {
this.classes = ImmutableClassDef.immutableSetOf(classes); this.classes = ImmutableClassDef.immutableSetOf(classes);
this.opcodes = opcodes; this.opcodes = opcodes;

View File

@ -192,12 +192,12 @@ public abstract class DexWriter<
private int getDataSectionOffset() { private int getDataSectionOffset() {
return HeaderItem.ITEM_SIZE + return HeaderItem.ITEM_SIZE +
stringSection.getItems().size() * StringIdItem.ITEM_SIZE + stringSection.getItemCount() * StringIdItem.ITEM_SIZE +
typeSection.getItems().size() * TypeIdItem.ITEM_SIZE + typeSection.getItemCount() * TypeIdItem.ITEM_SIZE +
protoSection.getItems().size() * ProtoIdItem.ITEM_SIZE + protoSection.getItemCount() * ProtoIdItem.ITEM_SIZE +
fieldSection.getItems().size() * FieldIdItem.ITEM_SIZE + fieldSection.getItemCount() * FieldIdItem.ITEM_SIZE +
methodSection.getItems().size() * MethodIdItem.ITEM_SIZE + methodSection.getItemCount() * MethodIdItem.ITEM_SIZE +
classSection.getItems().size() * ClassDefItem.ITEM_SIZE; classSection.getItemCount() * ClassDefItem.ITEM_SIZE;
} }
@Nonnull @Nonnull
@ -227,6 +227,22 @@ public abstract class DexWriter<
return classReferences; 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 { public void writeTo(@Nonnull DexDataStore dest) throws IOException {
this.writeTo(dest, MemoryDeferredOutputStream.getFactory()); this.writeTo(dest, MemoryDeferredOutputStream.getFactory());
} }

View File

@ -38,4 +38,5 @@ import java.util.Map;
public interface IndexSection<Key> { public interface IndexSection<Key> {
int getItemIndex(@Nonnull Key key); int getItemIndex(@Nonnull Key key);
@Nonnull Collection<? extends Map.Entry<? extends Key, Integer>> getItems(); @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 { private static class MethodKey extends BaseMethodReference implements MethodReference {
@Nonnull private final String definingClass; @Nonnull private final String definingClass;
@Nonnull private final String name; @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 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) { @Nonnull public static DexBuilder makeDexBuilder(@Nonnull Opcodes opcodes) {
BuilderContext context = new BuilderContext(); BuilderContext context = new BuilderContext();
return new DexBuilder(opcodes, context); return new DexBuilder(opcodes, context);

View File

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

View File

@ -31,7 +31,6 @@
package org.jf.dexlib2.writer.pool; package org.jf.dexlib2.writer.pool;
import com.google.common.collect.Maps;
import org.jf.dexlib2.writer.OffsetSection; import org.jf.dexlib2.writer.OffsetSection;
import org.jf.util.ExceptionWithContext; import org.jf.util.ExceptionWithContext;
@ -39,9 +38,7 @@ import javax.annotation.Nonnull;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
public abstract class BaseOffsetPool<Key> implements OffsetSection<Key> { public abstract class BaseOffsetPool<Key> extends BasePool<Key, Integer> implements OffsetSection<Key> {
@Nonnull protected final Map<Key, Integer> internedItems = Maps.newHashMap();
@Nonnull @Override public Collection<? extends Map.Entry<? extends Key, Integer>> getItems() { @Nonnull @Override public Collection<? extends Map.Entry<? extends Key, Integer>> getItems() {
return internedItems.entrySet(); 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.*;
import java.util.Map.Entry; 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, TypeListPool.Key<? extends Collection<? extends CharSequence>>, PoolClassDef, Field, PoolMethod,
Set<? extends Annotation>, EncodedValue> { Set<? extends Annotation>, EncodedValue> {
@Nonnull private HashMap<String, PoolClassDef> internedItems = Maps.newHashMap();
@Nonnull private final StringPool stringPool; @Nonnull private final StringPool stringPool;
@Nonnull private final TypePool typePool; @Nonnull private final TypePool typePool;
@Nonnull private final FieldPool fieldPool; @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, TypeListPool.Key<? extends Collection<? extends CharSequence>>, Field, PoolMethod,
EncodedValue, AnnotationElement> { EncodedValue, AnnotationElement> {
@Nonnull private final Markable[] sections = new Markable[] {
public static DexPool makeDexPool() { (Markable)stringSection, (Markable)typeSection, (Markable)protoSection, (Markable)fieldSection,
return makeDexPool(Opcodes.forApi(20)); (Markable)methodSection, (Markable)classSection, (Markable)typeListSection, (Markable)annotationSection,
} (Markable)annotationSetSection
};
@Deprecated
@Nonnull
public static DexPool makeDexPool(int api) {
return makeDexPool(Opcodes.forApi(api));
}
@Nonnull @Nonnull
public static DexPool makeDexPool(@Nonnull Opcodes opcodes) { public static DexPool makeDexPool(@Nonnull Opcodes opcodes) {
@ -84,29 +79,60 @@ public class DexPool extends DexWriter<CharSequence, StringReference, CharSequen
annotationPool, annotationSetPool); annotationPool, annotationSetPool);
} }
private DexPool(Opcodes opcodes, StringPool stringPool, TypePool typePool, ProtoPool protoPool, FieldPool fieldPool, protected DexPool(Opcodes opcodes, StringPool stringPool, TypePool typePool, ProtoPool protoPool,
MethodPool methodPool, ClassPool classPool, TypeListPool typeListPool, FieldPool fieldPool, MethodPool methodPool, ClassPool classPool, TypeListPool typeListPool,
AnnotationPool annotationPool, AnnotationSetPool annotationSetPool) { AnnotationPool annotationPool, AnnotationSetPool annotationSetPool) {
super(opcodes, stringPool, typePool, protoPool, fieldPool, methodPool, super(opcodes, stringPool, typePool, protoPool, fieldPool, methodPool,
classPool, typeListPool, annotationPool, annotationSetPool); classPool, typeListPool, annotationPool, annotationSetPool);
} }
public static void writeTo(@Nonnull DexDataStore dataStore, @Nonnull org.jf.dexlib2.iface.DexFile input) throws IOException { public static void writeTo(@Nonnull DexDataStore dataStore, @Nonnull org.jf.dexlib2.iface.DexFile input)
DexPool dexPool = makeDexPool(); throws IOException {
DexPool dexPool = makeDexPool(input.getOpcodes());
for (ClassDef classDef: input.getClasses()) { for (ClassDef classDef: input.getClasses()) {
((ClassPool)dexPool.classSection).intern(classDef); dexPool.internClass(classDef);
} }
dexPool.writeTo(dataStore); dexPool.writeTo(dataStore);
} }
public static void writeTo(@Nonnull String path, @Nonnull org.jf.dexlib2.iface.DexFile input) throws IOException { 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()) { for (ClassDef classDef: input.getClasses()) {
((ClassPool)dexPool.classSection).intern(classDef); dexPool.internClass(classDef);
} }
dexPool.writeTo(new FileDataStore(new File(path))); 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, @Override protected void writeEncodedValue(@Nonnull InternalEncodedValueWriter writer,
@Nonnull EncodedValue encodedValue) throws IOException { @Nonnull EncodedValue encodedValue) throws IOException {
switch (encodedValue.getValueType()) { 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 StringPool stringPool,
@Nonnull TypePool typePool, @Nonnull TypePool typePool,
@Nonnull FieldPool fieldPool, @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; package org.jf.dexlib2.writer.pool;
import com.google.common.collect.Maps;
import org.jf.dexlib2.writer.DexWriter; import org.jf.dexlib2.writer.DexWriter;
import org.jf.dexlib2.writer.NullableIndexSection; import org.jf.dexlib2.writer.NullableIndexSection;
import org.jf.util.ExceptionWithContext; import org.jf.util.ExceptionWithContext;
@ -41,9 +40,8 @@ import javax.annotation.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
public abstract class StringTypeBasePool implements NullableIndexSection<CharSequence> { public abstract class StringTypeBasePool extends BasePool<String, Integer>
@Nonnull protected final Map<String, Integer> internedItems = Maps.newHashMap(); implements NullableIndexSection<CharSequence>, Markable {
@Nonnull @Override public Collection<Map.Entry<String, Integer>> getItems() { @Nonnull @Override public Collection<Map.Entry<String, Integer>> getItems() {
return internedItems.entrySet(); return internedItems.entrySet();
} }

View File

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

View File

@ -51,11 +51,12 @@ import org.jf.dexlib2.immutable.instruction.ImmutableInstruction35mi;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.io.IOException;
import java.util.List; import java.util.List;
public class CustomMethodInlineTableTest { public class CustomMethodInlineTableTest {
@Test @Test
public void testCustomMethodInlineTable_Virtual() { public void testCustomMethodInlineTable_Virtual() throws IOException {
List<ImmutableInstruction> instructions = Lists.newArrayList( List<ImmutableInstruction> instructions = Lists.newArrayList(
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0), new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
new ImmutableInstruction10x(Opcode.RETURN_VOID)); new ImmutableInstruction10x(Opcode.RETURN_VOID));
@ -67,10 +68,12 @@ public class CustomMethodInlineTableTest {
ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null, ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
null, null, null, null, null, ImmutableList.of(method)); 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"); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false); MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
@ -82,7 +85,7 @@ public class CustomMethodInlineTableTest {
} }
@Test @Test
public void testCustomMethodInlineTable_Static() { public void testCustomMethodInlineTable_Static() throws IOException {
List<ImmutableInstruction> instructions = Lists.newArrayList( List<ImmutableInstruction> instructions = Lists.newArrayList(
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0), new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
new ImmutableInstruction10x(Opcode.RETURN_VOID)); new ImmutableInstruction10x(Opcode.RETURN_VOID));
@ -94,10 +97,12 @@ public class CustomMethodInlineTableTest {
ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null, ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
null, null, null, null, ImmutableList.of(method), 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"); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false); MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
@ -109,7 +114,7 @@ public class CustomMethodInlineTableTest {
} }
@Test @Test
public void testCustomMethodInlineTable_Direct() { public void testCustomMethodInlineTable_Direct() throws IOException {
List<ImmutableInstruction> instructions = Lists.newArrayList( List<ImmutableInstruction> instructions = Lists.newArrayList(
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0), new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
new ImmutableInstruction10x(Opcode.RETURN_VOID)); new ImmutableInstruction10x(Opcode.RETURN_VOID));
@ -121,10 +126,12 @@ public class CustomMethodInlineTableTest {
ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null, ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
null, null, null, null, ImmutableList.of(method), 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"); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false); 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( ImmutableSet<ClassDef> classes = ImmutableSet.<ClassDef>of(
objectClassDef, oneClassDef, twoClassDef, threeClassDef); 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 objectClassProto = classPath.getClass("Ljava/lang/Object;");
TypeProto oneClassProto = classPath.getClass("Ltest/one;"); TypeProto oneClassProto = classPath.getClass("Ltest/one;");
@ -88,7 +88,7 @@ public class SuperclassChainTest {
ClassDef twoClassDef = TestUtils.makeClassDef("Ltest/two;", "Ltest/one;"); ClassDef twoClassDef = TestUtils.makeClassDef("Ltest/two;", "Ltest/one;");
ClassDef threeClassDef = TestUtils.makeClassDef("Ltest/three;", "Ltest/two;"); ClassDef threeClassDef = TestUtils.makeClassDef("Ltest/three;", "Ltest/two;");
ImmutableSet<ClassDef> classes = ImmutableSet.<ClassDef>of(twoClassDef, threeClassDef); 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 unknownClassProto = classPath.getUnknownClass();
TypeProto oneClassProto = classPath.getClass("Ltest/one;"); 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(); MemoryDataStore dataStore = new MemoryDataStore();
try { 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) { } catch (IOException ex) {
throw new RuntimeException(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); ClassDef dbClassDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(dbClassDef); Assert.assertNotNull(dbClassDef);
Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null); Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null);
@ -112,12 +112,12 @@ public class DexWriterTest {
MemoryDataStore dataStore = new MemoryDataStore(); MemoryDataStore dataStore = new MemoryDataStore();
try { 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) { } catch (IOException ex) {
throw new RuntimeException(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); ClassDef dbClassDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(dbClassDef); Assert.assertNotNull(dbClassDef);
Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null); Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null);

View File

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

View File

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

View File

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

View File

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