MethodDefinition.java revision 0b2f7d6a57e90424b3ee455c041aab3996c05f2c
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.Format.*; 32import org.jf.baksmali.IndentingWriter; 33import org.jf.baksmali.Renderers.IntegerRenderer; 34import org.jf.baksmali.baksmali; 35import org.jf.dexlib.*; 36import org.jf.dexlib.Code.*; 37import org.jf.dexlib.Code.Analysis.AnalyzedInstruction; 38import org.jf.dexlib.Code.Analysis.MethodAnalyzer; 39import org.jf.dexlib.Code.Analysis.ValidationException; 40import org.jf.dexlib.Code.Format.Format; 41import org.jf.dexlib.Debug.DebugInstructionIterator; 42import org.jf.dexlib.Util.AccessFlags; 43import org.jf.dexlib.Util.ExceptionWithContext; 44import org.jf.dexlib.Util.SparseIntArray; 45 46import java.io.IOException; 47import java.util.*; 48 49public class MethodDefinition { 50 private final ClassDataItem.EncodedMethod encodedMethod; 51 private final MethodAnalyzer methodAnalyzer; 52 53 private final LabelCache labelCache = new LabelCache(); 54 55 private final SparseIntArray packedSwitchMap; 56 private final SparseIntArray sparseSwitchMap; 57 private final SparseIntArray instructionMap; 58 59 public MethodDefinition(ClassDataItem.EncodedMethod encodedMethod) { 60 61 62 try { 63 this.encodedMethod = encodedMethod; 64 65 //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh. 66 67 if (encodedMethod.codeItem != null) { 68 methodAnalyzer = new MethodAnalyzer(encodedMethod, baksmali.deodex); 69 List<AnalyzedInstruction> instructions = methodAnalyzer.getInstructions(); 70 71 packedSwitchMap = new SparseIntArray(1); 72 sparseSwitchMap = new SparseIntArray(1); 73 instructionMap = new SparseIntArray(instructions.size()); 74 75 int currentCodeAddress = 0; 76 for (int i=0; i<instructions.size(); i++) { 77 AnalyzedInstruction instruction = instructions.get(i); 78 if (instruction.getInstruction().opcode == Opcode.PACKED_SWITCH) { 79 packedSwitchMap.append( 80 currentCodeAddress + ((OffsetInstruction)instruction.getInstruction()).getTargetAddressOffset(), 81 currentCodeAddress); 82 } else if (instruction.getInstruction().opcode == Opcode.SPARSE_SWITCH) { 83 sparseSwitchMap.append( 84 currentCodeAddress + ((OffsetInstruction)instruction.getInstruction()).getTargetAddressOffset(), 85 currentCodeAddress); 86 } 87 instructionMap.append(currentCodeAddress, i); 88 currentCodeAddress += instruction.getInstruction().getSize(currentCodeAddress); 89 } 90 } else { 91 packedSwitchMap = null; 92 sparseSwitchMap = null; 93 instructionMap = null; 94 methodAnalyzer = null; 95 } 96 }catch (Exception ex) { 97 throw ExceptionWithContext.withContext(ex, String.format("Error while processing method %s", 98 encodedMethod.method.getMethodString())); 99 } 100 } 101 102 public void writeTo(IndentingWriter writer, AnnotationSetItem annotationSet, 103 AnnotationSetRefList parameterAnnotations) throws IOException { 104 final CodeItem codeItem = encodedMethod.codeItem; 105 106 writer.write(".method "); 107 writeAccessFlags(writer, encodedMethod); 108 writer.write(encodedMethod.method.getMethodName().getStringValue()); 109 writer.write(encodedMethod.method.getPrototype().getPrototypeString()); 110 writer.write('\n'); 111 112 writer.indent(4); 113 if (codeItem != null) { 114 if (baksmali.useLocalsDirective) { 115 writer.write(".locals "); 116 } else { 117 writer.write(".registers "); 118 } 119 writer.printIntAsDec(getRegisterCount(encodedMethod)); 120 writer.write('\n'); 121 writeParameters(writer, codeItem, parameterAnnotations); 122 if (annotationSet != null) { 123 AnnotationFormatter.writeTo(writer, annotationSet); 124 } 125 126 writer.write('\n'); 127 128 for (MethodItem methodItem: getMethodItems()) { 129 if (methodItem.writeTo(writer)) { 130 writer.write('\n'); 131 } 132 } 133 } else { 134 if (annotationSet != null) { 135 AnnotationFormatter.writeTo(writer, annotationSet); 136 } 137 } 138 writer.deindent(4); 139 writer.write(".end method\n"); 140 } 141 142 private static int getRegisterCount(ClassDataItem.EncodedMethod encodedMethod) 143 { 144 int totalRegisters = encodedMethod.codeItem.getRegisterCount(); 145 if (baksmali.useLocalsDirective) { 146 int parameterRegisters = encodedMethod.method.getPrototype().getParameterRegisterCount(); 147 if ((encodedMethod.accessFlags & AccessFlags.STATIC.getValue()) == 0) { 148 parameterRegisters++; 149 } 150 return totalRegisters - parameterRegisters; 151 } 152 return totalRegisters; 153 } 154 155 private static void writeAccessFlags(IndentingWriter writer, ClassDataItem.EncodedMethod encodedMethod) 156 throws IOException { 157 for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(encodedMethod.accessFlags)) { 158 writer.write(accessFlag.toString()); 159 writer.write(' '); 160 } 161 } 162 163 private static void writeParameters(IndentingWriter writer, CodeItem codeItem, 164 AnnotationSetRefList parameterAnnotations) throws IOException { 165 DebugInfoItem debugInfoItem = null; 166 if (baksmali.outputDebugInfo && codeItem != null) { 167 debugInfoItem = codeItem.getDebugInfo(); 168 } 169 170 int parameterCount = 0; 171 AnnotationSetItem[] annotations; 172 StringIdItem[] parameterNames = null; 173 174 if (parameterAnnotations != null) { 175 annotations = parameterAnnotations.getAnnotationSets(); 176 parameterCount = annotations.length; 177 } else { 178 annotations = new AnnotationSetItem[0]; 179 } 180 181 if (debugInfoItem != null) { 182 parameterNames = debugInfoItem.getParameterNames(); 183 } 184 if (parameterNames == null) { 185 parameterNames = new StringIdItem[0]; 186 } 187 188 if (parameterCount < parameterNames.length) { 189 parameterCount = parameterNames.length; 190 } 191 192 for (int i=0; i<parameterCount; i++) { 193 AnnotationSetItem annotationSet = null; 194 if (i < annotations.length) { 195 annotationSet = annotations[i]; 196 } 197 198 StringIdItem parameterName = null; 199 if (i < parameterNames.length) { 200 parameterName = parameterNames[i]; 201 } 202 203 writer.write(".parameter"); 204 205 if (parameterName != null) { 206 writer.write('"'); 207 writer.write(parameterName.getStringValue()); 208 writer.write('"'); 209 } 210 211 writer.write('\n'); 212 if (annotationSet != null) { 213 writer.indent(4); 214 AnnotationFormatter.writeTo(writer, annotationSet); 215 writer.deindent(4); 216 217 writer.write(".end parameter\n"); 218 } 219 } 220 } 221 222 public LabelCache getLabelCache() { 223 return labelCache; 224 } 225 226 public ValidationException getValidationException() { 227 if (methodAnalyzer == null) { 228 return null; 229 } 230 231 return methodAnalyzer.getValidationException(); 232 } 233 234 public int getPackedSwitchBaseAddress(int packedSwitchDataAddress) { 235 int packedSwitchBaseAddress = this.packedSwitchMap.get(packedSwitchDataAddress, -1); 236 237 if (packedSwitchBaseAddress == -1) { 238 throw new RuntimeException("Could not find the packed switch statement corresponding to the packed " + 239 "switch data at address " + packedSwitchDataAddress); 240 } 241 242 return packedSwitchBaseAddress; 243 } 244 245 public int getSparseSwitchBaseAddress(int sparseSwitchDataAddress) { 246 int sparseSwitchBaseAddress = this.sparseSwitchMap.get(sparseSwitchDataAddress, -1); 247 248 if (sparseSwitchBaseAddress == -1) { 249 throw new RuntimeException("Could not find the sparse switch statement corresponding to the sparse " + 250 "switch data at address " + sparseSwitchDataAddress); 251 } 252 253 return sparseSwitchBaseAddress; 254 } 255 256 /** 257 * @param instructions The instructions array for this method 258 * @param instruction The instruction 259 * @return true if the specified instruction is a NOP, and the next instruction is one of the variable sized 260 * switch/array data structures 261 */ 262 private boolean isInstructionPaddingNop(List<AnalyzedInstruction> instructions, AnalyzedInstruction instruction) { 263 if (instruction.getInstruction().opcode != Opcode.NOP || 264 instruction.getInstruction().getFormat().variableSizeFormat) { 265 266 return false; 267 } 268 269 if (instruction.getInstructionIndex() == instructions.size()-1) { 270 return false; 271 } 272 273 AnalyzedInstruction nextInstruction = instructions.get(instruction.getInstructionIndex()+1); 274 if (nextInstruction.getInstruction().getFormat().variableSizeFormat) { 275 return true; 276 } 277 return false; 278 } 279 280 private List<MethodItem> getMethodItems() { 281 List<MethodItem> methodItems = new ArrayList<MethodItem>(); 282 283 if (encodedMethod.codeItem == null) { 284 return methodItems; 285 } 286 287 if (baksmali.registerInfo != 0 || baksmali.deodex || baksmali.verify) { 288 methodAnalyzer.analyze(); 289 290 ValidationException validationException = methodAnalyzer.getValidationException(); 291 if (validationException != null) { 292 methodItems.add(new CommentMethodItem( 293 String.format("ValidationException: %s" ,validationException.getMessage()), 294 validationException.getCodeAddress(), Integer.MIN_VALUE)); 295 } else if (baksmali.verify) { 296 methodAnalyzer.verify(); 297 298 validationException = methodAnalyzer.getValidationException(); 299 if (validationException != null) { 300 methodItems.add(new CommentMethodItem( 301 String.format("ValidationException: %s" ,validationException.getMessage()), 302 validationException.getCodeAddress(), Integer.MIN_VALUE)); 303 } 304 } 305 } 306 List<AnalyzedInstruction> instructions = methodAnalyzer.getInstructions(); 307 308 AnalyzedInstruction lastInstruction = null; 309 310 for (int i=instructions.size()-1; i>=0; i--) { 311 AnalyzedInstruction instruction = instructions.get(i); 312 313 if (!instruction.isDead()) { 314 lastInstruction = instruction; 315 break; 316 } 317 } 318 319 boolean lastIsUnreachable = false; 320 321 int currentCodeAddress = 0; 322 for (int i=0; i<instructions.size(); i++) { 323 AnalyzedInstruction instruction = instructions.get(i); 324 325 MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 326 encodedMethod.codeItem, currentCodeAddress, instruction.isDead(), instruction.getInstruction(), 327 instruction == lastInstruction); 328 329 if (instruction.isDead() && !instruction.getInstruction().getFormat().variableSizeFormat) { 330 methodItems.add(new CommentedOutMethodItem(methodItem)); 331 lastIsUnreachable = false; 332 } else if ( instruction.getPredecessorCount() == 0 && 333 !instruction.getInstruction().getFormat().variableSizeFormat && 334 !isInstructionPaddingNop(instructions, instruction)) { 335 336 if (!lastIsUnreachable) { 337 methodItems.add( 338 new CommentMethodItem("Unreachable code", currentCodeAddress, Double.MIN_VALUE)); 339 } 340 341 methodItems.add(new CommentedOutMethodItem(methodItem)); 342 lastIsUnreachable = true; 343 } else { 344 methodItems.add(methodItem); 345 lastIsUnreachable = false; 346 } 347 348 if (instruction.getInstruction().getFormat() == Format.UnresolvedNullReference) { 349 methodItems.add(new CommentedOutMethodItem( 350 InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, encodedMethod.codeItem, 351 currentCodeAddress, instruction.isDead(), instruction.getOriginalInstruction(), 352 false))); 353 } 354 355 if (i != instructions.size() - 1) { 356 methodItems.add(new BlankMethodItem(currentCodeAddress)); 357 } 358 359 if (baksmali.addCodeOffsets) { 360 methodItems.add(new MethodItem(currentCodeAddress) { 361 362 @Override 363 public double getSortOrder() { 364 return -1000; 365 } 366 367 @Override 368 public boolean writeTo(IndentingWriter writer) throws IOException { 369 writer.write("#@"); 370 IntegerRenderer.writeUnsignedTo(writer, codeAddress); 371 return true; 372 } 373 }); 374 } 375 376 if (baksmali.registerInfo != 0 && !instruction.getInstruction().getFormat().variableSizeFormat) { 377 methodItems.add( 378 new PreInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress)); 379 380 methodItems.add( 381 new PostInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress)); 382 } 383 384 currentCodeAddress += instruction.getInstruction().getSize(currentCodeAddress); 385 } 386 387 addTries(methodItems); 388 addDebugInfo(methodItems); 389 390 if (baksmali.useSequentialLabels) { 391 setLabelSequentialNumbers(); 392 } 393 394 395 for (LabelMethodItem labelMethodItem: labelCache.getLabels()) { 396 if (labelMethodItem.isCommentedOut()) { 397 methodItems.add(new CommentedOutMethodItem(labelMethodItem)); 398 } else { 399 methodItems.add(labelMethodItem); 400 } 401 } 402 403 Collections.sort(methodItems); 404 405 return methodItems; 406 } 407 408 private void addTries(List<MethodItem> methodItems) { 409 if (encodedMethod.codeItem == null || encodedMethod.codeItem.getTries() == null) { 410 return; 411 } 412 413 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 414 415 for (CodeItem.TryItem tryItem: encodedMethod.codeItem.getTries()) { 416 int startAddress = tryItem.getStartCodeAddress(); 417 int endAddress = tryItem.getStartCodeAddress() + tryItem.getTryLength(); 418 419 /** 420 * The end address points to the address immediately after the end of the last 421 * instruction that the try block covers. We want the .catch directive and end_try 422 * label to be associated with the last covered instruction, so we need to get 423 * the address for that instruction 424 */ 425 426 int index = instructionMap.get(endAddress, -1); 427 int lastInstructionAddress; 428 429 /** 430 * If we couldn't find the index, then the try block probably extends to the last instruction in the 431 * method, and so endAddress would be the address immediately after the end of the last instruction. 432 * Check to make sure this is the case, if not, throw an exception. 433 */ 434 if (index == -1) { 435 Instruction lastInstruction = instructions[instructions.length - 1]; 436 lastInstructionAddress = instructionMap.keyAt(instructionMap.size() - 1); 437 438 if (endAddress != lastInstructionAddress + lastInstruction.getSize(lastInstructionAddress)) { 439 throw new RuntimeException("Invalid code offset " + endAddress + " for the try block end address"); 440 } 441 } else { 442 if (index == 0) { 443 throw new RuntimeException("Unexpected instruction index"); 444 } 445 Instruction lastInstruction = instructions[index - 1]; 446 447 if (lastInstruction.getFormat().variableSizeFormat) { 448 throw new RuntimeException("This try block unexpectedly ends on a switch/array data block."); 449 } 450 451 //getSize for non-variable size formats should return the same size regardless of code address, so just 452 //use a dummy address of "0" 453 lastInstructionAddress = endAddress - lastInstruction.getSize(0); 454 } 455 456 //add the catch all handler if it exists 457 int catchAllAddress = tryItem.encodedCatchHandler.getCatchAllHandlerAddress(); 458 if (catchAllAddress != -1) { 459 CatchMethodItem catchAllMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, null, 460 startAddress, endAddress, catchAllAddress); 461 methodItems.add(catchAllMethodItem); 462 } 463 464 //add the rest of the handlers 465 for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) { 466 //use the address from the last covered instruction 467 CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, 468 handler.exceptionType, startAddress, endAddress, handler.getHandlerAddress()); 469 methodItems.add(catchMethodItem); 470 } 471 } 472 } 473 474 private void addDebugInfo(final List<MethodItem> methodItems) { 475 if (encodedMethod.codeItem == null || encodedMethod.codeItem.getDebugInfo() == null) { 476 return; 477 } 478 479 final CodeItem codeItem = encodedMethod.codeItem; 480 DebugInfoItem debugInfoItem = codeItem.getDebugInfo(); 481 482 DebugInstructionIterator.DecodeInstructions(debugInfoItem, codeItem.getRegisterCount(), 483 new DebugInstructionIterator.ProcessDecodedDebugInstructionDelegate() { 484 @Override 485 public void ProcessStartLocal(final int codeAddress, final int length, final int registerNum, 486 final StringIdItem name, final TypeIdItem type) { 487 methodItems.add(new DebugMethodItem(codeAddress, -1) { 488 @Override 489 public boolean writeTo(IndentingWriter writer) throws IOException { 490 writeStartLocal(writer, codeItem, registerNum, name, type, null); 491 return true; 492 } 493 }); 494 } 495 496 @Override 497 public void ProcessStartLocalExtended(final int codeAddress, final int length, 498 final int registerNum, final StringIdItem name, 499 final TypeIdItem type, final StringIdItem signature) { 500 methodItems.add(new DebugMethodItem(codeAddress, -1) { 501 @Override 502 public boolean writeTo(IndentingWriter writer) throws IOException { 503 writeStartLocal(writer, codeItem, registerNum, name, type, signature); 504 return true; 505 } 506 }); 507 } 508 509 @Override 510 public void ProcessEndLocal(final int codeAddress, final int length, final int registerNum, 511 final StringIdItem name, final TypeIdItem type, 512 final StringIdItem signature) { 513 methodItems.add(new DebugMethodItem(codeAddress, -1) { 514 @Override 515 public boolean writeTo(IndentingWriter writer) throws IOException { 516 writeEndLocal(writer, codeItem, registerNum, name, type, signature); 517 return true; 518 } 519 }); 520 } 521 522 @Override 523 public void ProcessRestartLocal(final int codeAddress, final int length, final int registerNum, 524 final StringIdItem name, final TypeIdItem type, 525 final StringIdItem signature) { 526 methodItems.add(new DebugMethodItem(codeAddress, -1) { 527 @Override 528 public boolean writeTo(IndentingWriter writer) throws IOException { 529 writeRestartLocal(writer, codeItem, registerNum, name, type, signature); 530 return true; 531 } 532 }); 533 } 534 535 @Override 536 public void ProcessSetPrologueEnd(int codeAddress) { 537 methodItems.add(new DebugMethodItem(codeAddress, -4) { 538 @Override 539 public boolean writeTo(IndentingWriter writer) throws IOException { 540 writeEndPrologue(writer); 541 return true; 542 } 543 }); 544 } 545 546 @Override 547 public void ProcessSetEpilogueBegin(int codeAddress) { 548 methodItems.add(new DebugMethodItem(codeAddress, -4) { 549 @Override 550 public boolean writeTo(IndentingWriter writer) throws IOException { 551 writeBeginEpilogue(writer); 552 return true; 553 } 554 }); 555 } 556 557 @Override 558 public void ProcessSetFile(int codeAddress, int length, final StringIdItem name) { 559 methodItems.add(new DebugMethodItem(codeAddress, -3) { 560 @Override 561 public boolean writeTo(IndentingWriter writer) throws IOException { 562 writeSetFile(writer, name.getStringValue()); 563 return true; 564 } 565 }); 566 } 567 568 @Override 569 public void ProcessLineEmit(int codeAddress, final int line) { 570 methodItems.add(new DebugMethodItem(codeAddress, -2) { 571 @Override 572 public boolean writeTo(IndentingWriter writer) throws IOException { 573 writeLine(writer, line); 574 return true; 575 } 576 }); 577 } 578 }); 579 } 580 581 private void setLabelSequentialNumbers() { 582 HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>(); 583 ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels()); 584 585 //sort the labels by their location in the method 586 Collections.sort(sortedLabels); 587 588 for (LabelMethodItem labelMethodItem: sortedLabels) { 589 Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix()); 590 if (labelSequence == null) { 591 labelSequence = 0; 592 } 593 labelMethodItem.setLabelSequence(labelSequence); 594 nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1); 595 } 596 } 597 598 public static class LabelCache { 599 protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>(); 600 601 public LabelCache() { 602 } 603 604 public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) { 605 LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem); 606 if (internedLabelMethodItem != null) { 607 if (!labelMethodItem.isCommentedOut()) { 608 internedLabelMethodItem.setUncommented(); 609 } 610 return internedLabelMethodItem; 611 } 612 labels.put(labelMethodItem, labelMethodItem); 613 return labelMethodItem; 614 } 615 616 617 public Collection<LabelMethodItem> getLabels() { 618 return labels.values(); 619 } 620 } 621} 622