• 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.*;
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