/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dexfuzz.program.mutators; import dexfuzz.Log; import dexfuzz.MutationStats; import dexfuzz.program.MInsn; import dexfuzz.program.MutatableCode; import dexfuzz.program.Mutation; import dexfuzz.rawdex.Instruction; import dexfuzz.rawdex.Opcode; import java.util.List; import java.util.Random; public class ValuePrinter extends CodeMutator { /** * Every CodeMutator has an AssociatedMutation, representing the * mutation that this CodeMutator can perform, to allow separate * generateMutation() and applyMutation() phases, allowing serialization. */ public static class AssociatedMutation extends Mutation { public int printedOutputIdx; @Override public String getString() { return Integer.toString(printedOutputIdx); } @Override public void parseString(String[] elements) { printedOutputIdx = Integer.parseInt(elements[2]); } } // The following two methods are here for the benefit of MutationSerializer, // so it can create a CodeMutator and get the correct associated Mutation, as it // reads in mutations from a dump of mutations. @Override public Mutation getNewMutation() { return new AssociatedMutation(); } public ValuePrinter() { } public ValuePrinter(Random rng, MutationStats stats, List mutations) { super(rng, stats, mutations); likelihood = 40; } @Override protected boolean canMutate(MutatableCode mutatableCode) { for (MInsn mInsn : mutatableCode.getInstructions()) { if (getInstructionOutputType(mInsn) != OutputType.UNKNOWN) { return true; } } Log.debug("No instructions with legible output in method, skipping."); return false; } @Override protected Mutation generateMutation(MutatableCode mutatableCode) { // Find an instruction whose output we wish to print. int printedOutputIdx = 0; boolean foundInsn = false; while (!foundInsn) { printedOutputIdx = rng.nextInt(mutatableCode.getInstructionCount()); MInsn insnOutputToPrint = mutatableCode.getInstructionAt(printedOutputIdx); foundInsn = true; // Don't want to insert instructions where there are raw instructions for now. if (insnOutputToPrint.insn.justRaw) { foundInsn = false; } if (getInstructionOutputType(insnOutputToPrint) == OutputType.UNKNOWN) { foundInsn = false; } } AssociatedMutation mutation = new AssociatedMutation(); mutation.setup(this.getClass(), mutatableCode); mutation.printedOutputIdx = printedOutputIdx; return mutation; } @Override protected void applyMutation(Mutation uncastMutation) { // Cast the Mutation to our AssociatedMutation, so we can access its fields. AssociatedMutation mutation = (AssociatedMutation) uncastMutation; MutatableCode mutatableCode = mutation.mutatableCode; MInsn insnOutputToPrint = mutatableCode.getInstructionAt(mutation.printedOutputIdx); int outFieldIdx = mutatableCode.program.getNewItemCreator().findOrCreateFieldId( "Ljava/lang/System;", "Ljava/io/PrintStream;", "out"); OutputType outputType = getInstructionOutputType(insnOutputToPrint); if (outputType == OutputType.UNKNOWN) { Log.errorAndQuit("Requested to print output of an instruction, whose output" + " type is unknown."); } int printMethodIdx = mutatableCode.program.getNewItemCreator().findOrCreateMethodId( "Ljava/io/PrintStream;", "print", outputType.getSignatureForPrintln()); boolean isWide = false; boolean isRef = false; if (outputType == OutputType.LONG || outputType == OutputType.DOUBLE) { isWide = true; } if (outputType == OutputType.STRING) { isRef = true; } // If we're printing a wide value, we need to allocate 3 registers! if (isWide) { mutatableCode.allocateTemporaryVRegs(3); } else { mutatableCode.allocateTemporaryVRegs(2); } int streamRegister = mutatableCode.getTemporaryVReg(0); int valueRegister = mutatableCode.getTemporaryVReg(1); // Copy the value we want to print to the 2nd temporary register // Then load the out stream // Then call print(out stream, value) MInsn valueCopyInsn = new MInsn(); valueCopyInsn.insn = new Instruction(); if (isRef) { valueCopyInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_OBJECT_16); } else if (isWide) { valueCopyInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_WIDE_16); } else { valueCopyInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_16); } valueCopyInsn.insn.vregB = insnOutputToPrint.insn.vregA; valueCopyInsn.insn.vregA = valueRegister; MInsn streamLoadInsn = new MInsn(); streamLoadInsn.insn = new Instruction(); streamLoadInsn.insn.info = Instruction.getOpcodeInfo(Opcode.SGET_OBJECT); streamLoadInsn.insn.vregB = outFieldIdx; streamLoadInsn.insn.vregA = streamRegister; MInsn invokeInsn = new MInsn(); invokeInsn.insn = new Instruction(); invokeInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_VIRTUAL_RANGE); if (isWide) { invokeInsn.insn.vregA = 3; } else { invokeInsn.insn.vregA = 2; } invokeInsn.insn.vregB = printMethodIdx; invokeInsn.insn.vregC = streamRegister; Log.info(String.format("Printing output value of instruction %s", insnOutputToPrint)); stats.incrementStat("Printed output value"); mutatableCode.insertInstructionAfter(invokeInsn, mutation.printedOutputIdx); mutatableCode.insertInstructionAfter(streamLoadInsn, mutation.printedOutputIdx); mutatableCode.insertInstructionAfter(valueCopyInsn, mutation.printedOutputIdx); mutatableCode.finishedUsingTemporaryVRegs(); } private static enum OutputType { STRING("(Ljava/lang/String;)V"), BOOLEAN("(Z)V"), BYTE("(B)V"), CHAR("(C)V"), SHORT("(S)V"), INT("(I)V"), LONG("(J)V"), FLOAT("(F)V"), DOUBLE("(D)V"), UNKNOWN("UNKNOWN"); private String printingSignature; private OutputType(String s) { printingSignature = s; } public String getSignatureForPrintln() { return printingSignature; } } private OutputType getInstructionOutputType(MInsn mInsn) { Opcode opcode = mInsn.insn.info.opcode; if (opcode == Opcode.CONST_STRING || opcode == Opcode.CONST_STRING_JUMBO) { return OutputType.STRING; } if (opcode == Opcode.IGET_BOOLEAN || opcode == Opcode.SGET_BOOLEAN) { return OutputType.BOOLEAN; } if (opcode == Opcode.IGET_BYTE || opcode == Opcode.SGET_BYTE || opcode == Opcode.INT_TO_BYTE) { return OutputType.BYTE; } if (opcode == Opcode.IGET_CHAR || opcode == Opcode.SGET_CHAR || opcode == Opcode.INT_TO_CHAR) { return OutputType.CHAR; } if (opcode == Opcode.IGET_SHORT || opcode == Opcode.SGET_SHORT || opcode == Opcode.INT_TO_SHORT) { return OutputType.SHORT; } if (opcode == Opcode.NEG_INT || opcode == Opcode.NOT_INT || opcode == Opcode.LONG_TO_INT || opcode == Opcode.FLOAT_TO_INT || opcode == Opcode.DOUBLE_TO_INT || Opcode.isBetween(opcode, Opcode.ADD_INT, Opcode.USHR_INT) || Opcode.isBetween(opcode, Opcode.ADD_INT_2ADDR, Opcode.USHR_INT_2ADDR) || Opcode.isBetween(opcode, Opcode.ADD_INT_LIT16, Opcode.USHR_INT_LIT8)) { return OutputType.INT; } if (opcode == Opcode.NEG_LONG || opcode == Opcode.NOT_LONG || opcode == Opcode.INT_TO_LONG || opcode == Opcode.FLOAT_TO_LONG || opcode == Opcode.DOUBLE_TO_LONG || Opcode.isBetween(opcode, Opcode.ADD_LONG, Opcode.USHR_LONG) || Opcode.isBetween(opcode, Opcode.ADD_LONG_2ADDR, Opcode.USHR_LONG_2ADDR)) { return OutputType.LONG; } if (opcode == Opcode.NEG_FLOAT || opcode == Opcode.INT_TO_FLOAT || opcode == Opcode.LONG_TO_FLOAT || opcode == Opcode.DOUBLE_TO_FLOAT || Opcode.isBetween(opcode, Opcode.ADD_FLOAT, Opcode.REM_FLOAT) || Opcode.isBetween(opcode, Opcode.ADD_FLOAT_2ADDR, Opcode.REM_FLOAT_2ADDR)) { return OutputType.FLOAT; } if (opcode == Opcode.NEG_DOUBLE || opcode == Opcode.INT_TO_DOUBLE || opcode == Opcode.LONG_TO_DOUBLE || opcode == Opcode.FLOAT_TO_DOUBLE || Opcode.isBetween(opcode, Opcode.ADD_DOUBLE, Opcode.REM_DOUBLE) || Opcode.isBetween(opcode, Opcode.ADD_DOUBLE_2ADDR, Opcode.REM_DOUBLE_2ADDR)) { return OutputType.DOUBLE; } return OutputType.UNKNOWN; } }