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