CodeItem.java revision cf483d2e13428710c97d73043125ebecb1958cbc
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.InstructionReader; 32import org.jf.dexlib.Code.InstructionIterator; 33import org.jf.dexlib.Code.Opcode; 34import org.jf.dexlib.Code.InstructionWriter; 35import org.jf.dexlib.Util.AnnotatedOutput; 36import org.jf.dexlib.Util.Input; 37import org.jf.dexlib.Util.SparseArray; 38import org.jf.dexlib.Util.Leb128Utils; 39 40public class CodeItem extends Item<CodeItem> { 41 private int registerCount; 42 private int inWords; 43 private int outWords; 44 private DebugInfoItem debugInfo; 45 private byte[] encodedInstructions; 46 private Item[] referencedItems; 47 private TryItem[] tries; 48 private EncodedCatchHandler[] encodedCatchHandlers; 49 50 private ClassDataItem.EncodedMethod parent; 51 52 /** 53 * Creates a new uninitialized <code>CodeItem</code> 54 * @param dexFile The <code>DexFile</code> that this item belongs to 55 */ 56 public CodeItem(DexFile dexFile) { 57 super(dexFile); 58 } 59 60 /** 61 * Creates a new <code>CodeItem</code> with the given values. 62 * @param dexFile The <code>DexFile</code> that this item belongs to 63 * @param registerCount the number of registers that the method containing this code uses 64 * @param inWords the number of 2-byte words that the parameters to the method containing this code take 65 * @param outWords the maximum number of 2-byte words for the arguments of any method call in this code 66 * @param debugInfo the debug information for this code/method 67 * @param encodedInstructions the instructions, encoded as a byte array 68 * @param referencedItems an array of the items referenced by instructions, in order of occurance in the code 69 * @param tries an array of the tries defined for this code/method 70 * @param encodedCatchHandlers an array of the exception handlers defined for this code/method 71 */ 72 private CodeItem(DexFile dexFile, 73 int registerCount, 74 int inWords, 75 int outWords, 76 DebugInfoItem debugInfo, 77 byte[] encodedInstructions, 78 Item[] referencedItems, 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 this.encodedInstructions = encodedInstructions; 91 this.referencedItems = referencedItems; 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 encodedInstructions the instructions, encoded as a byte array 104 * @param referencedItems an array of the items referenced by instructions, in order of occurance in the code 105 * @param tries an array of the tries defined for this code/method 106 * @param encodedCatchHandlers an array of the exception handlers defined for this code/method 107 * @return a new <code>CodeItem</code> with the given values. 108 */ 109 public static CodeItem getInternedCodeItem(DexFile dexFile, 110 int registerCount, 111 int inWords, 112 int outWords, 113 DebugInfoItem debugInfo, 114 byte[] encodedInstructions, 115 Item[] referencedItems, 116 TryItem[] tries, 117 EncodedCatchHandler[] encodedCatchHandlers) { 118 CodeItem codeItem = new CodeItem(dexFile, registerCount, inWords, outWords, debugInfo, encodedInstructions, 119 referencedItems, tries, encodedCatchHandlers); 120 return dexFile.CodeItemsSection.intern(codeItem); 121 } 122 123 /** {@inheritDoc} */ 124 protected void readItem(Input in, ReadContext readContext) { 125 this.registerCount = in.readShort(); 126 this.inWords = in.readShort(); 127 this.outWords = in.readShort(); 128 int triesCount = in.readShort(); 129 this.debugInfo = (DebugInfoItem)readContext.getOffsettedItemByOffset(ItemType.TYPE_DEBUG_INFO_ITEM, 130 in.readInt()); 131 if (this.debugInfo != null) { 132 this.debugInfo.setParent(this); 133 } 134 int instructionCount = in.readInt(); 135 this.encodedInstructions = in.readBytes(instructionCount * 2); 136 this.referencedItems = InstructionReader.getReferencedItems(encodedInstructions, dexFile); 137 if (triesCount > 0) { 138 in.alignTo(4); 139 140 //we need to read in the catch handlers first, so save the offset to the try items for future reference 141 int triesOffset = in.getCursor(); 142 in.setCursor(triesOffset + 8 * triesCount); 143 144 //read in the encoded catch handlers 145 int encodedHandlerStart = in.getCursor(); 146 int handlerCount = in.readUnsignedLeb128(); 147 SparseArray<EncodedCatchHandler> handlerMap = new SparseArray<EncodedCatchHandler>(handlerCount); 148 encodedCatchHandlers = new EncodedCatchHandler[handlerCount]; 149 for (int i=0; i<handlerCount; i++) { 150 int position = in.getCursor() - encodedHandlerStart; 151 encodedCatchHandlers[i] = new EncodedCatchHandler(dexFile, in); 152 handlerMap.append(position, encodedCatchHandlers[i]); 153 } 154 int codeItemEnd = in.getCursor(); 155 156 //now go back and read the tries 157 in.setCursor(triesOffset); 158 tries = new TryItem[triesCount]; 159 for (int i=0; i<triesCount; i++) { 160 tries[i] = new TryItem(in, handlerMap); 161 } 162 163 //and now back to the end of the code item 164 in.setCursor(codeItemEnd); 165 } 166 } 167 168 /** {@inheritDoc} */ 169 protected int placeItem(int offset) { 170 offset += 16 + encodedInstructions.length; 171 if (tries != null && tries.length > 0) { 172 if (encodedInstructions.length % 4 != 0) { 173 offset+=2; 174 } 175 176 offset += tries.length * 8; 177 int encodedCatchHandlerBaseOffset = offset; 178 offset += Leb128Utils.unsignedLeb128Size(encodedCatchHandlers.length); 179 for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) { 180 offset = encodedCatchHandler.place(offset, encodedCatchHandlerBaseOffset); 181 } 182 } 183 return offset; 184 } 185 186 /** {@inheritDoc} */ 187 protected void writeItem(final AnnotatedOutput out) { 188 if (out.annotates()) { 189 out.annotate(0, parent.method.getMethodString()); 190 out.annotate(2, "registers_size: 0x" + Integer.toHexString(registerCount) + " (" + registerCount + ")"); 191 out.annotate(2, "ins_size: 0x" + Integer.toHexString(inWords) + " (" + inWords + ")"); 192 out.annotate(2, "outs_size: 0x" + Integer.toHexString(outWords) + " (" + outWords + ")"); 193 int triesLength = tries==null?0:tries.length; 194 out.annotate(2, "tries_size: 0x" + Integer.toHexString(triesLength) + " (" + triesLength + ")"); 195 if (debugInfo == null) { 196 out.annotate(4, "debug_info_off:"); 197 } else { 198 out.annotate(4, "debug_info_off: 0x" + debugInfo.getOffset()); 199 } 200 out.annotate(4, "insns_size: 0x" + Integer.toHexString(encodedInstructions.length / 2) + " (" + 201 (encodedInstructions.length / 2) + ")"); 202 InstructionIterator.IterateInstructions(encodedInstructions, 203 new InstructionIterator.ProcessRawInstructionDelegate() { 204 205 public void ProcessNormalInstruction(Opcode opcode, int index) { 206 out.annotate(opcode.format.size, "[0x" + Integer.toHexString(index/2) + "] " + opcode.name + 207 " instruction"); 208 } 209 210 public void ProcessReferenceInstruction(Opcode opcode, int index) { 211 out.annotate(opcode.format.size, "[0x" + Integer.toHexString(index/2) + "] " + opcode.name + 212 " instruction"); 213 } 214 215 public void ProcessPackedSwitchInstruction(int index, int targetCount, int instructionLength) { 216 out.annotate(instructionLength, "[0x" + Integer.toHexString(index/2) + "] " + 217 "packed_switch instruction"); 218 } 219 220 public void ProcessSparseSwitchInstruction(int index, int targetCount, int instructionLength) { 221 out.annotate(instructionLength, "[0x" + Integer.toHexString(index/2) + "] " + 222 "sparse_switch instruction"); 223 } 224 225 public void ProcessFillArrayDataInstruction(int index, int elementWidth, int elementCount, 226 int instructionLength) { 227 out.annotate(instructionLength, "[0x" + Integer.toHexString(index/2) + "] " + 228 "fill_array_data instruction"); 229 } 230 }); 231 } 232 233 out.writeShort(registerCount); 234 out.writeShort(inWords); 235 out.writeShort(outWords); 236 if (tries == null) { 237 out.writeShort(0); 238 } else { 239 out.writeShort(tries.length); 240 } 241 if (debugInfo == null) { 242 out.writeInt(0); 243 } else { 244 out.writeInt(debugInfo.getOffset()); 245 } 246 out.writeInt(encodedInstructions.length / 2); 247 InstructionWriter.writeInstructions(encodedInstructions, referencedItems, out); 248 249 if (tries != null && tries.length > 0) { 250 if (out.annotates()) { 251 if ((encodedInstructions.length % 4) != 0) { 252 out.annotate("padding"); 253 out.writeShort(0); 254 } 255 256 int index = 0; 257 for (TryItem tryItem: tries) { 258 out.annotate(0, "[0x" + Integer.toHexString(index++) + "] try_item"); 259 out.indent(); 260 tryItem.writeTo(out); 261 out.deindent(); 262 } 263 264 out.annotate("handler_count: 0x" + Integer.toHexString(encodedCatchHandlers.length) + "(" + 265 encodedCatchHandlers.length + ")"); 266 out.writeUnsignedLeb128(encodedCatchHandlers.length); 267 268 index = 0; 269 for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) { 270 out.annotate(0, "[" + Integer.toHexString(index++) + "] encoded_catch_handler"); 271 out.indent(); 272 encodedCatchHandler.writeTo(out); 273 out.deindent(); 274 } 275 } else { 276 if ((encodedInstructions.length % 4) != 0) { 277 out.writeShort(0); 278 } 279 280 for (TryItem tryItem: tries) { 281 tryItem.writeTo(out); 282 } 283 284 out.writeUnsignedLeb128(encodedCatchHandlers.length); 285 286 for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) { 287 encodedCatchHandler.writeTo(out); 288 } 289 } 290 } 291 } 292 293 /** {@inheritDoc} */ 294 public ItemType getItemType() { 295 return ItemType.TYPE_CODE_ITEM; 296 } 297 298 /** {@inheritDoc} */ 299 public String getConciseIdentity() { 300 //TODO: should mention the method name here 301 return "code_item @0x" + Integer.toHexString(getOffset()); 302 } 303 304 /** {@inheritDoc} */ 305 public int compareTo(CodeItem other) { 306 if (parent == null) { 307 if (other.parent == null) { 308 return 0; 309 } 310 return -1; 311 } 312 if (other.parent == null) { 313 return 1; 314 } 315 return parent.method.compareTo(other.parent.method); 316 } 317 318 /** 319 * @return the register count 320 */ 321 public int getRegisterCount() { 322 return registerCount; 323 } 324 325 /** 326 * @return a byte array containing the encoded instructions 327 */ 328 public byte[] getEncodedInstructions() { 329 return encodedInstructions; 330 } 331 332 /** 333 * @return an array of the <code>TryItem</code> objects in this <code>CodeItem</code> 334 */ 335 public TryItem[] getTries() { 336 return tries; 337 } 338 339 /** 340 * @return the <code>DebugInfoItem</code> associated with this <code>CodeItem</code> 341 */ 342 public DebugInfoItem getDebugInfo() { 343 return debugInfo; 344 } 345 346 /** 347 * Sets the <code>MethodIdItem</code> of the method that this <code>CodeItem</code> is associated with 348 * @param encodedMethod the <code>EncodedMethod</code> of the method that this <code>CodeItem</code> is associated 349 * with 350 */ 351 protected void setParent(ClassDataItem.EncodedMethod encodedMethod) { 352 this.parent = encodedMethod; 353 } 354 355 /** 356 * @return the MethodIdItem of the method that this CodeItem belongs to 357 */ 358 public ClassDataItem.EncodedMethod getParent() { 359 return parent; 360 } 361 362 public static class TryItem { 363 /** 364 * The address (in 2-byte words) within the code where the try block starts 365 */ 366 public final int startAddress; 367 368 /** 369 * The number of 2-byte words that the try block covers 370 */ 371 public final int instructionCount; 372 373 /** 374 * The associated exception handler 375 */ 376 public final EncodedCatchHandler encodedCatchHandler; 377 378 /** 379 * Construct a new <code>TryItem</code> with the given values 380 * @param startAddress the address (in 2-byte words) within the code where the try block starts 381 * @param instructionCount the number of 2-byte words that the try block covers 382 * @param encodedCatchHandler the associated exception handler 383 */ 384 public TryItem(int startAddress, int instructionCount, EncodedCatchHandler encodedCatchHandler) { 385 this.startAddress = startAddress; 386 this.instructionCount = instructionCount; 387 this.encodedCatchHandler = encodedCatchHandler; 388 } 389 390 /** 391 * This is used internally to construct a new <code>TryItem</code> while reading in a <code>DexFile</code> 392 * @param in the Input object to read the <code>TryItem</code> from 393 * @param encodedCatchHandlers a SparseArray of the EncodedCatchHandlers for this <code>CodeItem</code>. The 394 * key should be the offset of the EncodedCatchHandler from the beginning of the encoded_catch_handler_list 395 * structure. 396 */ 397 private TryItem(Input in, SparseArray<EncodedCatchHandler> encodedCatchHandlers) { 398 startAddress = in.readInt(); 399 instructionCount = in.readShort(); 400 401 encodedCatchHandler = encodedCatchHandlers.get(in.readShort()); 402 if (encodedCatchHandler == null) { 403 throw new RuntimeException("Could not find the EncodedCatchHandler referenced by this TryItem"); 404 } 405 } 406 407 /** 408 * Writes the <code>TryItem</code> to the given <code>AnnotatedOutput</code> object 409 * @param out the <code>AnnotatedOutput</code> object to write to 410 */ 411 private void writeTo(AnnotatedOutput out) { 412 if (out.annotates()) { 413 out.annotate(4, "start_addr: 0x" + Integer.toHexString(startAddress)); 414 out.annotate(2, "insn_count: 0x" + Integer.toHexString(instructionCount) + " (" + instructionCount + 415 ")"); 416 out.annotate(2, "handler_off: 0x" + Integer.toHexString(encodedCatchHandler.getOffsetInList())); 417 } 418 419 out.writeInt(startAddress); 420 out.writeShort(instructionCount); 421 out.writeShort(encodedCatchHandler.getOffsetInList()); 422 } 423 } 424 425 public static class EncodedCatchHandler { 426 /** 427 * An array of the individual exception handlers 428 */ 429 public final EncodedTypeAddrPair[] handlers; 430 431 /** 432 * The address within the code (in 2-byte words) for the catch all handler, or -1 if there is no catch all 433 * handler 434 */ 435 public final int catchAllHandlerAddress; 436 437 //TODO: would it be possible to get away without having these? and generate/create these values while writing? 438 private int baseOffset; 439 private int offset; 440 441 /** 442 * Constructs a new <code>EncodedCatchHandler</code> with the given values 443 * @param handlers an array of the individual exception handlers 444 * @param catchAllHandlerAddress The address within the code (in 2-byte words) for the catch all handler, or -1 445 * if there is no catch all handler 446 */ 447 public EncodedCatchHandler(EncodedTypeAddrPair[] handlers, int catchAllHandlerAddress) { 448 this.handlers = handlers; 449 this.catchAllHandlerAddress = catchAllHandlerAddress; 450 } 451 452 /** 453 * This is used internally to construct a new <code>EncodedCatchHandler</code> while reading in a 454 * <code>DexFile</code> 455 * @param dexFile the <code>DexFile</code> that is being read in 456 * @param in the Input object to read the <code>EncodedCatchHandler</code> from 457 */ 458 private EncodedCatchHandler(DexFile dexFile, Input in) { 459 int handlerCount = in.readSignedLeb128(); 460 461 if (handlerCount < 0) { 462 handlers = new EncodedTypeAddrPair[-1 * handlerCount]; 463 } else { 464 handlers = new EncodedTypeAddrPair[handlerCount]; 465 } 466 467 for (int i=0; i<handlers.length; i++) { 468 handlers[i] = new EncodedTypeAddrPair(dexFile, in); 469 } 470 471 if (handlerCount <= 0) { 472 catchAllHandlerAddress = in.readUnsignedLeb128(); 473 } else { 474 catchAllHandlerAddress = -1; 475 } 476 } 477 478 /** 479 * @return the offset of this <code>EncodedCatchHandler</code> from the beginning of the 480 * encoded_catch_handler_list structure 481 */ 482 private int getOffsetInList() { 483 return offset-baseOffset; 484 } 485 486 /** 487 * Places the <code>EncodedCatchHandler</code>, storing the offset and baseOffset, and returning the offset 488 * immediately following this <code>EncodedCatchHandler</code> 489 * @param offset the offset of this <code>EncodedCatchHandler</code> in the <code>DexFile</code> 490 * @param baseOffset the offset of the beginning of the encoded_catch_handler_list structure in the 491 * <code>DexFile</code> 492 * @return the offset immediately following this <code>EncodedCatchHandler</code> 493 */ 494 private int place(int offset, int baseOffset) { 495 this.offset = offset; 496 this.baseOffset = baseOffset; 497 498 int size = handlers.length; 499 if (catchAllHandlerAddress > -1) { 500 size *= -1; 501 offset += Leb128Utils.unsignedLeb128Size(catchAllHandlerAddress); 502 } 503 offset += Leb128Utils.signedLeb128Size(size); 504 505 for (EncodedTypeAddrPair handler: handlers) { 506 offset += handler.getSize(); 507 } 508 return offset; 509 } 510 511 /** 512 * Writes the <code>EncodedCatchHandler</code> to the given <code>AnnotatedOutput</code> object 513 * @param out the <code>AnnotatedOutput</code> object to write to 514 */ 515 private void writeTo(AnnotatedOutput out) { 516 if (out.annotates()) { 517 out.annotate("size: 0x" + Integer.toHexString(handlers.length) + " (" + handlers.length + ")"); 518 519 int size = handlers.length; 520 if (catchAllHandlerAddress > -1) { 521 size = size * -1; 522 } 523 out.writeSignedLeb128(size); 524 525 int index = 0; 526 for (EncodedTypeAddrPair handler: handlers) { 527 out.annotate(0, "[" + index++ + "] encoded_type_addr_pair"); 528 out.indent(); 529 handler.writeTo(out); 530 out.deindent(); 531 } 532 533 if (catchAllHandlerAddress > -1) { 534 out.annotate("catch_all_addr: 0x" + Integer.toHexString(catchAllHandlerAddress)); 535 out.writeUnsignedLeb128(catchAllHandlerAddress); 536 } 537 } else { 538 int size = handlers.length; 539 if (catchAllHandlerAddress > -1) { 540 size = size * -1; 541 } 542 out.writeSignedLeb128(size); 543 544 for (EncodedTypeAddrPair handler: handlers) { 545 handler.writeTo(out); 546 } 547 548 if (catchAllHandlerAddress > -1) { 549 out.writeUnsignedLeb128(catchAllHandlerAddress); 550 } 551 } 552 } 553 } 554 555 public static class EncodedTypeAddrPair { 556 /** 557 * The type of the <code>Exception</code> that this handler handles 558 */ 559 public final TypeIdItem exceptionType; 560 561 /** 562 * The address (in 2-byte words) in the code of the handler 563 */ 564 public final int handlerAddress; 565 566 /** 567 * Constructs a new <code>EncodedTypeAddrPair</code> with the given values 568 * @param exceptionType the type of the <code>Exception</code> that this handler handles 569 * @param handlerAddress the address (in 2-byte words) in the code of the handler 570 */ 571 public EncodedTypeAddrPair(TypeIdItem exceptionType, int handlerAddress) { 572 this.exceptionType = exceptionType; 573 this.handlerAddress = handlerAddress; 574 } 575 576 /** 577 * This is used internally to construct a new <code>EncodedTypeAddrPair</code> while reading in a 578 * <code>DexFile</code> 579 * @param dexFile the <code>DexFile</code> that is being read in 580 * @param in the Input object to read the <code>EncodedCatchHandler</code> from 581 */ 582 private EncodedTypeAddrPair(DexFile dexFile, Input in) { 583 exceptionType = dexFile.TypeIdsSection.getItemByIndex(in.readUnsignedLeb128()); 584 handlerAddress = in.readUnsignedLeb128(); 585 } 586 587 /** 588 * @return the size of this <code>EncodedTypeAddrPair</code> 589 */ 590 private int getSize() { 591 return Leb128Utils.unsignedLeb128Size(exceptionType.getIndex()) + 592 Leb128Utils.unsignedLeb128Size(handlerAddress); 593 } 594 595 /** 596 * Writes the <code>EncodedTypeAddrPair</code> to the given <code>AnnotatedOutput</code> object 597 * @param out the <code>AnnotatedOutput</code> object to write to 598 */ 599 private void writeTo(AnnotatedOutput out) { 600 if (out.annotates()) { 601 out.annotate("exception_type: " + exceptionType.getTypeDescriptor()); 602 out.writeUnsignedLeb128(exceptionType.getIndex()); 603 604 out.annotate("handler_addr: 0x" + Integer.toHexString(handlerAddress)); 605 out.writeUnsignedLeb128(handlerAddress); 606 } else { 607 out.writeUnsignedLeb128(exceptionType.getIndex()); 608 out.writeUnsignedLeb128(handlerAddress); 609 } 610 } 611 } 612} 613