MethodDefinition.java revision 7e24a9f010eeeff54f7ca0cb589a75cc251fabdd
1/* 2 * [The "BSD licence"] 3 * Copyright (c) 2009 Ben Gruver 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29package org.jf.baksmali.Adaptors; 30 31import org.jf.baksmali.Adaptors.Format.*; 32import org.jf.baksmali.baksmali; 33import org.jf.dexlib.*; 34import org.jf.dexlib.Code.Analysis.AnalyzedInstruction; 35import org.jf.dexlib.Code.Analysis.MethodAnalyzer; 36import org.jf.dexlib.Code.Analysis.RegisterType; 37import org.jf.dexlib.Debug.DebugInstructionIterator; 38import org.jf.dexlib.Code.Instruction; 39import org.jf.dexlib.Code.Opcode; 40import org.jf.dexlib.Code.OffsetInstruction; 41import org.jf.dexlib.Util.AccessFlags; 42import org.antlr.stringtemplate.StringTemplateGroup; 43import org.antlr.stringtemplate.StringTemplate; 44import org.jf.dexlib.Util.SparseIntArray; 45 46import java.util.*; 47 48public class MethodDefinition { 49 private final StringTemplateGroup stg; 50 private final ClassDataItem.EncodedMethod encodedMethod; 51 private final MethodAnalyzer methodAnalyzer; 52 53 private final LabelCache labelCache = new LabelCache(); 54 55 private final SparseIntArray packedSwitchMap; 56 private final SparseIntArray sparseSwitchMap; 57 private final SparseIntArray instructionMap; 58 59 public MethodDefinition(StringTemplateGroup stg, ClassDataItem.EncodedMethod encodedMethod) { 60 this.stg = stg; 61 this.encodedMethod = encodedMethod; 62 63 //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh. 64 65 if (encodedMethod.codeItem != null) { 66 methodAnalyzer = new MethodAnalyzer(encodedMethod); 67 AnalyzedInstruction[] instructions = methodAnalyzer.makeInstructionArray(); 68 69 packedSwitchMap = new SparseIntArray(1); 70 sparseSwitchMap = new SparseIntArray(1); 71 instructionMap = new SparseIntArray(instructions.length); 72 73 int currentCodeAddress = 0; 74 for (int i=0; i<instructions.length; i++) { 75 AnalyzedInstruction instruction = instructions[i]; 76 if (instruction.instruction.opcode == Opcode.PACKED_SWITCH) { 77 packedSwitchMap.append( 78 currentCodeAddress + ((OffsetInstruction)instruction).getTargetAddressOffset(), 79 currentCodeAddress); 80 } else if (instruction.instruction.opcode == Opcode.SPARSE_SWITCH) { 81 sparseSwitchMap.append( 82 currentCodeAddress + ((OffsetInstruction)instruction).getTargetAddressOffset(), 83 currentCodeAddress); 84 } 85 instructionMap.append(currentCodeAddress, i); 86 currentCodeAddress += instruction.instruction.getSize(currentCodeAddress); 87 } 88 } else { 89 packedSwitchMap = null; 90 sparseSwitchMap = null; 91 instructionMap = null; 92 methodAnalyzer = null; 93 } 94 } 95 96 public StringTemplate createTemplate(AnnotationSetItem annotationSet, 97 AnnotationSetRefList parameterAnnotations) { 98 99 CodeItem codeItem = encodedMethod.codeItem; 100 101 StringTemplate template = stg.getInstanceOf("method"); 102 103 template.setAttribute("AccessFlags", getAccessFlags(encodedMethod)); 104 template.setAttribute("MethodName", encodedMethod.method.getMethodName().getStringValue()); 105 template.setAttribute("Prototype", encodedMethod.method.getPrototype().getPrototypeString()); 106 template.setAttribute("HasCode", codeItem != null); 107 template.setAttribute("RegistersDirective", baksmali.useLocalsDirective?".locals":".registers"); 108 template.setAttribute("RegisterCount", codeItem==null?"0":Integer.toString(getRegisterCount(encodedMethod))); 109 template.setAttribute("Parameters", getParameters(stg, codeItem, parameterAnnotations)); 110 template.setAttribute("Annotations", getAnnotations(stg, annotationSet)); 111 template.setAttribute("MethodItems", getMethodItems()); 112 113 return template; 114 } 115 116 private static int getRegisterCount(ClassDataItem.EncodedMethod encodedMethod) 117 { 118 int totalRegisters = encodedMethod.codeItem.getRegisterCount(); 119 if (baksmali.useLocalsDirective) { 120 int parameterRegisters = encodedMethod.method.getPrototype().getParameterRegisterCount(); 121 if ((encodedMethod.accessFlags & AccessFlags.STATIC.getValue()) == 0) { 122 parameterRegisters++; 123 } 124 return totalRegisters - parameterRegisters; 125 } 126 return totalRegisters; 127 } 128 129 private static List<String> getAccessFlags(ClassDataItem.EncodedMethod encodedMethod) { 130 List<String> accessFlags = new ArrayList<String>(); 131 132 for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(encodedMethod.accessFlags)) { 133 accessFlags.add(accessFlag.toString()); 134 } 135 136 return accessFlags; 137 } 138 139 private static List<StringTemplate> getParameters(StringTemplateGroup stg, CodeItem codeItem, 140 AnnotationSetRefList parameterAnnotations) { 141 DebugInfoItem debugInfoItem = null; 142 if (baksmali.outputDebugInfo && codeItem != null) { 143 debugInfoItem = codeItem.getDebugInfo(); 144 } 145 146 int parameterCount = 0; 147 148 List<AnnotationSetItem> annotations = new ArrayList<AnnotationSetItem>(); 149 if (parameterAnnotations != null) { 150 AnnotationSetItem[] _annotations = parameterAnnotations.getAnnotationSets(); 151 if (_annotations != null) { 152 annotations.addAll(Arrays.asList(_annotations)); 153 } 154 155 parameterCount = annotations.size(); 156 } 157 158 List<String> parameterNames = new ArrayList<String>(); 159 if (debugInfoItem != null) { 160 StringIdItem[] _parameterNames = debugInfoItem.getParameterNames(); 161 if (_parameterNames != null) { 162 for (StringIdItem parameterName: _parameterNames) { 163 parameterNames.add(parameterName==null?null:parameterName.getStringValue()); 164 } 165 } 166 167 if (parameterCount < parameterNames.size()) { 168 parameterCount = parameterNames.size(); 169 } 170 } 171 172 List<StringTemplate> parameters = new ArrayList<StringTemplate>(); 173 for (int i=0; i<parameterCount; i++) { 174 AnnotationSetItem annotationSet = null; 175 if (i < annotations.size()) { 176 annotationSet = annotations.get(i); 177 } 178 179 String parameterName = null; 180 if (i < parameterNames.size()) { 181 parameterName = parameterNames.get(i); 182 } 183 184 parameters.add(ParameterAdaptor.createTemplate(stg, parameterName, annotationSet)); 185 } 186 187 return parameters; 188 } 189 190 public LabelCache getLabelCache() { 191 return labelCache; 192 } 193 194 public int getPackedSwitchBaseAddress(int packedSwitchDataAddress) { 195 int packedSwitchBaseAddress = this.packedSwitchMap.get(packedSwitchDataAddress, -1); 196 197 if (packedSwitchBaseAddress == -1) { 198 throw new RuntimeException("Could not find the packed switch statement corresponding to the packed " + 199 "switch data at address " + packedSwitchDataAddress); 200 } 201 202 return packedSwitchBaseAddress; 203 } 204 205 public int getSparseSwitchBaseAddress(int sparseSwitchDataAddress) { 206 int sparseSwitchBaseAddress = this.sparseSwitchMap.get(sparseSwitchDataAddress, -1); 207 208 if (sparseSwitchBaseAddress == -1) { 209 throw new RuntimeException("Could not find the sparse switch statement corresponding to the sparse " + 210 "switch data at address " + sparseSwitchDataAddress); 211 } 212 213 return sparseSwitchBaseAddress; 214 } 215 216 private static List<StringTemplate> getAnnotations(StringTemplateGroup stg, AnnotationSetItem annotationSet) { 217 if (annotationSet == null) { 218 return null; 219 } 220 221 List<StringTemplate> annotationAdaptors = new ArrayList<StringTemplate>(); 222 223 for (AnnotationItem annotationItem: annotationSet.getAnnotations()) { 224 annotationAdaptors.add(AnnotationAdaptor.createTemplate(stg, annotationItem)); 225 } 226 return annotationAdaptors; 227 } 228 229 private List<MethodItem> getMethodItems() { 230 List<MethodItem> methodItems = new ArrayList<MethodItem>(); 231 232 if (encodedMethod.codeItem == null) { 233 return methodItems; 234 } 235 236 AnalyzedInstruction[] instructions; 237 if (baksmali.verboseRegisterInfo) { 238 instructions = methodAnalyzer.analyze(); 239 } else { 240 instructions = methodAnalyzer.makeInstructionArray(); 241 } 242 243 int currentCodeAddress = 0; 244 for (int i=0; i<instructions.length; i++) { 245 AnalyzedInstruction instruction = instructions[i]; 246 247 methodItems.add(InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 248 encodedMethod.codeItem, currentCodeAddress, stg, instruction.instruction)); 249 250 if (i != instructions.length - 1) { 251 methodItems.add(new BlankMethodItem(stg, currentCodeAddress)); 252 } 253 254 if (baksmali.verboseRegisterInfo) { 255 if (instruction.getPredecessorCount() > 1 || i == 0) { 256 methodItems.add(new CommentMethodItem(stg, getPreInstructionRegisterString(instruction), 257 currentCodeAddress, Integer.MIN_VALUE)); 258 } 259 methodItems.add(new CommentMethodItem(stg, getPostInstructionRegisterString(instruction), 260 currentCodeAddress, Integer.MAX_VALUE-1)); 261 } 262 263 264 currentCodeAddress += instruction.instruction.getSize(currentCodeAddress); 265 } 266 267 addTries(methodItems); 268 addDebugInfo(methodItems); 269 270 if (baksmali.useSequentialLabels) { 271 setLabelSequentialNumbers(); 272 } 273 274 275 for (LabelMethodItem labelMethodItem: labelCache.getLabels()) { 276 if (labelMethodItem.isCommentedOut()) { 277 methodItems.add(new CommentedOutMethodItem(stg, labelMethodItem)); 278 } else { 279 methodItems.add(labelMethodItem); 280 } 281 } 282 283 Collections.sort(methodItems); 284 285 return methodItems; 286 } 287 288 private String getPreInstructionRegisterString(AnalyzedInstruction instruction) { 289 StringBuilder sb = new StringBuilder(); 290 291 for (int i=0; i<instruction.getRegisterCount(); i++) { 292 RegisterType registerType = instruction.getPreInstructionRegisterType(i); 293 sb.append("v"); 294 sb.append(i); 295 sb.append("="); 296 if (registerType == null) { 297 sb.append("null"); 298 } else { 299 sb.append(registerType.toString()); 300 } 301 sb.append(";"); 302 } 303 304 return sb.toString(); 305 } 306 307 private String getPostInstructionRegisterString(AnalyzedInstruction instruction) { 308 StringBuilder sb = new StringBuilder(); 309 310 for (int i=0; i<instruction.getRegisterCount(); i++) { 311 RegisterType registerType = instruction.getPostInstructionRegisterType(i); 312 sb.append("v"); 313 sb.append(i); 314 sb.append("="); 315 sb.append(registerType.toString()); 316 sb.append(";"); 317 } 318 319 return sb.toString(); 320 } 321 322 323 private void addTries(List<MethodItem> methodItems) { 324 if (encodedMethod.codeItem == null || encodedMethod.codeItem.getTries() == null) { 325 return; 326 } 327 328 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 329 330 for (CodeItem.TryItem tryItem: encodedMethod.codeItem.getTries()) { 331 int startAddress = tryItem.getStartCodeAddress(); 332 int endAddress = tryItem.getStartCodeAddress() + tryItem.getTryLength(); 333 334 /** 335 * The end address points to the address immediately after the end of the last 336 * instruction that the try block covers. We want the .catch directive and end_try 337 * label to be associated with the last covered instruction, so we need to get 338 * the address for that instruction 339 */ 340 341 int index = instructionMap.get(endAddress, -1); 342 int lastInstructionAddress; 343 344 /** 345 * If we couldn't find the index, then the try block probably extends to the last instruction in the 346 * method, and so endAddress would be the address immediately after the end of the last instruction. 347 * Check to make sure this is the case, if not, throw an exception. 348 */ 349 if (index == -1) { 350 Instruction lastInstruction = instructions[instructions.length - 1]; 351 lastInstructionAddress = instructionMap.keyAt(instructionMap.size() - 1); 352 353 if (endAddress != lastInstructionAddress + lastInstruction.getSize(lastInstructionAddress)) { 354 throw new RuntimeException("Invalid code offset " + endAddress + " for the try block end address"); 355 } 356 } else { 357 if (index == 0) { 358 throw new RuntimeException("Unexpected instruction index"); 359 } 360 Instruction lastInstruction = instructions[index - 1]; 361 362 if (lastInstruction.getFormat().variableSizeFormat) { 363 throw new RuntimeException("This try block unexpectedly ends on a switch/array data block."); 364 } 365 366 //getSize for non-variable size formats should return the same size regardless of code address, so just 367 //use a dummy address of "0" 368 lastInstructionAddress = endAddress - lastInstruction.getSize(0); 369 } 370 371 //add the catch all handler if it exists 372 int catchAllAddress = tryItem.encodedCatchHandler.getCatchAllHandlerAddress(); 373 if (catchAllAddress != -1) { 374 CatchMethodItem catchAllMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, stg, null, 375 startAddress, endAddress, catchAllAddress); 376 methodItems.add(catchAllMethodItem); 377 } 378 379 //add the rest of the handlers 380 for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) { 381 //use the address from the last covered instruction 382 CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, stg, 383 handler.exceptionType, startAddress, endAddress, handler.getHandlerAddress()); 384 methodItems.add(catchMethodItem); 385 } 386 } 387 } 388 389 private void addDebugInfo(final List<MethodItem> methodItems) { 390 if (encodedMethod.codeItem == null || encodedMethod.codeItem.getDebugInfo() == null) { 391 return; 392 } 393 394 final CodeItem codeItem = encodedMethod.codeItem; 395 DebugInfoItem debugInfoItem = codeItem.getDebugInfo(); 396 397 DebugInstructionIterator.DecodeInstructions(debugInfoItem, codeItem.getRegisterCount(), 398 new DebugInstructionIterator.ProcessDecodedDebugInstructionDelegate() { 399 @Override 400 public void ProcessStartLocal(int codeAddress, int length, int registerNum, StringIdItem name, 401 TypeIdItem type) { 402 methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "StartLocal", 403 -1, registerNum, name, type, null)); 404 } 405 406 @Override 407 public void ProcessStartLocalExtended(int codeAddress, int length, int registerNum, 408 StringIdItem name, TypeIdItem type, 409 StringIdItem signature) { 410 methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "StartLocal", 411 -1, registerNum, name, type, signature)); 412 } 413 414 @Override 415 public void ProcessEndLocal(int codeAddress, int length, int registerNum, StringIdItem name, 416 TypeIdItem type, StringIdItem signature) { 417 methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "EndLocal", -1, 418 registerNum, name, type, signature)); 419 } 420 421 @Override 422 public void ProcessRestartLocal(int codeAddress, int length, int registerNum, StringIdItem name, 423 TypeIdItem type, StringIdItem signature) { 424 methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "RestartLocal", -1, 425 registerNum, name, type, signature)); 426 } 427 428 @Override 429 public void ProcessSetPrologueEnd(int codeAddress) { 430 methodItems.add(new DebugMethodItem(codeAddress, stg, "EndPrologue", -4)); 431 } 432 433 @Override 434 public void ProcessSetEpilogueBegin(int codeAddress) { 435 methodItems.add(new DebugMethodItem(codeAddress, stg, "StartEpilogue", -4)); 436 } 437 438 @Override 439 public void ProcessSetFile(int codeAddress, int length, final StringIdItem name) { 440 methodItems.add(new DebugMethodItem(codeAddress, stg, "SetFile", -3) { 441 @Override 442 protected void setAttributes(StringTemplate template) { 443 template.setAttribute("FileName", name.getStringValue()); 444 } 445 }); 446 } 447 448 @Override 449 public void ProcessLineEmit(int codeAddress, final int line) { 450 methodItems.add(new DebugMethodItem(codeAddress, stg, "Line", -2) { 451 @Override 452 protected void setAttributes(StringTemplate template) { 453 template.setAttribute("Line", line); 454 } 455 }); 456 } 457 }); 458 } 459 460 private void setLabelSequentialNumbers() { 461 HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>(); 462 ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels()); 463 464 //sort the labels by their location in the method 465 Collections.sort(sortedLabels); 466 467 for (LabelMethodItem labelMethodItem: sortedLabels) { 468 Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix()); 469 if (labelSequence == null) { 470 labelSequence = 0; 471 } 472 labelMethodItem.setLabelSequence(labelSequence); 473 nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1); 474 } 475 } 476 477 public static class LabelCache { 478 protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>(); 479 480 public LabelCache() { 481 } 482 483 public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) { 484 LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem); 485 if (internedLabelMethodItem != null) { 486 if (!labelMethodItem.isCommentedOut()) { 487 internedLabelMethodItem.setUncommented(); 488 } 489 return internedLabelMethodItem; 490 } 491 labels.put(labelMethodItem, labelMethodItem); 492 return labelMethodItem; 493 } 494 495 496 public Collection<LabelMethodItem> getLabels() { 497 return labels.values(); 498 } 499 } 500} 501