/* * [The "BSD licence"] * Copyright (c) 2009 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.baksmali.Adaptors.Format.*; import org.jf.baksmali.baksmali; import org.jf.baksmali.main; import org.jf.dexlib.*; import org.jf.dexlib.Code.*; import org.jf.dexlib.Code.Analysis.AnalyzedInstruction; import org.jf.dexlib.Code.Analysis.MethodAnalyzer; import org.jf.dexlib.Code.Analysis.RegisterType; import org.jf.dexlib.Code.Analysis.ValidationException; import org.jf.dexlib.Debug.DebugInstructionIterator; import org.jf.dexlib.Util.AccessFlags; import org.antlr.stringtemplate.StringTemplateGroup; import org.antlr.stringtemplate.StringTemplate; import org.jf.dexlib.Util.SparseIntArray; import java.util.*; public class MethodDefinition { private final StringTemplateGroup stg; private final ClassDataItem.EncodedMethod encodedMethod; private final MethodAnalyzer methodAnalyzer; private final LabelCache labelCache = new LabelCache(); private final SparseIntArray packedSwitchMap; private final SparseIntArray sparseSwitchMap; private final SparseIntArray instructionMap; private final int registerCount; public MethodDefinition(StringTemplateGroup stg, ClassDataItem.EncodedMethod encodedMethod) { this.stg = stg; this.encodedMethod = encodedMethod; //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh. if (encodedMethod.codeItem != null) { methodAnalyzer = new MethodAnalyzer(encodedMethod); AnalyzedInstruction[] instructions = methodAnalyzer.makeInstructionArray(); packedSwitchMap = new SparseIntArray(1); sparseSwitchMap = new SparseIntArray(1); instructionMap = new SparseIntArray(instructions.length); registerCount = encodedMethod.codeItem.getRegisterCount(); int currentCodeAddress = 0; for (int i=0; i getAccessFlags(ClassDataItem.EncodedMethod encodedMethod) { List accessFlags = new ArrayList(); for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(encodedMethod.accessFlags)) { accessFlags.add(accessFlag.toString()); } return accessFlags; } private static List getParameters(StringTemplateGroup stg, CodeItem codeItem, AnnotationSetRefList parameterAnnotations) { DebugInfoItem debugInfoItem = null; if (baksmali.outputDebugInfo && codeItem != null) { debugInfoItem = codeItem.getDebugInfo(); } int parameterCount = 0; List annotations = new ArrayList(); if (parameterAnnotations != null) { AnnotationSetItem[] _annotations = parameterAnnotations.getAnnotationSets(); if (_annotations != null) { annotations.addAll(Arrays.asList(_annotations)); } parameterCount = annotations.size(); } List parameterNames = new ArrayList(); if (debugInfoItem != null) { StringIdItem[] _parameterNames = debugInfoItem.getParameterNames(); if (_parameterNames != null) { for (StringIdItem parameterName: _parameterNames) { parameterNames.add(parameterName==null?null:parameterName.getStringValue()); } } if (parameterCount < parameterNames.size()) { parameterCount = parameterNames.size(); } } List parameters = new ArrayList(); for (int i=0; i getAnnotations(StringTemplateGroup stg, AnnotationSetItem annotationSet) { if (annotationSet == null) { return null; } List annotationAdaptors = new ArrayList(); for (AnnotationItem annotationItem: annotationSet.getAnnotations()) { annotationAdaptors.add(AnnotationAdaptor.createTemplate(stg, annotationItem)); } return annotationAdaptors; } private List getMethodItems() { List methodItems = new ArrayList(); if (encodedMethod.codeItem == null) { return methodItems; } AnalyzedInstruction[] instructions; if (baksmali.registerInfo != 0) { instructions = methodAnalyzer.analyze(); ValidationException validationException = methodAnalyzer.getValidationException(); if (validationException != null) { methodItems.add(new CommentMethodItem(stg, String.format("ValidationException: %s" ,validationException.getMessage()), validationException.getCodeAddress(), Integer.MIN_VALUE)); } } else { instructions = methodAnalyzer.makeInstructionArray(); } BitSet printPreRegister = new BitSet(registerCount); BitSet printPostRegister = new BitSet(registerCount); int currentCodeAddress = 0; for (int i=0; i methodItems, MethodAnalyzer methodAnalyzer, int instructionNum, int currentCodeAddress) { if (instructionNum == 0) { return; } //MethodAnalyzer cached the analyzedInstructions, so we're not actually re-analyzing, just getting the //instructions that have already been analyzed AnalyzedInstruction[] instructions = methodAnalyzer.analyze(); AnalyzedInstruction instruction = instructions[instructionNum]; if (instruction.getPredecessorCount() <= 1) { return; } StringBuffer sb = new StringBuffer(); for (int registerNum=0; registerNum= 0; registerNum = registers.nextSetBit(registerNum + 1)) { RegisterType registerType = instruction.getPreInstructionRegisterType(registerNum); sb.append(RegisterFormatter.formatRegister(encodedMethod.codeItem,registerNum)); sb.append("="); if (registerType == null) { sb.append("null"); } else { sb.append(registerType.toString()); } sb.append(";"); } return sb.toString(); } private String getPostInstructionRegisterString(AnalyzedInstruction instruction, BitSet registers) { StringBuilder sb = new StringBuilder(); for (int registerNum = registers.nextSetBit(0); registerNum >= 0; registerNum = registers.nextSetBit(registerNum + 1)) { RegisterType registerType = instruction.getPostInstructionRegisterType(registerNum); sb.append(RegisterFormatter.formatRegister(encodedMethod.codeItem,registerNum)); sb.append("="); if (registerType == null) { sb.append("null"); } else { sb.append(registerType.toString()); } sb.append(";"); } return sb.toString(); } private void addTries(List methodItems) { if (encodedMethod.codeItem == null || encodedMethod.codeItem.getTries() == null) { return; } Instruction[] instructions = encodedMethod.codeItem.getInstructions(); for (CodeItem.TryItem tryItem: encodedMethod.codeItem.getTries()) { int startAddress = tryItem.getStartCodeAddress(); int endAddress = tryItem.getStartCodeAddress() + tryItem.getTryLength(); /** * 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 index = instructionMap.get(endAddress, -1); int lastInstructionAddress; /** * If we couldn't find the index, then the try block probably extends to the last instruction in the * method, and so endAddress would be the address immediately after the end of the last instruction. * Check to make sure this is the case, if not, throw an exception. */ if (index == -1) { Instruction lastInstruction = instructions[instructions.length - 1]; lastInstructionAddress = instructionMap.keyAt(instructionMap.size() - 1); if (endAddress != lastInstructionAddress + lastInstruction.getSize(lastInstructionAddress)) { throw new RuntimeException("Invalid code offset " + endAddress + " for the try block end address"); } } else { if (index == 0) { throw new RuntimeException("Unexpected instruction index"); } Instruction lastInstruction = instructions[index - 1]; if (lastInstruction.getFormat().variableSizeFormat) { throw new RuntimeException("This try block unexpectedly ends on a switch/array data block."); } //getSize for non-variable size formats should return the same size regardless of code address, so just //use a dummy address of "0" lastInstructionAddress = endAddress - lastInstruction.getSize(0); } //add the catch all handler if it exists int catchAllAddress = tryItem.encodedCatchHandler.getCatchAllHandlerAddress(); if (catchAllAddress != -1) { CatchMethodItem catchAllMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, stg, null, startAddress, endAddress, catchAllAddress); methodItems.add(catchAllMethodItem); } //add the rest of the handlers for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) { //use the address from the last covered instruction CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, stg, handler.exceptionType, startAddress, endAddress, handler.getHandlerAddress()); methodItems.add(catchMethodItem); } } } private void addDebugInfo(final List methodItems) { if (encodedMethod.codeItem == null || encodedMethod.codeItem.getDebugInfo() == null) { return; } final CodeItem codeItem = encodedMethod.codeItem; DebugInfoItem debugInfoItem = codeItem.getDebugInfo(); DebugInstructionIterator.DecodeInstructions(debugInfoItem, codeItem.getRegisterCount(), new DebugInstructionIterator.ProcessDecodedDebugInstructionDelegate() { @Override public void ProcessStartLocal(int codeAddress, int length, int registerNum, StringIdItem name, TypeIdItem type) { methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "StartLocal", -1, registerNum, name, type, null)); } @Override public void ProcessStartLocalExtended(int codeAddress, int length, int registerNum, StringIdItem name, TypeIdItem type, StringIdItem signature) { methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "StartLocal", -1, registerNum, name, type, signature)); } @Override public void ProcessEndLocal(int codeAddress, int length, int registerNum, StringIdItem name, TypeIdItem type, StringIdItem signature) { methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "EndLocal", -1, registerNum, name, type, signature)); } @Override public void ProcessRestartLocal(int codeAddress, int length, int registerNum, StringIdItem name, TypeIdItem type, StringIdItem signature) { methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "RestartLocal", -1, registerNum, name, type, signature)); } @Override public void ProcessSetPrologueEnd(int codeAddress) { methodItems.add(new DebugMethodItem(codeAddress, stg, "EndPrologue", -4)); } @Override public void ProcessSetEpilogueBegin(int codeAddress) { methodItems.add(new DebugMethodItem(codeAddress, stg, "StartEpilogue", -4)); } @Override public void ProcessSetFile(int codeAddress, int length, final StringIdItem name) { methodItems.add(new DebugMethodItem(codeAddress, stg, "SetFile", -3) { @Override protected void setAttributes(StringTemplate template) { template.setAttribute("FileName", name.getStringValue()); } }); } @Override public void ProcessLineEmit(int codeAddress, final int line) { methodItems.add(new DebugMethodItem(codeAddress, stg, "Line", -2) { @Override protected void setAttributes(StringTemplate template) { template.setAttribute("Line", line); } }); } }); } 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); } } public static class LabelCache { protected HashMap labels = new HashMap(); public LabelCache() { } public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) { LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem); if (internedLabelMethodItem != null) { if (!labelMethodItem.isCommentedOut()) { internedLabelMethodItem.setUncommented(); } return internedLabelMethodItem; } labels.put(labelMethodItem, labelMethodItem); return labelMethodItem; } public Collection getLabels() { return labels.values(); } } }