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