/* * [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.Adaptors; 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.dexlib2.AccessFlags; import org.jf.dexlib2.Format; import org.jf.dexlib2.Opcode; import org.jf.dexlib2.ReferenceType; import org.jf.dexlib2.analysis.AnalysisException; import org.jf.dexlib2.analysis.AnalyzedInstruction; import org.jf.dexlib2.analysis.MethodAnalyzer; import org.jf.dexlib2.dexbacked.DexBackedDexFile.InvalidItemIndex; import org.jf.dexlib2.iface.*; import org.jf.dexlib2.iface.debug.DebugItem; import org.jf.dexlib2.iface.instruction.Instruction; import org.jf.dexlib2.iface.instruction.OffsetInstruction; import org.jf.dexlib2.iface.instruction.ReferenceInstruction; import org.jf.dexlib2.iface.instruction.formats.Instruction31t; import org.jf.dexlib2.iface.reference.MethodReference; import org.jf.dexlib2.immutable.instruction.ImmutableInstruction31t; import org.jf.dexlib2.util.InstructionOffsetMap; import org.jf.dexlib2.util.InstructionOffsetMap.InvalidInstructionOffset; import org.jf.dexlib2.util.ReferenceUtil; import org.jf.dexlib2.util.SyntheticAccessorResolver; import org.jf.dexlib2.util.SyntheticAccessorResolver.AccessedMember; import org.jf.dexlib2.util.TypeUtils; import org.jf.util.ExceptionWithContext; import org.jf.util.IndentingWriter; import org.jf.util.SparseIntArray; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; import java.util.*; public class MethodDefinition { @Nonnull public final ClassDefinition classDef; @Nonnull public final Method method; @Nonnull public final MethodImplementation methodImpl; @Nonnull public final ImmutableList instructions; @Nonnull public final List effectiveInstructions; @Nonnull public final ImmutableList methodParameters; public RegisterFormatter registerFormatter; @Nonnull private final LabelCache labelCache = new LabelCache(); @Nonnull private final SparseIntArray packedSwitchMap; @Nonnull private final SparseIntArray sparseSwitchMap; @Nonnull private final InstructionOffsetMap instructionOffsetMap; public MethodDefinition(@Nonnull ClassDefinition classDef, @Nonnull Method method, @Nonnull MethodImplementation methodImpl) { this.classDef = classDef; this.method = method; this.methodImpl = methodImpl; try { //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh. instructions = ImmutableList.copyOf(methodImpl.getInstructions()); methodParameters = ImmutableList.copyOf(method.getParameters()); effectiveInstructions = Lists.newArrayList(instructions); packedSwitchMap = new SparseIntArray(0); sparseSwitchMap = new SparseIntArray(0); instructionOffsetMap = new InstructionOffsetMap(instructions); int endOffset = instructionOffsetMap.getInstructionCodeOffset(instructions.size()-1) + instructions.get(instructions.size()-1).getCodeUnits(); for (int i=0; i methodParameters = ImmutableList.copyOf(method.getParameters()); for (MethodParameter parameter: methodParameters) { writer.write(parameter.getType()); } writer.write(")"); writer.write(method.getReturnType()); writer.write('\n'); writer.indent(4); writeParameters(writer, method, methodParameters, options); String containingClass = null; if (options.implicitReferences) { containingClass = method.getDefiningClass(); } AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass); writer.deindent(4); writer.write(".end method\n"); } public void writeTo(IndentingWriter writer) throws IOException { int parameterRegisterCount = 0; if (!AccessFlags.STATIC.isSet(method.getAccessFlags())) { parameterRegisterCount++; } writer.write(".method "); writeAccessFlags(writer, method.getAccessFlags()); writer.write(method.getName()); writer.write("("); for (MethodParameter parameter: methodParameters) { String type = parameter.getType(); writer.write(type); parameterRegisterCount++; if (TypeUtils.isWideType(type)) { parameterRegisterCount++; } } writer.write(")"); writer.write(method.getReturnType()); writer.write('\n'); writer.indent(4); if (classDef.options.localsDirective) { writer.write(".locals "); writer.printSignedIntAsDec(methodImpl.getRegisterCount() - parameterRegisterCount); } else { writer.write(".registers "); writer.printSignedIntAsDec(methodImpl.getRegisterCount()); } writer.write('\n'); writeParameters(writer, method, methodParameters, classDef.options); if (registerFormatter == null) { registerFormatter = new RegisterFormatter(classDef.options, methodImpl.getRegisterCount(), parameterRegisterCount); } String containingClass = null; if (classDef.options.implicitReferences) { containingClass = method.getDefiningClass(); } AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass); writer.write('\n'); List methodItems = getMethodItems(); for (MethodItem methodItem: methodItems) { if (methodItem.writeTo(writer)) { writer.write('\n'); } } writer.deindent(4); writer.write(".end method\n"); } public Instruction findSwitchPayload(int targetOffset, Opcode type) { int targetIndex; try { targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset); } catch (InvalidInstructionOffset ex) { throw new InvalidSwitchPayload(targetOffset); } //TODO: does dalvik let you pad with multiple nops? //TODO: does dalvik let a switch instruction point to a non-payload instruction? Instruction instruction = instructions.get(targetIndex); if (instruction.getOpcode() != type) { // maybe it's pointing to a NOP padding instruction. Look at the next instruction if (instruction.getOpcode() == Opcode.NOP) { targetIndex += 1; if (targetIndex < instructions.size()) { instruction = instructions.get(targetIndex); if (instruction.getOpcode() == type) { return instruction; } } } throw new InvalidSwitchPayload(targetOffset); } else { return instruction; } } public int findPayloadOffset(int targetOffset, Opcode type) { int targetIndex; try { targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset); } catch (InvalidInstructionOffset ex) { throw new InvalidSwitchPayload(targetOffset); } //TODO: does dalvik let you pad with multiple nops? //TODO: does dalvik let a switch instruction point to a non-payload instruction? Instruction instruction = instructions.get(targetIndex); if (instruction.getOpcode() != type) { // maybe it's pointing to a NOP padding instruction. Look at the next instruction if (instruction.getOpcode() == Opcode.NOP) { targetIndex += 1; if (targetIndex < instructions.size()) { instruction = instructions.get(targetIndex); if (instruction.getOpcode() == type) { return instructionOffsetMap.getInstructionCodeOffset(targetIndex); } } } throw new InvalidSwitchPayload(targetOffset); } else { return targetOffset; } } private static void writeAccessFlags(IndentingWriter writer, int accessFlags) throws IOException { for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(accessFlags)) { writer.write(accessFlag.toString()); writer.write(' '); } } private static void writeParameters(IndentingWriter writer, Method method, List parameters, 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.debugInfo && parameterName != null) || annotations.size() != 0) { writer.write(".param p"); writer.printSignedIntAsDec(registerNumber); if (parameterName != null && options.debugInfo) { writer.write(", "); ReferenceFormatter.writeStringReference(writer, parameterName); } writer.write(" # "); writer.write(parameterType); writer.write("\n"); if (annotations.size() > 0) { writer.indent(4); String containingClass = null; if (options.implicitReferences) { containingClass = method.getDefiningClass(); } AnnotationFormatter.writeTo(writer, annotations, containingClass); writer.deindent(4); writer.write(".end param\n"); } } registerNumber++; if (TypeUtils.isWideType(parameterType)) { registerNumber++; } } } @Nonnull public LabelCache getLabelCache() { return labelCache; } public int getPackedSwitchBaseAddress(int packedSwitchPayloadCodeOffset) { return packedSwitchMap.get(packedSwitchPayloadCodeOffset, -1); } public int getSparseSwitchBaseAddress(int sparseSwitchPayloadCodeOffset) { return sparseSwitchMap.get(sparseSwitchPayloadCodeOffset, -1); } private List getMethodItems() { ArrayList methodItems = new ArrayList(); if ((classDef.options.registerInfo != 0) || (classDef.options.normalizeVirtualMethods) || (classDef.options.deodex && needsAnalyzed())) { addAnalyzedInstructionMethodItems(methodItems); } else { addInstructionMethodItems(methodItems); } addTries(methodItems); if (classDef.options.debugInfo) { addDebugInfo(methodItems); } if (classDef.options.sequentialLabels) { setLabelSequentialNumbers(); } for (LabelMethodItem labelMethodItem: labelCache.getLabels()) { methodItems.add(labelMethodItem); } Collections.sort(methodItems); return methodItems; } private boolean needsAnalyzed() { for (Instruction instruction: methodImpl.getInstructions()) { if (instruction.getOpcode().odexOnly()) { return true; } } return false; } private void addInstructionMethodItems(List methodItems) { int currentCodeAddress = 0; for (int i=0; i methodItems) { MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classDef.options.classPath, method, classDef.options.inlineResolver, classDef.options.normalizeVirtualMethods); AnalysisException analysisException = methodAnalyzer.getAnalysisException(); if (analysisException != null) { // TODO: need to keep track of whether any errors occurred, so we can exit with a non-zero result methodItems.add(new CommentMethodItem( String.format("AnalysisException: %s", analysisException.getMessage()), analysisException.codeAddress, Integer.MIN_VALUE)); analysisException.printStackTrace(System.err); } List instructions = methodAnalyzer.getAnalyzedInstructions(); int currentCodeAddress = 0; for (int i=0; i methodItems) { List> tryBlocks = methodImpl.getTryBlocks(); if (tryBlocks.size() == 0) { return; } int lastInstructionAddress = instructionOffsetMap.getInstructionCodeOffset(instructions.size() - 1); int codeSize = lastInstructionAddress + instructions.get(instructions.size() - 1).getCodeUnits(); for (TryBlock tryBlock: tryBlocks) { int startAddress = tryBlock.getStartCodeAddress(); int endAddress = startAddress + tryBlock.getCodeUnitCount(); if (startAddress >= codeSize) { throw new RuntimeException(String.format("Try start offset %d is past the end of the code block.", startAddress)); } // Note: not >=. endAddress == codeSize is valid, when the try covers the last instruction if (endAddress > codeSize) { throw new RuntimeException(String.format("Try end offset %d is past the end of the code block.", endAddress)); } /** * The end address points to the address immediately after the end of the last * instruction that the try block covers. We want the .catch directive and end_try * label to be associated with the last covered instruction, so we need to get * the address for that instruction */ int lastCoveredIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(endAddress - 1, false); int lastCoveredAddress = instructionOffsetMap.getInstructionCodeOffset(lastCoveredIndex); for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) { int handlerAddress = handler.getHandlerCodeAddress(); if (handlerAddress >= codeSize) { throw new ExceptionWithContext( "Exception handler offset %d is past the end of the code block.", handlerAddress); } //use the address from the last covered instruction CatchMethodItem catchMethodItem = new CatchMethodItem(classDef.options, labelCache, lastCoveredAddress, handler.getExceptionType(), startAddress, endAddress, handlerAddress); methodItems.add(catchMethodItem); } } } private void addDebugInfo(final List methodItems) { for (DebugItem debugItem: methodImpl.getDebugItems()) { methodItems.add(DebugMethodItem.build(registerFormatter, debugItem)); } } private void setLabelSequentialNumbers() { HashMap nextLabelSequenceByType = new HashMap(); ArrayList sortedLabels = new ArrayList(labelCache.getLabels()); //sort the labels by their location in the method Collections.sort(sortedLabels); for (LabelMethodItem labelMethodItem: sortedLabels) { Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix()); if (labelSequence == null) { labelSequence = 0; } labelMethodItem.setLabelSequence(labelSequence); nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1); } } @Nullable private String getContainingClassForImplicitReference() { if (classDef.options.implicitReferences) { return classDef.classDef.getType(); } return null; } public static class LabelCache { protected HashMap labels = new HashMap(); public LabelCache() { } public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) { LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem); if (internedLabelMethodItem != null) { return internedLabelMethodItem; } labels.put(labelMethodItem, labelMethodItem); return labelMethodItem; } public Collection getLabels() { return labels.values(); } } public static class InvalidSwitchPayload extends ExceptionWithContext { private final int payloadOffset; public InvalidSwitchPayload(int payloadOffset) { super("No switch payload at offset: %d", payloadOffset); this.payloadOffset = payloadOffset; } public int getPayloadOffset() { return payloadOffset; } } }