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