MethodDefinition.java revision d1662b67fecaf835227aff3a136949a2358ccd4e
1/* 2 * [The "BSD licence"] 3 * Copyright (c) 2010 Ben Gruver (JesusFreke) 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 com.google.common.collect.ImmutableList; 32import org.jf.baksmali.Adaptors.Debug.DebugMethodItem; 33import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory; 34import org.jf.dexlib2.AccessFlags; 35import org.jf.dexlib2.Opcode; 36import org.jf.dexlib2.iface.*; 37import org.jf.dexlib2.iface.debug.DebugItem; 38import org.jf.dexlib2.iface.instruction.Instruction; 39import org.jf.dexlib2.iface.instruction.OffsetInstruction; 40import org.jf.dexlib2.util.InstructionOffsetMap; 41import org.jf.dexlib2.util.MethodUtil; 42import org.jf.dexlib2.util.TypeUtils; 43import org.jf.util.IndentingWriter; 44import org.jf.baksmali.baksmali; 45import org.jf.util.ExceptionWithContext; 46import org.jf.dexlib.Util.SparseIntArray; 47 48import javax.annotation.Nonnull; 49import java.io.IOException; 50import java.util.*; 51 52public class MethodDefinition { 53 @Nonnull public final ClassDefinition classDef; 54 @Nonnull public final Method method; 55 @Nonnull public final MethodImplementation methodImpl; 56 @Nonnull public final ImmutableList<Instruction> instructions; 57 public RegisterFormatter registerFormatter; 58 59 @Nonnull private final String methodString; 60 61 @Nonnull private final LabelCache labelCache = new LabelCache(); 62 63 @Nonnull private final SparseIntArray packedSwitchMap; 64 @Nonnull private final SparseIntArray sparseSwitchMap; 65 @Nonnull private final InstructionOffsetMap instructionOffsetMap; 66 67 public MethodDefinition(@Nonnull ClassDefinition classDef, @Nonnull Method method, 68 @Nonnull MethodImplementation methodImpl) { 69 this.classDef = classDef; 70 this.method = method; 71 this.methodImpl = methodImpl; 72 73 this.methodString = MethodUtil.buildFullMethodString(classDef.classDef, method); 74 75 try { 76 //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh. 77 78 instructions = ImmutableList.copyOf(methodImpl.getInstructions()); 79 80 packedSwitchMap = new SparseIntArray(0); 81 sparseSwitchMap = new SparseIntArray(0); 82 instructionOffsetMap = new InstructionOffsetMap(instructions); 83 84 for (int i=0; i<instructions.size(); i++) { 85 Instruction instruction = instructions.get(i); 86 87 Opcode opcode = instruction.getOpcode(); 88 if (opcode == Opcode.PACKED_SWITCH) { 89 int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i); 90 int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset(); 91 targetOffset = findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD); 92 packedSwitchMap.append(targetOffset, codeOffset); 93 } else if (opcode == Opcode.SPARSE_SWITCH) { 94 int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i); 95 int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset(); 96 targetOffset = findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD); 97 sparseSwitchMap.append(targetOffset, codeOffset); 98 } 99 } 100 }catch (Exception ex) { 101 throw ExceptionWithContext.withContext(ex, "Error while processing method %s", methodString); 102 } 103 } 104 105 public static void writeEmptyMethodTo(IndentingWriter writer, Method method) throws IOException { 106 writer.write(".method "); 107 writeAccessFlags(writer, method.getAccessFlags()); 108 writer.write(method.getName()); 109 writer.write("("); 110 for (MethodParameter parameter: method.getParameters()) { 111 writer.write(parameter.getType()); 112 } 113 writer.write(")"); 114 writer.write(method.getReturnType()); 115 writer.write('\n'); 116 117 writer.indent(4); 118 writeParameters(writer, method); 119 AnnotationFormatter.writeTo(writer, method.getAnnotations()); 120 writer.deindent(4); 121 writer.write(".end method\n"); 122 } 123 124 public void writeTo(IndentingWriter writer) throws IOException { 125 int parameterRegisterCount = 0; 126 if (!AccessFlags.STATIC.isSet(method.getAccessFlags())) { 127 parameterRegisterCount++; 128 } 129 130 writer.write(".method "); 131 writeAccessFlags(writer, method.getAccessFlags()); 132 writer.write(method.getName()); 133 writer.write("("); 134 for (MethodParameter parameter: method.getParameters()) { 135 String type = parameter.getType(); 136 writer.write(type); 137 parameterRegisterCount++; 138 if (TypeUtils.isWideType(type)) { 139 parameterRegisterCount++; 140 } 141 } 142 writer.write(")"); 143 writer.write(method.getReturnType()); 144 writer.write('\n'); 145 146 writer.indent(4); 147 if (baksmali.useLocalsDirective) { 148 writer.write(".locals "); 149 writer.printSignedIntAsDec(methodImpl.getRegisterCount() - parameterRegisterCount); 150 } else { 151 writer.write(".registers "); 152 writer.printSignedIntAsDec(methodImpl.getRegisterCount()); 153 } 154 writer.write('\n'); 155 writeParameters(writer, method); 156 157 if (registerFormatter == null) { 158 registerFormatter = new RegisterFormatter(methodImpl.getRegisterCount(), parameterRegisterCount); 159 } 160 161 AnnotationFormatter.writeTo(writer, method.getAnnotations()); 162 163 writer.write('\n'); 164 165 List<MethodItem> methodItems = getMethodItems(); 166 int size = methodItems.size(); 167 for (int i=0; i<size; i++) { 168 MethodItem methodItem = methodItems.get(i); 169 if (methodItem.writeTo(writer)) { 170 writer.write('\n'); 171 } 172 } 173 writer.deindent(4); 174 writer.write(".end method\n"); 175 } 176 177 private int findSwitchPayload(int targetOffset, Opcode type) { 178 int targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset); 179 180 //TODO: does dalvik let you pad with multiple nops? 181 //TODO: does dalvik let a switch instruction point to a non-payload instruction? 182 183 Instruction instruction = instructions.get(targetIndex); 184 if (instruction.getOpcode() != type) { 185 // maybe it's pointing to a NOP padding instruction. Look at the next instruction 186 if (instruction.getOpcode() == Opcode.NOP) { 187 targetIndex += 1; 188 if (targetIndex < instructions.size()) { 189 instruction = instructions.get(targetIndex); 190 if (instruction.getOpcode() == type) { 191 return instructionOffsetMap.getInstructionCodeOffset(targetIndex); 192 } 193 } 194 } 195 throw new ExceptionWithContext("No switch payload at offset 0x%x", targetOffset); 196 } else { 197 return targetOffset; 198 } 199 } 200 201 private static void writeAccessFlags(IndentingWriter writer, int accessFlags) 202 throws IOException { 203 for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(accessFlags)) { 204 writer.write(accessFlag.toString()); 205 writer.write(' '); 206 } 207 } 208 209 private static void writeParameters(IndentingWriter writer, Method method) throws IOException { 210 boolean isStatic = AccessFlags.STATIC.isSet(method.getAccessFlags()); 211 int registerNumber = isStatic?0:1; 212 for (MethodParameter parameter: method.getParameters()) { 213 String parameterType = parameter.getType(); 214 String parameterName = parameter.getName(); 215 List<? extends Annotation> annotations = parameter.getAnnotations(); 216 if (parameterName != null || annotations.size() != 0) { 217 writer.write(".param p"); 218 writer.printSignedIntAsDec(registerNumber); 219 if (parameterName != null) { 220 writer.write(", "); 221 // TODO: does dalvik allow non-identifier parameter and/or local names? 222 writer.write(parameterName); 223 } 224 writer.write(" # "); 225 writer.write(parameterType); 226 if (annotations.size() > 0) { 227 writer.indent(4); 228 AnnotationFormatter.writeTo(writer, annotations); 229 writer.deindent(4); 230 writer.write(".end param\n"); 231 } else { 232 writer.write("\n"); 233 } 234 } 235 236 registerNumber++; 237 if (TypeUtils.isWideType(parameterType)) { 238 registerNumber++; 239 } 240 } 241 } 242 243 @Nonnull public LabelCache getLabelCache() { 244 return labelCache; 245 } 246 247 public int getPackedSwitchBaseAddress(int packedSwitchPayloadCodeOffset) { 248 return packedSwitchMap.get(packedSwitchPayloadCodeOffset, -1); 249 } 250 251 public int getSparseSwitchBaseAddress(int sparseSwitchPayloadCodeOffset) { 252 return sparseSwitchMap.get(sparseSwitchPayloadCodeOffset, -1); 253 } 254 255 private List<MethodItem> getMethodItems() { 256 ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>(); 257 258 //TODO: addAnalyzedInstructionMethodItems 259 addInstructionMethodItems(methodItems); 260 261 addTries(methodItems); 262 if (baksmali.outputDebugInfo) { 263 addDebugInfo(methodItems); 264 } 265 266 if (baksmali.useSequentialLabels) { 267 setLabelSequentialNumbers(); 268 } 269 270 for (LabelMethodItem labelMethodItem: labelCache.getLabels()) { 271 methodItems.add(labelMethodItem); 272 } 273 274 Collections.sort(methodItems); 275 276 return methodItems; 277 } 278 279 private void addInstructionMethodItems(List<MethodItem> methodItems) { 280 int currentCodeAddress = 0; 281 for (int i=0; i<instructions.size(); i++) { 282 Instruction instruction = instructions.get(i); 283 284 MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 285 currentCodeAddress, instruction); 286 287 methodItems.add(methodItem); 288 289 if (i != instructions.size() - 1) { 290 methodItems.add(new BlankMethodItem(currentCodeAddress)); 291 } 292 293 if (baksmali.addCodeOffsets) { 294 methodItems.add(new MethodItem(currentCodeAddress) { 295 296 @Override 297 public double getSortOrder() { 298 return -1000; 299 } 300 301 @Override 302 public boolean writeTo(IndentingWriter writer) throws IOException { 303 writer.write("#@"); 304 writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFFL); 305 return true; 306 } 307 }); 308 } 309 310 //TODO: uncomment 311 /*if (!baksmali.noAccessorComments && (instruction instanceof InstructionWithReference)) { 312 Opcode opcode = instruction.getOpcode(); 313 if (opcode == Opcode.INVOKE_STATIC || opcode == Opcode.INVOKE_STATIC_RANGE) { 314 MethodIdItem methodIdItem = 315 (MethodIdItem)((InstructionWithReference) instruction).getReferencedItem(); 316 317 if (SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodIdItem)) { 318 SyntheticAccessorResolver.AccessedMember accessedMember = 319 baksmali.syntheticAccessorResolver.getAccessedMember(methodIdItem); 320 if (accessedMember != null) { 321 methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress)); 322 } 323 } 324 } 325 }*/ 326 327 currentCodeAddress += instruction.getCodeUnits(); 328 } 329 } 330 331 //TODO: uncomment 332 /*private void addAnalyzedInstructionMethodItems(List<MethodItem> methodItems) { 333 methodAnalyzer = new MethodAnalyzer(encodedMethod, baksmali.deodex, baksmali.inlineResolver); 334 335 methodAnalyzer.analyze(); 336 337 ValidationException validationException = methodAnalyzer.getValidationException(); 338 if (validationException != null) { 339 methodItems.add(new CommentMethodItem( 340 String.format("ValidationException: %s" ,validationException.getMessage()), 341 validationException.getCodeAddress(), Integer.MIN_VALUE)); 342 } else if (baksmali.verify) { 343 methodAnalyzer.verify(); 344 345 validationException = methodAnalyzer.getValidationException(); 346 if (validationException != null) { 347 methodItems.add(new CommentMethodItem( 348 String.format("ValidationException: %s" ,validationException.getMessage()), 349 validationException.getCodeAddress(), Integer.MIN_VALUE)); 350 } 351 } 352 353 List<AnalyzedInstruction> instructions = methodAnalyzer.getInstructions(); 354 355 int currentCodeAddress = 0; 356 for (int i=0; i<instructions.size(); i++) { 357 AnalyzedInstruction instruction = instructions.get(i); 358 359 MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 360 encodedMethod.codeItem, currentCodeAddress, instruction.getInstruction()); 361 362 methodItems.add(methodItem); 363 364 if (instruction.getInstruction().getFormat() == Format.UnresolvedOdexInstruction) { 365 methodItems.add(new CommentedOutMethodItem( 366 InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 367 encodedMethod.codeItem, currentCodeAddress, instruction.getOriginalInstruction()))); 368 } 369 370 if (i != instructions.size() - 1) { 371 methodItems.add(new BlankMethodItem(currentCodeAddress)); 372 } 373 374 if (baksmali.addCodeOffsets) { 375 methodItems.add(new MethodItem(currentCodeAddress) { 376 377 @Override 378 public double getSortOrder() { 379 return -1000; 380 } 381 382 @Override 383 public boolean writeTo(IndentingWriter writer) throws IOException { 384 writer.write("#@"); 385 writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFF); 386 return true; 387 } 388 }); 389 } 390 391 if (baksmali.registerInfo != 0 && !instruction.getInstruction().getFormat().variableSizeFormat) { 392 methodItems.add( 393 new PreInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress)); 394 395 methodItems.add( 396 new PostInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress)); 397 } 398 399 currentCodeAddress += instruction.getInstruction().getSize(currentCodeAddress); 400 } 401 }*/ 402 403 private void addTries(List<MethodItem> methodItems) { 404 List<? extends TryBlock> tryBlocks = methodImpl.getTryBlocks(); 405 if (tryBlocks.size() == 0) { 406 return; 407 } 408 409 int lastInstructionAddress = instructionOffsetMap.getInstructionCodeOffset(instructions.size() - 1); 410 int codeSize = lastInstructionAddress + instructions.get(instructions.size() - 1).getCodeUnits(); 411 412 for (TryBlock tryBlock: tryBlocks) { 413 int startAddress = tryBlock.getStartCodeOffset(); 414 int endAddress = startAddress + tryBlock.getCodeUnitCount(); 415 416 if (startAddress >= codeSize) { 417 throw new RuntimeException(String.format("Try start offset %d is past the end of the code block.", 418 startAddress)); 419 } 420 // Note: not >=. endAddress == codeSize is valid, when the try covers the last instruction 421 if (endAddress > codeSize) { 422 throw new RuntimeException(String.format("Try end offset %d is past the end of the code block.", 423 endAddress)); 424 } 425 426 /** 427 * The end address points to the address immediately after the end of the last 428 * instruction that the try block covers. We want the .catch directive and end_try 429 * label to be associated with the last covered instruction, so we need to get 430 * the address for that instruction 431 */ 432 433 int lastCoveredIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(endAddress - 1, false); 434 int lastCoveredAddress = instructionOffsetMap.getInstructionCodeOffset(lastCoveredIndex); 435 436 for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) { 437 int handlerOffset = handler.getHandlerCodeOffset(); 438 if (handlerOffset >= codeSize) { 439 throw new ExceptionWithContext( 440 "Exception handler offset %d is past the end of the code block.", handlerOffset); 441 } 442 443 //use the address from the last covered instruction 444 CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastCoveredAddress, 445 handler.getExceptionType(), startAddress, endAddress, handlerOffset); 446 methodItems.add(catchMethodItem); 447 } 448 } 449 } 450 451 private void addDebugInfo(final List<MethodItem> methodItems) { 452 for (DebugItem debugItem: methodImpl.getDebugItems()) { 453 methodItems.add(DebugMethodItem.build(registerFormatter, debugItem)); 454 } 455 } 456 457 private void setLabelSequentialNumbers() { 458 HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>(); 459 ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels()); 460 461 //sort the labels by their location in the method 462 Collections.sort(sortedLabels); 463 464 for (LabelMethodItem labelMethodItem: sortedLabels) { 465 Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix()); 466 if (labelSequence == null) { 467 labelSequence = 0; 468 } 469 labelMethodItem.setLabelSequence(labelSequence); 470 nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1); 471 } 472 } 473 474 public static class LabelCache { 475 protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>(); 476 477 public LabelCache() { 478 } 479 480 public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) { 481 LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem); 482 if (internedLabelMethodItem != null) { 483 return internedLabelMethodItem; 484 } 485 labels.put(labelMethodItem, labelMethodItem); 486 return labelMethodItem; 487 } 488 489 490 public Collection<LabelMethodItem> getLabels() { 491 return labels.values(); 492 } 493 } 494} 495