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