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