mirror of
https://github.com/revanced/smali.git
synced 2025-05-28 11:50:12 +02:00
Implement a new command line interface for baksmali
This commit is contained in:
parent
93100e57b2
commit
5a5eafb818
@ -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 {
|
||||
@ -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;
|
||||
}
|
||||
@ -104,7 +104,7 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
|
||||
Reference reference = referenceInstruction.getReference();
|
||||
|
||||
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)));
|
||||
|
@ -28,105 +28,21 @@
|
||||
|
||||
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 javax.annotation.Nonnull;
|
||||
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 +50,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 +86,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:
|
||||
@ -243,4 +155,75 @@ public class baksmali {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static List<String> getDefaultBootClassPath(int apiLevel) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
@ -31,19 +31,37 @@
|
||||
|
||||
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.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.Nonnull;
|
||||
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;
|
||||
import java.util.jar.Attributes;
|
||||
|
||||
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 experimentalOpcodes = false;
|
||||
public boolean implicitReferences = false;
|
||||
public boolean normalizeVirtualMethods = false;
|
||||
|
||||
public class baksmaliOptions {
|
||||
// register info values
|
||||
public static final int ALL = 1;
|
||||
public static final int ALLPRE = 2;
|
||||
@ -53,56 +71,53 @@ 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(":"));
|
||||
}
|
||||
/**
|
||||
* 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) {
|
||||
class PublicResourceHandler extends DefaultHandler {
|
||||
|
||||
public void addExtraClassPath(String extraClassPath) {
|
||||
if (extraClassPath.startsWith(":")) {
|
||||
extraClassPath = extraClassPath.substring(1);
|
||||
@Nonnull private final String prefix;
|
||||
|
||||
public PublicResourceHandler(@Nonnull String prefix) {
|
||||
super();
|
||||
this.prefix = prefix;
|
||||
}
|
||||
}
|
||||
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]);
|
||||
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() {
|
||||
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 e) {
|
||||
continue;
|
||||
} catch (SAXException e) {
|
||||
continue;
|
||||
} catch (IOException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
baksmali/src/main/java/org/jf/baksmali/Command.java
Normal file
36
baksmali/src/main/java/org/jf/baksmali/Command.java
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public interface Command {
|
||||
void run();
|
||||
}
|
98
baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java
Normal file
98
baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.analysis.CustomInlineMethodResolver;
|
||||
import org.jf.dexlib2.analysis.InlineMethodResolver;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
|
||||
import org.jf.dexlib2.iface.DexFile;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@Parameters(commandDescription = "Deodexes an odex/oat file")
|
||||
public class DeodexCommand extends DisassembleCommand {
|
||||
@Parameter(names = "--check-package-private-access",
|
||||
description = "Use the package-private access check when calculating vtable indexes. This should " +
|
||||
"only be needed for 4.2.0 odexes. It was reverted in 4.2.1.")
|
||||
private boolean checkPackagePrivateAccess = false;
|
||||
|
||||
@Parameter(names = "--inline-table",
|
||||
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.")
|
||||
private String inlineTable;
|
||||
|
||||
public DeodexCommand(@Nonnull JCommander jc) {
|
||||
super(jc);
|
||||
}
|
||||
|
||||
@Override protected BaksmaliOptions getOptions(DexFile dexFile) {
|
||||
BaksmaliOptions options = super.getOptions(dexFile);
|
||||
|
||||
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 checkPackagePrivateAccess;
|
||||
}
|
||||
|
||||
@Override protected boolean needsClassPath() {
|
||||
return true;
|
||||
}
|
||||
}
|
330
baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java
Normal file
330
baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java
Normal file
@ -0,0 +1,330 @@
|
||||
/*
|
||||
* 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.validators.PositiveInteger;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.jf.dexlib2.DexFileFactory;
|
||||
import org.jf.dexlib2.analysis.ClassPath;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
|
||||
import org.jf.dexlib2.dexbacked.OatFile;
|
||||
import org.jf.dexlib2.iface.DexFile;
|
||||
import org.jf.dexlib2.util.SyntheticAccessorResolver;
|
||||
import org.jf.util.jcommander.CommaColonParameterSplitter;
|
||||
|
||||
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.")
|
||||
public class DisassembleCommand implements Command {
|
||||
|
||||
@Nonnull private final JCommander jc;
|
||||
|
||||
@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.")
|
||||
private int apiLevel = 15;
|
||||
|
||||
@Parameter(names = "--debug-info", arity = 1,
|
||||
description = "Whether to include debug information in the output (.local, .param, .line, etc.). Use " +
|
||||
"--debug-info=false to disable.")
|
||||
private boolean debugInfo = true;
|
||||
|
||||
@Parameter(names = {"-b", "--bootclasspath"},
|
||||
description = "A comma/colon separated list of the bootclasspath jar/oat files to include in the " +
|
||||
"classpath when analyzing the dex file. This will override any automatic selection of " +
|
||||
"bootclasspath files that baksmali would otherwise perform. This is analogous to Android's " +
|
||||
"BOOTCLASSPATH environment variable.",
|
||||
splitter = CommaColonParameterSplitter.class)
|
||||
private List<String> bootClassPath = new ArrayList<String>();
|
||||
|
||||
@Parameter(names = {"-c", "--classpath"},
|
||||
description = "A comma/colon separated list of additional jar/oat files to include in the classpath " +
|
||||
"when analyzing the dex file. These will be added to the classpath after any bootclasspath " +
|
||||
"entries.",
|
||||
splitter = CommaColonParameterSplitter.class)
|
||||
private List<String> classPath = Lists.newArrayList();
|
||||
|
||||
@Parameter(names = {"-d", "--classpath-dir"},
|
||||
description = "baksmali will search these directories in order for any classpath entries.")
|
||||
private List<String> classPathDirectories = Lists.newArrayList(".");
|
||||
|
||||
@Parameter(names = {"--code-offsets"},
|
||||
description = "Add comments to the disassembly containing the code offset within the method for each " +
|
||||
"instruction.")
|
||||
private boolean codeOffsets = false;
|
||||
|
||||
@Parameter(names = "--resolve-resources", arity=1,
|
||||
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 value should be a comma/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")
|
||||
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)
|
||||
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", arity = 1,
|
||||
description = "Generate helper comments for synthetic accessors. Use --accessor-comments=false to disable.")
|
||||
private boolean accessorComments = true;
|
||||
|
||||
@Parameter(names = "--normalize-virtual-methods",
|
||||
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.")
|
||||
private String outputDir = "out";
|
||||
|
||||
@Parameter(names = "--parameter-registers", arity = 1,
|
||||
description = "Use the pNN syntax for registers that refer to a method parameter on method entry. Use" +
|
||||
"--parameter-registers=false to disable.")
|
||||
private boolean parameterRegisters = true;
|
||||
|
||||
@Parameter(names = {"-r", "--register-info"}, arity=1,
|
||||
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.")
|
||||
private List<String> registerInfoTypes = Lists.newArrayList();
|
||||
|
||||
@Parameter(names = "--sequential-labels",
|
||||
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",
|
||||
description = "Use implicit (without the class name) method and field references for methods and " +
|
||||
"fields from the current class.")
|
||||
private boolean implicitReferences = false;
|
||||
|
||||
@Parameter(names = "--experimental",
|
||||
description = "Enable experimental opcodes to be disassembled, even if they aren't necessarily " +
|
||||
"supported in the Android runtime yet.")
|
||||
private boolean experimentalOpcodes = false;
|
||||
|
||||
@Parameter(description = "<file> - A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
|
||||
"files, you can specify which dex file to disassemble by appending the name of the dex file with a " +
|
||||
"colon. E.g. \"something.apk:classes2.dex\"")
|
||||
private List<String> inputList = Lists.newArrayList();
|
||||
|
||||
public DisassembleCommand(@Nonnull JCommander jc) {
|
||||
this.jc = jc;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (help || inputList == null || inputList.isEmpty()) {
|
||||
jc.usage(jc.getParsedCommand());
|
||||
return;
|
||||
}
|
||||
|
||||
if (inputList.size() > 1) {
|
||||
System.err.println("Too many files specified");
|
||||
jc.usage(jc.getParsedCommand());
|
||||
return;
|
||||
}
|
||||
|
||||
String input = inputList.get(0);
|
||||
File dexFileFile = new File(input);
|
||||
String dexFileEntry = null;
|
||||
if (!dexFileFile.exists()) {
|
||||
int colonIndex = input.lastIndexOf(':');
|
||||
|
||||
if (colonIndex >= 0) {
|
||||
dexFileFile = new File(input.substring(0, colonIndex));
|
||||
dexFileEntry = input.substring(colonIndex + 1);
|
||||
}
|
||||
|
||||
if (!dexFileFile.exists()) {
|
||||
System.err.println("Can't find the file " + input);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
//Read in and parse the dex file
|
||||
DexBackedDexFile dexFile = null;
|
||||
try {
|
||||
dexFile = DexFileFactory.loadDexFile(dexFileFile, dexFileEntry, apiLevel, experimentalOpcodes);
|
||||
} catch (DexFileFactory.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 (OatFile.OatDexFile oatDexFile : ex.oatFile.getDexFiles()) {
|
||||
System.err.println(oatDexFile.filename);
|
||||
}
|
||||
System.exit(1);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
if (dexFile.hasOdexOpcodes()) {
|
||||
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");
|
||||
}
|
||||
|
||||
if (needsClassPath() && bootClassPath.isEmpty()) {
|
||||
if (dexFile instanceof DexBackedOdexFile) {
|
||||
bootClassPath = ((DexBackedOdexFile)dexFile).getDependencies();
|
||||
} else {
|
||||
bootClassPath = Baksmali.getDefaultBootClassPath(apiLevel);
|
||||
}
|
||||
}
|
||||
|
||||
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 (!Baksmali.disassembleDexFile(dexFile, outputDirectoryFile, jobs, getOptions(dexFile))) {
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected boolean needsClassPath() {
|
||||
return !registerInfoTypes.isEmpty() || normalizeVirtualMethods;
|
||||
}
|
||||
|
||||
protected boolean shouldCheckPackagePrivateAccess() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected BaksmaliOptions getOptions(DexFile dexFile) {
|
||||
final BaksmaliOptions options = new BaksmaliOptions();
|
||||
|
||||
if (needsClassPath()) {
|
||||
try {
|
||||
options.classPath = ClassPath.fromClassPath(classPathDirectories,
|
||||
Iterables.concat(bootClassPath, classPath), dexFile, apiLevel,
|
||||
shouldCheckPackagePrivateAccess(), experimentalOpcodes);
|
||||
} catch (Exception ex) {
|
||||
System.err.println("\n\nError occurred while loading class path files. Aborting.");
|
||||
ex.printStackTrace(System.err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!resourceIdFiles.isEmpty()) {
|
||||
Map<String, File> resourceFiles = Maps.newHashMap();
|
||||
|
||||
for (String resourceIdFileSpec: resourceIdFiles) {
|
||||
int separatorIndex = resourceIdFileSpec.indexOf('=');
|
||||
if (separatorIndex == -1) {
|
||||
System.err.println(String.format("Invalid resource id spec: %s", resourceIdFileSpec));
|
||||
jc.usage(jc.getParsedCommand());
|
||||
System.exit(-1);
|
||||
}
|
||||
String prefix = resourceIdFileSpec.substring(0, separatorIndex);
|
||||
String resourceIdFilePath = resourceIdFileSpec.substring(separatorIndex+1);
|
||||
File resourceIdFile = new File(resourceIdFilePath);
|
||||
|
||||
if (!resourceIdFile.exists()) {
|
||||
System.err.println(String.format("Can't find file: %s", resourceIdFilePath));
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
resourceFiles.put(prefix, resourceIdFile);
|
||||
}
|
||||
|
||||
options.loadResourceIds(resourceFiles);
|
||||
}
|
||||
|
||||
options.parameterRegisters = parameterRegisters;
|
||||
options.localsDirective = localsDirective;
|
||||
options.sequentialLabels = sequentialLabels;
|
||||
options.debugInfo = debugInfo;
|
||||
options.codeOffsets = codeOffsets;
|
||||
options.accessorComments = accessorComments;
|
||||
options.experimentalOpcodes = experimentalOpcodes;
|
||||
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));
|
||||
jc.usage(jc.getParsedCommand());
|
||||
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;
|
||||
}
|
||||
}
|
149
baksmali/src/main/java/org/jf/baksmali/DumpCommand.java
Normal file
149
baksmali/src/main/java/org/jf/baksmali/DumpCommand.java
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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.DexFileFactory;
|
||||
import org.jf.dexlib2.Opcodes;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
||||
import org.jf.dexlib2.dexbacked.OatFile;
|
||||
import org.jf.dexlib2.dexbacked.raw.RawDexFile;
|
||||
import org.jf.dexlib2.dexbacked.raw.util.DexAnnotator;
|
||||
import org.jf.util.ConsoleUtil;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.*;
|
||||
|
||||
@Parameters(commandDescription = "Prints an annotated hex dump for the given dex file")
|
||||
public class DumpCommand implements Command {
|
||||
|
||||
@Nonnull
|
||||
private final JCommander jc;
|
||||
|
||||
@Parameter(names = {"-h", "-?", "--help"}, help = true,
|
||||
description = "Show usage information for this command.")
|
||||
public boolean help;
|
||||
|
||||
@Parameter(names = {"-a", "--api"},
|
||||
description = "The numeric api level of the file being disassembled.")
|
||||
public int apiLevel = 15;
|
||||
|
||||
@Parameter(names = "--experimental",
|
||||
description = "Enable experimental opcodes to be disassembled, even if they aren't necessarily " +
|
||||
"supported in the Android runtime yet.")
|
||||
public boolean experimentalOpcodes = false;
|
||||
|
||||
@Parameter(description = "<file> - A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
|
||||
"files, you can specify which dex file to disassemble by appending the name of the dex file with a " +
|
||||
"colon. E.g. \"something.apk:classes2.dex\"")
|
||||
public String input;
|
||||
|
||||
public DumpCommand(@Nonnull JCommander jc) {
|
||||
this.jc = jc;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (help || input == null || input.isEmpty()) {
|
||||
jc.usage("dump");
|
||||
return;
|
||||
}
|
||||
|
||||
String inputDexPath = input;
|
||||
|
||||
File dexFileFile = new File(inputDexPath);
|
||||
String dexFileEntry = null;
|
||||
if (!dexFileFile.exists()) {
|
||||
int colonIndex = inputDexPath.lastIndexOf(':');
|
||||
|
||||
if (colonIndex >= 0) {
|
||||
dexFileFile = new File(inputDexPath.substring(0, colonIndex));
|
||||
dexFileEntry = inputDexPath.substring(colonIndex+1);
|
||||
}
|
||||
|
||||
if (!dexFileFile.exists()) {
|
||||
System.err.println("Can't find the file " + inputDexPath);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
DexBackedDexFile dexFile = null;
|
||||
try {
|
||||
dexFile = DexFileFactory.loadDexFile(dexFileFile, dexFileEntry, apiLevel, experimentalOpcodes);
|
||||
} catch (DexFileFactory.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 (OatFile.OatDexFile oatDexFile: ex.oatFile.getDexFiles()) {
|
||||
System.err.println(oatDexFile.filename);
|
||||
}
|
||||
System.exit(1);
|
||||
} catch (IOException ex) {
|
||||
System.err.println("There was an error while reading the dex file");
|
||||
ex.printStackTrace(System.err);
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
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(Opcodes.forApi(apiLevel), dexFile);
|
||||
DexAnnotator annotator = new DexAnnotator(rawDexFile, consoleWidth);
|
||||
annotator.writeAnnotations(writer);
|
||||
}
|
||||
}
|
95
baksmali/src/main/java/org/jf/baksmali/HelpCommand.java
Normal file
95
baksmali/src/main/java/org/jf/baksmali/HelpCommand.java
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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 javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
@Parameters(commandDescription = "Shows usage information")
|
||||
public class HelpCommand implements Command {
|
||||
@Nonnull private final JCommander jc;
|
||||
|
||||
public HelpCommand(@Nonnull JCommander jc) {
|
||||
this.jc = jc;
|
||||
}
|
||||
|
||||
@Parameter(description = "If specified, only show the usage information for the given commands")
|
||||
private List<String> commands = Lists.newArrayList();
|
||||
|
||||
public void run() {
|
||||
if (commands == null || commands.isEmpty()) {
|
||||
jc.usage();
|
||||
} else {
|
||||
for (String cmd : commands) {
|
||||
if (cmd.equals("register-info")) {
|
||||
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 optionally accepts a " +
|
||||
"comma-separated list of values specifying which registers and how much " +
|
||||
"information to include. If no values are specified, \"ARGS,DEST\" is used as " +
|
||||
"the default. Valid values 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 {
|
||||
jc.usage(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Parameters(hidden = true)
|
||||
public static class HlepCommand extends HelpCommand {
|
||||
public HlepCommand(@Nonnull JCommander jc) {
|
||||
super(jc);
|
||||
}
|
||||
}
|
||||
}
|
101
baksmali/src/main/java/org/jf/baksmali/Main.java
Normal file
101
baksmali/src/main/java/org/jf/baksmali/Main.java
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.baksmali.HelpCommand.HlepCommand;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
public class Main {
|
||||
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;
|
||||
|
||||
public static void main(String[] args) {
|
||||
Main main = new Main();
|
||||
|
||||
JCommander jc = new JCommander(main);
|
||||
|
||||
jc.addCommand("disassemble", new DisassembleCommand(jc), "dis", "d");
|
||||
jc.addCommand("deodex", new DeodexCommand(jc), "de", "x");
|
||||
jc.addCommand("dump", new DumpCommand(jc), "du");
|
||||
jc.addCommand("help", new HelpCommand(jc), "h");
|
||||
jc.addCommand("hlep", new HlepCommand(jc));
|
||||
|
||||
jc.parse(args);
|
||||
|
||||
if (jc.getParsedCommand() == null || main.help) {
|
||||
jc.usage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (main.version) {
|
||||
version();
|
||||
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.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);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
@ -87,12 +87,12 @@ public class AnalysisTest {
|
||||
|
||||
DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), 15, false);
|
||||
|
||||
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,10 @@ 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);
|
||||
options.experimentalOpcodes);
|
||||
|
||||
// Remove unnecessary whitespace and optionally strip all comments from smali file
|
||||
String normalizedActual = getNormalizedSmali(classDef, options, stripComments);
|
||||
@ -62,13 +62,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 +81,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());
|
||||
}
|
||||
}
|
||||
|
@ -35,10 +35,10 @@ import org.junit.Test;
|
||||
|
||||
public class LambdaTest extends IdenticalRoundtripTest {
|
||||
|
||||
private baksmaliOptions createOptions() {
|
||||
baksmaliOptions options = new baksmaliOptions();
|
||||
private BaksmaliOptions createOptions() {
|
||||
BaksmaliOptions options = new BaksmaliOptions();
|
||||
options.apiLevel = 23; // since we need at least level 23 for lambda opcodes
|
||||
options.experimental = true; // since these opcodes aren't implemented in runtime yet);
|
||||
options.experimentalOpcodes = true; // since these opcodes aren't implemented in runtime yet);
|
||||
return options;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -109,7 +109,8 @@ subprojects {
|
||||
jflex_plugin: 'co.tomlee.gradle.plugins:gradle-jflex-plugin:0.0.2',
|
||||
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'
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ dependencies {
|
||||
compile depends.commons_cli
|
||||
compile depends.findbugs
|
||||
compile depends.guava
|
||||
compile depends.jcommander
|
||||
testCompile depends.junit
|
||||
}
|
||||
|
||||
|
@ -33,8 +33,89 @@ package org.jf.util;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.text.BreakIterator;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class StringWrapper {
|
||||
/**
|
||||
* Splits the given string into lines of maximum width maxWidth. The splitting is done using the current locale's
|
||||
* rules for splitting lines.
|
||||
*
|
||||
* @param string The string to split
|
||||
* @param maxWidth The maximum length of any line
|
||||
* @return An iterable of Strings containing the wrapped lines
|
||||
*/
|
||||
public static Iterable<String> wrapStringOnBreaks(@Nonnull final String string, final int maxWidth) {
|
||||
final BreakIterator breakIterator = BreakIterator.getLineInstance();
|
||||
breakIterator.setText(string);
|
||||
|
||||
return new Iterable<String>() {
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new Iterator<String>() {
|
||||
private int currentLineStart = 0;
|
||||
private boolean nextLineSet = false;
|
||||
private String nextLine;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (!nextLineSet) {
|
||||
calculateNext();
|
||||
}
|
||||
return nextLine != null;
|
||||
}
|
||||
|
||||
private void calculateNext() {
|
||||
int lineEnd = currentLineStart;
|
||||
while (true) {
|
||||
lineEnd = breakIterator.following(lineEnd);
|
||||
if (lineEnd == BreakIterator.DONE) {
|
||||
lineEnd = breakIterator.last();
|
||||
if (lineEnd <= currentLineStart) {
|
||||
nextLine = null;
|
||||
nextLineSet = true;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (lineEnd - currentLineStart > maxWidth) {
|
||||
lineEnd = breakIterator.preceding(lineEnd);
|
||||
if (lineEnd <= currentLineStart) {
|
||||
lineEnd = currentLineStart + maxWidth;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (string.charAt(lineEnd-1) == '\n') {
|
||||
nextLine = string.substring(currentLineStart, lineEnd-1);
|
||||
nextLineSet = true;
|
||||
currentLineStart = lineEnd;
|
||||
return;
|
||||
}
|
||||
}
|
||||
nextLine = string.substring(currentLineStart, lineEnd);
|
||||
nextLineSet = true;
|
||||
currentLineStart = lineEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
String ret = nextLine;
|
||||
nextLine = null;
|
||||
nextLineSet = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the given string into lines using on any embedded newlines, and wrapping the text as needed to conform to
|
||||
* the given maximum line width.
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.util.jcommander;
|
||||
|
||||
import com.beust.jcommander.converters.IParameterSplitter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A JCommander parameter splitter that splits a parameter value by either commas or colons
|
||||
*/
|
||||
public class CommaColonParameterSplitter implements IParameterSplitter {
|
||||
@Override
|
||||
public List<String> split(String value) {
|
||||
return Arrays.asList(value.split(":|,"));
|
||||
}
|
||||
}
|
@ -31,10 +31,34 @@
|
||||
|
||||
package org.jf.util;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class StringWrapperTest {
|
||||
@Test
|
||||
public void testWrapStringByWords() {
|
||||
validateResult2(new String[]{"abc", "abcdef", "abcdef"},
|
||||
"abc\nabcdefabcdef", 6);
|
||||
|
||||
validateResult2(new String[]{"abc", "abcdef", " ", "abcdef"},
|
||||
"abc\nabcdef abcdef", 6);
|
||||
|
||||
validateResult2(new String[]{"abc", "abcde ", "fabcde", "f"},
|
||||
"abc\nabcde fabcdef", 6);
|
||||
|
||||
validateResult2(new String[]{"abc def ghi ", "kjl mon pqr ", "stu vwx yz"},
|
||||
"abc def ghi kjl mon pqr stu vwx yz", 14);
|
||||
|
||||
validateResult2(new String[]{"abcdefg", "hikjlmo", "npqrstu", "vwxyz"},
|
||||
"abcdefghikjlmonpqrstuvwxyz", 7);
|
||||
|
||||
validateResult2(new String[]{"abc", "defhig"},
|
||||
"abc\ndefhig", 20);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapString() {
|
||||
validateResult(
|
||||
@ -115,4 +139,15 @@ public class StringWrapperTest {
|
||||
Assert.assertEquals(expected[i], actual[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void validateResult2(String[] expected, String textToWrap, int maxWidth) {
|
||||
List<String> result = Lists.newArrayList(StringWrapper.wrapStringOnBreaks(textToWrap, maxWidth));
|
||||
|
||||
Assert.assertEquals(expected.length, result.size());
|
||||
int i;
|
||||
for (i=0; i<result.size(); i++) {
|
||||
Assert.assertTrue(i < expected.length);
|
||||
Assert.assertEquals(expected[i], result.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user