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