Implement a new command line interface for baksmali

This commit is contained in:
Ben Gruver 2016-04-10 22:08:11 -07:00
parent 93100e57b2
commit 5a5eafb818
36 changed files with 1220 additions and 920 deletions

View File

@ -41,8 +41,8 @@ buildscript {
dependencies { dependencies {
compile project(':util') compile project(':util')
compile project(':dexlib2') compile project(':dexlib2')
compile depends.commons_cli
compile depends.guava compile depends.guava
compile depends.jcommander
testCompile depends.junit testCompile depends.junit
testCompile project(':smali') testCompile project(':smali')
@ -59,7 +59,7 @@ task fatJar(type: Jar) {
classifier = 'fat' classifier = 'fat'
manifest { manifest {
attributes('Main-Class': 'org.jf.baksmali.main') attributes('Main-Class': 'org.jf.baksmali.Main')
} }
doLast { doLast {
@ -100,3 +100,17 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
} }
tasks.getByPath(':release').dependsOn(proguard) tasks.getByPath(':release').dependsOn(proguard)
task fastbuild(dependsOn: build) {
}
task fb(dependsOn: fastbuild) {
}
tasks.getByPath('javadoc').onlyIf({
!gradle.taskGraph.hasTask(fastbuild)
})
tasks.getByPath('test').onlyIf({
!gradle.taskGraph.hasTask(fastbuild)
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,19 +31,37 @@
package org.jf.baksmali; package org.jf.baksmali;
import com.google.common.collect.Lists;
import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.analysis.InlineMethodResolver; import org.jf.dexlib2.analysis.InlineMethodResolver;
import org.jf.dexlib2.util.SyntheticAccessorResolver; import org.jf.dexlib2.util.SyntheticAccessorResolver;
import org.xml.sax.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.io.File;
import java.util.Arrays; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
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 // register info values
public static final int ALL = 1; public static final int ALL = 1;
public static final int ALLPRE = 2; public static final int ALLPRE = 2;
@ -53,56 +71,53 @@ public class baksmaliOptions {
public static final int MERGE = 32; public static final int MERGE = 32;
public static final int FULLMERGE = 64; public static final int FULLMERGE = 64;
public int apiLevel = 15;
public String outputDirectory = "out";
@Nullable public String dexEntry = null;
public List<String> bootClassPathDirs = Lists.newArrayList();
public List<String> bootClassPathEntries = Lists.newArrayList();
public List<String> extraClassPathEntries = Lists.newArrayList();
public Map<String,String> resourceIdFileEntries = new HashMap<String,String>();
public Map<Integer,String> resourceIds = new HashMap<Integer,String>();
public boolean noParameterRegisters = false;
public boolean useLocalsDirective = false;
public boolean useSequentialLabels = false;
public boolean outputDebugInfo = true;
public boolean addCodeOffsets = false;
public boolean noAccessorComments = false;
public boolean allowOdex = false;
public boolean deodex = false;
public boolean experimental = false;
public boolean ignoreErrors = false;
public boolean checkPackagePrivateAccess = false;
public boolean useImplicitReferences = false;
public boolean normalizeVirtualMethods = false;
public File customInlineDefinitions = null;
public InlineMethodResolver inlineResolver = null;
public int registerInfo = 0; public int registerInfo = 0;
public ClassPath classPath = null;
public int jobs = Runtime.getRuntime().availableProcessors();
public boolean disassemble = true;
public boolean dump = false;
public String dumpFileName = null;
public Map<Integer,String> resourceIds = new HashMap<Integer,String>();
public InlineMethodResolver inlineResolver = null;
public ClassPath classPath = null;
public SyntheticAccessorResolver syntheticAccessorResolver = null; public SyntheticAccessorResolver syntheticAccessorResolver = null;
public void setBootClassPath(String bootClassPath) { /**
bootClassPathEntries = Lists.newArrayList(bootClassPath.split(":")); * Load the resource ids from a set of public.xml files.
} *
* @param resourceFiles A map of resource prefixes -> public.xml files
*/
public void loadResourceIds(Map<String, File> resourceFiles) {
class PublicResourceHandler extends DefaultHandler {
public void addExtraClassPath(String extraClassPath) { @Nonnull private final String prefix;
if (extraClassPath.startsWith(":")) {
extraClassPath = extraClassPath.substring(1); public PublicResourceHandler(@Nonnull String prefix) {
super();
this.prefix = prefix;
}
} }
extraClassPathEntries.addAll(Arrays.asList(extraClassPath.split(":")));
}
public void setResourceIdFiles(String resourceIdFiles) { for (Map.Entry<String, File> entry: resourceFiles.entrySet()) {
for (String resourceIdFile: resourceIdFiles.split(":")) { try {
String[] entry = resourceIdFile.split("="); SAXParser saxp = SAXParserFactory.newInstance().newSAXParser();
resourceIdFileEntries.put(entry[1], entry[0]); 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;
}
} }
} }
} }

View 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();
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}
}

View 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;
}
}

View File

@ -1,73 +0,0 @@
/*
* [The "BSD licence"]
* Copyright (c) 2010 Ben Gruver (JesusFreke)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jf.baksmali;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.raw.RawDexFile;
import org.jf.dexlib2.dexbacked.raw.util.DexAnnotator;
import org.jf.util.ConsoleUtil;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class dump {
public static void dump(DexBackedDexFile dexFile, String dumpFileName, int apiLevel) throws IOException {
if (dumpFileName != null) {
Writer writer = null;
try {
writer = new BufferedWriter(new FileWriter(dumpFileName));
int consoleWidth = ConsoleUtil.getConsoleWidth();
if (consoleWidth <= 0) {
consoleWidth = 120;
}
RawDexFile rawDexFile = new RawDexFile(Opcodes.forApi(apiLevel), dexFile);
DexAnnotator annotator = new DexAnnotator(rawDexFile, consoleWidth);
annotator.writeAnnotations(writer);
} catch (IOException ex) {
System.err.println("There was an error while dumping the dex file to " + dumpFileName);
ex.printStackTrace(System.err);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException ex) {
System.err.println("There was an error while closing the dump file " + dumpFileName);
ex.printStackTrace(System.err);
}
}
}
}
}
}

View File

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

View File

@ -87,12 +87,12 @@ public class AnalysisTest {
DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), 15, false); DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), 15, false);
baksmaliOptions options = new baksmaliOptions(); BaksmaliOptions options = new BaksmaliOptions();
if (registerInfo) { if (registerInfo) {
options.registerInfo = baksmaliOptions.ALL | baksmaliOptions.FULLMERGE; options.registerInfo = BaksmaliOptions.ALL | BaksmaliOptions.FULLMERGE;
options.classPath = new ClassPath(); options.classPath = new ClassPath();
} }
options.useImplicitReferences = false; options.implicitReferences = false;
for (ClassDef classDef: dexFile.getClasses()) { for (ClassDef classDef: dexFile.getClasses()) {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,10 +35,10 @@ import org.junit.Test;
public class LambdaTest extends IdenticalRoundtripTest { public class LambdaTest extends IdenticalRoundtripTest {
private baksmaliOptions createOptions() { private BaksmaliOptions createOptions() {
baksmaliOptions options = new baksmaliOptions(); BaksmaliOptions options = new BaksmaliOptions();
options.apiLevel = 23; // since we need at least level 23 for lambda opcodes 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; return options;
} }

View File

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

View File

@ -109,7 +109,8 @@ subprojects {
jflex_plugin: 'co.tomlee.gradle.plugins:gradle-jflex-plugin:0.0.2', jflex_plugin: 'co.tomlee.gradle.plugins:gradle-jflex-plugin:0.0.2',
proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1', proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1',
dx: 'com.google.android.tools:dx:1.7', dx: 'com.google.android.tools:dx:1.7',
gson: 'com.google.code.gson:gson:2.3.1' gson: 'com.google.code.gson:gson:2.3.1',
jcommander: 'com.beust:jcommander:1.48'
] ]
} }

View File

@ -33,6 +33,7 @@ dependencies {
compile depends.commons_cli compile depends.commons_cli
compile depends.findbugs compile depends.findbugs
compile depends.guava compile depends.guava
compile depends.jcommander
testCompile depends.junit testCompile depends.junit
} }

View File

@ -33,8 +33,89 @@ package org.jf.util;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.text.BreakIterator;
import java.util.Iterator;
public class StringWrapper { 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 * 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. * the given maximum line width.

View File

@ -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(":|,"));
}
}

View File

@ -31,10 +31,34 @@
package org.jf.util; package org.jf.util;
import com.google.common.collect.Lists;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.util.List;
public class StringWrapperTest { 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 @Test
public void testWrapString() { public void testWrapString() {
validateResult( validateResult(
@ -115,4 +139,15 @@ public class StringWrapperTest {
Assert.assertEquals(expected[i], actual[i]); 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));
}
}
} }