Add helper comments for synthetic member accessors

This adds a comment before a synthetic member accessor is called, which
mentions what member in the parent is being accessed

Kudos to jasta for suggesting this feature!

git-svn-id: https://smali.googlecode.com/svn/trunk@809 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
jesusfreke@jesusfreke.com
2011-05-20 06:16:22 +00:00
parent 7ed253b78b
commit 2f376953b4
7 changed files with 342 additions and 3 deletions

View File

@ -29,6 +29,8 @@
package org.jf.baksmali.Adaptors; package org.jf.baksmali.Adaptors;
import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory; 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.util.IndentingWriter;
import org.jf.baksmali.baksmali; import org.jf.baksmali.baksmali;
import org.jf.dexlib.*; 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); currentCodeAddress += instruction.getSize(currentCodeAddress);
} }
} }

View File

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

View File

@ -31,6 +31,7 @@ package org.jf.baksmali;
import org.jf.baksmali.Adaptors.ClassDefinition; import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.dexlib.ClassDefItem; import org.jf.dexlib.ClassDefItem;
import org.jf.dexlib.Code.Analysis.ClassPath; import org.jf.dexlib.Code.Analysis.ClassPath;
import org.jf.dexlib.Code.Analysis.SyntheticAccessorResolver;
import org.jf.dexlib.DexFile; import org.jf.dexlib.DexFile;
import org.jf.util.ClassFileNameHandler; import org.jf.util.ClassFileNameHandler;
import org.jf.util.IndentingWriter; import org.jf.util.IndentingWriter;
@ -48,22 +49,27 @@ public class baksmali {
public static boolean useSequentialLabels = false; public static boolean useSequentialLabels = false;
public static boolean outputDebugInfo = true; public static boolean outputDebugInfo = true;
public static boolean addCodeOffsets = false; public static boolean addCodeOffsets = false;
public static boolean noAccessorComments = false;
public static boolean deodex = false; public static boolean deodex = false;
public static boolean verify = false; public static boolean verify = false;
public static int registerInfo = 0; public static int registerInfo = 0;
public static String bootClassPath; public static String bootClassPath;
public static SyntheticAccessorResolver syntheticAccessorResolver = null;
public static void disassembleDexFile(String dexFilePath, DexFile dexFile, boolean deodex, String outputDirectory, public static void disassembleDexFile(String dexFilePath, DexFile dexFile, boolean deodex, String outputDirectory,
String[] classPathDirs, String bootClassPath, String extraBootClassPath, String[] classPathDirs, String bootClassPath, String extraBootClassPath,
boolean noParameterRegisters, boolean useLocalsDirective, boolean noParameterRegisters, boolean useLocalsDirective,
boolean useSequentialLabels, boolean outputDebugInfo, boolean addCodeOffsets, boolean useSequentialLabels, boolean outputDebugInfo, boolean addCodeOffsets,
int registerInfo, boolean verify, boolean ignoreErrors) boolean noAccessorComments, int registerInfo, boolean verify,
boolean ignoreErrors)
{ {
baksmali.noParameterRegisters = noParameterRegisters; baksmali.noParameterRegisters = noParameterRegisters;
baksmali.useLocalsDirective = useLocalsDirective; baksmali.useLocalsDirective = useLocalsDirective;
baksmali.useSequentialLabels = useSequentialLabels; baksmali.useSequentialLabels = useSequentialLabels;
baksmali.outputDebugInfo = outputDebugInfo; baksmali.outputDebugInfo = outputDebugInfo;
baksmali.addCodeOffsets = addCodeOffsets; baksmali.addCodeOffsets = addCodeOffsets;
baksmali.noAccessorComments = noAccessorComments;
baksmali.deodex = deodex; baksmali.deodex = deodex;
baksmali.registerInfo = registerInfo; baksmali.registerInfo = registerInfo;
baksmali.bootClassPath = bootClassPath; 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 //sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file
//name collisions, then we'll use the same name for each class, if the dex file goes through multiple //name collisions, then we'll use the same name for each class, if the dex file goes through multiple
//baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames //baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames

View File

@ -103,6 +103,7 @@ public class main {
boolean useSequentialLabels = false; boolean useSequentialLabels = false;
boolean outputDebugInfo = true; boolean outputDebugInfo = true;
boolean addCodeOffsets = false; boolean addCodeOffsets = false;
boolean noAccessorComments = false;
boolean deodex = false; boolean deodex = false;
boolean verify = false; boolean verify = false;
boolean ignoreErrors = false; boolean ignoreErrors = false;
@ -204,6 +205,9 @@ public class main {
case 'x': case 'x':
deodex = true; deodex = true;
break; break;
case 'm':
noAccessorComments = true;
break;
case 'N': case 'N':
disassemble = false; disassemble = false;
break; break;
@ -278,7 +282,7 @@ public class main {
baksmali.disassembleDexFile(dexFileFile.getPath(), dexFile, deodex, outputDirectory, baksmali.disassembleDexFile(dexFileFile.getPath(), dexFile, deodex, outputDirectory,
bootClassPathDirsArray, bootClassPath, extraBootClassPathEntries.toString(), bootClassPathDirsArray, bootClassPath, extraBootClassPathEntries.toString(),
noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, addCodeOffsets, noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, addCodeOffsets,
registerInfo, verify, ignoreErrors); noAccessorComments, registerInfo, verify, ignoreErrors);
} }
if ((doDump || write) && !dexFile.isOdex()) { 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") .withDescription("add comments to the disassembly containing the code offset for each address")
.create("f"); .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") Option dumpOption = OptionBuilder.withLongOpt("dump-to")
.withDescription("dumps the given dex file into a single annotated dump file named FILE" + .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(classPathOption);
basicOptions.addOption(classPathDirOption); basicOptions.addOption(classPathDirOption);
basicOptions.addOption(codeOffsetOption); basicOptions.addOption(codeOffsetOption);
basicOptions.addOption(noAccessorCommentsOption);
debugOptions.addOption(dumpOption); debugOptions.addOption(dumpOption);
debugOptions.addOption(ignoreErrorsOption); debugOptions.addOption(ignoreErrorsOption);

View File

@ -343,6 +343,60 @@ public class ClassDataItem extends Item<ClassDataItem> {
return virtualMethods; 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<max) {
int index = (min+max)>>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<EncodedField> { public static class EncodedField implements Comparable<EncodedField> {
/** /**
* The <code>FieldIdItem</code> that this <code>EncodedField</code> is associated with * The <code>FieldIdItem</code> that this <code>EncodedField</code> is associated with

View File

@ -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<String, ClassDefItem> definedClasses = new HashMap<String, ClassDefItem>();
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());
}
}

View File

@ -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<MethodIdItem, AccessedMember> resolvedAccessors = new HashMap<MethodIdItem, AccessedMember>();
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;
}
}
}