CodeItem.java revision 5f758b60b4878b34e20864915e85be71259e7b19
1/* 2 * [The "BSD licence"] 3 * Copyright (c) 2009 Ben Gruver 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.dexlib; 30 31import org.jf.dexlib.Code.*; 32import org.jf.dexlib.Code.Format.Instruction20t; 33import org.jf.dexlib.Code.Format.Instruction30t; 34import org.jf.dexlib.Code.Format.Instruction21c; 35import org.jf.dexlib.Code.Format.Instruction31c; 36import org.jf.dexlib.Util.*; 37import org.jf.dexlib.Debug.DebugInstructionIterator; 38 39import java.util.List; 40import java.util.ArrayList; 41 42public class CodeItem extends Item<CodeItem> { 43 private int registerCount; 44 private int inWords; 45 private int outWords; 46 private DebugInfoItem debugInfo; 47 private Instruction[] instructions; 48 private TryItem[] tries; 49 private EncodedCatchHandler[] encodedCatchHandlers; 50 51 private ClassDataItem.EncodedMethod parent; 52 53 /** 54 * Creates a new uninitialized <code>CodeItem</code> 55 * @param dexFile The <code>DexFile</code> that this item belongs to 56 */ 57 public CodeItem(DexFile dexFile) { 58 super(dexFile); 59 } 60 61 /** 62 * Creates a new <code>CodeItem</code> with the given values. 63 * @param dexFile The <code>DexFile</code> that this item belongs to 64 * @param registerCount the number of registers that the method containing this code uses 65 * @param inWords the number of 2-byte words that the parameters to the method containing this code take 66 * @param outWords the maximum number of 2-byte words for the arguments of any method call in this code 67 * @param debugInfo the debug information for this code/method 68 * @param instructions the instructions for this code item 69 * @param tries an array of the tries defined for this code/method 70 * @param encodedCatchHandlers an array of the exception handlers defined for this code/method 71 */ 72 private CodeItem(DexFile dexFile, 73 int registerCount, 74 int inWords, 75 int outWords, 76 DebugInfoItem debugInfo, 77 Instruction[] instructions, 78 TryItem[] tries, 79 EncodedCatchHandler[] encodedCatchHandlers) { 80 super(dexFile); 81 82 this.registerCount = registerCount; 83 this.inWords = inWords; 84 this.outWords = outWords; 85 this.debugInfo = debugInfo; 86 if (debugInfo != null) { 87 debugInfo.setParent(this); 88 } 89 90 this.instructions = instructions; 91 this.tries = tries; 92 this.encodedCatchHandlers = encodedCatchHandlers; 93 } 94 95 /** 96 * Returns a new <code>CodeItem</code> with the given values. 97 * @param dexFile The <code>DexFile</code> that this item belongs to 98 * @param registerCount the number of registers that the method containing this code uses 99 * @param inWords the number of 2-byte words that the parameters to the method containing this code take 100 * @param outWords the maximum number of 2-byte words for the arguments of any method call in this code 101 * @param debugInfo the debug information for this code/method 102 * @param instructions the instructions for this code item 103 * @param tries a list of the tries defined for this code/method or null if none 104 * @param encodedCatchHandlers a list of the exception handlers defined for this code/method or null if none 105 * @return a new <code>CodeItem</code> with the given values. 106 */ 107 public static CodeItem getInternedCodeItem(DexFile dexFile, 108 int registerCount, 109 int inWords, 110 int outWords, 111 DebugInfoItem debugInfo, 112 List<Instruction> instructions, 113 List<TryItem> tries, 114 List<EncodedCatchHandler> encodedCatchHandlers) { 115 TryItem[] triesArray = null; 116 EncodedCatchHandler[] encodedCatchHandlersArray = null; 117 Instruction[] instructionsArray = null; 118 119 if (tries != null && tries.size() > 0) { 120 triesArray = new TryItem[tries.size()]; 121 tries.toArray(triesArray); 122 } 123 124 if (encodedCatchHandlers != null && encodedCatchHandlers.size() > 0) { 125 encodedCatchHandlersArray = new EncodedCatchHandler[encodedCatchHandlers.size()]; 126 encodedCatchHandlers.toArray(encodedCatchHandlersArray); 127 } 128 129 if (instructions != null && instructions.size() > 0) { 130 instructionsArray = new Instruction[instructions.size()]; 131 instructions.toArray(instructionsArray); 132 } 133 134 CodeItem codeItem = new CodeItem(dexFile, registerCount, inWords, outWords, debugInfo, instructionsArray, 135 triesArray, encodedCatchHandlersArray); 136 return dexFile.CodeItemsSection.intern(codeItem); 137 } 138 139 /** {@inheritDoc} */ 140 protected void readItem(Input in, ReadContext readContext) { 141 this.registerCount = in.readShort(); 142 this.inWords = in.readShort(); 143 this.outWords = in.readShort(); 144 int triesCount = in.readShort(); 145 this.debugInfo = (DebugInfoItem)readContext.getOffsettedItemByOffset(ItemType.TYPE_DEBUG_INFO_ITEM, 146 in.readInt()); 147 if (this.debugInfo != null) { 148 this.debugInfo.setParent(this); 149 } 150 int instructionCount = in.readInt(); 151 152 final ArrayList<Instruction> instructionList = new ArrayList<Instruction>(); 153 154 byte[] encodedInstructions = in.readBytes(instructionCount * 2); 155 InstructionIterator.IterateInstructions(dexFile, encodedInstructions, 156 new InstructionIterator.ProcessInstructionDelegate() { 157 public void ProcessInstruction(int index, Instruction instruction) { 158 instructionList.add(instruction); 159 } 160 }); 161 162 this.instructions = new Instruction[instructionList.size()]; 163 instructionList.toArray(instructions); 164 165 if (triesCount > 0) { 166 in.alignTo(4); 167 168 //we need to read in the catch handlers first, so save the offset to the try items for future reference 169 int triesOffset = in.getCursor(); 170 in.setCursor(triesOffset + 8 * triesCount); 171 172 //read in the encoded catch handlers 173 int encodedHandlerStart = in.getCursor(); 174 int handlerCount = in.readUnsignedLeb128(); 175 SparseArray<EncodedCatchHandler> handlerMap = new SparseArray<EncodedCatchHandler>(handlerCount); 176 encodedCatchHandlers = new EncodedCatchHandler[handlerCount]; 177 for (int i=0; i<handlerCount; i++) { 178 int position = in.getCursor() - encodedHandlerStart; 179 encodedCatchHandlers[i] = new EncodedCatchHandler(dexFile, in); 180 handlerMap.append(position, encodedCatchHandlers[i]); 181 } 182 int codeItemEnd = in.getCursor(); 183 184 //now go back and read the tries 185 in.setCursor(triesOffset); 186 tries = new TryItem[triesCount]; 187 for (int i=0; i<triesCount; i++) { 188 tries[i] = new TryItem(in, handlerMap); 189 } 190 191 //and now back to the end of the code item 192 in.setCursor(codeItemEnd); 193 } 194 } 195 196 /** {@inheritDoc} */ 197 protected int placeItem(int offset) { 198 offset += 16 + getInstructionsLength(); 199 200 if (tries != null && tries.length > 0) { 201 if (offset % 4 != 0) { 202 offset+=2; 203 } 204 205 offset += tries.length * 8; 206 int encodedCatchHandlerBaseOffset = offset; 207 offset += Leb128Utils.unsignedLeb128Size(encodedCatchHandlers.length); 208 for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) { 209 offset = encodedCatchHandler.place(offset, encodedCatchHandlerBaseOffset); 210 } 211 } 212 return offset; 213 } 214 215 /** {@inheritDoc} */ 216 protected void writeItem(final AnnotatedOutput out) { 217 int instructionsLength = getInstructionsLength()/2; 218 219 if (out.annotates()) { 220 out.annotate(0, parent.method.getMethodString()); 221 out.annotate(2, "registers_size: 0x" + Integer.toHexString(registerCount) + " (" + registerCount + ")"); 222 out.annotate(2, "ins_size: 0x" + Integer.toHexString(inWords) + " (" + inWords + ")"); 223 out.annotate(2, "outs_size: 0x" + Integer.toHexString(outWords) + " (" + outWords + ")"); 224 int triesLength = tries==null?0:tries.length; 225 out.annotate(2, "tries_size: 0x" + Integer.toHexString(triesLength) + " (" + triesLength + ")"); 226 if (debugInfo == null) { 227 out.annotate(4, "debug_info_off:"); 228 } else { 229 out.annotate(4, "debug_info_off: 0x" + debugInfo.getOffset()); 230 } 231 out.annotate(4, "insns_size: 0x" + Integer.toHexString(instructionsLength) + " (" + 232 (instructionsLength) + ")"); 233 } 234 235 out.writeShort(registerCount); 236 out.writeShort(inWords); 237 out.writeShort(outWords); 238 if (tries == null) { 239 out.writeShort(0); 240 } else { 241 out.writeShort(tries.length); 242 } 243 if (debugInfo == null) { 244 out.writeInt(0); 245 } else { 246 out.writeInt(debugInfo.getOffset()); 247 } 248 249 int currentCodeOffset = 0; 250 for (Instruction instruction: instructions) { 251 currentCodeOffset += instruction.getSize(currentCodeOffset); 252 } 253 254 out.writeInt(instructionsLength); 255 256 currentCodeOffset = 0; 257 for (Instruction instruction: instructions) { 258 currentCodeOffset = instruction.write(out, currentCodeOffset); 259 } 260 261 if (tries != null && tries.length > 0) { 262 if (out.annotates()) { 263 if ((currentCodeOffset % 4) != 0) { 264 out.annotate("padding"); 265 out.writeShort(0); 266 } 267 268 int index = 0; 269 for (TryItem tryItem: tries) { 270 out.annotate(0, "[0x" + Integer.toHexString(index++) + "] try_item"); 271 out.indent(); 272 tryItem.writeTo(out); 273 out.deindent(); 274 } 275 276 out.annotate("handler_count: 0x" + Integer.toHexString(encodedCatchHandlers.length) + "(" + 277 encodedCatchHandlers.length + ")"); 278 out.writeUnsignedLeb128(encodedCatchHandlers.length); 279 280 index = 0; 281 for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) { 282 out.annotate(0, "[" + Integer.toHexString(index++) + "] encoded_catch_handler"); 283 out.indent(); 284 encodedCatchHandler.writeTo(out); 285 out.deindent(); 286 } 287 } else { 288 if ((currentCodeOffset % 4) != 0) { 289 out.writeShort(0); 290 } 291 292 for (TryItem tryItem: tries) { 293 tryItem.writeTo(out); 294 } 295 296 out.writeUnsignedLeb128(encodedCatchHandlers.length); 297 298 for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) { 299 encodedCatchHandler.writeTo(out); 300 } 301 } 302 } 303 } 304 305 /** {@inheritDoc} */ 306 public ItemType getItemType() { 307 return ItemType.TYPE_CODE_ITEM; 308 } 309 310 /** {@inheritDoc} */ 311 public String getConciseIdentity() { 312 return "code_item @0x" + Integer.toHexString(getOffset()); 313 } 314 315 /** {@inheritDoc} */ 316 public int compareTo(CodeItem other) { 317 if (parent == null) { 318 if (other.parent == null) { 319 return 0; 320 } 321 return -1; 322 } 323 if (other.parent == null) { 324 return 1; 325 } 326 return parent.method.compareTo(other.parent.method); 327 } 328 329 /** 330 * @return the register count 331 */ 332 public int getRegisterCount() { 333 return registerCount; 334 } 335 336 /** 337 * @return an array of the instructions in this code item 338 */ 339 public Instruction[] getInstructions() { 340 return instructions; 341 } 342 343 /** 344 * @return an array of the <code>TryItem</code> objects in this <code>CodeItem</code> 345 */ 346 public TryItem[] getTries() { 347 return tries; 348 } 349 350 /** 351 * @return an array of the <code>EncodedCatchHandler</code> objects in this <code>CodeItem</code> 352 */ 353 public EncodedCatchHandler[] getHandlers() { 354 return encodedCatchHandlers; 355 } 356 357 /** 358 * @return the <code>DebugInfoItem</code> associated with this <code>CodeItem</code> 359 */ 360 public DebugInfoItem getDebugInfo() { 361 return debugInfo; 362 } 363 364 /** 365 * Sets the <code>MethodIdItem</code> of the method that this <code>CodeItem</code> is associated with 366 * @param encodedMethod the <code>EncodedMethod</code> of the method that this <code>CodeItem</code> is associated 367 * with 368 */ 369 protected void setParent(ClassDataItem.EncodedMethod encodedMethod) { 370 this.parent = encodedMethod; 371 } 372 373 /** 374 * @return the MethodIdItem of the method that this CodeItem belongs to 375 */ 376 public ClassDataItem.EncodedMethod getParent() { 377 return parent; 378 } 379 380 /** 381 * Used by OdexUtil to update this <code>CodeItem</code> with a deodexed version of the instructions 382 * @param newInstructions the new instructions to use for this code item 383 */ 384 public void updateCode(Instruction[] newInstructions) { 385 this.instructions = newInstructions; 386 } 387 388 private int getInstructionsLength() { 389 int offset = 0; 390 for (Instruction instruction: instructions) { 391 offset += instruction.getSize(offset); 392 } 393 return offset; 394 } 395 396 /** 397 * Go through the instructions and perform any of the following fixes that are applicable 398 * - Replace const-string instruction with const-string/jumbo, when the string index is too big 399 * - Replace goto and goto/16 with a larger version of goto, when the target is too far away 400 * TODO: we should be able to replace if-* instructions with targets that are too far away with a negated if followed by a goto/32 to the original target 401 * TODO: remove multiple nops that occur before a switch/array data pseudo instruction. In some cases, multiple smali-baksmali cycles with changes in between could cause nops to start piling up 402 * 403 * The above fixes are applied iteratively, until no more fixes have been performed 404 */ 405 public void fixInstructions(boolean fixStringConst, boolean fixGoto) { 406 boolean didSomething = false; 407 408 do 409 { 410 didSomething = false; 411 412 int currentCodeOffset = 0; 413 for (int i=0; i<instructions.length; i++) { 414 Instruction instruction = instructions[i]; 415 416 if (fixGoto && instruction.opcode == Opcode.GOTO) { 417 int offset = ((OffsetInstruction)instruction).getOffset(); 418 419 if (((byte)offset) != offset) { 420 //the offset doesn't fit within a byte, we need to upgrade to a goto/16 or goto/32 421 422 if ((short)offset == offset) { 423 //the offset fits in a short, so upgrade to a goto/16 h 424 replaceInstructionAtOffset(currentCodeOffset, new Instruction20t(Opcode.GOTO_16, offset)); 425 } 426 else { 427 //The offset won't fit into a short, we have to upgrade to a goto/32 428 replaceInstructionAtOffset(currentCodeOffset, new Instruction30t(Opcode.GOTO_32, offset)); 429 } 430 didSomething = true; 431 break; 432 } 433 } else if (fixGoto && instruction.opcode == Opcode.GOTO_16) { 434 int offset = ((OffsetInstruction)instruction).getOffset(); 435 436 if (((short)offset) != offset) { 437 //the offset doesn't fit within a short, we need to upgrade to a goto/32 438 replaceInstructionAtOffset(currentCodeOffset, new Instruction30t(Opcode.GOTO_32, offset)); 439 didSomething = true; 440 break; 441 } 442 } else if (fixStringConst && instruction.opcode == Opcode.CONST_STRING) { 443 Instruction21c constStringInstruction = (Instruction21c)instruction; 444 if (constStringInstruction.getReferencedItem().getIndex() > 0xFFFF) { 445 replaceInstructionAtOffset(currentCodeOffset, new Instruction31c(Opcode.CONST_STRING_JUMBO, 446 (short)constStringInstruction.getRegisterA(), 447 constStringInstruction.getReferencedItem())); 448 didSomething = true; 449 break; 450 } 451 } 452 453 currentCodeOffset += instruction.getSize(currentCodeOffset); 454 } 455 }while(didSomething); 456 } 457 458 private void replaceInstructionAtOffset(int offset, Instruction replacementInstruction) { 459 Instruction originalInstruction = null; 460 461 int[] originalInstructionOffsets = new int[instructions.length+1]; 462 SparseIntArray originalSwitchOffsetByOriginalSwitchDataOffset = new SparseIntArray(); 463 464 int currentCodeOffset = 0; 465 int instructionIndex = 0; 466 int i; 467 for (i=0; i<instructions.length; i++) { 468 Instruction instruction = instructions[i]; 469 470 if (currentCodeOffset == offset) { 471 originalInstruction = instruction; 472 instructionIndex = i; 473 } 474 475 if (instruction.opcode == Opcode.PACKED_SWITCH || instruction.opcode == Opcode.SPARSE_SWITCH) { 476 OffsetInstruction offsetInstruction = (OffsetInstruction)instruction; 477 478 int switchDataOffset = currentCodeOffset + offsetInstruction.getOffset() * 2; 479 if (originalSwitchOffsetByOriginalSwitchDataOffset.indexOfKey(switchDataOffset) < 0) { 480 originalSwitchOffsetByOriginalSwitchDataOffset.put(switchDataOffset, currentCodeOffset); 481 } 482 } 483 484 originalInstructionOffsets[i] = currentCodeOffset; 485 currentCodeOffset += instruction.getSize(currentCodeOffset); 486 } 487 //add the offset just past the end of the last instruction, to help when fixing up try blocks that end 488 //at the end of the method 489 originalInstructionOffsets[i] = currentCodeOffset; 490 491 if (originalInstruction == null) { 492 throw new RuntimeException("There is no instruction at offset " + offset); 493 } 494 495 instructions[instructionIndex] = replacementInstruction; 496 497 //if we're replacing the instruction with one of the same size, we don't have to worry about fixing 498 //up any offsets 499 if (originalInstruction.getSize(offset) == replacementInstruction.getSize(offset)) { 500 return; 501 } 502 503 //TODO: replace these with a callable delegate 504 final SparseIntArray originalOffsetsByNewOffset = new SparseIntArray(); 505 final SparseIntArray newOffsetsByOriginalOffset = new SparseIntArray(); 506 507 currentCodeOffset = 0; 508 for (i=0; i<instructions.length; i++) { 509 Instruction instruction = instructions[i]; 510 511 int originalOffset = originalInstructionOffsets[i]; 512 originalOffsetsByNewOffset.append(currentCodeOffset, originalOffset); 513 newOffsetsByOriginalOffset.append(originalOffset, currentCodeOffset); 514 515 currentCodeOffset += instruction.getSize(currentCodeOffset); 516 } 517 518 //add the offset just past the end of the last instruction, to help when fixing up try blocks that end 519 //at the end of the method 520 originalOffsetsByNewOffset.append(currentCodeOffset, originalInstructionOffsets[i]); 521 newOffsetsByOriginalOffset.append(originalInstructionOffsets[i], currentCodeOffset); 522 523 //update any "offset" instructions, or switch data instructions 524 currentCodeOffset = 0; 525 for (i=0; i<instructions.length; i++) { 526 Instruction instruction = instructions[i]; 527 528 if (instruction instanceof OffsetInstruction) { 529 OffsetInstruction offsetInstruction = (OffsetInstruction)instruction; 530 531 assert originalOffsetsByNewOffset.indexOfKey(currentCodeOffset) >= 0; 532 int originalOffset = originalOffsetsByNewOffset.get(currentCodeOffset); 533 534 int originalInstructionTarget = originalOffset + offsetInstruction.getOffset() * 2; 535 536 assert newOffsetsByOriginalOffset.indexOfKey(originalInstructionTarget) >= 0; 537 int newInstructionTarget = newOffsetsByOriginalOffset.get(originalInstructionTarget); 538 539 int newOffset = (newInstructionTarget - currentCodeOffset) / 2; 540 541 if (newOffset != offsetInstruction.getOffset()) { 542 offsetInstruction.updateOffset(newOffset); 543 } 544 } else if (instruction instanceof MultiOffsetInstruction) { 545 MultiOffsetInstruction multiOffsetInstruction = (MultiOffsetInstruction)instruction; 546 547 assert originalOffsetsByNewOffset.indexOfKey(currentCodeOffset) >= 0; 548 int originalDataOffset = originalOffsetsByNewOffset.get(currentCodeOffset); 549 550 int originalSwitchOffset = originalSwitchOffsetByOriginalSwitchDataOffset.get(originalDataOffset, -1); 551 if (originalSwitchOffset == -1) { 552 throw new RuntimeException("This method contains an unreferenced switch data block, and can't be automatically fixed."); 553 } 554 555 assert newOffsetsByOriginalOffset.indexOfKey(originalSwitchOffset) >= 0; 556 int newSwitchOffset = newOffsetsByOriginalOffset.get(originalSwitchOffset); 557 558 int[] targets = multiOffsetInstruction.getTargets(); 559 for (int t=0; t<targets.length; t++) { 560 int originalTargetOffset = originalSwitchOffset + targets[t]*2; 561 assert newOffsetsByOriginalOffset.indexOfKey(originalTargetOffset) >= 0; 562 int newTargetOffset = newOffsetsByOriginalOffset.get(originalTargetOffset); 563 int newOffset = (newTargetOffset - newSwitchOffset)/2; 564 if (newOffset != targets[t]) { 565 multiOffsetInstruction.updateTarget(t, newOffset); 566 } 567 } 568 } 569 currentCodeOffset += instruction.getSize(currentCodeOffset); 570 } 571 572 if (debugInfo != null) { 573 final byte[] encodedDebugInfo = debugInfo.getEncodedDebugInfo(); 574 575 ByteArrayInput debugInput = new ByteArrayInput(encodedDebugInfo); 576 577 DebugInstructionFixer debugInstructionFixer = new DebugInstructionFixer(encodedDebugInfo, 578 newOffsetsByOriginalOffset, originalOffsetsByNewOffset); 579 DebugInstructionIterator.IterateInstructions(debugInput, debugInstructionFixer); 580 581 if (debugInstructionFixer.result != null) { 582 debugInfo.setEncodedDebugInfo(debugInstructionFixer.result); 583 } 584 } 585 586 if (encodedCatchHandlers != null) { 587 for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) { 588 if (encodedCatchHandler.catchAllHandlerAddress != -1) { 589 assert newOffsetsByOriginalOffset.indexOfKey(encodedCatchHandler.catchAllHandlerAddress*2) >= 0; 590 encodedCatchHandler.catchAllHandlerAddress = 591 newOffsetsByOriginalOffset.get(encodedCatchHandler.catchAllHandlerAddress*2)/2; 592 } 593 594 for (EncodedTypeAddrPair handler: encodedCatchHandler.handlers) { 595 assert newOffsetsByOriginalOffset.indexOfKey(handler.handlerAddress*2) >= 0; 596 handler.handlerAddress = newOffsetsByOriginalOffset.get(handler.handlerAddress*2)/2; 597 } 598 } 599 } 600 601 if (this.tries != null) { 602 for (TryItem tryItem: tries) { 603 int startAddress = tryItem.startAddress; 604 int endAddress = tryItem.startAddress + tryItem.instructionCount; 605 606 assert newOffsetsByOriginalOffset.indexOfKey(startAddress * 2) >= 0; 607 tryItem.startAddress = newOffsetsByOriginalOffset.get(startAddress * 2)/2; 608 609 assert newOffsetsByOriginalOffset.indexOfKey(endAddress * 2) >= 0; 610 tryItem.instructionCount = newOffsetsByOriginalOffset.get(endAddress * 2)/2 - tryItem.startAddress; 611 } 612 } 613 } 614 615 private class DebugInstructionFixer extends DebugInstructionIterator.ProcessRawDebugInstructionDelegate { 616 private int address = 0; 617 private SparseIntArray newOffsetsByOriginalOffset; 618 private SparseIntArray originalOffsetsByNewOffset; 619 private final byte[] originalEncodedDebugInfo; 620 public byte[] result = null; 621 622 public DebugInstructionFixer(byte[] originalEncodedDebugInfo, SparseIntArray newOffsetsByOriginalOffset, 623 SparseIntArray originalOffsetsByNewOffset) { 624 this.newOffsetsByOriginalOffset = newOffsetsByOriginalOffset; 625 this.originalOffsetsByNewOffset = originalOffsetsByNewOffset; 626 this.originalEncodedDebugInfo = originalEncodedDebugInfo; 627 } 628 629 630 @Override 631 public void ProcessAdvancePC(int startOffset, int length, int addressDelta) { 632 address += addressDelta; 633 634 if (result != null) { 635 return; 636 } 637 638 int newOffset = newOffsetsByOriginalOffset.get(address*2, -1); 639 640 //The address might not point to an actual instruction in some cases, for example, if an AdvancePC 641 //instruction was inserted just before a "special" instruction, to fix up the offsets for a previous 642 //instruction replacement. 643 //In this case, it should be safe to skip, because there will be another AdvancePC/SpecialOpcode that will 644 //bump up the address to point to a valid instruction before anything (line/local/etc.) is emitted 645 if (newOffset == -1) { 646 return; 647 } 648 649 assert newOffset != -1; 650 newOffset = newOffset / 2; 651 652 if (newOffset != address) { 653 int newAddressDelta = newOffset - (address - addressDelta); 654 assert newAddressDelta > 0; 655 int addressDiffSize = Leb128Utils.unsignedLeb128Size(newAddressDelta); 656 657 result = new byte[originalEncodedDebugInfo.length + addressDiffSize - (length - 1)]; 658 659 System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startOffset); 660 661 result[startOffset] = 0x01; //DBG_ADVANCE_PC debug opcode 662 Leb128Utils.writeUnsignedLeb128(newAddressDelta, result, startOffset+1); 663 664 System.arraycopy(originalEncodedDebugInfo, startOffset+length, result, 665 startOffset + addressDiffSize + 1, 666 originalEncodedDebugInfo.length - (startOffset + addressDiffSize + 1)); 667 } 668 } 669 670 @Override 671 public void ProcessSpecialOpcode(int startOffset, int debugOpcode, int lineDelta, 672 int addressDelta) { 673 address += addressDelta; 674 if (result != null) { 675 return; 676 } 677 678 int newOffset = newOffsetsByOriginalOffset.get(address*2, -1); 679 assert newOffset != -1; 680 newOffset = newOffset / 2; 681 682 if (newOffset != address) { 683 int newAddressDelta = newOffset - (address - addressDelta); 684 assert newAddressDelta > 0; 685 686 //if the new address delta won't fit in the special opcode, we need to insert 687 //an additional DBG_ADVANCE_PC opcode 688 if (lineDelta < 2 && newAddressDelta > 16 || lineDelta > 1 && newAddressDelta > 15) { 689 int additionalAddressDelta = newOffset - address; 690 int additionalAddressDeltaSize = Leb128Utils.signedLeb128Size(additionalAddressDelta); 691 692 result = new byte[originalEncodedDebugInfo.length + additionalAddressDeltaSize + 1]; 693 694 System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startOffset); 695 result[startOffset] = 0x01; //DBG_ADVANCE_PC 696 Leb128Utils.writeUnsignedLeb128(additionalAddressDelta, result, startOffset+1); 697 System.arraycopy(originalEncodedDebugInfo, startOffset, result, 698 startOffset+additionalAddressDeltaSize+1, 699 result.length - (startOffset+additionalAddressDeltaSize+1)); 700 } else { 701 result = new byte[originalEncodedDebugInfo.length]; 702 System.arraycopy(originalEncodedDebugInfo, 0, result, 0, result.length); 703 result[startOffset] = DebugInfoBuilder.calculateSpecialOpcode(lineDelta, 704 newAddressDelta); 705 } 706 } 707 } 708 } 709 710 public static class TryItem { 711 /** 712 * The address (in 2-byte words) within the code where the try block starts 713 */ 714 private int startAddress; 715 716 /** 717 * The number of 2-byte words that the try block covers 718 */ 719 private int instructionCount; 720 721 /** 722 * The associated exception handler 723 */ 724 public final EncodedCatchHandler encodedCatchHandler; 725 726 /** 727 * Construct a new <code>TryItem</code> with the given values 728 * @param startAddress the address (in 2-byte words) within the code where the try block starts 729 * @param instructionCount the number of 2-byte words that the try block covers 730 * @param encodedCatchHandler the associated exception handler 731 */ 732 public TryItem(int startAddress, int instructionCount, EncodedCatchHandler encodedCatchHandler) { 733 this.startAddress = startAddress; 734 this.instructionCount = instructionCount; 735 this.encodedCatchHandler = encodedCatchHandler; 736 } 737 738 /** 739 * This is used internally to construct a new <code>TryItem</code> while reading in a <code>DexFile</code> 740 * @param in the Input object to read the <code>TryItem</code> from 741 * @param encodedCatchHandlers a SparseArray of the EncodedCatchHandlers for this <code>CodeItem</code>. The 742 * key should be the offset of the EncodedCatchHandler from the beginning of the encoded_catch_handler_list 743 * structure. 744 */ 745 private TryItem(Input in, SparseArray<EncodedCatchHandler> encodedCatchHandlers) { 746 startAddress = in.readInt(); 747 instructionCount = in.readShort(); 748 749 encodedCatchHandler = encodedCatchHandlers.get(in.readShort()); 750 if (encodedCatchHandler == null) { 751 throw new RuntimeException("Could not find the EncodedCatchHandler referenced by this TryItem"); 752 } 753 } 754 755 /** 756 * Writes the <code>TryItem</code> to the given <code>AnnotatedOutput</code> object 757 * @param out the <code>AnnotatedOutput</code> object to write to 758 */ 759 private void writeTo(AnnotatedOutput out) { 760 if (out.annotates()) { 761 out.annotate(4, "start_addr: 0x" + Integer.toHexString(startAddress)); 762 out.annotate(2, "insn_count: 0x" + Integer.toHexString(instructionCount) + " (" + instructionCount + 763 ")"); 764 out.annotate(2, "handler_off: 0x" + Integer.toHexString(encodedCatchHandler.getOffsetInList())); 765 } 766 767 out.writeInt(startAddress); 768 out.writeShort(instructionCount); 769 out.writeShort(encodedCatchHandler.getOffsetInList()); 770 } 771 772 /** 773 * @return The address (in 2-byte words) within the code where the try block starts 774 */ 775 public int getStartAddress() { 776 return startAddress; 777 } 778 779 /** 780 * @return The number of 2-byte words that the try block covers 781 */ 782 public int getInstructionCount() { 783 return instructionCount; 784 } 785 } 786 787 public static class EncodedCatchHandler { 788 /** 789 * An array of the individual exception handlers 790 */ 791 public final EncodedTypeAddrPair[] handlers; 792 793 /** 794 * The address within the code (in 2-byte words) for the catch all handler, or -1 if there is no catch all 795 * handler 796 */ 797 private int catchAllHandlerAddress; 798 799 private int baseOffset; 800 private int offset; 801 802 /** 803 * Constructs a new <code>EncodedCatchHandler</code> with the given values 804 * @param handlers an array of the individual exception handlers 805 * @param catchAllHandlerAddress The address within the code (in 2-byte words) for the catch all handler, or -1 806 * if there is no catch all handler 807 */ 808 public EncodedCatchHandler(EncodedTypeAddrPair[] handlers, int catchAllHandlerAddress) { 809 this.handlers = handlers; 810 this.catchAllHandlerAddress = catchAllHandlerAddress; 811 } 812 813 /** 814 * This is used internally to construct a new <code>EncodedCatchHandler</code> while reading in a 815 * <code>DexFile</code> 816 * @param dexFile the <code>DexFile</code> that is being read in 817 * @param in the Input object to read the <code>EncodedCatchHandler</code> from 818 */ 819 private EncodedCatchHandler(DexFile dexFile, Input in) { 820 int handlerCount = in.readSignedLeb128(); 821 822 if (handlerCount < 0) { 823 handlers = new EncodedTypeAddrPair[-1 * handlerCount]; 824 } else { 825 handlers = new EncodedTypeAddrPair[handlerCount]; 826 } 827 828 for (int i=0; i<handlers.length; i++) { 829 handlers[i] = new EncodedTypeAddrPair(dexFile, in); 830 } 831 832 if (handlerCount <= 0) { 833 catchAllHandlerAddress = in.readUnsignedLeb128(); 834 } else { 835 catchAllHandlerAddress = -1; 836 } 837 } 838 839 /** 840 * Returns the "Catch All" handler address for this <code>EncodedCatchHandler</code> 841 * @return 842 */ 843 public int getCatchAllHandlerAddress() { 844 return catchAllHandlerAddress; 845 } 846 847 /** 848 * @return the offset of this <code>EncodedCatchHandler</code> from the beginning of the 849 * encoded_catch_handler_list structure 850 */ 851 private int getOffsetInList() { 852 return offset-baseOffset; 853 } 854 855 /** 856 * Places the <code>EncodedCatchHandler</code>, storing the offset and baseOffset, and returning the offset 857 * immediately following this <code>EncodedCatchHandler</code> 858 * @param offset the offset of this <code>EncodedCatchHandler</code> in the <code>DexFile</code> 859 * @param baseOffset the offset of the beginning of the encoded_catch_handler_list structure in the 860 * <code>DexFile</code> 861 * @return the offset immediately following this <code>EncodedCatchHandler</code> 862 */ 863 private int place(int offset, int baseOffset) { 864 this.offset = offset; 865 this.baseOffset = baseOffset; 866 867 int size = handlers.length; 868 if (catchAllHandlerAddress > -1) { 869 size *= -1; 870 offset += Leb128Utils.unsignedLeb128Size(catchAllHandlerAddress); 871 } 872 offset += Leb128Utils.signedLeb128Size(size); 873 874 for (EncodedTypeAddrPair handler: handlers) { 875 offset += handler.getSize(); 876 } 877 return offset; 878 } 879 880 /** 881 * Writes the <code>EncodedCatchHandler</code> to the given <code>AnnotatedOutput</code> object 882 * @param out the <code>AnnotatedOutput</code> object to write to 883 */ 884 private void writeTo(AnnotatedOutput out) { 885 if (out.annotates()) { 886 out.annotate("size: 0x" + Integer.toHexString(handlers.length) + " (" + handlers.length + ")"); 887 888 int size = handlers.length; 889 if (catchAllHandlerAddress > -1) { 890 size = size * -1; 891 } 892 out.writeSignedLeb128(size); 893 894 int index = 0; 895 for (EncodedTypeAddrPair handler: handlers) { 896 out.annotate(0, "[" + index++ + "] encoded_type_addr_pair"); 897 out.indent(); 898 handler.writeTo(out); 899 out.deindent(); 900 } 901 902 if (catchAllHandlerAddress > -1) { 903 out.annotate("catch_all_addr: 0x" + Integer.toHexString(catchAllHandlerAddress)); 904 out.writeUnsignedLeb128(catchAllHandlerAddress); 905 } 906 } else { 907 int size = handlers.length; 908 if (catchAllHandlerAddress > -1) { 909 size = size * -1; 910 } 911 out.writeSignedLeb128(size); 912 913 for (EncodedTypeAddrPair handler: handlers) { 914 handler.writeTo(out); 915 } 916 917 if (catchAllHandlerAddress > -1) { 918 out.writeUnsignedLeb128(catchAllHandlerAddress); 919 } 920 } 921 } 922 923 @Override 924 public int hashCode() { 925 int hash = 0; 926 for (EncodedTypeAddrPair handler: handlers) { 927 hash = hash * 31 + handler.hashCode(); 928 } 929 hash = hash * 31 + catchAllHandlerAddress; 930 return hash; 931 } 932 933 @Override 934 public boolean equals(Object o) { 935 if (this==o) { 936 return true; 937 } 938 if (o==null || !this.getClass().equals(o.getClass())) { 939 return false; 940 } 941 942 EncodedCatchHandler other = (EncodedCatchHandler)o; 943 if (handlers.length != other.handlers.length || catchAllHandlerAddress != other.catchAllHandlerAddress) { 944 return false; 945 } 946 947 for (int i=0; i<handlers.length; i++) { 948 if (!handlers[i].equals(other.handlers[i])) { 949 return false; 950 } 951 } 952 953 return true; 954 } 955 } 956 957 public static class EncodedTypeAddrPair { 958 /** 959 * The type of the <code>Exception</code> that this handler handles 960 */ 961 public final TypeIdItem exceptionType; 962 963 /** 964 * The address (in 2-byte words) in the code of the handler 965 */ 966 private int handlerAddress; 967 968 /** 969 * Constructs a new <code>EncodedTypeAddrPair</code> with the given values 970 * @param exceptionType the type of the <code>Exception</code> that this handler handles 971 * @param handlerAddress the address (in 2-byte words) in the code of the handler 972 */ 973 public EncodedTypeAddrPair(TypeIdItem exceptionType, int handlerAddress) { 974 this.exceptionType = exceptionType; 975 this.handlerAddress = handlerAddress; 976 } 977 978 /** 979 * This is used internally to construct a new <code>EncodedTypeAddrPair</code> while reading in a 980 * <code>DexFile</code> 981 * @param dexFile the <code>DexFile</code> that is being read in 982 * @param in the Input object to read the <code>EncodedCatchHandler</code> from 983 */ 984 private EncodedTypeAddrPair(DexFile dexFile, Input in) { 985 exceptionType = dexFile.TypeIdsSection.getItemByIndex(in.readUnsignedLeb128()); 986 handlerAddress = in.readUnsignedLeb128(); 987 } 988 989 /** 990 * @return the size of this <code>EncodedTypeAddrPair</code> 991 */ 992 private int getSize() { 993 return Leb128Utils.unsignedLeb128Size(exceptionType.getIndex()) + 994 Leb128Utils.unsignedLeb128Size(handlerAddress); 995 } 996 997 /** 998 * Writes the <code>EncodedTypeAddrPair</code> to the given <code>AnnotatedOutput</code> object 999 * @param out the <code>AnnotatedOutput</code> object to write to 1000 */ 1001 private void writeTo(AnnotatedOutput out) { 1002 if (out.annotates()) { 1003 out.annotate("exception_type: " + exceptionType.getTypeDescriptor()); 1004 out.writeUnsignedLeb128(exceptionType.getIndex()); 1005 1006 out.annotate("handler_addr: 0x" + Integer.toHexString(handlerAddress)); 1007 out.writeUnsignedLeb128(handlerAddress); 1008 } else { 1009 out.writeUnsignedLeb128(exceptionType.getIndex()); 1010 out.writeUnsignedLeb128(handlerAddress); 1011 } 1012 } 1013 1014 public int getHandlerAddress() { 1015 return handlerAddress; 1016 } 1017 1018 @Override 1019 public int hashCode() { 1020 return exceptionType.hashCode() * 31 + handlerAddress; 1021 } 1022 1023 @Override 1024 public boolean equals(Object o) { 1025 if (this==o) { 1026 return true; 1027 } 1028 if (o==null || !this.getClass().equals(o.getClass())) { 1029 return false; 1030 } 1031 1032 EncodedTypeAddrPair other = (EncodedTypeAddrPair)o; 1033 return exceptionType == other.exceptionType && handlerAddress == other.handlerAddress; 1034 } 1035 } 1036} 1037