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