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