From 5a5eafb818cc18baeef8bdae1940401da3735f25 Mon Sep 17 00:00:00 2001 From: Ben Gruver Date: Sun, 10 Apr 2016 22:08:11 -0700 Subject: [PATCH] Implement a new command line interface for baksmali --- baksmali/build.gradle | 18 +- .../jf/baksmali/Adaptors/CatchMethodItem.java | 4 +- .../jf/baksmali/Adaptors/ClassDefinition.java | 13 +- .../Adaptors/EndTryLabelMethodItem.java | 4 +- .../jf/baksmali/Adaptors/FieldDefinition.java | 8 +- .../Format/InstructionMethodItem.java | 6 +- .../OffsetInstructionFormatMethodItem.java | 4 +- .../jf/baksmali/Adaptors/LabelMethodItem.java | 8 +- .../baksmali/Adaptors/MethodDefinition.java | 30 +- ...PostInstructionRegisterInfoMethodItem.java | 8 +- .../PreInstructionRegisterInfoMethodItem.java | 14 +- .../baksmali/Adaptors/RegisterFormatter.java | 10 +- .../baksmali/{baksmali.java => Baksmali.java} | 171 +++-- ...smaliOptions.java => BaksmaliOptions.java} | 111 ++-- .../main/java/org/jf/baksmali/Command.java | 36 ++ .../java/org/jf/baksmali/DeodexCommand.java | 98 +++ .../org/jf/baksmali/DisassembleCommand.java | 330 ++++++++++ .../java/org/jf/baksmali/DumpCommand.java | 149 +++++ .../java/org/jf/baksmali/HelpCommand.java | 95 +++ .../src/main/java/org/jf/baksmali/Main.java | 101 +++ .../src/main/java/org/jf/baksmali/dump.java | 73 --- .../src/main/java/org/jf/baksmali/main.java | 612 ------------------ .../java/org/jf/baksmali/AnalysisTest.java | 6 +- .../org/jf/baksmali/BaksmaliTestUtils.java | 10 +- .../test/java/org/jf/baksmali/DexTest.java | 2 +- .../java/org/jf/baksmali/DisassemblyTest.java | 4 +- .../org/jf/baksmali/FieldGapOrderTest.java | 4 +- .../jf/baksmali/ImplicitReferenceTest.java | 32 +- .../org/jf/baksmali/InterfaceOrderTest.java | 2 +- .../test/java/org/jf/baksmali/LambdaTest.java | 6 +- .../java/org/jf/baksmali/RoundtripTest.java | 4 +- build.gradle | 3 +- util/build.gradle | 1 + .../main/java/org/jf/util/StringWrapper.java | 81 +++ .../CommaColonParameterSplitter.java | 47 ++ .../java/org/jf/util/StringWrapperTest.java | 35 + 36 files changed, 1220 insertions(+), 920 deletions(-) rename baksmali/src/main/java/org/jf/baksmali/{baksmali.java => Baksmali.java} (60%) rename baksmali/src/main/java/org/jf/baksmali/{baksmaliOptions.java => BaksmaliOptions.java} (51%) create mode 100644 baksmali/src/main/java/org/jf/baksmali/Command.java create mode 100644 baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java create mode 100644 baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java create mode 100644 baksmali/src/main/java/org/jf/baksmali/DumpCommand.java create mode 100644 baksmali/src/main/java/org/jf/baksmali/HelpCommand.java create mode 100644 baksmali/src/main/java/org/jf/baksmali/Main.java delete mode 100644 baksmali/src/main/java/org/jf/baksmali/dump.java delete mode 100644 baksmali/src/main/java/org/jf/baksmali/main.java create mode 100644 util/src/main/java/org/jf/util/jcommander/CommaColonParameterSplitter.java diff --git a/baksmali/build.gradle b/baksmali/build.gradle index f3a14b19..b874747b 100644 --- a/baksmali/build.gradle +++ b/baksmali/build.gradle @@ -41,8 +41,8 @@ buildscript { dependencies { compile project(':util') compile project(':dexlib2') - compile depends.commons_cli compile depends.guava + compile depends.jcommander testCompile depends.junit testCompile project(':smali') @@ -59,7 +59,7 @@ task fatJar(type: Jar) { classifier = 'fat' manifest { - attributes('Main-Class': 'org.jf.baksmali.main') + attributes('Main-Class': 'org.jf.baksmali.Main') } doLast { @@ -100,3 +100,17 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) { } tasks.getByPath(':release').dependsOn(proguard) + +task fastbuild(dependsOn: build) { +} + +task fb(dependsOn: fastbuild) { +} + +tasks.getByPath('javadoc').onlyIf({ + !gradle.taskGraph.hasTask(fastbuild) +}) + +tasks.getByPath('test').onlyIf({ + !gradle.taskGraph.hasTask(fastbuild) +}) \ No newline at end of file diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/CatchMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/CatchMethodItem.java index 6c67d4ac..4b545ee6 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/CatchMethodItem.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/CatchMethodItem.java @@ -28,7 +28,7 @@ package org.jf.baksmali.Adaptors; -import org.jf.baksmali.baksmaliOptions; +import org.jf.baksmali.BaksmaliOptions; import org.jf.util.IndentingWriter; import javax.annotation.Nonnull; @@ -42,7 +42,7 @@ public class CatchMethodItem extends MethodItem { private final LabelMethodItem tryEndLabel; private final LabelMethodItem handlerLabel; - public CatchMethodItem(@Nonnull baksmaliOptions options, @Nonnull MethodDefinition.LabelCache labelCache, + public CatchMethodItem(@Nonnull BaksmaliOptions options, @Nonnull MethodDefinition.LabelCache labelCache, int codeAddress, @Nullable String exceptionType, int startAddress, int endAddress, int handlerAddress) { super(codeAddress); diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java index 2529af8a..361826da 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java @@ -28,8 +28,7 @@ package org.jf.baksmali.Adaptors; -import com.google.common.collect.Lists; -import org.jf.baksmali.baksmaliOptions; +import org.jf.baksmali.BaksmaliOptions; import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.dexbacked.DexBackedClassDef; import org.jf.dexlib2.dexbacked.DexBackedDexFile.InvalidItemIndex; @@ -46,16 +45,16 @@ import java.io.IOException; import java.util.*; public class ClassDefinition { - @Nonnull public final baksmaliOptions options; + @Nonnull public final BaksmaliOptions options; @Nonnull public final ClassDef classDef; @Nonnull private final HashSet fieldsSetInStaticConstructor; protected boolean validationErrors; - public ClassDefinition(@Nonnull baksmaliOptions options, @Nonnull ClassDef classDef) { + public ClassDefinition(@Nonnull BaksmaliOptions options, @Nonnull ClassDef classDef) { this.options = options; this.classDef = classDef; - fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor(); + fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor(classDef); } public boolean hadValidationErrors() { @@ -63,7 +62,7 @@ public class ClassDefinition { } @Nonnull - private HashSet findFieldsSetInStaticConstructor() { + private static HashSet findFieldsSetInStaticConstructor(@Nonnull ClassDef classDef) { HashSet fieldsSetInStaticConstructor = new HashSet(); for (Method method: classDef.getDirectMethods()) { @@ -166,7 +165,7 @@ public class ClassDefinition { writer.write("# annotations\n"); String containingClass = null; - if (options.useImplicitReferences) { + if (options.implicitReferences) { containingClass = classDef.getType(); } diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EndTryLabelMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EndTryLabelMethodItem.java index aed315d7..26807048 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EndTryLabelMethodItem.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EndTryLabelMethodItem.java @@ -28,14 +28,14 @@ package org.jf.baksmali.Adaptors; -import org.jf.baksmali.baksmaliOptions; +import org.jf.baksmali.BaksmaliOptions; import javax.annotation.Nonnull; public class EndTryLabelMethodItem extends LabelMethodItem { private int endTryAddress; - public EndTryLabelMethodItem(@Nonnull baksmaliOptions options, int codeAddress, int endTryAddress) { + public EndTryLabelMethodItem(@Nonnull BaksmaliOptions options, int codeAddress, int endTryAddress) { super(options, codeAddress, "try_end_"); this.endTryAddress = endTryAddress; } diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java index ae017914..90291b79 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java @@ -29,7 +29,7 @@ package org.jf.baksmali.Adaptors; import org.jf.baksmali.Adaptors.EncodedValue.EncodedValueAdaptor; -import org.jf.baksmali.baksmaliOptions; +import org.jf.baksmali.BaksmaliOptions; import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.iface.Annotation; import org.jf.dexlib2.iface.Field; @@ -41,7 +41,7 @@ import java.io.IOException; import java.util.Collection; public class FieldDefinition { - public static void writeTo(baksmaliOptions options, IndentingWriter writer, Field field, + public static void writeTo(BaksmaliOptions options, IndentingWriter writer, Field field, boolean setInStaticConstructor) throws IOException { EncodedValue initialValue = field.getInitialValue(); int accessFlags = field.getAccessFlags(); @@ -68,7 +68,7 @@ public class FieldDefinition { writer.write(" = "); String containingClass = null; - if (options.useImplicitReferences) { + if (options.implicitReferences) { containingClass = field.getDefiningClass(); } @@ -82,7 +82,7 @@ public class FieldDefinition { writer.indent(4); String containingClass = null; - if (options.useImplicitReferences) { + if (options.implicitReferences) { containingClass = field.getDefiningClass(); } diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java index fc43d6f1..6f788433 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java @@ -32,7 +32,7 @@ import org.jf.baksmali.Adaptors.MethodDefinition; import org.jf.baksmali.Adaptors.MethodDefinition.InvalidSwitchPayload; import org.jf.baksmali.Adaptors.MethodItem; import org.jf.baksmali.Renderers.LongRenderer; -import org.jf.baksmali.baksmaliOptions; +import org.jf.baksmali.BaksmaliOptions; import org.jf.dexlib2.Opcode; import org.jf.dexlib2.ReferenceType; import org.jf.dexlib2.VerificationError; @@ -67,7 +67,7 @@ public class InstructionMethodItem extends MethodItem { } private boolean isAllowedOdex(@Nonnull Opcode opcode) { - baksmaliOptions options = methodDef.classDef.options; + BaksmaliOptions options = methodDef.classDef.options; if (options.allowOdex) { return true; } @@ -104,7 +104,7 @@ public class InstructionMethodItem extends MethodItem { Reference reference = referenceInstruction.getReference(); String classContext = null; - if (methodDef.classDef.options.useImplicitReferences) { + if (methodDef.classDef.options.implicitReferences) { classContext = methodDef.method.getDefiningClass(); } diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OffsetInstructionFormatMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OffsetInstructionFormatMethodItem.java index 3ffb4bd4..be76edfe 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OffsetInstructionFormatMethodItem.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OffsetInstructionFormatMethodItem.java @@ -30,7 +30,7 @@ package org.jf.baksmali.Adaptors.Format; import org.jf.baksmali.Adaptors.LabelMethodItem; import org.jf.baksmali.Adaptors.MethodDefinition; -import org.jf.baksmali.baksmaliOptions; +import org.jf.baksmali.BaksmaliOptions; import org.jf.dexlib2.Opcode; import org.jf.dexlib2.iface.instruction.OffsetInstruction; import org.jf.util.IndentingWriter; @@ -41,7 +41,7 @@ import java.io.IOException; public class OffsetInstructionFormatMethodItem extends InstructionMethodItem { protected LabelMethodItem label; - public OffsetInstructionFormatMethodItem(@Nonnull baksmaliOptions options, @Nonnull MethodDefinition methodDef, + public OffsetInstructionFormatMethodItem(@Nonnull BaksmaliOptions options, @Nonnull MethodDefinition methodDef, int codeAddress, OffsetInstruction instruction) { super(methodDef, codeAddress, instruction); diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/LabelMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/LabelMethodItem.java index b152bb69..268d643c 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/LabelMethodItem.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/LabelMethodItem.java @@ -28,18 +28,18 @@ package org.jf.baksmali.Adaptors; -import org.jf.baksmali.baksmaliOptions; +import org.jf.baksmali.BaksmaliOptions; import org.jf.util.IndentingWriter; import javax.annotation.Nonnull; import java.io.IOException; public class LabelMethodItem extends MethodItem { - private final baksmaliOptions options; + private final BaksmaliOptions options; private final String labelPrefix; private int labelSequence; - public LabelMethodItem(@Nonnull baksmaliOptions options, int codeAddress, @Nonnull String labelPrefix) { + public LabelMethodItem(@Nonnull BaksmaliOptions options, int codeAddress, @Nonnull String labelPrefix) { super(codeAddress); this.options = options; this.labelPrefix = labelPrefix; @@ -76,7 +76,7 @@ public class LabelMethodItem extends MethodItem { public boolean writeTo(IndentingWriter writer) throws IOException { writer.write(':'); writer.write(labelPrefix); - if (options.useSequentialLabels) { + if (options.sequentialLabels) { writer.printUnsignedLongAsHex(labelSequence); } else { writer.printUnsignedLongAsHex(this.getLabelAddress()); diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java index ef2110a8..9332111b 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java @@ -32,7 +32,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import org.jf.baksmali.Adaptors.Debug.DebugMethodItem; import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory; -import org.jf.baksmali.baksmaliOptions; +import org.jf.baksmali.BaksmaliOptions; import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.Format; import org.jf.dexlib2.Opcode; @@ -163,7 +163,7 @@ public class MethodDefinition { } public static void writeEmptyMethodTo(IndentingWriter writer, Method method, - baksmaliOptions options) throws IOException { + BaksmaliOptions options) throws IOException { writer.write(".method "); writeAccessFlags(writer, method.getAccessFlags()); writer.write(method.getName()); @@ -180,7 +180,7 @@ public class MethodDefinition { writeParameters(writer, method, methodParameters, options); String containingClass = null; - if (options.useImplicitReferences) { + if (options.implicitReferences) { containingClass = method.getDefiningClass(); } AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass); @@ -212,7 +212,7 @@ public class MethodDefinition { writer.write('\n'); writer.indent(4); - if (classDef.options.useLocalsDirective) { + if (classDef.options.localsDirective) { writer.write(".locals "); writer.printSignedIntAsDec(methodImpl.getRegisterCount() - parameterRegisterCount); } else { @@ -228,7 +228,7 @@ public class MethodDefinition { } String containingClass = null; - if (classDef.options.useImplicitReferences) { + if (classDef.options.implicitReferences) { containingClass = method.getDefiningClass(); } AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass); @@ -313,18 +313,18 @@ public class MethodDefinition { private static void writeParameters(IndentingWriter writer, Method method, List parameters, - baksmaliOptions options) throws IOException { + BaksmaliOptions options) throws IOException { boolean isStatic = AccessFlags.STATIC.isSet(method.getAccessFlags()); int registerNumber = isStatic?0:1; for (MethodParameter parameter: parameters) { String parameterType = parameter.getType(); String parameterName = parameter.getName(); Collection annotations = parameter.getAnnotations(); - if ((options.outputDebugInfo && parameterName != null) || annotations.size() != 0) { + if ((options.debugInfo && parameterName != null) || annotations.size() != 0) { writer.write(".param p"); writer.printSignedIntAsDec(registerNumber); - if (parameterName != null && options.outputDebugInfo) { + if (parameterName != null && options.debugInfo) { writer.write(", "); ReferenceFormatter.writeStringReference(writer, parameterName); } @@ -335,7 +335,7 @@ public class MethodDefinition { writer.indent(4); String containingClass = null; - if (options.useImplicitReferences) { + if (options.implicitReferences) { containingClass = method.getDefiningClass(); } AnnotationFormatter.writeTo(writer, annotations, containingClass); @@ -374,11 +374,11 @@ public class MethodDefinition { } addTries(methodItems); - if (classDef.options.outputDebugInfo) { + if (classDef.options.debugInfo) { addDebugInfo(methodItems); } - if (classDef.options.useSequentialLabels) { + if (classDef.options.sequentialLabels) { setLabelSequentialNumbers(); } @@ -415,7 +415,7 @@ public class MethodDefinition { methodItems.add(new BlankMethodItem(currentCodeAddress)); } - if (classDef.options.addCodeOffsets) { + if (classDef.options.codeOffsets) { methodItems.add(new MethodItem(currentCodeAddress) { @Override @@ -432,7 +432,7 @@ public class MethodDefinition { }); } - if (!classDef.options.noAccessorComments && (instruction instanceof ReferenceInstruction)) { + if (classDef.options.accessorComments && (instruction instanceof ReferenceInstruction)) { Opcode opcode = instruction.getOpcode(); if (opcode.referenceType == ReferenceType.METHOD) { @@ -493,7 +493,7 @@ public class MethodDefinition { methodItems.add(new BlankMethodItem(currentCodeAddress)); } - if (classDef.options.addCodeOffsets) { + if (classDef.options.codeOffsets) { methodItems.add(new MethodItem(currentCodeAddress) { @Override @@ -597,7 +597,7 @@ public class MethodDefinition { @Nullable private String getContainingClassForImplicitReference() { - if (classDef.options.useImplicitReferences) { + if (classDef.options.implicitReferences) { return classDef.classDef.getType(); } return null; diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/PostInstructionRegisterInfoMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/PostInstructionRegisterInfoMethodItem.java index 812a282a..62826b1e 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/PostInstructionRegisterInfoMethodItem.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/PostInstructionRegisterInfoMethodItem.java @@ -28,7 +28,7 @@ package org.jf.baksmali.Adaptors; -import org.jf.baksmali.baksmaliOptions; +import org.jf.baksmali.BaksmaliOptions; import org.jf.dexlib2.analysis.AnalyzedInstruction; import org.jf.dexlib2.analysis.RegisterType; import org.jf.util.IndentingWriter; @@ -60,12 +60,12 @@ public class PostInstructionRegisterInfoMethodItem extends MethodItem { int registerCount = analyzedInstruction.getRegisterCount(); BitSet registers = new BitSet(registerCount); - if ((registerInfo & baksmaliOptions.ALL) != 0) { + if ((registerInfo & BaksmaliOptions.ALL) != 0) { registers.set(0, registerCount); } else { - if ((registerInfo & baksmaliOptions.ALLPOST) != 0) { + if ((registerInfo & BaksmaliOptions.ALLPOST) != 0) { registers.set(0, registerCount); - } else if ((registerInfo & baksmaliOptions.DEST) != 0) { + } else if ((registerInfo & BaksmaliOptions.DEST) != 0) { addDestRegs(registers, registerCount); } } diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/PreInstructionRegisterInfoMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/PreInstructionRegisterInfoMethodItem.java index f5329388..f934eddb 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/PreInstructionRegisterInfoMethodItem.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/PreInstructionRegisterInfoMethodItem.java @@ -28,7 +28,7 @@ package org.jf.baksmali.Adaptors; -import org.jf.baksmali.baksmaliOptions; +import org.jf.baksmali.BaksmaliOptions; import org.jf.dexlib2.analysis.AnalyzedInstruction; import org.jf.dexlib2.analysis.MethodAnalyzer; import org.jf.dexlib2.analysis.RegisterType; @@ -68,29 +68,29 @@ public class PreInstructionRegisterInfoMethodItem extends MethodItem { BitSet registers = new BitSet(registerCount); BitSet mergeRegisters = null; - if ((registerInfo & baksmaliOptions.ALL) != 0) { + if ((registerInfo & BaksmaliOptions.ALL) != 0) { registers.set(0, registerCount); } else { - if ((registerInfo & baksmaliOptions.ALLPRE) != 0) { + if ((registerInfo & BaksmaliOptions.ALLPRE) != 0) { registers.set(0, registerCount); } else { - if ((registerInfo & baksmaliOptions.ARGS) != 0) { + if ((registerInfo & BaksmaliOptions.ARGS) != 0) { addArgsRegs(registers); } - if ((registerInfo & baksmaliOptions.MERGE) != 0) { + if ((registerInfo & BaksmaliOptions.MERGE) != 0) { if (analyzedInstruction.isBeginningInstruction()) { addParamRegs(registers, registerCount); } mergeRegisters = new BitSet(registerCount); addMergeRegs(mergeRegisters, registerCount); - } else if ((registerInfo & baksmaliOptions.FULLMERGE) != 0 && + } else if ((registerInfo & BaksmaliOptions.FULLMERGE) != 0 && (analyzedInstruction.isBeginningInstruction())) { addParamRegs(registers, registerCount); } } } - if ((registerInfo & baksmaliOptions.FULLMERGE) != 0) { + if ((registerInfo & BaksmaliOptions.FULLMERGE) != 0) { if (mergeRegisters == null) { mergeRegisters = new BitSet(registerCount); addMergeRegs(mergeRegisters, registerCount); diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/RegisterFormatter.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/RegisterFormatter.java index bffcb385..3d72f468 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/RegisterFormatter.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/RegisterFormatter.java @@ -28,7 +28,7 @@ package org.jf.baksmali.Adaptors; -import org.jf.baksmali.baksmaliOptions; +import org.jf.baksmali.BaksmaliOptions; import org.jf.util.IndentingWriter; import javax.annotation.Nonnull; @@ -38,11 +38,11 @@ import java.io.IOException; * This class contains the logic used for formatting registers */ public class RegisterFormatter { - @Nonnull public final baksmaliOptions options; + @Nonnull public final BaksmaliOptions options; public final int registerCount; public final int parameterRegisterCount; - public RegisterFormatter(@Nonnull baksmaliOptions options, int registerCount, int parameterRegisterCount) { + public RegisterFormatter(@Nonnull BaksmaliOptions options, int registerCount, int parameterRegisterCount) { this.options = options; this.registerCount = registerCount; this.parameterRegisterCount = parameterRegisterCount; @@ -58,7 +58,7 @@ public class RegisterFormatter { * @param lastRegister the last register in the range */ public void writeRegisterRange(IndentingWriter writer, int startRegister, int lastRegister) throws IOException { - if (!options.noParameterRegisters) { + if (options.parameterRegisters) { assert startRegister <= lastRegister; if (startRegister >= registerCount - parameterRegisterCount) { @@ -86,7 +86,7 @@ public class RegisterFormatter { * @param register the register number */ public void writeTo(IndentingWriter writer, int register) throws IOException { - if (!options.noParameterRegisters) { + if (options.parameterRegisters) { if (register >= registerCount - parameterRegisterCount) { writer.write('p'); writer.printSignedIntAsDec((register - (registerCount - parameterRegisterCount))); diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/Baksmali.java similarity index 60% rename from baksmali/src/main/java/org/jf/baksmali/baksmali.java rename to baksmali/src/main/java/org/jf/baksmali/Baksmali.java index 50607340..24d7416d 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java +++ b/baksmali/src/main/java/org/jf/baksmali/Baksmali.java @@ -28,105 +28,21 @@ package org.jf.baksmali; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import org.jf.baksmali.Adaptors.ClassDefinition; -import org.jf.dexlib2.analysis.ClassPath; -import org.jf.dexlib2.analysis.CustomInlineMethodResolver; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.DexFile; -import org.jf.dexlib2.util.SyntheticAccessorResolver; import org.jf.util.ClassFileNameHandler; import org.jf.util.IndentingWriter; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; +import javax.annotation.Nonnull; import java.io.*; import java.util.List; -import java.util.Map.Entry; import java.util.concurrent.*; -public class baksmali { - - public static boolean disassembleDexFile(DexFile dexFile, final baksmaliOptions options) { - if (options.registerInfo != 0 || options.deodex || options.normalizeVirtualMethods) { - try { - Iterable 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 entry: options.resourceIdFileEntries.entrySet()) { - try { - SAXParser saxp = SAXParserFactory.newInstance().newSAXParser(); - String prefix = entry.getValue(); - saxp.parse(entry.getKey(), new PublicHandler(prefix)); - } catch (ParserConfigurationException e) { - continue; - } catch (SAXException e) { - continue; - } catch (IOException e) { - continue; - } - } - } - - File outputDirectoryFile = new File(options.outputDirectory); - if (!outputDirectoryFile.exists()) { - if (!outputDirectoryFile.mkdirs()) { - System.err.println("Can't create the output directory " + options.outputDirectory); - return false; - } - } +public class Baksmali { + public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options) { //sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file //name collisions, then we'll use the same name for each class, if the dex file goes through multiple @@ -134,13 +50,9 @@ public class baksmali { //may still change of course List classDefs = Ordering.natural().sortedCopy(dexFile.getClasses()); - if (!options.noAccessorComments) { - options.syntheticAccessorResolver = new SyntheticAccessorResolver(dexFile.getOpcodes(), classDefs); - } + final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDir, ".smali"); - final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali"); - - ExecutorService executor = Executors.newFixedThreadPool(options.jobs); + ExecutorService executor = Executors.newFixedThreadPool(jobs); List> tasks = Lists.newArrayList(); for (final ClassDef classDef: classDefs) { @@ -174,7 +86,7 @@ public class baksmali { } private static boolean disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler, - baksmaliOptions options) { + BaksmaliOptions options) { /** * The path for the disassembly file is based on the package name * The class descriptor will look something like: @@ -243,4 +155,75 @@ public class baksmali { } return true; } + + @Nonnull + public static List 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"); + } + } } diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java b/baksmali/src/main/java/org/jf/baksmali/BaksmaliOptions.java similarity index 51% rename from baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java rename to baksmali/src/main/java/org/jf/baksmali/BaksmaliOptions.java index 32685ddf..77febec5 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java +++ b/baksmali/src/main/java/org/jf/baksmali/BaksmaliOptions.java @@ -31,19 +31,37 @@ package org.jf.baksmali; -import com.google.common.collect.Lists; import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.analysis.InlineMethodResolver; import org.jf.dexlib2.util.SyntheticAccessorResolver; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; -import javax.annotation.Nullable; +import javax.annotation.Nonnull; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; import java.io.File; -import java.util.Arrays; +import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.jar.Attributes; + +public class BaksmaliOptions { + public int apiLevel = 15; + + public boolean parameterRegisters = true; + public boolean localsDirective = false; + public boolean sequentialLabels = false; + public boolean debugInfo = true; + public boolean codeOffsets = false; + public boolean accessorComments = true; + public boolean allowOdex = false; + public boolean deodex = false; + public boolean experimentalOpcodes = false; + public boolean implicitReferences = false; + public boolean normalizeVirtualMethods = false; -public class baksmaliOptions { // register info values public static final int ALL = 1; public static final int ALLPRE = 2; @@ -53,56 +71,53 @@ public class baksmaliOptions { public static final int MERGE = 32; public static final int FULLMERGE = 64; - public int apiLevel = 15; - public String outputDirectory = "out"; - @Nullable public String dexEntry = null; - public List bootClassPathDirs = Lists.newArrayList(); - - public List bootClassPathEntries = Lists.newArrayList(); - public List extraClassPathEntries = Lists.newArrayList(); - - public Map resourceIdFileEntries = new HashMap(); - public Map resourceIds = new HashMap(); - - public boolean noParameterRegisters = false; - public boolean useLocalsDirective = false; - public boolean useSequentialLabels = false; - public boolean outputDebugInfo = true; - public boolean addCodeOffsets = false; - public boolean noAccessorComments = false; - public boolean allowOdex = false; - public boolean deodex = false; - public boolean experimental = false; - public boolean ignoreErrors = false; - public boolean checkPackagePrivateAccess = false; - public boolean useImplicitReferences = false; - public boolean normalizeVirtualMethods = false; - public File customInlineDefinitions = null; - public InlineMethodResolver inlineResolver = null; public int registerInfo = 0; - public ClassPath classPath = null; - public int jobs = Runtime.getRuntime().availableProcessors(); - public boolean disassemble = true; - public boolean dump = false; - public String dumpFileName = null; + public Map resourceIds = new HashMap(); + public InlineMethodResolver inlineResolver = null; + public ClassPath classPath = null; public SyntheticAccessorResolver syntheticAccessorResolver = null; - public void setBootClassPath(String bootClassPath) { - bootClassPathEntries = Lists.newArrayList(bootClassPath.split(":")); - } + /** + * Load the resource ids from a set of public.xml files. + * + * @param resourceFiles A map of resource prefixes -> public.xml files + */ + public void loadResourceIds(Map resourceFiles) { + class PublicResourceHandler extends DefaultHandler { - public void addExtraClassPath(String extraClassPath) { - if (extraClassPath.startsWith(":")) { - extraClassPath = extraClassPath.substring(1); + @Nonnull private final String prefix; + + public PublicResourceHandler(@Nonnull String prefix) { + super(); + this.prefix = prefix; + } } - extraClassPathEntries.addAll(Arrays.asList(extraClassPath.split(":"))); - } - public void setResourceIdFiles(String resourceIdFiles) { - for (String resourceIdFile: resourceIdFiles.split(":")) { - String[] entry = resourceIdFile.split("="); - resourceIdFileEntries.put(entry[1], entry[0]); + for (Map.Entry entry: resourceFiles.entrySet()) { + try { + SAXParser saxp = SAXParserFactory.newInstance().newSAXParser(); + final String prefix = entry.getKey(); + saxp.parse(entry.getValue(), new DefaultHandler() { + public void startElement(String uri, String localName, String qName, Attributes attr) + throws SAXException { + if (qName.equals("public")) { + String resourceType = attr.getValue("type"); + String resourceName = attr.getValue("name").replace('.', '_'); + Integer resourceId = Integer.decode(attr.getValue("id")); + String qualifiedResourceName = + String.format("%s.%s.%s", prefix, resourceType, resourceName); + resourceIds.put(resourceId, qualifiedResourceName); + } + } + }); + } catch (ParserConfigurationException e) { + continue; + } catch (SAXException e) { + continue; + } catch (IOException e) { + continue; + } } } } diff --git a/baksmali/src/main/java/org/jf/baksmali/Command.java b/baksmali/src/main/java/org/jf/baksmali/Command.java new file mode 100644 index 00000000..aec139d4 --- /dev/null +++ b/baksmali/src/main/java/org/jf/baksmali/Command.java @@ -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(); +} diff --git a/baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java b/baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java new file mode 100644 index 00000000..2e66ecb1 --- /dev/null +++ b/baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java @@ -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; + } +} diff --git a/baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java b/baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java new file mode 100644 index 00000000..26f04d6d --- /dev/null +++ b/baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java @@ -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 bootClassPath = new ArrayList(); + + @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 classPath = Lists.newArrayList(); + + @Parameter(names = {"-d", "--classpath-dir"}, + description = "baksmali will search these directories in order for any classpath entries.") + private List 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 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 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 = " - 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 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 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; + } +} diff --git a/baksmali/src/main/java/org/jf/baksmali/DumpCommand.java b/baksmali/src/main/java/org/jf/baksmali/DumpCommand.java new file mode 100644 index 00000000..92a4510f --- /dev/null +++ b/baksmali/src/main/java/org/jf/baksmali/DumpCommand.java @@ -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 = " - 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); + } +} diff --git a/baksmali/src/main/java/org/jf/baksmali/HelpCommand.java b/baksmali/src/main/java/org/jf/baksmali/HelpCommand.java new file mode 100644 index 00000000..612e0a1d --- /dev/null +++ b/baksmali/src/main/java/org/jf/baksmali/HelpCommand.java @@ -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 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 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); + } + } +} diff --git a/baksmali/src/main/java/org/jf/baksmali/Main.java b/baksmali/src/main/java/org/jf/baksmali/Main.java new file mode 100644 index 00000000..0ccbac80 --- /dev/null +++ b/baksmali/src/main/java/org/jf/baksmali/Main.java @@ -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; + } +} diff --git a/baksmali/src/main/java/org/jf/baksmali/dump.java b/baksmali/src/main/java/org/jf/baksmali/dump.java deleted file mode 100644 index 79405e59..00000000 --- a/baksmali/src/main/java/org/jf/baksmali/dump.java +++ /dev/null @@ -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); - } - } - } - } - } -} diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java deleted file mode 100644 index 2d6ed8c4..00000000 --- a/baksmali/src/main/java/org/jf/baksmali/main.java +++ /dev/null @@ -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", - "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 syntax instead of the p 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" + - " (.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 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"); - } - } -} diff --git a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java index 2bb04dda..9a2a7549 100644 --- a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java +++ b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java @@ -87,12 +87,12 @@ public class AnalysisTest { DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), 15, false); - baksmaliOptions options = new baksmaliOptions(); + BaksmaliOptions options = new BaksmaliOptions(); if (registerInfo) { - options.registerInfo = baksmaliOptions.ALL | baksmaliOptions.FULLMERGE; + options.registerInfo = BaksmaliOptions.ALL | BaksmaliOptions.FULLMERGE; options.classPath = new ClassPath(); } - options.useImplicitReferences = false; + options.implicitReferences = false; for (ClassDef classDef: dexFile.getClasses()) { StringWriter stringWriter = new StringWriter(); diff --git a/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java b/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java index 1c570b6c..4bcc0ea3 100644 --- a/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java +++ b/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java @@ -48,10 +48,10 @@ import java.io.StringWriter; public class BaksmaliTestUtils { public static void assertSmaliCompiledEquals(String source, String expected, - baksmaliOptions options, boolean stripComments) throws IOException, + BaksmaliOptions options, boolean stripComments) throws IOException, RecognitionException { ClassDef classDef = SmaliTestUtils.compileSmali(source, options.apiLevel, - options.experimental); + options.experimentalOpcodes); // Remove unnecessary whitespace and optionally strip all comments from smali file String normalizedActual = getNormalizedSmali(classDef, options, stripComments); @@ -62,13 +62,13 @@ public class BaksmaliTestUtils { } public static void assertSmaliCompiledEquals(String source, String expected, - baksmaliOptions options) throws IOException, RecognitionException { + BaksmaliOptions options) throws IOException, RecognitionException { assertSmaliCompiledEquals(source, expected, options, false); } public static void assertSmaliCompiledEquals(String source, String expected) throws IOException, RecognitionException { - baksmaliOptions options = new baksmaliOptions(); + BaksmaliOptions options = new BaksmaliOptions(); assertSmaliCompiledEquals(source, expected, options); } @@ -81,7 +81,7 @@ public class BaksmaliTestUtils { } @Nonnull - public static String getNormalizedSmali(@Nonnull ClassDef classDef, @Nonnull baksmaliOptions options, + public static String getNormalizedSmali(@Nonnull ClassDef classDef, @Nonnull BaksmaliOptions options, boolean stripComments) throws IOException { StringWriter stringWriter = new StringWriter(); diff --git a/baksmali/src/test/java/org/jf/baksmali/DexTest.java b/baksmali/src/test/java/org/jf/baksmali/DexTest.java index 5a4db658..f9f55622 100644 --- a/baksmali/src/test/java/org/jf/baksmali/DexTest.java +++ b/baksmali/src/test/java/org/jf/baksmali/DexTest.java @@ -65,7 +65,7 @@ public abstract class DexTest { } @Nonnull - protected DexBackedDexFile getInputDexFile(@Nonnull String testName, @Nonnull baksmaliOptions options) { + protected DexBackedDexFile getInputDexFile(@Nonnull String testName, @Nonnull BaksmaliOptions options) { try { // Load file from resources as a stream byte[] inputBytes = BaksmaliTestUtils.readResourceBytesFully(getInputFilename(testName)); diff --git a/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java b/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java index 1a34e8c3..769372eb 100644 --- a/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java +++ b/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java @@ -57,10 +57,10 @@ public class DisassemblyTest extends DexTest { } protected void runTest(@Nonnull String testName) { - runTest(testName, new baksmaliOptions()); + runTest(testName, new BaksmaliOptions()); } - protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) { + protected void runTest(@Nonnull String testName, @Nonnull BaksmaliOptions options) { try { DexBackedDexFile inputDex = getInputDexFile(testName, options); Assert.assertEquals(1, inputDex.getClassCount()); diff --git a/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java b/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java index 78fabc0b..ad2aad5b 100644 --- a/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java +++ b/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java @@ -42,7 +42,7 @@ import org.junit.Test; public class FieldGapOrderTest extends DexTest { @Test public void testOldOrder() { - DexFile dexFile = getInputDexFile("FieldGapOrder", new baksmaliOptions()); + DexFile dexFile = getInputDexFile("FieldGapOrder", new BaksmaliOptions()); Assert.assertEquals(3, dexFile.getClasses().size()); ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), false, 66); @@ -56,7 +56,7 @@ public class FieldGapOrderTest extends DexTest { @Test public void testNewOrder() { - DexFile dexFile = getInputDexFile("FieldGapOrder", new baksmaliOptions()); + DexFile dexFile = getInputDexFile("FieldGapOrder", new BaksmaliOptions()); Assert.assertEquals(3, dexFile.getClasses().size()); ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), false, 67); diff --git a/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java index 1f2ae5bf..962a6be7 100644 --- a/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java +++ b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java @@ -62,8 +62,8 @@ public class ImplicitReferenceTest { "return-void\n" + ".end method\n"; - baksmaliOptions options = new baksmaliOptions(); - options.useImplicitReferences = true; + BaksmaliOptions options = new BaksmaliOptions(); + options.implicitReferences = true; BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @@ -93,8 +93,8 @@ public class ImplicitReferenceTest { " return-void\n" + ".end method\n"; - baksmaliOptions options = new baksmaliOptions(); - options.useImplicitReferences = false; + BaksmaliOptions options = new BaksmaliOptions(); + options.implicitReferences = false; BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @@ -118,8 +118,8 @@ public class ImplicitReferenceTest { ".field public static field3:Ljava/lang/reflect/Method; = I()V\n" + ".field public static field4:Ljava/lang/Class; = I\n"; - baksmaliOptions options = new baksmaliOptions(); - options.useImplicitReferences = true; + BaksmaliOptions options = new BaksmaliOptions(); + options.implicitReferences = true; BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @@ -143,8 +143,8 @@ public class ImplicitReferenceTest { ".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" + ".field public static field4:Ljava/lang/Class; = I\n"; - baksmaliOptions options = new baksmaliOptions(); - options.useImplicitReferences = false; + BaksmaliOptions options = new BaksmaliOptions(); + options.implicitReferences = false; BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @@ -174,8 +174,8 @@ public class ImplicitReferenceTest { " return-void\n" + ".end method\n"; - baksmaliOptions options = new baksmaliOptions(); - options.useImplicitReferences = true; + BaksmaliOptions options = new BaksmaliOptions(); + options.implicitReferences = true; BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @@ -205,8 +205,8 @@ public class ImplicitReferenceTest { " return-void\n" + ".end method\n"; - baksmaliOptions options = new baksmaliOptions(); - options.useImplicitReferences = false; + BaksmaliOptions options = new BaksmaliOptions(); + options.implicitReferences = false; BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @@ -228,8 +228,8 @@ public class ImplicitReferenceTest { ".field public static field2:Ljava/lang/reflect/Field; = V:I\n" + ".field public static field3:Ljava/lang/reflect/Field; = I:I\n"; - baksmaliOptions options = new baksmaliOptions(); - options.useImplicitReferences = true; + BaksmaliOptions options = new BaksmaliOptions(); + options.implicitReferences = true; BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @@ -251,8 +251,8 @@ public class ImplicitReferenceTest { ".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" + ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I\n"; - baksmaliOptions options = new baksmaliOptions(); - options.useImplicitReferences = false; + BaksmaliOptions options = new BaksmaliOptions(); + options.implicitReferences = false; BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } diff --git a/baksmali/src/test/java/org/jf/baksmali/InterfaceOrderTest.java b/baksmali/src/test/java/org/jf/baksmali/InterfaceOrderTest.java index d85d7913..f1ade1e9 100644 --- a/baksmali/src/test/java/org/jf/baksmali/InterfaceOrderTest.java +++ b/baksmali/src/test/java/org/jf/baksmali/InterfaceOrderTest.java @@ -36,6 +36,6 @@ import org.junit.Test; public class InterfaceOrderTest extends IdenticalRoundtripTest { @Test public void testInterfaceOrder() { - runTest("InterfaceOrder", new baksmaliOptions()); + runTest("InterfaceOrder", new BaksmaliOptions()); } } diff --git a/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java b/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java index 5431df54..9dd3237f 100644 --- a/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java +++ b/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java @@ -35,10 +35,10 @@ import org.junit.Test; public class LambdaTest extends IdenticalRoundtripTest { - private baksmaliOptions createOptions() { - baksmaliOptions options = new baksmaliOptions(); + private BaksmaliOptions createOptions() { + BaksmaliOptions options = new BaksmaliOptions(); options.apiLevel = 23; // since we need at least level 23 for lambda opcodes - options.experimental = true; // since these opcodes aren't implemented in runtime yet); + options.experimentalOpcodes = true; // since these opcodes aren't implemented in runtime yet); return options; } diff --git a/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java b/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java index c9ff2d4d..81e98a30 100644 --- a/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java +++ b/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java @@ -69,10 +69,10 @@ public abstract class RoundtripTest { } protected void runTest(@Nonnull String testName) { - runTest(testName, new baksmaliOptions()); + runTest(testName, new BaksmaliOptions()); } - protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) { + protected void runTest(@Nonnull String testName, @Nonnull BaksmaliOptions options) { try { // Load file from resources as a stream String inputFilename = getInputFilename(testName); diff --git a/build.gradle b/build.gradle index 80ac25f8..7f9c36af 100644 --- a/build.gradle +++ b/build.gradle @@ -109,7 +109,8 @@ subprojects { jflex_plugin: 'co.tomlee.gradle.plugins:gradle-jflex-plugin:0.0.2', proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1', dx: 'com.google.android.tools:dx:1.7', - gson: 'com.google.code.gson:gson:2.3.1' + gson: 'com.google.code.gson:gson:2.3.1', + jcommander: 'com.beust:jcommander:1.48' ] } diff --git a/util/build.gradle b/util/build.gradle index 407ef71f..290c3c7d 100644 --- a/util/build.gradle +++ b/util/build.gradle @@ -33,6 +33,7 @@ dependencies { compile depends.commons_cli compile depends.findbugs compile depends.guava + compile depends.jcommander testCompile depends.junit } diff --git a/util/src/main/java/org/jf/util/StringWrapper.java b/util/src/main/java/org/jf/util/StringWrapper.java index 91808300..052924fb 100644 --- a/util/src/main/java/org/jf/util/StringWrapper.java +++ b/util/src/main/java/org/jf/util/StringWrapper.java @@ -33,8 +33,89 @@ package org.jf.util; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.text.BreakIterator; +import java.util.Iterator; public class StringWrapper { + /** + * Splits the given string into lines of maximum width maxWidth. The splitting is done using the current locale's + * rules for splitting lines. + * + * @param string The string to split + * @param maxWidth The maximum length of any line + * @return An iterable of Strings containing the wrapped lines + */ + public static Iterable wrapStringOnBreaks(@Nonnull final String string, final int maxWidth) { + final BreakIterator breakIterator = BreakIterator.getLineInstance(); + breakIterator.setText(string); + + return new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + private int currentLineStart = 0; + private boolean nextLineSet = false; + private String nextLine; + + @Override + public boolean hasNext() { + if (!nextLineSet) { + calculateNext(); + } + return nextLine != null; + } + + private void calculateNext() { + int lineEnd = currentLineStart; + while (true) { + lineEnd = breakIterator.following(lineEnd); + if (lineEnd == BreakIterator.DONE) { + lineEnd = breakIterator.last(); + if (lineEnd <= currentLineStart) { + nextLine = null; + nextLineSet = true; + return; + } + break; + } + + if (lineEnd - currentLineStart > maxWidth) { + lineEnd = breakIterator.preceding(lineEnd); + if (lineEnd <= currentLineStart) { + lineEnd = currentLineStart + maxWidth; + } + break; + } + + if (string.charAt(lineEnd-1) == '\n') { + nextLine = string.substring(currentLineStart, lineEnd-1); + nextLineSet = true; + currentLineStart = lineEnd; + return; + } + } + nextLine = string.substring(currentLineStart, lineEnd); + nextLineSet = true; + currentLineStart = lineEnd; + } + + @Override + public String next() { + String ret = nextLine; + nextLine = null; + nextLineSet = false; + return ret; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + /** * Splits the given string into lines using on any embedded newlines, and wrapping the text as needed to conform to * the given maximum line width. diff --git a/util/src/main/java/org/jf/util/jcommander/CommaColonParameterSplitter.java b/util/src/main/java/org/jf/util/jcommander/CommaColonParameterSplitter.java new file mode 100644 index 00000000..b0b9e804 --- /dev/null +++ b/util/src/main/java/org/jf/util/jcommander/CommaColonParameterSplitter.java @@ -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 split(String value) { + return Arrays.asList(value.split(":|,")); + } +} diff --git a/util/src/test/java/org/jf/util/StringWrapperTest.java b/util/src/test/java/org/jf/util/StringWrapperTest.java index 64dca33e..94c79142 100644 --- a/util/src/test/java/org/jf/util/StringWrapperTest.java +++ b/util/src/test/java/org/jf/util/StringWrapperTest.java @@ -31,10 +31,34 @@ package org.jf.util; +import com.google.common.collect.Lists; import org.junit.Assert; import org.junit.Test; +import java.util.List; + public class StringWrapperTest { + @Test + public void testWrapStringByWords() { + validateResult2(new String[]{"abc", "abcdef", "abcdef"}, + "abc\nabcdefabcdef", 6); + + validateResult2(new String[]{"abc", "abcdef", " ", "abcdef"}, + "abc\nabcdef abcdef", 6); + + validateResult2(new String[]{"abc", "abcde ", "fabcde", "f"}, + "abc\nabcde fabcdef", 6); + + validateResult2(new String[]{"abc def ghi ", "kjl mon pqr ", "stu vwx yz"}, + "abc def ghi kjl mon pqr stu vwx yz", 14); + + validateResult2(new String[]{"abcdefg", "hikjlmo", "npqrstu", "vwxyz"}, + "abcdefghikjlmonpqrstuvwxyz", 7); + + validateResult2(new String[]{"abc", "defhig"}, + "abc\ndefhig", 20); + } + @Test public void testWrapString() { validateResult( @@ -115,4 +139,15 @@ public class StringWrapperTest { Assert.assertEquals(expected[i], actual[i]); } } + + public static void validateResult2(String[] expected, String textToWrap, int maxWidth) { + List result = Lists.newArrayList(StringWrapper.wrapStringOnBreaks(textToWrap, maxWidth)); + + Assert.assertEquals(expected.length, result.size()); + int i; + for (i=0; i