mirror of
https://github.com/revanced/smali.git
synced 2025-05-28 11:50:12 +02:00
Merge branch 'v2.2_WIP'
This commit is contained in:
commit
2d0f6254b1
@ -41,8 +41,8 @@ buildscript {
|
||||
dependencies {
|
||||
compile project(':util')
|
||||
compile project(':dexlib2')
|
||||
compile depends.commons_cli
|
||||
compile depends.guava
|
||||
compile depends.jcommander
|
||||
|
||||
testCompile depends.junit
|
||||
testCompile project(':smali')
|
||||
@ -59,7 +59,7 @@ task fatJar(type: Jar) {
|
||||
classifier = 'fat'
|
||||
|
||||
manifest {
|
||||
attributes('Main-Class': 'org.jf.baksmali.main')
|
||||
attributes('Main-Class': 'org.jf.baksmali.Main')
|
||||
}
|
||||
|
||||
doLast {
|
||||
@ -92,7 +92,7 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
|
||||
dontobfuscate
|
||||
dontoptimize
|
||||
|
||||
keep 'public class org.jf.baksmali.main { public static void main(java.lang.String[]); }'
|
||||
keep 'public class org.jf.baksmali.Main { public static void main(java.lang.String[]); }'
|
||||
keepclassmembers 'enum * { public static **[] values(); public static ** valueOf(java.lang.String); }'
|
||||
|
||||
dontwarn 'com.google.common.**'
|
||||
@ -100,3 +100,17 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
|
||||
}
|
||||
|
||||
tasks.getByPath(':release').dependsOn(proguard)
|
||||
|
||||
task fastbuild(dependsOn: build) {
|
||||
}
|
||||
|
||||
task fb(dependsOn: fastbuild) {
|
||||
}
|
||||
|
||||
tasks.getByPath('javadoc').onlyIf({
|
||||
!gradle.taskGraph.hasTask(fastbuild)
|
||||
})
|
||||
|
||||
tasks.getByPath('test').onlyIf({
|
||||
!gradle.taskGraph.hasTask(fastbuild)
|
||||
})
|
@ -28,7 +28,7 @@
|
||||
|
||||
package org.jf.baksmali.Adaptors;
|
||||
|
||||
import org.jf.baksmali.baksmaliOptions;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.util.IndentingWriter;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@ -42,7 +42,7 @@ public class CatchMethodItem extends MethodItem {
|
||||
private final LabelMethodItem tryEndLabel;
|
||||
private final LabelMethodItem handlerLabel;
|
||||
|
||||
public CatchMethodItem(@Nonnull baksmaliOptions options, @Nonnull MethodDefinition.LabelCache labelCache,
|
||||
public CatchMethodItem(@Nonnull BaksmaliOptions options, @Nonnull MethodDefinition.LabelCache labelCache,
|
||||
int codeAddress, @Nullable String exceptionType, int startAddress, int endAddress,
|
||||
int handlerAddress) {
|
||||
super(codeAddress);
|
||||
|
@ -28,8 +28,7 @@
|
||||
|
||||
package org.jf.baksmali.Adaptors;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.jf.baksmali.baksmaliOptions;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.dexlib2.AccessFlags;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedClassDef;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile.InvalidItemIndex;
|
||||
@ -46,16 +45,16 @@ import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class ClassDefinition {
|
||||
@Nonnull public final baksmaliOptions options;
|
||||
@Nonnull public final BaksmaliOptions options;
|
||||
@Nonnull public final ClassDef classDef;
|
||||
@Nonnull private final HashSet<String> fieldsSetInStaticConstructor;
|
||||
|
||||
protected boolean validationErrors;
|
||||
|
||||
public ClassDefinition(@Nonnull baksmaliOptions options, @Nonnull ClassDef classDef) {
|
||||
public ClassDefinition(@Nonnull BaksmaliOptions options, @Nonnull ClassDef classDef) {
|
||||
this.options = options;
|
||||
this.classDef = classDef;
|
||||
fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor();
|
||||
fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor(classDef);
|
||||
}
|
||||
|
||||
public boolean hadValidationErrors() {
|
||||
@ -63,7 +62,7 @@ public class ClassDefinition {
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private HashSet<String> findFieldsSetInStaticConstructor() {
|
||||
private static HashSet<String> findFieldsSetInStaticConstructor(@Nonnull ClassDef classDef) {
|
||||
HashSet<String> fieldsSetInStaticConstructor = new HashSet<String>();
|
||||
|
||||
for (Method method: classDef.getDirectMethods()) {
|
||||
@ -166,7 +165,7 @@ public class ClassDefinition {
|
||||
writer.write("# annotations\n");
|
||||
|
||||
String containingClass = null;
|
||||
if (options.useImplicitReferences) {
|
||||
if (options.implicitReferences) {
|
||||
containingClass = classDef.getType();
|
||||
}
|
||||
|
||||
|
@ -28,14 +28,14 @@
|
||||
|
||||
package org.jf.baksmali.Adaptors;
|
||||
|
||||
import org.jf.baksmali.baksmaliOptions;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class EndTryLabelMethodItem extends LabelMethodItem {
|
||||
private int endTryAddress;
|
||||
|
||||
public EndTryLabelMethodItem(@Nonnull baksmaliOptions options, int codeAddress, int endTryAddress) {
|
||||
public EndTryLabelMethodItem(@Nonnull BaksmaliOptions options, int codeAddress, int endTryAddress) {
|
||||
super(options, codeAddress, "try_end_");
|
||||
this.endTryAddress = endTryAddress;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@
|
||||
package org.jf.baksmali.Adaptors;
|
||||
|
||||
import org.jf.baksmali.Adaptors.EncodedValue.EncodedValueAdaptor;
|
||||
import org.jf.baksmali.baksmaliOptions;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.dexlib2.AccessFlags;
|
||||
import org.jf.dexlib2.iface.Annotation;
|
||||
import org.jf.dexlib2.iface.Field;
|
||||
@ -41,7 +41,7 @@ import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
public class FieldDefinition {
|
||||
public static void writeTo(baksmaliOptions options, IndentingWriter writer, Field field,
|
||||
public static void writeTo(BaksmaliOptions options, IndentingWriter writer, Field field,
|
||||
boolean setInStaticConstructor) throws IOException {
|
||||
EncodedValue initialValue = field.getInitialValue();
|
||||
int accessFlags = field.getAccessFlags();
|
||||
@ -68,7 +68,7 @@ public class FieldDefinition {
|
||||
writer.write(" = ");
|
||||
|
||||
String containingClass = null;
|
||||
if (options.useImplicitReferences) {
|
||||
if (options.implicitReferences) {
|
||||
containingClass = field.getDefiningClass();
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ public class FieldDefinition {
|
||||
writer.indent(4);
|
||||
|
||||
String containingClass = null;
|
||||
if (options.useImplicitReferences) {
|
||||
if (options.implicitReferences) {
|
||||
containingClass = field.getDefiningClass();
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ import org.jf.baksmali.Adaptors.MethodDefinition;
|
||||
import org.jf.baksmali.Adaptors.MethodDefinition.InvalidSwitchPayload;
|
||||
import org.jf.baksmali.Adaptors.MethodItem;
|
||||
import org.jf.baksmali.Renderers.LongRenderer;
|
||||
import org.jf.baksmali.baksmaliOptions;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.dexlib2.Opcode;
|
||||
import org.jf.dexlib2.ReferenceType;
|
||||
import org.jf.dexlib2.VerificationError;
|
||||
@ -67,7 +67,7 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
|
||||
}
|
||||
|
||||
private boolean isAllowedOdex(@Nonnull Opcode opcode) {
|
||||
baksmaliOptions options = methodDef.classDef.options;
|
||||
BaksmaliOptions options = methodDef.classDef.options;
|
||||
if (options.allowOdex) {
|
||||
return true;
|
||||
}
|
||||
@ -110,7 +110,7 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
|
||||
if (instruction instanceof ReferenceInstruction) {
|
||||
ReferenceInstruction referenceInstruction = (ReferenceInstruction)instruction;
|
||||
String classContext = null;
|
||||
if (methodDef.classDef.options.useImplicitReferences) {
|
||||
if (methodDef.classDef.options.implicitReferences) {
|
||||
classContext = methodDef.method.getDefiningClass();
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ package org.jf.baksmali.Adaptors.Format;
|
||||
|
||||
import org.jf.baksmali.Adaptors.LabelMethodItem;
|
||||
import org.jf.baksmali.Adaptors.MethodDefinition;
|
||||
import org.jf.baksmali.baksmaliOptions;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.dexlib2.Opcode;
|
||||
import org.jf.dexlib2.iface.instruction.OffsetInstruction;
|
||||
import org.jf.util.IndentingWriter;
|
||||
@ -41,7 +41,7 @@ import java.io.IOException;
|
||||
public class OffsetInstructionFormatMethodItem extends InstructionMethodItem<OffsetInstruction> {
|
||||
protected LabelMethodItem label;
|
||||
|
||||
public OffsetInstructionFormatMethodItem(@Nonnull baksmaliOptions options, @Nonnull MethodDefinition methodDef,
|
||||
public OffsetInstructionFormatMethodItem(@Nonnull BaksmaliOptions options, @Nonnull MethodDefinition methodDef,
|
||||
int codeAddress, OffsetInstruction instruction) {
|
||||
super(methodDef, codeAddress, instruction);
|
||||
|
||||
|
@ -28,18 +28,18 @@
|
||||
|
||||
package org.jf.baksmali.Adaptors;
|
||||
|
||||
import org.jf.baksmali.baksmaliOptions;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.util.IndentingWriter;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
|
||||
public class LabelMethodItem extends MethodItem {
|
||||
private final baksmaliOptions options;
|
||||
private final BaksmaliOptions options;
|
||||
private final String labelPrefix;
|
||||
private int labelSequence;
|
||||
|
||||
public LabelMethodItem(@Nonnull baksmaliOptions options, int codeAddress, @Nonnull String labelPrefix) {
|
||||
public LabelMethodItem(@Nonnull BaksmaliOptions options, int codeAddress, @Nonnull String labelPrefix) {
|
||||
super(codeAddress);
|
||||
this.options = options;
|
||||
this.labelPrefix = labelPrefix;
|
||||
@ -76,7 +76,7 @@ public class LabelMethodItem extends MethodItem {
|
||||
public boolean writeTo(IndentingWriter writer) throws IOException {
|
||||
writer.write(':');
|
||||
writer.write(labelPrefix);
|
||||
if (options.useSequentialLabels) {
|
||||
if (options.sequentialLabels) {
|
||||
writer.printUnsignedLongAsHex(labelSequence);
|
||||
} else {
|
||||
writer.printUnsignedLongAsHex(this.getLabelAddress());
|
||||
|
@ -32,7 +32,7 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.jf.baksmali.Adaptors.Debug.DebugMethodItem;
|
||||
import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory;
|
||||
import org.jf.baksmali.baksmaliOptions;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.dexlib2.AccessFlags;
|
||||
import org.jf.dexlib2.Format;
|
||||
import org.jf.dexlib2.Opcode;
|
||||
@ -163,7 +163,7 @@ public class MethodDefinition {
|
||||
}
|
||||
|
||||
public static void writeEmptyMethodTo(IndentingWriter writer, Method method,
|
||||
baksmaliOptions options) throws IOException {
|
||||
BaksmaliOptions options) throws IOException {
|
||||
writer.write(".method ");
|
||||
writeAccessFlags(writer, method.getAccessFlags());
|
||||
writer.write(method.getName());
|
||||
@ -180,7 +180,7 @@ public class MethodDefinition {
|
||||
writeParameters(writer, method, methodParameters, options);
|
||||
|
||||
String containingClass = null;
|
||||
if (options.useImplicitReferences) {
|
||||
if (options.implicitReferences) {
|
||||
containingClass = method.getDefiningClass();
|
||||
}
|
||||
AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass);
|
||||
@ -212,7 +212,7 @@ public class MethodDefinition {
|
||||
writer.write('\n');
|
||||
|
||||
writer.indent(4);
|
||||
if (classDef.options.useLocalsDirective) {
|
||||
if (classDef.options.localsDirective) {
|
||||
writer.write(".locals ");
|
||||
writer.printSignedIntAsDec(methodImpl.getRegisterCount() - parameterRegisterCount);
|
||||
} else {
|
||||
@ -228,7 +228,7 @@ public class MethodDefinition {
|
||||
}
|
||||
|
||||
String containingClass = null;
|
||||
if (classDef.options.useImplicitReferences) {
|
||||
if (classDef.options.implicitReferences) {
|
||||
containingClass = method.getDefiningClass();
|
||||
}
|
||||
AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass);
|
||||
@ -313,18 +313,18 @@ public class MethodDefinition {
|
||||
|
||||
private static void writeParameters(IndentingWriter writer, Method method,
|
||||
List<? extends MethodParameter> parameters,
|
||||
baksmaliOptions options) throws IOException {
|
||||
BaksmaliOptions options) throws IOException {
|
||||
boolean isStatic = AccessFlags.STATIC.isSet(method.getAccessFlags());
|
||||
int registerNumber = isStatic?0:1;
|
||||
for (MethodParameter parameter: parameters) {
|
||||
String parameterType = parameter.getType();
|
||||
String parameterName = parameter.getName();
|
||||
Collection<? extends Annotation> annotations = parameter.getAnnotations();
|
||||
if ((options.outputDebugInfo && parameterName != null) || annotations.size() != 0) {
|
||||
if ((options.debugInfo && parameterName != null) || annotations.size() != 0) {
|
||||
writer.write(".param p");
|
||||
writer.printSignedIntAsDec(registerNumber);
|
||||
|
||||
if (parameterName != null && options.outputDebugInfo) {
|
||||
if (parameterName != null && options.debugInfo) {
|
||||
writer.write(", ");
|
||||
ReferenceFormatter.writeStringReference(writer, parameterName);
|
||||
}
|
||||
@ -335,7 +335,7 @@ public class MethodDefinition {
|
||||
writer.indent(4);
|
||||
|
||||
String containingClass = null;
|
||||
if (options.useImplicitReferences) {
|
||||
if (options.implicitReferences) {
|
||||
containingClass = method.getDefiningClass();
|
||||
}
|
||||
AnnotationFormatter.writeTo(writer, annotations, containingClass);
|
||||
@ -374,11 +374,11 @@ public class MethodDefinition {
|
||||
}
|
||||
|
||||
addTries(methodItems);
|
||||
if (classDef.options.outputDebugInfo) {
|
||||
if (classDef.options.debugInfo) {
|
||||
addDebugInfo(methodItems);
|
||||
}
|
||||
|
||||
if (classDef.options.useSequentialLabels) {
|
||||
if (classDef.options.sequentialLabels) {
|
||||
setLabelSequentialNumbers();
|
||||
}
|
||||
|
||||
@ -415,7 +415,7 @@ public class MethodDefinition {
|
||||
methodItems.add(new BlankMethodItem(currentCodeAddress));
|
||||
}
|
||||
|
||||
if (classDef.options.addCodeOffsets) {
|
||||
if (classDef.options.codeOffsets) {
|
||||
methodItems.add(new MethodItem(currentCodeAddress) {
|
||||
|
||||
@Override
|
||||
@ -432,7 +432,7 @@ public class MethodDefinition {
|
||||
});
|
||||
}
|
||||
|
||||
if (!classDef.options.noAccessorComments && (instruction instanceof ReferenceInstruction)) {
|
||||
if (classDef.options.accessorComments && (instruction instanceof ReferenceInstruction)) {
|
||||
Opcode opcode = instruction.getOpcode();
|
||||
|
||||
if (opcode.referenceType == ReferenceType.METHOD) {
|
||||
@ -493,7 +493,7 @@ public class MethodDefinition {
|
||||
methodItems.add(new BlankMethodItem(currentCodeAddress));
|
||||
}
|
||||
|
||||
if (classDef.options.addCodeOffsets) {
|
||||
if (classDef.options.codeOffsets) {
|
||||
methodItems.add(new MethodItem(currentCodeAddress) {
|
||||
|
||||
@Override
|
||||
@ -597,7 +597,7 @@ public class MethodDefinition {
|
||||
|
||||
@Nullable
|
||||
private String getContainingClassForImplicitReference() {
|
||||
if (classDef.options.useImplicitReferences) {
|
||||
if (classDef.options.implicitReferences) {
|
||||
return classDef.classDef.getType();
|
||||
}
|
||||
return null;
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
package org.jf.baksmali.Adaptors;
|
||||
|
||||
import org.jf.baksmali.baksmaliOptions;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.dexlib2.analysis.AnalyzedInstruction;
|
||||
import org.jf.dexlib2.analysis.RegisterType;
|
||||
import org.jf.util.IndentingWriter;
|
||||
@ -60,12 +60,12 @@ public class PostInstructionRegisterInfoMethodItem extends MethodItem {
|
||||
int registerCount = analyzedInstruction.getRegisterCount();
|
||||
BitSet registers = new BitSet(registerCount);
|
||||
|
||||
if ((registerInfo & baksmaliOptions.ALL) != 0) {
|
||||
if ((registerInfo & BaksmaliOptions.ALL) != 0) {
|
||||
registers.set(0, registerCount);
|
||||
} else {
|
||||
if ((registerInfo & baksmaliOptions.ALLPOST) != 0) {
|
||||
if ((registerInfo & BaksmaliOptions.ALLPOST) != 0) {
|
||||
registers.set(0, registerCount);
|
||||
} else if ((registerInfo & baksmaliOptions.DEST) != 0) {
|
||||
} else if ((registerInfo & BaksmaliOptions.DEST) != 0) {
|
||||
addDestRegs(registers, registerCount);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
package org.jf.baksmali.Adaptors;
|
||||
|
||||
import org.jf.baksmali.baksmaliOptions;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.dexlib2.analysis.AnalyzedInstruction;
|
||||
import org.jf.dexlib2.analysis.MethodAnalyzer;
|
||||
import org.jf.dexlib2.analysis.RegisterType;
|
||||
@ -68,29 +68,29 @@ public class PreInstructionRegisterInfoMethodItem extends MethodItem {
|
||||
BitSet registers = new BitSet(registerCount);
|
||||
BitSet mergeRegisters = null;
|
||||
|
||||
if ((registerInfo & baksmaliOptions.ALL) != 0) {
|
||||
if ((registerInfo & BaksmaliOptions.ALL) != 0) {
|
||||
registers.set(0, registerCount);
|
||||
} else {
|
||||
if ((registerInfo & baksmaliOptions.ALLPRE) != 0) {
|
||||
if ((registerInfo & BaksmaliOptions.ALLPRE) != 0) {
|
||||
registers.set(0, registerCount);
|
||||
} else {
|
||||
if ((registerInfo & baksmaliOptions.ARGS) != 0) {
|
||||
if ((registerInfo & BaksmaliOptions.ARGS) != 0) {
|
||||
addArgsRegs(registers);
|
||||
}
|
||||
if ((registerInfo & baksmaliOptions.MERGE) != 0) {
|
||||
if ((registerInfo & BaksmaliOptions.MERGE) != 0) {
|
||||
if (analyzedInstruction.isBeginningInstruction()) {
|
||||
addParamRegs(registers, registerCount);
|
||||
}
|
||||
mergeRegisters = new BitSet(registerCount);
|
||||
addMergeRegs(mergeRegisters, registerCount);
|
||||
} else if ((registerInfo & baksmaliOptions.FULLMERGE) != 0 &&
|
||||
} else if ((registerInfo & BaksmaliOptions.FULLMERGE) != 0 &&
|
||||
(analyzedInstruction.isBeginningInstruction())) {
|
||||
addParamRegs(registers, registerCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((registerInfo & baksmaliOptions.FULLMERGE) != 0) {
|
||||
if ((registerInfo & BaksmaliOptions.FULLMERGE) != 0) {
|
||||
if (mergeRegisters == null) {
|
||||
mergeRegisters = new BitSet(registerCount);
|
||||
addMergeRegs(mergeRegisters, registerCount);
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
package org.jf.baksmali.Adaptors;
|
||||
|
||||
import org.jf.baksmali.baksmaliOptions;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.util.IndentingWriter;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@ -38,11 +38,11 @@ import java.io.IOException;
|
||||
* This class contains the logic used for formatting registers
|
||||
*/
|
||||
public class RegisterFormatter {
|
||||
@Nonnull public final baksmaliOptions options;
|
||||
@Nonnull public final BaksmaliOptions options;
|
||||
public final int registerCount;
|
||||
public final int parameterRegisterCount;
|
||||
|
||||
public RegisterFormatter(@Nonnull baksmaliOptions options, int registerCount, int parameterRegisterCount) {
|
||||
public RegisterFormatter(@Nonnull BaksmaliOptions options, int registerCount, int parameterRegisterCount) {
|
||||
this.options = options;
|
||||
this.registerCount = registerCount;
|
||||
this.parameterRegisterCount = parameterRegisterCount;
|
||||
@ -58,7 +58,7 @@ public class RegisterFormatter {
|
||||
* @param lastRegister the last register in the range
|
||||
*/
|
||||
public void writeRegisterRange(IndentingWriter writer, int startRegister, int lastRegister) throws IOException {
|
||||
if (!options.noParameterRegisters) {
|
||||
if (options.parameterRegisters) {
|
||||
assert startRegister <= lastRegister;
|
||||
|
||||
if (startRegister >= registerCount - parameterRegisterCount) {
|
||||
@ -86,7 +86,7 @@ public class RegisterFormatter {
|
||||
* @param register the register number
|
||||
*/
|
||||
public void writeTo(IndentingWriter writer, int register) throws IOException {
|
||||
if (!options.noParameterRegisters) {
|
||||
if (options.parameterRegisters) {
|
||||
if (register >= registerCount - parameterRegisterCount) {
|
||||
writer.write('p');
|
||||
writer.printSignedIntAsDec((register - (registerCount - parameterRegisterCount)));
|
||||
|
137
baksmali/src/main/java/org/jf/baksmali/AnalysisArguments.java
Normal file
137
baksmali/src/main/java/org/jf/baksmali/AnalysisArguments.java
Normal 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);
|
||||
}
|
||||
}
|
@ -28,105 +28,20 @@
|
||||
|
||||
package org.jf.baksmali;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Ordering;
|
||||
import org.jf.baksmali.Adaptors.ClassDefinition;
|
||||
import org.jf.dexlib2.analysis.ClassPath;
|
||||
import org.jf.dexlib2.analysis.CustomInlineMethodResolver;
|
||||
import org.jf.dexlib2.iface.ClassDef;
|
||||
import org.jf.dexlib2.iface.DexFile;
|
||||
import org.jf.dexlib2.util.SyntheticAccessorResolver;
|
||||
import org.jf.util.ClassFileNameHandler;
|
||||
import org.jf.util.IndentingWriter;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class baksmali {
|
||||
|
||||
public static boolean disassembleDexFile(DexFile dexFile, final baksmaliOptions options) {
|
||||
if (options.registerInfo != 0 || options.deodex || options.normalizeVirtualMethods) {
|
||||
try {
|
||||
Iterable<String> extraClassPathEntries;
|
||||
if (options.extraClassPathEntries != null) {
|
||||
extraClassPathEntries = options.extraClassPathEntries;
|
||||
} else {
|
||||
extraClassPathEntries = ImmutableList.of();
|
||||
}
|
||||
|
||||
options.classPath = ClassPath.fromClassPath(options.bootClassPathDirs,
|
||||
Iterables.concat(options.bootClassPathEntries, extraClassPathEntries), dexFile,
|
||||
options.apiLevel, options.checkPackagePrivateAccess, options.experimental);
|
||||
|
||||
if (options.customInlineDefinitions != null) {
|
||||
options.inlineResolver = new CustomInlineMethodResolver(options.classPath,
|
||||
options.customInlineDefinitions);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
System.err.println("\n\nError occurred while loading boot class path files. Aborting.");
|
||||
ex.printStackTrace(System.err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.resourceIdFileEntries != null) {
|
||||
class PublicHandler extends DefaultHandler {
|
||||
String prefix = null;
|
||||
public PublicHandler(String prefix) {
|
||||
super();
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public void startElement(String uri, String localName,
|
||||
String qName, Attributes attr) throws SAXException {
|
||||
if (qName.equals("public")) {
|
||||
String type = attr.getValue("type");
|
||||
String name = attr.getValue("name").replace('.', '_');
|
||||
Integer public_key = Integer.decode(attr.getValue("id"));
|
||||
String public_val = new StringBuffer()
|
||||
.append(prefix)
|
||||
.append(".")
|
||||
.append(type)
|
||||
.append(".")
|
||||
.append(name)
|
||||
.toString();
|
||||
options.resourceIds.put(public_key, public_val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (Entry<String,String> entry: options.resourceIdFileEntries.entrySet()) {
|
||||
try {
|
||||
SAXParser saxp = SAXParserFactory.newInstance().newSAXParser();
|
||||
String prefix = entry.getValue();
|
||||
saxp.parse(entry.getKey(), new PublicHandler(prefix));
|
||||
} catch (ParserConfigurationException e) {
|
||||
continue;
|
||||
} catch (SAXException e) {
|
||||
continue;
|
||||
} catch (IOException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File outputDirectoryFile = new File(options.outputDirectory);
|
||||
if (!outputDirectoryFile.exists()) {
|
||||
if (!outputDirectoryFile.mkdirs()) {
|
||||
System.err.println("Can't create the output directory " + options.outputDirectory);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public class Baksmali {
|
||||
public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options) {
|
||||
|
||||
//sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file
|
||||
//name collisions, then we'll use the same name for each class, if the dex file goes through multiple
|
||||
@ -134,13 +49,9 @@ public class baksmali {
|
||||
//may still change of course
|
||||
List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());
|
||||
|
||||
if (!options.noAccessorComments) {
|
||||
options.syntheticAccessorResolver = new SyntheticAccessorResolver(dexFile.getOpcodes(), classDefs);
|
||||
}
|
||||
final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDir, ".smali");
|
||||
|
||||
final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali");
|
||||
|
||||
ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(jobs);
|
||||
List<Future<Boolean>> tasks = Lists.newArrayList();
|
||||
|
||||
for (final ClassDef classDef: classDefs) {
|
||||
@ -174,7 +85,7 @@ public class baksmali {
|
||||
}
|
||||
|
||||
private static boolean disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler,
|
||||
baksmaliOptions options) {
|
||||
BaksmaliOptions options) {
|
||||
/**
|
||||
* The path for the disassembly file is based on the package name
|
||||
* The class descriptor will look something like:
|
@ -31,19 +31,35 @@
|
||||
|
||||
package org.jf.baksmali;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.jf.dexlib2.analysis.ClassPath;
|
||||
import org.jf.dexlib2.analysis.InlineMethodResolver;
|
||||
import org.jf.dexlib2.util.SyntheticAccessorResolver;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class baksmaliOptions {
|
||||
public class BaksmaliOptions {
|
||||
public int apiLevel = 15;
|
||||
|
||||
public boolean parameterRegisters = true;
|
||||
public boolean localsDirective = false;
|
||||
public boolean sequentialLabels = false;
|
||||
public boolean debugInfo = true;
|
||||
public boolean codeOffsets = false;
|
||||
public boolean accessorComments = true;
|
||||
public boolean allowOdex = false;
|
||||
public boolean deodex = false;
|
||||
public boolean implicitReferences = false;
|
||||
public boolean normalizeVirtualMethods = false;
|
||||
|
||||
// register info values
|
||||
public static final int ALL = 1;
|
||||
public static final int ALLPRE = 2;
|
||||
@ -53,56 +69,40 @@ public class baksmaliOptions {
|
||||
public static final int MERGE = 32;
|
||||
public static final int FULLMERGE = 64;
|
||||
|
||||
public int apiLevel = 15;
|
||||
public String outputDirectory = "out";
|
||||
@Nullable public String dexEntry = null;
|
||||
public List<String> bootClassPathDirs = Lists.newArrayList();
|
||||
|
||||
public List<String> bootClassPathEntries = Lists.newArrayList();
|
||||
public List<String> extraClassPathEntries = Lists.newArrayList();
|
||||
|
||||
public Map<String,String> resourceIdFileEntries = new HashMap<String,String>();
|
||||
public Map<Integer,String> resourceIds = new HashMap<Integer,String>();
|
||||
|
||||
public boolean noParameterRegisters = false;
|
||||
public boolean useLocalsDirective = false;
|
||||
public boolean useSequentialLabels = false;
|
||||
public boolean outputDebugInfo = true;
|
||||
public boolean addCodeOffsets = false;
|
||||
public boolean noAccessorComments = false;
|
||||
public boolean allowOdex = false;
|
||||
public boolean deodex = false;
|
||||
public boolean experimental = false;
|
||||
public boolean ignoreErrors = false;
|
||||
public boolean checkPackagePrivateAccess = false;
|
||||
public boolean useImplicitReferences = false;
|
||||
public boolean normalizeVirtualMethods = false;
|
||||
public File customInlineDefinitions = null;
|
||||
public InlineMethodResolver inlineResolver = null;
|
||||
public int registerInfo = 0;
|
||||
public ClassPath classPath = null;
|
||||
public int jobs = Runtime.getRuntime().availableProcessors();
|
||||
public boolean disassemble = true;
|
||||
public boolean dump = false;
|
||||
public String dumpFileName = null;
|
||||
|
||||
public Map<Integer,String> resourceIds = new HashMap<Integer,String>();
|
||||
public InlineMethodResolver inlineResolver = null;
|
||||
public ClassPath classPath = null;
|
||||
public SyntheticAccessorResolver syntheticAccessorResolver = null;
|
||||
|
||||
public void setBootClassPath(String bootClassPath) {
|
||||
bootClassPathEntries = Lists.newArrayList(bootClassPath.split(":"));
|
||||
}
|
||||
|
||||
public void addExtraClassPath(String extraClassPath) {
|
||||
if (extraClassPath.startsWith(":")) {
|
||||
extraClassPath = extraClassPath.substring(1);
|
||||
}
|
||||
extraClassPathEntries.addAll(Arrays.asList(extraClassPath.split(":")));
|
||||
}
|
||||
|
||||
public void setResourceIdFiles(String resourceIdFiles) {
|
||||
for (String resourceIdFile: resourceIdFiles.split(":")) {
|
||||
String[] entry = resourceIdFile.split("=");
|
||||
resourceIdFileEntries.put(entry[1], entry[0]);
|
||||
/**
|
||||
* Load the resource ids from a set of public.xml files.
|
||||
*
|
||||
* @param resourceFiles A map of resource prefixes -> public.xml files
|
||||
*/
|
||||
public void loadResourceIds(Map<String, File> resourceFiles) throws SAXException, IOException {
|
||||
for (Map.Entry<String, File> entry: resourceFiles.entrySet()) {
|
||||
try {
|
||||
SAXParser saxp = SAXParserFactory.newInstance().newSAXParser();
|
||||
final String prefix = entry.getKey();
|
||||
saxp.parse(entry.getValue(), new DefaultHandler() {
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName,
|
||||
Attributes attr) throws SAXException {
|
||||
if (qName.equals("public")) {
|
||||
String resourceType = attr.getValue("type");
|
||||
String resourceName = attr.getValue("name").replace('.', '_');
|
||||
Integer resourceId = Integer.decode(attr.getValue("id"));
|
||||
String qualifiedResourceName =
|
||||
String.format("%s.%s.%s", prefix, resourceType, resourceName);
|
||||
resourceIds.put(resourceId, qualifiedResourceName);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (ParserConfigurationException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
109
baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java
Normal file
109
baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java
Normal 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;
|
||||
}
|
||||
}
|
146
baksmali/src/main/java/org/jf/baksmali/DexInputCommand.java
Normal file
146
baksmali/src/main/java/org/jf/baksmali/DexInputCommand.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
283
baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java
Normal file
283
baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java
Normal 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;
|
||||
}
|
||||
}
|
114
baksmali/src/main/java/org/jf/baksmali/DumpCommand.java
Normal file
114
baksmali/src/main/java/org/jf/baksmali/DumpCommand.java
Normal 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);
|
||||
}
|
||||
}
|
204
baksmali/src/main/java/org/jf/baksmali/HelpCommand.java
Normal file
204
baksmali/src/main/java/org/jf/baksmali/HelpCommand.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
85
baksmali/src/main/java/org/jf/baksmali/ListCommand.java
Normal file
85
baksmali/src/main/java/org/jf/baksmali/ListCommand.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
102
baksmali/src/main/java/org/jf/baksmali/ListDexCommand.java
Normal file
102
baksmali/src/main/java/org/jf/baksmali/ListDexCommand.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
92
baksmali/src/main/java/org/jf/baksmali/ListHelpCommand.java
Normal file
92
baksmali/src/main/java/org/jf/baksmali/ListHelpCommand.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
50
baksmali/src/main/java/org/jf/baksmali/ListTypesCommand.java
Normal file
50
baksmali/src/main/java/org/jf/baksmali/ListTypesCommand.java
Normal 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);
|
||||
}
|
||||
}
|
158
baksmali/src/main/java/org/jf/baksmali/ListVtablesCommand.java
Normal file
158
baksmali/src/main/java/org/jf/baksmali/ListVtablesCommand.java
Normal 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;
|
||||
}
|
||||
}
|
126
baksmali/src/main/java/org/jf/baksmali/Main.java
Normal file
126
baksmali/src/main/java/org/jf/baksmali/Main.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -36,6 +36,7 @@ import com.google.common.io.Resources;
|
||||
import junit.framework.Assert;
|
||||
import org.jf.baksmali.Adaptors.ClassDefinition;
|
||||
import org.jf.dexlib2.DexFileFactory;
|
||||
import org.jf.dexlib2.Opcodes;
|
||||
import org.jf.dexlib2.analysis.ClassPath;
|
||||
import org.jf.dexlib2.iface.ClassDef;
|
||||
import org.jf.dexlib2.iface.DexFile;
|
||||
@ -85,14 +86,14 @@ public class AnalysisTest {
|
||||
public void runTest(String test, boolean registerInfo) throws IOException, URISyntaxException {
|
||||
String dexFilePath = String.format("%s%sclasses.dex", test, File.separatorChar);
|
||||
|
||||
DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), 15, false);
|
||||
DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), Opcodes.getDefault());
|
||||
|
||||
baksmaliOptions options = new baksmaliOptions();
|
||||
BaksmaliOptions options = new BaksmaliOptions();
|
||||
if (registerInfo) {
|
||||
options.registerInfo = baksmaliOptions.ALL | baksmaliOptions.FULLMERGE;
|
||||
options.registerInfo = BaksmaliOptions.ALL | BaksmaliOptions.FULLMERGE;
|
||||
options.classPath = new ClassPath();
|
||||
}
|
||||
options.useImplicitReferences = false;
|
||||
options.implicitReferences = false;
|
||||
|
||||
for (ClassDef classDef: dexFile.getClasses()) {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
|
@ -48,10 +48,9 @@ import java.io.StringWriter;
|
||||
|
||||
public class BaksmaliTestUtils {
|
||||
public static void assertSmaliCompiledEquals(String source, String expected,
|
||||
baksmaliOptions options, boolean stripComments) throws IOException,
|
||||
BaksmaliOptions options, boolean stripComments) throws IOException,
|
||||
RecognitionException {
|
||||
ClassDef classDef = SmaliTestUtils.compileSmali(source, options.apiLevel,
|
||||
options.experimental);
|
||||
ClassDef classDef = SmaliTestUtils.compileSmali(source, options.apiLevel);
|
||||
|
||||
// Remove unnecessary whitespace and optionally strip all comments from smali file
|
||||
String normalizedActual = getNormalizedSmali(classDef, options, stripComments);
|
||||
@ -62,13 +61,13 @@ public class BaksmaliTestUtils {
|
||||
}
|
||||
|
||||
public static void assertSmaliCompiledEquals(String source, String expected,
|
||||
baksmaliOptions options) throws IOException, RecognitionException {
|
||||
BaksmaliOptions options) throws IOException, RecognitionException {
|
||||
assertSmaliCompiledEquals(source, expected, options, false);
|
||||
}
|
||||
|
||||
public static void assertSmaliCompiledEquals(String source, String expected)
|
||||
throws IOException, RecognitionException {
|
||||
baksmaliOptions options = new baksmaliOptions();
|
||||
BaksmaliOptions options = new BaksmaliOptions();
|
||||
assertSmaliCompiledEquals(source, expected, options);
|
||||
}
|
||||
|
||||
@ -81,7 +80,7 @@ public class BaksmaliTestUtils {
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static String getNormalizedSmali(@Nonnull ClassDef classDef, @Nonnull baksmaliOptions options,
|
||||
public static String getNormalizedSmali(@Nonnull ClassDef classDef, @Nonnull BaksmaliOptions options,
|
||||
boolean stripComments)
|
||||
throws IOException {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
|
@ -65,7 +65,7 @@ public abstract class DexTest {
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
protected DexBackedDexFile getInputDexFile(@Nonnull String testName, @Nonnull baksmaliOptions options) {
|
||||
protected DexBackedDexFile getInputDexFile(@Nonnull String testName, @Nonnull BaksmaliOptions options) {
|
||||
try {
|
||||
// Load file from resources as a stream
|
||||
byte[] inputBytes = BaksmaliTestUtils.readResourceBytesFully(getInputFilename(testName));
|
||||
|
@ -57,10 +57,10 @@ public class DisassemblyTest extends DexTest {
|
||||
}
|
||||
|
||||
protected void runTest(@Nonnull String testName) {
|
||||
runTest(testName, new baksmaliOptions());
|
||||
runTest(testName, new BaksmaliOptions());
|
||||
}
|
||||
|
||||
protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) {
|
||||
protected void runTest(@Nonnull String testName, @Nonnull BaksmaliOptions options) {
|
||||
try {
|
||||
DexBackedDexFile inputDex = getInputDexFile(testName, options);
|
||||
Assert.assertEquals(1, inputDex.getClassCount());
|
||||
|
@ -42,7 +42,7 @@ import org.junit.Test;
|
||||
public class FieldGapOrderTest extends DexTest {
|
||||
@Test
|
||||
public void testOldOrder() {
|
||||
DexFile dexFile = getInputDexFile("FieldGapOrder", new baksmaliOptions());
|
||||
DexFile dexFile = getInputDexFile("FieldGapOrder", new BaksmaliOptions());
|
||||
Assert.assertEquals(3, dexFile.getClasses().size());
|
||||
|
||||
ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), false, 66);
|
||||
@ -56,7 +56,7 @@ public class FieldGapOrderTest extends DexTest {
|
||||
|
||||
@Test
|
||||
public void testNewOrder() {
|
||||
DexFile dexFile = getInputDexFile("FieldGapOrder", new baksmaliOptions());
|
||||
DexFile dexFile = getInputDexFile("FieldGapOrder", new BaksmaliOptions());
|
||||
Assert.assertEquals(3, dexFile.getClasses().size());
|
||||
|
||||
ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), false, 67);
|
||||
|
@ -62,8 +62,8 @@ public class ImplicitReferenceTest {
|
||||
"return-void\n" +
|
||||
".end method\n";
|
||||
|
||||
baksmaliOptions options = new baksmaliOptions();
|
||||
options.useImplicitReferences = true;
|
||||
BaksmaliOptions options = new BaksmaliOptions();
|
||||
options.implicitReferences = true;
|
||||
|
||||
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
|
||||
}
|
||||
@ -93,8 +93,8 @@ public class ImplicitReferenceTest {
|
||||
" return-void\n" +
|
||||
".end method\n";
|
||||
|
||||
baksmaliOptions options = new baksmaliOptions();
|
||||
options.useImplicitReferences = false;
|
||||
BaksmaliOptions options = new BaksmaliOptions();
|
||||
options.implicitReferences = false;
|
||||
|
||||
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
|
||||
}
|
||||
@ -118,8 +118,8 @@ public class ImplicitReferenceTest {
|
||||
".field public static field3:Ljava/lang/reflect/Method; = I()V\n" +
|
||||
".field public static field4:Ljava/lang/Class; = I\n";
|
||||
|
||||
baksmaliOptions options = new baksmaliOptions();
|
||||
options.useImplicitReferences = true;
|
||||
BaksmaliOptions options = new BaksmaliOptions();
|
||||
options.implicitReferences = true;
|
||||
|
||||
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
|
||||
}
|
||||
@ -143,8 +143,8 @@ public class ImplicitReferenceTest {
|
||||
".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" +
|
||||
".field public static field4:Ljava/lang/Class; = I\n";
|
||||
|
||||
baksmaliOptions options = new baksmaliOptions();
|
||||
options.useImplicitReferences = false;
|
||||
BaksmaliOptions options = new BaksmaliOptions();
|
||||
options.implicitReferences = false;
|
||||
|
||||
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
|
||||
}
|
||||
@ -174,8 +174,8 @@ public class ImplicitReferenceTest {
|
||||
" return-void\n" +
|
||||
".end method\n";
|
||||
|
||||
baksmaliOptions options = new baksmaliOptions();
|
||||
options.useImplicitReferences = true;
|
||||
BaksmaliOptions options = new BaksmaliOptions();
|
||||
options.implicitReferences = true;
|
||||
|
||||
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
|
||||
}
|
||||
@ -205,8 +205,8 @@ public class ImplicitReferenceTest {
|
||||
" return-void\n" +
|
||||
".end method\n";
|
||||
|
||||
baksmaliOptions options = new baksmaliOptions();
|
||||
options.useImplicitReferences = false;
|
||||
BaksmaliOptions options = new BaksmaliOptions();
|
||||
options.implicitReferences = false;
|
||||
|
||||
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
|
||||
}
|
||||
@ -228,8 +228,8 @@ public class ImplicitReferenceTest {
|
||||
".field public static field2:Ljava/lang/reflect/Field; = V:I\n" +
|
||||
".field public static field3:Ljava/lang/reflect/Field; = I:I\n";
|
||||
|
||||
baksmaliOptions options = new baksmaliOptions();
|
||||
options.useImplicitReferences = true;
|
||||
BaksmaliOptions options = new BaksmaliOptions();
|
||||
options.implicitReferences = true;
|
||||
|
||||
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
|
||||
}
|
||||
@ -251,8 +251,8 @@ public class ImplicitReferenceTest {
|
||||
".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" +
|
||||
".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I\n";
|
||||
|
||||
baksmaliOptions options = new baksmaliOptions();
|
||||
options.useImplicitReferences = false;
|
||||
BaksmaliOptions options = new BaksmaliOptions();
|
||||
options.implicitReferences = false;
|
||||
|
||||
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
|
||||
}
|
||||
|
@ -36,6 +36,6 @@ import org.junit.Test;
|
||||
public class InterfaceOrderTest extends IdenticalRoundtripTest {
|
||||
@Test
|
||||
public void testInterfaceOrder() {
|
||||
runTest("InterfaceOrder", new baksmaliOptions());
|
||||
runTest("InterfaceOrder", new BaksmaliOptions());
|
||||
}
|
||||
}
|
||||
|
@ -69,10 +69,10 @@ public abstract class RoundtripTest {
|
||||
}
|
||||
|
||||
protected void runTest(@Nonnull String testName) {
|
||||
runTest(testName, new baksmaliOptions());
|
||||
runTest(testName, new BaksmaliOptions());
|
||||
}
|
||||
|
||||
protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) {
|
||||
protected void runTest(@Nonnull String testName, @Nonnull BaksmaliOptions options) {
|
||||
try {
|
||||
// Load file from resources as a stream
|
||||
String inputFilename = getInputFilename(testName);
|
||||
|
@ -101,14 +101,15 @@ subprojects {
|
||||
guava: 'com.google.guava:guava:18.0',
|
||||
findbugs: 'com.google.code.findbugs:jsr305:1.3.9',
|
||||
junit: 'junit:junit:4.6',
|
||||
mockito: 'org.mockito:mockito-core:1.+',
|
||||
antlr_runtime: 'org.antlr:antlr-runtime:3.5.2',
|
||||
antlr: 'org.antlr:antlr:3.5.2',
|
||||
stringtemplate: 'org.antlr:stringtemplate:3.2.1',
|
||||
commons_cli: 'commons-cli:commons-cli:1.2',
|
||||
jflex_plugin: 'org.xbib.gradle.plugin:gradle-plugin-jflex:1.1.0',
|
||||
proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1',
|
||||
dx: 'com.google.android.tools:dx:1.7',
|
||||
gson: 'com.google.code.gson:gson:2.3.1'
|
||||
gson: 'com.google.code.gson:gson:2.3.1',
|
||||
jcommander: 'com.beust:jcommander:1.48'
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ d7cbf8a6629942e7bd315ffae7e1c77b082f3e11 - 60
|
||||
- return-void-barrier -> return-void-no-barrier
|
||||
1412dfa4adcd511902e510fa0c948b168ab5840c - 61 (re-commit of f3251d12)
|
||||
9d6bf69ad3012a9d843268fdd5325b6719b6d5f2 - 62
|
||||
- classpath list was added
|
||||
0de1133ba600f299b3d67938f650720d9f859eb2 - 63
|
||||
07785bb98dc8bbe192970e0f4c2cafd338a8dc68 - 64
|
||||
fa2c054b28d4b540c1b3651401a7a091282a015f - 65
|
||||
@ -21,4 +22,27 @@ fab6788358dfb64e5c370611ddbbbffab0ed0553 - 67
|
||||
6e2d5747d00697a25251d25dd33b953e54709507 - 68 (revert of 54b62480)
|
||||
0747466fca310eedea5fc49e37d54f240a0b3c0f - 69 (re-commit of 54b62480)
|
||||
501fd635a557645ab05f893c56e1f358e21bab82 - 70
|
||||
99170c636dfae4908b102347cfe9f92bad1881cc - 71
|
||||
99170c636dfae4908b102347cfe9f92bad1881cc - 71
|
||||
3cfa4d05afa76e19ca99ec964b535a15c73683f0 - 72
|
||||
- default methods
|
||||
d9786b0e5be23ea0258405165098b4216579209c - 73
|
||||
- fast class lookup table
|
||||
a4f1220c1518074db18ca1044e9201492975750b - 74
|
||||
625a64aad13905d8a2454bf3cc0e874487b110d5 - 75
|
||||
- bootclasspath list was added
|
||||
- class offsets moved out to a separate table
|
||||
919f5536182890d2e03f59b961acf8f7c836ff61 - 74 (revert of 625a64aa)
|
||||
9bdf108885a27ba05fae8501725649574d7c491b - 75 (re-commit of 625a64aa)
|
||||
a62d2f04a6ecf804f8a78e722a6ca8ccb2dfa931 - 76
|
||||
845e5064580bd37ad5014f7aa0d078be7265464d - 75 (revert of a62d2f04)
|
||||
29d38e77c553c6cf71fc4dafe2d22b4e3f814872 - 76 (re-commit of 845e5064)
|
||||
d1537b569b6cd18297c5e02d13cdd588c4366c51 - 77
|
||||
61b28a17d9b6e8e998103646e98e4a9772e11927 - 78
|
||||
9d07e3d128ccfa0ef7670feadd424a825e447d1d - 79
|
||||
952e1e3710158982941fc70326e9fddc3021235d - 80
|
||||
013e3f33495dcc31dba19c9de128d23ed441d7d3 - 81
|
||||
87f3fcbd0db352157fc59148e94647ef21b73bce - 82
|
||||
02b75806a80f8b75c3d6ba2ff97c995117630f36 - 83
|
||||
4359e61927866c254bc2d701e3ea4c48de10b79c - 84
|
||||
d549c28cfbddba945cb88857bcca3dce1414fb29 - 85
|
||||
952dbb19cd094b8bfb01dbb33e0878db429e499a - 86
|
||||
|
@ -51,6 +51,7 @@ dependencies {
|
||||
compile depends.guava
|
||||
|
||||
testCompile depends.junit
|
||||
testCompile depends.mockito
|
||||
|
||||
accessorTestGenerator project('accessorTestGenerator')
|
||||
|
||||
|
@ -31,14 +31,19 @@
|
||||
|
||||
package org.jf.dexlib2;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
|
||||
import org.jf.dexlib2.dexbacked.OatFile;
|
||||
import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException;
|
||||
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
|
||||
import org.jf.dexlib2.dexbacked.ZipDexContainer;
|
||||
import org.jf.dexlib2.dexbacked.ZipDexContainer.NotAZipFileException;
|
||||
import org.jf.dexlib2.iface.DexFile;
|
||||
import org.jf.dexlib2.iface.MultiDexContainer;
|
||||
import org.jf.dexlib2.writer.pool.DexPool;
|
||||
import org.jf.util.ExceptionWithContext;
|
||||
|
||||
@ -46,80 +51,45 @@ import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public final class DexFileFactory {
|
||||
|
||||
@Nonnull
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull String path, int api) throws IOException {
|
||||
return loadDexFile(path, api, false);
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull String path, @Nonnull Opcodes opcodes) throws IOException {
|
||||
return loadDexFile(new File(path), opcodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a dex/apk/odex/oat file.
|
||||
*
|
||||
* For oat files with multiple dex files, the first will be opened. For zip/apk files, the "classes.dex" entry
|
||||
* will be opened.
|
||||
*
|
||||
* @param file The file to open
|
||||
* @param opcodes The set of opcodes to use
|
||||
* @return A DexBackedDexFile for the given file
|
||||
*
|
||||
* @throws UnsupportedOatVersionException If file refers to an unsupported oat file
|
||||
* @throws DexFileNotFoundException If file does not exist, if file is a zip file but does not have a "classes.dex"
|
||||
* entry, or if file is an oat file that has no dex entries.
|
||||
* @throws UnsupportedFileTypeException If file is not a valid dex/zip/odex/oat file, or if the "classes.dex" entry
|
||||
* in a zip file is not a valid dex file
|
||||
*/
|
||||
@Nonnull
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull String path, int api, boolean experimental)
|
||||
throws IOException {
|
||||
return loadDexFile(new File(path), "classes.dex", Opcodes.forApi(api, experimental));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api) throws IOException {
|
||||
return loadDexFile(dexFile, api, false);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api, boolean experimental)
|
||||
throws IOException {
|
||||
return loadDexFile(dexFile, null, Opcodes.forApi(api, experimental));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, @Nullable String dexEntry, int api,
|
||||
boolean experimental) throws IOException {
|
||||
return loadDexFile(dexFile, dexEntry, Opcodes.forApi(api, experimental));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, @Nullable String dexEntry,
|
||||
@Nonnull Opcodes opcodes) throws IOException {
|
||||
ZipFile zipFile = null;
|
||||
boolean isZipFile = false;
|
||||
try {
|
||||
zipFile = new ZipFile(dexFile);
|
||||
// if we get here, it's safe to assume we have a zip file
|
||||
isZipFile = true;
|
||||
|
||||
String zipEntryName = MoreObjects.firstNonNull(dexEntry, "classes.dex");
|
||||
ZipEntry zipEntry = zipFile.getEntry(zipEntryName);
|
||||
if (zipEntry == null) {
|
||||
throw new DexFileNotFound("zip file %s does not contain a %s file", dexFile.getName(), zipEntryName);
|
||||
}
|
||||
long fileLength = zipEntry.getSize();
|
||||
if (fileLength < 40) {
|
||||
throw new ExceptionWithContext("The %s file in %s is too small to be a valid dex file",
|
||||
zipEntryName, dexFile.getName());
|
||||
} else if (fileLength > Integer.MAX_VALUE) {
|
||||
throw new ExceptionWithContext("The %s file in %s is too large to read in",
|
||||
zipEntryName, dexFile.getName());
|
||||
}
|
||||
byte[] dexBytes = new byte[(int)fileLength];
|
||||
ByteStreams.readFully(zipFile.getInputStream(zipEntry), dexBytes);
|
||||
return new DexBackedDexFile(opcodes, dexBytes);
|
||||
} catch (IOException ex) {
|
||||
// don't continue on if we know it's a zip file
|
||||
if (isZipFile) {
|
||||
throw ex;
|
||||
}
|
||||
} finally {
|
||||
if (zipFile != null) {
|
||||
try {
|
||||
zipFile.close();
|
||||
} catch (IOException ex) {
|
||||
// just eat it
|
||||
}
|
||||
}
|
||||
public static DexBackedDexFile loadDexFile(@Nonnull File file, @Nonnull Opcodes opcodes) throws IOException {
|
||||
if (!file.exists()) {
|
||||
throw new DexFileNotFoundException("%s does not exist", file.getName());
|
||||
}
|
||||
|
||||
InputStream inputStream = new BufferedInputStream(new FileInputStream(dexFile));
|
||||
|
||||
try {
|
||||
ZipDexContainer container = new ZipDexContainer(file, opcodes);
|
||||
return new DexEntryFinder(file.getPath(), container).findEntry("classes.dex", true);
|
||||
} catch (NotAZipFileException ex) {
|
||||
// eat it and continue
|
||||
}
|
||||
|
||||
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
|
||||
try {
|
||||
try {
|
||||
return DexBackedDexFile.fromInputStream(opcodes, inputStream);
|
||||
@ -127,14 +97,15 @@ public final class DexFileFactory {
|
||||
// just eat it
|
||||
}
|
||||
|
||||
// Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails
|
||||
|
||||
try {
|
||||
return DexBackedOdexFile.fromInputStream(opcodes, inputStream);
|
||||
} catch (DexBackedOdexFile.NotAnOdexFile ex) {
|
||||
// just eat it
|
||||
}
|
||||
|
||||
// Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream
|
||||
// back to the same position, if they fails
|
||||
|
||||
OatFile oatFile = null;
|
||||
try {
|
||||
oatFile = OatFile.fromInputStream(inputStream);
|
||||
@ -150,71 +121,181 @@ public final class DexFileFactory {
|
||||
List<OatDexFile> oatDexFiles = oatFile.getDexFiles();
|
||||
|
||||
if (oatDexFiles.size() == 0) {
|
||||
throw new DexFileNotFound("Oat file %s contains no dex files", dexFile.getName());
|
||||
throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName());
|
||||
}
|
||||
|
||||
if (dexEntry == null) {
|
||||
if (oatDexFiles.size() > 1) {
|
||||
throw new MultipleDexFilesException(oatFile);
|
||||
}
|
||||
return oatDexFiles.get(0);
|
||||
} else {
|
||||
// first check for an exact match
|
||||
for (OatDexFile oatDexFile : oatFile.getDexFiles()) {
|
||||
if (oatDexFile.filename.equals(dexEntry)) {
|
||||
return oatDexFile;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dexEntry.contains("/")) {
|
||||
for (OatDexFile oatDexFile : oatFile.getDexFiles()) {
|
||||
File oatEntryFile = new File(oatDexFile.filename);
|
||||
if (oatEntryFile.getName().equals(dexEntry)) {
|
||||
return oatDexFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new DexFileNotFound("oat file %s does not contain a dex file named %s",
|
||||
dexFile.getName(), dexEntry);
|
||||
}
|
||||
return oatDexFiles.get(0);
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
|
||||
throw new ExceptionWithContext("%s is not an apk, dex, odex or oat file.", dexFile.getPath());
|
||||
throw new UnsupportedFileTypeException("%s is not an apk, dex, odex or oat file.", file.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a dex entry from a container format (zip/oat)
|
||||
*
|
||||
* This has two modes of operation, depending on the exactMatch parameter. When exactMatch is true, it will only
|
||||
* load an entry whose name exactly matches that provided by the dexEntry parameter.
|
||||
*
|
||||
* When exactMatch is false, then it will search for any entry that dexEntry is a path suffix of. "path suffix"
|
||||
* meaning all the path components in dexEntry must fully match the corresponding path components in the entry name,
|
||||
* but some path components at the beginning of entry name can be missing.
|
||||
*
|
||||
* For example, if an oat file contains a "/system/framework/framework.jar:classes2.dex" entry, then the following
|
||||
* will match (not an exhaustive list):
|
||||
*
|
||||
* "/system/framework/framework.jar:classes2.dex"
|
||||
* "system/framework/framework.jar:classes2.dex"
|
||||
* "framework/framework.jar:classes2.dex"
|
||||
* "framework.jar:classes2.dex"
|
||||
* "classes2.dex"
|
||||
*
|
||||
* Note that partial path components specifically don't match. So something like "work/framework.jar:classes2.dex"
|
||||
* would not match.
|
||||
*
|
||||
* If dexEntry contains an initial slash, it will be ignored for purposes of this suffix match -- but not when
|
||||
* performing an exact match.
|
||||
*
|
||||
* If multiple entries match the given dexEntry, a MultipleMatchingDexEntriesException will be thrown
|
||||
*
|
||||
* @param file The container file. This must be either a zip (apk) file or an oat file.
|
||||
* @param dexEntry The name of the entry to load. This can either be the exact entry name, if exactMatch is true,
|
||||
* or it can be a path suffix.
|
||||
* @param exactMatch If true, dexE
|
||||
* @param opcodes The set of opcodes to use
|
||||
* @return A DexBackedDexFile for the given entry
|
||||
*
|
||||
* @throws UnsupportedOatVersionException If file refers to an unsupported oat file
|
||||
* @throws DexFileNotFoundException If the file does not exist, or if no matching entry could be found
|
||||
* @throws UnsupportedFileTypeException If file is not a valid zip/oat file, or if the matching entry is not a
|
||||
* valid dex file
|
||||
* @throws MultipleMatchingDexEntriesException If multiple entries match the given dexEntry
|
||||
*/
|
||||
public static DexBackedDexFile loadDexEntry(@Nonnull File file, @Nonnull String dexEntry,
|
||||
boolean exactMatch, @Nonnull Opcodes opcodes) throws IOException {
|
||||
if (!file.exists()) {
|
||||
throw new DexFileNotFoundException("Container file %s does not exist", file.getName());
|
||||
}
|
||||
|
||||
try {
|
||||
ZipDexContainer container = new ZipDexContainer(file, opcodes);
|
||||
return new DexEntryFinder(file.getPath(), container).findEntry(dexEntry, exactMatch);
|
||||
} catch (NotAZipFileException ex) {
|
||||
// eat it and continue
|
||||
}
|
||||
|
||||
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
|
||||
try {
|
||||
OatFile oatFile = null;
|
||||
try {
|
||||
oatFile = OatFile.fromInputStream(inputStream);
|
||||
} catch (NotAnOatFileException ex) {
|
||||
// just eat it
|
||||
}
|
||||
|
||||
if (oatFile != null) {
|
||||
if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) {
|
||||
throw new UnsupportedOatVersionException(oatFile);
|
||||
}
|
||||
|
||||
List<OatDexFile> oatDexFiles = oatFile.getDexFiles();
|
||||
|
||||
if (oatDexFiles.size() == 0) {
|
||||
throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName());
|
||||
}
|
||||
|
||||
return new DexEntryFinder(file.getPath(), oatFile).findEntry(dexEntry, exactMatch);
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
|
||||
throw new UnsupportedFileTypeException("%s is not an apk or oat file.", file.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a file containing 1 or more dex files
|
||||
*
|
||||
* If the given file is a dex or odex file, it will return a MultiDexContainer containing that single entry.
|
||||
* Otherwise, for an oat or zip file, it will return an OatFile or ZipDexContainer respectively.
|
||||
*
|
||||
* @param file The file to open
|
||||
* @param opcodes The set of opcodes to use
|
||||
* @return A MultiDexContainer
|
||||
* @throws DexFileNotFoundException If the given file does not exist
|
||||
* @throws UnsupportedFileTypeException If the given file is not a valid dex/zip/odex/oat file
|
||||
*/
|
||||
public static MultiDexContainer<? extends DexBackedDexFile> loadDexContainer(
|
||||
@Nonnull File file, @Nonnull final Opcodes opcodes) throws IOException {
|
||||
if (!file.exists()) {
|
||||
throw new DexFileNotFoundException("%s does not exist", file.getName());
|
||||
}
|
||||
|
||||
ZipDexContainer zipDexContainer = new ZipDexContainer(file, opcodes);
|
||||
if (zipDexContainer.isZipFile()) {
|
||||
return zipDexContainer;
|
||||
}
|
||||
|
||||
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
|
||||
try {
|
||||
try {
|
||||
DexBackedDexFile dexFile = DexBackedDexFile.fromInputStream(opcodes, inputStream);
|
||||
return new SingletonMultiDexContainer(file.getPath(), dexFile);
|
||||
} catch (DexBackedDexFile.NotADexFile ex) {
|
||||
// just eat it
|
||||
}
|
||||
|
||||
try {
|
||||
DexBackedOdexFile odexFile = DexBackedOdexFile.fromInputStream(opcodes, inputStream);
|
||||
return new SingletonMultiDexContainer(file.getPath(), odexFile);
|
||||
} catch (DexBackedOdexFile.NotAnOdexFile ex) {
|
||||
// just eat it
|
||||
}
|
||||
|
||||
// Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream
|
||||
// back to the same position, if they fails
|
||||
|
||||
OatFile oatFile = null;
|
||||
try {
|
||||
oatFile = OatFile.fromInputStream(inputStream);
|
||||
} catch (NotAnOatFileException ex) {
|
||||
// just eat it
|
||||
}
|
||||
|
||||
if (oatFile != null) {
|
||||
// TODO: we should support loading earlier oat files, just not deodexing them
|
||||
if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) {
|
||||
throw new UnsupportedOatVersionException(oatFile);
|
||||
}
|
||||
return oatFile;
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
|
||||
throw new UnsupportedFileTypeException("%s is not an apk, dex, odex or oat file.", file.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a DexFile out to disk
|
||||
*
|
||||
* @param path The path to write the dex file to
|
||||
* @param dexFile a DexFile to write
|
||||
*/
|
||||
public static void writeDexFile(@Nonnull String path, @Nonnull DexFile dexFile) throws IOException {
|
||||
DexPool.writeTo(path, dexFile);
|
||||
}
|
||||
|
||||
private DexFileFactory() {}
|
||||
|
||||
public static class DexFileNotFound extends ExceptionWithContext {
|
||||
public DexFileNotFound(@Nullable Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public DexFileNotFound(@Nullable Throwable cause, @Nullable String message, Object... formatArgs) {
|
||||
super(cause, message, formatArgs);
|
||||
}
|
||||
|
||||
public DexFileNotFound(@Nullable String message, Object... formatArgs) {
|
||||
public static class DexFileNotFoundException extends ExceptionWithContext {
|
||||
public DexFileNotFoundException(@Nullable String message, Object... formatArgs) {
|
||||
super(message, formatArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MultipleDexFilesException extends ExceptionWithContext {
|
||||
@Nonnull public final OatFile oatFile;
|
||||
|
||||
public MultipleDexFilesException(@Nonnull OatFile oatFile) {
|
||||
super("Oat file has multiple dex files.");
|
||||
this.oatFile = oatFile;
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnsupportedOatVersionException extends ExceptionWithContext {
|
||||
@Nonnull public final OatFile oatFile;
|
||||
|
||||
@ -223,4 +304,155 @@ public final class DexFileFactory {
|
||||
this.oatFile = oatFile;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MultipleMatchingDexEntriesException extends ExceptionWithContext {
|
||||
public MultipleMatchingDexEntriesException(@Nonnull String message, Object... formatArgs) {
|
||||
super(String.format(message, formatArgs));
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnsupportedFileTypeException extends ExceptionWithContext {
|
||||
public UnsupportedFileTypeException(@Nonnull String message, Object... formatArgs) {
|
||||
super(String.format(message, formatArgs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches two entries fully, ignoring any initial slash, if any
|
||||
*/
|
||||
private static boolean fullEntryMatch(@Nonnull String entry, @Nonnull String targetEntry) {
|
||||
if (entry.equals(targetEntry)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (entry.charAt(0) == '/') {
|
||||
entry = entry.substring(1);
|
||||
}
|
||||
|
||||
if (targetEntry.charAt(0) == '/') {
|
||||
targetEntry = targetEntry.substring(1);
|
||||
}
|
||||
|
||||
return entry.equals(targetEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a partial match against entry and targetEntry.
|
||||
*
|
||||
* This is considered a partial match if targetEntry is a suffix of entry, and if the suffix starts
|
||||
* on a path "part" (ignoring the initial separator, if any). Both '/' and ':' are considered separators for this.
|
||||
*
|
||||
* So entry="/blah/blah/something.dex" and targetEntry="lah/something.dex" shouldn't match, but
|
||||
* both targetEntry="blah/something.dex" and "/blah/something.dex" should match.
|
||||
*/
|
||||
private static boolean partialEntryMatch(String entry, String targetEntry) {
|
||||
if (entry.equals(targetEntry)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!entry.endsWith(targetEntry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the first matching part is a full entry. We don't want to match "/blah/blah/something.dex" with
|
||||
// "lah/something.dex", but both "/blah/something.dex" and "blah/something.dex" should match
|
||||
char precedingChar = entry.charAt(entry.length() - targetEntry.length() - 1);
|
||||
char firstTargetChar = targetEntry.charAt(0);
|
||||
// This is a device path, so we should always use the linux separator '/', rather than the current platform's
|
||||
// separator
|
||||
return firstTargetChar == ':' || firstTargetChar == '/' || precedingChar == ':' || precedingChar == '/';
|
||||
}
|
||||
|
||||
protected static class DexEntryFinder {
|
||||
private final String filename;
|
||||
private final MultiDexContainer<? extends DexBackedDexFile> dexContainer;
|
||||
|
||||
public DexEntryFinder(@Nonnull String filename,
|
||||
@Nonnull MultiDexContainer<? extends DexBackedDexFile> dexContainer) {
|
||||
this.filename = filename;
|
||||
this.dexContainer = dexContainer;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public DexBackedDexFile findEntry(@Nonnull String targetEntry, boolean exactMatch) throws IOException {
|
||||
if (exactMatch) {
|
||||
try {
|
||||
DexBackedDexFile dexFile = dexContainer.getEntry(targetEntry);
|
||||
if (dexFile == null) {
|
||||
throw new DexFileNotFoundException("Could not find entry %s in %s.", targetEntry, filename);
|
||||
}
|
||||
return dexFile;
|
||||
} catch (NotADexFile ex) {
|
||||
throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file", targetEntry, filename);
|
||||
}
|
||||
}
|
||||
|
||||
// find all full and partial matches
|
||||
List<String> fullMatches = Lists.newArrayList();
|
||||
List<DexBackedDexFile> fullEntries = Lists.newArrayList();
|
||||
List<String> partialMatches = Lists.newArrayList();
|
||||
List<DexBackedDexFile> partialEntries = Lists.newArrayList();
|
||||
for (String entry: dexContainer.getDexEntryNames()) {
|
||||
if (fullEntryMatch(entry, targetEntry)) {
|
||||
// We want to grab all full matches, regardless of whether they're actually a dex file.
|
||||
fullMatches.add(entry);
|
||||
fullEntries.add(dexContainer.getEntry(entry));
|
||||
} else if (partialEntryMatch(entry, targetEntry)) {
|
||||
partialMatches.add(entry);
|
||||
partialEntries.add(dexContainer.getEntry(entry));
|
||||
}
|
||||
}
|
||||
|
||||
// full matches always take priority
|
||||
if (fullEntries.size() == 1) {
|
||||
try {
|
||||
DexBackedDexFile dexFile = fullEntries.get(0);
|
||||
assert dexFile != null;
|
||||
return dexFile;
|
||||
} catch (NotADexFile ex) {
|
||||
throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file",
|
||||
fullMatches.get(0), filename);
|
||||
}
|
||||
}
|
||||
if (fullEntries.size() > 1) {
|
||||
// This should be quite rare. This would only happen if an oat file has two entries that differ
|
||||
// only by an initial path separator. e.g. "/blah/blah.dex" and "blah/blah.dex"
|
||||
throw new MultipleMatchingDexEntriesException(String.format(
|
||||
"Multiple entries in %s match %s: %s", filename, targetEntry,
|
||||
Joiner.on(", ").join(fullMatches)));
|
||||
}
|
||||
|
||||
if (partialEntries.size() == 0) {
|
||||
throw new DexFileNotFoundException("Could not find a dex entry in %s matching %s",
|
||||
filename, targetEntry);
|
||||
}
|
||||
if (partialEntries.size() > 1) {
|
||||
throw new MultipleMatchingDexEntriesException(String.format(
|
||||
"Multiple dex entries in %s match %s: %s", filename, targetEntry,
|
||||
Joiner.on(", ").join(partialMatches)));
|
||||
}
|
||||
return partialEntries.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SingletonMultiDexContainer implements MultiDexContainer<DexBackedDexFile> {
|
||||
private final String entryName;
|
||||
private final DexBackedDexFile dexFile;
|
||||
|
||||
public SingletonMultiDexContainer(@Nonnull String entryName, @Nonnull DexBackedDexFile dexFile) {
|
||||
this.entryName = entryName;
|
||||
this.dexFile = dexFile;
|
||||
}
|
||||
|
||||
@Nonnull @Override public List<String> getDexEntryNames() throws IOException {
|
||||
return ImmutableList.of(entryName);
|
||||
}
|
||||
|
||||
@Nullable @Override public DexBackedDexFile getEntry(@Nonnull String entryName) throws IOException {
|
||||
if (entryName.equals(this.entryName)) {
|
||||
return dexFile;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -330,8 +330,6 @@ public enum Opcode
|
||||
public static final int JUMBO_OPCODE = 0x200;
|
||||
//if the instruction can initialize an uninitialized object reference
|
||||
public static final int CAN_INITIALIZE_REFERENCE = 0x400;
|
||||
//if the instruction is experimental (not potentially supported by Android runtime yet)
|
||||
public static final int EXPERIMENTAL = 0x800;
|
||||
|
||||
private static final int ALL_APIS = 0xFFFF0000;
|
||||
|
||||
@ -471,10 +469,6 @@ public enum Opcode
|
||||
return (flags & CAN_INITIALIZE_REFERENCE) != 0;
|
||||
}
|
||||
|
||||
public final boolean isExperimental() {
|
||||
return (flags & EXPERIMENTAL) != 0;
|
||||
}
|
||||
|
||||
private static class VersionConstraint {
|
||||
@Nonnull public final Range<Integer> apiRange;
|
||||
@Nonnull public final Range<Integer> artVersionRange;
|
||||
|
@ -39,6 +39,10 @@ import javax.annotation.Nullable;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.jf.dexlib2.VersionMap.NO_VERSION;
|
||||
import static org.jf.dexlib2.VersionMap.mapApiToArtVersion;
|
||||
import static org.jf.dexlib2.VersionMap.mapArtVersionToApi;
|
||||
|
||||
public class Opcodes {
|
||||
|
||||
/**
|
||||
@ -52,37 +56,36 @@ public class Opcodes {
|
||||
|
||||
@Nonnull
|
||||
public static Opcodes forApi(int api) {
|
||||
return new Opcodes(api, VersionMap.mapApiToArtVersion(api), false);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static Opcodes forApi(int api, boolean experimental) {
|
||||
return new Opcodes(api, VersionMap.mapApiToArtVersion(api), experimental);
|
||||
return new Opcodes(api, NO_VERSION);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static Opcodes forArtVersion(int artVersion) {
|
||||
return forArtVersion(artVersion, false);
|
||||
return new Opcodes(NO_VERSION, artVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a default Opcodes instance for when the exact Opcodes to use doesn't matter or isn't known
|
||||
*/
|
||||
@Nonnull
|
||||
public static Opcodes forArtVersion(int artVersion, boolean experimental) {
|
||||
return new Opcodes(VersionMap.mapArtVersionToApi(artVersion), artVersion, experimental);
|
||||
public static Opcodes getDefault() {
|
||||
// The last pre-art api
|
||||
return forApi(20);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Opcodes(int api) {
|
||||
this(api, false);
|
||||
}
|
||||
private Opcodes(int api, int artVersion) {
|
||||
|
||||
@Deprecated
|
||||
public Opcodes(int api, boolean experimental) {
|
||||
this(api, VersionMap.mapApiToArtVersion(api), experimental);
|
||||
}
|
||||
|
||||
private Opcodes(int api, int artVersion, boolean experimental) {
|
||||
this.api = api;
|
||||
this.artVersion = artVersion;
|
||||
if (api >= 21) {
|
||||
this.api = api;
|
||||
this.artVersion = mapApiToArtVersion(api);
|
||||
} else if (artVersion >= 0 && artVersion < 39) {
|
||||
this.api = mapArtVersionToApi(artVersion);
|
||||
this.artVersion = artVersion;
|
||||
} else {
|
||||
this.api = api;
|
||||
this.artVersion = artVersion;
|
||||
}
|
||||
|
||||
opcodeValues = new EnumMap<Opcode, Short>(Opcode.class);
|
||||
opcodesByName = Maps.newHashMap();
|
||||
@ -104,7 +107,7 @@ public class Opcodes {
|
||||
}
|
||||
|
||||
Short opcodeValue = versionToValueMap.get(version);
|
||||
if (opcodeValue != null && (!opcode.isExperimental() || experimental)) {
|
||||
if (opcodeValue != null) {
|
||||
if (!opcode.format.isPayloadFormat) {
|
||||
opcodesByValue[opcodeValue] = opcode;
|
||||
}
|
||||
@ -142,6 +145,6 @@ public class Opcodes {
|
||||
}
|
||||
|
||||
public boolean isArt() {
|
||||
return artVersion != VersionMap.NO_VERSION;
|
||||
return artVersion != NO_VERSION;
|
||||
}
|
||||
}
|
||||
|
@ -35,16 +35,38 @@ public class VersionMap {
|
||||
public static final int NO_VERSION = -1;
|
||||
|
||||
public static int mapArtVersionToApi(int artVersion) {
|
||||
// TODO: implement this
|
||||
return 20;
|
||||
if (artVersion >= 79) {
|
||||
return 24;
|
||||
}
|
||||
if (artVersion >= 64) {
|
||||
return 23;
|
||||
}
|
||||
if (artVersion >= 45) {
|
||||
return 22;
|
||||
}
|
||||
if (artVersion >= 39) {
|
||||
return 21;
|
||||
}
|
||||
return 19;
|
||||
}
|
||||
|
||||
public static int mapApiToArtVersion(int api) {
|
||||
// TODO: implement this
|
||||
if (api < 20) {
|
||||
return NO_VERSION;
|
||||
} else {
|
||||
return 56;
|
||||
switch (api) {
|
||||
case 19:
|
||||
case 20:
|
||||
return 7;
|
||||
case 21:
|
||||
return 39;
|
||||
case 22:
|
||||
return 45;
|
||||
case 23:
|
||||
return 64;
|
||||
case 24:
|
||||
return 79;
|
||||
}
|
||||
if (api > 24) {
|
||||
return 79;
|
||||
}
|
||||
return NO_VERSION;
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
/**
|
||||
* The actual instruction
|
||||
*/
|
||||
@Nullable
|
||||
@Nonnull
|
||||
protected Instruction instruction;
|
||||
|
||||
/**
|
||||
@ -65,21 +65,25 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
/**
|
||||
* Instructions that can pass on execution to this one during normal execution
|
||||
*/
|
||||
@Nonnull
|
||||
protected final TreeSet<AnalyzedInstruction> predecessors = new TreeSet<AnalyzedInstruction>();
|
||||
|
||||
/**
|
||||
* Instructions that can execution could pass on to next during normal execution
|
||||
*/
|
||||
@Nonnull
|
||||
protected final LinkedList<AnalyzedInstruction> successors = new LinkedList<AnalyzedInstruction>();
|
||||
|
||||
/**
|
||||
* This contains the register types *before* the instruction has executed
|
||||
*/
|
||||
@Nonnull
|
||||
protected final RegisterType[] preRegisterMap;
|
||||
|
||||
/**
|
||||
* This contains the register types *after* the instruction has executed
|
||||
*/
|
||||
@Nonnull
|
||||
protected final RegisterType[] postRegisterMap;
|
||||
|
||||
/**
|
||||
@ -94,8 +98,8 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
*/
|
||||
protected final Instruction originalInstruction;
|
||||
|
||||
public AnalyzedInstruction(MethodAnalyzer methodAnalyzer, Instruction instruction, int instructionIndex,
|
||||
int registerCount) {
|
||||
public AnalyzedInstruction(@Nonnull MethodAnalyzer methodAnalyzer, @Nonnull Instruction instruction,
|
||||
int instructionIndex, int registerCount) {
|
||||
this.methodAnalyzer = methodAnalyzer;
|
||||
this.instruction = instruction;
|
||||
this.originalInstruction = instruction;
|
||||
@ -150,18 +154,17 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
instruction = originalInstruction;
|
||||
}
|
||||
|
||||
public int getSuccessorCount() {
|
||||
return successors.size();
|
||||
}
|
||||
|
||||
public List<AnalyzedInstruction> getSuccesors() {
|
||||
@Nonnull
|
||||
public List<AnalyzedInstruction> getSuccessors() {
|
||||
return Collections.unmodifiableList(successors);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Instruction getInstruction() {
|
||||
return instruction;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Instruction getOriginalInstruction() {
|
||||
return originalInstruction;
|
||||
}
|
||||
@ -184,11 +187,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
if (predecessors.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (predecessors.first().instructionIndex == -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return predecessors.first().instructionIndex == -1;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -237,6 +236,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
* @param registerNumber the register number
|
||||
* @return The register type resulting from merging the post-instruction register types from all predecessors
|
||||
*/
|
||||
@Nonnull
|
||||
protected RegisterType getMergedPreRegisterTypeFromPredecessors(int registerNumber) {
|
||||
RegisterType mergedRegisterType = null;
|
||||
for (AnalyzedInstruction predecessor: predecessors) {
|
||||
@ -249,6 +249,10 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mergedRegisterType == null) {
|
||||
// This is a start-of-method or unreachable instruction.
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return mergedRegisterType;
|
||||
}
|
||||
/**
|
||||
@ -275,10 +279,10 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
*
|
||||
* This is used to set the register type for only one branch from a conditional jump.
|
||||
*
|
||||
* @param predecessor Which predecessor is being overriden
|
||||
* @param registerNumber The register number of the register being overriden
|
||||
* @param predecessor Which predecessor is being overridden
|
||||
* @param registerNumber The register number of the register being overridden
|
||||
* @param registerType The overridden register type
|
||||
* @param verifiedInstructions
|
||||
* @param verifiedInstructions A bit vector of instructions that have been verified
|
||||
*
|
||||
* @return true if the post-instruction register type for this instruction changed as a result of this override
|
||||
*/
|
||||
@ -309,7 +313,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
}
|
||||
|
||||
protected boolean isInvokeInit() {
|
||||
if (instruction == null || !instruction.getOpcode().canInitializeReference()) {
|
||||
if (!instruction.getOpcode().canInitializeReference()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -364,10 +368,10 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (instruction.getOpcode() == Opcode.IF_EQZ || instruction.getOpcode() == Opcode.IF_NEZ) {
|
||||
if (getPredecessorCount() == 1 && (instruction.getOpcode() == Opcode.IF_EQZ ||
|
||||
instruction.getOpcode() == Opcode.IF_NEZ)) {
|
||||
AnalyzedInstruction previousInstruction = getPreviousInstruction();
|
||||
if (previousInstruction != null &&
|
||||
previousInstruction.instruction != null &&
|
||||
previousInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF &&
|
||||
registerNumber == ((Instruction22c)previousInstruction.instruction).getRegisterB() &&
|
||||
MethodAnalyzer.canNarrowAfterInstanceOf(previousInstruction, this, methodAnalyzer.getClassPath())) {
|
||||
@ -421,7 +425,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
return preRegisterMap[registerNumber];
|
||||
}
|
||||
|
||||
public int compareTo(AnalyzedInstruction analyzedInstruction) {
|
||||
public int compareTo(@Nonnull AnalyzedInstruction analyzedInstruction) {
|
||||
if (instructionIndex < analyzedInstruction.instructionIndex) {
|
||||
return -1;
|
||||
} else if (instructionIndex == analyzedInstruction.instructionIndex) {
|
||||
|
@ -36,28 +36,18 @@ import com.google.common.base.Suppliers;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.jf.dexlib2.DexFileFactory;
|
||||
import org.jf.dexlib2.DexFileFactory.DexFileNotFound;
|
||||
import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
|
||||
import org.jf.dexlib2.Opcodes;
|
||||
import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
|
||||
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
|
||||
import org.jf.dexlib2.iface.ClassDef;
|
||||
import org.jf.dexlib2.iface.DexFile;
|
||||
import org.jf.dexlib2.immutable.ImmutableDexFile;
|
||||
import org.jf.util.ExceptionWithContext;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ClassPath {
|
||||
@Nonnull private final TypeProto unknownClass;
|
||||
@ -114,7 +104,7 @@ public class ClassPath {
|
||||
|
||||
private static ClassProvider getBasicClasses() {
|
||||
// fallbacks for some special classes that we assume are present
|
||||
return new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), ImmutableSet.of(
|
||||
return new DexClassProvider(new ImmutableDexFile(Opcodes.getDefault(), ImmutableSet.of(
|
||||
new ReflectionClassDef(Class.class),
|
||||
new ReflectionClassDef(Cloneable.class),
|
||||
new ReflectionClassDef(Object.class),
|
||||
@ -164,119 +154,6 @@ public class ClassPath {
|
||||
return checkPackagePrivateAccess;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
|
||||
int api, boolean experimental) {
|
||||
return fromClassPath(classPathDirs, classPath, dexFile, api, api == 17, experimental);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
|
||||
int api, boolean checkPackagePrivateAccess, boolean experimental) {
|
||||
List<ClassProvider> providers = Lists.newArrayList();
|
||||
|
||||
int oatVersion = NOT_ART;
|
||||
|
||||
for (String classPathEntry: classPath) {
|
||||
List<? extends DexFile> classPathDexFiles =
|
||||
loadClassPathEntry(classPathDirs, classPathEntry, api, experimental);
|
||||
if (oatVersion == NOT_ART) {
|
||||
for (DexFile classPathDexFile: classPathDexFiles) {
|
||||
if (classPathDexFile instanceof OatDexFile) {
|
||||
oatVersion = ((OatDexFile)classPathDexFile).getOatVersion();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (DexFile classPathDexFile: classPathDexFiles) {
|
||||
providers.add(new DexClassProvider(classPathDexFile));
|
||||
}
|
||||
}
|
||||
providers.add(new DexClassProvider(dexFile));
|
||||
return new ClassPath(providers, checkPackagePrivateAccess, oatVersion);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
|
||||
int api, boolean checkPackagePrivateAccess, boolean experimental,
|
||||
int oatVersion) {
|
||||
List<ClassProvider> providers = Lists.newArrayList();
|
||||
|
||||
for (String classPathEntry: classPath) {
|
||||
List<? extends DexFile> classPathDexFiles =
|
||||
loadClassPathEntry(classPathDirs, classPathEntry, api, experimental);
|
||||
for (DexFile classPathDexFile: classPathDexFiles) {
|
||||
providers.add(new DexClassProvider(classPathDexFile));
|
||||
}
|
||||
}
|
||||
providers.add(new DexClassProvider(dexFile));
|
||||
return new ClassPath(providers, checkPackagePrivateAccess, oatVersion);
|
||||
}
|
||||
|
||||
private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
|
||||
|
||||
@Nonnull
|
||||
private static List<? extends DexFile> loadClassPathEntry(@Nonnull Iterable<String> classPathDirs,
|
||||
@Nonnull String bootClassPathEntry, int api,
|
||||
boolean experimental) {
|
||||
File rawEntry = new File(bootClassPathEntry);
|
||||
// strip off the path - we only care about the filename
|
||||
String entryName = rawEntry.getName();
|
||||
|
||||
// if it's a dalvik-cache entry, grab the name of the jar/apk
|
||||
if (entryName.endsWith("@classes.dex")) {
|
||||
Matcher m = dalvikCacheOdexPattern.matcher(entryName);
|
||||
|
||||
if (!m.find()) {
|
||||
throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", bootClassPathEntry));
|
||||
}
|
||||
|
||||
entryName = m.group(1);
|
||||
}
|
||||
|
||||
int extIndex = entryName.lastIndexOf(".");
|
||||
|
||||
String baseEntryName;
|
||||
if (extIndex == -1) {
|
||||
baseEntryName = entryName;
|
||||
} else {
|
||||
baseEntryName = entryName.substring(0, extIndex);
|
||||
}
|
||||
|
||||
for (String classPathDir: classPathDirs) {
|
||||
String[] extensions;
|
||||
|
||||
if (entryName.endsWith(".oat")) {
|
||||
extensions = new String[] { ".oat" };
|
||||
} else {
|
||||
extensions = new String[] { "", ".odex", ".jar", ".apk", ".zip" };
|
||||
}
|
||||
|
||||
for (String ext: extensions) {
|
||||
File file = new File(classPathDir, baseEntryName + ext);
|
||||
|
||||
if (file.exists() && file.isFile()) {
|
||||
if (!file.canRead()) {
|
||||
System.err.println(String.format(
|
||||
"warning: cannot open %s for reading. Will continue looking.", file.getPath()));
|
||||
} else {
|
||||
try {
|
||||
return ImmutableList.of(DexFileFactory.loadDexFile(file, api, experimental));
|
||||
} catch (DexFileNotFound ex) {
|
||||
// ignore and continue
|
||||
} catch (MultipleDexFilesException ex) {
|
||||
return ex.oatFile.getDexFiles();
|
||||
} catch (Exception ex) {
|
||||
throw ExceptionWithContext.withContext(ex,
|
||||
"Error while reading boot class path entry \"%s\"", bootClassPathEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new ExceptionWithContext("Cannot locate boot class path file %s", bootClassPathEntry);
|
||||
}
|
||||
|
||||
private final Supplier<OdexedFieldInstructionMapper> fieldInstructionMapperSupplier = Suppliers.memoize(
|
||||
new Supplier<OdexedFieldInstructionMapper>() {
|
||||
@Override public OdexedFieldInstructionMapper get() {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -39,12 +39,10 @@ import com.google.common.collect.*;
|
||||
import com.google.common.primitives.Ints;
|
||||
import org.jf.dexlib2.AccessFlags;
|
||||
import org.jf.dexlib2.analysis.util.TypeProtoUtils;
|
||||
import org.jf.dexlib2.iface.ClassDef;
|
||||
import org.jf.dexlib2.iface.Field;
|
||||
import org.jf.dexlib2.iface.Method;
|
||||
import org.jf.dexlib2.base.reference.BaseMethodReference;
|
||||
import org.jf.dexlib2.iface.*;
|
||||
import org.jf.dexlib2.iface.reference.FieldReference;
|
||||
import org.jf.dexlib2.iface.reference.MethodReference;
|
||||
import org.jf.dexlib2.immutable.ImmutableMethod;
|
||||
import org.jf.dexlib2.util.MethodUtil;
|
||||
import org.jf.util.AlignmentUtils;
|
||||
import org.jf.util.ExceptionWithContext;
|
||||
@ -53,6 +51,7 @@ import org.jf.util.SparseArray;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* A class "prototype". This contains things like the interfaces, the superclass, the vtable and the instance fields
|
||||
@ -123,11 +122,18 @@ public class ClassProto implements TypeProto {
|
||||
*/
|
||||
@Nonnull
|
||||
protected LinkedHashMap<String, ClassDef> getInterfaces() {
|
||||
return interfacesSupplier.get();
|
||||
if (!classPath.isArt() || classPath.oatVersion < 72) {
|
||||
return preDefaultMethodInterfaceSupplier.get();
|
||||
} else {
|
||||
return postDefaultMethodInterfaceSupplier.get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This calculates the interfaces in the order required for vtable generation for dalvik and pre-default method ART
|
||||
*/
|
||||
@Nonnull
|
||||
private final Supplier<LinkedHashMap<String, ClassDef>> interfacesSupplier =
|
||||
private final Supplier<LinkedHashMap<String, ClassDef>> preDefaultMethodInterfaceSupplier =
|
||||
Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
|
||||
@Override public LinkedHashMap<String, ClassDef> get() {
|
||||
Set<String> unresolvedInterfaces = new HashSet<String>(0);
|
||||
@ -149,7 +155,8 @@ public class ClassProto implements TypeProto {
|
||||
ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
|
||||
for (String superInterface: interfaceProto.getInterfaces().keySet()) {
|
||||
if (!interfaces.containsKey(superInterface)) {
|
||||
interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface));
|
||||
interfaces.put(superInterface,
|
||||
interfaceProto.getInterfaces().get(superInterface));
|
||||
}
|
||||
}
|
||||
if (!interfaceProto.interfacesFullyResolved) {
|
||||
@ -159,6 +166,7 @@ public class ClassProto implements TypeProto {
|
||||
}
|
||||
}
|
||||
} catch (UnresolvedClassException ex) {
|
||||
interfaces.put(type, null);
|
||||
unresolvedInterfaces.add(type);
|
||||
interfacesFullyResolved = false;
|
||||
}
|
||||
@ -197,6 +205,71 @@ public class ClassProto implements TypeProto {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* This calculates the interfaces in the order required for vtable generation for post-default method ART
|
||||
*/
|
||||
@Nonnull
|
||||
private final Supplier<LinkedHashMap<String, ClassDef>> postDefaultMethodInterfaceSupplier =
|
||||
Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
|
||||
@Override public LinkedHashMap<String, ClassDef> get() {
|
||||
Set<String> unresolvedInterfaces = new HashSet<String>(0);
|
||||
LinkedHashMap<String, ClassDef> interfaces = Maps.newLinkedHashMap();
|
||||
|
||||
String superclass = getSuperclass();
|
||||
if (superclass != null) {
|
||||
ClassProto superclassProto = (ClassProto) classPath.getClass(superclass);
|
||||
for (String superclassInterface: superclassProto.getInterfaces().keySet()) {
|
||||
interfaces.put(superclassInterface, null);
|
||||
}
|
||||
if (!superclassProto.interfacesFullyResolved) {
|
||||
unresolvedInterfaces.addAll(superclassProto.getUnresolvedInterfaces());
|
||||
interfacesFullyResolved = false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
for (String interfaceType: getClassDef().getInterfaces()) {
|
||||
if (!interfaces.containsKey(interfaceType)) {
|
||||
ClassProto interfaceProto = (ClassProto)classPath.getClass(interfaceType);
|
||||
try {
|
||||
for (Entry<String, ClassDef> entry: interfaceProto.getInterfaces().entrySet()) {
|
||||
if (!interfaces.containsKey(entry.getKey())) {
|
||||
interfaces.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
} catch (UnresolvedClassException ex) {
|
||||
interfaces.put(interfaceType, null);
|
||||
unresolvedInterfaces.add(interfaceType);
|
||||
interfacesFullyResolved = false;
|
||||
}
|
||||
if (!interfaceProto.interfacesFullyResolved) {
|
||||
unresolvedInterfaces.addAll(interfaceProto.getUnresolvedInterfaces());
|
||||
interfacesFullyResolved = false;
|
||||
}
|
||||
try {
|
||||
ClassDef interfaceDef = classPath.getClassDef(interfaceType);
|
||||
interfaces.put(interfaceType, interfaceDef);
|
||||
} catch (UnresolvedClassException ex) {
|
||||
interfaces.put(interfaceType, null);
|
||||
unresolvedInterfaces.add(interfaceType);
|
||||
interfacesFullyResolved = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (UnresolvedClassException ex) {
|
||||
interfaces.put(type, null);
|
||||
unresolvedInterfaces.add(type);
|
||||
interfacesFullyResolved = false;
|
||||
}
|
||||
|
||||
if (unresolvedInterfaces.size() > 0) {
|
||||
ClassProto.this.unresolvedInterfaces = unresolvedInterfaces;
|
||||
}
|
||||
|
||||
return interfaces;
|
||||
}
|
||||
});
|
||||
|
||||
@Nonnull
|
||||
protected Set<String> getUnresolvedInterfaces() {
|
||||
if (unresolvedInterfaces == null) {
|
||||
@ -379,7 +452,10 @@ public class ClassProto implements TypeProto {
|
||||
}
|
||||
|
||||
public int findMethodIndexInVtable(@Nonnull MethodReference method) {
|
||||
List<Method> vtable = getVtable();
|
||||
return findMethodIndexInVtable(getVtable(), method);
|
||||
}
|
||||
|
||||
private int findMethodIndexInVtable(@Nonnull List<Method> vtable, MethodReference method) {
|
||||
for (int i=0; i<vtable.size(); i++) {
|
||||
Method candidate = vtable.get(i);
|
||||
if (MethodUtil.methodSignaturesMatch(candidate, method)) {
|
||||
@ -392,7 +468,20 @@ public class ClassProto implements TypeProto {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Nonnull SparseArray<FieldReference> getInstanceFields() {
|
||||
private int findMethodIndexInVtableReverse(@Nonnull List<Method> vtable, MethodReference method) {
|
||||
for (int i=vtable.size() - 1; i>=0; i--) {
|
||||
Method candidate = vtable.get(i);
|
||||
if (MethodUtil.methodSignaturesMatch(candidate, method)) {
|
||||
if (!classPath.shouldCheckPackagePrivateAccess() ||
|
||||
AnalyzedMethodUtil.canAccess(this, candidate, true, false, false)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Nonnull public SparseArray<FieldReference> getInstanceFields() {
|
||||
if (classPath.isArt()) {
|
||||
return artInstanceFieldsSupplier.get();
|
||||
} else {
|
||||
@ -440,9 +529,7 @@ public class ClassProto implements TypeProto {
|
||||
ClassProto superclass = null;
|
||||
if (superclassType != null) {
|
||||
superclass = (ClassProto) classPath.getClass(superclassType);
|
||||
if (superclass != null) {
|
||||
startFieldOffset = superclass.getNextFieldOffset();
|
||||
}
|
||||
startFieldOffset = superclass.getNextFieldOffset();
|
||||
}
|
||||
|
||||
int fieldIndexMod;
|
||||
@ -530,13 +617,11 @@ public class ClassProto implements TypeProto {
|
||||
|
||||
//add padding to align the wide fields, if needed
|
||||
if (fieldTypes[i] == WIDE && !gotDouble) {
|
||||
if (!gotDouble) {
|
||||
if (fieldOffset % 8 != 0) {
|
||||
assert fieldOffset % 8 == 4;
|
||||
fieldOffset += 4;
|
||||
}
|
||||
gotDouble = true;
|
||||
if (fieldOffset % 8 != 0) {
|
||||
assert fieldOffset % 8 == 4;
|
||||
fieldOffset += 4;
|
||||
}
|
||||
gotDouble = true;
|
||||
}
|
||||
|
||||
instanceFields.append(fieldOffset, field);
|
||||
@ -574,7 +659,7 @@ public class ClassProto implements TypeProto {
|
||||
public static FieldGap newFieldGap(int offset, int size, int oatVersion) {
|
||||
if (oatVersion >= 67) {
|
||||
return new FieldGap(offset, size) {
|
||||
@Override public int compareTo(FieldGap o) {
|
||||
@Override public int compareTo(@Nonnull FieldGap o) {
|
||||
int result = Ints.compare(o.size, size);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
@ -584,7 +669,7 @@ public class ClassProto implements TypeProto {
|
||||
};
|
||||
} else {
|
||||
return new FieldGap(offset, size) {
|
||||
@Override public int compareTo(FieldGap o) {
|
||||
@Override public int compareTo(@Nonnull FieldGap o) {
|
||||
int result = Ints.compare(size, o.size);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
@ -778,12 +863,18 @@ public class ClassProto implements TypeProto {
|
||||
throw new ExceptionWithContext("Invalid type: %s", type);
|
||||
}
|
||||
|
||||
@Nonnull List<Method> getVtable() {
|
||||
return vtableSupplier.get();
|
||||
@Nonnull public List<Method> getVtable() {
|
||||
if (!classPath.isArt() || classPath.oatVersion < 72) {
|
||||
return preDefaultMethodVtableSupplier.get();
|
||||
} else if (classPath.oatVersion < 87) {
|
||||
return buggyPostDefaultMethodVtableSupplier.get();
|
||||
} else {
|
||||
return postDefaultMethodVtableSupplier.get();
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: check the case when we have a package private method that overrides an interface method
|
||||
@Nonnull private final Supplier<List<Method>> vtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
|
||||
@Nonnull private final Supplier<List<Method>> preDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
|
||||
@Override public List<Method> get() {
|
||||
List<Method> vtable = Lists.newArrayList();
|
||||
|
||||
@ -812,52 +903,315 @@ public class ClassProto implements TypeProto {
|
||||
//iterate over the virtual methods in the current class, and only add them when we don't already have the
|
||||
//method (i.e. if it was implemented by the superclass)
|
||||
if (!isInterface()) {
|
||||
addToVtable(getClassDef().getVirtualMethods(), vtable, true);
|
||||
addToVtable(getClassDef().getVirtualMethods(), vtable, true, true);
|
||||
|
||||
// assume that interface method is implemented in the current class, when adding it to vtable
|
||||
// otherwise it looks like that method is invoked on an interface, which fails Dalvik's optimization checks
|
||||
for (ClassDef interfaceDef: getDirectInterfaces()) {
|
||||
// We use the current class for any vtable method references that we add, rather than the interface, so
|
||||
// we don't end up trying to call invoke-virtual using an interface, which will fail verification
|
||||
Iterable<ClassDef> interfaces = getDirectInterfaces();
|
||||
for (ClassDef interfaceDef: interfaces) {
|
||||
List<Method> interfaceMethods = Lists.newArrayList();
|
||||
for (Method interfaceMethod: interfaceDef.getVirtualMethods()) {
|
||||
ImmutableMethod method = new ImmutableMethod(
|
||||
type,
|
||||
interfaceMethod.getName(),
|
||||
interfaceMethod.getParameters(),
|
||||
interfaceMethod.getReturnType(),
|
||||
interfaceMethod.getAccessFlags(),
|
||||
interfaceMethod.getAnnotations(),
|
||||
interfaceMethod.getImplementation());
|
||||
interfaceMethods.add(method);
|
||||
interfaceMethods.add(new ReparentedMethod(interfaceMethod, type));
|
||||
}
|
||||
addToVtable(interfaceMethods, vtable, false);
|
||||
addToVtable(interfaceMethods, vtable, false, true);
|
||||
}
|
||||
}
|
||||
return vtable;
|
||||
}
|
||||
});
|
||||
|
||||
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods,
|
||||
@Nonnull List<Method> vtable, boolean replaceExisting) {
|
||||
List<? extends Method> methods = Lists.newArrayList(localMethods);
|
||||
Collections.sort(methods);
|
||||
/**
|
||||
* This is the vtable supplier for a version of art that had buggy vtable calculation logic. In some cases it can
|
||||
* produce multiple vtable entries for a given virtual method. This supplier duplicates this buggy logic in order to
|
||||
* generate an identical vtable
|
||||
*/
|
||||
@Nonnull private final Supplier<List<Method>> buggyPostDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
|
||||
@Override public List<Method> get() {
|
||||
List<Method> vtable = Lists.newArrayList();
|
||||
|
||||
outer: for (Method virtualMethod: methods) {
|
||||
for (int i=0; i<vtable.size(); i++) {
|
||||
Method superMethod = vtable.get(i);
|
||||
if (MethodUtil.methodSignaturesMatch(superMethod, virtualMethod)) {
|
||||
if (!classPath.shouldCheckPackagePrivateAccess() ||
|
||||
AnalyzedMethodUtil.canAccess(ClassProto.this, superMethod, true, false, false)) {
|
||||
if (replaceExisting) {
|
||||
vtable.set(i, virtualMethod);
|
||||
//copy the virtual methods from the superclass
|
||||
String superclassType;
|
||||
try {
|
||||
superclassType = getSuperclass();
|
||||
} catch (UnresolvedClassException ex) {
|
||||
vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
|
||||
vtableFullyResolved = false;
|
||||
return vtable;
|
||||
}
|
||||
|
||||
if (superclassType != null) {
|
||||
ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
|
||||
vtable.addAll(superclass.getVtable());
|
||||
|
||||
// if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by
|
||||
// this class should start, so we just propagate what we can from the parent and hope for the best.
|
||||
if (!superclass.vtableFullyResolved) {
|
||||
vtableFullyResolved = false;
|
||||
return vtable;
|
||||
}
|
||||
}
|
||||
|
||||
//iterate over the virtual methods in the current class, and only add them when we don't already have the
|
||||
//method (i.e. if it was implemented by the superclass)
|
||||
if (!isInterface()) {
|
||||
addToVtable(getClassDef().getVirtualMethods(), vtable, true, true);
|
||||
|
||||
List<String> interfaces = Lists.newArrayList(getInterfaces().keySet());
|
||||
|
||||
List<Method> defaultMethods = Lists.newArrayList();
|
||||
List<Method> defaultConflictMethods = Lists.newArrayList();
|
||||
List<Method> mirandaMethods = Lists.newArrayList();
|
||||
|
||||
final HashMap<MethodReference, Integer> methodOrder = Maps.newHashMap();
|
||||
|
||||
for (int i=interfaces.size()-1; i>=0; i--) {
|
||||
String interfaceType = interfaces.get(i);
|
||||
ClassDef interfaceDef = classPath.getClassDef(interfaceType);
|
||||
|
||||
for (Method interfaceMethod : interfaceDef.getVirtualMethods()) {
|
||||
|
||||
int vtableIndex = findMethodIndexInVtableReverse(vtable, interfaceMethod);
|
||||
Method oldVtableMethod = null;
|
||||
if (vtableIndex >= 0) {
|
||||
oldVtableMethod = vtable.get(vtableIndex);
|
||||
}
|
||||
|
||||
for (int j=0; j<vtable.size(); j++) {
|
||||
Method candidate = vtable.get(j);
|
||||
if (MethodUtil.methodSignaturesMatch(candidate, interfaceMethod)) {
|
||||
if (!classPath.shouldCheckPackagePrivateAccess() ||
|
||||
AnalyzedMethodUtil.canAccess(ClassProto.this, candidate, true, false, false)) {
|
||||
if (interfaceMethodOverrides(interfaceMethod, candidate)) {
|
||||
vtable.set(j, interfaceMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vtableIndex >= 0) {
|
||||
if (!isOverridableByDefaultMethod(vtable.get(vtableIndex))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int defaultMethodIndex = findMethodIndexInVtable(defaultMethods, interfaceMethod);
|
||||
|
||||
if (defaultMethodIndex >= 0) {
|
||||
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
|
||||
ClassProto existingInterface = (ClassProto)classPath.getClass(
|
||||
defaultMethods.get(defaultMethodIndex).getDefiningClass());
|
||||
if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
|
||||
Method removedMethod = defaultMethods.remove(defaultMethodIndex);
|
||||
defaultConflictMethods.add(removedMethod);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int defaultConflictMethodIndex = findMethodIndexInVtable(
|
||||
defaultConflictMethods, interfaceMethod);
|
||||
if (defaultConflictMethodIndex >= 0) {
|
||||
// There's already a matching method in the conflict list, we don't need to do
|
||||
// anything else
|
||||
continue;
|
||||
}
|
||||
|
||||
int mirandaMethodIndex = findMethodIndexInVtable(mirandaMethods, interfaceMethod);
|
||||
|
||||
if (mirandaMethodIndex >= 0) {
|
||||
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
|
||||
|
||||
ClassProto existingInterface = (ClassProto)classPath.getClass(
|
||||
mirandaMethods.get(mirandaMethodIndex).getDefiningClass());
|
||||
if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
|
||||
Method oldMethod = mirandaMethods.remove(mirandaMethodIndex);
|
||||
int methodOrderValue = methodOrder.get(oldMethod);
|
||||
methodOrder.put(interfaceMethod, methodOrderValue);
|
||||
defaultMethods.add(interfaceMethod);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
|
||||
if (oldVtableMethod != null) {
|
||||
if (!interfaceMethodOverrides(interfaceMethod, oldVtableMethod)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
defaultMethods.add(interfaceMethod);
|
||||
methodOrder.put(interfaceMethod, methodOrder.size());
|
||||
} else {
|
||||
// TODO: do we need to check interfaceMethodOverrides here?
|
||||
if (oldVtableMethod == null) {
|
||||
mirandaMethods.add(interfaceMethod);
|
||||
methodOrder.put(interfaceMethod, methodOrder.size());
|
||||
}
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Comparator<MethodReference> comparator = new Comparator<MethodReference>() {
|
||||
@Override public int compare(MethodReference o1, MethodReference o2) {
|
||||
return Ints.compare(methodOrder.get(o1), methodOrder.get(o2));
|
||||
}
|
||||
};
|
||||
|
||||
// The methods should be in the same order within each list as they were iterated over.
|
||||
// They can be misordered if, e.g. a method was originally added to the default list, but then moved
|
||||
// to the conflict list.
|
||||
Collections.sort(mirandaMethods, comparator);
|
||||
Collections.sort(defaultMethods, comparator);
|
||||
Collections.sort(defaultConflictMethods, comparator);
|
||||
|
||||
vtable.addAll(mirandaMethods);
|
||||
vtable.addAll(defaultMethods);
|
||||
vtable.addAll(defaultConflictMethods);
|
||||
}
|
||||
return vtable;
|
||||
}
|
||||
});
|
||||
|
||||
@Nonnull private final Supplier<List<Method>> postDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
|
||||
@Override public List<Method> get() {
|
||||
List<Method> vtable = Lists.newArrayList();
|
||||
|
||||
//copy the virtual methods from the superclass
|
||||
String superclassType;
|
||||
try {
|
||||
superclassType = getSuperclass();
|
||||
} catch (UnresolvedClassException ex) {
|
||||
vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
|
||||
vtableFullyResolved = false;
|
||||
return vtable;
|
||||
}
|
||||
|
||||
if (superclassType != null) {
|
||||
ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
|
||||
vtable.addAll(superclass.getVtable());
|
||||
|
||||
// if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by
|
||||
// this class should start, so we just propagate what we can from the parent and hope for the best.
|
||||
if (!superclass.vtableFullyResolved) {
|
||||
vtableFullyResolved = false;
|
||||
return vtable;
|
||||
}
|
||||
}
|
||||
|
||||
//iterate over the virtual methods in the current class, and only add them when we don't already have the
|
||||
//method (i.e. if it was implemented by the superclass)
|
||||
if (!isInterface()) {
|
||||
addToVtable(getClassDef().getVirtualMethods(), vtable, true, true);
|
||||
|
||||
Iterable<ClassDef> interfaces = Lists.reverse(Lists.newArrayList(getDirectInterfaces()));
|
||||
|
||||
List<Method> defaultMethods = Lists.newArrayList();
|
||||
List<Method> defaultConflictMethods = Lists.newArrayList();
|
||||
List<Method> mirandaMethods = Lists.newArrayList();
|
||||
|
||||
final HashMap<MethodReference, Integer> methodOrder = Maps.newHashMap();
|
||||
|
||||
for (ClassDef interfaceDef: interfaces) {
|
||||
for (Method interfaceMethod : interfaceDef.getVirtualMethods()) {
|
||||
|
||||
int vtableIndex = findMethodIndexInVtable(vtable, interfaceMethod);
|
||||
|
||||
if (vtableIndex >= 0) {
|
||||
if (interfaceMethodOverrides(interfaceMethod, vtable.get(vtableIndex))) {
|
||||
vtable.set(vtableIndex, interfaceMethod);
|
||||
}
|
||||
} else {
|
||||
int defaultMethodIndex = findMethodIndexInVtable(defaultMethods, interfaceMethod);
|
||||
|
||||
if (defaultMethodIndex >= 0) {
|
||||
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
|
||||
ClassProto existingInterface = (ClassProto)classPath.getClass(
|
||||
defaultMethods.get(defaultMethodIndex).getDefiningClass());
|
||||
if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
|
||||
Method removedMethod = defaultMethods.remove(defaultMethodIndex);
|
||||
defaultConflictMethods.add(removedMethod);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int defaultConflictMethodIndex = findMethodIndexInVtable(
|
||||
defaultConflictMethods, interfaceMethod);
|
||||
if (defaultConflictMethodIndex >= 0) {
|
||||
// There's already a matching method in the conflict list, we don't need to do
|
||||
// anything else
|
||||
continue;
|
||||
}
|
||||
|
||||
int mirandaMethodIndex = findMethodIndexInVtable(mirandaMethods, interfaceMethod);
|
||||
|
||||
if (mirandaMethodIndex >= 0) {
|
||||
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
|
||||
|
||||
ClassProto existingInterface = (ClassProto)classPath.getClass(
|
||||
mirandaMethods.get(mirandaMethodIndex).getDefiningClass());
|
||||
if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
|
||||
Method oldMethod = mirandaMethods.remove(mirandaMethodIndex);
|
||||
int methodOrderValue = methodOrder.get(oldMethod);
|
||||
methodOrder.put(interfaceMethod, methodOrderValue);
|
||||
defaultMethods.add(interfaceMethod);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
|
||||
defaultMethods.add(interfaceMethod);
|
||||
methodOrder.put(interfaceMethod, methodOrder.size());
|
||||
} else {
|
||||
mirandaMethods.add(interfaceMethod);
|
||||
methodOrder.put(interfaceMethod, methodOrder.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Comparator<MethodReference> comparator = new Comparator<MethodReference>() {
|
||||
@Override public int compare(MethodReference o1, MethodReference o2) {
|
||||
return Ints.compare(methodOrder.get(o1), methodOrder.get(o2));
|
||||
}
|
||||
};
|
||||
|
||||
// The methods should be in the same order within each list as they were iterated over.
|
||||
// They can be misordered if, e.g. a method was originally added to the default list, but then moved
|
||||
// to the conflict list.
|
||||
Collections.sort(defaultMethods, comparator);
|
||||
Collections.sort(defaultConflictMethods, comparator);
|
||||
Collections.sort(mirandaMethods, comparator);
|
||||
addToVtable(defaultMethods, vtable, false, false);
|
||||
addToVtable(defaultConflictMethods, vtable, false, false);
|
||||
addToVtable(mirandaMethods, vtable, false, false);
|
||||
}
|
||||
return vtable;
|
||||
}
|
||||
});
|
||||
|
||||
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods, @Nonnull List<Method> vtable,
|
||||
boolean replaceExisting, boolean sort) {
|
||||
if (sort) {
|
||||
ArrayList<Method> methods = Lists.newArrayList(localMethods);
|
||||
Collections.sort(methods);
|
||||
localMethods = methods;
|
||||
}
|
||||
|
||||
for (Method virtualMethod: localMethods) {
|
||||
int vtableIndex = findMethodIndexInVtable(vtable, virtualMethod);
|
||||
|
||||
if (vtableIndex >= 0) {
|
||||
if (replaceExisting) {
|
||||
vtable.set(vtableIndex, virtualMethod);
|
||||
}
|
||||
} else {
|
||||
// we didn't find an equivalent method, so add it as a new entry
|
||||
vtable.add(virtualMethod);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static byte getFieldType(@Nonnull FieldReference field) {
|
||||
switch (field.getType().charAt(0)) {
|
||||
@ -871,4 +1225,68 @@ public class ClassProto implements TypeProto {
|
||||
return 2; //OTHER
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOverridableByDefaultMethod(@Nonnull Method method) {
|
||||
ClassProto classProto = (ClassProto)classPath.getClass(method.getDefiningClass());
|
||||
return classProto.isInterface();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the interface method overrides the virtual or interface method2
|
||||
* @param method A Method from an interface
|
||||
* @param method2 A Method from an interface or a class
|
||||
* @return true if the interface method overrides the virtual or interface method2
|
||||
*/
|
||||
private boolean interfaceMethodOverrides(@Nonnull Method method, @Nonnull Method method2) {
|
||||
ClassProto classProto = (ClassProto)classPath.getClass(method2.getDefiningClass());
|
||||
|
||||
if (classProto.isInterface()) {
|
||||
ClassProto targetClassProto = (ClassProto)classPath.getClass(method.getDefiningClass());
|
||||
return targetClassProto.implementsInterface(method2.getDefiningClass());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static class ReparentedMethod extends BaseMethodReference implements Method {
|
||||
private final Method method;
|
||||
private final String definingClass;
|
||||
|
||||
public ReparentedMethod(Method method, String definingClass) {
|
||||
this.method = method;
|
||||
this.definingClass = definingClass;
|
||||
}
|
||||
|
||||
@Nonnull @Override public String getDefiningClass() {
|
||||
return definingClass;
|
||||
}
|
||||
|
||||
@Nonnull @Override public String getName() {
|
||||
return method.getName();
|
||||
}
|
||||
|
||||
@Nonnull @Override public List<? extends CharSequence> getParameterTypes() {
|
||||
return method.getParameterTypes();
|
||||
}
|
||||
|
||||
@Nonnull @Override public String getReturnType() {
|
||||
return method.getReturnType();
|
||||
}
|
||||
|
||||
@Nonnull @Override public List<? extends MethodParameter> getParameters() {
|
||||
return method.getParameters();
|
||||
}
|
||||
|
||||
@Override public int getAccessFlags() {
|
||||
return method.getAccessFlags();
|
||||
}
|
||||
|
||||
@Nonnull @Override public Set<? extends Annotation> getAnnotations() {
|
||||
return method.getAnnotations();
|
||||
}
|
||||
|
||||
@Nullable @Override public MethodImplementation getImplementation() {
|
||||
return method.getImplementation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -36,6 +36,7 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.jf.dexlib2.AccessFlags;
|
||||
import org.jf.dexlib2.Opcode;
|
||||
import org.jf.dexlib2.base.reference.BaseMethodReference;
|
||||
import org.jf.dexlib2.iface.*;
|
||||
import org.jf.dexlib2.iface.instruction.*;
|
||||
import org.jf.dexlib2.iface.instruction.formats.*;
|
||||
@ -89,10 +90,10 @@ public class MethodAnalyzer {
|
||||
|
||||
@Nullable private AnalysisException analysisException = null;
|
||||
|
||||
//This is a dummy instruction that occurs immediately before the first real instruction. We can initialize the
|
||||
//register types for this instruction to the parameter types, in order to have them propagate to all of its
|
||||
//successors, e.g. the first real instruction, the first instructions in any exception handlers covering the first
|
||||
//instruction, etc.
|
||||
// This is a dummy instruction that occurs immediately before the first real instruction. We can initialize the
|
||||
// register types for this instruction to the parameter types, in order to have them propagate to all of its
|
||||
// successors, e.g. the first real instruction, the first instructions in any exception handlers covering the first
|
||||
// instruction, etc.
|
||||
private final AnalyzedInstruction startOfMethod;
|
||||
|
||||
public MethodAnalyzer(@Nonnull ClassPath classPath, @Nonnull Method method,
|
||||
@ -110,27 +111,16 @@ public class MethodAnalyzer {
|
||||
|
||||
this.methodImpl = methodImpl;
|
||||
|
||||
//override AnalyzedInstruction and provide custom implementations of some of the methods, so that we don't
|
||||
//have to handle the case this special case of instruction being null, in the main class
|
||||
startOfMethod = new AnalyzedInstruction(this, null, -1, methodImpl.getRegisterCount()) {
|
||||
public boolean setsRegister() {
|
||||
return false;
|
||||
// Override AnalyzedInstruction and provide custom implementations of some of the methods, so that we don't
|
||||
// have to handle the case this special case of instruction being null, in the main class
|
||||
startOfMethod = new AnalyzedInstruction(this, new ImmutableInstruction10x(Opcode.NOP), -1, methodImpl.getRegisterCount()) {
|
||||
@Override protected boolean addPredecessor(AnalyzedInstruction predecessor) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setsWideRegister() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setsRegister(int registerNumber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDestinationRegister() {
|
||||
assert false;
|
||||
return -1;
|
||||
@Override @Nonnull
|
||||
public RegisterType getPredecessorRegisterType(@Nonnull AnalyzedInstruction predecessor, int registerNumber) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
|
||||
@ -141,6 +131,7 @@ public class MethodAnalyzer {
|
||||
analyze();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public ClassPath getClassPath() {
|
||||
return classPath;
|
||||
}
|
||||
@ -1176,32 +1167,36 @@ public class MethodAnalyzer {
|
||||
setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, castRegisterType);
|
||||
}
|
||||
|
||||
private static boolean isNarrowingConversion(RegisterType originalType, RegisterType newType) {
|
||||
if (originalType.type == null || newType.type == null) {
|
||||
return false;
|
||||
}
|
||||
if (originalType.type.isInterface()) {
|
||||
return newType.type.implementsInterface(originalType.type.getType());
|
||||
} else {
|
||||
TypeProto commonSuperclass = newType.type.getCommonSuperclass(originalType.type);
|
||||
return commonSuperclass.getType().equals(originalType.type.getType());
|
||||
}
|
||||
}
|
||||
|
||||
static boolean canNarrowAfterInstanceOf(AnalyzedInstruction analyzedInstanceOfInstruction,
|
||||
AnalyzedInstruction analyzedIfInstruction, ClassPath classPath) {
|
||||
Instruction ifInstruction = analyzedIfInstruction.instruction;
|
||||
assert analyzedIfInstruction.instruction != null;
|
||||
if (((Instruction21t)ifInstruction).getRegisterA() == analyzedInstanceOfInstruction.getDestinationRegister()) {
|
||||
Reference reference = ((Instruction22c)analyzedInstanceOfInstruction.getInstruction()).getReference();
|
||||
RegisterType registerType = RegisterType.getRegisterType(classPath, (TypeReference)reference);
|
||||
|
||||
if (registerType.type != null && !registerType.type.isInterface()) {
|
||||
int objectRegister = ((TwoRegisterInstruction)analyzedInstanceOfInstruction.getInstruction())
|
||||
.getRegisterB();
|
||||
try {
|
||||
if (registerType.type != null && !registerType.type.isInterface()) {
|
||||
int objectRegister = ((TwoRegisterInstruction)analyzedInstanceOfInstruction.getInstruction())
|
||||
.getRegisterB();
|
||||
|
||||
RegisterType originalType = analyzedIfInstruction.getPreInstructionRegisterType(objectRegister);
|
||||
RegisterType originalType = analyzedIfInstruction.getPreInstructionRegisterType(objectRegister);
|
||||
|
||||
if (originalType.type != null) {
|
||||
// Only override if we're going from an interface to a class, or are going to a narrower class
|
||||
if (originalType.type.isInterface()) {
|
||||
return true;
|
||||
} else {
|
||||
TypeProto commonSuperclass = registerType.type.getCommonSuperclass(originalType.type);
|
||||
// only if it's a narrowing conversion
|
||||
if (commonSuperclass.getType().equals(originalType.type.getType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return isNarrowingConversion(originalType, registerType);
|
||||
}
|
||||
} catch (UnresolvedClassException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -1216,16 +1211,47 @@ public class MethodAnalyzer {
|
||||
private void analyzeIfEqzNez(@Nonnull AnalyzedInstruction analyzedInstruction) {
|
||||
int instructionIndex = analyzedInstruction.getInstructionIndex();
|
||||
if (instructionIndex > 0) {
|
||||
AnalyzedInstruction prevAnalyzedInstruction = analyzedInstructions.valueAt(instructionIndex - 1);
|
||||
if (prevAnalyzedInstruction.instruction != null &&
|
||||
prevAnalyzedInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF) {
|
||||
if (analyzedInstruction.getPredecessorCount() != 1) {
|
||||
return;
|
||||
}
|
||||
AnalyzedInstruction prevAnalyzedInstruction = analyzedInstruction.getPredecessors().first();
|
||||
if (prevAnalyzedInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF) {
|
||||
if (canNarrowAfterInstanceOf(prevAnalyzedInstruction, analyzedInstruction, classPath)) {
|
||||
// Propagate the original type to the failing branch, and the new type to the successful branch
|
||||
int narrowingRegister = ((Instruction22c)prevAnalyzedInstruction.instruction).getRegisterB();
|
||||
RegisterType originalType = analyzedInstruction.getPreInstructionRegisterType(narrowingRegister);
|
||||
List<Integer> narrowingRegisters = Lists.newArrayList();
|
||||
|
||||
RegisterType newType = RegisterType.getRegisterType(classPath,
|
||||
(TypeReference)((Instruction22c)prevAnalyzedInstruction.instruction).getReference());
|
||||
|
||||
if (instructionIndex > 1) {
|
||||
// If we have something like:
|
||||
// move-object/from16 v0, p1
|
||||
// instance-of v2, v0, Lblah;
|
||||
// if-eqz v2, :target
|
||||
// Then we need to narrow both v0 AND p1
|
||||
AnalyzedInstruction prevPrevAnalyzedInstruction =
|
||||
analyzedInstructions.valueAt(instructionIndex - 2);
|
||||
Opcode opcode = prevPrevAnalyzedInstruction.instruction.getOpcode();
|
||||
if (opcode == Opcode.MOVE_OBJECT || opcode == Opcode.MOVE_OBJECT_16 ||
|
||||
opcode == Opcode.MOVE_OBJECT_FROM16) {
|
||||
TwoRegisterInstruction moveInstruction =
|
||||
((TwoRegisterInstruction)prevPrevAnalyzedInstruction.instruction);
|
||||
RegisterType originalType =
|
||||
prevPrevAnalyzedInstruction.getPostInstructionRegisterType(
|
||||
moveInstruction.getRegisterB());
|
||||
if (originalType.type != null) {
|
||||
if (isNarrowingConversion(originalType, newType)) {
|
||||
narrowingRegisters.add(
|
||||
((TwoRegisterInstruction)prevPrevAnalyzedInstruction.instruction).getRegisterB());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate the original type to the failing branch, and the new type to the successful branch
|
||||
int narrowingRegister = ((Instruction22c)prevAnalyzedInstruction.instruction).getRegisterB();
|
||||
narrowingRegisters.add(narrowingRegister);
|
||||
RegisterType originalType = analyzedInstruction.getPreInstructionRegisterType(narrowingRegister);
|
||||
|
||||
AnalyzedInstruction fallthroughInstruction = analyzedInstructions.valueAt(
|
||||
analyzedInstruction.getInstructionIndex() + 1);
|
||||
|
||||
@ -1233,16 +1259,18 @@ public class MethodAnalyzer {
|
||||
((Instruction21t)analyzedInstruction.instruction).getCodeOffset();
|
||||
AnalyzedInstruction branchInstruction = analyzedInstructions.get(nextAddress);
|
||||
|
||||
if (analyzedInstruction.instruction.getOpcode() == Opcode.IF_EQZ) {
|
||||
overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction,
|
||||
narrowingRegister, newType);
|
||||
overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
|
||||
narrowingRegister, originalType);
|
||||
} else {
|
||||
overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction,
|
||||
narrowingRegister, originalType);
|
||||
overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
|
||||
narrowingRegister, newType);
|
||||
for (int register: narrowingRegisters) {
|
||||
if (analyzedInstruction.instruction.getOpcode() == Opcode.IF_EQZ) {
|
||||
overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction,
|
||||
register, newType);
|
||||
overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
|
||||
register, originalType);
|
||||
} else {
|
||||
overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction,
|
||||
register, originalType);
|
||||
overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
|
||||
register, newType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1695,13 +1723,13 @@ public class MethodAnalyzer {
|
||||
|
||||
// fieldClass is now the first accessible class found. Now. we need to make sure that the field is
|
||||
// actually valid for this class
|
||||
resolvedField = classPath.getClass(fieldClass.getType()).getFieldByOffset(fieldOffset);
|
||||
if (resolvedField == null) {
|
||||
FieldReference newResolvedField = classPath.getClass(fieldClass.getType()).getFieldByOffset(fieldOffset);
|
||||
if (newResolvedField == null) {
|
||||
throw new ExceptionWithContext("Couldn't find accessible class while resolving field %s",
|
||||
ReferenceUtil.getShortFieldDescriptor(resolvedField));
|
||||
}
|
||||
resolvedField = new ImmutableFieldReference(fieldClass.getType(), resolvedField.getName(),
|
||||
resolvedField.getType());
|
||||
resolvedField = new ImmutableFieldReference(fieldClass.getType(), newResolvedField.getName(),
|
||||
newResolvedField.getType());
|
||||
}
|
||||
|
||||
String fieldType = resolvedField.getType();
|
||||
@ -1733,41 +1761,9 @@ public class MethodAnalyzer {
|
||||
targetMethod = (MethodReference)instruction.getReference();
|
||||
}
|
||||
|
||||
TypeProto typeProto = classPath.getClass(targetMethod.getDefiningClass());
|
||||
int methodIndex;
|
||||
try {
|
||||
methodIndex = typeProto.findMethodIndexInVtable(targetMethod);
|
||||
} catch (UnresolvedClassException ex) {
|
||||
return true;
|
||||
}
|
||||
MethodReference replacementMethod = normalizeMethodReference(targetMethod);
|
||||
|
||||
if (methodIndex < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Method replacementMethod = typeProto.getMethodByVtableIndex(methodIndex);
|
||||
assert replacementMethod != null;
|
||||
while (true) {
|
||||
String superType = typeProto.getSuperclass();
|
||||
if (superType == null) {
|
||||
break;
|
||||
}
|
||||
typeProto = classPath.getClass(superType);
|
||||
Method resolvedMethod = typeProto.getMethodByVtableIndex(methodIndex);
|
||||
if (resolvedMethod == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!resolvedMethod.equals(replacementMethod)) {
|
||||
if (!AnalyzedMethodUtil.canAccess(typeProto, replacementMethod, true, true, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
replacementMethod = resolvedMethod;
|
||||
}
|
||||
}
|
||||
|
||||
if (replacementMethod.equals(method)) {
|
||||
if (replacementMethod == null || replacementMethod.equals(targetMethod)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1839,7 +1835,9 @@ public class MethodAnalyzer {
|
||||
// no need to check class access for invoke-super. A class can obviously access its superclass.
|
||||
ClassDef thisClass = classPath.getClassDef(method.getDefiningClass());
|
||||
|
||||
if (!isSuper && !TypeUtils.canAccessClass(
|
||||
if (classPath.getClass(resolvedMethod.getDefiningClass()).isInterface()) {
|
||||
resolvedMethod = new ReparentedMethodReference(resolvedMethod, objectRegisterTypeProto.getType());
|
||||
} else if (!isSuper && !TypeUtils.canAccessClass(
|
||||
thisClass.getType(), classPath.getClassDef(resolvedMethod.getDefiningClass()))) {
|
||||
|
||||
// the class is not accessible. So we start looking at objectRegisterTypeProto (which may be different
|
||||
@ -1860,13 +1858,20 @@ public class MethodAnalyzer {
|
||||
MethodReference newResolvedMethod =
|
||||
classPath.getClass(methodClass.getType()).getMethodByVtableIndex(methodIndex);
|
||||
if (newResolvedMethod == null) {
|
||||
// TODO: fix NPE here
|
||||
throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s",
|
||||
ReferenceUtil.getMethodDescriptor(resolvedMethod, true));
|
||||
}
|
||||
resolvedMethod = newResolvedMethod;
|
||||
resolvedMethod = new ImmutableMethodReference(methodClass.getType(), resolvedMethod.getName(),
|
||||
resolvedMethod.getParameterTypes(), resolvedMethod.getReturnType());
|
||||
|
||||
}
|
||||
|
||||
if (normalizeVirtualMethods) {
|
||||
MethodReference replacementMethod = normalizeMethodReference(resolvedMethod);
|
||||
if (replacementMethod != null) {
|
||||
resolvedMethod = replacementMethod;
|
||||
}
|
||||
}
|
||||
|
||||
Instruction deodexedInstruction;
|
||||
@ -1967,4 +1972,70 @@ public class MethodAnalyzer {
|
||||
"pair because it is the last register.", registerNumber));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MethodReference normalizeMethodReference(@Nonnull MethodReference methodRef) {
|
||||
TypeProto typeProto = classPath.getClass(methodRef.getDefiningClass());
|
||||
int methodIndex;
|
||||
try {
|
||||
methodIndex = typeProto.findMethodIndexInVtable(methodRef);
|
||||
} catch (UnresolvedClassException ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (methodIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClassProto thisClass = (ClassProto)classPath.getClass(method.getDefiningClass());
|
||||
|
||||
Method replacementMethod = typeProto.getMethodByVtableIndex(methodIndex);
|
||||
assert replacementMethod != null;
|
||||
while (true) {
|
||||
String superType = typeProto.getSuperclass();
|
||||
if (superType == null) {
|
||||
break;
|
||||
}
|
||||
typeProto = classPath.getClass(superType);
|
||||
Method resolvedMethod = typeProto.getMethodByVtableIndex(methodIndex);
|
||||
if (resolvedMethod == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!resolvedMethod.equals(replacementMethod)) {
|
||||
if (!AnalyzedMethodUtil.canAccess(thisClass, resolvedMethod, false, false, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
replacementMethod = resolvedMethod;
|
||||
}
|
||||
}
|
||||
return replacementMethod;
|
||||
}
|
||||
|
||||
private static class ReparentedMethodReference extends BaseMethodReference {
|
||||
private final MethodReference baseReference;
|
||||
private final String definingClass;
|
||||
|
||||
public ReparentedMethodReference(MethodReference baseReference, String definingClass) {
|
||||
this.baseReference = baseReference;
|
||||
this.definingClass = definingClass;
|
||||
}
|
||||
|
||||
@Override @Nonnull public String getName() {
|
||||
return baseReference.getName();
|
||||
}
|
||||
|
||||
@Override @Nonnull public List<? extends CharSequence> getParameterTypes() {
|
||||
return baseReference.getParameterTypes();
|
||||
}
|
||||
|
||||
@Override @Nonnull public String getReturnType() {
|
||||
return baseReference.getReturnType();
|
||||
}
|
||||
|
||||
@Nonnull @Override public String getDefiningClass() {
|
||||
return definingClass;
|
||||
}
|
||||
}
|
||||
}
|
@ -235,7 +235,7 @@ public class RegisterType {
|
||||
case '[':
|
||||
return getRegisterType(REFERENCE, classPath.getClass(type));
|
||||
default:
|
||||
throw new ExceptionWithContext("Invalid type: " + type);
|
||||
throw new AnalysisException("Invalid type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,12 +31,43 @@
|
||||
|
||||
package org.jf.dexlib2.analysis.reflection.util;
|
||||
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
|
||||
public class ReflectionUtils {
|
||||
|
||||
private static ImmutableBiMap<String, String> primitiveMap = ImmutableBiMap.<String, String>builder()
|
||||
.put("boolean", "Z")
|
||||
.put("int", "I")
|
||||
.put("long", "J")
|
||||
.put("double", "D")
|
||||
.put("void", "V")
|
||||
.put("float", "F")
|
||||
.put("char", "C")
|
||||
.put("short", "S")
|
||||
.put("byte", "B")
|
||||
.build();
|
||||
|
||||
public static String javaToDexName(String javaName) {
|
||||
javaName = javaName.replace('.', '/');
|
||||
if (javaName.length() > 1 && javaName.charAt(javaName.length()-1) != ';') {
|
||||
javaName = 'L' + javaName + ';';
|
||||
if (javaName.charAt(0) == '[') {
|
||||
return javaName.replace('.', '/');
|
||||
}
|
||||
return javaName;
|
||||
|
||||
if (primitiveMap.containsKey(javaName)) {
|
||||
return primitiveMap.get(javaName);
|
||||
}
|
||||
|
||||
return 'L' + javaName.replace('.', '/') + ';';
|
||||
}
|
||||
|
||||
public static String dexToJavaName(String dexName) {
|
||||
if (dexName.charAt(0) == '[') {
|
||||
return dexName.replace('/', '.');
|
||||
}
|
||||
|
||||
if (primitiveMap.inverse().containsKey(dexName)) {
|
||||
return primitiveMap.inverse().get(dexName);
|
||||
}
|
||||
|
||||
return dexName.replace('/', '.').substring(1, dexName.length()-2);
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
package org.jf.dexlib2.base.reference;
|
||||
|
||||
import org.jf.dexlib2.iface.reference.FieldReference;
|
||||
import org.jf.dexlib2.util.ReferenceUtil;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@ -64,4 +65,8 @@ public abstract class BaseFieldReference implements FieldReference {
|
||||
if (res != 0) return res;
|
||||
return getType().compareTo(o.getType());
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return ReferenceUtil.getFieldDescriptor(this);
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ package org.jf.dexlib2.base.reference;
|
||||
|
||||
import com.google.common.collect.Ordering;
|
||||
import org.jf.dexlib2.iface.reference.MethodProtoReference;
|
||||
import org.jf.dexlib2.util.ReferenceUtil;
|
||||
import org.jf.util.CharSequenceUtils;
|
||||
import org.jf.util.CollectionUtils;
|
||||
|
||||
@ -63,4 +64,8 @@ public abstract class BaseMethodProtoReference implements MethodProtoReference {
|
||||
if (res != 0) return res;
|
||||
return CollectionUtils.compareAsIterable(Ordering.usingToString(), getParameterTypes(), o.getParameterTypes());
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return ReferenceUtil.getMethodProtoDescriptor(this);
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ package org.jf.dexlib2.base.reference;
|
||||
|
||||
import com.google.common.collect.Ordering;
|
||||
import org.jf.dexlib2.iface.reference.MethodReference;
|
||||
import org.jf.dexlib2.util.ReferenceUtil;
|
||||
import org.jf.util.CharSequenceUtils;
|
||||
import org.jf.util.CollectionUtils;
|
||||
|
||||
@ -70,4 +71,8 @@ public abstract class BaseMethodReference implements MethodReference {
|
||||
if (res != 0) return res;
|
||||
return CollectionUtils.compareAsIterable(Ordering.usingToString(), getParameterTypes(), o.getParameterTypes());
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return ReferenceUtil.getMethodDescriptor(this);
|
||||
}
|
||||
}
|
||||
|
@ -58,5 +58,5 @@ public abstract class BaseStringReference implements StringReference {
|
||||
@Override public int length() { return getString().length(); }
|
||||
@Override public char charAt(int index) { return getString().charAt(index); }
|
||||
@Override public CharSequence subSequence(int start, int end) { return getString().subSequence(start, end); }
|
||||
@Override public String toString() { return getString(); }
|
||||
@Override @Nonnull public String toString() { return getString(); }
|
||||
}
|
||||
|
@ -33,9 +33,15 @@ package org.jf.dexlib2.dexbacked;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import org.jf.dexlib2.Opcodes;
|
||||
import org.jf.dexlib2.ReferenceType;
|
||||
import org.jf.dexlib2.dexbacked.raw.*;
|
||||
import org.jf.dexlib2.dexbacked.reference.DexBackedFieldReference;
|
||||
import org.jf.dexlib2.dexbacked.reference.DexBackedMethodReference;
|
||||
import org.jf.dexlib2.dexbacked.reference.DexBackedStringReference;
|
||||
import org.jf.dexlib2.dexbacked.reference.DexBackedTypeReference;
|
||||
import org.jf.dexlib2.dexbacked.util.FixedSizeSet;
|
||||
import org.jf.dexlib2.iface.DexFile;
|
||||
import org.jf.dexlib2.iface.reference.Reference;
|
||||
import org.jf.util.ExceptionWithContext;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@ -43,6 +49,8 @@ import javax.annotation.Nullable;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.AbstractList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
|
||||
@ -61,7 +69,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
|
||||
private final int classCount;
|
||||
private final int classStartOffset;
|
||||
|
||||
private DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
|
||||
protected DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
|
||||
super(buf, offset);
|
||||
|
||||
this.opcodes = opcodes;
|
||||
@ -85,7 +93,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
|
||||
}
|
||||
|
||||
public DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull BaseDexBuffer buf) {
|
||||
this(opcodes, buf.buf);
|
||||
this(opcodes, buf.buf, buf.baseOffset);
|
||||
}
|
||||
|
||||
public DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, int offset) {
|
||||
@ -96,6 +104,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
|
||||
this(opcodes, buf, 0, true);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static DexBackedDexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is)
|
||||
throws IOException {
|
||||
if (!is.markSupported()) {
|
||||
@ -148,7 +157,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
|
||||
};
|
||||
}
|
||||
|
||||
private static void verifyMagicAndByteOrder(@Nonnull byte[] buf, int offset) {
|
||||
protected static void verifyMagicAndByteOrder(@Nonnull byte[] buf, int offset) {
|
||||
if (!HeaderItem.verifyMagic(buf, offset)) {
|
||||
StringBuilder sb = new StringBuilder("Invalid magic value:");
|
||||
for (int i=0; i<8; i++) {
|
||||
@ -265,6 +274,81 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
|
||||
return getType(typeIndex);
|
||||
}
|
||||
|
||||
public List<DexBackedStringReference> getStrings() {
|
||||
return new AbstractList<DexBackedStringReference>() {
|
||||
@Override public DexBackedStringReference get(int index) {
|
||||
if (index < 0 || index >= getStringCount()) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return new DexBackedStringReference(DexBackedDexFile.this, index);
|
||||
}
|
||||
|
||||
@Override public int size() {
|
||||
return getStringCount();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public List<DexBackedTypeReference> getTypes() {
|
||||
return new AbstractList<DexBackedTypeReference>() {
|
||||
@Override public DexBackedTypeReference get(int index) {
|
||||
if (index < 0 || index >= getTypeCount()) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return new DexBackedTypeReference(DexBackedDexFile.this, index);
|
||||
}
|
||||
|
||||
@Override public int size() {
|
||||
return getTypeCount();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public List<DexBackedMethodReference> getMethods() {
|
||||
return new AbstractList<DexBackedMethodReference>() {
|
||||
@Override public DexBackedMethodReference get(int index) {
|
||||
if (index < 0 || index >= getMethodCount()) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return new DexBackedMethodReference(DexBackedDexFile.this, index);
|
||||
}
|
||||
|
||||
@Override public int size() {
|
||||
return getMethodCount();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public List<DexBackedFieldReference> getFields() {
|
||||
return new AbstractList<DexBackedFieldReference>() {
|
||||
@Override public DexBackedFieldReference get(int index) {
|
||||
if (index < 0 || index >= getFieldCount()) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return new DexBackedFieldReference(DexBackedDexFile.this, index);
|
||||
}
|
||||
|
||||
@Override public int size() {
|
||||
return getFieldCount();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public List<? extends Reference> getReferences(int referenceType) {
|
||||
switch (referenceType) {
|
||||
case ReferenceType.STRING:
|
||||
return getStrings();
|
||||
case ReferenceType.TYPE:
|
||||
return getTypes();
|
||||
case ReferenceType.METHOD:
|
||||
return getMethods();
|
||||
case ReferenceType.FIELD:
|
||||
return getFields();
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Invalid reference type: %d", referenceType));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public DexReader readerAt(int offset) {
|
||||
|
@ -129,7 +129,11 @@ public class DexBackedMethodImplementation implements MethodImplementation {
|
||||
return DebugInfo.newOrEmpty(dexFile, 0, this);
|
||||
}
|
||||
if (debugOffset < 0) {
|
||||
System.err.println("%s: Invalid debug offset");
|
||||
System.err.println(String.format("%s: Invalid debug offset", method));
|
||||
return DebugInfo.newOrEmpty(dexFile, 0, this);
|
||||
}
|
||||
if (debugOffset >= dexFile.buf.length) {
|
||||
System.err.println(String.format("%s: Invalid debug offset", method));
|
||||
return DebugInfo.newOrEmpty(dexFile, 0, this);
|
||||
}
|
||||
return DebugInfo.newOrEmpty(dexFile, debugOffset, this);
|
||||
|
@ -31,22 +31,29 @@
|
||||
|
||||
package org.jf.dexlib2.dexbacked;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import org.jf.dexlib2.Opcodes;
|
||||
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
|
||||
import org.jf.dexlib2.dexbacked.OatFile.SymbolTable.Symbol;
|
||||
import org.jf.dexlib2.dexbacked.raw.HeaderItem;
|
||||
import org.jf.dexlib2.iface.MultiDexContainer;
|
||||
import org.jf.util.AbstractForwardSequentialList;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.AbstractList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class OatFile extends BaseDexBuffer {
|
||||
public class OatFile extends BaseDexBuffer implements MultiDexContainer<OatDexFile> {
|
||||
private static final byte[] ELF_MAGIC = new byte[] { 0x7f, 'E', 'L', 'F' };
|
||||
private static final byte[] OAT_MAGIC = new byte[] { 'o', 'a', 't', '\n' };
|
||||
private static final int MIN_ELF_HEADER_SIZE = 52;
|
||||
@ -54,7 +61,7 @@ public class OatFile extends BaseDexBuffer {
|
||||
// These are the "known working" versions that I have manually inspected the source for.
|
||||
// Later version may or may not work, depending on what changed.
|
||||
private static final int MIN_OAT_VERSION = 56;
|
||||
private static final int MAX_OAT_VERSION = 71;
|
||||
private static final int MAX_OAT_VERSION = 86;
|
||||
|
||||
public static final int UNSUPPORTED = 0;
|
||||
public static final int SUPPORTED = 1;
|
||||
@ -148,6 +155,18 @@ public class OatFile extends BaseDexBuffer {
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<String> getBootClassPath() {
|
||||
if (getOatVersion() < 75) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
String bcp = oatHeader.getKeyValue("bootclasspath");
|
||||
if (bcp == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
return Arrays.asList(bcp.split(":"));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<OatDexFile> getDexFiles() {
|
||||
return new AbstractForwardSequentialList<OatDexFile>() {
|
||||
@ -156,44 +175,44 @@ public class OatFile extends BaseDexBuffer {
|
||||
}
|
||||
|
||||
@Nonnull @Override public Iterator<OatDexFile> iterator() {
|
||||
return new Iterator<OatDexFile>() {
|
||||
int index = 0;
|
||||
int offset = oatHeader.getDexListStart();
|
||||
|
||||
@Override public boolean hasNext() {
|
||||
return index < size();
|
||||
return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, OatDexFile>() {
|
||||
@Nullable @Override public OatDexFile apply(DexEntry dexEntry) {
|
||||
return dexEntry.getDexFile();
|
||||
}
|
||||
|
||||
@Override public OatDexFile next() {
|
||||
int filenameLength = readSmallUint(offset);
|
||||
offset += 4;
|
||||
|
||||
// TODO: what is the correct character encoding?
|
||||
String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII"));
|
||||
offset += filenameLength;
|
||||
|
||||
offset += 4; // checksum
|
||||
|
||||
int dexOffset = readSmallUint(offset) + oatHeader.offset;
|
||||
offset += 4;
|
||||
|
||||
int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET);
|
||||
offset += 4 * classCount;
|
||||
|
||||
index++;
|
||||
|
||||
return new OatDexFile(dexOffset, filename);
|
||||
}
|
||||
|
||||
@Override public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public class OatDexFile extends DexBackedDexFile {
|
||||
@Nonnull @Override public List<String> getDexEntryNames() throws IOException {
|
||||
return new AbstractForwardSequentialList<String>() {
|
||||
@Override public int size() {
|
||||
return oatHeader.getDexFileCount();
|
||||
}
|
||||
|
||||
@Nonnull @Override public Iterator<String> iterator() {
|
||||
return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, String>() {
|
||||
@Nullable @Override public String apply(DexEntry dexEntry) {
|
||||
return dexEntry.entryName;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable @Override public OatDexFile getEntry(@Nonnull String entryName) throws IOException {
|
||||
DexEntryIterator iterator = new DexEntryIterator();
|
||||
while (iterator.hasNext()) {
|
||||
DexEntry entry = iterator.next();
|
||||
|
||||
if (entry.entryName.equals(entryName)) {
|
||||
return entry.getDexFile();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public class OatDexFile extends DexBackedDexFile implements MultiDexContainer.MultiDexFile {
|
||||
@Nonnull public final String filename;
|
||||
|
||||
public OatDexFile(int offset, @Nonnull String filename) {
|
||||
@ -201,8 +220,12 @@ public class OatFile extends BaseDexBuffer {
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
public int getOatVersion() {
|
||||
return OatFile.this.getOatVersion();
|
||||
@Nonnull @Override public String getEntryName() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
@Nonnull @Override public OatFile getContainer() {
|
||||
return OatFile.this;
|
||||
}
|
||||
|
||||
@Override public boolean hasOdexOpcodes() {
|
||||
@ -211,57 +234,87 @@ public class OatFile extends BaseDexBuffer {
|
||||
}
|
||||
|
||||
private class OatHeader {
|
||||
private final int offset;
|
||||
private final int headerOffset;
|
||||
|
||||
public OatHeader(int offset) {
|
||||
this.offset = offset;
|
||||
this.headerOffset = offset;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
for (int i=0; i<OAT_MAGIC.length; i++) {
|
||||
if (buf[offset + i] != OAT_MAGIC[i]) {
|
||||
if (buf[headerOffset + i] != OAT_MAGIC[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=4; i<7; i++) {
|
||||
if (buf[offset + i] < '0' || buf[offset + i] > '9') {
|
||||
if (buf[headerOffset + i] < '0' || buf[headerOffset + i] > '9') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return buf[offset + 7] == 0;
|
||||
return buf[headerOffset + 7] == 0;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return Integer.valueOf(new String(buf, offset + 4, 3));
|
||||
return Integer.valueOf(new String(buf, headerOffset + 4, 3));
|
||||
}
|
||||
|
||||
public int getDexFileCount() {
|
||||
return readSmallUint(offset + 20);
|
||||
return readSmallUint(headerOffset + 20);
|
||||
}
|
||||
|
||||
public int getKeyValueStoreSize() {
|
||||
int version = getVersion();
|
||||
if (version < 56) {
|
||||
if (getVersion() < MIN_OAT_VERSION) {
|
||||
throw new IllegalStateException("Unsupported oat version");
|
||||
}
|
||||
int fieldOffset = 17 * 4;
|
||||
return readSmallUint(offset + fieldOffset);
|
||||
return readSmallUint(headerOffset + fieldOffset);
|
||||
}
|
||||
|
||||
public int getHeaderSize() {
|
||||
int version = getVersion();
|
||||
if (version >= 56) {
|
||||
return 18*4 + getKeyValueStoreSize();
|
||||
} else {
|
||||
if (getVersion() < MIN_OAT_VERSION) {
|
||||
throw new IllegalStateException("Unsupported oat version");
|
||||
}
|
||||
return 18*4 + getKeyValueStoreSize();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getKeyValue(@Nonnull String key) {
|
||||
int size = getKeyValueStoreSize();
|
||||
|
||||
int offset = headerOffset + 18 * 4;
|
||||
int endOffset = offset + size;
|
||||
|
||||
while (offset < endOffset) {
|
||||
int keyStartOffset = offset;
|
||||
while (offset < endOffset && buf[offset] != '\0') {
|
||||
offset++;
|
||||
}
|
||||
if (offset >= endOffset) {
|
||||
throw new InvalidOatFileException("Oat file contains truncated key value store");
|
||||
}
|
||||
int keyEndOffset = offset;
|
||||
|
||||
String k = new String(buf, keyStartOffset, keyEndOffset - keyStartOffset);
|
||||
if (k.equals(key)) {
|
||||
int valueStartOffset = ++offset;
|
||||
while (offset < endOffset && buf[offset] != '\0') {
|
||||
offset++;
|
||||
}
|
||||
if (offset >= endOffset) {
|
||||
throw new InvalidOatFileException("Oat file contains truncated key value store");
|
||||
}
|
||||
int valueEndOffset = offset;
|
||||
return new String(buf, valueStartOffset, valueEndOffset - valueStartOffset);
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getDexListStart() {
|
||||
return offset + getHeaderSize();
|
||||
return headerOffset + getHeaderSize();
|
||||
}
|
||||
}
|
||||
|
||||
@ -481,7 +534,64 @@ public class OatFile extends BaseDexBuffer {
|
||||
|
||||
return new String(buf, start, end-start, Charset.forName("US-ASCII"));
|
||||
}
|
||||
}
|
||||
|
||||
private class DexEntry {
|
||||
public final String entryName;
|
||||
public final int dexOffset;
|
||||
|
||||
public DexEntry(String entryName, int dexOffset) {
|
||||
this.entryName = entryName;
|
||||
this.dexOffset = dexOffset;
|
||||
}
|
||||
|
||||
public OatDexFile getDexFile() {
|
||||
return new OatDexFile(dexOffset, entryName);
|
||||
}
|
||||
}
|
||||
|
||||
private class DexEntryIterator implements Iterator<DexEntry> {
|
||||
int index = 0;
|
||||
int offset = oatHeader.getDexListStart();
|
||||
|
||||
@Override public boolean hasNext() {
|
||||
return index < oatHeader.getDexFileCount();
|
||||
}
|
||||
|
||||
@Override public DexEntry next() {
|
||||
int filenameLength = readSmallUint(offset);
|
||||
offset += 4;
|
||||
|
||||
// TODO: what is the correct character encoding?
|
||||
String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII"));
|
||||
offset += filenameLength;
|
||||
|
||||
offset += 4; // checksum
|
||||
|
||||
int dexOffset = readSmallUint(offset) + oatHeader.headerOffset;
|
||||
offset += 4;
|
||||
|
||||
|
||||
if (getOatVersion() >= 75) {
|
||||
offset += 4; // offset to class offsets table
|
||||
}
|
||||
if (getOatVersion() >= 73) {
|
||||
offset += 4; // lookup table offset
|
||||
}
|
||||
if (getOatVersion() < 75) {
|
||||
// prior to 75, the class offsets are included here directly
|
||||
int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET);
|
||||
offset += 4 * classCount;
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
return new DexEntry(filename, dexOffset);
|
||||
}
|
||||
|
||||
@Override public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidOatFileException extends RuntimeException {
|
||||
@ -493,4 +603,5 @@ public class OatFile extends BaseDexBuffer {
|
||||
public static class NotAnOatFileException extends RuntimeException {
|
||||
public NotAnOatFileException() {}
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -45,18 +45,6 @@ public class ImmutableDexFile implements DexFile {
|
||||
@Nonnull protected final ImmutableSet<? extends ImmutableClassDef> classes;
|
||||
@Nonnull private final Opcodes opcodes;
|
||||
|
||||
@Deprecated
|
||||
public ImmutableDexFile(@Nullable Collection<? extends ClassDef> classes) {
|
||||
this.classes = ImmutableClassDef.immutableSetOf(classes);
|
||||
this.opcodes = Opcodes.forApi(19);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public ImmutableDexFile(@Nullable ImmutableSet<? extends ImmutableClassDef> classes) {
|
||||
this.classes = ImmutableUtils.nullToEmptySet(classes);
|
||||
this.opcodes = Opcodes.forApi(19);
|
||||
}
|
||||
|
||||
public ImmutableDexFile(@Nonnull Opcodes opcodes, @Nullable Collection<? extends ClassDef> classes) {
|
||||
this.classes = ImmutableClassDef.immutableSetOf(classes);
|
||||
this.opcodes = opcodes;
|
||||
|
@ -192,12 +192,12 @@ public abstract class DexWriter<
|
||||
|
||||
private int getDataSectionOffset() {
|
||||
return HeaderItem.ITEM_SIZE +
|
||||
stringSection.getItems().size() * StringIdItem.ITEM_SIZE +
|
||||
typeSection.getItems().size() * TypeIdItem.ITEM_SIZE +
|
||||
protoSection.getItems().size() * ProtoIdItem.ITEM_SIZE +
|
||||
fieldSection.getItems().size() * FieldIdItem.ITEM_SIZE +
|
||||
methodSection.getItems().size() * MethodIdItem.ITEM_SIZE +
|
||||
classSection.getItems().size() * ClassDefItem.ITEM_SIZE;
|
||||
stringSection.getItemCount() * StringIdItem.ITEM_SIZE +
|
||||
typeSection.getItemCount() * TypeIdItem.ITEM_SIZE +
|
||||
protoSection.getItemCount() * ProtoIdItem.ITEM_SIZE +
|
||||
fieldSection.getItemCount() * FieldIdItem.ITEM_SIZE +
|
||||
methodSection.getItemCount() * MethodIdItem.ITEM_SIZE +
|
||||
classSection.getItemCount() * ClassDefItem.ITEM_SIZE;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@ -227,6 +227,22 @@ public abstract class DexWriter<
|
||||
return classReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether any of the size-sensitive constant pools have overflowed.
|
||||
*
|
||||
* This checks whether the type, method, field pools are larger than 64k entries.
|
||||
*
|
||||
* Note that even if this returns true, it may still be possible to successfully write the dex file, if the
|
||||
* overflowed items are not referenced anywhere that uses a 16-bit index
|
||||
*
|
||||
* @return true if any of the size-sensitive constant pools have overflowed
|
||||
*/
|
||||
public boolean hasOverflowed() {
|
||||
return methodSection.getItemCount() > (1 << 16) ||
|
||||
typeSection.getItemCount() > (1 << 16) ||
|
||||
fieldSection.getItemCount() > (1 << 16);
|
||||
}
|
||||
|
||||
public void writeTo(@Nonnull DexDataStore dest) throws IOException {
|
||||
this.writeTo(dest, MemoryDeferredOutputStream.getFactory());
|
||||
}
|
||||
|
@ -38,4 +38,5 @@ import java.util.Map;
|
||||
public interface IndexSection<Key> {
|
||||
int getItemIndex(@Nonnull Key key);
|
||||
@Nonnull Collection<? extends Map.Entry<? extends Key, Integer>> getItems();
|
||||
int getItemCount();
|
||||
}
|
||||
|
@ -443,4 +443,8 @@ public class BuilderClassPool implements ClassSection<BuilderStringReference, Bu
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override public int getItemCount() {
|
||||
return internedItems.size();
|
||||
}
|
||||
}
|
||||
|
@ -104,4 +104,8 @@ public class BuilderFieldPool
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override public int getItemCount() {
|
||||
return internedItems.size();
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,10 @@ class BuilderMethodPool implements MethodSection<BuilderStringReference, Builder
|
||||
};
|
||||
}
|
||||
|
||||
@Override public int getItemCount() {
|
||||
return internedItems.size();
|
||||
}
|
||||
|
||||
private static class MethodKey extends BaseMethodReference implements MethodReference {
|
||||
@Nonnull private final String definingClass;
|
||||
@Nonnull private final String name;
|
||||
|
@ -103,4 +103,8 @@ class BuilderProtoPool
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override public int getItemCount() {
|
||||
return internedItems.size();
|
||||
}
|
||||
}
|
||||
|
@ -86,4 +86,8 @@ class BuilderStringPool implements StringSection<BuilderStringReference, Builder
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override public int getItemCount() {
|
||||
return internedItems.size();
|
||||
}
|
||||
}
|
||||
|
@ -92,4 +92,8 @@ class BuilderTypePool implements TypeSection<BuilderStringReference, BuilderType
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override public int getItemCount() {
|
||||
return internedItems.size();
|
||||
}
|
||||
}
|
||||
|
@ -60,18 +60,6 @@ public class DexBuilder extends DexWriter<BuilderStringReference, BuilderStringR
|
||||
|
||||
@Nonnull private final BuilderContext context;
|
||||
|
||||
@Nonnull public static DexBuilder makeDexBuilder() {
|
||||
BuilderContext context = new BuilderContext();
|
||||
return new DexBuilder(Opcodes.forApi(20), context);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Nonnull
|
||||
public static DexBuilder makeDexBuilder(int api) {
|
||||
BuilderContext context = new BuilderContext();
|
||||
return new DexBuilder(Opcodes.forApi(api), context);
|
||||
}
|
||||
|
||||
@Nonnull public static DexBuilder makeDexBuilder(@Nonnull Opcodes opcodes) {
|
||||
BuilderContext context = new BuilderContext();
|
||||
return new DexBuilder(opcodes, context);
|
||||
|
@ -31,7 +31,6 @@
|
||||
|
||||
package org.jf.dexlib2.writer.pool;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.jf.dexlib2.writer.IndexSection;
|
||||
import org.jf.util.ExceptionWithContext;
|
||||
|
||||
@ -39,9 +38,7 @@ import javax.annotation.Nonnull;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class BaseIndexPool<Key> implements IndexSection<Key> {
|
||||
@Nonnull protected final Map<Key, Integer> internedItems = Maps.newHashMap();
|
||||
|
||||
public abstract class BaseIndexPool<Key> extends BasePool<Key, Integer> implements IndexSection<Key> {
|
||||
@Nonnull @Override public Collection<? extends Map.Entry<? extends Key, Integer>> getItems() {
|
||||
return internedItems.entrySet();
|
||||
}
|
||||
|
@ -31,7 +31,6 @@
|
||||
|
||||
package org.jf.dexlib2.writer.pool;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.jf.dexlib2.writer.OffsetSection;
|
||||
import org.jf.util.ExceptionWithContext;
|
||||
|
||||
@ -39,9 +38,7 @@ import javax.annotation.Nonnull;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class BaseOffsetPool<Key> implements OffsetSection<Key> {
|
||||
@Nonnull protected final Map<Key, Integer> internedItems = Maps.newHashMap();
|
||||
|
||||
public abstract class BaseOffsetPool<Key> extends BasePool<Key, Integer> implements OffsetSection<Key> {
|
||||
@Nonnull @Override public Collection<? extends Map.Entry<? extends Key, Integer>> getItems() {
|
||||
return internedItems.entrySet();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -58,11 +58,9 @@ import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class ClassPool implements ClassSection<CharSequence, CharSequence,
|
||||
public class ClassPool extends BasePool<String, PoolClassDef> implements ClassSection<CharSequence, CharSequence,
|
||||
TypeListPool.Key<? extends Collection<? extends CharSequence>>, PoolClassDef, Field, PoolMethod,
|
||||
Set<? extends Annotation>, EncodedValue> {
|
||||
@Nonnull private HashMap<String, PoolClassDef> internedItems = Maps.newHashMap();
|
||||
|
||||
@Nonnull private final StringPool stringPool;
|
||||
@Nonnull private final TypePool typePool;
|
||||
@Nonnull private final FieldPool fieldPool;
|
||||
|
@ -56,16 +56,11 @@ public class DexPool extends DexWriter<CharSequence, StringReference, CharSequen
|
||||
TypeListPool.Key<? extends Collection<? extends CharSequence>>, Field, PoolMethod,
|
||||
EncodedValue, AnnotationElement> {
|
||||
|
||||
@Nonnull
|
||||
public static DexPool makeDexPool() {
|
||||
return makeDexPool(Opcodes.forApi(20));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Nonnull
|
||||
public static DexPool makeDexPool(int api) {
|
||||
return makeDexPool(Opcodes.forApi(api));
|
||||
}
|
||||
private final Markable[] sections = new Markable[] {
|
||||
(Markable)stringSection, (Markable)typeSection, (Markable)protoSection, (Markable)fieldSection,
|
||||
(Markable)methodSection, (Markable)classSection, (Markable)typeListSection, (Markable)annotationSection,
|
||||
(Markable)annotationSetSection
|
||||
};
|
||||
|
||||
@Nonnull
|
||||
public static DexPool makeDexPool(@Nonnull Opcodes opcodes) {
|
||||
@ -84,29 +79,60 @@ public class DexPool extends DexWriter<CharSequence, StringReference, CharSequen
|
||||
annotationPool, annotationSetPool);
|
||||
}
|
||||
|
||||
private DexPool(Opcodes opcodes, StringPool stringPool, TypePool typePool, ProtoPool protoPool, FieldPool fieldPool,
|
||||
MethodPool methodPool, ClassPool classPool, TypeListPool typeListPool,
|
||||
AnnotationPool annotationPool, AnnotationSetPool annotationSetPool) {
|
||||
protected DexPool(Opcodes opcodes, StringPool stringPool, TypePool typePool, ProtoPool protoPool,
|
||||
FieldPool fieldPool, MethodPool methodPool, ClassPool classPool, TypeListPool typeListPool,
|
||||
AnnotationPool annotationPool, AnnotationSetPool annotationSetPool) {
|
||||
super(opcodes, stringPool, typePool, protoPool, fieldPool, methodPool,
|
||||
classPool, typeListPool, annotationPool, annotationSetPool);
|
||||
}
|
||||
|
||||
public static void writeTo(@Nonnull DexDataStore dataStore, @Nonnull org.jf.dexlib2.iface.DexFile input) throws IOException {
|
||||
DexPool dexPool = makeDexPool();
|
||||
public static void writeTo(@Nonnull DexDataStore dataStore, @Nonnull org.jf.dexlib2.iface.DexFile input)
|
||||
throws IOException {
|
||||
DexPool dexPool = makeDexPool(input.getOpcodes());
|
||||
for (ClassDef classDef: input.getClasses()) {
|
||||
((ClassPool)dexPool.classSection).intern(classDef);
|
||||
dexPool.internClass(classDef);
|
||||
}
|
||||
dexPool.writeTo(dataStore);
|
||||
}
|
||||
|
||||
public static void writeTo(@Nonnull String path, @Nonnull org.jf.dexlib2.iface.DexFile input) throws IOException {
|
||||
DexPool dexPool = makeDexPool();
|
||||
DexPool dexPool = makeDexPool(input.getOpcodes());
|
||||
for (ClassDef classDef: input.getClasses()) {
|
||||
((ClassPool)dexPool.classSection).intern(classDef);
|
||||
dexPool.internClass(classDef);
|
||||
}
|
||||
dexPool.writeTo(new FileDataStore(new File(path)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Interns a class into this DexPool
|
||||
* @param classDef The class to intern
|
||||
*/
|
||||
public void internClass(ClassDef classDef) {
|
||||
((ClassPool)classSection).intern(classDef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a marked state that can be returned to by calling reset()
|
||||
*
|
||||
* This is useful to rollback the last added class if it causes a method/field/type overflow
|
||||
*/
|
||||
public void mark() {
|
||||
for (Markable section: sections) {
|
||||
section.mark();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets to the last marked state
|
||||
*
|
||||
* This is useful to rollback the last added class if it causes a method/field/type overflow
|
||||
*/
|
||||
public void reset() {
|
||||
for (Markable section: sections) {
|
||||
section.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected void writeEncodedValue(@Nonnull InternalEncodedValueWriter writer,
|
||||
@Nonnull EncodedValue encodedValue) throws IOException {
|
||||
switch (encodedValue.getValueType()) {
|
||||
@ -165,7 +191,7 @@ public class DexPool extends DexWriter<CharSequence, StringReference, CharSequen
|
||||
}
|
||||
}
|
||||
|
||||
public static void internEncodedValue(@Nonnull EncodedValue encodedValue,
|
||||
static void internEncodedValue(@Nonnull EncodedValue encodedValue,
|
||||
@Nonnull StringPool stringPool,
|
||||
@Nonnull TypePool typePool,
|
||||
@Nonnull FieldPool fieldPool,
|
||||
|
@ -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();
|
||||
}
|
@ -31,7 +31,6 @@
|
||||
|
||||
package org.jf.dexlib2.writer.pool;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.jf.dexlib2.writer.DexWriter;
|
||||
import org.jf.dexlib2.writer.NullableIndexSection;
|
||||
import org.jf.util.ExceptionWithContext;
|
||||
@ -41,9 +40,8 @@ import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class StringTypeBasePool implements NullableIndexSection<CharSequence> {
|
||||
@Nonnull protected final Map<String, Integer> internedItems = Maps.newHashMap();
|
||||
|
||||
public abstract class StringTypeBasePool extends BasePool<String, Integer>
|
||||
implements NullableIndexSection<CharSequence>, Markable {
|
||||
@Nonnull @Override public Collection<Map.Entry<String, Integer>> getItems() {
|
||||
return internedItems.entrySet();
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ public class AccessorTest {
|
||||
public void testAccessors() throws IOException {
|
||||
URL url = AccessorTest.class.getClassLoader().getResource("accessorTest.dex");
|
||||
Assert.assertNotNull(url);
|
||||
DexFile f = DexFileFactory.loadDexFile(url.getFile(), 15, false);
|
||||
DexFile f = DexFileFactory.loadDexFile(url.getFile(), Opcodes.getDefault());
|
||||
|
||||
SyntheticAccessorResolver sar = new SyntheticAccessorResolver(f.getOpcodes(), f.getClasses());
|
||||
|
||||
|
227
dexlib2/src/test/java/org/jf/dexlib2/DexEntryFinderTest.java
Normal file
227
dexlib2/src/test/java/org/jf/dexlib2/DexEntryFinderTest.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -32,8 +32,10 @@
|
||||
package org.jf.dexlib2.analysis;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Lists;
|
||||
import junit.framework.Assert;
|
||||
import org.jf.dexlib2.Opcodes;
|
||||
import org.jf.dexlib2.iface.ClassDef;
|
||||
import org.jf.dexlib2.immutable.ImmutableDexFile;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -51,49 +53,53 @@ public class CommonSuperclassTest {
|
||||
// fivetwothree
|
||||
// fivethree
|
||||
|
||||
private final ClassPath classPath;
|
||||
private final ClassPath oldClassPath;
|
||||
private final ClassPath newClassPath;
|
||||
|
||||
|
||||
public CommonSuperclassTest() throws IOException {
|
||||
classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19),
|
||||
ImmutableSet.of(
|
||||
TestUtils.makeClassDef("Ljava/lang/Object;", null),
|
||||
TestUtils.makeClassDef("Ltest/one;", "Ljava/lang/Object;"),
|
||||
TestUtils.makeClassDef("Ltest/two;", "Ljava/lang/Object;"),
|
||||
TestUtils.makeClassDef("Ltest/onetwo;", "Ltest/one;"),
|
||||
TestUtils.makeClassDef("Ltest/onetwothree;", "Ltest/onetwo;"),
|
||||
TestUtils.makeClassDef("Ltest/onethree;", "Ltest/one;"),
|
||||
TestUtils.makeClassDef("Ltest/fivetwo;", "Ltest/five;"),
|
||||
TestUtils.makeClassDef("Ltest/fivetwothree;", "Ltest/fivetwo;"),
|
||||
TestUtils.makeClassDef("Ltest/fivethree;", "Ltest/five;"),
|
||||
TestUtils.makeInterfaceDef("Ljava/lang/Cloneable;"),
|
||||
TestUtils.makeInterfaceDef("Ljava/io/Serializable;"),
|
||||
ImmutableSet<ClassDef> classes = ImmutableSet.of(
|
||||
TestUtils.makeClassDef("Ljava/lang/Object;", null),
|
||||
TestUtils.makeClassDef("Ltest/one;", "Ljava/lang/Object;"),
|
||||
TestUtils.makeClassDef("Ltest/two;", "Ljava/lang/Object;"),
|
||||
TestUtils.makeClassDef("Ltest/onetwo;", "Ltest/one;"),
|
||||
TestUtils.makeClassDef("Ltest/onetwothree;", "Ltest/onetwo;"),
|
||||
TestUtils.makeClassDef("Ltest/onethree;", "Ltest/one;"),
|
||||
TestUtils.makeClassDef("Ltest/fivetwo;", "Ltest/five;"),
|
||||
TestUtils.makeClassDef("Ltest/fivetwothree;", "Ltest/fivetwo;"),
|
||||
TestUtils.makeClassDef("Ltest/fivethree;", "Ltest/five;"),
|
||||
TestUtils.makeInterfaceDef("Ljava/lang/Cloneable;"),
|
||||
TestUtils.makeInterfaceDef("Ljava/io/Serializable;"),
|
||||
|
||||
// basic class and interface
|
||||
TestUtils.makeClassDef("Liface/classiface1;", "Ljava/lang/Object;", "Liface/iface1;"),
|
||||
TestUtils.makeInterfaceDef("Liface/iface1;"),
|
||||
// basic class and interface
|
||||
TestUtils.makeClassDef("Liface/classiface1;", "Ljava/lang/Object;", "Liface/iface1;"),
|
||||
TestUtils.makeInterfaceDef("Liface/iface1;"),
|
||||
|
||||
// a more complex interface tree
|
||||
TestUtils.makeInterfaceDef("Liface/base1;"),
|
||||
// implements undefined interface
|
||||
TestUtils.makeInterfaceDef("Liface/sub1;", "Liface/base1;", "Liface/base2;"),
|
||||
// this implements sub1, so that its interfaces can't be fully resolved either
|
||||
TestUtils.makeInterfaceDef("Liface/sub2;", "Liface/base1;", "Liface/sub1;"),
|
||||
TestUtils.makeInterfaceDef("Liface/sub3;", "Liface/base1;"),
|
||||
TestUtils.makeInterfaceDef("Liface/sub4;", "Liface/base1;", "Liface/sub3;"),
|
||||
TestUtils.makeClassDef("Liface/classsub1;", "Ljava/lang/Object;", "Liface/sub1;"),
|
||||
TestUtils.makeClassDef("Liface/classsub2;", "Ljava/lang/Object;", "Liface/sub2;"),
|
||||
TestUtils.makeClassDef("Liface/classsub3;", "Ljava/lang/Object;", "Liface/sub3;",
|
||||
"Liface/base;"),
|
||||
TestUtils.makeClassDef("Liface/classsub4;", "Ljava/lang/Object;", "Liface/sub3;",
|
||||
"Liface/sub4;"),
|
||||
TestUtils.makeClassDef("Liface/classsubsub4;", "Liface/classsub4;"),
|
||||
TestUtils.makeClassDef("Liface/classsub1234;", "Ljava/lang/Object;", "Liface/sub1;",
|
||||
"Liface/sub2;", "Liface/sub3;", "Liface/sub4;")
|
||||
))));
|
||||
// a more complex interface tree
|
||||
TestUtils.makeInterfaceDef("Liface/base1;"),
|
||||
// implements undefined interface
|
||||
TestUtils.makeInterfaceDef("Liface/sub1;", "Liface/base1;", "Liface/base2;"),
|
||||
// this implements sub1, so that its interfaces can't be fully resolved either
|
||||
TestUtils.makeInterfaceDef("Liface/sub2;", "Liface/base1;", "Liface/sub1;"),
|
||||
TestUtils.makeInterfaceDef("Liface/sub3;", "Liface/base1;"),
|
||||
TestUtils.makeInterfaceDef("Liface/sub4;", "Liface/base1;", "Liface/sub3;"),
|
||||
TestUtils.makeClassDef("Liface/classsub1;", "Ljava/lang/Object;", "Liface/sub1;"),
|
||||
TestUtils.makeClassDef("Liface/classsub2;", "Ljava/lang/Object;", "Liface/sub2;"),
|
||||
TestUtils.makeClassDef("Liface/classsub3;", "Ljava/lang/Object;", "Liface/sub3;",
|
||||
"Liface/base;"),
|
||||
TestUtils.makeClassDef("Liface/classsub4;", "Ljava/lang/Object;", "Liface/sub3;",
|
||||
"Liface/sub4;"),
|
||||
TestUtils.makeClassDef("Liface/classsubsub4;", "Liface/classsub4;"),
|
||||
TestUtils.makeClassDef("Liface/classsub1234;", "Ljava/lang/Object;", "Liface/sub1;",
|
||||
"Liface/sub2;", "Liface/sub3;", "Liface/sub4;"));
|
||||
|
||||
oldClassPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.getDefault(), classes)));
|
||||
newClassPath = new ClassPath(Lists.newArrayList(new DexClassProvider(
|
||||
new ImmutableDexFile(Opcodes.forArtVersion(72), classes))), true, 72);
|
||||
}
|
||||
|
||||
public void superclassTest(String commonSuperclass,
|
||||
String type1, String type2) {
|
||||
public void superclassTest(ClassPath classPath, String commonSuperclass,
|
||||
String type1, String type2) {
|
||||
TypeProto commonSuperclassProto = classPath.getClass(commonSuperclass);
|
||||
TypeProto type1Proto = classPath.getClass(type1);
|
||||
TypeProto type2Proto = classPath.getClass(type2);
|
||||
@ -102,6 +108,11 @@ public class CommonSuperclassTest {
|
||||
Assert.assertSame(commonSuperclassProto, type2Proto.getCommonSuperclass(type1Proto));
|
||||
}
|
||||
|
||||
public void superclassTest(String commonSuperclass, String type1, String type2) {
|
||||
superclassTest(oldClassPath, commonSuperclass, type1, type2);
|
||||
superclassTest(newClassPath, commonSuperclass, type1, type2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommonSuperclass() throws IOException {
|
||||
String object = "Ljava/lang/Object;";
|
||||
@ -131,7 +142,11 @@ public class CommonSuperclassTest {
|
||||
// same value, but different object
|
||||
Assert.assertEquals(
|
||||
onetwo,
|
||||
classPath.getClass(onetwo).getCommonSuperclass(new ClassProto(classPath, onetwo)).getType());
|
||||
oldClassPath.getClass(onetwo).getCommonSuperclass(new ClassProto(oldClassPath, onetwo)).getType());
|
||||
|
||||
Assert.assertEquals(
|
||||
onetwo,
|
||||
newClassPath.getClass(onetwo).getCommonSuperclass(new ClassProto(newClassPath, onetwo)).getType());
|
||||
|
||||
// other object is superclass
|
||||
superclassTest(object, object, one);
|
||||
|
@ -51,11 +51,12 @@ import org.jf.dexlib2.immutable.instruction.ImmutableInstruction35mi;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class CustomMethodInlineTableTest {
|
||||
@Test
|
||||
public void testCustomMethodInlineTable_Virtual() {
|
||||
public void testCustomMethodInlineTable_Virtual() throws IOException {
|
||||
List<ImmutableInstruction> instructions = Lists.newArrayList(
|
||||
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
|
||||
new ImmutableInstruction10x(Opcode.RETURN_VOID));
|
||||
@ -67,10 +68,12 @@ public class CustomMethodInlineTableTest {
|
||||
ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
|
||||
null, null, null, null, null, ImmutableList.of(method));
|
||||
|
||||
DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
|
||||
DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), ImmutableList.of(classDef));
|
||||
|
||||
ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
|
||||
ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
|
||||
ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
|
||||
|
||||
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
|
||||
15, false);
|
||||
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
|
||||
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
|
||||
|
||||
@ -82,7 +85,7 @@ public class CustomMethodInlineTableTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomMethodInlineTable_Static() {
|
||||
public void testCustomMethodInlineTable_Static() throws IOException {
|
||||
List<ImmutableInstruction> instructions = Lists.newArrayList(
|
||||
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
|
||||
new ImmutableInstruction10x(Opcode.RETURN_VOID));
|
||||
@ -94,10 +97,12 @@ public class CustomMethodInlineTableTest {
|
||||
ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
|
||||
null, null, null, null, ImmutableList.of(method), null);
|
||||
|
||||
DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
|
||||
DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), ImmutableList.of(classDef));
|
||||
|
||||
ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
|
||||
ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
|
||||
ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
|
||||
|
||||
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
|
||||
15, false);
|
||||
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
|
||||
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
|
||||
|
||||
@ -109,7 +114,7 @@ public class CustomMethodInlineTableTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomMethodInlineTable_Direct() {
|
||||
public void testCustomMethodInlineTable_Direct() throws IOException {
|
||||
List<ImmutableInstruction> instructions = Lists.newArrayList(
|
||||
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
|
||||
new ImmutableInstruction10x(Opcode.RETURN_VOID));
|
||||
@ -121,10 +126,12 @@ public class CustomMethodInlineTableTest {
|
||||
ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
|
||||
null, null, null, null, ImmutableList.of(method), null);
|
||||
|
||||
DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
|
||||
DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), ImmutableList.of(classDef));
|
||||
|
||||
ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
|
||||
ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
|
||||
ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
|
||||
|
||||
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
|
||||
15, false);
|
||||
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
|
||||
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -57,7 +57,7 @@ public class SuperclassChainTest {
|
||||
ImmutableSet<ClassDef> classes = ImmutableSet.<ClassDef>of(
|
||||
objectClassDef, oneClassDef, twoClassDef, threeClassDef);
|
||||
|
||||
ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), classes)));
|
||||
ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.getDefault(), classes)));
|
||||
|
||||
TypeProto objectClassProto = classPath.getClass("Ljava/lang/Object;");
|
||||
TypeProto oneClassProto = classPath.getClass("Ltest/one;");
|
||||
@ -88,7 +88,7 @@ public class SuperclassChainTest {
|
||||
ClassDef twoClassDef = TestUtils.makeClassDef("Ltest/two;", "Ltest/one;");
|
||||
ClassDef threeClassDef = TestUtils.makeClassDef("Ltest/three;", "Ltest/two;");
|
||||
ImmutableSet<ClassDef> classes = ImmutableSet.<ClassDef>of(twoClassDef, threeClassDef);
|
||||
ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), classes)));
|
||||
ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.getDefault(), classes)));
|
||||
|
||||
TypeProto unknownClassProto = classPath.getUnknownClass();
|
||||
TypeProto oneClassProto = classPath.getClass("Ltest/one;");
|
||||
|
106
dexlib2/src/test/java/org/jf/dexlib2/pool/RollbackTest.java
Normal file
106
dexlib2/src/test/java/org/jf/dexlib2/pool/RollbackTest.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
@ -72,12 +72,12 @@ public class DexWriterTest {
|
||||
MemoryDataStore dataStore = new MemoryDataStore();
|
||||
|
||||
try {
|
||||
DexPool.writeTo(dataStore, new ImmutableDexFile(Opcodes.forApi(19), ImmutableSet.of(classDef)));
|
||||
DexPool.writeTo(dataStore, new ImmutableDexFile(Opcodes.getDefault(), ImmutableSet.of(classDef)));
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(15), dataStore.getData());
|
||||
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dataStore.getData());
|
||||
ClassDef dbClassDef = Iterables.getFirst(dexFile.getClasses(), null);
|
||||
Assert.assertNotNull(dbClassDef);
|
||||
Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null);
|
||||
@ -112,12 +112,12 @@ public class DexWriterTest {
|
||||
MemoryDataStore dataStore = new MemoryDataStore();
|
||||
|
||||
try {
|
||||
DexPool.writeTo(dataStore, new ImmutableDexFile(Opcodes.forApi(19), ImmutableSet.of(classDef)));
|
||||
DexPool.writeTo(dataStore, new ImmutableDexFile(Opcodes.getDefault(), ImmutableSet.of(classDef)));
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(15), dataStore.getData());
|
||||
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dataStore.getData());
|
||||
ClassDef dbClassDef = Iterables.getFirst(dexFile.getClasses(), null);
|
||||
Assert.assertNotNull(dbClassDef);
|
||||
Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null);
|
||||
|
@ -62,7 +62,7 @@ import java.util.List;
|
||||
public class JumboStringConversionTest {
|
||||
@Test
|
||||
public void testJumboStringConversion() throws IOException {
|
||||
DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.forApi(15));
|
||||
DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.getDefault());
|
||||
|
||||
MethodImplementationBuilder methodBuilder = new MethodImplementationBuilder(1);
|
||||
for (int i=0; i<66000; i++) {
|
||||
@ -92,7 +92,7 @@ public class JumboStringConversionTest {
|
||||
MemoryDataStore dexStore = new MemoryDataStore();
|
||||
dexBuilder.writeTo(dexStore);
|
||||
|
||||
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(15), dexStore.getData());
|
||||
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dexStore.getData());
|
||||
|
||||
ClassDef classDef = Iterables.getFirst(dexFile.getClasses(), null);
|
||||
Assert.assertNotNull(classDef);
|
||||
@ -122,7 +122,7 @@ public class JumboStringConversionTest {
|
||||
|
||||
@Test
|
||||
public void testJumboStringConversion_NonMethodBuilder() throws IOException {
|
||||
DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.forApi(15));
|
||||
DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.getDefault());
|
||||
|
||||
final List<Instruction> instructions = Lists.newArrayList();
|
||||
for (int i=0; i<66000; i++) {
|
||||
@ -189,7 +189,7 @@ public class JumboStringConversionTest {
|
||||
MemoryDataStore dexStore = new MemoryDataStore();
|
||||
dexBuilder.writeTo(dexStore);
|
||||
|
||||
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(15), dexStore.getData());
|
||||
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dexStore.getData());
|
||||
|
||||
ClassDef classDef = Iterables.getFirst(dexFile.getClasses(), null);
|
||||
Assert.assertNotNull(classDef);
|
||||
|
@ -76,8 +76,8 @@ dependencies {
|
||||
compile project(':util')
|
||||
compile project(':dexlib2')
|
||||
compile depends.antlr_runtime
|
||||
compile depends.jcommander
|
||||
compile depends.stringtemplate
|
||||
compile depends.commons_cli
|
||||
|
||||
testCompile depends.junit
|
||||
|
||||
@ -95,7 +95,7 @@ task fatJar(type: Jar, dependsOn: jar) {
|
||||
classifier = 'fat'
|
||||
|
||||
manifest {
|
||||
attributes('Main-Class': 'org.jf.smali.main')
|
||||
attributes('Main-Class': 'org.jf.smali.Main')
|
||||
}
|
||||
|
||||
doLast {
|
||||
@ -141,7 +141,7 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
|
||||
dontobfuscate
|
||||
dontoptimize
|
||||
|
||||
keep 'public class org.jf.smali.main { public static void main(java.lang.String[]); }'
|
||||
keep 'public class org.jf.smali.Main { public static void main(java.lang.String[]); }'
|
||||
keepclassmembers 'enum * { public static **[] values(); public static ** valueOf(java.lang.String); }'
|
||||
|
||||
dontwarn 'com.google.common.**'
|
||||
|
@ -263,8 +263,8 @@ import org.jf.dexlib2.Opcodes;
|
||||
this.allowOdex = allowOdex;
|
||||
}
|
||||
|
||||
public void setApiLevel(int apiLevel, boolean experimental) {
|
||||
this.opcodes = new Opcodes(apiLevel, experimental);
|
||||
public void setApiLevel(int apiLevel) {
|
||||
this.opcodes = Opcodes.forApi(apiLevel);
|
||||
this.apiLevel = apiLevel;
|
||||
}
|
||||
|
||||
|
@ -85,8 +85,8 @@ import java.util.*;
|
||||
this.dexBuilder = dexBuilder;
|
||||
}
|
||||
|
||||
public void setApiLevel(int apiLevel, boolean experimental) {
|
||||
this.opcodes = new Opcodes(apiLevel, experimental);
|
||||
public void setApiLevel(int apiLevel) {
|
||||
this.opcodes = Opcodes.forApi(apiLevel);
|
||||
this.apiLevel = apiLevel;
|
||||
}
|
||||
|
||||
|
113
smali/src/main/java/org/jf/smali/AssembleCommand.java
Normal file
113
smali/src/main/java/org/jf/smali/AssembleCommand.java
Normal 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;
|
||||
}
|
||||
}
|
92
smali/src/main/java/org/jf/smali/HelpCommand.java
Normal file
92
smali/src/main/java/org/jf/smali/HelpCommand.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
123
smali/src/main/java/org/jf/smali/Main.java
Normal file
123
smali/src/main/java/org/jf/smali/Main.java
Normal 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
Loading…
x
Reference in New Issue
Block a user