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 b37d6d12..1cc537de 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java @@ -29,6 +29,8 @@ package org.jf.baksmali.Adaptors; import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory; +import org.jf.dexlib.Code.Analysis.SyntheticAccessorResolver; +import org.jf.dexlib.Code.InstructionWithReference; import org.jf.util.IndentingWriter; import org.jf.baksmali.baksmali; import org.jf.dexlib.*; @@ -343,6 +345,21 @@ public class MethodDefinition { }); } + if (instruction instanceof InstructionWithReference) { + if (instruction.opcode == Opcode.INVOKE_STATIC || instruction.opcode == Opcode.INVOKE_STATIC_RANGE) { + MethodIdItem methodIdItem = + (MethodIdItem)((InstructionWithReference) instruction).getReferencedItem(); + + if (SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodIdItem)) { + SyntheticAccessorResolver.AccessedMember accessedMember = + baksmali.syntheticAccessorResolver.getAccessedMember(methodIdItem); + if (accessedMember != null) { + methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress)); + } + } + } + } + currentCodeAddress += instruction.getSize(currentCodeAddress); } } diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/SyntheticAccessCommentMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/SyntheticAccessCommentMethodItem.java new file mode 100644 index 00000000..f32d7aa0 --- /dev/null +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/SyntheticAccessCommentMethodItem.java @@ -0,0 +1,62 @@ +/* + * [The "BSD licence"] + * Copyright (c) 2011 Ben Gruver + * 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.Adaptors; + +import org.jf.dexlib.Code.Analysis.SyntheticAccessorResolver; +import static org.jf.dexlib.Code.Analysis.SyntheticAccessorResolver.AccessedMember; +import org.jf.util.IndentingWriter; + +import java.io.IOException; + +public class SyntheticAccessCommentMethodItem extends MethodItem { + private final AccessedMember accessedMember; + + public SyntheticAccessCommentMethodItem(AccessedMember accessedMember, int codeAddress) { + super(codeAddress); + this.accessedMember = accessedMember; + } + + public double getSortOrder() { + //just before the pre-instruction register information, if any + return 99.8; + } + + public boolean writeTo(IndentingWriter writer) throws IOException { + writer.write('#'); + if (accessedMember.getAccessedMemberType() == SyntheticAccessorResolver.METHOD) { + writer.write("calls: "); + } else if (accessedMember.getAccessedMemberType() == SyntheticAccessorResolver.GETTER) { + writer.write("getter for: "); + } else { + writer.write("setter for: "); + } + ReferenceFormatter.writeReference(writer, accessedMember.getAccessedMember()); + return true; + } +} diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/baksmali.java index 793694fe..f746ed90 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmali.java @@ -31,6 +31,7 @@ package org.jf.baksmali; import org.jf.baksmali.Adaptors.ClassDefinition; import org.jf.dexlib.ClassDefItem; import org.jf.dexlib.Code.Analysis.ClassPath; +import org.jf.dexlib.Code.Analysis.SyntheticAccessorResolver; import org.jf.dexlib.DexFile; import org.jf.util.ClassFileNameHandler; import org.jf.util.IndentingWriter; @@ -48,22 +49,27 @@ public class baksmali { public static boolean useSequentialLabels = false; public static boolean outputDebugInfo = true; public static boolean addCodeOffsets = false; + public static boolean noAccessorComments = false; public static boolean deodex = false; public static boolean verify = false; public static int registerInfo = 0; public static String bootClassPath; + public static SyntheticAccessorResolver syntheticAccessorResolver = null; + public static void disassembleDexFile(String dexFilePath, DexFile dexFile, boolean deodex, String outputDirectory, String[] classPathDirs, String bootClassPath, String extraBootClassPath, boolean noParameterRegisters, boolean useLocalsDirective, boolean useSequentialLabels, boolean outputDebugInfo, boolean addCodeOffsets, - int registerInfo, boolean verify, boolean ignoreErrors) + boolean noAccessorComments, int registerInfo, boolean verify, + boolean ignoreErrors) { baksmali.noParameterRegisters = noParameterRegisters; baksmali.useLocalsDirective = useLocalsDirective; baksmali.useSequentialLabels = useSequentialLabels; baksmali.outputDebugInfo = outputDebugInfo; baksmali.addCodeOffsets = addCodeOffsets; + baksmali.noAccessorComments = noAccessorComments; baksmali.deodex = deodex; baksmali.registerInfo = registerInfo; baksmali.bootClassPath = bootClassPath; @@ -120,6 +126,10 @@ public class baksmali { } } + if (!noAccessorComments) { + syntheticAccessorResolver = new SyntheticAccessorResolver(dexFile); + } + //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 //baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java index d72c4523..7e7c15a6 100644 --- a/baksmali/src/main/java/org/jf/baksmali/main.java +++ b/baksmali/src/main/java/org/jf/baksmali/main.java @@ -103,6 +103,7 @@ public class main { boolean useSequentialLabels = false; boolean outputDebugInfo = true; boolean addCodeOffsets = false; + boolean noAccessorComments = false; boolean deodex = false; boolean verify = false; boolean ignoreErrors = false; @@ -204,6 +205,9 @@ public class main { case 'x': deodex = true; break; + case 'm': + noAccessorComments = true; + break; case 'N': disassemble = false; break; @@ -278,7 +282,7 @@ public class main { baksmali.disassembleDexFile(dexFileFile.getPath(), dexFile, deodex, outputDirectory, bootClassPathDirsArray, bootClassPath, extraBootClassPathEntries.toString(), noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, addCodeOffsets, - registerInfo, verify, ignoreErrors); + noAccessorComments, registerInfo, verify, ignoreErrors); } if ((doDump || write) && !dexFile.isOdex()) { @@ -407,7 +411,9 @@ public class main { .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 dumpOption = OptionBuilder.withLongOpt("dump-to") .withDescription("dumps the given dex file into a single annotated dump file named FILE" + @@ -457,6 +463,7 @@ public class main { basicOptions.addOption(classPathOption); basicOptions.addOption(classPathDirOption); basicOptions.addOption(codeOffsetOption); + basicOptions.addOption(noAccessorCommentsOption); debugOptions.addOption(dumpOption); debugOptions.addOption(ignoreErrorsOption); diff --git a/dexlib/src/main/java/org/jf/dexlib/ClassDataItem.java b/dexlib/src/main/java/org/jf/dexlib/ClassDataItem.java index c809d354..89a6965e 100644 --- a/dexlib/src/main/java/org/jf/dexlib/ClassDataItem.java +++ b/dexlib/src/main/java/org/jf/dexlib/ClassDataItem.java @@ -343,6 +343,60 @@ public class ClassDataItem extends Item { return virtualMethods; } + /** + * Performs a binary search for the definition of the specified direct method + * @param methodIdItem The MethodIdItem of the direct method to search for + * @return The EncodedMethod for the specified direct method, or null if not found + */ + public EncodedMethod findDirectMethodByMethodId(MethodIdItem methodIdItem) { + return findMethodByMethodIdInternal(methodIdItem.index, directMethods); + } + + /** + * Performs a binary search for the definition of the specified virtual method + * @param methodIdItem The MethodIdItem of the virtual method to search for + * @return The EncodedMethod for the specified virtual method, or null if not found + */ + public EncodedMethod findVirtualMethodByMethodId(MethodIdItem methodIdItem) { + return findMethodByMethodIdInternal(methodIdItem.index, virtualMethods); + } + + /** + * Performs a binary search for the definition of the specified method. It can be either direct or virtual + * @param methodIdItem The MethodIdItem of the virtual method to search for + * @return The EncodedMethod for the specified virtual method, or null if not found + */ + public EncodedMethod findMethodByMethodId(MethodIdItem methodIdItem) { + EncodedMethod encodedMethod = findMethodByMethodIdInternal(methodIdItem.index, directMethods); + if (encodedMethod != null) { + return encodedMethod; + } + + return findMethodByMethodIdInternal(methodIdItem.index, virtualMethods); + } + + private static EncodedMethod findMethodByMethodIdInternal(int methodIdItemIndex, EncodedMethod[] encodedMethods) { + int min = 0; + int max = encodedMethods.length; + + while (min>1; + + EncodedMethod encodedMethod = encodedMethods[index]; + + int encodedMethodIndex = encodedMethod.method.getIndex(); + if (encodedMethodIndex == methodIdItemIndex) { + return encodedMethod; + } else if (encodedMethodIndex < methodIdItemIndex) { + min = index; + } else { + max = index; + } + } + + return null; + } + public static class EncodedField implements Comparable { /** * The FieldIdItem that this EncodedField is associated with diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DexFileClassMap.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DexFileClassMap.java new file mode 100644 index 00000000..ca442687 --- /dev/null +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DexFileClassMap.java @@ -0,0 +1,56 @@ +/* + * [The "BSD licence"] + * Copyright (c) 2011 Ben Gruver + * 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.dexlib.Code.Analysis; + +import org.jf.dexlib.ClassDefItem; +import org.jf.dexlib.DexFile; +import org.jf.dexlib.TypeIdItem; + +import java.util.HashMap; + +/** + * Keeps a simple map of classes defined in a dex file, allowing you to look them up by TypeIdItem or name + */ +public class DexFileClassMap { + private final HashMap definedClasses = new HashMap(); + + public DexFileClassMap(DexFile dexFile) { + for (ClassDefItem classDefItem: dexFile.ClassDefsSection.getItems()) { + definedClasses.put(classDefItem.getClassType().getTypeDescriptor(), classDefItem); + } + } + + public ClassDefItem getClassDefByName(String typeName) { + return definedClasses.get(typeName); + } + + public ClassDefItem getClassDefByType(TypeIdItem typeIdItem) { + return definedClasses.get(typeIdItem.getTypeDescriptor()); + } +} diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/SyntheticAccessorResolver.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/SyntheticAccessorResolver.java new file mode 100644 index 00000000..f370569e --- /dev/null +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/SyntheticAccessorResolver.java @@ -0,0 +1,133 @@ +/* + * [The "BSD licence"] + * Copyright (c) 2011 Ben Gruver + * 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.dexlib.Code.Analysis; + +import org.jf.dexlib.*; +import org.jf.dexlib.Code.Format.Instruction22c; +import org.jf.dexlib.Code.Instruction; +import org.jf.dexlib.Code.InstructionWithReference; +import org.jf.dexlib.Util.AccessFlags; + +import java.util.HashMap; + +public class SyntheticAccessorResolver { + public static final int METHOD = 0; + public static final int GETTER = 1; + public static final int SETTER = 2; + + private final DexFileClassMap classMap; + private final HashMap resolvedAccessors = new HashMap(); + + public SyntheticAccessorResolver(DexFile dexFile) { + classMap = new DexFileClassMap(dexFile); + } + + public static boolean looksLikeSyntheticAccessor(MethodIdItem methodIdItem) { + return methodIdItem.getMethodName().getStringValue().startsWith("access$"); + } + + public AccessedMember getAccessedMember(MethodIdItem methodIdItem) { + AccessedMember accessedMember = resolvedAccessors.get(methodIdItem); + if (accessedMember != null) { + return accessedMember; + } + + ClassDefItem classDefItem = classMap.getClassDefByType(methodIdItem.getContainingClass()); + if (classDefItem == null) { + return null; + } + + ClassDataItem.EncodedMethod encodedMethod = classDefItem.getClassData().findDirectMethodByMethodId(methodIdItem); + if (encodedMethod == null) { + return null; + } + + //A synthetic accessor will be marked synthetic + if ((encodedMethod.accessFlags & AccessFlags.SYNTHETIC.getValue()) == 0) { + return null; + } + + Instruction[] instructions = encodedMethod.codeItem.getInstructions(); + + //TODO: add support for odexed formats + switch (instructions[0].opcode.format) { + case Format35c: + case Format3rc: { + //a synthetic method access should be either 2 or 3 instructions, depending on if the method returns + //anything or not + if (instructions.length < 2 || instructions.length > 3) { + return null; + } + InstructionWithReference instruction = (InstructionWithReference)instructions[0]; + MethodIdItem referencedMethodIdItem = (MethodIdItem)instruction.getReferencedItem(); + + accessedMember = new AccessedMember(METHOD, referencedMethodIdItem); + resolvedAccessors.put(methodIdItem, accessedMember); + return accessedMember; + } + case Format22c: { + //a synthetic field access should be exactly 2 instructions. The set/put, and then the return + if (instructions.length != 2) { + return null; + } + Instruction22c instruction = (Instruction22c)instructions[0]; + FieldIdItem referencedFieldIdItem = (FieldIdItem)instruction.getReferencedItem(); + + if (instruction.opcode.setsRegister() || instruction.opcode.setsWideRegister()) { + accessedMember = new AccessedMember(GETTER, referencedFieldIdItem); + } else { + accessedMember = new AccessedMember(SETTER, referencedFieldIdItem); + } + + resolvedAccessors.put(methodIdItem, accessedMember); + return accessedMember; + } + default: + return null; + } + } + + public static class AccessedMember { + private final int accessedMemberType; + private final Item accessedMember; + + public AccessedMember(int accessedMemberType, Item accessedMember) { + this.accessedMemberType = accessedMemberType; + this.accessedMember = accessedMember; + } + + public int getAccessedMemberType() { + return accessedMemberType; + } + + public Item getAccessedMember() { + return accessedMember; + } + } +}