• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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