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