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