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