Home | History | Annotate | Download | only in program
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package dexfuzz.program;
     18 
     19 import dexfuzz.Log;
     20 import dexfuzz.rawdex.Instruction;
     21 import dexfuzz.rawdex.Opcode;
     22 
     23 import java.util.ArrayList;
     24 import java.util.Collections;
     25 import java.util.LinkedList;
     26 import java.util.List;
     27 
     28 /**
     29  * A class that represents a CodeItem in a way that is more amenable to mutation.
     30  */
     31 public class MutatableCode {
     32   /**
     33    * To ensure we update the correct CodeItem in the raw DEX file.
     34    */
     35   public int codeItemIdx;
     36 
     37   /**
     38    * This is an index into the Program's list of MutatableCodes.
     39    */
     40   public int mutatableCodeIdx;
     41 
     42   /**
     43    * Number of registers this code uses.
     44    */
     45   public short registersSize;
     46 
     47   /**
     48    * Number of ins this code has.
     49    */
     50   public short insSize;
     51 
     52   /**
     53    * Number of outs this code has.
     54    */
     55   public short outsSize;
     56 
     57   /**
     58    * Number of tries this code has.
     59    */
     60   public short triesSize;
     61 
     62   /**
     63    * CodeTranslator is responsible for creating this, and
     64    * converting it back to a list of Instructions.
     65    */
     66   private List<MInsn> mutatableInsns;
     67 
     68   /**
     69    * CodeTranslator is responsible for creating this, and
     70    * converting it back to the correct form for CodeItems.
     71    */
     72   public List<MTryBlock> mutatableTries;
     73 
     74   /**
     75    * The name of the method this code represents.
     76    */
     77   public String name;
     78   public String shorty;
     79   public boolean isStatic;
     80 
     81   /**
     82    * The Program that owns this MutatableCode.
     83    * Currently used to get the size of constant pools for
     84    * PoolIndexChanger/RandomInstructionGenerator
     85    */
     86   public Program program;
     87 
     88   private short originalInVReg;
     89   private short tempVRegsAllocated;
     90   private short initialTempVReg;
     91   private boolean vregsNeedCopying;
     92   private int numMoveInsnsGenerated;
     93 
     94   public MutatableCode(Program program) {
     95     this.program = program;
     96     this.mutatableInsns = new LinkedList<MInsn>();
     97   }
     98 
     99   /**
    100    * Call this to update all instructions after the provided mInsn, to have their
    101    * locations adjusted by the provided offset. It will also mark that they have been updated.
    102    */
    103   public void updateInstructionLocationsAfter(MInsn mInsn, int offset) {
    104     boolean updating = false;
    105     for (MInsn mInsnChecking : mutatableInsns) {
    106       if (updating) {
    107         mInsnChecking.locationUpdated = true;
    108         mInsnChecking.location += offset;
    109       } else {
    110         if (mInsnChecking == mInsn) {
    111           updating = true;
    112         }
    113       }
    114 
    115     }
    116   }
    117 
    118   private void recalculateLocations() {
    119     int loc = 0;
    120     for (MInsn mInsn : mutatableInsns) {
    121       mInsn.location = loc;
    122       loc += mInsn.insn.getSize();
    123     }
    124   }
    125 
    126   public List<MInsn> getInstructions() {
    127     return Collections.unmodifiableList(mutatableInsns);
    128   }
    129 
    130   public int getInstructionCount() {
    131     return mutatableInsns.size();
    132   }
    133 
    134   public int getInstructionIndex(MInsn mInsn) {
    135     return mutatableInsns.indexOf(mInsn);
    136   }
    137 
    138   public MInsn getInstructionAt(int idx) {
    139     return mutatableInsns.get(idx);
    140   }
    141 
    142   public void addInstructionToEnd(MInsn mInsn) {
    143     mutatableInsns.add(mInsn);
    144   }
    145 
    146   public void insertInstructionAfter(MInsn toBeInserted, int insertionIdx) {
    147     if ((insertionIdx + 1) < mutatableInsns.size()) {
    148       insertInstructionAt(toBeInserted, insertionIdx + 1);
    149     } else {
    150       // Appending to end.
    151       MInsn finalInsn = mutatableInsns.get(mutatableInsns.size() - 1);
    152       toBeInserted.location = finalInsn.location + finalInsn.insn.getSize();
    153       mutatableInsns.add(toBeInserted);
    154     }
    155   }
    156 
    157   /**
    158    * Has same semantics as List.add()
    159    */
    160   public void insertInstructionAt(MInsn toBeInserted, int insertionIdx) {
    161     MInsn currentInsn = mutatableInsns.get(insertionIdx);
    162     toBeInserted.location = currentInsn.location;
    163     mutatableInsns.add(insertionIdx , toBeInserted);
    164     updateInstructionLocationsAfter(toBeInserted, toBeInserted.insn.getSize());
    165   }
    166 
    167   /**
    168    * Checks if any MTryBlock's instruction refs pointed at the 'before' MInsn,
    169    * and points them to the 'after' MInsn, if so. 'twoWay' will check if 'after'
    170    * was pointed to, and point refs to the 'before' insn.
    171    * (one-way is used when deleting instructions,
    172    * two-way is used when swapping instructions.)
    173    */
    174   private void updateTryBlocksWithReplacementInsn(MInsn before, MInsn after,
    175       boolean twoWay) {
    176     if (triesSize > 0) {
    177       for (MTryBlock mTryBlock : mutatableTries) {
    178         if (mTryBlock.startInsn == before) {
    179           Log.debug("Try block's first instruction was updated");
    180           mTryBlock.startInsn = after;
    181         } else if (twoWay && mTryBlock.startInsn == after) {
    182           Log.debug("Try block's first instruction was updated");
    183           mTryBlock.startInsn = before;
    184         }
    185         if (mTryBlock.endInsn == before) {
    186           Log.debug("Try block's last instruction was updated");
    187           mTryBlock.endInsn = after;
    188         } else if (twoWay && mTryBlock.endInsn == after) {
    189           Log.debug("Try block's last instruction was updated");
    190           mTryBlock.endInsn = before;
    191         }
    192         if (mTryBlock.catchAllHandler == before) {
    193           Log.debug("Try block's catch-all instruction was updated");
    194           mTryBlock.catchAllHandler = after;
    195         } else if (twoWay && mTryBlock.catchAllHandler == after) {
    196           Log.debug("Try block's catch-all instruction was updated");
    197           mTryBlock.catchAllHandler = before;
    198         }
    199 
    200         List<Integer> matchesIndicesToChange = new ArrayList<Integer>();
    201         List<Integer> replacementIndicesToChange = null;
    202         if (twoWay) {
    203           replacementIndicesToChange = new ArrayList<Integer>();
    204         }
    205 
    206         int idx = 0;
    207         for (MInsn handler : mTryBlock.handlers) {
    208           if (handler == before) {
    209             matchesIndicesToChange.add(idx);
    210             Log.debug("Try block's handler instruction was updated");
    211           } else if (twoWay && handler == after) {
    212             replacementIndicesToChange.add(idx);
    213             Log.debug("Try block's handler instruction was updated");
    214           }
    215           idx++;
    216         }
    217 
    218         for (int idxToChange : matchesIndicesToChange) {
    219           mTryBlock.handlers.set(idxToChange, after);
    220         }
    221 
    222         if (twoWay) {
    223           for (int idxToChange : replacementIndicesToChange) {
    224             mTryBlock.handlers.set(idxToChange, before);
    225           }
    226         }
    227       }
    228     }
    229   }
    230 
    231   /**
    232    * The actual implementation of deleteInstruction called by
    233    * the single-argument deleteInstructions.
    234    */
    235   private void deleteInstructionFull(MInsn toBeDeleted, int toBeDeletedIdx) {
    236     // Make sure we update all locations afterwards first.
    237     updateInstructionLocationsAfter(toBeDeleted, -(toBeDeleted.insn.getSize()));
    238 
    239     // Remove it.
    240     mutatableInsns.remove(toBeDeletedIdx);
    241 
    242     // Update any branch instructions that branched to the instruction we just deleted!
    243 
    244     // First, pick the replacement target.
    245     int replacementTargetIdx = toBeDeletedIdx;
    246     if (replacementTargetIdx == mutatableInsns.size()) {
    247       replacementTargetIdx--;
    248     }
    249     MInsn replacementTarget = mutatableInsns.get(replacementTargetIdx);
    250 
    251     for (MInsn mInsn : mutatableInsns) {
    252       if (mInsn instanceof MBranchInsn) {
    253         // Check if this branch insn points at the insn we just deleted.
    254         MBranchInsn branchInsn = (MBranchInsn) mInsn;
    255         MInsn target = branchInsn.target;
    256         if (target == toBeDeleted) {
    257           Log.debug(branchInsn + " was pointing at the deleted instruction, updated.");
    258           branchInsn.target = replacementTarget;
    259         }
    260       } else if (mInsn instanceof MSwitchInsn) {
    261         // Check if any of this switch insn's targets points at the insn we just deleted.
    262         MSwitchInsn switchInsn = (MSwitchInsn) mInsn;
    263         List<Integer> indicesToChange = new ArrayList<Integer>();
    264         int idx = 0;
    265         for (MInsn target : switchInsn.targets) {
    266           if (target == toBeDeleted) {
    267             indicesToChange.add(idx);
    268             Log.debug(switchInsn + "[" + idx
    269                 + "] was pointing at the deleted instruction, updated.");
    270           }
    271           idx++;
    272         }
    273         for (int idxToChange : indicesToChange) {
    274           switchInsn.targets.remove(idxToChange);
    275           switchInsn.targets.add(idxToChange, replacementTarget);
    276         }
    277       }
    278     }
    279 
    280     // Now update the try blocks.
    281     updateTryBlocksWithReplacementInsn(toBeDeleted, replacementTarget, false);
    282   }
    283 
    284   /**
    285    * Delete the provided MInsn.
    286    */
    287   public void deleteInstruction(MInsn toBeDeleted) {
    288     deleteInstructionFull(toBeDeleted, mutatableInsns.indexOf(toBeDeleted));
    289   }
    290 
    291   /**
    292    * Delete the MInsn at the provided index.
    293    */
    294   public void deleteInstruction(int toBeDeletedIdx) {
    295     deleteInstructionFull(mutatableInsns.get(toBeDeletedIdx), toBeDeletedIdx);
    296   }
    297 
    298   public void swapInstructionsByIndex(int aIdx, int bIdx) {
    299     MInsn aInsn = mutatableInsns.get(aIdx);
    300     MInsn bInsn = mutatableInsns.get(bIdx);
    301 
    302     mutatableInsns.set(aIdx, bInsn);
    303     mutatableInsns.set(bIdx, aInsn);
    304 
    305     updateTryBlocksWithReplacementInsn(aInsn, bInsn, true);
    306 
    307     recalculateLocations();
    308   }
    309 
    310   /**
    311    * Some mutators may require the use of temporary registers. For instance,
    312    * to easily add in printing of values without having to look for registers
    313    * that aren't currently live.
    314    * The idea is to allocate these registers at the top of the set of registers.
    315    * Because this will then shift where the arguments to the method are, we then
    316    * change the start of the method to copy the arguments to the method
    317    * into the place where the rest of the method's code expects them to be.
    318    * Call allocateTemporaryVRegs(n), then use getTemporaryVReg(n),
    319    * and then make sure finishedUsingTemporaryVRegs() is called!
    320    */
    321   public void allocateTemporaryVRegs(int count) {
    322     if (count > tempVRegsAllocated) {
    323       if (tempVRegsAllocated == 0) {
    324         Log.info("Allocating temporary vregs for method...");
    325         initialTempVReg = registersSize;
    326         originalInVReg = (short) (registersSize - insSize);
    327       } else {
    328         Log.info("Extending allocation of temporary vregs for method...");
    329       }
    330       registersSize = (short) (initialTempVReg + count);
    331       if (outsSize < count) {
    332         outsSize = (short) count;
    333       }
    334       vregsNeedCopying = true;
    335       tempVRegsAllocated = (short) count;
    336     }
    337   }
    338 
    339   public int getTemporaryVReg(int number) {
    340     if (number >= tempVRegsAllocated) {
    341       Log.errorAndQuit("Not allocated enough temporary vregs!");
    342     }
    343     return initialTempVReg + number;
    344   }
    345 
    346   public void finishedUsingTemporaryVRegs() {
    347     if (tempVRegsAllocated > 0 && vregsNeedCopying) {
    348       // Just delete all the move instructions and generate again, if we already have some.
    349       while (numMoveInsnsGenerated > 0) {
    350         deleteInstruction(0);
    351         numMoveInsnsGenerated--;
    352       }
    353 
    354       Log.info("Moving 'in' vregs to correct locations after allocating temporary vregs");
    355 
    356       int shortyIdx = 0;
    357       if (isStatic) {
    358         shortyIdx = 1;
    359       }
    360 
    361       int insertionCounter = 0;
    362 
    363       // Insert copy insns that move all the in VRs down.
    364       for (int i = 0; i < insSize; i++) {
    365         MInsn moveInsn = new MInsn();
    366         moveInsn.insn = new Instruction();
    367         moveInsn.insn.vregA = originalInVReg + i;
    368         moveInsn.insn.vregB = originalInVReg + i + tempVRegsAllocated;
    369 
    370         char type = 'L';
    371         if (shortyIdx > 0) {
    372           type = shorty.charAt(shortyIdx);
    373         }
    374         shortyIdx++;
    375 
    376         if (type == 'L') {
    377           moveInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_OBJECT_16);
    378         } else if (type == 'D' || type == 'J') {
    379           moveInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_WIDE_16);
    380           i++;
    381         } else {
    382           moveInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_16);
    383         }
    384 
    385         insertInstructionAt(moveInsn, insertionCounter);
    386         insertionCounter++;
    387         Log.info("Temp vregs creation, Added instruction " + moveInsn);
    388         numMoveInsnsGenerated++;
    389       }
    390 
    391       vregsNeedCopying = false;
    392     }
    393   }
    394 
    395   /**
    396    * When we insert new Field/Type/MethodIds into the DEX file, this may shunt some Ids
    397    * into a new position in the table. If this happens, every reference to the Ids must
    398    * be updated! Because CodeItems have their Instructions wrapped into a graph of MInsns
    399    * during mutation, they don't have a view of all their instructions during mutation,
    400    * and so if they are asked to update their instructions' indices into the tables, they
    401    * must call this method to get the actual list of instructions they currently own.
    402    */
    403   public List<Instruction> requestLatestInstructions() {
    404     List<Instruction> latestInsns = new ArrayList<Instruction>();
    405     for (MInsn mInsn : mutatableInsns) {
    406       latestInsns.add(mInsn.insn);
    407     }
    408     return latestInsns;
    409   }
    410 }
    411