CodeItem.java revision a8ca776c1d369376e7804d4ee2e9a008c705e69a
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); 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 private int baseOffset; 803 private int offset; 804 805 /** 806 * Constructs a new <code>EncodedCatchHandler</code> with the given values 807 * @param handlers an array of the individual exception handlers 808 * @param catchAllHandlerAddress The address within the code (in 2-byte words) for the catch all handler, or -1 809 * if there is no catch all handler 810 */ 811 public EncodedCatchHandler(EncodedTypeAddrPair[] handlers, int catchAllHandlerAddress) { 812 this.handlers = handlers; 813 this.catchAllHandlerAddress = catchAllHandlerAddress; 814 } 815 816 /** 817 * This is used internally to construct a new <code>EncodedCatchHandler</code> while reading in a 818 * <code>DexFile</code> 819 * @param dexFile the <code>DexFile</code> that is being read in 820 * @param in the Input object to read the <code>EncodedCatchHandler</code> from 821 */ 822 private EncodedCatchHandler(DexFile dexFile, Input in) { 823 int handlerCount = in.readSignedLeb128(); 824 825 if (handlerCount < 0) { 826 handlers = new EncodedTypeAddrPair[-1 * handlerCount]; 827 } else { 828 handlers = new EncodedTypeAddrPair[handlerCount]; 829 } 830 831 for (int i=0; i<handlers.length; i++) { 832 handlers[i] = new EncodedTypeAddrPair(dexFile, in); 833 } 834 835 if (handlerCount <= 0) { 836 catchAllHandlerAddress = in.readUnsignedLeb128(); 837 } else { 838 catchAllHandlerAddress = -1; 839 } 840 } 841 842 /** 843 * Returns the "Catch All" handler address for this <code>EncodedCatchHandler</code> 844 * @return 845 */ 846 public int getCatchAllHandlerAddress() { 847 return catchAllHandlerAddress; 848 } 849 850 /** 851 * @return the offset of this <code>EncodedCatchHandler</code> from the beginning of the 852 * encoded_catch_handler_list structure 853 */ 854 private int getOffsetInList() { 855 return offset-baseOffset; 856 } 857 858 /** 859 * Places the <code>EncodedCatchHandler</code>, storing the offset and baseOffset, and returning the offset 860 * immediately following this <code>EncodedCatchHandler</code> 861 * @param offset the offset of this <code>EncodedCatchHandler</code> in the <code>DexFile</code> 862 * @param baseOffset the offset of the beginning of the encoded_catch_handler_list structure in the 863 * <code>DexFile</code> 864 * @return the offset immediately following this <code>EncodedCatchHandler</code> 865 */ 866 private int place(int offset, int baseOffset) { 867 this.offset = offset; 868 this.baseOffset = baseOffset; 869 870 int size = handlers.length; 871 if (catchAllHandlerAddress > -1) { 872 size *= -1; 873 offset += Leb128Utils.unsignedLeb128Size(catchAllHandlerAddress); 874 } 875 offset += Leb128Utils.signedLeb128Size(size); 876 877 for (EncodedTypeAddrPair handler: handlers) { 878 offset += handler.getSize(); 879 } 880 return offset; 881 } 882 883 /** 884 * Writes the <code>EncodedCatchHandler</code> to the given <code>AnnotatedOutput</code> object 885 * @param out the <code>AnnotatedOutput</code> object to write to 886 */ 887 private void writeTo(AnnotatedOutput out) { 888 if (out.annotates()) { 889 out.annotate("size: 0x" + Integer.toHexString(handlers.length) + " (" + handlers.length + ")"); 890 891 int size = handlers.length; 892 if (catchAllHandlerAddress > -1) { 893 size = size * -1; 894 } 895 out.writeSignedLeb128(size); 896 897 int index = 0; 898 for (EncodedTypeAddrPair handler: handlers) { 899 out.annotate(0, "[" + index++ + "] encoded_type_addr_pair"); 900 out.indent(); 901 handler.writeTo(out); 902 out.deindent(); 903 } 904 905 if (catchAllHandlerAddress > -1) { 906 out.annotate("catch_all_addr: 0x" + Integer.toHexString(catchAllHandlerAddress)); 907 out.writeUnsignedLeb128(catchAllHandlerAddress); 908 } 909 } else { 910 int size = handlers.length; 911 if (catchAllHandlerAddress > -1) { 912 size = size * -1; 913 } 914 out.writeSignedLeb128(size); 915 916 for (EncodedTypeAddrPair handler: handlers) { 917 handler.writeTo(out); 918 } 919 920 if (catchAllHandlerAddress > -1) { 921 out.writeUnsignedLeb128(catchAllHandlerAddress); 922 } 923 } 924 } 925 926 @Override 927 public int hashCode() { 928 int hash = 0; 929 for (EncodedTypeAddrPair handler: handlers) { 930 hash = hash * 31 + handler.hashCode(); 931 } 932 hash = hash * 31 + catchAllHandlerAddress; 933 return hash; 934 } 935 936 @Override 937 public boolean equals(Object o) { 938 if (this==o) { 939 return true; 940 } 941 if (o==null || !this.getClass().equals(o.getClass())) { 942 return false; 943 } 944 945 EncodedCatchHandler other = (EncodedCatchHandler)o; 946 if (handlers.length != other.handlers.length || catchAllHandlerAddress != other.catchAllHandlerAddress) { 947 return false; 948 } 949 950 for (int i=0; i<handlers.length; i++) { 951 if (!handlers[i].equals(other.handlers[i])) { 952 return false; 953 } 954 } 955 956 return true; 957 } 958 } 959 960 public static class EncodedTypeAddrPair { 961 /** 962 * The type of the <code>Exception</code> that this handler handles 963 */ 964 public final TypeIdItem exceptionType; 965 966 /** 967 * The address (in 2-byte words) in the code of the handler 968 */ 969 private int handlerAddress; 970 971 /** 972 * Constructs a new <code>EncodedTypeAddrPair</code> with the given values 973 * @param exceptionType the type of the <code>Exception</code> that this handler handles 974 * @param handlerAddress the address (in 2-byte words) in the code of the handler 975 */ 976 public EncodedTypeAddrPair(TypeIdItem exceptionType, int handlerAddress) { 977 this.exceptionType = exceptionType; 978 this.handlerAddress = handlerAddress; 979 } 980 981 /** 982 * This is used internally to construct a new <code>EncodedTypeAddrPair</code> while reading in a 983 * <code>DexFile</code> 984 * @param dexFile the <code>DexFile</code> that is being read in 985 * @param in the Input object to read the <code>EncodedCatchHandler</code> from 986 */ 987 private EncodedTypeAddrPair(DexFile dexFile, Input in) { 988 exceptionType = dexFile.TypeIdsSection.getItemByIndex(in.readUnsignedLeb128()); 989 handlerAddress = in.readUnsignedLeb128(); 990 } 991 992 /** 993 * @return the size of this <code>EncodedTypeAddrPair</code> 994 */ 995 private int getSize() { 996 return Leb128Utils.unsignedLeb128Size(exceptionType.getIndex()) + 997 Leb128Utils.unsignedLeb128Size(handlerAddress); 998 } 999 1000 /** 1001 * Writes the <code>EncodedTypeAddrPair</code> to the given <code>AnnotatedOutput</code> object 1002 * @param out the <code>AnnotatedOutput</code> object to write to 1003 */ 1004 private void writeTo(AnnotatedOutput out) { 1005 if (out.annotates()) { 1006 out.annotate("exception_type: " + exceptionType.getTypeDescriptor()); 1007 out.writeUnsignedLeb128(exceptionType.getIndex()); 1008 1009 out.annotate("handler_addr: 0x" + Integer.toHexString(handlerAddress)); 1010 out.writeUnsignedLeb128(handlerAddress); 1011 } else { 1012 out.writeUnsignedLeb128(exceptionType.getIndex()); 1013 out.writeUnsignedLeb128(handlerAddress); 1014 } 1015 } 1016 1017 public int getHandlerAddress() { 1018 return handlerAddress; 1019 } 1020 1021 @Override 1022 public int hashCode() { 1023 return exceptionType.hashCode() * 31 + handlerAddress; 1024 } 1025 1026 @Override 1027 public boolean equals(Object o) { 1028 if (this==o) { 1029 return true; 1030 } 1031 if (o==null || !this.getClass().equals(o.getClass())) { 1032 return false; 1033 } 1034 1035 EncodedTypeAddrPair other = (EncodedTypeAddrPair)o; 1036 return exceptionType == other.exceptionType && handlerAddress == other.handlerAddress; 1037 } 1038 } 1039} 1040