CodeItem.java revision 2904f4060318acebfa5a1c8d43b362dcfdd063b0
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 //TODO: should mention the method name here 313 return "code_item @0x" + Integer.toHexString(getOffset()); 314 } 315 316 /** {@inheritDoc} */ 317 public int compareTo(CodeItem other) { 318 if (parent == null) { 319 if (other.parent == null) { 320 return 0; 321 } 322 return -1; 323 } 324 if (other.parent == null) { 325 return 1; 326 } 327 return parent.method.compareTo(other.parent.method); 328 } 329 330 /** 331 * @return the register count 332 */ 333 public int getRegisterCount() { 334 return registerCount; 335 } 336 337 /** 338 * @return an array of the instructions in this code item 339 */ 340 public Instruction[] getInstructions() { 341 return instructions; 342 } 343 344 /** 345 * @return an array of the <code>TryItem</code> objects in this <code>CodeItem</code> 346 */ 347 public TryItem[] getTries() { 348 return tries; 349 } 350 351 /** 352 * @return an array of the <code>EncodedCatchHandler</code> objects in this <code>CodeItem</code> 353 */ 354 public EncodedCatchHandler[] getHandlers() { 355 return encodedCatchHandlers; 356 } 357 358 /** 359 * @return the <code>DebugInfoItem</code> associated with this <code>CodeItem</code> 360 */ 361 public DebugInfoItem getDebugInfo() { 362 return debugInfo; 363 } 364 365 /** 366 * Sets the <code>MethodIdItem</code> of the method that this <code>CodeItem</code> is associated with 367 * @param encodedMethod the <code>EncodedMethod</code> of the method that this <code>CodeItem</code> is associated 368 * with 369 */ 370 protected void setParent(ClassDataItem.EncodedMethod encodedMethod) { 371 this.parent = encodedMethod; 372 } 373 374 /** 375 * @return the MethodIdItem of the method that this CodeItem belongs to 376 */ 377 public ClassDataItem.EncodedMethod getParent() { 378 return parent; 379 } 380 381 /** 382 * Used by OdexUtil to update this <code>CodeItem</code> with a deodexed version of the instructions 383 * @param newInstructions the new instructions to use for this code item 384 */ 385 public void updateCode(Instruction[] newInstructions) { 386 this.instructions = newInstructions; 387 } 388 389 private int getInstructionsLength() { 390 int offset = 0; 391 for (Instruction instruction: instructions) { 392 offset += instruction.getSize(offset); 393 } 394 return offset; 395 } 396 397 /** 398 * Go through the instructions and perform any of the following fixes that are applicable 399 * - Replace const-string instruction with const-string/jumbo, when the string index is too big 400 * - Replace goto and goto/16 with a larger version of goto, when the target is too far away 401 * 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 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); 551 if (originalSwitchOffset == 0) { 552 //TODO: is it safe to skip an unreferenced switch data instruction? Or should it throw an exception? 553 continue; 554 } 555 556 assert newOffsetsByOriginalOffset.indexOfKey(originalSwitchOffset) >= 0; 557 int newSwitchOffset = newOffsetsByOriginalOffset.get(originalSwitchOffset); 558 559 int[] targets = multiOffsetInstruction.getTargets(); 560 for (int t=0; t<targets.length; t++) { 561 int originalTargetOffset = originalSwitchOffset + targets[t]*2; 562 assert newOffsetsByOriginalOffset.indexOfKey(originalTargetOffset) >= 0; 563 int newTargetOffset = newOffsetsByOriginalOffset.get(originalTargetOffset); 564 int newOffset = (newTargetOffset - newSwitchOffset)/2; 565 if (newOffset != targets[t]) { 566 multiOffsetInstruction.updateTarget(t, newOffset); 567 } 568 } 569 } 570 currentCodeOffset += instruction.getSize(currentCodeOffset); 571 } 572 573 if (debugInfo != null) { 574 final byte[] encodedDebugInfo = debugInfo.getEncodedDebugInfo(); 575 576 ByteArrayInput debugInput = new ByteArrayInput(encodedDebugInfo); 577 578 DebugInstructionFixer debugInstructionFixer = new DebugInstructionFixer(encodedDebugInfo, 579 newOffsetsByOriginalOffset, originalOffsetsByNewOffset); 580 DebugInstructionIterator.IterateInstructions(debugInput, debugInstructionFixer); 581 582 assert debugInstructionFixer.result != null; 583 584 if (debugInstructionFixer.result != null) { 585 debugInfo.setEncodedDebugInfo(debugInstructionFixer.result); 586 } 587 } 588 589 if (encodedCatchHandlers != null) { 590 for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) { 591 if (encodedCatchHandler.catchAllHandlerAddress != -1) { 592 assert newOffsetsByOriginalOffset.indexOfKey(encodedCatchHandler.catchAllHandlerAddress*2) >= 0; 593 encodedCatchHandler.catchAllHandlerAddress = 594 newOffsetsByOriginalOffset.get(encodedCatchHandler.catchAllHandlerAddress*2)/2; 595 } 596 597 for (EncodedTypeAddrPair handler: encodedCatchHandler.handlers) { 598 assert newOffsetsByOriginalOffset.indexOfKey(handler.handlerAddress*2) >= 0; 599 handler.handlerAddress = newOffsetsByOriginalOffset.get(handler.handlerAddress*2)/2; 600 } 601 } 602 } 603 604 if (this.tries != null) { 605 for (TryItem tryItem: tries) { 606 int startAddress = tryItem.startAddress; 607 int endAddress = tryItem.startAddress + tryItem.instructionCount; 608 609 assert newOffsetsByOriginalOffset.indexOfKey(startAddress * 2) >= 0; 610 tryItem.startAddress = newOffsetsByOriginalOffset.get(startAddress * 2)/2; 611 612 assert newOffsetsByOriginalOffset.indexOfKey(endAddress * 2) >= 0; 613 tryItem.instructionCount = newOffsetsByOriginalOffset.get(endAddress * 2)/2 - tryItem.startAddress; 614 } 615 } 616 } 617 618 private class DebugInstructionFixer extends DebugInstructionIterator.ProcessRawDebugInstructionDelegate { 619 private int address = 0; 620 private SparseIntArray newOffsetsByOriginalOffset; 621 private SparseIntArray originalOffsetsByNewOffset; 622 private final byte[] originalEncodedDebugInfo; 623 public byte[] result = null; 624 625 public DebugInstructionFixer(byte[] originalEncodedDebugInfo, SparseIntArray newOffsetsByOriginalOffset, 626 SparseIntArray originalOffsetsByNewOffset) { 627 this.newOffsetsByOriginalOffset = newOffsetsByOriginalOffset; 628 this.originalOffsetsByNewOffset = originalOffsetsByNewOffset; 629 this.originalEncodedDebugInfo = originalEncodedDebugInfo; 630 } 631 632 633 @Override 634 public void ProcessAdvancePC(int startOffset, int length, int addressDelta) { 635 address += addressDelta; 636 637 if (result != null) { 638 return; 639 } 640 641 int newOffset = newOffsetsByOriginalOffset.get(address*2, -1); 642 643 //The address might not point to an actual instruction in some cases, for example, if an AdvancePC 644 //instruction was inserted just before a "special" instruction, to fix up the offsets for a previous 645 //instruction replacement. 646 //In this case, it should be safe to skip, because there will be another AdvancePC/SpecialOpcode that will 647 //bump up the address to point to a valid instruction before anything (line/local/etc.) is emitted 648 if (newOffset == -1) { 649 return; 650 } 651 652 assert newOffset != -1; 653 newOffset = newOffset / 2; 654 655 if (newOffset != address) { 656 int newAddressDelta = newOffset - (address - addressDelta); 657 assert newAddressDelta > 0; 658 int addressDiffSize = Leb128Utils.unsignedLeb128Size(newAddressDelta); 659 660 result = new byte[originalEncodedDebugInfo.length + addressDiffSize - (length - 1)]; 661 662 System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startOffset); 663 664 result[startOffset] = 0x01; //DBG_ADVANCE_PC debug opcode 665 Leb128Utils.writeUnsignedLeb128(newAddressDelta, result, startOffset+1); 666 667 System.arraycopy(originalEncodedDebugInfo, startOffset+length, result, 668 startOffset + addressDiffSize + 1, 669 originalEncodedDebugInfo.length - (startOffset + addressDiffSize + 1)); 670 } 671 } 672 673 @Override 674 public void ProcessSpecialOpcode(int startOffset, int debugOpcode, int lineDelta, 675 int addressDelta) { 676 address += addressDelta; 677 if (result != null) { 678 return; 679 } 680 681 int newOffset = newOffsetsByOriginalOffset.get(address*2, -1); 682 assert newOffset != -1; 683 newOffset = newOffset / 2; 684 685 if (newOffset != address) { 686 int newAddressDelta = newOffset - (address - addressDelta); 687 assert newAddressDelta > 0; 688 689 //if the new address delta won't fit in the special opcode, we need to insert 690 //an additional DBG_ADVANCE_PC opcode 691 if (lineDelta < 2 && newAddressDelta > 16 || lineDelta > 1 && newAddressDelta > 15) { 692 int additionalAddressDelta = newOffset - address; 693 int additionalAddressDeltaSize = Leb128Utils.signedLeb128Size(additionalAddressDelta); 694 695 result = new byte[originalEncodedDebugInfo.length + additionalAddressDeltaSize + 1]; 696 697 System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startOffset); 698 result[startOffset] = 0x01; //DBG_ADVANCE_PC 699 Leb128Utils.writeUnsignedLeb128(additionalAddressDelta, result, startOffset+1); 700 System.arraycopy(originalEncodedDebugInfo, startOffset, result, 701 startOffset+additionalAddressDeltaSize+1, 702 result.length - (startOffset+additionalAddressDeltaSize+1)); 703 } else { 704 result = new byte[originalEncodedDebugInfo.length]; 705 System.arraycopy(originalEncodedDebugInfo, 0, result, 0, result.length); 706 result[startOffset] = DebugInfoBuilder.calculateSpecialOpcode(lineDelta, 707 newAddressDelta); 708 } 709 } 710 } 711 } 712 713 public static class TryItem { 714 /** 715 * The address (in 2-byte words) within the code where the try block starts 716 */ 717 private int startAddress; 718 719 /** 720 * The number of 2-byte words that the try block covers 721 */ 722 private int instructionCount; 723 724 /** 725 * The associated exception handler 726 */ 727 public final EncodedCatchHandler encodedCatchHandler; 728 729 /** 730 * Construct a new <code>TryItem</code> with the given values 731 * @param startAddress the address (in 2-byte words) within the code where the try block starts 732 * @param instructionCount the number of 2-byte words that the try block covers 733 * @param encodedCatchHandler the associated exception handler 734 */ 735 public TryItem(int startAddress, int instructionCount, EncodedCatchHandler encodedCatchHandler) { 736 this.startAddress = startAddress; 737 this.instructionCount = instructionCount; 738 this.encodedCatchHandler = encodedCatchHandler; 739 } 740 741 /** 742 * This is used internally to construct a new <code>TryItem</code> while reading in a <code>DexFile</code> 743 * @param in the Input object to read the <code>TryItem</code> from 744 * @param encodedCatchHandlers a SparseArray of the EncodedCatchHandlers for this <code>CodeItem</code>. The 745 * key should be the offset of the EncodedCatchHandler from the beginning of the encoded_catch_handler_list 746 * structure. 747 */ 748 private TryItem(Input in, SparseArray<EncodedCatchHandler> encodedCatchHandlers) { 749 startAddress = in.readInt(); 750 instructionCount = in.readShort(); 751 752 encodedCatchHandler = encodedCatchHandlers.get(in.readShort()); 753 if (encodedCatchHandler == null) { 754 throw new RuntimeException("Could not find the EncodedCatchHandler referenced by this TryItem"); 755 } 756 } 757 758 /** 759 * Writes the <code>TryItem</code> to the given <code>AnnotatedOutput</code> object 760 * @param out the <code>AnnotatedOutput</code> object to write to 761 */ 762 private void writeTo(AnnotatedOutput out) { 763 if (out.annotates()) { 764 out.annotate(4, "start_addr: 0x" + Integer.toHexString(startAddress)); 765 out.annotate(2, "insn_count: 0x" + Integer.toHexString(instructionCount) + " (" + instructionCount + 766 ")"); 767 out.annotate(2, "handler_off: 0x" + Integer.toHexString(encodedCatchHandler.getOffsetInList())); 768 } 769 770 out.writeInt(startAddress); 771 out.writeShort(instructionCount); 772 out.writeShort(encodedCatchHandler.getOffsetInList()); 773 } 774 775 /** 776 * @return The address (in 2-byte words) within the code where the try block starts 777 */ 778 public int getStartAddress() { 779 return startAddress; 780 } 781 782 /** 783 * @return The number of 2-byte words that the try block covers 784 */ 785 public int getInstructionCount() { 786 return instructionCount; 787 } 788 } 789 790 public static class EncodedCatchHandler { 791 /** 792 * An array of the individual exception handlers 793 */ 794 public final EncodedTypeAddrPair[] handlers; 795 796 /** 797 * The address within the code (in 2-byte words) for the catch all handler, or -1 if there is no catch all 798 * handler 799 */ 800 private int catchAllHandlerAddress; 801 802 //TODO: would it be possible to get away without having these? and generate/create these values while writing? 803 private int baseOffset; 804 private int offset; 805 806 /** 807 * Constructs a new <code>EncodedCatchHandler</code> with the given values 808 * @param handlers an array of the individual exception handlers 809 * @param catchAllHandlerAddress The address within the code (in 2-byte words) for the catch all handler, or -1 810 * if there is no catch all handler 811 */ 812 public EncodedCatchHandler(EncodedTypeAddrPair[] handlers, int catchAllHandlerAddress) { 813 this.handlers = handlers; 814 this.catchAllHandlerAddress = catchAllHandlerAddress; 815 } 816 817 /** 818 * This is used internally to construct a new <code>EncodedCatchHandler</code> while reading in a 819 * <code>DexFile</code> 820 * @param dexFile the <code>DexFile</code> that is being read in 821 * @param in the Input object to read the <code>EncodedCatchHandler</code> from 822 */ 823 private EncodedCatchHandler(DexFile dexFile, Input in) { 824 int handlerCount = in.readSignedLeb128(); 825 826 if (handlerCount < 0) { 827 handlers = new EncodedTypeAddrPair[-1 * handlerCount]; 828 } else { 829 handlers = new EncodedTypeAddrPair[handlerCount]; 830 } 831 832 for (int i=0; i<handlers.length; i++) { 833 handlers[i] = new EncodedTypeAddrPair(dexFile, in); 834 } 835 836 if (handlerCount <= 0) { 837 catchAllHandlerAddress = in.readUnsignedLeb128(); 838 } else { 839 catchAllHandlerAddress = -1; 840 } 841 } 842 843 /** 844 * Returns the "Catch All" handler address for this <code>EncodedCatchHandler</code> 845 * @return 846 */ 847 public int getCatchAllHandlerAddress() { 848 return catchAllHandlerAddress; 849 } 850 851 /** 852 * @return the offset of this <code>EncodedCatchHandler</code> from the beginning of the 853 * encoded_catch_handler_list structure 854 */ 855 private int getOffsetInList() { 856 return offset-baseOffset; 857 } 858 859 /** 860 * Places the <code>EncodedCatchHandler</code>, storing the offset and baseOffset, and returning the offset 861 * immediately following this <code>EncodedCatchHandler</code> 862 * @param offset the offset of this <code>EncodedCatchHandler</code> in the <code>DexFile</code> 863 * @param baseOffset the offset of the beginning of the encoded_catch_handler_list structure in the 864 * <code>DexFile</code> 865 * @return the offset immediately following this <code>EncodedCatchHandler</code> 866 */ 867 private int place(int offset, int baseOffset) { 868 this.offset = offset; 869 this.baseOffset = baseOffset; 870 871 int size = handlers.length; 872 if (catchAllHandlerAddress > -1) { 873 size *= -1; 874 offset += Leb128Utils.unsignedLeb128Size(catchAllHandlerAddress); 875 } 876 offset += Leb128Utils.signedLeb128Size(size); 877 878 for (EncodedTypeAddrPair handler: handlers) { 879 offset += handler.getSize(); 880 } 881 return offset; 882 } 883 884 /** 885 * Writes the <code>EncodedCatchHandler</code> to the given <code>AnnotatedOutput</code> object 886 * @param out the <code>AnnotatedOutput</code> object to write to 887 */ 888 private void writeTo(AnnotatedOutput out) { 889 if (out.annotates()) { 890 out.annotate("size: 0x" + Integer.toHexString(handlers.length) + " (" + handlers.length + ")"); 891 892 int size = handlers.length; 893 if (catchAllHandlerAddress > -1) { 894 size = size * -1; 895 } 896 out.writeSignedLeb128(size); 897 898 int index = 0; 899 for (EncodedTypeAddrPair handler: handlers) { 900 out.annotate(0, "[" + index++ + "] encoded_type_addr_pair"); 901 out.indent(); 902 handler.writeTo(out); 903 out.deindent(); 904 } 905 906 if (catchAllHandlerAddress > -1) { 907 out.annotate("catch_all_addr: 0x" + Integer.toHexString(catchAllHandlerAddress)); 908 out.writeUnsignedLeb128(catchAllHandlerAddress); 909 } 910 } else { 911 int size = handlers.length; 912 if (catchAllHandlerAddress > -1) { 913 size = size * -1; 914 } 915 out.writeSignedLeb128(size); 916 917 for (EncodedTypeAddrPair handler: handlers) { 918 handler.writeTo(out); 919 } 920 921 if (catchAllHandlerAddress > -1) { 922 out.writeUnsignedLeb128(catchAllHandlerAddress); 923 } 924 } 925 } 926 927 @Override 928 public int hashCode() { 929 int hash = 0; 930 for (EncodedTypeAddrPair handler: handlers) { 931 hash = hash * 31 + handler.hashCode(); 932 } 933 hash = hash * 31 + catchAllHandlerAddress; 934 return hash; 935 } 936 937 @Override 938 public boolean equals(Object o) { 939 if (this==o) { 940 return true; 941 } 942 if (o==null || !this.getClass().equals(o.getClass())) { 943 return false; 944 } 945 946 EncodedCatchHandler other = (EncodedCatchHandler)o; 947 if (handlers.length != other.handlers.length || catchAllHandlerAddress != other.catchAllHandlerAddress) { 948 return false; 949 } 950 951 for (int i=0; i<handlers.length; i++) { 952 if (!handlers[i].equals(other.handlers[i])) { 953 return false; 954 } 955 } 956 957 return true; 958 } 959 } 960 961 public static class EncodedTypeAddrPair { 962 /** 963 * The type of the <code>Exception</code> that this handler handles 964 */ 965 public final TypeIdItem exceptionType; 966 967 /** 968 * The address (in 2-byte words) in the code of the handler 969 */ 970 private int handlerAddress; 971 972 /** 973 * Constructs a new <code>EncodedTypeAddrPair</code> with the given values 974 * @param exceptionType the type of the <code>Exception</code> that this handler handles 975 * @param handlerAddress the address (in 2-byte words) in the code of the handler 976 */ 977 public EncodedTypeAddrPair(TypeIdItem exceptionType, int handlerAddress) { 978 this.exceptionType = exceptionType; 979 this.handlerAddress = handlerAddress; 980 } 981 982 /** 983 * This is used internally to construct a new <code>EncodedTypeAddrPair</code> while reading in a 984 * <code>DexFile</code> 985 * @param dexFile the <code>DexFile</code> that is being read in 986 * @param in the Input object to read the <code>EncodedCatchHandler</code> from 987 */ 988 private EncodedTypeAddrPair(DexFile dexFile, Input in) { 989 exceptionType = dexFile.TypeIdsSection.getItemByIndex(in.readUnsignedLeb128()); 990 handlerAddress = in.readUnsignedLeb128(); 991 } 992 993 /** 994 * @return the size of this <code>EncodedTypeAddrPair</code> 995 */ 996 private int getSize() { 997 return Leb128Utils.unsignedLeb128Size(exceptionType.getIndex()) + 998 Leb128Utils.unsignedLeb128Size(handlerAddress); 999 } 1000 1001 /** 1002 * Writes the <code>EncodedTypeAddrPair</code> to the given <code>AnnotatedOutput</code> object 1003 * @param out the <code>AnnotatedOutput</code> object to write to 1004 */ 1005 private void writeTo(AnnotatedOutput out) { 1006 if (out.annotates()) { 1007 out.annotate("exception_type: " + exceptionType.getTypeDescriptor()); 1008 out.writeUnsignedLeb128(exceptionType.getIndex()); 1009 1010 out.annotate("handler_addr: 0x" + Integer.toHexString(handlerAddress)); 1011 out.writeUnsignedLeb128(handlerAddress); 1012 } else { 1013 out.writeUnsignedLeb128(exceptionType.getIndex()); 1014 out.writeUnsignedLeb128(handlerAddress); 1015 } 1016 } 1017 1018 public int getHandlerAddress() { 1019 return handlerAddress; 1020 } 1021 1022 @Override 1023 public int hashCode() { 1024 return exceptionType.hashCode() * 31 + handlerAddress; 1025 } 1026 1027 @Override 1028 public boolean equals(Object o) { 1029 if (this==o) { 1030 return true; 1031 } 1032 if (o==null || !this.getClass().equals(o.getClass())) { 1033 return false; 1034 } 1035 1036 EncodedTypeAddrPair other = (EncodedTypeAddrPair)o; 1037 return exceptionType == other.exceptionType && handlerAddress == other.handlerAddress; 1038 } 1039 } 1040} 1041