diff options
160 files changed, 17775 insertions, 0 deletions
@@ -105,6 +105,7 @@ include $(art_path)/imgdiag/Android.mk include $(art_path)/patchoat/Android.mk include $(art_path)/dalvikvm/Android.mk include $(art_path)/tools/Android.mk +include $(art_path)/tools/dexfuzz/Android.mk include $(art_path)/sigchainlib/Android.mk diff --git a/tools/dexfuzz/Android.mk b/tools/dexfuzz/Android.mk new file mode 100644 index 0000000..d8f5582 --- /dev/null +++ b/tools/dexfuzz/Android.mk @@ -0,0 +1,37 @@ +# +# 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. +# + +LOCAL_PATH := $(call my-dir) + +# --- dexfuzz.jar ---------------- +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAR_MANIFEST := manifest.txt +LOCAL_IS_HOST_MODULE := true +LOCAL_MODULE := dexfuzz +include $(BUILD_HOST_JAVA_LIBRARY) + +# --- dexfuzz script ---------------- +include $(CLEAR_VARS) +LOCAL_IS_HOST_MODULE := true +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE := dexfuzz +include $(BUILD_SYSTEM)/base_rules.mk +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/dexfuzz $(ACP) + @echo "Copy: $(PRIVATE_MODULE) ($@)" + $(copy-file-to-new-target) + $(hide) chmod 755 $@ diff --git a/tools/dexfuzz/README b/tools/dexfuzz/README new file mode 100644 index 0000000..c4795f2 --- /dev/null +++ b/tools/dexfuzz/README @@ -0,0 +1,130 @@ +DexFuzz +======= + +DexFuzz is primarily a tool for fuzzing DEX files. Fuzzing is the introduction of +subtle changes ("mutations") to a file to produce a new test case. These test cases +can be used to test the various modes of execution available to ART (Interpreter, +Quick compiler, Optimizing compiler) to check for bugs in these modes of execution. +This is done by differential testing - each test file is executed with each mode of +execution, and any differences between the resulting outputs may be an indication of +a bug in one of the modes. + +For a wider overview of DexFuzz, see: + +http://community.arm.com/groups/android-community/blog/2014/11/26/the-art-of-fuzz-testing + +In typical operation, you provide DexFuzz with a set of DEX files that are the "seeds" +for mutation - e.g. some tests taken from the ART test suite - and point it at an +ADB-connected Android device, and it will fuzz these seed files, and execute the +resulting new tests on the Android device. + +How to run DexFuzz +================== + +1. Build dexfuzz with mmm tools/dexfuzz from within art/. +2. Make sure you have an Android device connected via ADB, that is capable of + having DEX files pushed to it and executed with the dalvikvm command. +3. Make sure you're in the Android build environment! + (That is, . build/envsetup.sh && lunch) +4. Create a new directory, and place some DEX files in here. These are the seed files + that are mutated to form new tests. +5. Create a directory on your device that mutated test files can be pushed to and + executed in, using dalvikvm. For example, /data/art-test/ +6. If you currently have multiple devices connected via ADB, find out the name of + your device using "adb devices -l". +7. Run this command: + +dexfuzz --inputs=<seeds dir> --execute --repeat=<attempts> \ + --dump-output <combination of ISA(s) and and backend(s)> + +You MUST specify one of the following ISAs: + --arm + --arm64 + --x86 + --x86_64 + --mips + --mips64 + +And also at least two of the following backends: + --interpreter + --quick + --optimizing + +Note that if you wanted to test both ARM and ARM64 on an ARM64 device, you can use +--allarm. Also in this case only one backend is needed, if i.e., you wanted to test +ARM Quick Backend vs. ARM64 Quick Backend. + +Some legal examples: + --arm --quick --optimizing + --x86 --quick --optimizing --interpreter + --allarm --quick + +Add in --device=<device name, e.g. device:generic> if you want to specify a device. +Add in --execute-dir=<dir on device> if you want to specify an execution directory. + (The default is /data/art-test/) + +As the fuzzer works, you'll see output like: + +|-----------------------------------------------------------------| +|Iterations|VerifyFail|MutateFail|Timed Out |Successful|Divergence| +|-----------------------------------------------------------------| +| 48 | 37 | 4 | 0 | 6 | 1 | + +Iterations - number of attempts we've made to mutate DEX files. +VerifyFail - the number of mutated files that ended up failing to verify, either + on the host, or the target. +MutateFail - because mutation is a random process, and has attempt thresholds to + avoid attempting to mutate a file indefinitely, it is possible that + an attempt to mutate a file doesn't actually mutate it. This counts + those occurrences. +Timed Out - mutated files that timed out for one or more backends. + Current timeouts are: + Quick - 5 seconds + Optimizing - 5 seconds + Intepreter - 30 seconds + (use --short-timeouts to set all backends to 2 seconds.) +Successful - mutated files that executed and all backends agreed on the resulting + output. NB: if all backends crashed with the same output, this would + be considered a success - proper detection of crashes is still to come. +Divergence - mutated files that executed and some backend disagreed about the + resulting output. Divergent programs are run multiple times with a + single backend, to check if they diverge from themselves, and these are + not included in the count. If multiple architectures are being used + (ARM/ARM64), and the divergences align with different architectures, + these are also not included in the count. + +8. Check report.log for the full report, including input file and RNG seed for each + test program. This allows you to recreate a bad program with, e.g.: + +dexfuzz --input=<input file> --seed=<seed value> + +Check dexfuzz --help for the full list of options. + +NOTE: DEX files with unicode strings are not fully supported yet, and DEX files with +JNI elements are not supported at all currently. + +Mutation Likelihoods +==================== + +Each bytecode mutation has a chance out of 100% of firing. Following is the listing +of each mutation's probability. If you wish to easily adjust these values, copy +these values into a file called likelihoods.txt, and run dexfuzz with +--likelihoods=likelihoods.txt. + +ArithOpChanger 75 +BranchShifter 30 +CmpBiasChanger 30 +ConstantValueChanger 70 +ConversionRepeater 50 +FieldFlagChanger 40 +InstructionDeleter 40 +InstructionDuplicator 80 +InstructionSwapper 80 +NewMethodCaller 10 +NonsenseStringPrinter 10 +PoolIndexChanger 30 +RandomInstructionGenerator 30 +SwitchBranchShifter 30 +TryBlockShifter 40 +ValuePrinter 40 +VRegChanger 60 diff --git a/tools/dexfuzz/dexfuzz b/tools/dexfuzz/dexfuzz new file mode 100755 index 0000000..cd47008 --- /dev/null +++ b/tools/dexfuzz/dexfuzz @@ -0,0 +1,24 @@ +#!/bin/bash +# +# 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. +# + +# +# Wrapper script for calling dexfuzz.jar. +# +DEBUG= +#DEBUG="-Xdebug -Xrunjdwp:transport=dt_socket,address=127.0.0.1:8888,server=y,suspend=n -XX:+HeapDumpOnOutOfMemoryError -ea" + +java ${DEBUG} -jar ${ANDROID_HOST_OUT}/framework/dexfuzz.jar "$@" diff --git a/tools/dexfuzz/manifest.txt b/tools/dexfuzz/manifest.txt new file mode 100644 index 0000000..9e4c214 --- /dev/null +++ b/tools/dexfuzz/manifest.txt @@ -0,0 +1 @@ +Main-Class: dexfuzz.DexFuzz diff --git a/tools/dexfuzz/src/dexfuzz/DexFuzz.java b/tools/dexfuzz/src/dexfuzz/DexFuzz.java new file mode 100644 index 0000000..2fb9663 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/DexFuzz.java @@ -0,0 +1,101 @@ +/* + * 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; + +import dexfuzz.fuzzers.Fuzzer; +import dexfuzz.fuzzers.FuzzerMultipleExecute; +import dexfuzz.fuzzers.FuzzerMultipleNoExecute; +import dexfuzz.fuzzers.FuzzerSingleExecute; +import dexfuzz.fuzzers.FuzzerSingleNoExecute; +import dexfuzz.listeners.BaseListener; +import dexfuzz.listeners.ConsoleLoggerListener; +import dexfuzz.listeners.LogFileListener; +import dexfuzz.listeners.MultiplexerListener; +import dexfuzz.listeners.UniqueProgramTrackerListener; +import dexfuzz.listeners.UpdatingConsoleListener; + +/** + * Entrypoint class for dexfuzz. + */ +public class DexFuzz { + private static int majorVersion = 1; + private static int minorVersion = 0; + private static int seedChangeVersion = 0; + + /** + * Entrypoint to dexfuzz. + */ + public static void main(String[] args) { + // Report the version number, which should be incremented every time something will cause + // the same input seed to produce a different result than before. + Log.always(String.format("DexFuzz v%d.%d.%d", + majorVersion, minorVersion, seedChangeVersion)); + Log.always(""); + + if (!Options.readOptions(args)) { + Log.error("Failed to validate options."); + Options.usage(); + } + + // Create the Listener, which will listen for events and report them. + BaseListener listener = null; + if (Options.repeat > 1 && Options.execute) { + // Create a Listener that is responsible for multiple Listeners. + MultiplexerListener multipleListener = new MultiplexerListener(); + multipleListener.setup(); + // Add the live updating listener, but only if we're not printing out lots of logs. + if (!Log.likelyToLog()) { + multipleListener.addListener(new UpdatingConsoleListener()); + } else { + // If we are dumping out lots of logs, then use the ConsoleLogger instead. + multipleListener.addListener(new ConsoleLoggerListener()); + } + // Add the file logging listener. + multipleListener.addListener(new LogFileListener(Options.reportLogFile)); + // Add the unique program tracker. + multipleListener.addListener(new UniqueProgramTrackerListener(Options.uniqueDatabaseFile)); + listener = multipleListener; + } else { + // Just use the basic listener. + listener = new ConsoleLoggerListener(); + } + + // Create the Fuzzer that uses a particular strategy for fuzzing. + Fuzzer fuzzer = null; + if ((Options.repeat > 1) && Options.execute) { + fuzzer = new FuzzerMultipleExecute(listener); + } else if ((Options.repeat > 1) && !Options.execute) { + fuzzer = new FuzzerMultipleNoExecute(listener); + } else if ((Options.repeat == 1) && Options.execute) { + fuzzer = new FuzzerSingleExecute(listener); + } else if ((Options.repeat == 1) && !Options.execute) { + fuzzer = new FuzzerSingleNoExecute(listener); + } else { + Log.errorAndQuit("Invalid options provided, desired fuzzer unknown."); + } + // TODO: Implement FuzzerFindMinimalMutations. + // TODO: Implement FuzzerGenerational. + + // Actually run the Fuzzer. + fuzzer.run(); + fuzzer.printTimingInfo(); + fuzzer.shutdown(); + + // Cleanup the Listener. + listener.shutdown(); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/ExecutionResult.java b/tools/dexfuzz/src/dexfuzz/ExecutionResult.java new file mode 100644 index 0000000..3a8c6cb --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/ExecutionResult.java @@ -0,0 +1,111 @@ +/* + * 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; + +import java.util.List; + +/** + * Stores the output of an executed command. + */ +public class ExecutionResult { + public List<String> output; + public List<String> error; + public int returnValue; + + private String flattenedOutput; + private String flattenedOutputWithNewlines; + private String flattenedError; + private String flattenedErrorWithNewlines; + private String flattenedAll; + + private static final int TIMEOUT_RETURN_VALUE = 124; + private static final int SIGABORT_RETURN_VALUE = 134; + + /** + * Get only the output, with all lines concatenated together, excluding newline characters. + */ + public String getFlattenedOutput() { + if (flattenedOutput == null) { + StringBuilder builder = new StringBuilder(); + for (String line : output) { + builder.append(line); + } + flattenedOutput = builder.toString(); + } + return flattenedOutput; + } + + /** + * Get only the output, with all lines concatenated together, including newline characters. + */ + public String getFlattenedOutputWithNewlines() { + if (flattenedOutputWithNewlines == null) { + StringBuilder builder = new StringBuilder(); + for (String line : output) { + builder.append(line).append("\n"); + } + flattenedOutputWithNewlines = builder.toString(); + } + return flattenedOutputWithNewlines; + } + + /** + * Get only the error, with all lines concatenated together, excluding newline characters. + */ + public String getFlattenedError() { + if (flattenedError == null) { + StringBuilder builder = new StringBuilder(); + for (String line : error) { + builder.append(line); + } + flattenedError = builder.toString(); + } + return flattenedError; + } + + /** + * Get only the error, with all lines concatenated together, including newline characters. + */ + public String getFlattenedErrorWithNewlines() { + if (flattenedErrorWithNewlines == null) { + StringBuilder builder = new StringBuilder(); + for (String line : error) { + builder.append(line).append("\n"); + } + flattenedErrorWithNewlines = builder.toString(); + } + return flattenedErrorWithNewlines; + } + + /** + * Get both the output and error, concatenated together, excluding newline characters. + */ + public String getFlattenedAll() { + if (flattenedAll == null) { + flattenedAll = getFlattenedOutput() + getFlattenedError(); + } + return flattenedAll; + } + + public boolean isTimeout() { + return (returnValue == TIMEOUT_RETURN_VALUE); + } + + public boolean isSigabort() { + return (returnValue == SIGABORT_RETURN_VALUE); + } +}
\ No newline at end of file diff --git a/tools/dexfuzz/src/dexfuzz/Log.java b/tools/dexfuzz/src/dexfuzz/Log.java new file mode 100644 index 0000000..853550b --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/Log.java @@ -0,0 +1,78 @@ +/* + * 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; + +/** + * Provides access to the logging facilities of dexfuzz. + */ +public class Log { + private static LogTag threshold = LogTag.ERROR; + + // Disable the constructor for this class. + private Log() { } + + public static enum LogTag { + DEBUG, + INFO, + WARN, + ERROR, + ALWAYS + } + + public static void setLoggingLevel(LogTag tag) { + threshold = tag; + } + + public static boolean likelyToLog() { + return (threshold.ordinal() < LogTag.ERROR.ordinal()); + } + + public static void debug(String msg) { + log(LogTag.DEBUG, msg); + } + + public static void info(String msg) { + log(LogTag.INFO, msg); + } + + public static void warn(String msg) { + log(LogTag.WARN, msg); + } + + public static void error(String msg) { + log(LogTag.ERROR, msg); + } + + public static void always(String msg) { + System.out.println(msg); + } + + private static void log(LogTag tag, String msg) { + if (tag.ordinal() >= threshold.ordinal()) { + System.out.println("[" + tag.toString() + "] " + msg); + } + } + + /** + * Reports error and then terminates the program. + */ + public static void errorAndQuit(String msg) { + error(msg); + // TODO: Signal sleeping threads. + System.exit(1); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/MutationStats.java b/tools/dexfuzz/src/dexfuzz/MutationStats.java new file mode 100644 index 0000000..c65b4f2 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/MutationStats.java @@ -0,0 +1,74 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A wrapper for a dictionary tracking what mutations have been performed. + */ +public class MutationStats { + + public static class StatNotFoundException extends RuntimeException { + private static final long serialVersionUID = -7038515184655168470L; + } + + private Map<String,Long> stats; + private List<String> statsOrder; + + public MutationStats() { + stats = new HashMap<String,Long>(); + statsOrder = new ArrayList<String>(); + } + + public void incrementStat(String statName) { + increaseStat(statName, 1); + } + + /** + * Increase the named stat by the specified amount. + */ + public void increaseStat(String statName, long amt) { + if (!stats.containsKey(statName)) { + stats.put(statName, 0L); + statsOrder.add(statName); + } + stats.put(statName, stats.get(statName) + amt); + } + + /** + * Get a string representing the collected stats - looks like a JSON dictionary. + */ + public String getStatsString() { + StringBuilder builder = new StringBuilder(); + builder.append("{"); + boolean first = true; + for (String statName : statsOrder) { + if (!first) { + builder.append(", "); + } else { + first = false; + } + builder.append("\"").append(statName).append("\": ").append(stats.get(statName)); + } + builder.append("}"); + return builder.toString(); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/Options.java b/tools/dexfuzz/src/dexfuzz/Options.java new file mode 100644 index 0000000..1ae7b5e --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/Options.java @@ -0,0 +1,434 @@ +/* + * 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; + +import dexfuzz.Log.LogTag; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Stores options for dexfuzz. + */ +public class Options { + /** + * Constructor has been disabled for this class, which should only be used statically. + */ + private Options() { } + + // KEY VALUE OPTIONS + public static final List<String> inputFileList = new ArrayList<String>(); + public static String outputFile = ""; + public static long rngSeed = -1; + public static boolean usingProvidedSeed = false; + public static int methodMutations = 3; + public static int minMethods = 2; + public static int maxMethods = 10; + public static final Map<String,Integer> mutationLikelihoods = new HashMap<String,Integer>(); + public static String executeClass = "Main"; + public static String deviceName = ""; + public static boolean usingSpecificDevice = false; + public static int repeat = 1; + public static String executeDirectory = "/data/art-test"; + public static String dumpMutationsFile = "mutations.dump"; + public static String loadMutationsFile = "mutations.dump"; + public static String reportLogFile = "report.log"; + public static String uniqueDatabaseFile = "unique_progs.db"; + + // FLAG OPTIONS + public static boolean execute; + public static boolean local; + public static boolean noBootImage; + public static boolean useInterpreter; + public static boolean useQuick; + public static boolean useOptimizing; + public static boolean useArchArm; + public static boolean useArchArm64; + public static boolean useArchX86; + public static boolean useArchX86_64; + public static boolean useArchMips; + public static boolean useArchMips64; + public static boolean skipHostVerify; + public static boolean shortTimeouts; + public static boolean dumpOutput; + public static boolean dumpVerify; + public static boolean mutateLimit; + public static boolean reportUnique; + public static boolean skipMutation; + public static boolean dumpMutations; + public static boolean loadMutations; + + /** + * Print out usage information about dexfuzz, and then exit. + */ + public static void usage() { + Log.always("DexFuzz Usage:"); + Log.always(" --input=<file> : Seed DEX file to be fuzzed"); + Log.always(" (Can specify multiple times.)"); + Log.always(" --inputs=<file> : Directory containing DEX files to be fuzzed."); + Log.always(" --output=<file> : Output DEX file to be produced"); + Log.always(""); + Log.always(" --execute : Execute the resulting fuzzed program"); + Log.always(" --local : Execute on host (Not available yet.)"); + Log.always(" --device=<device> : Execute on an ADB-connected-device, where <device> is"); + Log.always(" the argument given to adb -s. Default execution mode."); + Log.always(" --execute-dir=<dir> : Push tests to this directory to execute them."); + Log.always(" (Default: /data/art-test)"); + Log.always(" --no-boot-image : Use this flag when boot.art is not available."); + Log.always(" --skip-host-verify : When executing, skip host-verification stage"); + Log.always(" --execute-class=<c> : When executing, execute this class (default: Main)"); + Log.always(""); + Log.always(" --interpreter : Include the Interpreter in comparisons"); + Log.always(" --quick : Include the Quick Compiler in comparisons"); + Log.always(" --optimizing : Include the Optimizing Compiler in comparisons"); + Log.always(""); + Log.always(" --arm : Include ARM backends in comparisons"); + Log.always(" --arm64 : Include ARM64 backends in comparisons"); + Log.always(" --allarm : Short for --arm --arm64"); + Log.always(" --x86 : Include x86 backends in comparisons"); + Log.always(" --x86-64 : Include x86-64 backends in comparisons"); + Log.always(" --mips : Include MIPS backends in comparisons"); + Log.always(" --mips64 : Include MIPS64 backends in comparisons"); + Log.always(""); + Log.always(" --dump-output : Dump outputs of executed programs"); + Log.always(" --dump-verify : Dump outputs of verification"); + Log.always(" --repeat=<n> : Fuzz N programs, executing each one."); + Log.always(" --short-timeouts : Shorten timeouts (faster; use if"); + Log.always(" you want to focus on output divergences)"); + Log.always(" --seed=<seed> : RNG seed to use"); + Log.always(" --method-mutations=<n> : Maximum number of mutations to perform on each method."); + Log.always(" (Default: 3)"); + Log.always(" --min-methods=<n> : Minimum number of methods to mutate. (Default: 2)"); + Log.always(" --max-methods=<n> : Maximum number of methods to mutate. (Default: 10)"); + Log.always(" --one-mutation : Short for --method-mutations=1 "); + Log.always(" --min-methods=1 --max-methods=1"); + Log.always(" --likelihoods=<file> : A file containing a table of mutation likelihoods"); + Log.always(" --mutate-limit : Mutate only methods whose names end with _MUTATE"); + Log.always(" --skip-mutation : Do not actually mutate the input, just output it"); + Log.always(" after parsing"); + Log.always(""); + Log.always(" --dump-mutations[=<file>] : Dump an editable set of mutations applied"); + Log.always(" to <file> (default: mutations.dump)"); + Log.always(" --load-mutations[=<file>] : Load and apply a set of mutations"); + Log.always(" from <file> (default: mutations.dump)"); + Log.always(" --log=<tag> : Set more verbose logging level: DEBUG, INFO, WARN"); + Log.always(" --report=<file> : Use <file> to report results when using --repeat"); + Log.always(" (Default: report.log)"); + Log.always(" --report-unique : Print out information about unique programs generated"); + Log.always(" --unique-db=<file> : Use <file> store results about unique programs"); + Log.always(" (Default: unique_progs.db)"); + Log.always(""); + System.exit(0); + } + + /** + * Given a flag option (one that does not feature an =), handle it + * accordingly. Report an error and print usage info if the flag is not + * recognised. + */ + private static void handleFlagOption(String flag) { + if (flag.equals("execute")) { + execute = true; + } else if (flag.equals("local")) { + local = true; + } else if (flag.equals("no-boot-image")) { + noBootImage = true; + } else if (flag.equals("skip-host-verify")) { + skipHostVerify = true; + } else if (flag.equals("interpreter")) { + useInterpreter = true; + } else if (flag.equals("quick")) { + useQuick = true; + } else if (flag.equals("optimizing")) { + useOptimizing = true; + } else if (flag.equals("arm")) { + useArchArm = true; + } else if (flag.equals("arm64")) { + useArchArm64 = true; + } else if (flag.equals("allarm")) { + useArchArm = true; + useArchArm64 = true; + } else if (flag.equals("x86")) { + useArchX86 = true; + } else if (flag.equals("x86-64")) { + useArchX86_64 = true; + } else if (flag.equals("mips")) { + useArchMips = true; + } else if (flag.equals("mips64")) { + useArchMips64 = true; + } else if (flag.equals("mutate-limit")) { + mutateLimit = true; + } else if (flag.equals("report-unique")) { + reportUnique = true; + } else if (flag.equals("dump-output")) { + dumpOutput = true; + } else if (flag.equals("dump-verify")) { + dumpVerify = true; + } else if (flag.equals("short-timeouts")) { + shortTimeouts = true; + } else if (flag.equals("skip-mutation")) { + skipMutation = true; + } else if (flag.equals("dump-mutations")) { + dumpMutations = true; + } else if (flag.equals("load-mutations")) { + loadMutations = true; + } else if (flag.equals("one-mutation")) { + methodMutations = 1; + minMethods = 1; + maxMethods = 1; + } else if (flag.equals("help")) { + usage(); + } else { + Log.error("Unrecognised flag: --" + flag); + usage(); + } + } + + /** + * Given a key-value option (one that features an =), handle it + * accordingly. Report an error and print usage info if the key is not + * recognised. + */ + private static void handleKeyValueOption(String key, String value) { + if (key.equals("input")) { + inputFileList.add(value); + } else if (key.equals("inputs")) { + File folder = new File(value); + if (folder.listFiles() == null) { + Log.errorAndQuit("Specified argument to --inputs is not a directory!"); + } + for (File file : folder.listFiles()) { + String inputName = value + "/" + file.getName(); + Log.always("Adding " + inputName + " to input seed files."); + inputFileList.add(inputName); + } + } else if (key.equals("output")) { + outputFile = value; + } else if (key.equals("seed")) { + rngSeed = Long.parseLong(value); + usingProvidedSeed = true; + } else if (key.equals("method-mutations")) { + methodMutations = Integer.parseInt(value); + } else if (key.equals("min-methods")) { + minMethods = Integer.parseInt(value); + } else if (key.equals("max-methods")) { + maxMethods = Integer.parseInt(value); + } else if (key.equals("repeat")) { + repeat = Integer.parseInt(value); + } else if (key.equals("log")) { + Log.setLoggingLevel(LogTag.valueOf(value.toUpperCase())); + } else if (key.equals("likelihoods")) { + setupMutationLikelihoodTable(value); + } else if (key.equals("dump-mutations")) { + dumpMutations = true; + dumpMutationsFile = value; + } else if (key.equals("load-mutations")) { + loadMutations = true; + loadMutationsFile = value; + } else if (key.equals("report")) { + reportLogFile = value; + } else if (key.equals("unique-db")) { + uniqueDatabaseFile = value; + } else if (key.equals("execute-class")) { + executeClass = value; + } else if (key.equals("device")) { + deviceName = value; + usingSpecificDevice = true; + } else if (key.equals("execute-dir")) { + executeDirectory = value; + } else { + Log.error("Unrecognised key: --" + key); + usage(); + } + } + + private static void setupMutationLikelihoodTable(String tableFilename) { + try { + BufferedReader reader = new BufferedReader(new FileReader(tableFilename)); + String line = reader.readLine(); + while (line != null) { + line = line.replaceAll("\\s+", " "); + String[] entries = line.split(" "); + String name = entries[0].toLowerCase(); + int likelihood = Integer.parseInt(entries[1]); + if (likelihood > 100) { + likelihood = 100; + } + if (likelihood < 0) { + likelihood = 0; + } + mutationLikelihoods.put(name, likelihood); + line = reader.readLine(); + } + reader.close(); + } catch (FileNotFoundException e) { + Log.error("Unable to open mutation probability table file: " + tableFilename); + } catch (IOException e) { + Log.error("Unable to read mutation probability table file: " + tableFilename); + } + } + + /** + * Called by the DexFuzz class during program initialisation to parse + * the program's command line arguments. + * @return If options were successfully read and validated. + */ + public static boolean readOptions(String[] args) { + for (String arg : args) { + if (!(arg.startsWith("--"))) { + Log.error("Unrecognised option: " + arg); + usage(); + } + + // cut off the -- + arg = arg.substring(2); + + // choose between a --X=Y option (keyvalue) and a --X option (flag) + if (arg.contains("=")) { + String[] split = arg.split("="); + handleKeyValueOption(split[0], split[1]); + } else { + handleFlagOption(arg); + } + } + + return validateOptions(); + } + + /** + * Checks if the current options settings are valid, called after reading + * all options. + * @return If the options are valid or not. + */ + private static boolean validateOptions() { + // Deal with option assumptions. + if (inputFileList.isEmpty()) { + File seedFile = new File("fuzzingseed.dex"); + if (seedFile.exists()) { + Log.always("Assuming --input=fuzzingseed.dex"); + inputFileList.add("fuzzingseed.dex"); + } else { + Log.errorAndQuit("No input given, and couldn't find fuzzingseed.dex!"); + return false; + } + } + + if (outputFile.equals("")) { + Log.always("Assuming --output=fuzzingseed_fuzzed.dex"); + outputFile = "fuzzingseed_fuzzed.dex"; + } + + + if (mutationLikelihoods.isEmpty()) { + File likelihoodsFile = new File("likelihoods.txt"); + if (likelihoodsFile.exists()) { + Log.always("Assuming --likelihoods=likelihoods.txt "); + setupMutationLikelihoodTable("likelihoods.txt"); + } else { + Log.always("Using default likelihoods (see README for values)"); + } + } + + // Now check for hard failures. + if (repeat < 1) { + Log.error("--repeat must be at least 1!"); + return false; + } + if (usingProvidedSeed && repeat > 1) { + Log.error("Cannot use --repeat with --seed"); + return false; + } + if (loadMutations && dumpMutations) { + Log.error("Cannot both load and dump mutations"); + return false; + } + if (repeat == 1 && inputFileList.size() > 1) { + Log.error("Must use --repeat if you have provided more than one input"); + return false; + } + if (methodMutations < 0) { + Log.error("Cannot use --method-mutations with a negative value."); + return false; + } + if (minMethods < 0) { + Log.error("Cannot use --min-methods with a negative value."); + return false; + } + if (maxMethods < 0) { + Log.error("Cannot use --max-methods with a negative value."); + return false; + } + if (maxMethods < minMethods) { + Log.error("Cannot use --max-methods that's smaller than --min-methods"); + return false; + } + if (local && usingSpecificDevice) { + Log.error("Cannot use --local and --device!"); + return false; + } + if (execute) { + if (!(useArchArm + || useArchArm64 + || useArchX86 + || useArchX86_64 + || useArchMips + || useArchMips64)) { + Log.error("No architecture to execute on was specified!"); + return false; + } + if ((useArchArm || useArchArm64) && (useArchX86 || useArchX86_64)) { + Log.error("Did you mean to specify ARM and x86?"); + return false; + } + if ((useArchArm || useArchArm64) && (useArchMips || useArchMips64)) { + Log.error("Did you mean to specify ARM and MIPS?"); + return false; + } + if ((useArchX86 || useArchX86_64) && (useArchMips || useArchMips64)) { + Log.error("Did you mean to specify x86 and MIPS?"); + return false; + } + int backends = 0; + if (useInterpreter) { + backends++; + } + if (useQuick) { + backends++; + } + if (useOptimizing) { + backends++; + } + if (useArchArm && useArchArm64) { + // Could just be comparing quick-ARM versus quick-ARM64? + backends++; + } + if (backends < 2) { + Log.error("Not enough backends specified! Try --quick --interpreter!"); + return false; + } + } + + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/StreamConsumer.java b/tools/dexfuzz/src/dexfuzz/StreamConsumer.java new file mode 100644 index 0000000..cd93374 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/StreamConsumer.java @@ -0,0 +1,202 @@ +/* + * 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; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Semaphore; + +/** + * process.waitFor() can block if its output buffers are not drained. + * These threads are used to keep the buffers drained, and provide the final + * output once the command has finished executing. Each Executor has its own + * output and error StreamConsumers. + */ +public class StreamConsumer extends Thread { + private List<String> output; + private BufferedReader reader; + + private State state; + + private Semaphore workToBeDone; + private Semaphore outputIsReady; + + enum State { + WAITING, + CONSUMING, + SHOULD_STOP_CONSUMING, + FINISHED, + ERROR + } + + /** + * Create a StreamConsumer, will be immediately ready to start consuming. + */ + public StreamConsumer() { + output = new ArrayList<String>(); + workToBeDone = new Semaphore(0); + outputIsReady = new Semaphore(0); + + state = State.WAITING; + } + + /** + * Executor should call this to provide its StreamConsumers with the Streams + * for a Process it is about to call waitFor() on. + */ + public void giveStreamAndStartConsuming(InputStream stream) { + output.clear(); + + reader = new BufferedReader(new InputStreamReader(stream)); + + changeState(State.CONSUMING, State.WAITING); + + // Tell consumer there is work to be done. + workToBeDone.release(); + } + + /** + * Executor should call this once its call to waitFor() returns. + */ + public void processFinished() { + changeState(State.SHOULD_STOP_CONSUMING, State.CONSUMING); + } + + /** + * Executor should call this to get the captured output of this StreamConsumer. + */ + public List<String> getOutput() { + + try { + // Wait until the output is ready. + outputIsReady.acquire(); + } catch (InterruptedException e) { + Log.error("Client of StreamConsumer was interrupted while waiting for output?"); + return null; + } + + // Take a copy of the Strings, so when we call output.clear(), we don't + // clear the ExecutionResult's list. + List<String> copy = new ArrayList<String>(output); + return copy; + } + + /** + * Executor should call this when we're shutting down. + */ + public void shutdown() { + changeState(State.FINISHED, State.WAITING); + + // Tell Consumer there is work to be done (it will check first if FINISHED has been set.) + workToBeDone.release(); + } + + private void consume() { + try { + + if (checkState(State.SHOULD_STOP_CONSUMING)) { + // Caller already called processFinished() before we even started + // consuming. Just get what we can and finish. + while (reader.ready()) { + output.add(reader.readLine()); + } + } else { + // Caller's process is still executing, so just loop and consume. + while (checkState(State.CONSUMING)) { + Thread.sleep(50); + while (reader.ready()) { + output.add(reader.readLine()); + } + } + } + + if (checkState(State.SHOULD_STOP_CONSUMING)) { + changeState(State.WAITING, State.SHOULD_STOP_CONSUMING); + } else { + Log.error("StreamConsumer stopped consuming, but was not told to?"); + setErrorState(); + } + + reader.close(); + + } catch (IOException e) { + Log.error("StreamConsumer caught IOException while consuming"); + setErrorState(); + } catch (InterruptedException e) { + Log.error("StreamConsumer caught InterruptedException while consuming"); + setErrorState(); + } + + // Tell client of Consumer that the output is ready. + outputIsReady.release(); + } + + @Override + public void run() { + while (checkState(State.WAITING)) { + try { + // Wait until there is work to be done + workToBeDone.acquire(); + } catch (InterruptedException e) { + Log.error("StreamConsumer caught InterruptedException while waiting for work"); + setErrorState(); + break; + } + + // Check first if we're done + if (checkState(State.FINISHED)) { + break; + } + + // Make sure we're either supposed to be consuming + // or supposed to be finishing up consuming + if (!(checkState(State.CONSUMING) || checkState(State.SHOULD_STOP_CONSUMING))) { + Log.error("invalid state: StreamConsumer told about work, but not CONSUMING?"); + Log.error("state was: " + getCurrentState()); + setErrorState(); + break; + } + + consume(); + } + } + + private synchronized boolean checkState(State expectedState) { + return (expectedState == state); + } + + private synchronized void changeState(State newState, State previousState) { + if (state != previousState) { + Log.error("StreamConsumer Unexpected state: " + state + ", expected " + previousState); + state = State.ERROR; + } else { + state = newState; + } + } + + private synchronized void setErrorState() { + state = State.ERROR; + } + + private synchronized State getCurrentState() { + return state; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/Timer.java b/tools/dexfuzz/src/dexfuzz/Timer.java new file mode 100644 index 0000000..8979b8a --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/Timer.java @@ -0,0 +1,70 @@ +/* + * 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; + +import dexfuzz.listeners.BaseListener; + +/** + * For timing splits of program execution. + */ +public class Timer { + /** + * The name of the timer, the phase of the program it is intended to time. + */ + private String name; + + /** + * A point in time remembered when start() is called. + */ + private long startPoint; + + /** + * A cumulative count of how much time has elapsed. Updated each time + * stop() is called. + */ + private long elapsedTime; + + /** + * Initialise a new timer with the provided name. + */ + public Timer(String name) { + this.name = name; + this.elapsedTime = 0L; + } + + /** + * Start timing. + */ + public void start() { + startPoint = System.currentTimeMillis(); + } + + /** + * Stop timing, update how much time has elapsed. + */ + public void stop() { + long endPoint = System.currentTimeMillis(); + elapsedTime += (endPoint - startPoint); + } + + /** + * Log the elapsed time this timer has recorded. + */ + public void printTime(BaseListener listener) { + listener.handleTiming(name, ((float)elapsedTime) / 1000.0f); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/Architecture.java b/tools/dexfuzz/src/dexfuzz/executors/Architecture.java new file mode 100644 index 0000000..5cdabc3 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/Architecture.java @@ -0,0 +1,31 @@ +/* + * 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.executors; + +/** + * Every Executor must specify an Architecture. It is important that when reduced + * to lower case, these match the ISA string that ART would produce. For example, + * the architecture directory used for /data/dalvik-cache/${ISA} + */ +public enum Architecture { + ARM, + ARM64, + X86, + X86_64, + MIPS, + MIPS64 +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/Arm64InterpreterExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/Arm64InterpreterExecutor.java new file mode 100644 index 0000000..a945283 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/Arm64InterpreterExecutor.java @@ -0,0 +1,49 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class Arm64InterpreterExecutor extends Executor { + + public Arm64InterpreterExecutor(BaseListener listener, Device device) { + super("ARM64 Interpreter", 30, listener, Architecture.ARM64, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm64 -Xint "); + if (device.noBootImageAvailable()) { + commandBuilder.append("-Ximage:/data/art-test/core.art -Xnorelocate "); + } + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/arm64/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/Arm64OptimizingBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/Arm64OptimizingBackendExecutor.java new file mode 100644 index 0000000..2204ba8 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/Arm64OptimizingBackendExecutor.java @@ -0,0 +1,49 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class Arm64OptimizingBackendExecutor extends Executor { + + public Arm64OptimizingBackendExecutor(BaseListener listener, Device device) { + super("ARM64 Optimizing Backend", 5, listener, Architecture.ARM64, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm64 -Xcompiler-option --compiler-backend=Optimizing "); + if (device.noBootImageAvailable()) { + commandBuilder.append("-Ximage:/data/art-test/core.art -Xnorelocate "); + } + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/arm64/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/Arm64QuickBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/Arm64QuickBackendExecutor.java new file mode 100644 index 0000000..55c9c7a --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/Arm64QuickBackendExecutor.java @@ -0,0 +1,49 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class Arm64QuickBackendExecutor extends Executor { + + public Arm64QuickBackendExecutor(BaseListener listener, Device device) { + super("ARM64 Quick Backend", 5, listener, Architecture.ARM64, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm64 "); + if (device.noBootImageAvailable()) { + commandBuilder.append("-Ximage:/data/art-test/core.art -Xnorelocate "); + } + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/arm64/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/ArmInterpreterExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/ArmInterpreterExecutor.java new file mode 100644 index 0000000..68ce2e0 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/ArmInterpreterExecutor.java @@ -0,0 +1,49 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class ArmInterpreterExecutor extends Executor { + + public ArmInterpreterExecutor(BaseListener listener, Device device) { + super("ARM Interpreter", 30, listener, Architecture.ARM, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm32 -Xint "); + if (device.noBootImageAvailable()) { + commandBuilder.append("-Ximage:/data/art-test/core.art -Xnorelocate "); + } + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/arm/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/ArmOptimizingBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/ArmOptimizingBackendExecutor.java new file mode 100644 index 0000000..78cf652 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/ArmOptimizingBackendExecutor.java @@ -0,0 +1,49 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class ArmOptimizingBackendExecutor extends Executor { + + public ArmOptimizingBackendExecutor(BaseListener listener, Device device) { + super("ARM Optimizing Backend", 5, listener, Architecture.ARM, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm32 -Xcompiler-option --compiler-backend=Optimizing "); + if (device.noBootImageAvailable()) { + commandBuilder.append("-Ximage:/data/art-test/core.art -Xnorelocate "); + } + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/arm/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/ArmQuickBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/ArmQuickBackendExecutor.java new file mode 100644 index 0000000..8f026b2 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/ArmQuickBackendExecutor.java @@ -0,0 +1,49 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class ArmQuickBackendExecutor extends Executor { + + public ArmQuickBackendExecutor(BaseListener listener, Device device) { + super("ARM Quick Backend", 5, listener, Architecture.ARM, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm32 "); + if (device.noBootImageAvailable()) { + commandBuilder.append("-Ximage:/data/art-test/core.art -Xnorelocate "); + } + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/arm/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/Device.java b/tools/dexfuzz/src/dexfuzz/executors/Device.java new file mode 100644 index 0000000..8c03103 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/Device.java @@ -0,0 +1,98 @@ +/* + * 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.executors; + +/** + * Handles execution either on a remote device, or locally. + * Currently only remote execution, on an ADB-connected device, is supported. + */ +public class Device { + private boolean isLocal; + private String deviceName; + private boolean usingSpecificDevice; + private boolean noBootImage; + + /** + * The constructor for a local "device". Not yet supported. + */ + public Device() { + this.isLocal = true; + throw new UnsupportedOperationException("Currently local execution is not supported."); + } + + /** + * The constructor for an ADB connected device. + */ + public Device(String deviceName, boolean noBootImage) { + if (!deviceName.isEmpty()) { + this.deviceName = deviceName; + this.usingSpecificDevice = true; + } + this.noBootImage = noBootImage; + } + + /** + * Get the name that would be provided to adb -s to communicate specifically with this device. + */ + public String getName() { + if (isLocal) { + return "LOCAL DEVICE"; + } + return deviceName; + } + + public boolean isLocal() { + return isLocal; + } + + /** + * Certain AOSP builds of Android may not have a full boot.art built. This will be set if + * we use --no-boot-image, and is used by Executors when deciding the arguments for dalvikvm + * and dex2oat when performing host-side verification. + */ + public boolean noBootImageAvailable() { + return noBootImage; + } + + /** + * Get the command prefix for this device if we want to use adb shell. + */ + public String getExecutionShellPrefix() { + if (isLocal) { + return ""; + } + return getExecutionPrefixWithAdb("shell"); + } + + /** + * Get the command prefix for this device if we want to use adb push. + */ + public String getExecutionPushPrefix() { + if (isLocal) { + return ""; + } + return getExecutionPrefixWithAdb("push"); + } + + private String getExecutionPrefixWithAdb(String command) { + if (usingSpecificDevice) { + return String.format("adb -s %s %s ", deviceName, command); + } else { + return String.format("adb %s ", command); + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/Executor.java b/tools/dexfuzz/src/dexfuzz/executors/Executor.java new file mode 100644 index 0000000..7cc584d --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/Executor.java @@ -0,0 +1,303 @@ +/* + * 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.executors; + +import dexfuzz.ExecutionResult; +import dexfuzz.Log; +import dexfuzz.Options; +import dexfuzz.StreamConsumer; +import dexfuzz.listeners.BaseListener; + +import java.io.IOException; +import java.util.Map; + +/** + * Base class containing the common methods for executing a particular backend of ART. + */ +public abstract class Executor { + private String androidHostOut; + private String androidProductOut; + + private StreamConsumer outputConsumer; + private StreamConsumer errorConsumer; + + protected ExecutionResult executionResult; + protected String executeClass; + + // Set by subclasses. + protected String name; + protected int timeout; + protected BaseListener listener; + protected String testLocation; + protected Architecture architecture; + protected Device device; + + protected Executor(String name, int timeout, BaseListener listener, Architecture architecture, + Device device) { + executeClass = Options.executeClass; + + if (Options.shortTimeouts) { + this.timeout = 2; + } else { + this.timeout = timeout; + } + + this.name = name; + this.listener = listener; + this.architecture = architecture; + this.device = device; + + this.testLocation = Options.executeDirectory; + + Map<String, String> envVars = System.getenv(); + androidProductOut = checkForEnvVar(envVars, "ANDROID_PRODUCT_OUT"); + androidHostOut = checkForEnvVar(envVars, "ANDROID_HOST_OUT"); + + outputConsumer = new StreamConsumer(); + outputConsumer.start(); + errorConsumer = new StreamConsumer(); + errorConsumer.start(); + + if (!device.isLocal()) { + // Check for ADB. + try { + ProcessBuilder pb = new ProcessBuilder(); + pb.command("adb", "devices"); + Process process = pb.start(); + int exitValue = process.waitFor(); + if (exitValue != 0) { + Log.errorAndQuit("Problem executing ADB - is it in your $PATH?"); + } + } catch (IOException e) { + Log.errorAndQuit("IOException when executing ADB, is it working?"); + } catch (InterruptedException e) { + Log.errorAndQuit("InterruptedException when executing ADB, is it working?"); + } + + // Check we can run something on ADB. + ExecutionResult result = executeOnDevice("true", true); + if (result.getFlattenedAll().contains("device not found")) { + Log.errorAndQuit("Couldn't connect to specified ADB device: " + device.getName()); + } + } + } + + private String checkForEnvVar(Map<String, String> envVars, String key) { + if (!envVars.containsKey(key)) { + Log.errorAndQuit("Cannot run a fuzzed program if $" + key + " is not set!"); + } + return envVars.get(key); + } + + private ExecutionResult executeCommand(String command, boolean captureOutput) { + ExecutionResult result = new ExecutionResult(); + + Log.info("Executing: " + command); + + try { + ProcessBuilder processBuilder = new ProcessBuilder(command.split(" ")); + processBuilder.environment().put("ANDROID_ROOT", androidHostOut); + Process process = processBuilder.start(); + + if (captureOutput) { + // Give the streams to the StreamConsumers. + outputConsumer.giveStreamAndStartConsuming(process.getInputStream()); + errorConsumer.giveStreamAndStartConsuming(process.getErrorStream()); + } + + // Wait until the process is done - the StreamConsumers will keep the + // buffers drained, so this shouldn't block indefinitely. + // Get the return value as well. + result.returnValue = process.waitFor(); + + Log.info("Return value: " + result.returnValue); + + if (captureOutput) { + // Tell the StreamConsumers to stop consuming, and wait for them to finish + // so we know we have all of the output. + outputConsumer.processFinished(); + errorConsumer.processFinished(); + result.output = outputConsumer.getOutput(); + result.error = errorConsumer.getOutput(); + + // Always explicitly indicate the return code in the text output now. + // NB: adb shell doesn't actually return exit codes currently, but this will + // be useful if/when it does. + result.output.add("RETURN CODE: " + result.returnValue); + } + + } catch (IOException e) { + Log.errorAndQuit("ExecutionResult.execute() caught an IOException"); + } catch (InterruptedException e) { + Log.errorAndQuit("ExecutionResult.execute() caught an InterruptedException"); + } + + return result; + } + + /** + * Called by subclass Executors in their execute() implementations. + */ + protected ExecutionResult executeOnDevice(String command, boolean captureOutput) { + String timeoutString = "timeout " + timeout + " "; + return executeCommand(timeoutString + device.getExecutionShellPrefix() + command, + captureOutput); + } + + private ExecutionResult pushToDevice(String command) { + return executeCommand(device.getExecutionPushPrefix() + command, false); + } + + /** + * Call this to make sure the StreamConsumer threads are stopped. + */ + public void shutdown() { + outputConsumer.shutdown(); + errorConsumer.shutdown(); + } + + /** + * Called by the Fuzzer after each execution has finished, to clear the results. + */ + public void reset() { + executionResult = null; + } + + /** + * Called by the Fuzzer to verify the mutated program using the host-side dex2oat. + */ + public boolean verifyOnHost(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dex2oat "); + + // This assumes that the Architecture enum's name, when reduced to lower-case, + // matches what dex2oat would expect. + commandBuilder.append("--instruction-set=").append(architecture.toString().toLowerCase()); + commandBuilder.append(" --instruction-set-features=default "); + + // Select the correct boot image. + commandBuilder.append("--boot-image=").append(androidProductOut); + if (device.noBootImageAvailable()) { + commandBuilder.append("/data/art-test/core.art "); + } else { + commandBuilder.append("/system/framework/boot.art "); + } + + commandBuilder.append("--oat-file=output.oat "); + commandBuilder.append("--android-root=").append(androidHostOut).append(" "); + commandBuilder.append("--runtime-arg -classpath "); + commandBuilder.append("--runtime-arg ").append(programName).append(" "); + commandBuilder.append("--dex-file=").append(programName).append(" "); + commandBuilder.append("--compiler-filter=interpret-only --runtime-arg -Xnorelocate "); + + ExecutionResult verificationResult = executeCommand(commandBuilder.toString(), true); + + boolean success = true; + + if (verificationResult.isSigabort()) { + listener.handleHostVerificationSigabort(verificationResult); + success = false; + } + + if (success) { + // Search for a keyword that indicates verification was not successful. + // TODO: Determine if dex2oat crashed? + for (String line : verificationResult.error) { + if (line.contains("Verification error") + || line.contains("Failure to verify dex file")) { + success = false; + } + if (Options.dumpVerify) { + // Strip out the start of the log lines. + listener.handleDumpVerify(line.replaceFirst(".*(cc|h):\\d+] ", "")); + } + } + } + + if (!success) { + listener.handleFailedHostVerification(verificationResult); + } + + executeCommand("rm output.oat", false); + + return success; + } + + /** + * Called by the Fuzzer to upload the program to the target device. + * TODO: Check if we're executing on a local device, and don't do this? + */ + public void uploadToTarget(String programName) { + pushToDevice(programName + " " + testLocation); + } + + /** + * Executor subclasses need to override this, to construct their arguments for dalvikvm + * invocation correctly. + */ + public abstract void execute(String programName); + + /** + * Executor subclasses need to override this, to delete their generated OAT file correctly. + */ + public abstract void deleteGeneratedOatFile(String programName); + + /** + * Executor subclasses need to override this, to report if they need a cleaned code cache. + */ + public abstract boolean needsCleanCodeCache(); + + /** + * Fuzzer.checkForArchitectureSplit() will use this determine the architecture of the Executor. + */ + public Architecture getArchitecture() { + return architecture; + } + + /** + * Used in each subclass of Executor's deleteGeneratedOatFile() method, to know what to delete. + */ + protected String getOatFileName(String programName) { + // Converts e.g. /data/art-test/file.dex to data@art-test@file.dex + return (testLocation.replace("/", "@").substring(1) + "@" + programName); + } + + /** + * Used by the Fuzzer to get result of execution. + */ + public ExecutionResult getResult() { + return executionResult; + } + + /** + * Because dex2oat can accept a program with soft errors on the host, and then fail after + * performing hard verification on the target, we need to check if the Executor detected + * a target verification failure, before doing anything else with the resulting output. + * Used by the Fuzzer. + */ + public boolean verifyOnTarget() { + // TODO: Remove this once host-verification can be forced to always fail? + if (executionResult.getFlattenedOutput().contains("VerifyError")) { + return false; + } + return true; + } + + public String getName() { + return name; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/Mips64InterpreterExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/Mips64InterpreterExecutor.java new file mode 100644 index 0000000..9f27b5e --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/Mips64InterpreterExecutor.java @@ -0,0 +1,47 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class Mips64InterpreterExecutor extends Executor { + + public Mips64InterpreterExecutor(BaseListener listener, Device device) { + super("MIPS64 Interpreter", 30, listener, Architecture.MIPS64, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm64 -Xint "); + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/mips64/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return false; + } +}
\ No newline at end of file diff --git a/tools/dexfuzz/src/dexfuzz/executors/Mips64OptimizingBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/Mips64OptimizingBackendExecutor.java new file mode 100644 index 0000000..b30240d --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/Mips64OptimizingBackendExecutor.java @@ -0,0 +1,46 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class Mips64OptimizingBackendExecutor extends Executor { + + public Mips64OptimizingBackendExecutor(BaseListener listener, Device device) { + super("MIPS64 Optimizing Backend", 5, listener, Architecture.MIPS64, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm64 -Xcompiler-option --compiler-backend=Optimizing "); + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/mips64/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/Mips64QuickBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/Mips64QuickBackendExecutor.java new file mode 100644 index 0000000..42ccd1e --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/Mips64QuickBackendExecutor.java @@ -0,0 +1,46 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class Mips64QuickBackendExecutor extends Executor { + + public Mips64QuickBackendExecutor(BaseListener listener, Device device) { + super("MIPS64 Quick Backend", 5, listener, Architecture.MIPS64, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm64 "); + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/mips64/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/MipsInterpreterExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/MipsInterpreterExecutor.java new file mode 100644 index 0000000..524eaa9 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/MipsInterpreterExecutor.java @@ -0,0 +1,46 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class MipsInterpreterExecutor extends Executor { + + public MipsInterpreterExecutor(BaseListener listener, Device device) { + super("MIPS Interpreter", 30, listener, Architecture.MIPS, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm32 -Xint "); + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/mips/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/MipsOptimizingBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/MipsOptimizingBackendExecutor.java new file mode 100644 index 0000000..fcc92c8 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/MipsOptimizingBackendExecutor.java @@ -0,0 +1,46 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class MipsOptimizingBackendExecutor extends Executor { + + public MipsOptimizingBackendExecutor(BaseListener listener, Device device) { + super("MIPS Optimizing Backend", 5, listener, Architecture.MIPS, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm32 -Xcompiler-option --compiler-backend=Optimizing "); + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/mips/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/MipsQuickBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/MipsQuickBackendExecutor.java new file mode 100644 index 0000000..cb442f9 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/MipsQuickBackendExecutor.java @@ -0,0 +1,46 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class MipsQuickBackendExecutor extends Executor { + + public MipsQuickBackendExecutor(BaseListener listener, Device device) { + super("MIPS Quick Backend", 5, listener, Architecture.MIPS, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm32 "); + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/mips/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/X86InterpreterExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/X86InterpreterExecutor.java new file mode 100644 index 0000000..93c14e9 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/X86InterpreterExecutor.java @@ -0,0 +1,46 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class X86InterpreterExecutor extends Executor { + + public X86InterpreterExecutor(BaseListener listener, Device device) { + super("x86 Interpreter", 30, listener, Architecture.X86, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm32 -Xint "); + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/x86/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/X86OptimizingBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/X86OptimizingBackendExecutor.java new file mode 100644 index 0000000..b27d5ca --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/X86OptimizingBackendExecutor.java @@ -0,0 +1,46 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class X86OptimizingBackendExecutor extends Executor { + + public X86OptimizingBackendExecutor(BaseListener listener, Device device) { + super("x86 Optimizing Backend", 5, listener, Architecture.X86, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm32 -Xcompiler-option --compiler-backend=Optimizing "); + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/x86/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/X86QuickBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/X86QuickBackendExecutor.java new file mode 100644 index 0000000..d8ec217 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/X86QuickBackendExecutor.java @@ -0,0 +1,46 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class X86QuickBackendExecutor extends Executor { + + public X86QuickBackendExecutor(BaseListener listener, Device device) { + super("x86 Quick Backend", 5, listener, Architecture.X86, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm32 "); + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/x86/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/X86_64InterpreterExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/X86_64InterpreterExecutor.java new file mode 100644 index 0000000..7497322 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/X86_64InterpreterExecutor.java @@ -0,0 +1,46 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class X86_64InterpreterExecutor extends Executor { + + public X86_64InterpreterExecutor(BaseListener listener, Device device) { + super("x86_64 Interpreter", 30, listener, Architecture.X86_64, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm64 -Xint "); + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/x86_64/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/X86_64OptimizingBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/X86_64OptimizingBackendExecutor.java new file mode 100644 index 0000000..a978f73 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/X86_64OptimizingBackendExecutor.java @@ -0,0 +1,46 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class X86_64OptimizingBackendExecutor extends Executor { + + public X86_64OptimizingBackendExecutor(BaseListener listener, Device device) { + super("x86_64 Optimizing Backend", 5, listener, Architecture.X86_64, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm64 -Xcompiler-option --compiler-backend=Optimizing "); + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/x86_64/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/executors/X86_64QuickBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/X86_64QuickBackendExecutor.java new file mode 100644 index 0000000..85532d8 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/executors/X86_64QuickBackendExecutor.java @@ -0,0 +1,46 @@ +/* + * 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.executors; + +import dexfuzz.listeners.BaseListener; + +public class X86_64QuickBackendExecutor extends Executor { + + public X86_64QuickBackendExecutor(BaseListener listener, Device device) { + super("x86_64 Quick Backend", 5, listener, Architecture.X86_64, device); + } + + @Override + public void execute(String programName) { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("dalvikvm64 "); + commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" "); + commandBuilder.append(executeClass); + executionResult = executeOnDevice(commandBuilder.toString(), true); + } + + @Override + public void deleteGeneratedOatFile(String programName) { + String command = "rm -f /data/dalvik-cache/x86_64/" + getOatFileName(programName); + executeOnDevice(command, false); + } + + @Override + public boolean needsCleanCodeCache() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java b/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java new file mode 100644 index 0000000..4c1acdb --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java @@ -0,0 +1,449 @@ +/* + * 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.fuzzers; + +import dexfuzz.Log; +import dexfuzz.Options; +import dexfuzz.Timer; +import dexfuzz.executors.Architecture; +import dexfuzz.executors.Arm64InterpreterExecutor; +import dexfuzz.executors.Arm64OptimizingBackendExecutor; +import dexfuzz.executors.Arm64QuickBackendExecutor; +import dexfuzz.executors.ArmInterpreterExecutor; +import dexfuzz.executors.ArmOptimizingBackendExecutor; +import dexfuzz.executors.ArmQuickBackendExecutor; +import dexfuzz.executors.Device; +import dexfuzz.executors.Executor; +import dexfuzz.executors.Mips64InterpreterExecutor; +import dexfuzz.executors.Mips64OptimizingBackendExecutor; +import dexfuzz.executors.Mips64QuickBackendExecutor; +import dexfuzz.executors.MipsInterpreterExecutor; +import dexfuzz.executors.MipsOptimizingBackendExecutor; +import dexfuzz.executors.MipsQuickBackendExecutor; +import dexfuzz.executors.X86InterpreterExecutor; +import dexfuzz.executors.X86OptimizingBackendExecutor; +import dexfuzz.executors.X86QuickBackendExecutor; +import dexfuzz.executors.X86_64InterpreterExecutor; +import dexfuzz.executors.X86_64OptimizingBackendExecutor; +import dexfuzz.executors.X86_64QuickBackendExecutor; +import dexfuzz.listeners.BaseListener; +import dexfuzz.program.Mutation; +import dexfuzz.program.Program; +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.OffsetTracker; +import dexfuzz.rawdex.RawDexFile; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A particular fuzzing strategy, this class provides the common methods + * most fuzzing will involve, and subclasses override the run() method, to + * employ a particular strategy. + */ +public abstract class Fuzzer { + private List<Executor> executors; + private OffsetTracker offsetTracker; + + /** + * This is the executor that we use to test for self-divergent programs. + */ + private Executor goldenExecutor; + + /* + * These two flags are set during fuzz(), and then cleared at the end of execute(). + */ + private boolean mutatedSuccessfully; + private boolean savedSuccessfully; + + private Timer totalTimer = new Timer("Total Time"); + private Timer timerDexInput = new Timer("DEX Input"); + private Timer timerProgGen = new Timer("Program Generation"); + private Timer timerMutation = new Timer("Mutation Time"); + private Timer timerDexOutput = new Timer("DEX Output"); + private Timer timerChecksumCalc = new Timer("Checksum Calculation"); + + protected BaseListener listener; + + protected Fuzzer(BaseListener listener) { + totalTimer.start(); + executors = new ArrayList<Executor>(); + this.listener = listener; + } + + public abstract void run(); + + protected abstract String getNextInputFilename(); + + protected abstract String getNextOutputFilename(); + + /** + * Call this after fuzzer execution to print out timing results. + */ + public void printTimingInfo() { + totalTimer.stop(); + timerDexInput.printTime(listener); + timerProgGen.printTime(listener); + timerMutation.printTime(listener); + timerDexOutput.printTime(listener); + timerChecksumCalc.printTime(listener); + totalTimer.printTime(listener); + } + + /** + * Make sure this is called to correctly shutdown each Executor's StreamConsumers. + */ + public void shutdown() { + if (executors != null) { + for (Executor executor : executors) { + executor.shutdown(); + } + } + } + + private void addExecutorsForArchitecture(Device device, Class<? extends Executor> quick, + Class<? extends Executor> optimizing, Class<? extends Executor> interpreter) { + // NB: Currently QuickBackend MUST come immediately before same arch's Interpreter. + // This is because intepreter execution relies on there being an OAT file already + // created to produce correct debug information. Otherwise we will see + // false-positive divergences. + try { + if (Options.useQuick) { + Constructor<? extends Executor> constructor = + quick.getConstructor(BaseListener.class, Device.class); + executors.add(constructor.newInstance(listener, device)); + } + if (Options.useOptimizing) { + Constructor<? extends Executor> constructor = + optimizing.getConstructor(BaseListener.class, Device.class); + executors.add(constructor.newInstance(listener, device)); + } + if (Options.useInterpreter) { + Constructor<? extends Executor> constructor = + interpreter.getConstructor(BaseListener.class, Device.class); + executors.add(constructor.newInstance(listener, device)); + } + } catch (NoSuchMethodException e) { + Log.errorAndQuit("Executor doesn't have correct constructor."); + } catch (InstantiationException e) { + Log.errorAndQuit("Executor couldn't be instantiated."); + } catch (IllegalAccessException e) { + Log.errorAndQuit("Executor couldn't be accessed."); + } catch (IllegalArgumentException e) { + Log.errorAndQuit("Invalid arguments to instantiation of Executor."); + } catch (InvocationTargetException e) { + Log.errorAndQuit("Instantiation of Executor threw an Exception!"); + } + } + + protected void addExecutors() { + Device device = null; + if (Options.local) { + device = new Device(); + } else { + device = new Device(Options.deviceName, Options.noBootImage); + } + + if (Options.useArchArm64) { + addExecutorsForArchitecture(device, Arm64QuickBackendExecutor.class, + Arm64OptimizingBackendExecutor.class, Arm64InterpreterExecutor.class); + } + + if (Options.useArchArm) { + addExecutorsForArchitecture(device, ArmQuickBackendExecutor.class, + ArmOptimizingBackendExecutor.class, ArmInterpreterExecutor.class); + } + + if (Options.useArchX86_64) { + addExecutorsForArchitecture(device, X86_64QuickBackendExecutor.class, + X86_64OptimizingBackendExecutor.class, X86_64InterpreterExecutor.class); + } + + if (Options.useArchX86) { + addExecutorsForArchitecture(device, X86QuickBackendExecutor.class, + X86OptimizingBackendExecutor.class, X86InterpreterExecutor.class); + } + + if (Options.useArchMips64) { + addExecutorsForArchitecture(device, Mips64QuickBackendExecutor.class, + Mips64OptimizingBackendExecutor.class, Mips64InterpreterExecutor.class); + } + + if (Options.useArchMips) { + addExecutorsForArchitecture(device, MipsQuickBackendExecutor.class, + MipsOptimizingBackendExecutor.class, MipsInterpreterExecutor.class); + } + + // Add the first backend as the golden executor for self-divergence tests. + goldenExecutor = executors.get(0); + } + + /** + * Called from each Fuzzer subclass that we can instantiate. Parses the program, fuzzes it, + * and then saves it, if mutation was successful. We can use --skip-mutation to bypass + * the mutation phase, if we wanted to verify that a test program itself works. + */ + protected Program fuzz() { + String inputFile = getNextInputFilename(); + Program program = loadProgram(inputFile, null); + if (program == null) { + Log.errorAndQuit("Problem loading seed file."); + } + // Mutate the program. + if (!Options.skipMutation) { + timerMutation.start(); + program.mutateTheProgram(); + + mutatedSuccessfully = program.updateRawDexFile(); + timerMutation.stop(); + if (!mutatedSuccessfully) { + listener.handleMutationFail(); + } + } else { + Log.info("Skipping mutation stage as requested."); + mutatedSuccessfully = true; + } + if (mutatedSuccessfully) { + savedSuccessfully = saveProgram(program, getNextOutputFilename()); + } + return program; + } + + protected boolean safeToExecute() { + return mutatedSuccessfully && savedSuccessfully; + } + + protected void execute(Program program) { + if (!safeToExecute()) { + Log.errorAndQuit("Your Fuzzer subclass called execute() " + + "without checking safeToExecute()!"); + } + + String programName = getNextOutputFilename(); + boolean verified = true; + if (!Options.skipHostVerify) { + verified = goldenExecutor.verifyOnHost(programName); + } + if (verified) { + boolean skipAnalysis = false; + boolean uploadedToTarget = false; + if (!Options.skipHostVerify) { + listener.handleSuccessfulHostVerification(); + } + for (Executor executor : executors) { + executor.reset(); + if (!uploadedToTarget) { + executor.uploadToTarget(programName); + } else { + uploadedToTarget = true; + } + if (executor.needsCleanCodeCache()) { + executor.deleteGeneratedOatFile(programName); + } + executor.execute(programName); + if (!executor.verifyOnTarget()) { + listener.handleFailedTargetVerification(); + skipAnalysis = true; + break; + } + // Results are saved in the executors until they reset, usually at the + // next iteration. + } + + if (!skipAnalysis) { + listener.handleSuccessfullyFuzzedFile(programName); + analyseResults(program, programName); + } + } + mutatedSuccessfully = false; + savedSuccessfully = false; + } + + /** + * Checks if the different outputs we observed align with different architectures. + */ + private boolean checkForArchitectureSplit(Map<String, List<Executor>> outputMap) { + if (outputMap.size() != 2) { + // Cannot have a two-way split if we don't have 2 kinds of output. + return false; + } + + Architecture[] architectures = new Architecture[2]; + int archIdx = 0; + + // For each kind of output we saw, make sure they all + // came from the same architecture. + for (List<Executor> executorList : outputMap.values()) { + architectures[archIdx] = executorList.get(0).getArchitecture(); + for (int execIdx = 1; execIdx < executorList.size(); execIdx++) { + if (executorList.get(execIdx).getArchitecture() != architectures[archIdx]) { + // Not every executor with this output shared the same architecture. + return false; + } + } + archIdx++; + } + + // Now make sure that the two outputs we saw were different architectures. + if (architectures[0] == architectures[1]) { + return false; + } + return true; + } + + private boolean checkGoldenExecutorForSelfDivergence(String programName) { + // Run golden executor 5 times, make sure it always produces + // the same output, otherwise report that it is self-divergent. + + // TODO: Instead, produce a list of acceptable outputs, and see if the divergent + // outputs of the backends fall within this set of outputs. + String seenOutput = null; + for (int i = 0; i < 5; i++) { + goldenExecutor.reset(); + goldenExecutor.execute(programName); + String output = goldenExecutor.getResult().getFlattenedOutput(); + if (seenOutput == null) { + seenOutput = output; + } else if (!seenOutput.equals(output)) { + return true; + } + } + return false; + } + + private void analyseResults(Program program, String programName) { + // Check timeouts. + // Construct two lists of executors, those who timed out, and those who did not. + // Report if we had some timeouts. + List<Executor> timedOut = new ArrayList<Executor>(); + List<Executor> didNotTimeOut = new ArrayList<Executor>(); + for (Executor executor : executors) { + if (executor.getResult().isTimeout()) { + timedOut.add(executor); + } else { + didNotTimeOut.add(executor); + } + } + if (!timedOut.isEmpty()) { + listener.handleTimeouts(timedOut, didNotTimeOut); + // Do not bother reporting divergence information. + return; + } + + // Check divergences. + // Construct a map {output1: [executor that produced output1, ...], output2: [...]} + // If the map has more than one output, we had divergence, report it. + Map<String, List<Executor>> outputMap = new HashMap<String, List<Executor>>(); + for (Executor executor : executors) { + String output = executor.getResult().getFlattenedOutput(); + if (Options.dumpOutput) { + listener.handleDumpOutput( + executor.getResult().getFlattenedOutputWithNewlines(), executor); + } + if (outputMap.containsKey(output)) { + outputMap.get(output).add(executor); + } else { + List<Executor> newList = new ArrayList<Executor>(); + newList.add(executor); + outputMap.put(output, newList); + } + } + + if (outputMap.size() > 1) { + // Report that we had divergence. + listener.handleDivergences(outputMap); + listener.handleMutations(program.getMutations()); + // If we found divergences, try running the "golden executor" + // a few times in succession, to see if the output it produces is different + // from run to run. If so, then we're probably executing something with either: + // a) randomness + // b) timing-dependent code + // c) threads + // So we will not consider it a "true" divergence, but still useful? + if (checkGoldenExecutorForSelfDivergence(programName)) { + listener.handleSelfDivergence(); + return; + } + // If we found divergences, try checking if the differences + // in outputs align with differences in architectures. + // For example, if we have: {Output1: [ARM, ARM], Output2: [ARM64, ARM64]} + if (checkForArchitectureSplit(outputMap)) { + listener.handleArchitectureSplit(); + } + } else { + // No problems with execution. + listener.handleSuccess(outputMap); + } + } + + private Program loadProgram(String inputName, List<Mutation> mutations) { + Program program = null; + try { + DexRandomAccessFile input = new DexRandomAccessFile(inputName, "r"); + offsetTracker = new OffsetTracker(); + input.setOffsetTracker(offsetTracker); + // Read the raw DexFile + RawDexFile rawDexFile = new RawDexFile(); + timerDexInput.start(); + rawDexFile.read(input); + timerDexInput.stop(); + input.close(); + // Create the program view. + timerProgGen.start(); + program = new Program(rawDexFile, mutations, listener); + timerProgGen.stop(); + } catch (FileNotFoundException e) { + Log.errorAndQuit("Couldn't open a file called " + inputName); + } catch (IOException e) { + Log.errorAndQuit("IOException when trying to load a DEX test file!"); + } + return program; + } + + private boolean saveProgram(Program program, String outputName) { + boolean success = false; + + try { + // Write out the results of mutation. + DexRandomAccessFile output = new DexRandomAccessFile(outputName, "rw"); + output.setOffsetTracker(offsetTracker); + // Delete the contents of the file, in case it already existed. + output.setLength(0); + // Write out the file. + timerDexOutput.start(); + program.writeRawDexFile(output); + timerDexOutput.stop(); + // Recalculate the header info. + timerChecksumCalc.start(); + program.updateRawDexFileHeader(output); + timerChecksumCalc.stop(); + output.close(); + success = true; + } catch (FileNotFoundException e) { + Log.errorAndQuit("Couldn't open a file called " + outputName); + } catch (IOException e) { + Log.errorAndQuit("IOException when trying to save a DEX test file!"); + } + return success; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultiple.java b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultiple.java new file mode 100644 index 0000000..8abaeb0 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultiple.java @@ -0,0 +1,43 @@ +/* + * 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.fuzzers; + +import dexfuzz.Options; +import dexfuzz.listeners.BaseListener; + +/** + * Superclass for fuzzing strategies that perform multiple fuzzes, and want + * their inputs to come from the input list in a round-robin fashion. + */ +public abstract class FuzzerMultiple extends Fuzzer { + protected int iterations; + + protected FuzzerMultiple(BaseListener listener) { + super(listener); + } + + @Override + protected String getNextInputFilename() { + String inputFile = Options.inputFileList.get(0); + if (Options.inputFileList.size() > 1) { + int nextIndex = iterations % Options.inputFileList.size(); + inputFile = Options.inputFileList.get(nextIndex); + } + listener.handleFuzzingFile(inputFile); + return inputFile; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleExecute.java b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleExecute.java new file mode 100644 index 0000000..0cf6df7 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleExecute.java @@ -0,0 +1,51 @@ +/* + * 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.fuzzers; + +import dexfuzz.Options; +import dexfuzz.listeners.BaseListener; +import dexfuzz.program.Program; + +/** + * Fuzz programs multiple times, testing each. + */ +public class FuzzerMultipleExecute extends FuzzerMultiple { + public FuzzerMultipleExecute(BaseListener listener) { + super(listener); + addExecutors(); + } + + @Override + protected String getNextOutputFilename() { + // In MultipleExecute, always use the same output. + return Options.outputFile; + } + + @Override + public void run() { + // TODO: Test that all seed files execute correctly before they are mutated! + for (iterations = 0; iterations < Options.repeat; iterations++) { + listener.handleIterationStarted(iterations); + Program program = fuzz(); + if (safeToExecute()) { + execute(program); + } + listener.handleIterationFinished(iterations); + } + listener.handleSummary(); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleNoExecute.java b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleNoExecute.java new file mode 100644 index 0000000..fea8788 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleNoExecute.java @@ -0,0 +1,46 @@ +/* + * 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.fuzzers; + +import dexfuzz.Options; +import dexfuzz.listeners.BaseListener; + +/** + * Fuzz programs multiple times, writing each one to a new DEX file. + */ +public class FuzzerMultipleNoExecute extends FuzzerMultiple { + public FuzzerMultipleNoExecute(BaseListener listener) { + super(listener); + } + + @Override + protected String getNextOutputFilename() { + // In MultipleNoExecute, produce multiple files, each prefixed + // with the iteration value. + return String.format("%09d_%s", iterations, Options.outputFile); + } + + @Override + public void run() { + for (iterations = 0; iterations < Options.repeat; iterations++) { + listener.handleIterationStarted(iterations); + fuzz(); + listener.handleIterationFinished(iterations); + } + listener.handleSummary(); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingle.java b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingle.java new file mode 100644 index 0000000..68b47c2 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingle.java @@ -0,0 +1,38 @@ +/* + * 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.fuzzers; + +import dexfuzz.Options; +import dexfuzz.listeners.BaseListener; + +/** + * Superclass for fuzzers that fuzz once. + */ +public abstract class FuzzerSingle extends Fuzzer { + protected FuzzerSingle(BaseListener listener) { + super(listener); + } + + @Override + protected String getNextInputFilename() { + return Options.inputFileList.get(0); + } + + protected String getNextOutputFilename() { + return Options.outputFile; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleExecute.java b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleExecute.java new file mode 100644 index 0000000..de0c7db --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleExecute.java @@ -0,0 +1,38 @@ +/* + * 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.fuzzers; + +import dexfuzz.listeners.BaseListener; +import dexfuzz.program.Program; + +/** + * Fuzz a DEX file once, and test it. + */ +public class FuzzerSingleExecute extends FuzzerSingle { + public FuzzerSingleExecute(BaseListener listener) { + super(listener); + addExecutors(); + } + + @Override + public void run() { + Program program = fuzz(); + if (safeToExecute()) { + execute(program); + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleNoExecute.java b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleNoExecute.java new file mode 100644 index 0000000..6015284 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleNoExecute.java @@ -0,0 +1,33 @@ +/* + * 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.fuzzers; + +import dexfuzz.listeners.BaseListener; + +/** + * Fuzz a DEX file once, but don't test it. + */ +public class FuzzerSingleNoExecute extends FuzzerSingle { + public FuzzerSingleNoExecute(BaseListener listener) { + super(listener); + } + + @Override + public void run() { + fuzz(); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/listeners/BaseListener.java b/tools/dexfuzz/src/dexfuzz/listeners/BaseListener.java new file mode 100644 index 0000000..e33fb09 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/listeners/BaseListener.java @@ -0,0 +1,77 @@ +/* + * 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.listeners; + +import dexfuzz.ExecutionResult; +import dexfuzz.executors.Executor; +import dexfuzz.program.Mutation; + +import java.util.List; +import java.util.Map; + +/** + * Base class for Listeners, who are notified about certain events in dexfuzz's execution. + */ +public abstract class BaseListener { + public void setup() { } + + public void shutdown() { } + + public void handleSuccessfulHostVerification() { } + + public void handleFailedHostVerification(ExecutionResult verificationResult) { } + + public void handleFailedTargetVerification() { } + + public void handleIterationStarted(int iteration) { } + + public void handleIterationFinished(int iteration) { } + + public void handleTimeouts(List<Executor> timedOut, List<Executor> didNotTimeOut) { } + + public void handleDivergences(Map<String, List<Executor>> outputMap) { } + + public void handleFuzzingFile(String inputFile) { } + + public void handleSeed(long seed) { } + + public void handleHostVerificationSigabort(ExecutionResult verificationResult) { } + + public void handleSuccess(Map<String, List<Executor>> outputMap) { } + + public void handleDumpOutput(String outputLine, Executor executor) { } + + public void handleDumpVerify(String verifyLine) { } + + public void handleMutationStats(String statsString) { } + + public void handleTiming(String name, float elapsedTime) { } + + public void handleMutationFail() { } + + public void handleSummary() { } + + public void handleSuccessfullyFuzzedFile(String programName) { } + + public void handleSelfDivergence() { } + + public void handleMessage(String msg) { } + + public void handleMutations(List<Mutation> mutations) { } + + public void handleArchitectureSplit() { } +} diff --git a/tools/dexfuzz/src/dexfuzz/listeners/ConsoleLoggerListener.java b/tools/dexfuzz/src/dexfuzz/listeners/ConsoleLoggerListener.java new file mode 100644 index 0000000..1ea74d9 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/listeners/ConsoleLoggerListener.java @@ -0,0 +1,163 @@ +/* + * 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.listeners; + +import dexfuzz.ExecutionResult; +import dexfuzz.executors.Executor; +import dexfuzz.program.Mutation; + +import java.util.List; +import java.util.Map; + +/** + * Logs output to the console, when not using --repeat. + */ +public class ConsoleLoggerListener extends BaseListener { + @Override + public void setup() { + + } + + @Override + public void shutdown() { + + } + + private void logToConsole(String msg) { + System.out.println("CONSOLE: " + msg); + } + + @Override + public void handleSuccessfulHostVerification() { + logToConsole("Successful host verification"); + } + + @Override + public void handleFailedHostVerification(ExecutionResult verificationResult) { + logToConsole("Failed host verification"); + } + + @Override + public void handleMutations(List<Mutation> mutations) { + for (Mutation mutation : mutations) { + logToConsole("Applied mutation: " + mutation.toString()); + } + } + + @Override + public void handleArchitectureSplit() { + logToConsole("Detected architectural split."); + } + + @Override + public void handleFailedTargetVerification() { + logToConsole("Failed target verification"); + } + + @Override + public void handleIterationStarted(int iteration) { + logToConsole("Starting iteration " + iteration); + } + + @Override + public void handleIterationFinished(int iteration) { + logToConsole("Finished iteration " + iteration); + } + + @Override + public void handleTimeouts(List<Executor> timedOut, List<Executor> didNotTimeOut) { + logToConsole("Timed out: " + timedOut.size() + " Did not time out: " + didNotTimeOut.size()); + } + + @Override + public void handleDivergences(Map<String, List<Executor>> outputMap) { + logToConsole("Got divergences!"); + int outputCount = 1; + for (List<Executor> executors : outputMap.values()) { + logToConsole("Output " + outputCount + ":"); + for (Executor executor : executors) { + logToConsole(" " + executor.getName()); + } + outputCount++; + } + } + + @Override + public void handleFuzzingFile(String inputFile) { + logToConsole("Fuzzing: " + inputFile); + } + + @Override + public void handleSeed(long seed) { + logToConsole("Seed: " + seed); + } + + @Override + public void handleHostVerificationSigabort(ExecutionResult verificationResult) { + logToConsole("Sigaborted host verification"); + } + + @Override + public void handleSuccessfullyFuzzedFile(String programName) { + logToConsole("Program " + programName + " successfully fuzzed."); + } + + @Override + public void handleSuccess(Map<String, List<Executor>> outputMap) { + logToConsole("Execution was successful"); + } + + @Override + public void handleDumpOutput(String outputLine, Executor executor) { + logToConsole(executor.getName() + " OUTPUT: " + outputLine); + } + + @Override + public void handleDumpVerify(String verifyLine) { + logToConsole("VERIFY: " + verifyLine); + } + + @Override + public void handleMutationFail() { + logToConsole("DEX file was not mutated"); + } + + @Override + public void handleMutationStats(String statsString) { + logToConsole("Mutations performed: " + statsString); + } + + @Override + public void handleTiming(String name, float elapsedTime) { + logToConsole(String.format("'%s': %.3fs", name, elapsedTime)); + } + + @Override + public void handleSummary() { + logToConsole("--- SUMMARY ---"); + } + + @Override + public void handleSelfDivergence() { + logToConsole("Seen self divergence"); + } + + @Override + public void handleMessage(String msg) { + logToConsole(msg); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/listeners/LogFileListener.java b/tools/dexfuzz/src/dexfuzz/listeners/LogFileListener.java new file mode 100644 index 0000000..09ee756 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/listeners/LogFileListener.java @@ -0,0 +1,277 @@ +/* + * 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.listeners; + +import dexfuzz.ExecutionResult; +import dexfuzz.Log; +import dexfuzz.executors.Executor; +import dexfuzz.program.Mutation; +import dexfuzz.program.MutationSerializer; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * Logs events to a file. + */ +public class LogFileListener extends BaseListener { + private BufferedWriter writer; + boolean ready = false; + + long successfulVerification; + long failedVerification; + long failedMutation; + long success; + long timedOut; + long divergence; + long selfDivergent; + long architectureSplit; + long iterations; + + private String logFile; + + public LogFileListener(String logFile) { + this.logFile = logFile; + } + + @Override + public void setup() { + try { + writer = new BufferedWriter(new FileWriter(logFile)); + ready = true; + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void shutdown() { + try { + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + Log.always("Full log in " + logFile); + } + + private void write(String msg) { + if (!ready) { + return; + } + try { + writer.write(msg + "\n"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void handleSuccessfulHostVerification() { + write("Host verification: SUCCESS"); + successfulVerification++; + } + + @Override + public void handleFailedHostVerification(ExecutionResult verificationResult) { + write("Host verification: FAILED"); + failedVerification++; + } + + @Override + public void handleFailedTargetVerification() { + write("Target verification: FAILED"); + failedVerification++; + } + + @Override + public void handleIterationStarted(int iteration) { + write("--> FUZZ " + (iteration + 1)); + Date now = new Date(System.currentTimeMillis()); + write("Time started: " + now.toString()); + iterations++; + } + + @Override + public void handleTimeouts(List<Executor> timedOut, List<Executor> didNotTimeOut) { + write("Some executors timed out."); + write("Timed out:"); + for (Executor executor : timedOut) { + write(" " + executor.getName()); + } + if (!didNotTimeOut.isEmpty()) { + write("Did not time out:"); + for (Executor executor : didNotTimeOut) { + write(" " + executor.getName()); + } + } + this.timedOut++; + } + + @Override + public void handleDivergences(Map<String, List<Executor>> outputMap) { + write("DIVERGENCE between some executors!"); + int outputCount = 1; + for (List<Executor> executors : outputMap.values()) { + write("Output " + outputCount + ":"); + for (Executor executor : executors) { + write(" " + executor.getName()); + } + outputCount++; + } + divergence++; + + // You are probably interested in reading about these divergences while fuzzing + // is taking place, so flush the writer now. + try { + writer.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void handleFuzzingFile(String inputFile) { + write("Fuzzing file '" + inputFile + "'"); + } + + @Override + public void handleSeed(long seed) { + write("Using " + seed + " for seed."); + // Flush the seed as well, so if anything goes wrong we can see what seed lead + // to the issue. + try { + writer.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void handleHostVerificationSigabort(ExecutionResult verificationResult) { + write("Host verification: SIGABORTED"); + } + + @Override + public void handleSuccess(Map<String, List<Executor>> outputMap) { + write("All executors agreed on result."); + success++; + } + + @Override + public void handleDumpOutput(String outputLine, Executor executor) { + write(executor.getName() + " OUTPUT:"); + write(outputLine); + } + + @Override + public void handleDumpVerify(String verifyLine) { + write("VERIFY: " + verifyLine); + } + + @Override + public void handleMutationStats(String statsString) { + write("Mutation Stats: " + statsString); + } + + @Override + public void handleTiming(String name, float elapsedTime) { + write(String.format("'%s': %.3fs", name, elapsedTime)); + } + + @Override + public void handleMutationFail() { + write("Mutation process: FAILED"); + failedMutation++; + } + + @Override + public void handleSummary() { + write(""); + write("---+++--- SUMMARY ---+++---"); + write("Fuzzing attempts: " + iterations); + write(" Failed verification: " + failedVerification); + write(" Failed mutation: " + failedMutation); + write(" Timed out: " + timedOut); + write("Successful: " + success); + write(" Self divergent: " + selfDivergent); + write(" Architecture split: " + architectureSplit); + write("Produced divergence: " + divergence); + + long truelyDivergent = divergence - (selfDivergent + architectureSplit); + long verified = success + timedOut + truelyDivergent; + + write(""); + + float verifiedTotalRatio = + (((float) (verified)) / iterations) * 100.0f; + write(String.format("Percentage Verified/Total: %.3f%%", verifiedTotalRatio)); + + float timedOutVerifiedRatio = + (((float) timedOut) / (verified)) * 100.0f; + write(String.format("Percentage Timed Out/Verified: %.3f%%", timedOutVerifiedRatio)); + + float successfulVerifiedRatio = + (((float) success) / (verified)) * 100.0f; + write(String.format("Percentage Successful/Verified: %.3f%%", successfulVerifiedRatio)); + + float divergentVerifiedRatio = + (((float) truelyDivergent) / (verified)) * 100.0f; + write(String.format("Percentage Divergent/Verified: %.3f%%", divergentVerifiedRatio)); + + write("---+++--- SUMMARY ---+++---"); + write(""); + } + + @Override + public void handleIterationFinished(int iteration) { + write(""); + } + + @Override + public void handleSuccessfullyFuzzedFile(String programName) { + write("Successfully fuzzed file '" + programName + "'"); + } + + @Override + public void handleSelfDivergence() { + write("Golden Executor was self-divergent!"); + selfDivergent++; + } + + @Override + public void handleArchitectureSplit() { + write("Divergent outputs align with difference in architectures."); + architectureSplit++; + } + + @Override + public void handleMessage(String msg) { + write(msg); + } + + @Override + public void handleMutations(List<Mutation> mutations) { + write("Mutations Report"); + for (Mutation mutation : mutations) { + write(MutationSerializer.getMutationString(mutation)); + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/listeners/MultiplexerListener.java b/tools/dexfuzz/src/dexfuzz/listeners/MultiplexerListener.java new file mode 100644 index 0000000..28ebce7 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/listeners/MultiplexerListener.java @@ -0,0 +1,205 @@ +/* + * 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.listeners; + +import dexfuzz.ExecutionResult; +import dexfuzz.executors.Executor; +import dexfuzz.program.Mutation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Handles situation where multiple Listeners are wanted, passes notifications + * onto each Listener it is responsible for. + */ +public class MultiplexerListener extends BaseListener { + + private List<BaseListener> listeners; + + @Override + public void setup() { + listeners = new ArrayList<BaseListener>(); + } + + public void addListener(BaseListener listener) { + listeners.add(listener); + listener.setup(); + } + + @Override + public void shutdown() { + for (BaseListener listener : listeners) { + listener.shutdown(); + } + } + + @Override + public void handleSuccessfulHostVerification() { + for (BaseListener listener : listeners) { + listener.handleSuccessfulHostVerification(); + } + } + + @Override + public void handleFailedHostVerification(ExecutionResult verificationResult) { + for (BaseListener listener : listeners) { + listener.handleFailedHostVerification(verificationResult); + } + } + + @Override + public void handleFailedTargetVerification() { + for (BaseListener listener : listeners) { + listener.handleFailedTargetVerification(); + } + } + + @Override + public void handleIterationStarted(int iteration) { + for (BaseListener listener : listeners) { + listener.handleIterationStarted(iteration); + } + } + + @Override + public void handleIterationFinished(int iteration) { + for (BaseListener listener : listeners) { + listener.handleIterationFinished(iteration); + } + } + + @Override + public void handleTimeouts(List<Executor> timedOut, List<Executor> didNotTimeOut) { + for (BaseListener listener : listeners) { + listener.handleTimeouts(timedOut, didNotTimeOut); + } + } + + @Override + public void handleDivergences(Map<String, List<Executor>> outputMap) { + for (BaseListener listener : listeners) { + listener.handleDivergences(outputMap); + } + } + + @Override + public void handleFuzzingFile(String inputFile) { + for (BaseListener listener : listeners) { + listener.handleFuzzingFile(inputFile); + } + } + + @Override + public void handleSeed(long seed) { + for (BaseListener listener : listeners) { + listener.handleSeed(seed); + } + } + + @Override + public void handleHostVerificationSigabort(ExecutionResult verificationResult) { + for (BaseListener listener : listeners) { + listener.handleHostVerificationSigabort(verificationResult); + } + } + + @Override + public void handleSuccess(Map<String, List<Executor>> outputMap) { + for (BaseListener listener : listeners) { + listener.handleSuccess(outputMap); + } + } + + @Override + public void handleDumpOutput(String outputLine, Executor executor) { + for (BaseListener listener : listeners) { + listener.handleDumpOutput(outputLine, executor); + } + } + + @Override + public void handleDumpVerify(String verifyLine) { + for (BaseListener listener : listeners) { + listener.handleDumpVerify(verifyLine); + } + } + + @Override + public void handleMutationStats(String statsString) { + for (BaseListener listener : listeners) { + listener.handleMutationStats(statsString); + } + } + + @Override + public void handleTiming(String name, float elapsedTime) { + for (BaseListener listener : listeners) { + listener.handleTiming(name, elapsedTime); + } + } + + @Override + public void handleMutationFail() { + for (BaseListener listener : listeners) { + listener.handleMutationFail(); + } + } + + @Override + public void handleSummary() { + for (BaseListener listener : listeners) { + listener.handleSummary(); + } + } + + @Override + public void handleSuccessfullyFuzzedFile(String programName) { + for (BaseListener listener : listeners) { + listener.handleSuccessfullyFuzzedFile(programName); + } + } + + @Override + public void handleSelfDivergence() { + for (BaseListener listener : listeners) { + listener.handleSelfDivergence(); + } + } + + @Override + public void handleMessage(String msg) { + for (BaseListener listener : listeners) { + listener.handleMessage(msg); + } + } + + @Override + public void handleMutations(List<Mutation> mutations) { + for (BaseListener listener : listeners) { + listener.handleMutations(mutations); + } + } + + @Override + public void handleArchitectureSplit() { + for (BaseListener listener : listeners) { + listener.handleArchitectureSplit(); + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/listeners/UniqueProgramTrackerListener.java b/tools/dexfuzz/src/dexfuzz/listeners/UniqueProgramTrackerListener.java new file mode 100644 index 0000000..affaffc --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/listeners/UniqueProgramTrackerListener.java @@ -0,0 +1,259 @@ +/* + * 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.listeners; + +import dexfuzz.Log; +import dexfuzz.Options; +import dexfuzz.executors.Executor; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Tracks unique programs and outputs. Also saves divergent programs! + */ +public class UniqueProgramTrackerListener extends BaseListener { + /** + * Map of unique program MD5 sums, mapped to times seen. + */ + private Map<String, Integer> uniquePrograms; + + /** + * Map of unique program outputs (MD5'd), mapped to times seen. + */ + private Map<String, Integer> uniqueOutputs; + + /** + * Used to remember the seed used to fuzz the fuzzed file, so we can save it with this + * seed as a name, if we find a divergence. + */ + private long currentSeed; + + /** + * Used to remember the name of the file we've fuzzed, so we can save it if we + * find a divergence. + */ + private String fuzzedFile; + + private MessageDigest digest; + private String databaseFile; + + /** + * Save the database every X number of iterations. + */ + private static final int saveDatabasePeriod = 20; + + public UniqueProgramTrackerListener(String databaseFile) { + this.databaseFile = databaseFile; + } + + @Override + public void handleSeed(long seed) { + currentSeed = seed; + } + + /** + * Given a program filename, calculate the MD5sum of + * this program. + */ + private String getMD5SumOfProgram(String programName) { + byte[] buf = new byte[256]; + try { + FileInputStream stream = new FileInputStream(programName); + boolean done = false; + while (!done) { + int bytesRead = stream.read(buf); + if (bytesRead == -1) { + done = true; + } else { + digest.update(buf); + } + } + stream.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return new String(digest.digest()); + } + + private String getMD5SumOfOutput(String output) { + digest.update(output.getBytes()); + return new String(digest.digest()); + } + + @SuppressWarnings("unchecked") + private void loadUniqueProgsData() { + File file = new File(databaseFile); + if (!file.exists()) { + uniquePrograms = new HashMap<String, Integer>(); + uniqueOutputs = new HashMap<String, Integer>(); + return; + } + + try { + ObjectInputStream objectStream = + new ObjectInputStream(new FileInputStream(databaseFile)); + uniquePrograms = (Map<String, Integer>) objectStream.readObject(); + uniqueOutputs = (Map<String, Integer>) objectStream.readObject(); + objectStream.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + + } + + private void saveUniqueProgsData() { + // Since we could potentially stop the program while writing out this DB, + // copy the old file beforehand, and then delete it if we successfully wrote out the DB. + boolean oldWasSaved = false; + File file = new File(databaseFile); + if (file.exists()) { + try { + Process process = + Runtime.getRuntime().exec(String.format("cp %1$s %1$s.old", databaseFile)); + // Shouldn't block, cp shouldn't produce output. + process.waitFor(); + oldWasSaved = true; + } catch (IOException exception) { + exception.printStackTrace(); + } catch (InterruptedException exception) { + exception.printStackTrace(); + } + } + + // Now write out the DB. + boolean success = false; + try { + ObjectOutputStream objectStream = + new ObjectOutputStream(new FileOutputStream(databaseFile)); + objectStream.writeObject(uniquePrograms); + objectStream.writeObject(uniqueOutputs); + objectStream.close(); + success = true; + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + // If we get here, and we successfully wrote out the DB, delete the saved one. + if (oldWasSaved && success) { + try { + Process process = + Runtime.getRuntime().exec(String.format("rm %s.old", databaseFile)); + // Shouldn't block, rm shouldn't produce output. + process.waitFor(); + } catch (IOException exception) { + exception.printStackTrace(); + } catch (InterruptedException exception) { + exception.printStackTrace(); + } + } else if (oldWasSaved && !success) { + Log.error("Failed to successfully write out the unique programs DB!"); + Log.error("Old DB should be saved in " + databaseFile + ".old"); + } + } + + private void addToMap(String md5sum, Map<String, Integer> map) { + if (map.containsKey(md5sum)) { + map.put(md5sum, map.get(md5sum) + 1); + } else { + map.put(md5sum, 1); + } + } + + private void saveDivergentProgram() { + File before = new File(fuzzedFile); + File after = new File(String.format("divergent_programs/%d.dex", currentSeed)); + boolean success = before.renameTo(after); + if (!success) { + Log.error("Failed to save divergent program! Does divergent_programs/ exist?"); + } + } + + @Override + public void setup() { + try { + digest = MessageDigest.getInstance("MD5"); + loadUniqueProgsData(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + @Override + public void handleIterationFinished(int iteration) { + if ((iteration % saveDatabasePeriod) == (saveDatabasePeriod - 1)) { + saveUniqueProgsData(); + } + } + + @Override + public void handleSuccessfullyFuzzedFile(String programName) { + String md5sum = getMD5SumOfProgram(programName); + addToMap(md5sum, uniquePrograms); + + fuzzedFile = programName; + } + + @Override + public void handleDivergences(Map<String, List<Executor>> outputMap) { + // Just use the first one. + String output = (String) outputMap.keySet().toArray()[0]; + String md5sum = getMD5SumOfOutput(output); + addToMap(md5sum, uniqueOutputs); + + saveDivergentProgram(); + } + + @Override + public void handleSuccess(Map<String, List<Executor>> outputMap) { + // There's only one, use it. + String output = (String) outputMap.keySet().toArray()[0]; + String md5sum = getMD5SumOfOutput(output); + addToMap(md5sum, uniqueOutputs); + } + + @Override + public void handleSummary() { + if (Options.reportUnique) { + Log.always("-- UNIQUE PROGRAM REPORT --"); + Log.always("Unique Programs Seen: " + uniquePrograms.size()); + Log.always("Unique Outputs Seen: " + uniqueOutputs.size()); + Log.always("---------------------------"); + } + + saveUniqueProgsData(); + } + +} diff --git a/tools/dexfuzz/src/dexfuzz/listeners/UpdatingConsoleListener.java b/tools/dexfuzz/src/dexfuzz/listeners/UpdatingConsoleListener.java new file mode 100644 index 0000000..39d1d2f --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/listeners/UpdatingConsoleListener.java @@ -0,0 +1,108 @@ +/* + * 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.listeners; + +import dexfuzz.ExecutionResult; +import dexfuzz.executors.Executor; + +import java.util.List; +import java.util.Map; + +/** + * Implements the live updating table of results when --repeat is being used. + */ +public class UpdatingConsoleListener extends BaseListener { + long successfulVerification; + long failedVerification; + long failedMutation; + long success; + long timedOut; + long divergence; + long selfDivergent; + long architectureSplit; + long iterations; + + @Override + public void setup() { + System.out.println("|-----------------------------------------------------------------|"); + System.out.println("|Iterations|VerifyFail|MutateFail|Timed Out |Successful|Divergence|"); + System.out.println("|-----------------------------------------------------------------|"); + } + + @Override + public void handleSuccessfulHostVerification() { + successfulVerification++; + } + + @Override + public void handleFailedHostVerification(ExecutionResult verificationResult) { + failedVerification++; + } + + @Override + public void handleFailedTargetVerification() { + failedVerification++; + } + + @Override + public void handleIterationStarted(int iteration) { + iterations++; + } + + @Override + public void handleIterationFinished(int iteration) { + String output = String.format("| %-9d| %-9d| %-9d| %-9d| %-9d| %-9d|", + iterations, failedVerification, failedMutation, timedOut, success, + divergence - (selfDivergent + architectureSplit)); + System.out.print("\r" + output); + } + + @Override + public void handleTimeouts(List<Executor> timedOut, List<Executor> didNotTimeOut) { + this.timedOut++; + } + + @Override + public void handleDivergences(Map<String, List<Executor>> outputMap) { + divergence++; + } + + @Override + public void handleSelfDivergence() { + selfDivergent++; + } + + @Override + public void handleArchitectureSplit() { + architectureSplit++; + } + + @Override + public void handleSuccess(Map<String, List<Executor>> outputMap) { + success++; + } + + @Override + public void handleMutationFail() { + failedMutation++; + } + + @Override + public void handleSummary() { + System.out.println("\n|-----------------------------------------------------------------|"); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/CodeTranslator.java b/tools/dexfuzz/src/dexfuzz/program/CodeTranslator.java new file mode 100644 index 0000000..650501b --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/CodeTranslator.java @@ -0,0 +1,592 @@ +/* + * 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; + +import dexfuzz.Log; +import dexfuzz.rawdex.CodeItem; +import dexfuzz.rawdex.EncodedCatchHandler; +import dexfuzz.rawdex.EncodedTypeAddrPair; +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.Opcode; +import dexfuzz.rawdex.TryItem; +import dexfuzz.rawdex.formats.ContainsTarget; +import dexfuzz.rawdex.formats.RawInsnHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Translates from a CodeItem (the raw list of Instructions) to MutatableCode + * (graph of Instructions, using MInsns and subclasses) and vice-versa. + */ +public class CodeTranslator { + + /** + * Given a raw DEX file's CodeItem, produce a MutatableCode object, that CodeMutators + * are designed to operate on. + * @param codeItemIdx Used to make sure the correct CodeItem is updated later after mutation. + * @return A new MutatableCode object, which contains all relevant information + * obtained from the CodeItem. + */ + public MutatableCode codeItemToMutatableCode(Program program, CodeItem codeItem, + int codeItemIdx, int mutatableCodeIdx) { + Log.debug("Translating CodeItem " + codeItemIdx + + " (" + codeItem.meta.methodName + ") to MutatableCode"); + + MutatableCode mutatableCode = new MutatableCode(program); + + codeItem.registerMutatableCode(mutatableCode); + + mutatableCode.name = codeItem.meta.methodName; + mutatableCode.shorty = codeItem.meta.shorty; + mutatableCode.isStatic = codeItem.meta.isStatic; + + mutatableCode.codeItemIdx = codeItemIdx; + + mutatableCode.mutatableCodeIdx = mutatableCodeIdx; + + mutatableCode.registersSize = codeItem.registersSize; + mutatableCode.insSize = codeItem.insSize; + mutatableCode.outsSize = codeItem.outsSize; + mutatableCode.triesSize = codeItem.triesSize; + + // Temporary map from bytecode offset -> instruction. + Map<Integer,MInsn> insnLocationMap = new HashMap<Integer,MInsn>(); + + List<Instruction> inputInsns = codeItem.insns; + + // Create the MInsns. + int loc = 0; + for (Instruction insn : inputInsns) { + MInsn mInsn = null; + + if (isInstructionSwitch(insn)) { + mInsn = new MSwitchInsn(); + } else if (isInstructionBranch(insn)) { + mInsn = new MBranchInsn(); + } else if (isInstructionFillArrayData(insn)) { + mInsn = new MInsnWithData(); + } else { + mInsn = new MInsn(); + } + + mInsn.insn = insn; + + // Populate the temporary map. + insnLocationMap.put(loc, mInsn); + + // Populate the proper list of mutatable instructions. + mutatableCode.addInstructionToEnd(mInsn); + + // Calculate the offsets for each instruction. + mInsn.location = loc; + mInsn.locationUpdated = false; + + loc += mInsn.insn.getSize(); + } + + // Now make branch/switch instructions point at the right target instructions. + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn instanceof MSwitchInsn) { + readSwitchInstruction((MSwitchInsn) mInsn, insnLocationMap); + } else if (mInsn instanceof MInsnWithData) { + ContainsTarget containsTarget = (ContainsTarget) mInsn.insn.info.format; + int targetLoc = mInsn.location + (int) containsTarget.getTarget(mInsn.insn); + ((MInsnWithData)mInsn).dataTarget = insnLocationMap.get(targetLoc); + if (((MInsnWithData)mInsn).dataTarget == null) { + Log.errorAndQuit("Bad offset calculation in data-target insn"); + } + } else if (mInsn instanceof MBranchInsn) { + ContainsTarget containsTarget = (ContainsTarget) mInsn.insn.info.format; + int targetLoc = mInsn.location + (int) containsTarget.getTarget(mInsn.insn); + ((MBranchInsn)mInsn).target = insnLocationMap.get(targetLoc); + if (((MBranchInsn)mInsn).target == null) { + Log.errorAndQuit("Bad offset calculation in branch insn"); + } + } + } + + // Now create try blocks. + if (mutatableCode.triesSize > 0) { + readTryBlocks(codeItem, mutatableCode, insnLocationMap); + } + + return mutatableCode; + } + + /** + * Given a MutatableCode item that may have been mutated, update the original CodeItem + * correctly, to allow valid DEX to be written back to the output file. + */ + public void mutatableCodeToCodeItem(CodeItem codeItem, MutatableCode mutatableCode) { + Log.debug("Translating MutatableCode " + mutatableCode.name + + " to CodeItem " + mutatableCode.codeItemIdx); + + // We must first align any data instructions at the end of the code + // before we recalculate any offsets. + // This also updates their sizes... + alignDataInstructions(mutatableCode); + + // Validate that the tracked locations for instructions are valid. + // Also mark locations as no longer being updated. + int loc = 0; + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn.insn.justRaw) { + // All just_raw instructions need alignment! + if ((loc % 2) != 0) { + loc++; + } + } + if (mInsn.location != loc) { + Log.errorAndQuit(String.format("%s does not have expected location 0x%x", + mInsn, loc)); + } + mInsn.locationUpdated = false; + loc += mInsn.insn.getSize(); + } + + // This new list will be attached to the CodeItem at the end... + List<Instruction> outputInsns = new LinkedList<Instruction>(); + + // Go through our new list of MInsns, adding them to the new + // list of instructions that will be attached to the CodeItem. + // Also recalculate offsets for branches. + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn instanceof MSwitchInsn) { + updateSwitchInstruction((MSwitchInsn)mInsn, mutatableCode); + } else if (mInsn instanceof MInsnWithData) { + MInsn target = ((MInsnWithData) mInsn).dataTarget; + int dataOffset = target.location - mInsn.location; + ContainsTarget containsTarget = (ContainsTarget) mInsn.insn.info.format; + containsTarget.setTarget(mInsn.insn, dataOffset); + } else if (mInsn instanceof MBranchInsn) { + MInsn target = ((MBranchInsn) mInsn).target; + int branchOffset = target.location - mInsn.location; + ContainsTarget containsTarget = (ContainsTarget) mInsn.insn.info.format; + containsTarget.setTarget(mInsn.insn, branchOffset); + } + outputInsns.add(mInsn.insn); + } + + // Calculate the new insns_size. + int newInsnsSize = 0; + for (Instruction insn : outputInsns) { + newInsnsSize += insn.getSize(); + } + + if (mutatableCode.triesSize > 0) { + updateTryBlocks(codeItem, mutatableCode); + } + + codeItem.insnsSize = newInsnsSize; + codeItem.insns = outputInsns; + codeItem.registersSize = mutatableCode.registersSize; + codeItem.insSize = mutatableCode.insSize; + codeItem.outsSize = mutatableCode.outsSize; + codeItem.triesSize = mutatableCode.triesSize; + } + + /** + * The TryItem specifies an offset into the EncodedCatchHandlerList for a given CodeItem, + * but we only have an array of the EncodedCatchHandlers that the List contains. + * This function produces a map that offers a way to find out the index into our array, + * from the try handler's offset. + */ + private Map<Short,Integer> createTryHandlerOffsetToIndexMap(CodeItem codeItem) { + // Create a sorted set of offsets. + List<Short> uniqueOffsets = new ArrayList<Short>(); + for (TryItem tryItem : codeItem.tries) { + int index = 0; + while (true) { + if ((index == uniqueOffsets.size()) + || (uniqueOffsets.get(index) > tryItem.handlerOff)) { + // First condition means we're at the end of the set (or we're inserting + // into an empty set) + // Second condition means that the offset belongs here + // ...so insert it here, pushing the element currently in this position to the + // right, if it exists + uniqueOffsets.add(index, tryItem.handlerOff); + break; + } else if (uniqueOffsets.get(index) == tryItem.handlerOff) { + // We've already seen it, and we're making a set, not a list. + break; + } else { + // Keep searching. + index++; + } + } + } + // Now we have an (implicit) index -> offset mapping! + + // Now create the reverse mapping. + Map<Short,Integer> offsetIndexMap = new HashMap<Short,Integer>(); + for (int i = 0; i < uniqueOffsets.size(); i++) { + offsetIndexMap.put(uniqueOffsets.get(i), i); + } + + return offsetIndexMap; + } + + private void readTryBlocks(CodeItem codeItem, MutatableCode mutatableCode, + Map<Integer,MInsn> insnLocationMap) { + mutatableCode.mutatableTries = new LinkedList<MTryBlock>(); + + Map<Short,Integer> offsetIndexMap = createTryHandlerOffsetToIndexMap(codeItem); + + // Read each TryItem into a MutatableTryBlock. + for (TryItem tryItem : codeItem.tries) { + MTryBlock mTryBlock = new MTryBlock(); + + // Get the MInsns that form the start and end of the try block. + int startLocation = tryItem.startAddr; + mTryBlock.startInsn = insnLocationMap.get(startLocation); + int endLocation = tryItem.startAddr + tryItem.insnCount; + mTryBlock.endInsn = insnLocationMap.get(endLocation); + + // Sanity checks. + if (mTryBlock.startInsn == null) { + Log.errorAndQuit(String.format( + "Couldn't find a mutatable insn at start offset 0x%x", + startLocation)); + } + if (mTryBlock.endInsn == null) { + Log.errorAndQuit(String.format( + "Couldn't find a mutatable insn at end offset 0x%x", + endLocation)); + } + + // Get the EncodedCatchHandler. + int handlerIdx = offsetIndexMap.get(tryItem.handlerOff); + EncodedCatchHandler encodedCatchHandler = codeItem.handlers.list[handlerIdx]; + + // Do we have a catch all? If so, associate the MInsn that's there. + if (encodedCatchHandler.size <= 0) { + mTryBlock.catchAllHandler = + insnLocationMap.get(encodedCatchHandler.catchAllAddr); + // Sanity check. + if (mTryBlock.catchAllHandler == null) { + Log.errorAndQuit( + String.format("Couldn't find a mutatable insn at catch-all offset 0x%x", + encodedCatchHandler.catchAllAddr)); + } + } + // Do we have explicitly-typed handlers? This will remain empty if not. + mTryBlock.handlers = new LinkedList<MInsn>(); + + // Associate all the explicitly-typed handlers. + for (int i = 0; i < Math.abs(encodedCatchHandler.size); i++) { + EncodedTypeAddrPair handler = encodedCatchHandler.handlers[i]; + MInsn handlerInsn = insnLocationMap.get(handler.addr); + // Sanity check. + if (handlerInsn == null) { + Log.errorAndQuit(String.format( + "Couldn't find a mutatable instruction at handler offset 0x%x", + handler.addr)); + } + mTryBlock.handlers.add(handlerInsn); + } + + // Now finally add the new MutatableTryBlock into this MutatableCode's list! + mutatableCode.mutatableTries.add(mTryBlock); + } + } + + private void updateTryBlocks(CodeItem codeItem, MutatableCode mutatableCode) { + + // TODO: Support ability to add extra try blocks/handlers? + + for (MTryBlock mTryBlock : mutatableCode.mutatableTries) { + if (mTryBlock.startInsn.location > mTryBlock.endInsn.location) { + // Mutation has put this try block's end insn before its start insn. Fix this. + MInsn tempInsn = mTryBlock.startInsn; + mTryBlock.startInsn = mTryBlock.endInsn; + mTryBlock.endInsn = tempInsn; + } + } + + // First, manipulate the try blocks if they overlap. + for (int i = 0; i < mutatableCode.mutatableTries.size() - 1; i++) { + MTryBlock first = mutatableCode.mutatableTries.get(i); + MTryBlock second = mutatableCode.mutatableTries.get(i + 1); + + // Do they overlap? + if (first.endInsn.location > second.startInsn.location) { + + Log.debug("Found overlap in TryBlocks, moving 2nd TryBlock..."); + Log.debug("1st TryBlock goes from " + first.startInsn + " to " + first.endInsn); + Log.debug("2nd TryBlock goes from " + second.startInsn + " to " + second.endInsn); + + // Find the first instruction that comes after that does not overlap + // with the first try block. + MInsn newInsn = second.startInsn; + int ptr = mutatableCode.getInstructionIndex(newInsn); + while (first.endInsn.location > newInsn.location) { + ptr++; + newInsn = mutatableCode.getInstructionAt(ptr); + } + second.startInsn = newInsn; + + Log.debug("Now 2nd TryBlock goes from " + second.startInsn + " to " + second.endInsn); + } + } + + Map<Short,Integer> offsetIndexMap = createTryHandlerOffsetToIndexMap(codeItem); + + int tryItemIdx = 0; + for (MTryBlock mTryBlock : mutatableCode.mutatableTries) { + TryItem tryItem = codeItem.tries[tryItemIdx]; + + tryItem.startAddr = mTryBlock.startInsn.location; + tryItem.insnCount = + (short) (mTryBlock.endInsn.location - mTryBlock.startInsn.location); + + // Get the EncodedCatchHandler. + EncodedCatchHandler encodedCatchHandler = + codeItem.handlers.list[offsetIndexMap.get(tryItem.handlerOff)]; + + if (encodedCatchHandler.size <= 0) { + encodedCatchHandler.catchAllAddr = mTryBlock.catchAllHandler.location; + } + for (int i = 0; i < Math.abs(encodedCatchHandler.size); i++) { + MInsn handlerInsn = mTryBlock.handlers.get(i); + EncodedTypeAddrPair handler = encodedCatchHandler.handlers[i]; + handler.addr = handlerInsn.location; + } + tryItemIdx++; + } + } + + /** + * Given a switch instruction, find the associated data's raw[] form, and update + * the targets of the switch instruction to point to the correct instructions. + */ + private void readSwitchInstruction(MSwitchInsn switchInsn, + Map<Integer,MInsn> insnLocationMap) { + // Find the data. + ContainsTarget containsTarget = (ContainsTarget) switchInsn.insn.info.format; + int dataLocation = switchInsn.location + (int) containsTarget.getTarget(switchInsn.insn); + switchInsn.dataTarget = insnLocationMap.get(dataLocation); + if (switchInsn.dataTarget == null) { + Log.errorAndQuit("Bad offset calculation for data target in switch insn"); + } + + // Now read the data. + Instruction dataInsn = switchInsn.dataTarget.insn; + + int rawPtr = 2; + + int targetsSize = (int) RawInsnHelper.getUnsignedShortFromTwoBytes(dataInsn.rawBytes, rawPtr); + rawPtr += 2; + + int[] keys = new int[targetsSize]; + int[] targets = new int[targetsSize]; + + if (dataInsn.rawType == 1) { + switchInsn.packed = true; + // Dealing with a packed-switch. + // Read the first key. + keys[0] = (int) RawInsnHelper.getUnsignedIntFromFourBytes(dataInsn.rawBytes, rawPtr); + rawPtr += 4; + // Calculate the rest of the keys. + for (int i = 1; i < targetsSize; i++) { + keys[i] = keys[i - 1] + 1; + } + } else if (dataInsn.rawType == 2) { + // Dealing with a sparse-switch. + // Read all of the keys. + for (int i = 0; i < targetsSize; i++) { + keys[i] = (int) RawInsnHelper.getUnsignedIntFromFourBytes(dataInsn.rawBytes, + rawPtr); + rawPtr += 4; + } + } + + // Now read the targets. + for (int i = 0; i < targetsSize; i++) { + targets[i] = (int) RawInsnHelper.getUnsignedIntFromFourBytes(dataInsn.rawBytes, + rawPtr); + rawPtr += 4; + } + + // Store the keys. + switchInsn.keys = keys; + + // Convert our targets[] offsets into pointers to MInsns. + for (int target : targets) { + int targetLocation = switchInsn.location + target; + MInsn targetInsn = insnLocationMap.get(targetLocation); + switchInsn.targets.add(targetInsn); + if (targetInsn == null) { + Log.errorAndQuit("Bad offset calculation for target in switch insn"); + } + } + } + + /** + * Given a mutatable switch instruction, which may have had some of its branch + * targets moved, update all the target offsets in the raw[] form of the instruction. + */ + private void updateSwitchInstruction(MSwitchInsn switchInsn, MutatableCode mutatableCode) { + // Update the offset to the data instruction + MInsn dataTarget = switchInsn.dataTarget; + int dataOffset = dataTarget.location - switchInsn.location; + ContainsTarget containsTarget = (ContainsTarget) switchInsn.insn.info.format; + containsTarget.setTarget(switchInsn.insn, dataOffset); + + int targetsSize = switchInsn.targets.size(); + + int[] keys = switchInsn.keys; + int[] targets = new int[targetsSize]; + + // Calculate the new offsets. + int targetIdx = 0; + for (MInsn target : switchInsn.targets) { + targets[targetIdx] = target.location - switchInsn.location; + targetIdx++; + } + + // Now write the data back to the raw bytes. + Instruction dataInsn = switchInsn.dataTarget.insn; + + int rawPtr = 2; + + // Write out the size. + RawInsnHelper.writeUnsignedShortToTwoBytes(dataInsn.rawBytes, rawPtr, targetsSize); + rawPtr += 2; + + // Write out the keys. + if (switchInsn.packed) { + // Only write out one key - the first. + RawInsnHelper.writeUnsignedIntToFourBytes(dataInsn.rawBytes, rawPtr, keys[0]); + rawPtr += 4; + } else { + // Write out all the keys. + for (int i = 0; i < targetsSize; i++) { + RawInsnHelper.writeUnsignedIntToFourBytes(dataInsn.rawBytes, rawPtr, keys[i]); + rawPtr += 4; + } + } + + // Write out all the targets. + for (int i = 0; i < targetsSize; i++) { + RawInsnHelper.writeUnsignedIntToFourBytes(dataInsn.rawBytes, rawPtr, targets[i]); + rawPtr += 4; + } + } + + /** + * After mutation, data instructions may no longer be 4-byte aligned. + * If this is the case, insert nops to align them all. + * This makes a number of assumptions about data currently: + * - data is always at the end of method insns + * - all data instructions are stored contiguously + */ + private void alignDataInstructions(MutatableCode mutatableCode) { + // Find all the switch data instructions. + List<MInsn> dataInsns = new ArrayList<MInsn>(); + + // Update raw sizes of the data instructions as well. + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn instanceof MSwitchInsn) { + // Update the raw size of the instruction. + MSwitchInsn switchInsn = (MSwitchInsn) mInsn; + int targetsSize = switchInsn.targets.size(); + Instruction dataInsn = switchInsn.dataTarget.insn; + if (switchInsn.packed) { + dataInsn.rawSize = (targetsSize * 2) + 4; + } else { + dataInsn.rawSize = (targetsSize * 4) + 2; + } + dataInsns.add(switchInsn.dataTarget); + } else if (mInsn instanceof MInsnWithData) { + MInsnWithData insnWithData = + (MInsnWithData) mInsn; + dataInsns.add(insnWithData.dataTarget); + } + } + + // Only need to align switch data instructions if there are any! + if (!dataInsns.isEmpty()) { + + Log.debug("Found data instructions, checking alignment..."); + + // Sort data_insns by location. + Collections.sort(dataInsns, new Comparator<MInsn>() { + @Override + public int compare(MInsn first, MInsn second) { + if (first.location < second.location) { + return -1; + } else if (first.location > second.location) { + return 1; + } + return 0; + } + }); + + boolean performedAlignment = false; + + // Go through all the data insns, and insert an alignment nop if they're unaligned. + for (MInsn dataInsn : dataInsns) { + if (dataInsn.location % 2 != 0) { + Log.debug("Aligning data instruction with a nop."); + int alignmentNopIdx = mutatableCode.getInstructionIndex(dataInsn); + MInsn nop = new MInsn(); + nop.insn = new Instruction(); + nop.insn.info = Instruction.getOpcodeInfo(Opcode.NOP); + mutatableCode.insertInstructionAt(nop, alignmentNopIdx); + performedAlignment = true; + } + } + + if (!performedAlignment) { + Log.debug("Alignment okay."); + } + } + } + + /** + * Determine if a particular instruction is a branch instruction, based on opcode. + */ + private boolean isInstructionBranch(Instruction insn) { + Opcode opcode = insn.info.opcode; + if (Opcode.isBetween(opcode, Opcode.IF_EQ, Opcode.IF_LEZ) + || Opcode.isBetween(opcode, Opcode.GOTO, Opcode.GOTO_32)) { + return true; + } + return false; + } + + /** + * Determine if a particular instruction is a switch instruction, based on opcode. + */ + private boolean isInstructionSwitch(Instruction insn) { + Opcode opcode = insn.info.opcode; + if (Opcode.isBetween(opcode, Opcode.PACKED_SWITCH, Opcode.SPARSE_SWITCH)) { + return true; + } + return false; + } + + private boolean isInstructionFillArrayData(Instruction insn) { + return (insn.info.opcode == Opcode.FILL_ARRAY_DATA); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/IdCreator.java b/tools/dexfuzz/src/dexfuzz/program/IdCreator.java new file mode 100644 index 0000000..c506fa6 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/IdCreator.java @@ -0,0 +1,804 @@ +/* + * 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; + +import dexfuzz.Log; +import dexfuzz.rawdex.FieldIdItem; +import dexfuzz.rawdex.MethodIdItem; +import dexfuzz.rawdex.Offset; +import dexfuzz.rawdex.Offsettable; +import dexfuzz.rawdex.ProtoIdItem; +import dexfuzz.rawdex.RawDexFile; +import dexfuzz.rawdex.RawDexObject.IndexUpdateKind; +import dexfuzz.rawdex.StringDataItem; +import dexfuzz.rawdex.StringIdItem; +import dexfuzz.rawdex.TypeIdItem; +import dexfuzz.rawdex.TypeItem; +import dexfuzz.rawdex.TypeList; + +import java.util.ArrayList; +import java.util.List; + +/** + * Responsible for the finding and creation of TypeIds, MethodIds, FieldIds, and StringIds, + * during mutation. + */ +public class IdCreator { + private RawDexFile rawDexFile; + + public IdCreator(RawDexFile rawDexFile) { + this.rawDexFile = rawDexFile; + } + + private int findProtoIdInsertionPoint(String signature) { + int returnTypeIdx = findTypeId(convertSignatureToReturnType(signature)); + String[] parameterListStrings = convertSignatureToParameterList(signature); + TypeList parameterList = null; + if (parameterListStrings.length > 0) { + parameterList = findTypeList(parameterListStrings); + } + + if (returnTypeIdx < 0) { + Log.errorAndQuit("Did not create necessary return type before finding insertion " + + "point for new proto!"); + } + + if (parameterListStrings.length > 0 && parameterList == null) { + Log.errorAndQuit("Did not create necessary parameter list before finding insertion " + + "point for new proto!"); + } + + int protoIdIdx = 0; + for (ProtoIdItem protoId : rawDexFile.protoIds) { + if (returnTypeIdx < protoId.returnTypeIdx) { + break; + } + if (returnTypeIdx == protoId.returnTypeIdx + && parameterListStrings.length == 0) { + break; + } + if (returnTypeIdx == protoId.returnTypeIdx + && parameterListStrings.length > 0 + && protoId.parametersOff.pointsToSomething() + && parameterList.comesBefore( + (TypeList) protoId.parametersOff.getPointedToItem())) { + break; + } + protoIdIdx++; + } + return protoIdIdx; + } + + private int findMethodIdInsertionPoint(String className, String methodName, String signature) { + int classIdx = findTypeId(className); + int nameIdx = findString(methodName); + int protoIdx = findProtoId(signature); + + if (classIdx < 0 || nameIdx < 0 || protoIdx < 0) { + Log.errorAndQuit("Did not create necessary class, name or proto strings before finding " + + " insertion point for new method!"); + } + + int methodIdIdx = 0; + for (MethodIdItem methodId : rawDexFile.methodIds) { + if (classIdx < methodId.classIdx) { + break; + } + if (classIdx == methodId.classIdx && nameIdx < methodId.nameIdx) { + break; + } + if (classIdx == methodId.classIdx && nameIdx == methodId.nameIdx + && protoIdx < methodId.protoIdx) { + break; + } + methodIdIdx++; + } + return methodIdIdx; + } + + private int findTypeIdInsertionPoint(String className) { + int descriptorIdx = findString(className); + + if (descriptorIdx < 0) { + Log.errorAndQuit("Did not create necessary descriptor string before finding " + + " insertion point for new type!"); + } + + int typeIdIdx = 0; + for (TypeIdItem typeId : rawDexFile.typeIds) { + if (descriptorIdx < typeId.descriptorIdx) { + break; + } + typeIdIdx++; + } + return typeIdIdx; + } + + private int findStringDataInsertionPoint(String string) { + int stringDataIdx = 0; + for (StringDataItem stringData : rawDexFile.stringDatas) { + if (stringData.getSize() > 0 && stringData.getString().compareTo(string) >= 0) { + break; + } + stringDataIdx++; + } + return stringDataIdx; + } + + private int findFieldIdInsertionPoint(String className, String typeName, String fieldName) { + int classIdx = findTypeId(className); + int typeIdx = findTypeId(typeName); + int nameIdx = findString(fieldName); + + if (classIdx < 0 || typeIdx < 0 || nameIdx < 0) { + Log.errorAndQuit("Did not create necessary class, type or name strings before finding " + + " insertion point for new field!"); + } + + int fieldIdIdx = 0; + for (FieldIdItem fieldId : rawDexFile.fieldIds) { + if (classIdx < fieldId.classIdx) { + break; + } + if (classIdx == fieldId.classIdx && nameIdx < fieldId.nameIdx) { + break; + } + if (classIdx == fieldId.classIdx && nameIdx == fieldId.nameIdx + && typeIdx < fieldId.typeIdx) { + break; + } + fieldIdIdx++; + } + return fieldIdIdx; + } + + private int createMethodId(String className, String methodName, String signature) { + if (rawDexFile.methodIds.size() >= 65536) { + Log.errorAndQuit("Referenced too many methods for the DEX file."); + } + + // Search for (or create) the prototype. + int protoIdx = findOrCreateProtoId(signature); + + // Search for (or create) the owning class. + // NB: findOrCreateProtoId could create new types, so this must come + // after it! + int typeIdIdx = findOrCreateTypeId(className); + + // Search for (or create) the string representing the method name. + // NB: findOrCreateProtoId/TypeId could create new strings, so this must come + // after them! + int methodNameStringIdx = findOrCreateString(methodName); + + // Create MethodIdItem. + MethodIdItem newMethodId = new MethodIdItem(); + newMethodId.classIdx = (short) typeIdIdx; + newMethodId.protoIdx = (short) protoIdx; + newMethodId.nameIdx = methodNameStringIdx; + + // MethodIds must be ordered. + int newMethodIdIdx = findMethodIdInsertionPoint(className, methodName, signature); + + rawDexFile.methodIds.add(newMethodIdIdx, newMethodId); + + // Insert into OffsetTracker. + if (newMethodIdIdx == 0) { + rawDexFile.getOffsetTracker() + .insertNewOffsettableAsFirstOfType(newMethodId, rawDexFile); + } else { + MethodIdItem prevMethodId = rawDexFile.methodIds.get(newMethodIdIdx - 1); + rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newMethodId, prevMethodId); + } + + Log.info(String.format("Created new MethodIdItem for %s %s %s, index: 0x%04x", + className, methodName, signature, newMethodIdIdx)); + + // Now that we've potentially moved a lot of method IDs along, all references + // to them need to be updated. + rawDexFile.incrementIndex(IndexUpdateKind.METHOD_ID, newMethodIdIdx); + + // All done, return the index for the new method. + return newMethodIdIdx; + } + + private int findMethodId(String className, String methodName, String signature) { + int classIdx = findTypeId(className); + if (classIdx == -1) { + return -1; + } + int nameIdx = findString(methodName); + if (nameIdx == -1) { + return -1; + } + int protoIdx = findProtoId(signature); + if (nameIdx == -1) { + return -1; + } + + int methodIdIdx = 0; + for (MethodIdItem methodId : rawDexFile.methodIds) { + if (classIdx == methodId.classIdx + && nameIdx == methodId.nameIdx + && protoIdx == methodId.protoIdx) { + return methodIdIdx; + } + methodIdIdx++; + } + return -1; + } + + /** + * Given a fully qualified class name (Ljava/lang/System;), method name (gc) and + * and signature (()V), either find the MethodId in our DEX file's table, or create it. + */ + public int findOrCreateMethodId(String className, String methodName, String shorty) { + int methodIdIdx = findMethodId(className, methodName, shorty); + if (methodIdIdx != -1) { + return methodIdIdx; + } + return createMethodId(className, methodName, shorty); + } + + private int createTypeId(String className) { + if (rawDexFile.typeIds.size() >= 65536) { + Log.errorAndQuit("Referenced too many classes for the DEX file."); + } + + // Search for (or create) the string representing the class descriptor. + int descriptorStringIdx = findOrCreateString(className); + + // Create TypeIdItem. + TypeIdItem newTypeId = new TypeIdItem(); + newTypeId.descriptorIdx = descriptorStringIdx; + + // TypeIds must be ordered. + int newTypeIdIdx = findTypeIdInsertionPoint(className); + + rawDexFile.typeIds.add(newTypeIdIdx, newTypeId); + + // Insert into OffsetTracker. + if (newTypeIdIdx == 0) { + rawDexFile.getOffsetTracker().insertNewOffsettableAsFirstOfType(newTypeId, rawDexFile); + } else { + TypeIdItem prevTypeId = rawDexFile.typeIds.get(newTypeIdIdx - 1); + rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newTypeId, prevTypeId); + } + + Log.info(String.format("Created new ClassIdItem for %s, index: 0x%04x", + className, newTypeIdIdx)); + + // Now that we've potentially moved a lot of type IDs along, all references + // to them need to be updated. + rawDexFile.incrementIndex(IndexUpdateKind.TYPE_ID, newTypeIdIdx); + + // All done, return the index for the new class. + return newTypeIdIdx; + } + + private int findTypeId(String className) { + int descriptorIdx = findString(className); + if (descriptorIdx == -1) { + return -1; + } + + // Search for class. + int typeIdIdx = 0; + for (TypeIdItem typeId : rawDexFile.typeIds) { + if (descriptorIdx == typeId.descriptorIdx) { + return typeIdIdx; + } + typeIdIdx++; + } + return -1; + } + + /** + * Given a fully qualified class name (Ljava/lang/System;) + * either find the TypeId in our DEX file's table, or create it. + */ + public int findOrCreateTypeId(String className) { + int typeIdIdx = findTypeId(className); + if (typeIdIdx != -1) { + return typeIdIdx; + } + return createTypeId(className); + } + + private int createString(String string) { + // Didn't find it, create one... + int stringsCount = rawDexFile.stringIds.size(); + if (stringsCount != rawDexFile.stringDatas.size()) { + Log.errorAndQuit("Corrupted DEX file, len(StringIDs) != len(StringDatas)"); + } + + // StringData must be ordered. + int newStringIdx = findStringDataInsertionPoint(string); + + // Create StringDataItem. + StringDataItem newStringData = new StringDataItem(); + newStringData.setSize(string.length()); + newStringData.setString(string); + + rawDexFile.stringDatas.add(newStringIdx, newStringData); + + // Insert into OffsetTracker. + // (Need to save the Offsettable, because the StringIdItem will point to it.) + Offsettable offsettableStringData = null; + if (newStringIdx == 0) { + offsettableStringData = + rawDexFile.getOffsetTracker() + .insertNewOffsettableAsFirstOfType(newStringData, rawDexFile); + } else { + StringDataItem prevStringData = rawDexFile.stringDatas.get(newStringIdx - 1); + offsettableStringData = rawDexFile.getOffsetTracker() + .insertNewOffsettableAfter(newStringData, prevStringData); + } + + // Create StringIdItem. + StringIdItem newStringId = new StringIdItem(); + newStringId.stringDataOff = new Offset(false); + newStringId.stringDataOff.pointToNew(offsettableStringData); + + rawDexFile.stringIds.add(newStringIdx, newStringId); + + // Insert into OffsetTracker. + if (newStringIdx == 0) { + rawDexFile.getOffsetTracker() + .insertNewOffsettableAsFirstOfType(newStringId, rawDexFile); + } else { + StringIdItem prevStringId = rawDexFile.stringIds.get(newStringIdx - 1); + rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newStringId, prevStringId); + } + + + Log.info(String.format("Created new StringIdItem and StringDataItem for %s, index: 0x%04x", + string, newStringIdx)); + + // Now that we've potentially moved a lot of string IDs along, all references + // to them need to be updated. + rawDexFile.incrementIndex(IndexUpdateKind.STRING_ID, newStringIdx); + + // All done, return the index for the new string. + return newStringIdx; + } + + private int findString(String string) { + // Search for string. + int stringIdx = 0; + for (StringDataItem stringDataItem : rawDexFile.stringDatas) { + if (stringDataItem.getSize() == 0 && string.isEmpty()) { + return stringIdx; + } else if (stringDataItem.getSize() > 0 && stringDataItem.getString().equals(string)) { + return stringIdx; + } + stringIdx++; + } + return -1; + } + + /** + * Given a string, either find the StringId in our DEX file's table, or create it. + */ + public int findOrCreateString(String string) { + int stringIdx = findString(string); + if (stringIdx != -1) { + return stringIdx; + } + return createString(string); + } + + private int createFieldId(String className, String typeName, String fieldName) { + if (rawDexFile.fieldIds.size() >= 65536) { + Log.errorAndQuit("Referenced too many fields for the DEX file."); + } + + // Search for (or create) the owning class. + int classIdx = findOrCreateTypeId(className); + + // Search for (or create) the field's type. + int typeIdx = findOrCreateTypeId(typeName); + + // The creation of the typeIdx may have changed the classIdx, search again! + classIdx = findOrCreateTypeId(className); + + // Search for (or create) the string representing the field name. + int fieldNameStringIdx = findOrCreateString(fieldName); + + // Create FieldIdItem. + FieldIdItem newFieldId = new FieldIdItem(); + newFieldId.classIdx = (short) classIdx; + newFieldId.typeIdx = (short) typeIdx; + newFieldId.nameIdx = fieldNameStringIdx; + + // FieldIds must be ordered. + int newFieldIdIdx = findFieldIdInsertionPoint(className, typeName, fieldName); + + rawDexFile.fieldIds.add(newFieldIdIdx, newFieldId); + + // Insert into OffsetTracker. + if (newFieldIdIdx == 0 && rawDexFile.fieldIds.size() == 1) { + // Special case: we didn't have any fields before! + rawDexFile.getOffsetTracker() + .insertNewOffsettableAsFirstEverField(newFieldId, rawDexFile); + } else if (newFieldIdIdx == 0) { + rawDexFile.getOffsetTracker().insertNewOffsettableAsFirstOfType(newFieldId, rawDexFile); + } else { + FieldIdItem prevFieldId = rawDexFile.fieldIds.get(newFieldIdIdx - 1); + rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newFieldId, prevFieldId); + } + + Log.info(String.format("Created new FieldIdItem for %s %s %s, index: 0x%04x", + className, typeName, fieldName, newFieldIdIdx)); + + // Now that we've potentially moved a lot of field IDs along, all references + // to them need to be updated. + rawDexFile.incrementIndex(IndexUpdateKind.FIELD_ID, newFieldIdIdx); + + // All done, return the index for the new field. + return newFieldIdIdx; + } + + private int findFieldId(String className, String typeName, String fieldName) { + int classIdx = findTypeId(className); + if (classIdx == -1) { + return -1; + } + int typeIdx = findTypeId(typeName); + if (typeIdx == -1) { + return -1; + } + int nameIdx = findString(fieldName); + if (nameIdx == -1) { + return -1; + } + + int fieldIdIdx = 0; + for (FieldIdItem fieldId : rawDexFile.fieldIds) { + if (classIdx == fieldId.classIdx + && typeIdx == fieldId.typeIdx + && nameIdx == fieldId.nameIdx) { + return fieldIdIdx; + } + fieldIdIdx++; + } + return -1; + } + + /** + * Given a field's fully qualified class name, type name, and name, + * either find the FieldId in our DEX file's table, or create it. + */ + public int findOrCreateFieldId(String className, String typeName, String fieldName) { + int fieldIdx = findFieldId(className, typeName, fieldName); + if (fieldIdx != -1) { + return fieldIdx; + } + return createFieldId(className, typeName, fieldName); + } + + /** + * Returns a 1 or 2 element String[]. If 1 element, the only element is the return type + * part of the signature. If 2 elements, the first is the parameters, the second is + * the return type. + */ + private String[] convertSignatureToParametersAndReturnType(String signature) { + if (signature.charAt(0) != '(' || !signature.contains(")")) { + Log.errorAndQuit("Invalid signature: " + signature); + } + String[] elems = signature.substring(1).split("\\)"); + return elems; + } + + private String[] convertSignatureToParameterList(String signature) { + String[] elems = convertSignatureToParametersAndReturnType(signature); + String parameters = ""; + if (elems.length == 2) { + parameters = elems[0]; + } + + List<String> parameterList = new ArrayList<String>(); + + int typePointer = 0; + while (typePointer != parameters.length()) { + if (elems[0].charAt(typePointer) == 'L') { + int start = typePointer; + // Read up to the next ; + while (elems[0].charAt(typePointer) != ';') { + typePointer++; + } + parameterList.add(parameters.substring(start, typePointer + 1)); + } else { + parameterList.add(Character.toString(parameters.charAt(typePointer))); + } + typePointer++; + } + + return parameterList.toArray(new String[]{}); + } + + private String convertSignatureToReturnType(String signature) { + String[] elems = convertSignatureToParametersAndReturnType(signature); + String returnType = ""; + if (elems.length == 1) { + returnType = elems[0]; + } else { + returnType = elems[1]; + } + + return returnType; + } + + private String convertSignatureToShorty(String signature) { + String[] elems = convertSignatureToParametersAndReturnType(signature); + + StringBuilder shortyBuilder = new StringBuilder(); + + String parameters = ""; + String returnType = ""; + + if (elems.length == 1) { + shortyBuilder.append("V"); + } else { + parameters = elems[0]; + returnType = elems[1]; + char returnChar = returnType.charAt(0); + // Arrays are references in shorties. + if (returnChar == '[') { + returnChar = 'L'; + } + shortyBuilder.append(returnChar); + } + + int typePointer = 0; + while (typePointer != parameters.length()) { + if (parameters.charAt(typePointer) == 'L') { + shortyBuilder.append('L'); + // Read up to the next ; + while (parameters.charAt(typePointer) != ';') { + typePointer++; + if (typePointer == parameters.length()) { + Log.errorAndQuit("Illegal type specified in signature - L with no ;!"); + } + } + } else if (parameters.charAt(typePointer) == '[') { + // Arrays are references in shorties. + shortyBuilder.append('L'); + // Read past all the [s + while (parameters.charAt(typePointer) == '[') { + typePointer++; + } + if (parameters.charAt(typePointer) == 'L') { + // Read up to the next ; + while (parameters.charAt(typePointer) != ';') { + typePointer++; + if (typePointer == parameters.length()) { + Log.errorAndQuit("Illegal type specified in signature - L with no ;!"); + } + } + } + } else { + shortyBuilder.append(parameters.charAt(typePointer)); + } + + typePointer++; + } + + return shortyBuilder.toString(); + } + + private Integer[] convertParameterListToTypeIdList(String[] parameterList) { + List<Integer> typeIdList = new ArrayList<Integer>(); + for (String parameter : parameterList) { + int typeIdx = findTypeId(parameter); + if (typeIdx == -1) { + return null; + } + typeIdList.add(typeIdx); + } + return typeIdList.toArray(new Integer[]{}); + } + + private TypeList createTypeList(String[] parameterList) { + TypeList typeList = new TypeList(); + List<TypeItem> typeItemList = new ArrayList<TypeItem>(); + + // This must be done as two passes, one to create all the types, + // and then one to put them in the type list. + for (String parameter : parameterList) { + findOrCreateTypeId(parameter); + } + + // Now actually put them in the list. + for (String parameter : parameterList) { + TypeItem typeItem = new TypeItem(); + typeItem.typeIdx = (short) findOrCreateTypeId(parameter); + typeItemList.add(typeItem); + } + typeList.list = typeItemList.toArray(new TypeItem[]{}); + typeList.size = typeItemList.size(); + + // Insert into OffsetTracker. + if (rawDexFile.typeLists == null) { + // Special case: we didn't have any fields before! + Log.info("Need to create first type list."); + rawDexFile.typeLists = new ArrayList<TypeList>(); + rawDexFile.getOffsetTracker() + .insertNewOffsettableAsFirstEverTypeList(typeList, rawDexFile); + } else { + TypeList prevTypeList = + rawDexFile.typeLists.get(rawDexFile.typeLists.size() - 1); + rawDexFile.getOffsetTracker().insertNewOffsettableAfter(typeList, prevTypeList); + } + + // Finally, add this new TypeList to the list of them. + rawDexFile.typeLists.add(typeList); + + return typeList; + } + + private TypeList findTypeList(String[] parameterList) { + Integer[] typeIdList = convertParameterListToTypeIdList(parameterList); + if (typeIdList == null) { + return null; + } + + if (rawDexFile.typeLists == null) { + // There's no type lists yet! + return null; + } + + for (TypeList typeList : rawDexFile.typeLists) { + if (typeList.size != typeIdList.length) { + continue; + } + + boolean found = true; + int idx = 0; + for (TypeItem typeItem : typeList.list) { + if (typeItem.typeIdx != typeIdList[idx]) { + found = false; + break; + } + idx++; + } + if (found && idx == parameterList.length) { + return typeList; + } + } + + return null; + } + + private TypeList findOrCreateTypeList(String[] parameterList) { + TypeList typeList = findTypeList(parameterList); + if (typeList != null) { + return typeList; + } + return createTypeList(parameterList); + } + + private int createProtoId(String signature) { + String shorty = convertSignatureToShorty(signature); + String returnType = convertSignatureToReturnType(signature); + String[] parameterList = convertSignatureToParameterList(signature); + + if (rawDexFile.protoIds.size() >= 65536) { + Log.errorAndQuit("Referenced too many protos for the DEX file."); + } + + TypeList typeList = null; + Offsettable typeListOffsettable = null; + + if (parameterList.length > 0) { + // Search for (or create) the parameter list. + typeList = findOrCreateTypeList(parameterList); + + typeListOffsettable = + rawDexFile.getOffsetTracker().getOffsettableForItem(typeList); + } + + // Search for (or create) the return type. + int returnTypeIdx = findOrCreateTypeId(returnType); + + // Search for (or create) the shorty string. + int shortyIdx = findOrCreateString(shorty); + + // Create ProtoIdItem. + ProtoIdItem newProtoId = new ProtoIdItem(); + newProtoId.shortyIdx = shortyIdx; + newProtoId.returnTypeIdx = returnTypeIdx; + newProtoId.parametersOff = new Offset(false); + if (parameterList.length > 0) { + newProtoId.parametersOff.pointToNew(typeListOffsettable); + } + + // ProtoIds must be ordered. + int newProtoIdIdx = findProtoIdInsertionPoint(signature); + + rawDexFile.protoIds.add(newProtoIdIdx, newProtoId); + + // Insert into OffsetTracker. + if (newProtoIdIdx == 0) { + rawDexFile.getOffsetTracker().insertNewOffsettableAsFirstOfType(newProtoId, rawDexFile); + } else { + ProtoIdItem prevProtoId = rawDexFile.protoIds.get(newProtoIdIdx - 1); + rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newProtoId, prevProtoId); + } + + Log.info(String.format("Created new ProtoIdItem for %s, index: 0x%04x", + signature, newProtoIdIdx)); + + // Now that we've potentially moved a lot of proto IDs along, all references + // to them need to be updated. + rawDexFile.incrementIndex(IndexUpdateKind.PROTO_ID, newProtoIdIdx); + + // All done, return the index for the new proto. + return newProtoIdIdx; + } + + private int findProtoId(String signature) { + String shorty = convertSignatureToShorty(signature); + String returnType = convertSignatureToReturnType(signature); + String[] parameterList = convertSignatureToParameterList(signature); + + int shortyIdx = findString(shorty); + if (shortyIdx == -1) { + return -1; + } + int returnTypeIdx = findTypeId(returnType); + if (returnTypeIdx == -1) { + return -1; + } + + // Only look for a TypeList if there's a parameter list. + TypeList typeList = null; + if (parameterList.length > 0) { + typeList = findTypeList(parameterList); + if (typeList == null) { + return -1; + } + } + + int protoIdIdx = 0; + for (ProtoIdItem protoId : rawDexFile.protoIds) { + if (parameterList.length > 0) { + // With parameters. + if (shortyIdx == protoId.shortyIdx + && returnTypeIdx == protoId.returnTypeIdx + && typeList.equals(protoId.parametersOff.getPointedToItem())) { + return protoIdIdx; + } + } else { + // Without parameters. + if (shortyIdx == protoId.shortyIdx + && returnTypeIdx == protoId.returnTypeIdx) { + return protoIdIdx; + } + } + protoIdIdx++; + } + return -1; + } + + private int findOrCreateProtoId(String signature) { + int protoIdx = findProtoId(signature); + if (protoIdx != -1) { + return protoIdx; + } + return createProtoId(signature); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/MBranchInsn.java b/tools/dexfuzz/src/dexfuzz/program/MBranchInsn.java new file mode 100644 index 0000000..ea66844 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/MBranchInsn.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * A subclass of the MInsn, that tracks its target instruction. + */ +public class MBranchInsn extends MInsn { + /** + * The MInsn this branch instruction branches to. + */ + public MInsn target; + + /** + * Clone this MBranchInsn, and clone the wrapped Instruction. + */ + public MBranchInsn clone() { + MBranchInsn newInsn = new MBranchInsn(); + newInsn.insn = insn.clone(); + newInsn.target = target; + return newInsn; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/MInsn.java b/tools/dexfuzz/src/dexfuzz/program/MInsn.java new file mode 100644 index 0000000..10f7755 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/MInsn.java @@ -0,0 +1,64 @@ +/* + * 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; + +import dexfuzz.rawdex.Instruction; + +/** + * Base class that is a thin wrapper for Instructions currently, also tracking location + * as the instruction is moved around. + */ +public class MInsn { + /** + * The raw DEX instruction that this instruction represents. + */ + public Instruction insn; + + + /** + * The location of this instruction, as an offset in code words from the beginning. + * May become invalid if instructions around it are mutated. + */ + public int location; + + /** + * Denotes if the currently associated location can be trusted. + */ + public boolean locationUpdated; + + /** + * Clone this MInsn, and clone the wrapped Instruction. + */ + public MInsn clone() { + MInsn newInsn = new MInsn(); + newInsn.insn = insn.clone(); + // It is the responsibility of the cloner to update these values. + newInsn.location = location; + newInsn.locationUpdated = locationUpdated; + return newInsn; + } + + /** + * Get the String representation of an instruction. + */ + public String toString() { + return String.format("{0x%04x%s: %s}", + location, + (locationUpdated) ? "!" : "", + insn.toString()); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/MInsnWithData.java b/tools/dexfuzz/src/dexfuzz/program/MInsnWithData.java new file mode 100644 index 0000000..ffed883 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/MInsnWithData.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * A subclass of the MInsn, that tracks the data instruction. + */ +public class MInsnWithData extends MInsn { + /** + * The MInsn that represents the data this instruction uses. + */ + public MInsn dataTarget; + + /** + * Clone this MInsnWithData, and clone the wrapped Instruction. + */ + public MInsnWithData clone() { + MInsnWithData newInsn = new MInsnWithData(); + newInsn.insn = insn.clone(); + newInsn.dataTarget = dataTarget; + return newInsn; + } +}
\ No newline at end of file diff --git a/tools/dexfuzz/src/dexfuzz/program/MSwitchInsn.java b/tools/dexfuzz/src/dexfuzz/program/MSwitchInsn.java new file mode 100644 index 0000000..d8693fe --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/MSwitchInsn.java @@ -0,0 +1,50 @@ +/* + * 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; + +import java.util.LinkedList; +import java.util.List; + +/** + * A subclass of the MInsnWithData, that also has multiple jump targets. + */ +public class MSwitchInsn extends MInsnWithData { + /** + * The MInsns this switch instruction branches to. + */ + public List<MInsn> targets = new LinkedList<MInsn>(); + + public boolean packed; + + public int[] keys; + + /** + * Clone this MSwitchInsn, and clone the wrapped Instruction. + */ + public MSwitchInsn clone() { + MSwitchInsn newInsn = new MSwitchInsn(); + newInsn.insn = insn.clone(); + newInsn.dataTarget = dataTarget; + newInsn.packed = packed; + for (MInsn target : targets) { + newInsn.targets.add(target); + } + newInsn.keys = new int[keys.length]; + System.arraycopy(keys, 0, newInsn.keys, 0, keys.length); + return newInsn; + } +}
\ No newline at end of file diff --git a/tools/dexfuzz/src/dexfuzz/program/MTryBlock.java b/tools/dexfuzz/src/dexfuzz/program/MTryBlock.java new file mode 100644 index 0000000..a1dc029 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/MTryBlock.java @@ -0,0 +1,29 @@ +/* + * 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; + +import java.util.List; + +/** + * Tracks where try blocks start and end. + */ +public class MTryBlock { + public MInsn startInsn; + public MInsn endInsn; + public List<MInsn> handlers; + public MInsn catchAllHandler; +} diff --git a/tools/dexfuzz/src/dexfuzz/program/MutatableCode.java b/tools/dexfuzz/src/dexfuzz/program/MutatableCode.java new file mode 100644 index 0000000..c56b1bc --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/MutatableCode.java @@ -0,0 +1,410 @@ +/* + * 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; + +import dexfuzz.Log; +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.Opcode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * A class that represents a CodeItem in a way that is more amenable to mutation. + */ +public class MutatableCode { + /** + * To ensure we update the correct CodeItem in the raw DEX file. + */ + public int codeItemIdx; + + /** + * This is an index into the Program's list of MutatableCodes. + */ + public int mutatableCodeIdx; + + /** + * Number of registers this code uses. + */ + public short registersSize; + + /** + * Number of ins this code has. + */ + public short insSize; + + /** + * Number of outs this code has. + */ + public short outsSize; + + /** + * Number of tries this code has. + */ + public short triesSize; + + /** + * CodeTranslator is responsible for creating this, and + * converting it back to a list of Instructions. + */ + private List<MInsn> mutatableInsns; + + /** + * CodeTranslator is responsible for creating this, and + * converting it back to the correct form for CodeItems. + */ + public List<MTryBlock> mutatableTries; + + /** + * The name of the method this code represents. + */ + public String name; + public String shorty; + public boolean isStatic; + + /** + * The Program that owns this MutatableCode. + * Currently used to get the size of constant pools for + * PoolIndexChanger/RandomInstructionGenerator + */ + public Program program; + + private short originalInVReg; + private short tempVRegsAllocated; + private short initialTempVReg; + private boolean vregsNeedCopying; + private int numMoveInsnsGenerated; + + public MutatableCode(Program program) { + this.program = program; + this.mutatableInsns = new LinkedList<MInsn>(); + } + + /** + * Call this to update all instructions after the provided mInsn, to have their + * locations adjusted by the provided offset. It will also mark that they have been updated. + */ + public void updateInstructionLocationsAfter(MInsn mInsn, int offset) { + boolean updating = false; + for (MInsn mInsnChecking : mutatableInsns) { + if (updating) { + mInsnChecking.locationUpdated = true; + mInsnChecking.location += offset; + } else { + if (mInsnChecking == mInsn) { + updating = true; + } + } + + } + } + + private void recalculateLocations() { + int loc = 0; + for (MInsn mInsn : mutatableInsns) { + mInsn.location = loc; + loc += mInsn.insn.getSize(); + } + } + + public List<MInsn> getInstructions() { + return Collections.unmodifiableList(mutatableInsns); + } + + public int getInstructionCount() { + return mutatableInsns.size(); + } + + public int getInstructionIndex(MInsn mInsn) { + return mutatableInsns.indexOf(mInsn); + } + + public MInsn getInstructionAt(int idx) { + return mutatableInsns.get(idx); + } + + public void addInstructionToEnd(MInsn mInsn) { + mutatableInsns.add(mInsn); + } + + public void insertInstructionAfter(MInsn toBeInserted, int insertionIdx) { + if ((insertionIdx + 1) < mutatableInsns.size()) { + insertInstructionAt(toBeInserted, insertionIdx + 1); + } else { + // Appending to end. + MInsn finalInsn = mutatableInsns.get(mutatableInsns.size() - 1); + toBeInserted.location = finalInsn.location + finalInsn.insn.getSize(); + mutatableInsns.add(toBeInserted); + } + } + + /** + * Has same semantics as List.add() + */ + public void insertInstructionAt(MInsn toBeInserted, int insertionIdx) { + MInsn currentInsn = mutatableInsns.get(insertionIdx); + toBeInserted.location = currentInsn.location; + mutatableInsns.add(insertionIdx , toBeInserted); + updateInstructionLocationsAfter(toBeInserted, toBeInserted.insn.getSize()); + } + + /** + * Checks if any MTryBlock's instruction refs pointed at the 'before' MInsn, + * and points them to the 'after' MInsn, if so. 'twoWay' will check if 'after' + * was pointed to, and point refs to the 'before' insn. + * (one-way is used when deleting instructions, + * two-way is used when swapping instructions.) + */ + private void updateTryBlocksWithReplacementInsn(MInsn before, MInsn after, + boolean twoWay) { + if (triesSize > 0) { + for (MTryBlock mTryBlock : mutatableTries) { + if (mTryBlock.startInsn == before) { + Log.debug("Try block's first instruction was updated"); + mTryBlock.startInsn = after; + } else if (twoWay && mTryBlock.startInsn == after) { + Log.debug("Try block's first instruction was updated"); + mTryBlock.startInsn = before; + } + if (mTryBlock.endInsn == before) { + Log.debug("Try block's last instruction was updated"); + mTryBlock.endInsn = after; + } else if (twoWay && mTryBlock.endInsn == after) { + Log.debug("Try block's last instruction was updated"); + mTryBlock.endInsn = before; + } + if (mTryBlock.catchAllHandler == before) { + Log.debug("Try block's catch-all instruction was updated"); + mTryBlock.catchAllHandler = after; + } else if (twoWay && mTryBlock.catchAllHandler == after) { + Log.debug("Try block's catch-all instruction was updated"); + mTryBlock.catchAllHandler = before; + } + + List<Integer> matchesIndicesToChange = new ArrayList<Integer>(); + List<Integer> replacementIndicesToChange = null; + if (twoWay) { + replacementIndicesToChange = new ArrayList<Integer>(); + } + + int idx = 0; + for (MInsn handler : mTryBlock.handlers) { + if (handler == before) { + matchesIndicesToChange.add(idx); + Log.debug("Try block's handler instruction was updated"); + } else if (twoWay && handler == after) { + replacementIndicesToChange.add(idx); + Log.debug("Try block's handler instruction was updated"); + } + idx++; + } + + for (int idxToChange : matchesIndicesToChange) { + mTryBlock.handlers.set(idxToChange, after); + } + + if (twoWay) { + for (int idxToChange : replacementIndicesToChange) { + mTryBlock.handlers.set(idxToChange, before); + } + } + } + } + } + + /** + * The actual implementation of deleteInstruction called by + * the single-argument deleteInstructions. + */ + private void deleteInstructionFull(MInsn toBeDeleted, int toBeDeletedIdx) { + // Make sure we update all locations afterwards first. + updateInstructionLocationsAfter(toBeDeleted, -(toBeDeleted.insn.getSize())); + + // Remove it. + mutatableInsns.remove(toBeDeletedIdx); + + // Update any branch instructions that branched to the instruction we just deleted! + + // First, pick the replacement target. + int replacementTargetIdx = toBeDeletedIdx; + if (replacementTargetIdx == mutatableInsns.size()) { + replacementTargetIdx--; + } + MInsn replacementTarget = mutatableInsns.get(replacementTargetIdx); + + for (MInsn mInsn : mutatableInsns) { + if (mInsn instanceof MBranchInsn) { + // Check if this branch insn points at the insn we just deleted. + MBranchInsn branchInsn = (MBranchInsn) mInsn; + MInsn target = branchInsn.target; + if (target == toBeDeleted) { + Log.debug(branchInsn + " was pointing at the deleted instruction, updated."); + branchInsn.target = replacementTarget; + } + } else if (mInsn instanceof MSwitchInsn) { + // Check if any of this switch insn's targets points at the insn we just deleted. + MSwitchInsn switchInsn = (MSwitchInsn) mInsn; + List<Integer> indicesToChange = new ArrayList<Integer>(); + int idx = 0; + for (MInsn target : switchInsn.targets) { + if (target == toBeDeleted) { + indicesToChange.add(idx); + Log.debug(switchInsn + "[" + idx + + "] was pointing at the deleted instruction, updated."); + } + idx++; + } + for (int idxToChange : indicesToChange) { + switchInsn.targets.remove(idxToChange); + switchInsn.targets.add(idxToChange, replacementTarget); + } + } + } + + // Now update the try blocks. + updateTryBlocksWithReplacementInsn(toBeDeleted, replacementTarget, false); + } + + /** + * Delete the provided MInsn. + */ + public void deleteInstruction(MInsn toBeDeleted) { + deleteInstructionFull(toBeDeleted, mutatableInsns.indexOf(toBeDeleted)); + } + + /** + * Delete the MInsn at the provided index. + */ + public void deleteInstruction(int toBeDeletedIdx) { + deleteInstructionFull(mutatableInsns.get(toBeDeletedIdx), toBeDeletedIdx); + } + + public void swapInstructionsByIndex(int aIdx, int bIdx) { + MInsn aInsn = mutatableInsns.get(aIdx); + MInsn bInsn = mutatableInsns.get(bIdx); + + mutatableInsns.set(aIdx, bInsn); + mutatableInsns.set(bIdx, aInsn); + + updateTryBlocksWithReplacementInsn(aInsn, bInsn, true); + + recalculateLocations(); + } + + /** + * Some mutators may require the use of temporary registers. For instance, + * to easily add in printing of values without having to look for registers + * that aren't currently live. + * The idea is to allocate these registers at the top of the set of registers. + * Because this will then shift where the arguments to the method are, we then + * change the start of the method to copy the arguments to the method + * into the place where the rest of the method's code expects them to be. + * Call allocateTemporaryVRegs(n), then use getTemporaryVReg(n), + * and then make sure finishedUsingTemporaryVRegs() is called! + */ + public void allocateTemporaryVRegs(int count) { + if (count > tempVRegsAllocated) { + if (tempVRegsAllocated == 0) { + Log.info("Allocating temporary vregs for method..."); + initialTempVReg = registersSize; + originalInVReg = (short) (registersSize - insSize); + } else { + Log.info("Extending allocation of temporary vregs for method..."); + } + registersSize = (short) (initialTempVReg + count); + if (outsSize < count) { + outsSize = (short) count; + } + vregsNeedCopying = true; + tempVRegsAllocated = (short) count; + } + } + + public int getTemporaryVReg(int number) { + if (number >= tempVRegsAllocated) { + Log.errorAndQuit("Not allocated enough temporary vregs!"); + } + return initialTempVReg + number; + } + + public void finishedUsingTemporaryVRegs() { + if (tempVRegsAllocated > 0 && vregsNeedCopying) { + // Just delete all the move instructions and generate again, if we already have some. + while (numMoveInsnsGenerated > 0) { + deleteInstruction(0); + numMoveInsnsGenerated--; + } + + Log.info("Moving 'in' vregs to correct locations after allocating temporary vregs"); + + int shortyIdx = 0; + if (isStatic) { + shortyIdx = 1; + } + + int insertionCounter = 0; + + // Insert copy insns that move all the in VRs down. + for (int i = 0; i < insSize; i++) { + MInsn moveInsn = new MInsn(); + moveInsn.insn = new Instruction(); + moveInsn.insn.vregA = originalInVReg + i; + moveInsn.insn.vregB = originalInVReg + i + tempVRegsAllocated; + + char type = 'L'; + if (shortyIdx > 0) { + type = shorty.charAt(shortyIdx); + } + shortyIdx++; + + if (type == 'L') { + moveInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_OBJECT_16); + } else if (type == 'D' || type == 'J') { + moveInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_WIDE_16); + i++; + } else { + moveInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_16); + } + + insertInstructionAt(moveInsn, insertionCounter); + insertionCounter++; + Log.info("Temp vregs creation, Added instruction " + moveInsn); + numMoveInsnsGenerated++; + } + + vregsNeedCopying = false; + } + } + + /** + * When we insert new Field/Type/MethodIds into the DEX file, this may shunt some Ids + * into a new position in the table. If this happens, every reference to the Ids must + * be updated! Because CodeItems have their Instructions wrapped into a graph of MInsns + * during mutation, they don't have a view of all their instructions during mutation, + * and so if they are asked to update their instructions' indices into the tables, they + * must call this method to get the actual list of instructions they currently own. + */ + public List<Instruction> requestLatestInstructions() { + List<Instruction> latestInsns = new ArrayList<Instruction>(); + for (MInsn mInsn : mutatableInsns) { + latestInsns.add(mInsn.insn); + } + return latestInsns; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/Mutation.java b/tools/dexfuzz/src/dexfuzz/program/Mutation.java new file mode 100644 index 0000000..2eba718 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/Mutation.java @@ -0,0 +1,46 @@ +/* + * 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; + +import dexfuzz.program.mutators.CodeMutator; + +/** + * Mutation should be subclassed by an AssociatedMutation in each CodeMutator, + * which will describe the parameters of the mutation, and override the getString() + * and parseString() methods here to allow serialization of the mutations. + */ +public abstract class Mutation { + + public MutatableCode mutatableCode; + + // The first field of any serialized mutation - the mutator that uses it. + public Class<? extends CodeMutator> mutatorClass; + // The second field of any serialized mutation... + // This is an index into the Program's list of MutatableCodes + // i.e., it is NOT an index into the DEX file's CodeItems! + public int mutatableCodeIdx; + + public void setup(Class<? extends CodeMutator> mutatorClass, MutatableCode mutatableCode) { + this.mutatorClass = mutatorClass; + this.mutatableCode = mutatableCode; + this.mutatableCodeIdx = mutatableCode.mutatableCodeIdx; + } + + public abstract String getString(); + + public abstract void parseString(String[] elements); +}
\ No newline at end of file diff --git a/tools/dexfuzz/src/dexfuzz/program/MutationSerializer.java b/tools/dexfuzz/src/dexfuzz/program/MutationSerializer.java new file mode 100644 index 0000000..7f79517 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/MutationSerializer.java @@ -0,0 +1,95 @@ +/* + * 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; + +import dexfuzz.Log; +import dexfuzz.program.mutators.CodeMutator; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; + +/** + * Responsible for serializing mutations, allowing replay of mutations, and searching + * for a minimal set of mutations. + */ +public class MutationSerializer { + public static String getMutationString(Mutation mutation) { + StringBuilder builder = new StringBuilder(); + builder.append(mutation.mutatorClass.getCanonicalName()).append(" "); + builder.append(mutation.mutatableCodeIdx).append(" "); + builder.append(mutation.getString()); + return builder.toString(); + } + + public static void writeMutation(BufferedWriter writer, Mutation mutation) throws IOException { + // Write out the common fields. + writer.write(mutation.mutatorClass.getCanonicalName() + " " + + mutation.mutatableCodeIdx + " "); + + // Use the mutation's own function to write out the rest of the fields. + writer.write(mutation.getString() + "\n"); + } + + @SuppressWarnings("unchecked") + public static Mutation readMutation(BufferedReader reader) throws IOException { + String line = reader.readLine(); + String[] fields = null; + if (line != null) { + fields = line.split(" "); + } else { + Log.errorAndQuit("Could not read line during mutation loading."); + } + + // Read the mutator's class name + String mutatorClassName = fields[0]; + + // Get the class for that mutator + Class<? extends CodeMutator> mutatorClass = null; + try { + mutatorClass = (Class<? extends CodeMutator>) Class.forName(mutatorClassName); + } catch (ClassNotFoundException e) { + Log.errorAndQuit("Cannot find a mutator class called: " + mutatorClassName); + } + + Mutation mutation = null; + try { + mutation = mutatorClass.newInstance().getNewMutation(); + } catch (InstantiationException e) { + Log.errorAndQuit("Unable to instantiate " + mutatorClassName + + " using default constructor."); + } catch (IllegalAccessException e) { + Log.errorAndQuit("Unable to access methods in " + mutatorClassName + "."); + } + + if (mutation == null) { + Log.errorAndQuit("Unable to get Mutation for Mutator: " + mutatorClassName); + } + + // Populate the common fields of the mutation. + mutation.mutatorClass = mutatorClass; + // The Program must set this later, using the mutatable_code_idx + // into its list of MutatableCodes. + mutation.mutatableCode = null; + mutation.mutatableCodeIdx = Integer.parseInt(fields[1]); + + // Use the mutation's own method to read the rest of the fields. + mutation.parseString(fields); + + return mutation; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/Program.java b/tools/dexfuzz/src/dexfuzz/program/Program.java new file mode 100644 index 0000000..286fe52 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/Program.java @@ -0,0 +1,602 @@ +/* + * 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; + +import dexfuzz.Log; +import dexfuzz.MutationStats; +import dexfuzz.Options; +import dexfuzz.listeners.BaseListener; +import dexfuzz.program.mutators.ArithOpChanger; +import dexfuzz.program.mutators.BranchShifter; +import dexfuzz.program.mutators.CmpBiasChanger; +import dexfuzz.program.mutators.CodeMutator; +import dexfuzz.program.mutators.ConstantValueChanger; +import dexfuzz.program.mutators.ConversionRepeater; +import dexfuzz.program.mutators.FieldFlagChanger; +import dexfuzz.program.mutators.InstructionDeleter; +import dexfuzz.program.mutators.InstructionDuplicator; +import dexfuzz.program.mutators.InstructionSwapper; +import dexfuzz.program.mutators.NewMethodCaller; +import dexfuzz.program.mutators.NonsenseStringPrinter; +import dexfuzz.program.mutators.PoolIndexChanger; +import dexfuzz.program.mutators.RandomInstructionGenerator; +import dexfuzz.program.mutators.SwitchBranchShifter; +import dexfuzz.program.mutators.TryBlockShifter; +import dexfuzz.program.mutators.ValuePrinter; +import dexfuzz.program.mutators.VRegChanger; +import dexfuzz.rawdex.ClassDataItem; +import dexfuzz.rawdex.ClassDefItem; +import dexfuzz.rawdex.CodeItem; +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.EncodedField; +import dexfuzz.rawdex.EncodedMethod; +import dexfuzz.rawdex.FieldIdItem; +import dexfuzz.rawdex.MethodIdItem; +import dexfuzz.rawdex.ProtoIdItem; +import dexfuzz.rawdex.RawDexFile; +import dexfuzz.rawdex.TypeIdItem; +import dexfuzz.rawdex.formats.ContainsPoolIndex.PoolIndexKind; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +/** + * After the raw DEX file has been parsed, it is passed into this class + * that represents the program in a mutatable form. + * The class uses a CodeTranslator to translate between the raw DEX form + * for a method, and the mutatable form. It also controls all CodeMutators, + * deciding which ones should be applied to each CodeItem. + */ +public class Program { + /** + * The RNG used during mutation. + */ + private Random rng; + + /** + * The seed that was given to the RNG. + */ + public long rngSeed; + + /** + * The parsed raw DEX file. + */ + private RawDexFile rawDexFile; + + /** + * The system responsible for translating from CodeItems to MutatableCode and vice-versa. + */ + private CodeTranslator translator; + + /** + * Responsible for adding new class ID items, method ID items, etc. + */ + private IdCreator idCreator; + + /** + * A list of all the MutatableCode that the CodeTranslator produced from + * CodeItems that are acceptable to mutate. + */ + private List<MutatableCode> mutatableCodes; + + /** + * A list of all MutatableCode items that were mutated when mutateTheProgram() + * was called. updateRawDexFile() will update the relevant CodeItems when called, + * and then clear this list. + */ + private List<MutatableCode> mutatedCodes; + + /** + * A list of all registered CodeMutators that this Program can use to mutate methods. + */ + private List<CodeMutator> mutators; + + /** + * Used if we're loading mutations from a file, so we can find the correct mutator. + */ + private Map<Class<? extends CodeMutator>, CodeMutator> mutatorsLookupByClass; + + /** + * Tracks mutation stats. + */ + private MutationStats mutationStats; + + /** + * A list of all mutations used for loading/dumping mutations from/to a file. + */ + private List<Mutation> mutations; + + /** + * The listener who is interested in events. + * May be a listener that is responsible for multiple listeners. + */ + private BaseListener listener; + + /** + * Given a maximum number of mutations that can be performed on a method, n, + * give up after attempting (n * this value) mutations for any method. + */ + private static final int MAXIMUM_MUTATION_ATTEMPT_FACTOR = 10; + + /** + * Construct the mutatable Program based on the raw DEX file that was parsed initially. + */ + public Program(RawDexFile rawDexFile, List<Mutation> previousMutations, + BaseListener listener) { + this.listener = listener; + + idCreator = new IdCreator(rawDexFile); + + // Set up the RNG. + rng = new Random(); + if (Options.usingProvidedSeed) { + rng.setSeed(Options.rngSeed); + rngSeed = Options.rngSeed; + } else { + long seed = System.currentTimeMillis(); + listener.handleSeed(seed); + rng.setSeed(seed); + rngSeed = seed; + } + + if (previousMutations != null) { + mutations = previousMutations; + } else { + // Allocate the mutations list. + mutations = new ArrayList<Mutation>(); + + // Read in the mutations if we need to. + if (Options.loadMutations) { + // Allocate the mutators lookup table. + mutatorsLookupByClass = new HashMap<Class<? extends CodeMutator>, CodeMutator>(); + loadMutationsFromDisk(Options.loadMutationsFile); + } + } + + // Allocate the mutators list. + mutators = new ArrayList<CodeMutator>(); + + this.rawDexFile = rawDexFile; + + mutatableCodes = new ArrayList<MutatableCode>(); + mutatedCodes = new ArrayList<MutatableCode>(); + + translator = new CodeTranslator(); + + mutationStats = new MutationStats(); + + // Register all the code mutators here. + registerMutator(new ArithOpChanger(rng, mutationStats, mutations)); + registerMutator(new BranchShifter(rng, mutationStats, mutations)); + registerMutator(new CmpBiasChanger(rng, mutationStats, mutations)); + registerMutator(new ConstantValueChanger(rng, mutationStats, mutations)); + registerMutator(new ConversionRepeater(rng, mutationStats, mutations)); + registerMutator(new FieldFlagChanger(rng, mutationStats, mutations)); + registerMutator(new InstructionDeleter(rng, mutationStats, mutations)); + registerMutator(new InstructionDuplicator(rng, mutationStats, mutations)); + registerMutator(new InstructionSwapper(rng, mutationStats, mutations)); + registerMutator(new NewMethodCaller(rng, mutationStats, mutations)); + registerMutator(new NonsenseStringPrinter(rng, mutationStats, mutations)); + registerMutator(new PoolIndexChanger(rng, mutationStats, mutations)); + registerMutator(new RandomInstructionGenerator(rng, mutationStats, mutations)); + registerMutator(new SwitchBranchShifter(rng, mutationStats, mutations)); + registerMutator(new TryBlockShifter(rng, mutationStats, mutations)); + registerMutator(new ValuePrinter(rng, mutationStats, mutations)); + registerMutator(new VRegChanger(rng, mutationStats, mutations)); + + associateClassDefsAndClassData(); + associateCodeItemsWithMethodNames(); + + int codeItemIdx = 0; + for (CodeItem codeItem : rawDexFile.codeItems) { + if (legalToMutate(codeItem)) { + Log.debug("Legal to mutate code item " + codeItemIdx); + int mutatableCodeIdx = mutatableCodes.size(); + mutatableCodes.add(translator.codeItemToMutatableCode(this, codeItem, + codeItemIdx, mutatableCodeIdx)); + } else { + Log.debug("Not legal to mutate code item " + codeItemIdx); + } + codeItemIdx++; + } + } + + private void registerMutator(CodeMutator mutator) { + if (mutator.canBeTriggered()) { + Log.debug("Registering mutator " + mutator.getClass().getSimpleName()); + mutators.add(mutator); + } + if (Options.loadMutations) { + mutatorsLookupByClass.put(mutator.getClass(), mutator); + } + } + + /** + * Associate ClassDefItem to a ClassDataItem and vice-versa. + * This is so when we're associating method names with code items, + * we can find the name of the class the method belongs to. + */ + private void associateClassDefsAndClassData() { + for (ClassDefItem classDefItem : rawDexFile.classDefs) { + if (classDefItem.classDataOff.pointsToSomething()) { + ClassDataItem classDataItem = (ClassDataItem) + classDefItem.classDataOff.getPointedToItem(); + classDataItem.meta.classDefItem = classDefItem; + classDefItem.meta.classDataItem = classDataItem; + } + } + } + + /** + * For each CodeItem, find the name of the method the item represents. + * This is done to allow the filtering of mutating methods based on if + * they have the name *_MUTATE, but also for debugging info. + */ + private void associateCodeItemsWithMethodNames() { + // Associate method names with codeItems. + for (ClassDataItem classDataItem : rawDexFile.classDatas) { + + String className = ""; + if (classDataItem.meta.classDefItem != null) { + int typeIdx = classDataItem.meta.classDefItem.classIdx; + TypeIdItem typeIdItem = rawDexFile.typeIds.get(typeIdx); + className = rawDexFile.stringDatas.get(typeIdItem.descriptorIdx).getString() + "."; + } + + // Do direct methods... + // Track the current method index with this value, since the encoding in + // each EncodedMethod is the absolute index for the first EncodedMethod, + // and then relative index for the rest... + int methodIdx = 0; + for (EncodedMethod method : classDataItem.directMethods) { + methodIdx = associateMethod(method, methodIdx, className); + } + // Reset methodIdx for virtual methods... + methodIdx = 0; + for (EncodedMethod method : classDataItem.virtualMethods) { + methodIdx = associateMethod(method, methodIdx, className); + } + } + } + + /** + * Associate the name of the provided method with its CodeItem, if it + * has one. + * + * @param methodIdx The method index of the last EncodedMethod that was handled in this class. + * @return The method index of the EncodedMethod that has just been handled in this class. + */ + private int associateMethod(EncodedMethod method, int methodIdx, String className) { + if (!method.codeOff.pointsToSomething()) { + // This method doesn't have a code item, so we won't encounter it later. + return methodIdx; + } + + // First method index is an absolute index. + // The rest are relative to the previous. + // (so if methodIdx is initialised to 0, this single line works) + methodIdx = methodIdx + method.methodIdxDiff; + + // Get the name. + MethodIdItem methodIdItem = rawDexFile.methodIds.get(methodIdx); + ProtoIdItem protoIdItem = rawDexFile.protoIds.get(methodIdItem.protoIdx); + String shorty = rawDexFile.stringDatas.get(protoIdItem.shortyIdx).getString(); + String methodName = className + + rawDexFile.stringDatas.get(methodIdItem.nameIdx).getString(); + + // Get the codeItem. + if (method.codeOff.getPointedToItem() instanceof CodeItem) { + CodeItem codeItem = (CodeItem) method.codeOff.getPointedToItem(); + codeItem.meta.methodName = methodName; + codeItem.meta.shorty = shorty; + codeItem.meta.isStatic = method.isStatic(); + } else { + Log.errorAndQuit("You've got an EncodedMethod that points to an Offsettable" + + " that does not contain a CodeItem"); + } + + return methodIdx; + } + + /** + * Determine, based on the current options supplied to dexfuzz, as well as + * its capabilities, if the provided CodeItem can be mutated. + * @param codeItem The CodeItem we may wish to mutate. + * @return If the CodeItem can be mutated. + */ + private boolean legalToMutate(CodeItem codeItem) { + if (!Options.mutateLimit) { + Log.debug("Mutating everything."); + return true; + } + if (codeItem.meta.methodName.endsWith("_MUTATE")) { + Log.debug("Code item marked with _MUTATE."); + return true; + } + Log.debug("Code item not marked with _MUTATE, but not mutating all code items."); + return false; + } + + private int getNumberOfMutationsToPerform() { + // We want n mutations to be twice as likely as n+1 mutations. + // + // So if we have max 3, + // then 0 has 8 chances ("tickets"), + // 1 has 4 chances + // 2 has 2 chances + // and 3 has 1 chance + + // Allocate the tickets + // n mutations need (2^(n+1) - 1) tickets + // e.g. + // 3 mutations => 15 tickets + // 4 mutations => 31 tickets + int tickets = (2 << Options.methodMutations) - 1; + + // Pick the lucky ticket + int luckyTicket = rng.nextInt(tickets); + + // The tickets are put into buckets with accordance with log-base-2. + // have to make sure it's luckyTicket + 1, because log(0) is undefined + // so: + // log_2(1) => 0 + // log_2(2) => 1 + // log_2(3) => 1 + // log_2(4) => 2 + // log_2(5) => 2 + // log_2(6) => 2 + // log_2(7) => 2 + // log_2(8) => 3 + // ... + // so to make the highest mutation value the rarest, + // subtract log_2(luckyTicket+1) from the maximum number + // log2(x) <=> 31 - Integer.numberOfLeadingZeros(x) + int luckyMutation = Options.methodMutations + - (31 - Integer.numberOfLeadingZeros(luckyTicket + 1)); + + return luckyMutation; + } + + /** + * Returns true if we completely failed to mutate this method's mutatable code after + * attempting to. + */ + private boolean mutateAMutatableCode(MutatableCode mutatableCode) { + int mutations = getNumberOfMutationsToPerform(); + + Log.info("Attempting " + mutations + " mutations for method " + mutatableCode.name); + + int mutationsApplied = 0; + + int maximumMutationAttempts = Options.methodMutations * MAXIMUM_MUTATION_ATTEMPT_FACTOR; + int mutationAttempts = 0; + boolean hadToBail = false; + + while (mutationsApplied < mutations) { + int mutatorIdx = rng.nextInt(mutators.size()); + CodeMutator mutator = mutators.get(mutatorIdx); + Log.info("Running mutator " + mutator.getClass().getSimpleName()); + if (mutator.attemptToMutate(mutatableCode)) { + mutationsApplied++; + } + mutationAttempts++; + if (mutationAttempts > maximumMutationAttempts) { + Log.info("Bailing out on mutation for this method, tried too many times..."); + hadToBail = true; + break; + } + } + + // If any of them actually mutated it, excellent! + if (mutationsApplied > 0) { + Log.info("Method was mutated."); + mutatedCodes.add(mutatableCode); + } else { + Log.info("Method was not mutated."); + } + + return ((mutationsApplied == 0) && hadToBail); + } + + /** + * Go through each mutatable method in turn, and attempt to mutate it. + * Afterwards, call updateRawDexFile() to apply the results of mutation to the + * original code. + */ + public void mutateTheProgram() { + if (Options.loadMutations) { + applyMutationsFromList(); + return; + } + + // Typically, this is 2 to 10... + int methodsToMutate = Options.minMethods + + rng.nextInt((Options.maxMethods - Options.minMethods) + 1); + + // Check we aren't trying to mutate more methods than we have. + if (methodsToMutate > mutatableCodes.size()) { + methodsToMutate = mutatableCodes.size(); + } + + // Check if we're going to end up mutating all the methods. + if (methodsToMutate == mutatableCodes.size()) { + // Just do them all in order. + Log.info("Mutating all possible methods."); + for (MutatableCode mutatableCode : mutatableCodes) { + if (mutatableCode == null) { + Log.errorAndQuit("Why do you have a null MutatableCode?"); + } + mutateAMutatableCode(mutatableCode); + } + Log.info("Finished mutating all possible methods."); + } else { + // Pick them at random. + Log.info("Randomly selecting " + methodsToMutate + " methods to mutate."); + while (mutatedCodes.size() < methodsToMutate) { + int randomMethodIdx = rng.nextInt(mutatableCodes.size()); + MutatableCode mutatableCode = mutatableCodes.get(randomMethodIdx); + if (mutatableCode == null) { + Log.errorAndQuit("Why do you have a null MutatableCode?"); + } + if (!mutatedCodes.contains(mutatableCode)) { + boolean completelyFailedToMutate = mutateAMutatableCode(mutatableCode); + if (completelyFailedToMutate) { + methodsToMutate--; + } + } + } + Log.info("Finished mutating the methods."); + } + + listener.handleMutationStats(mutationStats.getStatsString()); + + if (Options.dumpMutations) { + writeMutationsToDisk(Options.dumpMutationsFile); + } + } + + private void writeMutationsToDisk(String fileName) { + Log.debug("Writing mutations to disk."); + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(fileName)); + for (Mutation mutation : mutations) { + MutationSerializer.writeMutation(writer, mutation); + } + writer.close(); + } catch (IOException e) { + Log.errorAndQuit("IOException while writing mutations to disk..."); + } + } + + private void loadMutationsFromDisk(String fileName) { + Log.debug("Loading mutations from disk."); + try { + BufferedReader reader = new BufferedReader(new FileReader(fileName)); + while (reader.ready()) { + Mutation mutation = MutationSerializer.readMutation(reader); + mutations.add(mutation); + } + reader.close(); + } catch (IOException e) { + Log.errorAndQuit("IOException while loading mutations from disk..."); + } + } + + private void applyMutationsFromList() { + Log.info("Applying preloaded list of mutations..."); + for (Mutation mutation : mutations) { + // Repopulate the MutatableCode field from the recorded index into the Program's list. + mutation.mutatableCode = mutatableCodes.get(mutation.mutatableCodeIdx); + + // Get the right mutator. + CodeMutator mutator = mutatorsLookupByClass.get(mutation.mutatorClass); + + // Apply the mutation. + mutator.forceMutate(mutation); + + // Add this mutatable code to the list of mutated codes, if we haven't already. + if (!mutatedCodes.contains(mutation.mutatableCode)) { + mutatedCodes.add(mutation.mutatableCode); + } + } + Log.info("...finished applying preloaded list of mutations."); + } + + public List<Mutation> getMutations() { + return mutations; + } + + /** + * Updates any CodeItems that need to be updated after mutation. + */ + public boolean updateRawDexFile() { + boolean anythingMutated = !(mutatedCodes.isEmpty()); + for (MutatableCode mutatedCode : mutatedCodes) { + translator.mutatableCodeToCodeItem(rawDexFile.codeItems + .get(mutatedCode.codeItemIdx), mutatedCode); + } + mutatedCodes.clear(); + return anythingMutated; + } + + public void writeRawDexFile(DexRandomAccessFile file) throws IOException { + rawDexFile.write(file); + } + + public void updateRawDexFileHeader(DexRandomAccessFile file) throws IOException { + rawDexFile.updateHeader(file); + } + + /** + * Used by the CodeMutators to determine legal index values. + */ + public int getTotalPoolIndicesByKind(PoolIndexKind poolIndexKind) { + switch (poolIndexKind) { + case Type: + return rawDexFile.typeIds.size(); + case Field: + return rawDexFile.fieldIds.size(); + case String: + return rawDexFile.stringIds.size(); + case Method: + return rawDexFile.methodIds.size(); + case Invalid: + return 0; + default: + } + return 0; + } + + /** + * Used by the CodeMutators to lookup and/or create Ids. + */ + public IdCreator getNewItemCreator() { + return idCreator; + } + + /** + * Used by FieldFlagChanger, to find an EncodedField for a specified field in an insn, + * if that field is actually defined in this DEX file. If not, null is returned. + */ + public EncodedField getEncodedField(int fieldIdx) { + if (fieldIdx >= rawDexFile.fieldIds.size()) { + Log.debug(String.format("Field idx 0x%x specified is not defined in this DEX file.", + fieldIdx)); + return null; + } + FieldIdItem fieldId = rawDexFile.fieldIds.get(fieldIdx); + + for (ClassDefItem classDef : rawDexFile.classDefs) { + if (classDef.classIdx == fieldId.classIdx) { + ClassDataItem classData = classDef.meta.classDataItem; + return classData.getEncodedFieldWithIndex(fieldIdx); + } + } + + Log.debug(String.format("Field idx 0x%x specified is not defined in this DEX file.", + fieldIdx)); + return null; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/ArithOpChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/ArithOpChanger.java new file mode 100644 index 0000000..4c69694 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/ArithOpChanger.java @@ -0,0 +1,290 @@ +/* + * 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 dexfuzz.rawdex.OpcodeInfo; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class ArithOpChanger 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 arithmeticInsnIdx; + public int newOpcode; + + @Override + public String getString() { + StringBuilder builder = new StringBuilder(); + builder.append(arithmeticInsnIdx).append(" "); + builder.append(newOpcode); + return builder.toString(); + } + + @Override + public void parseString(String[] elements) { + arithmeticInsnIdx = Integer.parseInt(elements[2]); + newOpcode = Integer.parseInt(elements[3]); + } + } + + // 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 ArithOpChanger() { } + + public ArithOpChanger(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 75; + } + + // A cache that should only exist between generateMutation() and applyMutation(), + // or be created at the start of applyMutation(), if we're reading in mutations from + // a file. + private List<MInsn> arithmeticInsns = null; + + private void generateCachedArithmeticInsns(MutatableCode mutatableCode) { + if (arithmeticInsns != null) { + return; + } + + arithmeticInsns = new ArrayList<MInsn>(); + + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isArithmeticOperation(mInsn)) { + arithmeticInsns.add(mInsn); + } + } + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isArithmeticOperation(mInsn)) { + return true; + } + } + + Log.debug("No arithmetic operations in method, skipping..."); + return false; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + generateCachedArithmeticInsns(mutatableCode); + + int arithmeticInsnIdx = rng.nextInt(arithmeticInsns.size()); + + MInsn randomInsn = arithmeticInsns.get(arithmeticInsnIdx); + + OpcodeInfo oldOpcodeInfo = randomInsn.insn.info; + + OpcodeInfo newOpcodeInfo = oldOpcodeInfo; + + while (newOpcodeInfo.value == oldOpcodeInfo.value) { + newOpcodeInfo = Instruction.getOpcodeInfo(getLegalDifferentOpcode(randomInsn)); + } + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.arithmeticInsnIdx = arithmeticInsnIdx; + mutation.newOpcode = newOpcodeInfo.value; + 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; + + generateCachedArithmeticInsns(mutatableCode); + + MInsn randomInsn = arithmeticInsns.get(mutation.arithmeticInsnIdx); + + String oldInsnString = randomInsn.toString(); + + OpcodeInfo newOpcodeInfo = Instruction.getOpcodeInfo(mutation.newOpcode); + + randomInsn.insn.info = newOpcodeInfo; + + Log.info("Changed " + oldInsnString + " to " + randomInsn); + + stats.incrementStat("Changed arithmetic opcode"); + + // Clear the cache. + arithmeticInsns = null; + } + + private boolean isArithmeticOperation(MInsn mInsn) { + Opcode opcode = mInsn.insn.info.opcode; + if (Opcode.isBetween(opcode, Opcode.ADD_INT, Opcode.USHR_INT_LIT8)) { + return true; + } + return false; + } + + private Opcode getLegalDifferentOpcode(MInsn mInsn) { + Opcode opcode = mInsn.insn.info.opcode; + + for (List<Opcode> opcodeList : opcodeLists) { + Opcode first = opcodeList.get(0); + Opcode last = opcodeList.get(opcodeList.size() - 1); + if (Opcode.isBetween(opcode, first, last)) { + int newOpcodeIdx = rng.nextInt(opcodeList.size()); + return opcodeList.get(newOpcodeIdx); + } + } + + return opcode; + } + + private static List<Opcode> intOpcodes = new ArrayList<Opcode>(); + private static List<Opcode> int2addrOpcodes = new ArrayList<Opcode>(); + private static List<Opcode> longOpcodes = new ArrayList<Opcode>(); + private static List<Opcode> long2addrOpcodes = new ArrayList<Opcode>(); + private static List<Opcode> floatOpcodes = new ArrayList<Opcode>(); + private static List<Opcode> float2addrOpcodes = new ArrayList<Opcode>(); + private static List<Opcode> doubleOpcodes = new ArrayList<Opcode>(); + private static List<Opcode> double2addrOpcodes = new ArrayList<Opcode>(); + private static List<Opcode> intLit8Opcodes = new ArrayList<Opcode>(); + private static List<Opcode> intLit16Opcodes = new ArrayList<Opcode>(); + private static List<List<Opcode>> opcodeLists = new ArrayList<List<Opcode>>(); + + static { + intOpcodes.add(Opcode.ADD_INT); + intOpcodes.add(Opcode.SUB_INT); + intOpcodes.add(Opcode.MUL_INT); + intOpcodes.add(Opcode.DIV_INT); + intOpcodes.add(Opcode.REM_INT); + intOpcodes.add(Opcode.AND_INT); + intOpcodes.add(Opcode.OR_INT); + intOpcodes.add(Opcode.XOR_INT); + intOpcodes.add(Opcode.SHL_INT); + intOpcodes.add(Opcode.SHR_INT); + intOpcodes.add(Opcode.USHR_INT); + + int2addrOpcodes.add(Opcode.ADD_INT_2ADDR); + int2addrOpcodes.add(Opcode.SUB_INT_2ADDR); + int2addrOpcodes.add(Opcode.MUL_INT_2ADDR); + int2addrOpcodes.add(Opcode.DIV_INT_2ADDR); + int2addrOpcodes.add(Opcode.REM_INT_2ADDR); + int2addrOpcodes.add(Opcode.AND_INT_2ADDR); + int2addrOpcodes.add(Opcode.OR_INT_2ADDR); + int2addrOpcodes.add(Opcode.XOR_INT_2ADDR); + int2addrOpcodes.add(Opcode.SHL_INT_2ADDR); + int2addrOpcodes.add(Opcode.SHR_INT_2ADDR); + int2addrOpcodes.add(Opcode.USHR_INT_2ADDR); + + longOpcodes.add(Opcode.ADD_LONG); + longOpcodes.add(Opcode.SUB_LONG); + longOpcodes.add(Opcode.MUL_LONG); + longOpcodes.add(Opcode.DIV_LONG); + longOpcodes.add(Opcode.REM_LONG); + longOpcodes.add(Opcode.AND_LONG); + longOpcodes.add(Opcode.OR_LONG); + longOpcodes.add(Opcode.XOR_LONG); + longOpcodes.add(Opcode.SHL_LONG); + longOpcodes.add(Opcode.SHR_LONG); + longOpcodes.add(Opcode.USHR_LONG); + + long2addrOpcodes.add(Opcode.ADD_LONG_2ADDR); + long2addrOpcodes.add(Opcode.SUB_LONG_2ADDR); + long2addrOpcodes.add(Opcode.MUL_LONG_2ADDR); + long2addrOpcodes.add(Opcode.DIV_LONG_2ADDR); + long2addrOpcodes.add(Opcode.REM_LONG_2ADDR); + long2addrOpcodes.add(Opcode.AND_LONG_2ADDR); + long2addrOpcodes.add(Opcode.OR_LONG_2ADDR); + long2addrOpcodes.add(Opcode.XOR_LONG_2ADDR); + long2addrOpcodes.add(Opcode.SHL_LONG_2ADDR); + long2addrOpcodes.add(Opcode.SHR_LONG_2ADDR); + long2addrOpcodes.add(Opcode.USHR_LONG_2ADDR); + + floatOpcodes.add(Opcode.ADD_FLOAT); + floatOpcodes.add(Opcode.SUB_FLOAT); + floatOpcodes.add(Opcode.MUL_FLOAT); + floatOpcodes.add(Opcode.DIV_FLOAT); + floatOpcodes.add(Opcode.REM_FLOAT); + + float2addrOpcodes.add(Opcode.ADD_FLOAT_2ADDR); + float2addrOpcodes.add(Opcode.SUB_FLOAT_2ADDR); + float2addrOpcodes.add(Opcode.MUL_FLOAT_2ADDR); + float2addrOpcodes.add(Opcode.DIV_FLOAT_2ADDR); + float2addrOpcodes.add(Opcode.REM_FLOAT_2ADDR); + + doubleOpcodes.add(Opcode.ADD_DOUBLE); + doubleOpcodes.add(Opcode.SUB_DOUBLE); + doubleOpcodes.add(Opcode.MUL_DOUBLE); + doubleOpcodes.add(Opcode.DIV_DOUBLE); + doubleOpcodes.add(Opcode.REM_DOUBLE); + + double2addrOpcodes.add(Opcode.ADD_DOUBLE_2ADDR); + double2addrOpcodes.add(Opcode.SUB_DOUBLE_2ADDR); + double2addrOpcodes.add(Opcode.MUL_DOUBLE_2ADDR); + double2addrOpcodes.add(Opcode.DIV_DOUBLE_2ADDR); + double2addrOpcodes.add(Opcode.REM_DOUBLE_2ADDR); + + intLit8Opcodes.add(Opcode.ADD_INT_LIT8); + intLit8Opcodes.add(Opcode.RSUB_INT_LIT8); + intLit8Opcodes.add(Opcode.MUL_INT_LIT8); + intLit8Opcodes.add(Opcode.DIV_INT_LIT8); + intLit8Opcodes.add(Opcode.REM_INT_LIT8); + intLit8Opcodes.add(Opcode.AND_INT_LIT8); + intLit8Opcodes.add(Opcode.OR_INT_LIT8); + intLit8Opcodes.add(Opcode.XOR_INT_LIT8); + intLit8Opcodes.add(Opcode.SHL_INT_LIT8); + intLit8Opcodes.add(Opcode.SHR_INT_LIT8); + intLit8Opcodes.add(Opcode.USHR_INT_LIT8); + + intLit16Opcodes.add(Opcode.ADD_INT_LIT16); + intLit16Opcodes.add(Opcode.RSUB_INT); + intLit16Opcodes.add(Opcode.MUL_INT_LIT16); + intLit16Opcodes.add(Opcode.DIV_INT_LIT16); + intLit16Opcodes.add(Opcode.REM_INT_LIT16); + intLit16Opcodes.add(Opcode.AND_INT_LIT16); + intLit16Opcodes.add(Opcode.OR_INT_LIT16); + intLit16Opcodes.add(Opcode.XOR_INT_LIT16); + + opcodeLists.add(intOpcodes); + opcodeLists.add(longOpcodes); + opcodeLists.add(floatOpcodes); + opcodeLists.add(doubleOpcodes); + opcodeLists.add(int2addrOpcodes); + opcodeLists.add(long2addrOpcodes); + opcodeLists.add(float2addrOpcodes); + opcodeLists.add(double2addrOpcodes); + opcodeLists.add(intLit8Opcodes); + opcodeLists.add(intLit16Opcodes); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/BranchShifter.java b/tools/dexfuzz/src/dexfuzz/program/mutators/BranchShifter.java new file mode 100644 index 0000000..a28f5ba --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/BranchShifter.java @@ -0,0 +1,170 @@ +/* + * 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.MBranchInsn; +import dexfuzz.program.MInsn; +import dexfuzz.program.MutatableCode; +import dexfuzz.program.Mutation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class BranchShifter 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 branchInsnIdx; + public int newTargetIdx; + + @Override + public String getString() { + StringBuilder builder = new StringBuilder(); + builder.append(branchInsnIdx).append(" "); + builder.append(newTargetIdx); + return builder.toString(); + } + + @Override + public void parseString(String[] elements) { + branchInsnIdx = Integer.parseInt(elements[2]); + newTargetIdx = Integer.parseInt(elements[3]); + } + } + + // 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 BranchShifter() { } + + public BranchShifter(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 30; + } + + // A cache that should only exist between generateMutation() and applyMutation(), + // or be created at the start of applyMutation(), if we're reading in mutations from + // a file. + private List<MBranchInsn> branchInsns; + + private void generateCachedBranchInsns(MutatableCode mutatableCode) { + if (branchInsns != null) { + return; + } + + branchInsns = new ArrayList<MBranchInsn>(); + + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn instanceof MBranchInsn) { + branchInsns.add((MBranchInsn) mInsn); + } + } + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + // Can't shift a branch if there's only one instruction in the method. + if (mutatableCode.getInstructionCount() == 1) { + Log.debug("Method contains only one instruction, skipping."); + return false; + } + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn instanceof MBranchInsn) { + return true; + } + } + + Log.debug("Method contains no branch instructions."); + return false; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + generateCachedBranchInsns(mutatableCode); + + // Pick a random branching instruction. + int branchInsnIdx = rng.nextInt(branchInsns.size()); + MBranchInsn branchInsn = branchInsns.get(branchInsnIdx); + + // Get its original target, find its index. + MInsn oldTargetInsn = branchInsn.target; + int oldTargetInsnIdx = mutatableCode.getInstructionIndex(oldTargetInsn); + + int newTargetIdx = oldTargetInsnIdx; + + int delta = 0; + + // Keep searching for a new index. + while (newTargetIdx == oldTargetInsnIdx) { + // Vary by +/- 2 instructions. + delta = 0; + while (delta == 0) { + delta = (rng.nextInt(5) - 2); + } + + newTargetIdx = oldTargetInsnIdx + delta; + + // Check the new index is legal. + if (newTargetIdx < 0) { + newTargetIdx = 0; + } else if (newTargetIdx >= mutatableCode.getInstructionCount()) { + newTargetIdx = mutatableCode.getInstructionCount() - 1; + } + } + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.branchInsnIdx = branchInsnIdx; + mutation.newTargetIdx = newTargetIdx; + 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; + + generateCachedBranchInsns(mutatableCode); + + MBranchInsn branchInsn = branchInsns.get(mutation.branchInsnIdx); + + // Get the new target. + MInsn newTargetInsn = mutatableCode.getInstructionAt(mutation.newTargetIdx); + + // Set the new target. + branchInsn.target = newTargetInsn; + + Log.info("Shifted the target of " + branchInsn + " to point to " + newTargetInsn); + + stats.incrementStat("Shifted branch target"); + + // Clear cache. + branchInsns = null; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/CmpBiasChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/CmpBiasChanger.java new file mode 100644 index 0000000..dc60e79 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/CmpBiasChanger.java @@ -0,0 +1,154 @@ +/* + * 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.ArrayList; +import java.util.List; +import java.util.Random; + +public class CmpBiasChanger 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 cmpBiasInsnIdx; + + @Override + public String getString() { + return Integer.toString(cmpBiasInsnIdx); + } + + @Override + public void parseString(String[] elements) { + cmpBiasInsnIdx = 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 CmpBiasChanger() { } + + public CmpBiasChanger(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 30; + } + + // A cache that should only exist between generateMutation() and applyMutation(), + // or be created at the start of applyMutation(), if we're reading in mutations from + // a file. + private List<MInsn> cmpBiasInsns = null; + + private void generateCachedCmpBiasInsns(MutatableCode mutatableCode) { + if (cmpBiasInsns != null) { + return; + } + + cmpBiasInsns = new ArrayList<MInsn>(); + + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isCmpBiasOperation(mInsn)) { + cmpBiasInsns.add(mInsn); + } + } + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isCmpBiasOperation(mInsn)) { + return true; + } + } + + Log.debug("No cmp-with-bias operations in method, skipping..."); + return false; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + generateCachedCmpBiasInsns(mutatableCode); + + int cmpBiasInsnIdx = rng.nextInt(cmpBiasInsns.size()); + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.cmpBiasInsnIdx = cmpBiasInsnIdx; + 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; + + generateCachedCmpBiasInsns(mutatableCode); + + MInsn cmpBiasInsn = cmpBiasInsns.get(mutation.cmpBiasInsnIdx); + + String oldInsnString = cmpBiasInsn.toString(); + + Opcode newOpcode = getLegalDifferentOpcode(cmpBiasInsn); + + cmpBiasInsn.insn.info = Instruction.getOpcodeInfo(newOpcode); + + Log.info("Changed " + oldInsnString + " to " + cmpBiasInsn); + + stats.incrementStat("Changed comparison bias"); + + // Clear cache. + cmpBiasInsns = null; + } + + private Opcode getLegalDifferentOpcode(MInsn mInsn) { + Opcode opcode = mInsn.insn.info.opcode; + if (opcode == Opcode.CMPG_DOUBLE) { + return Opcode.CMPL_DOUBLE; + } + if (opcode == Opcode.CMPL_DOUBLE) { + return Opcode.CMPG_DOUBLE; + } + if (opcode == Opcode.CMPG_FLOAT) { + return Opcode.CMPL_FLOAT; + } + return Opcode.CMPG_FLOAT; + } + + private boolean isCmpBiasOperation(MInsn mInsn) { + Opcode opcode = mInsn.insn.info.opcode; + if (Opcode.isBetween(opcode, Opcode.CMPL_FLOAT, Opcode.CMPG_DOUBLE)) { + return true; + } + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/CodeMutator.java b/tools/dexfuzz/src/dexfuzz/program/mutators/CodeMutator.java new file mode 100644 index 0000000..be566ad --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/CodeMutator.java @@ -0,0 +1,136 @@ +/* + * 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.Options; +import dexfuzz.program.MutatableCode; +import dexfuzz.program.Mutation; + +import java.util.List; +import java.util.Random; + +/** + * The base class for all classes that can mutate methods. + */ +public abstract class CodeMutator { + /** + * The RNG, passed in by the Program that initialised us. + */ + protected Random rng; + + /** + * Used to track which mutations happen. + */ + protected MutationStats stats; + + /** + * Used to track mutations that have been applied so far. + */ + protected List<Mutation> mutations; + + /** + * The chance, out of 100, that this mutator actually mutates the the program + * when asked to by the Program. The default is 50% chance, but each mutator that + * extends CodeMutator should its own default. + */ + protected int likelihood = 50; + + /** + * This constructor is only intended for use in MutationRecorder... + */ + public CodeMutator() { + + } + + /** + * Constructor that all subclasses must call... + * + * @param rng The RNG that the Program created. + */ + public CodeMutator(Random rng, MutationStats stats, List<Mutation> mutations) { + this.rng = rng; + this.stats = stats; + this.mutations = mutations; + + String name = this.getClass().getSimpleName().toLowerCase(); + + if (Options.mutationLikelihoods.containsKey(name)) { + likelihood = Options.mutationLikelihoods.get(name); + Log.info("Set mutation likelihood to " + likelihood + + "% for " + this.getClass().getSimpleName()); + } + } + + /** + * When the Program picks a particular mutator to mutate the code, it calls + * this function, that determines if the mutator will actually mutate the code. + * If so, it then calls the mutationFunction() method, that every subclass CodeMutator + * is expected to implement to perform its mutation. + * + * @return If mutation took place. + */ + public boolean attemptToMutate(MutatableCode mutatableCode) { + if (shouldMutate(mutatableCode)) { + generateAndApplyMutation(mutatableCode); + return true; + } + Log.info("Skipping mutation."); + return false; + } + + public void forceMutate(Mutation mutation) { + Log.info("Forcing mutation."); + applyMutation(mutation); + } + + public boolean canBeTriggered() { + return (likelihood > 0); + } + + /** + * Randomly determine if the mutator will actually mutate a method, based on its + * provided likelihood of mutation. + * + * @return If the method should be mutated. + */ + private boolean shouldMutate(MutatableCode mutatableCode) { + return ((rng.nextInt(100) < likelihood) && canMutate(mutatableCode)); + } + + private void generateAndApplyMutation(MutatableCode mutatableCode) { + Mutation mutation = generateMutation(mutatableCode); + // Always save the mutation. + mutations.add(mutation); + applyMutation(mutation); + } + + /** + * A CodeMutator must override this method if there is any reason why could not mutate + * a particular method, and return false in that case. + */ + protected boolean canMutate(MutatableCode mutatableCode) { + return true; + } + + protected abstract Mutation generateMutation(MutatableCode mutatableCode); + + protected abstract void applyMutation(Mutation uncastMutation); + + public abstract Mutation getNewMutation(); +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/ConstantValueChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/ConstantValueChanger.java new file mode 100644 index 0000000..22f04e8 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/ConstantValueChanger.java @@ -0,0 +1,149 @@ +/* + * 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.formats.ContainsConst; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class ConstantValueChanger 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 constInsnIdx; + public long newConstant; + + @Override + public String getString() { + StringBuilder builder = new StringBuilder(); + builder.append(constInsnIdx).append(" "); + builder.append(newConstant); + return builder.toString(); + } + + @Override + public void parseString(String[] elements) { + constInsnIdx = Integer.parseInt(elements[2]); + newConstant = Long.parseLong(elements[3]); + } + } + + // 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 ConstantValueChanger() { } + + public ConstantValueChanger(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 70; + } + + // A cache that should only exist between generateMutation() and applyMutation(), + // or be created at the start of applyMutation(), if we're reading in mutations from + // a file. + private List<MInsn> constInsns = null; + + private void generateCachedConstInsns(MutatableCode mutatableCode) { + if (constInsns != null) { + return; + } + + constInsns = new ArrayList<MInsn>(); + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn.insn.info.format instanceof ContainsConst) { + constInsns.add(mInsn); + } + } + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn.insn.info.format instanceof ContainsConst) { + return true; + } + } + + Log.debug("Method contains no const instructions."); + return false; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + generateCachedConstInsns(mutatableCode); + + // Pick a random const instruction. + int constInsnIdx = rng.nextInt(constInsns.size()); + MInsn constInsn = constInsns.get(constInsnIdx); + + // Get the constant. + long oldConstant = ((ContainsConst)constInsn.insn.info.format).getConst(constInsn.insn); + + long newConstant = oldConstant; + + // Make a new constant. + while (newConstant == oldConstant) { + newConstant = rng.nextLong() + % ((ContainsConst)constInsn.insn.info.format).getConstRange(); + } + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.constInsnIdx = constInsnIdx; + mutation.newConstant = newConstant; + 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; + + generateCachedConstInsns(mutatableCode); + + MInsn constInsn = constInsns.get(mutation.constInsnIdx); + + long oldConstant = ((ContainsConst)constInsn.insn.info.format).getConst(constInsn.insn); + + Log.info("Changed constant value #" + oldConstant + " to #" + mutation.newConstant + + " in " + constInsn); + + stats.incrementStat("Changed constant value"); + + // Set the new constant. + ((ContainsConst)constInsn.insn.info.format).setConst(constInsn.insn, mutation.newConstant); + + // Clear cache. + constInsns = null; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/ConversionRepeater.java b/tools/dexfuzz/src/dexfuzz/program/mutators/ConversionRepeater.java new file mode 100644 index 0000000..7fdf304 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/ConversionRepeater.java @@ -0,0 +1,200 @@ +/* + * 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.ArrayList; +import java.util.List; +import java.util.Random; + +public class ConversionRepeater 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 conversionInsnIdx; + + @Override + public String getString() { + return Integer.toString(conversionInsnIdx); + } + + @Override + public void parseString(String[] elements) { + conversionInsnIdx = 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 ConversionRepeater() { } + + public ConversionRepeater(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 50; + } + + // A cache that should only exist between generateMutation() and applyMutation(), + // or be created at the start of applyMutation(), if we're reading in mutations from + // a file. + private List<MInsn> conversionInsns = null; + + private void generateCachedConversionInsns(MutatableCode mutatableCode) { + if (conversionInsns != null) { + return; + } + + conversionInsns = new ArrayList<MInsn>(); + + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isConversionInstruction(mInsn)) { + conversionInsns.add(mInsn); + } + } + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isConversionInstruction(mInsn)) { + return true; + } + } + + Log.debug("No conversion operations in method, skipping..."); + return false; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + generateCachedConversionInsns(mutatableCode); + int conversionInsnIdx = rng.nextInt(conversionInsns.size()); + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.conversionInsnIdx = conversionInsnIdx; + 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; + + generateCachedConversionInsns(mutatableCode); + + MInsn originalInsn = conversionInsns.get(mutation.conversionInsnIdx); + + // We want to create two new instructions: + // [original conversion] eg float-to-int + // NEW: [there] eg int-to-float (with vregs of first inst swapped) + // NEW: [back] eg float-to-int + + // Create the "there" instruction. + MInsn newInsnThere = originalInsn.clone(); + + // Flip the opcode. + Opcode oppositeOpcode = null; + switch (newInsnThere.insn.info.opcode) { + case INT_TO_LONG: + oppositeOpcode = Opcode.LONG_TO_INT; + break; + case INT_TO_FLOAT: + oppositeOpcode = Opcode.FLOAT_TO_INT; + break; + case INT_TO_DOUBLE: + oppositeOpcode = Opcode.DOUBLE_TO_INT; + break; + case LONG_TO_INT: + oppositeOpcode = Opcode.INT_TO_LONG; + break; + case LONG_TO_FLOAT: + oppositeOpcode = Opcode.FLOAT_TO_LONG; + break; + case LONG_TO_DOUBLE: + oppositeOpcode = Opcode.DOUBLE_TO_LONG; + break; + case FLOAT_TO_INT: + oppositeOpcode = Opcode.INT_TO_FLOAT; + break; + case FLOAT_TO_LONG: + oppositeOpcode = Opcode.LONG_TO_FLOAT; + break; + case FLOAT_TO_DOUBLE: + oppositeOpcode = Opcode.DOUBLE_TO_FLOAT; + break; + case DOUBLE_TO_INT: + oppositeOpcode = Opcode.INT_TO_DOUBLE; + break; + case DOUBLE_TO_LONG: + oppositeOpcode = Opcode.LONG_TO_DOUBLE; + break; + case DOUBLE_TO_FLOAT: + oppositeOpcode = Opcode.FLOAT_TO_DOUBLE; + break; + default: + Log.errorAndQuit( + "Trying to repeat the conversion in an insn that is not a conversion insn."); + } + newInsnThere.insn.info = Instruction.getOpcodeInfo(oppositeOpcode); + + // Swap the vregs. + long tempReg = newInsnThere.insn.vregA; + newInsnThere.insn.vregA = newInsnThere.insn.vregB; + newInsnThere.insn.vregB = tempReg; + + // Create the "back" instruction. + MInsn newInsnBack = originalInsn.clone(); + + // Get the index into the MutatableCode's mInsns list for this insn. + int originalInsnIdx = mutatableCode.getInstructionIndex(originalInsn); + + // Insert the new instructions. + mutatableCode.insertInstructionAfter(newInsnThere, originalInsnIdx); + mutatableCode.insertInstructionAfter(newInsnBack, originalInsnIdx + 1); + + Log.info("Performing conversion repetition for " + originalInsn); + + stats.incrementStat("Repeating conversion"); + + // Clear the cache. + conversionInsns = null; + } + + private boolean isConversionInstruction(MInsn mInsn) { + Opcode opcode = mInsn.insn.info.opcode; + if (Opcode.isBetween(opcode, Opcode.INT_TO_LONG, Opcode.DOUBLE_TO_FLOAT)) { + return true; + } + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/FieldFlagChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/FieldFlagChanger.java new file mode 100644 index 0000000..0849d12 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/FieldFlagChanger.java @@ -0,0 +1,167 @@ +/* + * 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.EncodedField; +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.Opcode; +import dexfuzz.rawdex.formats.ContainsPoolIndex; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class FieldFlagChanger 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 fieldInsnIdx; + public boolean setVolatile; + + @Override + public String getString() { + StringBuilder builder = new StringBuilder(); + builder.append(fieldInsnIdx).append(" "); + builder.append(setVolatile); + return builder.toString(); + } + + @Override + public void parseString(String[] elements) { + fieldInsnIdx = Integer.parseInt(elements[2]); + setVolatile = Boolean.parseBoolean(elements[3]); + } + } + + // 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 FieldFlagChanger() { } + + public FieldFlagChanger(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 40; + } + + // A cache that should only exist between generateMutation() and applyMutation(), + // or be created at the start of applyMutation(), if we're reading in mutations from + // a file. + private List<MInsn> fieldInsns = null; + + private void generateCachedFieldInsns(MutatableCode mutatableCode) { + if (fieldInsns != null) { + return; + } + + fieldInsns = new ArrayList<MInsn>(); + + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isFileDefinedFieldInstruction(mInsn, mutatableCode)) { + fieldInsns.add(mInsn); + } + } + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isFileDefinedFieldInstruction(mInsn, mutatableCode)) { + return true; + } + } + + Log.debug("No field instructions in method, skipping..."); + return false; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + generateCachedFieldInsns(mutatableCode); + + int fieldInsnIdx = rng.nextInt(fieldInsns.size()); + + Instruction insn = fieldInsns.get(fieldInsnIdx).insn; + ContainsPoolIndex containsPoolIndex = (ContainsPoolIndex) insn.info.format; + int fieldIdx = containsPoolIndex.getPoolIndex(insn); + EncodedField encodedField = mutatableCode.program.getEncodedField(fieldIdx); + + boolean setVolatile = false; + if (!encodedField.isVolatile()) { + setVolatile = true; + } + // TODO: Flip more flags? + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.fieldInsnIdx = fieldInsnIdx; + mutation.setVolatile = setVolatile; + 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; + + generateCachedFieldInsns(mutatableCode); + + Instruction insn = fieldInsns.get(mutation.fieldInsnIdx).insn; + ContainsPoolIndex containsPoolIndex = (ContainsPoolIndex) insn.info.format; + int fieldIdx = containsPoolIndex.getPoolIndex(insn); + EncodedField encodedField = mutatableCode.program.getEncodedField(fieldIdx); + + if (mutation.setVolatile) { + encodedField.setVolatile(true); + Log.info("Set field idx " + fieldIdx + " as volatile"); + } else { + encodedField.setVolatile(false); + Log.info("Set field idx " + fieldIdx + " as not volatile"); + } + + stats.incrementStat("Changed volatility of field"); + + // Clear cache. + fieldInsns = null; + } + + private boolean isFileDefinedFieldInstruction(MInsn mInsn, MutatableCode mutatableCode) { + Opcode opcode = mInsn.insn.info.opcode; + if (Opcode.isBetween(opcode, Opcode.IGET, Opcode.SPUT_SHORT)) { + Instruction insn = mInsn.insn; + ContainsPoolIndex containsPoolIndex = (ContainsPoolIndex) insn.info.format; + int fieldIdx = containsPoolIndex.getPoolIndex(insn); + if (mutatableCode.program.getEncodedField(fieldIdx) != null) { + return true; + } + } + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDeleter.java b/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDeleter.java new file mode 100644 index 0000000..8ffa4c5 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDeleter.java @@ -0,0 +1,138 @@ +/* + * 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.MInsnWithData; +import dexfuzz.program.MutatableCode; +import dexfuzz.program.Mutation; + +import java.util.List; +import java.util.Random; + +public class InstructionDeleter 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 insnToDeleteIdx; + + @Override + public String getString() { + return Integer.toString(insnToDeleteIdx); + } + + @Override + public void parseString(String[] elements) { + insnToDeleteIdx = 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 InstructionDeleter() { } + + public InstructionDeleter(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 40; + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + if (mutatableCode.getInstructionCount() < 4) { + // TODO: Make this more sophisticated - right now this is to avoid problems with + // a method that has 3 instructions: fill-array-data; return-void; <data for fill-array-data> + Log.debug("Cannot delete insns in a method with only a few."); + return false; + } + return true; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + // Pick an instruction at random... + int insnIdx = rng.nextInt(mutatableCode.getInstructionCount()); + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.insnToDeleteIdx = insnIdx; + 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 toBeDeleted = + mutatableCode.getInstructionAt(mutation.insnToDeleteIdx); + + Log.info("Deleting " + toBeDeleted); + + stats.incrementStat("Deleted instruction"); + + // Delete the instruction. + mutatableCode.deleteInstruction(mutation.insnToDeleteIdx); + + // If we delete a with-data insn, we should delete the associated data insn as well. + if (toBeDeleted instanceof MInsnWithData) { + Log.info(toBeDeleted + " had associated data, so the data insn was deleted."); + // Get the data instruction. + MInsn dataInsn = + ((MInsnWithData)toBeDeleted).dataTarget; + mutatableCode.deleteInstruction(dataInsn); + stats.incrementStat("Deleted a with-data insn's data"); + } + // If we delete a data insn, we should delete the associated with-data insn as well. + if (toBeDeleted.insn.justRaw) { + // .justRaw implies that this is a data insn. + Log.info(toBeDeleted + + " was data, so the associated with-data insn was deleted."); + + // First, find the with-data insn that is pointing to this insn. + MInsn withDataInsn = null; + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn instanceof MInsnWithData) { + if (((MInsnWithData)mInsn).dataTarget == toBeDeleted) { + withDataInsn = mInsn; + break; + } + } + } + + // Now delete the with-data insn. + if (withDataInsn != null) { + mutatableCode.deleteInstruction(withDataInsn); + stats.incrementStat("Deleted a data insn's with-data insn"); + } else { + Log.errorAndQuit("Tried to delete a data insn, " + + "but it didn't have any with-data insn pointing at it?"); + } + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDuplicator.java b/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDuplicator.java new file mode 100644 index 0000000..4917056 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDuplicator.java @@ -0,0 +1,104 @@ +/* + * 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.Opcode; + +import java.util.List; +import java.util.Random; + +public class InstructionDuplicator 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 insnToDuplicateIdx; + + @Override + public String getString() { + return Integer.toString(insnToDuplicateIdx); + } + + @Override + public void parseString(String[] elements) { + insnToDuplicateIdx = 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 InstructionDuplicator() { } + + public InstructionDuplicator(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 80; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + boolean foundInsn = false; + int insnIdx = 0; + + while (!foundInsn) { + // Pick an instruction at random... + insnIdx = rng.nextInt(mutatableCode.getInstructionCount()); + MInsn oldInsn = mutatableCode.getInstructionAt(insnIdx); + foundInsn = true; + Opcode opcode = oldInsn.insn.info.opcode; + // ...check it's a legal instruction to duplicate. + if (opcode == Opcode.SPARSE_SWITCH || opcode == Opcode.PACKED_SWITCH + || opcode == Opcode.FILL_ARRAY_DATA || oldInsn.insn.justRaw) { + foundInsn = false; + } + } + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.insnToDuplicateIdx = insnIdx; + 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 oldInsn = mutatableCode.getInstructionAt(mutation.insnToDuplicateIdx); + + MInsn newInsn = oldInsn.clone(); + + Log.info("Duplicating " + oldInsn); + + stats.incrementStat("Duplicated instruction"); + + mutatableCode.insertInstructionAt(newInsn, mutation.insnToDuplicateIdx); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionSwapper.java b/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionSwapper.java new file mode 100644 index 0000000..17ea939 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionSwapper.java @@ -0,0 +1,159 @@ +/* + * 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 java.util.List; +import java.util.Random; + +public class InstructionSwapper 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 swapInsnIdx; + public int swapWithInsnIdx; + + @Override + public String getString() { + StringBuilder builder = new StringBuilder(); + builder.append(swapInsnIdx).append(" "); + builder.append(swapWithInsnIdx); + return builder.toString(); + } + + @Override + public void parseString(String[] elements) { + swapInsnIdx = Integer.parseInt(elements[2]); + swapWithInsnIdx = Integer.parseInt(elements[3]); + } + } + + // 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 InstructionSwapper() { } + + public InstructionSwapper(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 80; + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + if (mutatableCode.getInstructionCount() == 1) { + // Cannot swap one instruction. + Log.debug("Cannot swap insns in a method with only one."); + return false; + } + return true; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + int swapInsnIdx = 0; + int swapWithInsnIdx = 0; + + boolean foundFirstInsn = false; + boolean foundSecondInsn = false; + + while (!foundFirstInsn || !foundSecondInsn) { + // Look for the first insn. + while (!foundFirstInsn) { + swapInsnIdx = rng.nextInt(mutatableCode.getInstructionCount()); + MInsn toBeSwapped = mutatableCode.getInstructionAt(swapInsnIdx); + foundFirstInsn = true; + if (toBeSwapped.insn.justRaw) { + foundFirstInsn = false; + } + } + + // Look for the second insn. + int secondInsnAttempts = 0; + while (!foundSecondInsn) { + int delta = rng.nextInt(5) - 1; + + if (delta == 0) { + continue; + } + + swapWithInsnIdx = swapInsnIdx + delta; + foundSecondInsn = true; + + // Check insn is in valid range. + if (swapWithInsnIdx < 0) { + foundSecondInsn = false; + } else if (swapWithInsnIdx >= mutatableCode.getInstructionCount()) { + foundSecondInsn = false; + } + + // Finally, check if we're swapping with a raw insn. + if (foundSecondInsn) { + if (mutatableCode.getInstructionAt(swapWithInsnIdx).insn.justRaw) { + foundSecondInsn = false; + } + } + + // If we've checked 10 times for an insn to swap with, + // and still found nothing, then try a new first insn. + if (!foundSecondInsn) { + secondInsnAttempts++; + if (secondInsnAttempts == 10) { + foundFirstInsn = false; + break; + } + } + } + } + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.swapInsnIdx = swapInsnIdx; + mutation.swapWithInsnIdx = swapWithInsnIdx; + 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 toBeSwapped = mutatableCode.getInstructionAt(mutation.swapInsnIdx); + MInsn swappedWith = mutatableCode.getInstructionAt(mutation.swapWithInsnIdx); + + Log.info("Swapping " + toBeSwapped + " with " + swappedWith); + + stats.incrementStat("Swapped two instructions"); + + mutatableCode.swapInstructionsByIndex(mutation.swapInsnIdx, mutation.swapWithInsnIdx); + + Log.info("Now " + swappedWith + " is swapped with " + toBeSwapped); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/NewMethodCaller.java b/tools/dexfuzz/src/dexfuzz/program/mutators/NewMethodCaller.java new file mode 100644 index 0000000..88a2f9a --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/NewMethodCaller.java @@ -0,0 +1,186 @@ +/* + * 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.Instruction.InvokeFormatInfo; +import dexfuzz.rawdex.Opcode; + +import java.util.List; +import java.util.Random; + +public class NewMethodCaller 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 enum InvokeType { + VIRTUAL, + DIRECT, + SUPER, + STATIC, + INTERFACE + } + + public int insertionIdx; + public InvokeType invokeType; + public String className; + public String methodName; + public String signature; + public int numArgs; + public int[] args; + + @Override + public String getString() { + StringBuilder argsString = new StringBuilder(); + for (int i = 0; i < numArgs; i++) { + argsString.append(args[i]); + if (i < (numArgs - 1)) { + argsString.append(" "); + } + } + String result = String.format("%d %d %s %s %s %d %s", + insertionIdx, + invokeType.ordinal(), + className, + methodName, + signature, + numArgs, + argsString); + return result; + } + + @Override + public void parseString(String[] elements) { + insertionIdx = Integer.parseInt(elements[2]); + invokeType = InvokeType.values()[Integer.parseInt(elements[3])]; + className = elements[4]; + methodName = elements[5]; + signature = elements[6]; + numArgs = Integer.parseInt(elements[7]); + args = new int[numArgs]; + for (int i = 0; i < numArgs; i++) { + args[i] = Integer.parseInt(elements[8 + i]); + } + } + } + + // 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 NewMethodCaller() { } + + public NewMethodCaller(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 10; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + // Find the insertion point. + int insertionIdx = 0; + boolean foundInsn = false; + + while (!foundInsn) { + insertionIdx = rng.nextInt(mutatableCode.getInstructionCount()); + MInsn insertionPoint = + mutatableCode.getInstructionAt(insertionIdx); + foundInsn = true; + + // Don't want to insert instructions where there are raw instructions for now. + if (insertionPoint.insn.justRaw) { + foundInsn = false; + } + } + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.insertionIdx = insertionIdx; + + // TODO: Right now this mutator can only insert calls to System.gc() Add more! + + mutation.invokeType = AssociatedMutation.InvokeType.STATIC; + mutation.className = "Ljava/lang/System;"; + mutation.methodName = "gc"; + mutation.signature = "()V"; + mutation.numArgs = 0; + + 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 newInsn = new MInsn(); + newInsn.insn = new Instruction(); + + switch (mutation.invokeType) { + case VIRTUAL: + newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_VIRTUAL); + break; + case DIRECT: + newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_DIRECT); + break; + case SUPER: + newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_SUPER); + break; + case STATIC: + newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_STATIC); + break; + case INTERFACE: + newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_INTERFACE); + break; + default: + } + + // TODO: Handle more than just static invokes. + + int methodIdx = mutatableCode.program.getNewItemCreator() + .findOrCreateMethodId(mutation.className, + mutation.methodName, mutation.signature); + + newInsn.insn.vregB = methodIdx; + newInsn.insn.invokeFormatInfo = new InvokeFormatInfo(); + + // TODO: More field population, when we call methods that take arguments. + + MInsn insertionPoint = + mutatableCode.getInstructionAt(mutation.insertionIdx); + + Log.info(String.format("Called new method %s %s %s, inserting at %s", + mutation.className, mutation.methodName, mutation.signature, insertionPoint)); + + stats.incrementStat("Called new method"); + + mutatableCode.insertInstructionAt(newInsn, mutation.insertionIdx); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/NonsenseStringPrinter.java b/tools/dexfuzz/src/dexfuzz/program/mutators/NonsenseStringPrinter.java new file mode 100644 index 0000000..b4c9d7b --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/NonsenseStringPrinter.java @@ -0,0 +1,162 @@ +/* + * 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 NonsenseStringPrinter 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 insertionIdx; + public String nonsenseString; + + @Override + public String getString() { + StringBuilder builder = new StringBuilder(); + builder.append(insertionIdx).append(" "); + builder.append(nonsenseString); + return builder.toString(); + } + + @Override + public void parseString(String[] elements) { + insertionIdx = Integer.parseInt(elements[2]); + nonsenseString = elements[3]; + } + } + + // 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 NonsenseStringPrinter() { } + + public NonsenseStringPrinter(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 10; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + // Find the insertion point + int insertionIdx = 0; + boolean foundInsn = false; + + while (!foundInsn) { + insertionIdx = rng.nextInt(mutatableCode.getInstructionCount()); + MInsn insertionPoint = + mutatableCode.getInstructionAt(insertionIdx); + foundInsn = true; + + // Don't want to insert instructions where there are raw instructions for now. + if (insertionPoint.insn.justRaw) { + foundInsn = false; + } + } + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.insertionIdx = insertionIdx; + mutation.nonsenseString = getRandomString(); + 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; + + int outFieldIdx = mutatableCode.program.getNewItemCreator().findOrCreateFieldId( + "Ljava/lang/System;", + "Ljava/io/PrintStream;", + "out"); + int printMethodIdx = mutatableCode.program.getNewItemCreator().findOrCreateMethodId( + "Ljava/io/PrintStream;", + "print", + "(Ljava/lang/String;)V"); + int nonsenseStringIdx = mutatableCode.program.getNewItemCreator().findOrCreateString( + mutation.nonsenseString); + + MInsn insertionPoint = mutatableCode.getInstructionAt(mutation.insertionIdx); + + mutatableCode.allocateTemporaryVRegs(2); + + int streamRegister = mutatableCode.getTemporaryVReg(0); + int stringRegister = mutatableCode.getTemporaryVReg(1); + + // Load into string and stream into the temporary registers. + // then call print(stream, string) + MInsn constStringInsn = new MInsn(); + constStringInsn.insn = new Instruction(); + constStringInsn.insn.info = Instruction.getOpcodeInfo(Opcode.CONST_STRING); + constStringInsn.insn.vregB = nonsenseStringIdx; + constStringInsn.insn.vregA = stringRegister; + + 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); + invokeInsn.insn.vregA = 2; + invokeInsn.insn.vregB = printMethodIdx; + invokeInsn.insn.vregC = streamRegister; + + Log.info(String.format("Printing nonsense string '%s', inserting at %s", + mutation.nonsenseString, insertionPoint)); + + stats.incrementStat("Printed nonsense string"); + + mutatableCode.insertInstructionAt(invokeInsn, mutation.insertionIdx); + mutatableCode.insertInstructionAt(streamLoadInsn, mutation.insertionIdx); + mutatableCode.insertInstructionAt(constStringInsn, mutation.insertionIdx); + + mutatableCode.finishedUsingTemporaryVRegs(); + } + + private String getRandomString() { + int size = rng.nextInt(10); + int start = (int) 'A'; + int end = (int) 'Z'; + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < size; i++) { + builder.append((char) (rng.nextInt((end + 1) - start) + start)); + } + return builder.toString(); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/PoolIndexChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/PoolIndexChanger.java new file mode 100644 index 0000000..cae5dc1 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/PoolIndexChanger.java @@ -0,0 +1,199 @@ +/* + * 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.formats.ContainsPoolIndex; +import dexfuzz.rawdex.formats.ContainsPoolIndex.PoolIndexKind; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class PoolIndexChanger 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 poolIndexInsnIdx; + public int newPoolIndex; + + @Override + public String getString() { + StringBuilder builder = new StringBuilder(); + builder.append(poolIndexInsnIdx).append(" "); + builder.append(newPoolIndex); + return builder.toString(); + } + + @Override + public void parseString(String[] elements) { + poolIndexInsnIdx = Integer.parseInt(elements[2]); + newPoolIndex = Integer.parseInt(elements[3]); + } + } + + // 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 PoolIndexChanger() { } + + public PoolIndexChanger(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 30; + } + + // A cache that should only exist between generateMutation() and applyMutation(), + // or be created at the start of applyMutation(), if we're reading in mutations from + // a file. + private List<MInsn> poolIndexInsns = null; + + private void generateCachedPoolIndexInsns(MutatableCode mutatableCode) { + if (poolIndexInsns != null) { + return; + } + + poolIndexInsns = new ArrayList<MInsn>(); + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn.insn.info.format instanceof ContainsPoolIndex) { + poolIndexInsns.add(mInsn); + } + } + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + // Remember what kinds of pool indices we see. + List<PoolIndexKind> seenKinds = new ArrayList<PoolIndexKind>(); + + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn.insn.info.format instanceof ContainsPoolIndex) { + + ContainsPoolIndex containsPoolIndex = + (ContainsPoolIndex)mInsn.insn.info.format; + + PoolIndexKind newPoolIndexKind = + containsPoolIndex.getPoolIndexKind(mInsn.insn.info); + + seenKinds.add(newPoolIndexKind); + } + } + + // Now check that there exists a kind such that the max pool index for + // the kind is greater than 1 (i.e., something can be changed) + if (!seenKinds.isEmpty()) { + + for (PoolIndexKind kind : seenKinds) { + int numPoolIndices = mutatableCode.program.getTotalPoolIndicesByKind(kind); + if (numPoolIndices > 1) { + return true; + } + } + + Log.debug("Method does not contain any insns that index into a const pool size > 1"); + return false; + } + + Log.debug("Method contains no instructions that index into the constant pool."); + return false; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + generateCachedPoolIndexInsns(mutatableCode); + + int poolIndexInsnIdx = 0; + boolean found = false; + + int oldPoolIndex = 0; + int newPoolIndex = 0; + int maxPoolIndex = 0; + + // Pick a random instruction. + while (!found) { + poolIndexInsnIdx = rng.nextInt(poolIndexInsns.size()); + MInsn poolIndexInsn = poolIndexInsns.get(poolIndexInsnIdx); + + found = true; + + ContainsPoolIndex containsPoolIndex = + (ContainsPoolIndex)poolIndexInsn.insn.info.format; + + // Get the pool index. + oldPoolIndex = containsPoolIndex.getPoolIndex(poolIndexInsn.insn); + newPoolIndex = oldPoolIndex; + + // Get the largest pool index available for the provided kind of pool index. + PoolIndexKind poolIndexKind = + containsPoolIndex.getPoolIndexKind(poolIndexInsn.insn.info); + maxPoolIndex = mutatableCode.program.getTotalPoolIndicesByKind(poolIndexKind); + + if (maxPoolIndex <= 1) { + found = false; + } + } + + // Get a new pool index. + while (newPoolIndex == oldPoolIndex) { + newPoolIndex = rng.nextInt(maxPoolIndex); + } + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.poolIndexInsnIdx = poolIndexInsnIdx; + mutation.newPoolIndex = newPoolIndex; + 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; + + generateCachedPoolIndexInsns(mutatableCode); + + MInsn poolIndexInsn = poolIndexInsns.get(mutation.poolIndexInsnIdx); + + ContainsPoolIndex containsPoolIndex = + (ContainsPoolIndex) poolIndexInsn.insn.info.format; + + int oldPoolIndex = containsPoolIndex.getPoolIndex(poolIndexInsn.insn); + + Log.info("Changed pool index " + oldPoolIndex + " to " + mutation.newPoolIndex + + " in " + poolIndexInsn); + + stats.incrementStat("Changed constant pool index"); + + // Set the new pool index. + containsPoolIndex.setPoolIndex(poolIndexInsn.insn, mutation.newPoolIndex); + + // Clear cache. + poolIndexInsns = null; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/RandomInstructionGenerator.java b/tools/dexfuzz/src/dexfuzz/program/mutators/RandomInstructionGenerator.java new file mode 100644 index 0000000..ff43c7c --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/RandomInstructionGenerator.java @@ -0,0 +1,279 @@ +/* + * 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.MBranchInsn; +import dexfuzz.program.MInsn; +import dexfuzz.program.MutatableCode; +import dexfuzz.program.Mutation; +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.Opcode; +import dexfuzz.rawdex.OpcodeInfo; +import dexfuzz.rawdex.formats.AbstractFormat; +import dexfuzz.rawdex.formats.ContainsConst; +import dexfuzz.rawdex.formats.ContainsPoolIndex; +import dexfuzz.rawdex.formats.ContainsPoolIndex.PoolIndexKind; +import dexfuzz.rawdex.formats.ContainsVRegs; + +import java.util.List; +import java.util.Random; + +public class RandomInstructionGenerator 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 insertionIdx; + public int newOpcode; + public boolean hasConst; + public long constValue; + public boolean hasPoolIndex; + public int poolIndexValue; + public boolean hasVregs; + public int vregCount; + public int vregA; + public int vregB; + public int vregC; + public int branchTargetIdx; + + @Override + public String getString() { + String result = String.format("%d %d %s %d %s %d %s %d %d %d %d %d", + insertionIdx, + newOpcode, + (hasConst ? "T" : "F"), + constValue, + (hasPoolIndex ? "T" : "F"), + poolIndexValue, + (hasVregs ? "T" : "F"), + vregCount, + vregA, + vregB, + vregC, + branchTargetIdx + ); + return result; + } + + @Override + public void parseString(String[] elements) { + insertionIdx = Integer.parseInt(elements[2]); + newOpcode = Integer.parseInt(elements[3]); + hasConst = (elements[4].equals("T")); + constValue = Long.parseLong(elements[5]); + hasPoolIndex = (elements[6].equals("T")); + poolIndexValue = Integer.parseInt(elements[7]); + hasVregs = (elements[8].equals("T")); + vregCount = Integer.parseInt(elements[9]); + vregA = Integer.parseInt(elements[10]); + vregB = Integer.parseInt(elements[11]); + vregC = Integer.parseInt(elements[12]); + branchTargetIdx = Integer.parseInt(elements[13]); + } + } + + // 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 RandomInstructionGenerator() { } + + public RandomInstructionGenerator(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 30; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + // Find the insertion point. + int insertionIdx = 0; + boolean foundInsn = false; + + while (!foundInsn) { + insertionIdx = rng.nextInt(mutatableCode.getInstructionCount()); + MInsn insertionPoint = + mutatableCode.getInstructionAt(insertionIdx); + foundInsn = true; + + // Don't want to insert instructions where there are raw instructions for now. + if (insertionPoint.insn.justRaw) { + foundInsn = false; + } + } + + Opcode newOpcode = null; + int opcodeCount = Opcode.values().length; + boolean foundOpcode = false; + + while (!foundOpcode) { + newOpcode = Opcode.values()[rng.nextInt(opcodeCount)]; + foundOpcode = true; + if (Opcode.isBetween(newOpcode, Opcode.FILLED_NEW_ARRAY, Opcode.FILL_ARRAY_DATA) + || Opcode.isBetween(newOpcode, Opcode.PACKED_SWITCH, Opcode.SPARSE_SWITCH) + || Opcode.isBetween(newOpcode, Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_INTERFACE) + || Opcode.isBetween(newOpcode, + Opcode.INVOKE_VIRTUAL_RANGE, Opcode.INVOKE_INTERFACE_RANGE) + // Can never accept these instructions at compile time. + || Opcode.isBetween(newOpcode, Opcode.IGET_QUICK, Opcode.IPUT_SHORT_QUICK) + // Unused opcodes... + || Opcode.isBetween(newOpcode, Opcode.UNUSED_3E, Opcode.UNUSED_43) + || Opcode.isBetween(newOpcode, Opcode.UNUSED_79, Opcode.UNUSED_7A) + || Opcode.isBetween(newOpcode, Opcode.UNUSED_EF, Opcode.UNUSED_FF)) { + foundOpcode = false; + } + } + + OpcodeInfo newOpcodeInfo = Instruction.getOpcodeInfo(newOpcode); + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.insertionIdx = insertionIdx; + mutation.newOpcode = newOpcodeInfo.value; + + AbstractFormat fmt = newOpcodeInfo.format; + + if (fmt instanceof ContainsConst) { + mutation.hasConst = true; + mutation.constValue = rng.nextLong() % ((ContainsConst)fmt).getConstRange(); + } + if (fmt instanceof ContainsPoolIndex) { + mutation.hasPoolIndex = true; + ContainsPoolIndex containsPoolIndex = (ContainsPoolIndex) fmt; + PoolIndexKind poolIndexKind = containsPoolIndex.getPoolIndexKind(newOpcodeInfo); + int maxPoolIndex = mutatableCode.program.getTotalPoolIndicesByKind(poolIndexKind); + if (maxPoolIndex > 0) { + mutation.poolIndexValue = rng.nextInt(maxPoolIndex); + } else { + mutation.poolIndexValue = 0; + } + } + if (mutatableCode.registersSize == 0) { + mutatableCode.registersSize = 1; + } + if (fmt instanceof ContainsVRegs) { + mutation.hasVregs = true; + ContainsVRegs containsVregs = (ContainsVRegs) fmt; + mutation.vregCount = containsVregs.getVRegCount(); + switch (mutation.vregCount) { + case 3: + mutation.vregC = rng.nextInt(mutatableCode.registersSize); + // fallthrough + case 2: + mutation.vregB = rng.nextInt(mutatableCode.registersSize); + // fallthrough + case 1: + mutation.vregA = rng.nextInt(mutatableCode.registersSize); + break; + default: + Log.errorAndQuit("Invalid number of vregs specified."); + } + } + // If we have some kind of branch, pick a random target. + if (Opcode.isBetween(newOpcode, Opcode.IF_EQ, Opcode.IF_LEZ) + || Opcode.isBetween(newOpcode, Opcode.GOTO, Opcode.GOTO_32)) { + mutation.branchTargetIdx = rng.nextInt(mutatableCode.getInstructionCount()); + } + + 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; + + Opcode newOpcode = Instruction.getOpcodeInfo(mutation.newOpcode).opcode; + + boolean isBranch = false; + if (Opcode.isBetween(newOpcode, Opcode.IF_EQ, Opcode.IF_LEZ) + || Opcode.isBetween(newOpcode, Opcode.GOTO, Opcode.GOTO_32)) { + isBranch = true; + } + + MInsn newInsn = null; + if (!isBranch) { + newInsn = new MInsn(); + } else { + newInsn = new MBranchInsn(); + } + newInsn.insn = new Instruction(); + newInsn.insn.info = Instruction.getOpcodeInfo(mutation.newOpcode); + AbstractFormat fmt = newInsn.insn.info.format; + + if (mutation.hasConst) { + ContainsConst containsConst = (ContainsConst) fmt; + containsConst.setConst(newInsn.insn, mutation.constValue); + } + if (mutation.hasPoolIndex) { + ContainsPoolIndex containsPoolIndex = (ContainsPoolIndex) fmt; + containsPoolIndex.setPoolIndex(newInsn.insn, mutation.poolIndexValue); + } + if (mutation.hasVregs) { + switch (mutation.vregCount) { + case 3: + newInsn.insn.vregC = mutation.vregC; + // fallthrough + case 2: + newInsn.insn.vregB = mutation.vregB; + // fallthrough + case 1: + newInsn.insn.vregA = mutation.vregA; + break; + default: + Log.errorAndQuit("Invalid number of vregs specified."); + } + } + + if (isBranch) { + // We have a branch instruction, point it at its target. + MBranchInsn newBranchInsn = (MBranchInsn) newInsn; + newBranchInsn.target = mutatableCode.getInstructionAt(mutation.branchTargetIdx); + } + + MInsn insertionPoint = + mutatableCode.getInstructionAt(mutation.insertionIdx); + + Log.info("Generated random instruction: " + newInsn + + ", inserting at " + insertionPoint); + + stats.incrementStat("Generated random instruction"); + + mutatableCode.insertInstructionAt(newInsn, mutation.insertionIdx); + + // If we've generated a monitor insn, generate the matching opposing insn. + if (newInsn.insn.info.opcode == Opcode.MONITOR_ENTER) { + MInsn exitInsn = newInsn.clone(); + exitInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MONITOR_EXIT); + mutatableCode.insertInstructionAfter(exitInsn, mutation.insertionIdx); + Log.info("Generated matching monitor-exit: " + exitInsn); + } else if (newInsn.insn.info.opcode == Opcode.MONITOR_EXIT) { + MInsn enterInsn = newInsn.clone(); + enterInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MONITOR_ENTER); + mutatableCode.insertInstructionAt(enterInsn, mutation.insertionIdx); + Log.info("Generated matching monitor-enter: " + enterInsn); + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/SwitchBranchShifter.java b/tools/dexfuzz/src/dexfuzz/program/mutators/SwitchBranchShifter.java new file mode 100644 index 0000000..6981034 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/SwitchBranchShifter.java @@ -0,0 +1,175 @@ +/* + * 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.MSwitchInsn; +import dexfuzz.program.MutatableCode; +import dexfuzz.program.Mutation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class SwitchBranchShifter 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 switchInsnIdx; + public int switchTargetIdx; + public int newTargetIdx; + + @Override + public String getString() { + StringBuilder builder = new StringBuilder(); + builder.append(switchInsnIdx).append(" "); + builder.append(switchTargetIdx).append(" "); + builder.append(newTargetIdx); + return builder.toString(); + } + + @Override + public void parseString(String[] elements) { + switchInsnIdx = Integer.parseInt(elements[2]); + switchTargetIdx = Integer.parseInt(elements[3]); + newTargetIdx = Integer.parseInt(elements[4]); + } + } + + // 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 SwitchBranchShifter() { } + + public SwitchBranchShifter(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 30; + } + + // A cache that should only exist between generateMutation() and applyMutation(), + // or be created at the start of applyMutation(), if we're reading in mutations from + // a file. + private List<MSwitchInsn> switchInsns; + + private void generateCachedSwitchInsns(MutatableCode mutatableCode) { + if (switchInsns != null) { + return; + } + + switchInsns = new ArrayList<MSwitchInsn>(); + + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn instanceof MSwitchInsn) { + switchInsns.add((MSwitchInsn) mInsn); + } + } + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn instanceof MSwitchInsn) { + return true; + } + } + + Log.debug("Method contains no switch instructions."); + return false; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + generateCachedSwitchInsns(mutatableCode); + + // Pick a random switch instruction. + int switchInsnIdx = rng.nextInt(switchInsns.size()); + MSwitchInsn switchInsn = switchInsns.get(switchInsnIdx); + + // Pick a random one of its targets. + int switchTargetIdx = rng.nextInt(switchInsn.targets.size()); + + // Get the original target, find its index. + MInsn oldTargetInsn = switchInsn.targets.get(switchTargetIdx); + int oldTargetInsnIdx = mutatableCode.getInstructionIndex(oldTargetInsn); + + int newTargetIdx = oldTargetInsnIdx; + + int delta = 0; + + // Keep searching for a new index. + while (newTargetIdx == oldTargetInsnIdx) { + // Vary by +/- 2 instructions. + delta = 0; + while (delta == 0) { + delta = (rng.nextInt(5) - 2); + } + + newTargetIdx = oldTargetInsnIdx + delta; + + // Check the new index is legal + if (newTargetIdx < 0) { + newTargetIdx = 0; + } else if (newTargetIdx >= mutatableCode.getInstructionCount()) { + newTargetIdx = mutatableCode.getInstructionCount() - 1; + } + } + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.switchInsnIdx = switchInsnIdx; + mutation.switchTargetIdx = switchTargetIdx; + mutation.newTargetIdx = newTargetIdx; + 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; + + generateCachedSwitchInsns(mutatableCode); + + MSwitchInsn switchInsn = switchInsns.get(mutation.switchInsnIdx); + + // Get the new target. + MInsn newTargetInsn = + mutatableCode.getInstructionAt(mutation.newTargetIdx); + + // Set the new target. + switchInsn.targets.remove(mutation.switchTargetIdx); + switchInsn.targets.add(mutation.switchTargetIdx, newTargetInsn); + + Log.info("Shifted target #" + mutation.switchTargetIdx + " of " + switchInsn + + " to point to " + newTargetInsn); + + stats.incrementStat("Shifted switch target"); + + // Clear cache. + switchInsns = null; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/TryBlockShifter.java b/tools/dexfuzz/src/dexfuzz/program/mutators/TryBlockShifter.java new file mode 100644 index 0000000..1bf6463 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/TryBlockShifter.java @@ -0,0 +1,208 @@ +/* + * 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.MTryBlock; +import dexfuzz.program.MutatableCode; +import dexfuzz.program.Mutation; + +import java.util.List; +import java.util.Random; + +public class TryBlockShifter 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 tryIdx; + public boolean shiftingTryBlock; // false => shifting handler + public boolean shiftingStart; // false => shifting end (try block only) + public boolean shiftingHandlerCatchall; + public int shiftingHandlerIdx; + public int newShiftedInsnIdx; + + @Override + public String getString() { + String result = String.format("%d %s %s %s %d %d", + tryIdx, + (shiftingTryBlock ? "T" : "F"), + (shiftingStart ? "T" : "F"), + (shiftingHandlerCatchall ? "T" : "F"), + shiftingHandlerIdx, + newShiftedInsnIdx + ); + return result; + } + + @Override + public void parseString(String[] elements) { + tryIdx = Integer.parseInt(elements[2]); + shiftingTryBlock = elements[3].equals("T"); + shiftingStart = elements[4].equals("T"); + shiftingHandlerCatchall = elements[5].equals("T"); + shiftingHandlerIdx = Integer.parseInt(elements[6]); + newShiftedInsnIdx = Integer.parseInt(elements[7]); + } + } + + // 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 TryBlockShifter() { } + + public TryBlockShifter(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 40; + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + if (mutatableCode.triesSize > 0) { + return true; + } + + Log.debug("Method contains no tries."); + return false; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + // Pick a random try. + int tryIdx = rng.nextInt(mutatableCode.triesSize); + MTryBlock tryBlock = mutatableCode.mutatableTries.get(tryIdx); + + boolean shiftingTryBlock = rng.nextBoolean(); + boolean shiftingStart = false; + boolean shiftingHandlerCatchall = false; + int shiftingHandlerIdx = -1; + if (shiftingTryBlock) { + // We're shifting the boundaries of the try block + // determine if we shift the start or the end. + shiftingStart = rng.nextBoolean(); + } else { + // We're shifting the start of a handler of the try block. + if (tryBlock.handlers.isEmpty()) { + // No handlers, so we MUST mutate the catchall + shiftingHandlerCatchall = true; + } else if (tryBlock.catchAllHandler != null) { + // There is a catchall handler, so potentially mutate it. + shiftingHandlerCatchall = rng.nextBoolean(); + } + // If we're not going to shift the catchall handler, then + // pick an explicit handler to shift. + if (!shiftingHandlerCatchall) { + shiftingHandlerIdx = rng.nextInt(tryBlock.handlers.size()); + } + } + + // Get the original instruction wherever we're shifting. + MInsn oldInsn = null; + if (shiftingTryBlock && shiftingStart) { + oldInsn = tryBlock.startInsn; + } else if (shiftingTryBlock && !(shiftingStart)) { + oldInsn = tryBlock.endInsn; + } else if (!(shiftingTryBlock) && shiftingHandlerCatchall) { + oldInsn = tryBlock.catchAllHandler; + } else if (!(shiftingTryBlock) && !(shiftingHandlerCatchall) + && (shiftingHandlerIdx != -1)) { + oldInsn = tryBlock.handlers.get(shiftingHandlerIdx); + } else { + Log.errorAndQuit("Faulty logic in TryBlockShifter!"); + } + + // Find the index of this instruction. + int oldInsnIdx = mutatableCode.getInstructionIndex(oldInsn); + + int newInsnIdx = oldInsnIdx; + + int delta = 0; + + // Keep searching for a new index. + while (newInsnIdx == oldInsnIdx) { + // Vary by +/- 2 instructions. + delta = 0; + while (delta == 0) { + delta = (rng.nextInt(5) - 2); + } + + newInsnIdx = oldInsnIdx + delta; + + // Check the new index is legal. + if (newInsnIdx < 0) { + newInsnIdx = 0; + } else if (newInsnIdx >= mutatableCode.getInstructionCount()) { + newInsnIdx = mutatableCode.getInstructionCount() - 1; + } + } + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.tryIdx = tryIdx; + mutation.shiftingTryBlock = shiftingTryBlock; + mutation.shiftingStart = shiftingStart; + mutation.shiftingHandlerCatchall = shiftingHandlerCatchall; + mutation.shiftingHandlerIdx = shiftingHandlerIdx; + mutation.newShiftedInsnIdx = newInsnIdx; + 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; + + MTryBlock tryBlock = mutatableCode.mutatableTries.get(mutation.tryIdx); + + MInsn newInsn = + mutatableCode.getInstructionAt(mutation.newShiftedInsnIdx); + + // Find the right mutatable instruction in try block, and point it at the new instruction. + if (mutation.shiftingTryBlock && mutation.shiftingStart) { + tryBlock.startInsn = newInsn; + Log.info("Shifted the start of try block #" + mutation.tryIdx + + " to be at " + newInsn); + } else if (mutation.shiftingTryBlock && !(mutation.shiftingStart)) { + tryBlock.endInsn = newInsn; + Log.info("Shifted the end of try block #" + mutation.tryIdx + + " to be at " + newInsn); + } else if (!(mutation.shiftingTryBlock) && mutation.shiftingHandlerCatchall) { + tryBlock.catchAllHandler = newInsn; + Log.info("Shifted the catch all handler of try block #" + mutation.tryIdx + + " to be at " + newInsn); + } else if (!(mutation.shiftingTryBlock) && !(mutation.shiftingHandlerCatchall) + && (mutation.shiftingHandlerIdx != -1)) { + tryBlock.handlers.set(mutation.shiftingHandlerIdx, newInsn); + Log.info("Shifted handler #" + mutation.shiftingHandlerIdx + + " of try block #" + mutation.tryIdx + " to be at " + newInsn); + } else { + Log.errorAndQuit("faulty logic in TryBlockShifter"); + } + + stats.incrementStat("Shifted boundary in a try block"); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/VRegChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/VRegChanger.java new file mode 100644 index 0000000..d685f7d --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/VRegChanger.java @@ -0,0 +1,195 @@ +/* + * 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.formats.ContainsVRegs; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class VRegChanger 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 vregInsnIdx; + public int mutatingVreg; + public int newVregValue; + + @Override + public String getString() { + StringBuilder builder = new StringBuilder(); + builder.append(vregInsnIdx).append(" "); + builder.append(mutatingVreg).append(" "); + builder.append(newVregValue); + return builder.toString(); + } + + @Override + public void parseString(String[] elements) { + vregInsnIdx = Integer.parseInt(elements[2]); + mutatingVreg = Integer.parseInt(elements[3]); + newVregValue = Integer.parseInt(elements[4]); + } + } + + // 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 VRegChanger() { } + + public VRegChanger(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 60; + } + + // A cache that should only exist between generateMutation() and applyMutation(), + // or be created at the start of applyMutation(), if we're reading in mutations from + // a file. + private List<MInsn> vregInsns = null; + + private void generateCachedVRegInsns(MutatableCode mutatableCode) { + if (vregInsns != null) { + return; + } + + vregInsns = new ArrayList<MInsn>(); + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn.insn.info.format instanceof ContainsVRegs) { + vregInsns.add(mInsn); + } + } + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + if (mutatableCode.registersSize < 2) { + Log.debug("Impossible to change vregs in a method with fewer than 2 registers."); + return false; + } + + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (mInsn.insn.info.format instanceof ContainsVRegs) { + return true; + } + } + + return false; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + generateCachedVRegInsns(mutatableCode); + + // Pick a random vreg instruction. + int vregInsnIdx = rng.nextInt(vregInsns.size()); + MInsn vregInsn = vregInsns.get(vregInsnIdx); + + // Get the number of VRegs this instruction uses. + int numVregs = ((ContainsVRegs)vregInsn.insn.info.format).getVRegCount(); + + // Pick which vreg to mutate. + int mutatingVreg = rng.nextInt(numVregs); + + // Find the old index. + int oldVregValue = 0; + + switch (mutatingVreg) { + case 0: + oldVregValue = (int) vregInsn.insn.vregA; + break; + case 1: + oldVregValue = (int) vregInsn.insn.vregB; + break; + case 2: + oldVregValue = (int) vregInsn.insn.vregC; + break; + default: + Log.errorAndQuit("Invalid number of vregs reported by a Format."); + } + + // Search for a new vreg value. + int newVregValue = oldVregValue; + while (newVregValue == oldVregValue) { + newVregValue = rng.nextInt(mutatableCode.registersSize); + } + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.vregInsnIdx = vregInsnIdx; + mutation.mutatingVreg = mutatingVreg; + mutation.newVregValue = newVregValue; + 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; + + generateCachedVRegInsns(mutatableCode); + + MInsn vregInsn = vregInsns.get(mutation.vregInsnIdx); + + // Remember what the instruction used to look like. + String oldInsnString = vregInsn.toString(); + + int oldVregValue = 0; + + String vregId = "A"; + switch (mutation.mutatingVreg) { + case 0: + oldVregValue = (int) vregInsn.insn.vregA; + vregInsn.insn.vregA = (long) mutation.newVregValue; + break; + case 1: + oldVregValue = (int) vregInsn.insn.vregB; + vregInsn.insn.vregB = (long) mutation.newVregValue; + vregId = "B"; + break; + case 2: + oldVregValue = (int) vregInsn.insn.vregC; + vregInsn.insn.vregC = (long) mutation.newVregValue; + vregId = "C"; + break; + default: + Log.errorAndQuit("Invalid number of vregs specified in a VRegChanger mutation."); + } + + Log.info("In " + oldInsnString + " changed v" + vregId + ": v" + oldVregValue + + " to v" + mutation.newVregValue); + + stats.incrementStat("Changed a virtual register"); + + // Clear cache. + vregInsns = null; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/ValuePrinter.java b/tools/dexfuzz/src/dexfuzz/program/mutators/ValuePrinter.java new file mode 100644 index 0000000..271aca3 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/ValuePrinter.java @@ -0,0 +1,266 @@ +/* + * 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<Mutation> 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; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationElement.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationElement.java new file mode 100644 index 0000000..6bb2f96 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationElement.java @@ -0,0 +1,44 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class AnnotationElement implements RawDexObject { + public int nameIdx; + public EncodedValue value; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + nameIdx = file.readUleb128(); + (value = new EncodedValue()).read(file); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUleb128(nameIdx); + value.write(file); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.STRING_ID && nameIdx >= insertedIdx) { + nameIdx++; + } + value.incrementIndex(kind, insertedIdx); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationItem.java new file mode 100644 index 0000000..40cf5e4 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationItem.java @@ -0,0 +1,43 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class AnnotationItem implements RawDexObject { + public int visibility; + public EncodedAnnotation annotation; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().getNewOffsettable(file, this); + visibility = file.readUnsignedByte(); + (annotation = new EncodedAnnotation()).read(file); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeByte(visibility); + annotation.write(file); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + annotation.incrementIndex(kind, insertedIdx); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationOffItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationOffItem.java new file mode 100644 index 0000000..b44cd18 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationOffItem.java @@ -0,0 +1,38 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class AnnotationOffItem implements RawDexObject { + public Offset annotationOff; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + annotationOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().tryToWriteOffset(annotationOff, file, false /* ULEB128 */); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetItem.java new file mode 100644 index 0000000..1e1c540 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetItem.java @@ -0,0 +1,50 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class AnnotationSetItem implements RawDexObject { + public int size; + public AnnotationOffItem[] entries; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.alignForwards(4); + file.getOffsetTracker().getNewOffsettable(file, this); + size = file.readUInt(); + entries = new AnnotationOffItem[size]; + for (int i = 0; i < size; i++) { + (entries[i] = new AnnotationOffItem()).read(file); + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.alignForwards(4); + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeUInt(size); + for (AnnotationOffItem annotationOffItem : entries) { + annotationOffItem.write(file); + } + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefItem.java new file mode 100644 index 0000000..cb543de --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefItem.java @@ -0,0 +1,38 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class AnnotationSetRefItem implements RawDexObject { + public Offset annotationsOff; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + annotationsOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().tryToWriteOffset(annotationsOff, file, false /* ULEB128 */); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefList.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefList.java new file mode 100644 index 0000000..1d27053 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefList.java @@ -0,0 +1,50 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class AnnotationSetRefList implements RawDexObject { + public int size; + public AnnotationSetRefItem[] list; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.alignForwards(4); + file.getOffsetTracker().getNewOffsettable(file, this); + size = file.readUInt(); + list = new AnnotationSetRefItem[size]; + for (int i = 0; i < size; i++) { + (list[i] = new AnnotationSetRefItem()).read(file); + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.alignForwards(4); + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeUInt(size); + for (AnnotationSetRefItem annotationSetRefItem : list) { + annotationSetRefItem.write(file); + } + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationsDirectoryItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationsDirectoryItem.java new file mode 100644 index 0000000..8285472 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationsDirectoryItem.java @@ -0,0 +1,101 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class AnnotationsDirectoryItem implements RawDexObject { + public Offset classAnnotationsOff; + public int fieldsSize; + public int annotatedMethodsSize; + public int annotatedParametersSize; + public FieldAnnotation[] fieldAnnotations; + public MethodAnnotation[] methodAnnotations; + public ParameterAnnotation[] parameterAnnotations; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.alignForwards(4); + file.getOffsetTracker().getNewOffsettable(file, this); + classAnnotationsOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + fieldsSize = file.readUInt(); + annotatedMethodsSize = file.readUInt(); + annotatedParametersSize = file.readUInt(); + if (fieldsSize != 0) { + fieldAnnotations = new FieldAnnotation[fieldsSize]; + for (int i = 0; i < fieldsSize; i++) { + (fieldAnnotations[i] = new FieldAnnotation()).read(file); + } + } + if (annotatedMethodsSize != 0) { + methodAnnotations = new MethodAnnotation[annotatedMethodsSize]; + for (int i = 0; i < annotatedMethodsSize; i++) { + (methodAnnotations[i] = new MethodAnnotation()).read(file); + } + } + if (annotatedParametersSize != 0) { + parameterAnnotations = new ParameterAnnotation[annotatedParametersSize]; + for (int i = 0; i < annotatedParametersSize; i++) { + (parameterAnnotations[i] = new ParameterAnnotation()).read(file); + } + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.alignForwards(4); + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.getOffsetTracker().tryToWriteOffset(classAnnotationsOff, file, false /* ULEB128 */); + file.writeUInt(fieldsSize); + file.writeUInt(annotatedMethodsSize); + file.writeUInt(annotatedParametersSize); + if (fieldAnnotations != null) { + for (FieldAnnotation fieldAnnotation : fieldAnnotations) { + fieldAnnotation.write(file); + } + } + if (methodAnnotations != null) { + for (MethodAnnotation methodAnnotation : methodAnnotations) { + methodAnnotation.write(file); + } + } + if (parameterAnnotations != null) { + for (ParameterAnnotation parameterAnnotation : parameterAnnotations) { + parameterAnnotation.write(file); + } + } + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (fieldAnnotations != null) { + for (FieldAnnotation fieldAnnotation : fieldAnnotations) { + fieldAnnotation.incrementIndex(kind, insertedIdx); + } + } + if (methodAnnotations != null) { + for (MethodAnnotation methodAnnotation : methodAnnotations) { + methodAnnotation.incrementIndex(kind, insertedIdx); + } + } + if (parameterAnnotations != null) { + for (ParameterAnnotation parameterAnnotation : parameterAnnotations) { + parameterAnnotation.incrementIndex(kind, insertedIdx); + } + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/ClassDataItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/ClassDataItem.java new file mode 100644 index 0000000..79564bc --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/ClassDataItem.java @@ -0,0 +1,142 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class ClassDataItem implements RawDexObject { + public int staticFieldsSize; + public int instanceFieldsSize; + public int directMethodsSize; + public int virtualMethodsSize; + + public EncodedField[] staticFields; + public EncodedField[] instanceFields; + public EncodedMethod[] directMethods; + public EncodedMethod[] virtualMethods; + + public static class MetaInfo { + public ClassDefItem classDefItem; + } + + public MetaInfo meta = new MetaInfo(); + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().getNewOffsettable(file, this); + staticFieldsSize = file.readUleb128(); + instanceFieldsSize = file.readUleb128(); + directMethodsSize = file.readUleb128(); + virtualMethodsSize = file.readUleb128(); + + staticFields = new EncodedField[staticFieldsSize]; + for (int i = 0; i < staticFieldsSize; i++) { + (staticFields[i] = new EncodedField()).read(file); + } + instanceFields = new EncodedField[instanceFieldsSize]; + for (int i = 0; i < instanceFieldsSize; i++) { + (instanceFields[i] = new EncodedField()).read(file); + } + directMethods = new EncodedMethod[directMethodsSize]; + for (int i = 0; i < directMethodsSize; i++) { + (directMethods[i] = new EncodedMethod()).read(file); + } + virtualMethods = new EncodedMethod[virtualMethodsSize]; + for (int i = 0; i < virtualMethodsSize; i++) { + (virtualMethods[i] = new EncodedMethod()).read(file); + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeUleb128(staticFieldsSize); + file.writeUleb128(instanceFieldsSize); + file.writeUleb128(directMethodsSize); + file.writeUleb128(virtualMethodsSize); + for (int i = 0; i < staticFieldsSize; i++) { + staticFields[i].write(file); + } + for (int i = 0; i < instanceFieldsSize; i++) { + instanceFields[i].write(file); + } + for (int i = 0; i < directMethodsSize; i++) { + directMethods[i].write(file); + } + for (int i = 0; i < virtualMethodsSize; i++) { + virtualMethods[i].write(file); + } + } + + private void incrementEncodedFields(int insertedIdx, EncodedField[] fields) { + int fieldIdx = 0; + for (EncodedField field : fields) { + fieldIdx = field.fieldIdxDiff; + if (fieldIdx >= insertedIdx) { + field.fieldIdxDiff++; + // Only need to increment one, as all the others are diffed from the previous. + break; + } + } + } + + private void incrementEncodedMethods(int insertedIdx, EncodedMethod[] methods) { + int methodIdx = 0; + for (EncodedMethod method : methods) { + methodIdx = method.methodIdxDiff; + if (methodIdx >= insertedIdx) { + method.methodIdxDiff++; + // Only need to increment one, as all the others are diffed from the previous. + break; + } + } + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.FIELD_ID) { + incrementEncodedFields(insertedIdx, staticFields); + incrementEncodedFields(insertedIdx, instanceFields); + } + if (kind == IndexUpdateKind.METHOD_ID) { + incrementEncodedMethods(insertedIdx, directMethods); + incrementEncodedMethods(insertedIdx, virtualMethods); + } + } + + /** + * For a given field index, search this ClassDataItem for a definition of this field. + * @return null if the field isn't in this ClassDataItem. + */ + public EncodedField getEncodedFieldWithIndex(int fieldIdx) { + int searchFieldIdx = 0; + for (EncodedField field : instanceFields) { + searchFieldIdx += field.fieldIdxDiff; + if (searchFieldIdx == fieldIdx) { + return field; + } + } + searchFieldIdx = 0; + for (EncodedField field : staticFields) { + searchFieldIdx += field.fieldIdxDiff; + if (searchFieldIdx == fieldIdx) { + return field; + } + } + return null; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/ClassDefItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/ClassDefItem.java new file mode 100644 index 0000000..5e68d24 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/ClassDefItem.java @@ -0,0 +1,77 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class ClassDefItem implements RawDexObject { + public static int data_size = 0x20; + + public int classIdx; + public int accessFlags; + public int superclassIdx; + public Offset interfacesOff; + public int sourceFileIdx; + public Offset annotationsOff; + public Offset classDataOff; + public Offset staticValuesOff; + + public static class MetaInfo { + public ClassDataItem classDataItem; + } + + public MetaInfo meta = new MetaInfo(); + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().getNewOffsettable(file, this); + classIdx = file.readUInt(); + accessFlags = file.readUInt(); + superclassIdx = file.readUInt(); + interfacesOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + sourceFileIdx = file.readUInt(); + annotationsOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + classDataOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + staticValuesOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeUInt(classIdx); + file.writeUInt(accessFlags); + file.writeUInt(superclassIdx); + file.getOffsetTracker().tryToWriteOffset(interfacesOff, file, false /* ULEB128 */); + file.writeUInt(sourceFileIdx); + file.getOffsetTracker().tryToWriteOffset(annotationsOff, file, false /* ULEB128 */); + file.getOffsetTracker().tryToWriteOffset(classDataOff, file, false /* ULEB128 */); + file.getOffsetTracker().tryToWriteOffset(staticValuesOff, file, false /* ULEB128 */); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.TYPE_ID && classIdx >= insertedIdx) { + classIdx++; + } + if (kind == IndexUpdateKind.TYPE_ID && superclassIdx >= insertedIdx) { + superclassIdx++; + } + if (kind == IndexUpdateKind.STRING_ID && sourceFileIdx >= insertedIdx) { + sourceFileIdx++; + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/CodeItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/CodeItem.java new file mode 100644 index 0000000..af3c377 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/CodeItem.java @@ -0,0 +1,205 @@ +/* + * 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.rawdex; + +import dexfuzz.Log; +import dexfuzz.program.MutatableCode; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +public class CodeItem implements RawDexObject { + public short registersSize; + public short insSize; + public short outsSize; + public short triesSize; + public int debugInfoOff; // NB: this is a special case + public int insnsSize; + public List<Instruction> insns; + public TryItem[] tries; + public EncodedCatchHandlerList handlers; + + private MutatableCode mutatableCode; + + public static class MethodMetaInfo { + public String methodName; + public boolean isStatic; + public String shorty; + } + + public MethodMetaInfo meta = new MethodMetaInfo(); + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.alignForwards(4); + file.getOffsetTracker().getNewOffsettable(file, this); + registersSize = file.readUShort(); + insSize = file.readUShort(); + outsSize = file.readUShort(); + triesSize = file.readUShort(); + debugInfoOff = file.readUInt(); + insnsSize = file.readUInt(); + populateInstructionList(file); + if (triesSize > 0) { + if ((insnsSize % 2) != 0) { + // Consume padding. + file.readUShort(); + } + tries = new TryItem[triesSize]; + for (int i = 0; i < triesSize; i++) { + (tries[i] = new TryItem()).read(file); + } + (handlers = new EncodedCatchHandlerList()).read(file); + } + } + + private void populateInstructionList(DexRandomAccessFile file) throws IOException { + insns = new LinkedList<Instruction>(); + long insnsOffset = file.getFilePointer(); + if (insnsOffset != 0) { + long finger = insnsOffset; + long insnsEnd = insnsOffset + (2 * insnsSize); + + while (finger < insnsEnd) { + file.seek(finger); + Instruction newInsn = new Instruction(); + newInsn.read(file); + insns.add(newInsn); + finger += (2 * newInsn.getSize()); + } + + file.seek(finger); + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.alignForwards(4); + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeUShort(registersSize); + file.writeUShort(insSize); + file.writeUShort(outsSize); + file.writeUShort(triesSize); + // We do not support retaining debug info currently. + file.writeUInt(0 /*debug_info_off*/); + file.writeUInt(insnsSize); + for (Instruction insn : insns) { + insn.write(file); + } + if (triesSize > 0) { + if ((insnsSize % 2) != 0) { + // produce padding + file.writeUShort((short) 0); + } + for (TryItem tryItem : tries) { + tryItem.write(file); + } + handlers.write(file); + } + } + + /** + * CodeTranslator should call this to notify a CodeItem about its + * mutatable code, so it can always get the "latest" view of its + * instructions. + */ + public void registerMutatableCode(MutatableCode mutatableCode) { + this.mutatableCode = mutatableCode; + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.TYPE_ID && triesSize > 0) { + // EncodedCatchHandlerList (well, the EncodedTypeAddrPairs it owns) + // are only interested in TYPE_IDs. + handlers.incrementIndex(kind, insertedIdx); + } + + if (kind == IndexUpdateKind.PROTO_ID) { + // The only kind we can't encounter in an instruction. + return; + } + + List<Instruction> insnsToIncrement = insns; + + // If we have an associated MutatableCode, then it may have created some new insns + // that we won't know about yet, during the mutation phase. + // + // Ask for the latest view of the insns. + if (mutatableCode != null) { + insnsToIncrement = mutatableCode.requestLatestInstructions(); + } + + for (Instruction insn : insnsToIncrement) { + Opcode opcode = insn.info.opcode; + switch (kind) { + case STRING_ID: + if (opcode == Opcode.CONST_STRING || opcode == Opcode.CONST_STRING_JUMBO) { + // STRING@BBBB + if (insn.vregB >= insertedIdx) { + insn.vregB++; + } + } + break; + case TYPE_ID: + if (opcode == Opcode.CONST_CLASS + || opcode == Opcode.CHECK_CAST + || opcode == Opcode.NEW_INSTANCE + || opcode == Opcode.FILLED_NEW_ARRAY + || opcode == Opcode.FILLED_NEW_ARRAY_RANGE) { + // TYPE@BBBB + if (insn.vregB >= insertedIdx) { + insn.vregB++; + } + } else if (opcode == Opcode.INSTANCE_OF || opcode == Opcode.NEW_ARRAY) { + // TYPE@CCCC + if (insn.vregC >= insertedIdx) { + insn.vregC++; + } + } + break; + case FIELD_ID: + if (Opcode.isBetween(opcode, Opcode.SGET, Opcode.SPUT_SHORT)) { + // FIELD@BBBB + if (insn.vregB >= insertedIdx) { + insn.vregB++; + } + } else if (Opcode.isBetween(opcode, Opcode.IGET, Opcode.IPUT_SHORT)) { + // FIELD@CCCC + if (insn.vregC >= insertedIdx) { + insn.vregC++; + } + } + break; + case METHOD_ID: + if (Opcode.isBetween(opcode, Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_INTERFACE) + || Opcode.isBetween(opcode, + Opcode.INVOKE_VIRTUAL_RANGE, Opcode.INVOKE_INTERFACE_RANGE)) { + // METHOD@BBBB + if (insn.vregB >= insertedIdx) { + insn.vregB++; + } + } + break; + default: + Log.errorAndQuit("Unexpected IndexUpdateKind requested " + + "in Instruction.incrementIndex()"); + } + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/DebugInfoItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/DebugInfoItem.java new file mode 100644 index 0000000..922ee58 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/DebugInfoItem.java @@ -0,0 +1,47 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +// Right now we are not parsing debug_info_item, just take the raw size +public class DebugInfoItem implements RawDexObject { + private int size; + private byte[] data; + + public DebugInfoItem(int size) { + this.size = size; + } + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().getNewOffsettable(file, this); + data = new byte[size]; + file.read(data); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.write(data); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/DexRandomAccessFile.java b/tools/dexfuzz/src/dexfuzz/rawdex/DexRandomAccessFile.java new file mode 100644 index 0000000..ec75585 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/DexRandomAccessFile.java @@ -0,0 +1,319 @@ +/* + * 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.rawdex; + +import dexfuzz.Log; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * An extension to RandomAccessFile that allows reading/writing + * DEX files in little-endian form, the variable-length LEB format + * and also provides word-alignment functions. + */ +public class DexRandomAccessFile extends RandomAccessFile { + private OffsetTracker offsetTracker; + + public OffsetTracker getOffsetTracker() { + return offsetTracker; + } + + public void setOffsetTracker(OffsetTracker offsetTracker) { + this.offsetTracker = offsetTracker; + } + + /** + * Constructor, passes straight on to RandomAccessFile currently. + * @param filename The file to open. + * @param mode Strings "r" or "rw" work best. + */ + public DexRandomAccessFile(String filename, String mode) + throws FileNotFoundException { + super(filename, mode); + } + + /** + * @return A 16-bit number, read from the file as little-endian. + */ + public short readUShort() throws IOException { + int b1 = readUnsignedByte(); + int b2 = readUnsignedByte(); + return (short) ((b2 << 8) | b1); + } + + /** + * @param value A 16-bit number to be written to the file in little-endian. + */ + public void writeUShort(short value) throws IOException { + int b1 = value & 0xff; + int b2 = (value & 0xff00) >> 8; + writeByte(b1); + writeByte(b2); + } + + /** + * @return A 32-bit number, read from the file as little-endian. + */ + public int readUInt() throws IOException { + int b1 = readUnsignedByte(); + int b2 = readUnsignedByte(); + int b3 = readUnsignedByte(); + int b4 = readUnsignedByte(); + return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1; + } + + /** + * @param value A 32-bit number to be written to the file in little-endian. + */ + public void writeUInt(int value) throws IOException { + int b1 = value & 0xff; + writeByte(b1); + int b2 = (value & 0xff00) >> 8; + writeByte(b2); + int b3 = (value & 0xff0000) >> 16; + writeByte(b3); + int b4 = (value & 0xff000000) >> 24; + writeByte(b4); + } + + /** + * @return An up to 32-bit number, read from the file in ULEB128 form. + */ + public int readUleb128() throws IOException { + int shift = 0; + int value = 0; + int rawByte = readUnsignedByte(); + boolean done = false; + while (!done) { + // Get the lower seven bits. + // 0x7f = 0111 1111 + value |= ((rawByte & 0x7f) << shift); + shift += 7; + // Check the 8th bit - if it's 0, we're done. + // 0x80 = 1000 0000 + if ((rawByte & 0x80) == 0) { + done = true; + } else { + rawByte = readUnsignedByte(); + } + } + return value; + } + + /** + * @param value A 32-bit number to be written to the file in ULEB128 form. + */ + public void writeUleb128(int value) throws IOException { + if (value == 0) { + writeByte(0); + return; + } + + while (value != 0) { + int marker = 1; + // If we're down to the last 7 bits, the marker will be 0. + if ((value & 0xffffff80) == 0) { + marker = 0; + } + // Get the lowest 7 bits, add on the marker in the high bit. + int nextByte = value & 0x7f | (marker << 7); + writeByte(nextByte); + value >>>= 7; + } + } + + /** + * Write out ULEB128 value always using 5 bytes. + * A version of ULEB128 that will always write out 5 bytes, because this + * value will be patched later, and if we used a smaller encoding, the new value + * may overflow the previously selected encoding size. + * The largest encoding for 0 in ULEB128 would be: + * 0x80 0x80 0x80 0x80 0x00 + * and for 1 would be: + * 0x81 0x80 0x80 0x80 0x00 + */ + public void writeLargestUleb128(int value) throws IOException { + Log.debug("Writing " + value + " using the largest possible ULEB128 encoding."); + if (value == 0) { + writeByte(0x80); + writeByte(0x80); + writeByte(0x80); + writeByte(0x80); + writeByte(0x0); + return; + } + + for (int i = 0; i < 5; i++) { + int marker = 1; + // If we're writing the 5th byte, the marker is 0. + if (i == 4) { + marker = 0; + } + // Get the lowest 7 bits, add on the marker in the high bit. + int nextByte = value & 0x7f | (marker << 7); + writeByte(nextByte); + value >>>= 7; + } + } + + /** + * @return An up to 32-bit number, read from the file in SLEB128 form. + */ + public int readSleb128() throws IOException { + int shift = 0; + int value = 0; + int rawByte = readUnsignedByte(); + boolean done = false; + boolean mustSignExtend = false; + while (!done) { + // Get the lower seven bits. + // 0x7f = 0111 1111 + value |= ((rawByte & 0x7f) << shift); + shift += 7; + // Check the 8th bit - if it's 0, we're done. + // 0x80 = 1000 0000 + if ((rawByte & 0x80) == 0) { + // Check the 7th bit - if it's a 1, we need to sign extend. + if ((rawByte & 0x60) != 0) { + mustSignExtend = true; + } + done = true; + } else { + rawByte = readUnsignedByte(); + } + } + if (mustSignExtend) { + // Example: + // say we shifted 7 bits, we need + // to make all the upper 25 bits 1s. + // load a 1... + // 00000000 00000000 00000000 00000001 + // << 7 + // 00000000 00000000 00000000 10000000 + // - 1 + // 00000000 00000000 00000000 01111111 + // ~ + // 11111111 11111111 11111111 10000000 + int upperOnes = ~((1 << shift) - 1); + value |= (upperOnes); + } + return value; + } + + /** + * @param value A 32-bit number to be written to the file in SLEB128 form. + */ + public void writeSleb128(int value) throws IOException { + if (value == 0) { + writeByte(0); + return; + } + if (value > 0) { + writeUleb128(value); + return; + } + if (value == -1) { + writeByte(0x7f); + } + + // When it's all 1s (0xffffffff), we're done! + while (value != 0xffffffff) { + int marker = 1; + // If we're down to the last 7 bits (i.e., shifting a further 7 is all 1s), + // the marker will be 0. + if ((value >> 7) == 0xffffffff) { + marker = 0; + } + // Get the lowest 7 bits, add on the marker in the high bit. + int nextByte = value & 0x7f | (marker << 7); + writeByte(nextByte); + value >>= 7; + } + } + + /** + * In DEX format, strings are in MUTF-8 format, the first ULEB128 value is the decoded size + * (i.e., string.length), and then follows a null-terminated series of characters. + * @param decodedSize The ULEB128 value that should have been read just before this. + * @return The raw bytes of the string, not including the null character. + */ + public byte[] readDexUtf(int decodedSize) throws IOException { + // In the dex MUTF-8, the encoded size can never be larger than 3 times + // the actual string's length (which is the ULEB128 value just before this + // string, the "decoded size") + + // Therefore, allocate as much space as we might need. + byte[] str = new byte[decodedSize * 3]; + + // Get our first byte. + int encodedSize = 0; + byte rawByte = readByte(); + + // Keep reading until we find the end marker. + while (rawByte != 0) { + str[encodedSize++] = rawByte; + rawByte = readByte(); + } + + // Copy everything we read into str into the correctly-sized actual string. + byte[] actualString = new byte[encodedSize]; + for (int i = 0; i < encodedSize; i++) { + actualString[i] = str[i]; + } + + return actualString; + } + + /** + * Writes out raw bytes that would have been read by readDexUTF(). + * Will automatically write out the null-byte at the end. + * @param data Bytes to be written out. + */ + public void writeDexUtf(byte[] data) throws IOException { + write(data); + // Remember to add the end marker. + writeByte(0); + } + + /** + * Align the file handle's seek pointer to the next N bytes. + * @param alignment N to align to. + */ + public void alignForwards(int alignment) throws IOException { + long offset = getFilePointer(); + long mask = alignment - 1; + if ((offset & mask) != 0) { + int extra = alignment - (int) (offset & mask); + seek(offset + extra); + } + } + + /** + * Align the file handle's seek pointer backwards to the previous N bytes. + * @param alignment N to align to. + */ + public void alignBackwards(int alignment) throws IOException { + long offset = getFilePointer(); + long mask = alignment - 1; + if ((offset & mask) != 0) { + offset &= (~mask); + seek(offset); + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedAnnotation.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedAnnotation.java new file mode 100644 index 0000000..085a4a8 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedAnnotation.java @@ -0,0 +1,61 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class EncodedAnnotation implements RawDexObject { + public int typeIdx; + public int size; + public AnnotationElement[] elements; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + typeIdx = file.readUleb128(); + size = file.readUleb128(); + if (size != 0) { + elements = new AnnotationElement[size]; + for (int i = 0; i < size; i++) { + (elements[i] = new AnnotationElement()).read(file); + } + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUleb128(typeIdx); + file.writeUleb128(size); + if (size != 0) { + for (AnnotationElement annotationElement : elements) { + annotationElement.write(file); + } + } + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.TYPE_ID && typeIdx >= insertedIdx) { + typeIdx++; + } + if (size != 0) { + for (AnnotationElement element : elements) { + element.incrementIndex(kind, insertedIdx); + } + } + } + +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedArray.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedArray.java new file mode 100644 index 0000000..267ff09 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedArray.java @@ -0,0 +1,54 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class EncodedArray implements RawDexObject { + public int size; + public EncodedValue[] values; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + size = file.readUleb128(); + if (size != 0) { + values = new EncodedValue[size]; + for (int i = 0; i < size; i++) { + (values[i] = new EncodedValue()).read(file); + } + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUleb128(size); + if (size != 0) { + for (EncodedValue encodedValue : values) { + encodedValue.write(file); + } + } + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (size != 0) { + for (EncodedValue value : values) { + value.incrementIndex(kind, insertedIdx); + } + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedArrayItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedArrayItem.java new file mode 100644 index 0000000..e461a54 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedArrayItem.java @@ -0,0 +1,40 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class EncodedArrayItem implements RawDexObject { + public EncodedArray value; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().getNewOffsettable(file, this); + (value = new EncodedArray()).read(file); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + value.write(file); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandler.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandler.java new file mode 100644 index 0000000..83786c8 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandler.java @@ -0,0 +1,62 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class EncodedCatchHandler implements RawDexObject { + public int size; + public EncodedTypeAddrPair[] handlers; + public int catchAllAddr; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + size = file.readSleb128(); + int absoluteSize = Math.abs(size); + if (absoluteSize > 0) { + handlers = new EncodedTypeAddrPair[absoluteSize]; + for (int i = 0; i < Math.abs(size); i++) { + (handlers[i] = new EncodedTypeAddrPair()).read(file); + } + } + if (size <= 0) { + catchAllAddr = file.readUleb128(); + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeSleb128(size); + if (handlers != null) { + for (EncodedTypeAddrPair encodedTypeAddrPair : handlers) { + encodedTypeAddrPair.write(file); + } + } + if (size <= 0) { + file.writeUleb128(catchAllAddr); + } + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (handlers != null) { + for (EncodedTypeAddrPair handler : handlers) { + handler.incrementIndex(kind, insertedIdx); + } + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandlerList.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandlerList.java new file mode 100644 index 0000000..5619b5f --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandlerList.java @@ -0,0 +1,48 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class EncodedCatchHandlerList implements RawDexObject { + public int size; + public EncodedCatchHandler[] list; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + size = file.readUleb128(); + list = new EncodedCatchHandler[size]; + for (int i = 0; i < size; i++) { + (list[i] = new EncodedCatchHandler()).read(file); + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUleb128(size); + for (EncodedCatchHandler encodedCatchHandler : list) { + encodedCatchHandler.write(file); + } + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + for (EncodedCatchHandler handler : list) { + handler.incrementIndex(kind, insertedIdx); + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedField.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedField.java new file mode 100644 index 0000000..18c1d43 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedField.java @@ -0,0 +1,79 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class EncodedField implements RawDexObject { + public int fieldIdxDiff; + public int accessFlags; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + fieldIdxDiff = file.readUleb128(); + accessFlags = file.readUleb128(); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUleb128(fieldIdxDiff); + file.writeUleb128(accessFlags); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + // NB: our idx_diff is handled in ClassDataItem... + } + + public boolean isVolatile() { + return ((accessFlags & Flag.ACC_VOLATILE.getValue()) != 0); + } + + /** + * Set/unset the volatile flag for this EncodedField. + */ + public void setVolatile(boolean turnOn) { + if (turnOn) { + accessFlags |= Flag.ACC_VOLATILE.getValue(); + } else { + accessFlags &= ~(Flag.ACC_VOLATILE.getValue()); + } + } + + private static enum Flag { + ACC_PUBLIC(0x1), + ACC_PRIVATE(0x2), + ACC_PROTECTED(0x4), + ACC_STATIC(0x8), + ACC_FINAL(0x10), + ACC_VOLATILE(0x40), + ACC_TRANSIENT(0x80), + ACC_SYNTHETIC(0x1000), + ACC_ENUM(0x4000); + + private int value; + + private Flag(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedMethod.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedMethod.java new file mode 100644 index 0000000..3a8163a --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedMethod.java @@ -0,0 +1,95 @@ +/* + * 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.rawdex; + +import dexfuzz.Log; + +import java.io.IOException; + +public class EncodedMethod implements RawDexObject { + public int methodIdxDiff; + public int accessFlags; + public Offset codeOff; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + methodIdxDiff = file.readUleb128(); + accessFlags = file.readUleb128(); + codeOff = file.getOffsetTracker().getNewOffset(file.readUleb128()); + if (isNative()) { + Log.errorAndQuit("Sorry, DEX files with native methods are not supported yet."); + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUleb128(methodIdxDiff); + file.writeUleb128(accessFlags); + file.getOffsetTracker().tryToWriteOffset(codeOff, file, true /* ULEB128 */); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + // NB: our idx_diff is handled in ClassDataItem... + } + + public boolean isStatic() { + return ((accessFlags & Flag.ACC_STATIC.getValue()) != 0); + } + + public boolean isNative() { + return ((accessFlags & Flag.ACC_NATIVE.getValue()) != 0); + } + + /** + * Set/unset the static flag for this EncodedMethod. + */ + public void setStatic(boolean turnOn) { + if (turnOn) { + accessFlags |= Flag.ACC_STATIC.getValue(); + } else { + accessFlags &= ~(Flag.ACC_STATIC.getValue()); + } + } + + private static enum Flag { + ACC_PUBLIC(0x1), + ACC_PRIVATE(0x2), + ACC_PROTECTED(0x4), + ACC_STATIC(0x8), + ACC_FINAL(0x10), + ACC_SYNCHRONIZED(0x20), + ACC_VARARGS(0x80), + ACC_NATIVE(0x100), + ACC_ABSTRACT(0x400), + ACC_STRICT(0x800), + ACC_SYNTHETIC(0x1000), + ACC_ENUM(0x4000), + ACC_CONSTRUCTOR(0x10000); + + private int value; + + private Flag(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedTypeAddrPair.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedTypeAddrPair.java new file mode 100644 index 0000000..ea49ae1 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedTypeAddrPair.java @@ -0,0 +1,43 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class EncodedTypeAddrPair implements RawDexObject { + public int typeIdx; + public int addr; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + typeIdx = file.readUleb128(); + addr = file.readUleb128(); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUleb128(typeIdx); + file.writeUleb128(addr); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.TYPE_ID && typeIdx >= insertedIdx) { + typeIdx++; + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedValue.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedValue.java new file mode 100644 index 0000000..fdf133f --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedValue.java @@ -0,0 +1,98 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class EncodedValue implements RawDexObject { + public byte valueArg; + public byte valueType; + public byte[] value; + public EncodedArray encodedArray; + public EncodedAnnotation encodedAnnotation; + + private static final byte VALUE_BYTE = 0x00; + private static final byte VALUE_ARRAY = 0x1c; + private static final byte VALUE_ANNOTATION = 0x1d; + private static final byte VALUE_NULL = 0x1e; + private static final byte VALUE_BOOLEAN = 0x1f; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + int valueArgAndType = file.readUnsignedByte(); + + // Get lower 5 bits. + valueType = (byte) (valueArgAndType & 0x1f); + // Get upper 3 bits. + valueArg = (byte) ((valueArgAndType & 0xe0) >> 5); + + int size = 0; + + switch (valueType) { + case VALUE_BYTE: + size = 1; + break; + case VALUE_ARRAY: + (encodedArray = new EncodedArray()).read(file); + size = 0; // So we don't read into value. + break; + case VALUE_ANNOTATION: + (encodedAnnotation = new EncodedAnnotation()).read(file); + size = 0; // So we don't read into value. + break; + case VALUE_NULL: + case VALUE_BOOLEAN: + // No value + size = 0; + break; + default: + // All others encode value_arg as (size - 1), so... + size = valueArg + 1; + break; + } + + if (size != 0) { + value = new byte[size]; + for (int i = 0; i < size; i++) { + value[i] = file.readByte(); + } + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + int valueArgAndType = ((valueType) | (valueArg << 5)); + file.writeByte(valueArgAndType); + + if (encodedArray != null) { + encodedArray.write(file); + } else if (encodedAnnotation != null) { + encodedAnnotation.write(file); + } else if (value != null) { + file.write(value); + } + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (encodedArray != null) { + encodedArray.incrementIndex(kind, insertedIdx); + } else if (encodedAnnotation != null) { + encodedAnnotation.incrementIndex(kind, insertedIdx); + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/FieldAnnotation.java b/tools/dexfuzz/src/dexfuzz/rawdex/FieldAnnotation.java new file mode 100644 index 0000000..98f812e --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/FieldAnnotation.java @@ -0,0 +1,43 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class FieldAnnotation implements RawDexObject { + public int fieldIdx; + public Offset annotationsOff; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + fieldIdx = file.readUInt(); + annotationsOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUInt(fieldIdx); + file.getOffsetTracker().tryToWriteOffset(annotationsOff, file, false /* ULEB128 */); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.FIELD_ID && fieldIdx >= insertedIdx) { + fieldIdx++; + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/FieldIdItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/FieldIdItem.java new file mode 100644 index 0000000..75f2078 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/FieldIdItem.java @@ -0,0 +1,54 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class FieldIdItem implements RawDexObject { + public short classIdx; + public short typeIdx; + public int nameIdx; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().getNewOffsettable(file, this); + classIdx = file.readUShort(); + typeIdx = file.readUShort(); + nameIdx = file.readUInt(); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeUShort(classIdx); + file.writeUShort(typeIdx); + file.writeUInt(nameIdx); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.TYPE_ID && classIdx >= insertedIdx) { + classIdx++; + } + if (kind == IndexUpdateKind.TYPE_ID && typeIdx >= insertedIdx) { + typeIdx++; + } + if (kind == IndexUpdateKind.STRING_ID && nameIdx >= insertedIdx) { + nameIdx++; + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/HeaderItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/HeaderItem.java new file mode 100644 index 0000000..e6b290c --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/HeaderItem.java @@ -0,0 +1,128 @@ +/* + * 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.rawdex; + +import dexfuzz.Log; + +import java.io.IOException; + +public class HeaderItem implements RawDexObject { + public byte[] magic; + public int checksum; + public byte[] signature; // Verification doesn't depend on this, so we don't update it. + public int fileSize; + public int headerSize; + public int endianTag; + public int linkSize; + public Offset linkOff; + public Offset mapOff; + public int stringIdsSize; + public Offset stringIdsOff; + public int typeIdsSize; + public Offset typeIdsOff; + public int protoIdsSize; + public Offset protoIdsOff; + public int fieldIdsSize; + public Offset fieldIdsOff; + public int methodIdsSize; + public Offset methodIdsOff; + public int classDefsSize; + public Offset classDefsOff; + public int dataSize; + public Offset dataOff; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().getNewOffsettable(file, this); + magic = new byte[8]; + for (int i = 0; i < 8; i++) { + magic[i] = file.readByte(); + } + checksum = file.readUInt(); + signature = new byte[20]; + for (int i = 0; i < 20; i++) { + signature[i] = file.readByte(); + } + fileSize = file.readUInt(); + headerSize = file.readUInt(); + endianTag = file.readUInt(); + linkSize = file.readUInt(); + linkOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + mapOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + stringIdsSize = file.readUInt(); + stringIdsOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + typeIdsSize = file.readUInt(); + typeIdsOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + protoIdsSize = file.readUInt(); + protoIdsOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + fieldIdsSize = file.readUInt(); + fieldIdsOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + methodIdsSize = file.readUInt(); + methodIdsOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + classDefsSize = file.readUInt(); + classDefsOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + dataSize = file.readUInt(); + dataOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + if (headerSize != 0x70) { + Log.errorAndQuit("Invalid header size in header."); + } + if (file.getFilePointer() != headerSize) { + Log.errorAndQuit("Read a different amount than expected in header: " + + file.getFilePointer()); + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + for (int i = 0; i < 8; i++) { + file.writeByte(magic[i]); + } + // Will be recalculated later! + file.writeUInt(checksum); + for (int i = 0; i < 20; i++) { + file.writeByte(signature[i]); + } + // Will be recalculated later! + file.writeUInt(fileSize); + file.writeUInt(headerSize); + file.writeUInt(endianTag); + file.writeUInt(linkSize); + file.getOffsetTracker().tryToWriteOffset(linkOff, file, false /* ULEB128 */); + file.getOffsetTracker().tryToWriteOffset(mapOff, file, false /* ULEB128 */); + file.writeUInt(stringIdsSize); + file.getOffsetTracker().tryToWriteOffset(stringIdsOff, file, false /* ULEB128 */); + file.writeUInt(typeIdsSize); + file.getOffsetTracker().tryToWriteOffset(typeIdsOff, file, false /* ULEB128 */); + file.writeUInt(protoIdsSize); + file.getOffsetTracker().tryToWriteOffset(protoIdsOff, file, false /* ULEB128 */); + file.writeUInt(fieldIdsSize); + file.getOffsetTracker().tryToWriteOffset(fieldIdsOff, file, false /* ULEB128 */); + file.writeUInt(methodIdsSize); + file.getOffsetTracker().tryToWriteOffset(methodIdsOff, file, false /* ULEB128 */); + file.writeUInt(classDefsSize); + file.getOffsetTracker().tryToWriteOffset(classDefsOff, file, false /* ULEB128 */); + // will be recalculated later + file.writeUInt(dataSize); + file.getOffsetTracker().tryToWriteOffset(dataOff, file, false /* ULEB128 */); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/Instruction.java b/tools/dexfuzz/src/dexfuzz/rawdex/Instruction.java new file mode 100644 index 0000000..2dda78f --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/Instruction.java @@ -0,0 +1,584 @@ +/* + * 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.rawdex; + +import dexfuzz.Log; +import dexfuzz.rawdex.formats.AbstractFormat; +import dexfuzz.rawdex.formats.ContainsConst; +import dexfuzz.rawdex.formats.ContainsPoolIndex; +import dexfuzz.rawdex.formats.ContainsTarget; +import dexfuzz.rawdex.formats.ContainsVRegs; +import dexfuzz.rawdex.formats.Format10t; +import dexfuzz.rawdex.formats.Format10x; +import dexfuzz.rawdex.formats.Format11n; +import dexfuzz.rawdex.formats.Format11x; +import dexfuzz.rawdex.formats.Format12x; +import dexfuzz.rawdex.formats.Format20t; +import dexfuzz.rawdex.formats.Format21c; +import dexfuzz.rawdex.formats.Format21h; +import dexfuzz.rawdex.formats.Format21s; +import dexfuzz.rawdex.formats.Format21t; +import dexfuzz.rawdex.formats.Format22b; +import dexfuzz.rawdex.formats.Format22c; +import dexfuzz.rawdex.formats.Format22s; +import dexfuzz.rawdex.formats.Format22t; +import dexfuzz.rawdex.formats.Format22x; +import dexfuzz.rawdex.formats.Format23x; +import dexfuzz.rawdex.formats.Format30t; +import dexfuzz.rawdex.formats.Format31c; +import dexfuzz.rawdex.formats.Format31i; +import dexfuzz.rawdex.formats.Format31t; +import dexfuzz.rawdex.formats.Format32x; +import dexfuzz.rawdex.formats.Format35c; +import dexfuzz.rawdex.formats.Format3rc; +import dexfuzz.rawdex.formats.Format51l; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class Instruction implements RawDexObject { + // Only used by Format35* instructions + public static class InvokeFormatInfo { + public byte vregD; + public byte vregE; + public byte vregF; + public byte vregG; + } + + // Immutable information about this class of instruction. + public OpcodeInfo info; + + // The raw bytes of the instruction. + // Only used during reading, and writing out is done from the decoded instruction data. + // Except in the case of the 3 "data" instructions. + public byte[] rawBytes; + + public static final int RAW_TYPE_PACKED_SWITCH_DATA = 1; + public static final int RAW_TYPE_SPARSE_SWITCH_DATA = 2; + public static final int RAW_TYPE_FILL_ARRAY_DATA_DATA = 3; + + public int rawType; + public boolean justRaw; + public int rawSize; + + public long vregA = 0; + public long vregB = 0; + public long vregC = 0; + + public InvokeFormatInfo invokeFormatInfo; + + /** + * Clone an instruction. + */ + public Instruction clone() { + Instruction newInsn = new Instruction(); + // If we've generated a new instruction, we won't have calculated its raw array. + if (newInsn.rawBytes != null) { + newInsn.rawBytes = new byte[rawBytes.length]; + for (int i = 0; i < rawBytes.length; i++) { + newInsn.rawBytes[i] = rawBytes[i]; + } + } + newInsn.justRaw = justRaw; + newInsn.rawType = rawType; + newInsn.rawSize = rawSize; + + newInsn.vregA = vregA; + newInsn.vregB = vregB; + newInsn.vregC = vregC; + newInsn.info = info; + if (invokeFormatInfo != null) { + newInsn.invokeFormatInfo = new InvokeFormatInfo(); + newInsn.invokeFormatInfo.vregD = invokeFormatInfo.vregD; + newInsn.invokeFormatInfo.vregE = invokeFormatInfo.vregE; + newInsn.invokeFormatInfo.vregF = invokeFormatInfo.vregF; + newInsn.invokeFormatInfo.vregG = invokeFormatInfo.vregG; + } + return newInsn; + } + + @Override + public void read(DexRandomAccessFile file) throws IOException { + // Remember the offset, so after reading the opcode, we can read the whole + // insn into raw_bytes. + long offset = file.getFilePointer(); + int opcodeValue = readOpcode(file); + info = getOpcodeInfo(opcodeValue); + if (info == null) { + Log.errorAndQuit("Couldn't find OpcodeInfo for opcode with value: " + + opcodeValue); + } + + rawBytes = new byte[2 * getSize()]; + file.seek(offset); + file.read(rawBytes); + + vregA = info.format.getA(rawBytes); + vregB = info.format.getB(rawBytes); + vregC = info.format.getC(rawBytes); + + // Special case for 35* formats. + if (info.format.needsInvokeFormatInfo()) { + invokeFormatInfo = new InvokeFormatInfo(); + invokeFormatInfo.vregD = (byte) (rawBytes[4] >> 4); + invokeFormatInfo.vregE = (byte) (rawBytes[5] & 0xf); + invokeFormatInfo.vregF = (byte) (rawBytes[5] >> 4); + invokeFormatInfo.vregG = (byte) (rawBytes[1] & 0xf); + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + if (justRaw) { + // It is the responsibility of the CodeTranslator to make + // sure the raw bytes have been updated. + file.write(rawBytes); + } else { + info.format.writeToFile(file, this); + } + } + + /** + * Get the size of an instruction, in code-words. (Code-words are 16-bits.) + */ + public int getSize() { + if (justRaw) { + // It is the responsibility of the CodeTranslator to make sure + // the raw size has been updated. + return rawSize; + } + return info.format.getSize(); + } + + private int readOpcode(DexRandomAccessFile file) throws IOException { + short firstCodeWord = file.readUShort(); + int opcode = (firstCodeWord & 0xff); + int upperBits = (firstCodeWord & 0xff00) >> 8; + if (opcode == 0x0 && upperBits != 0x0) { + justRaw = true; + rawType = upperBits; + // Need to calculate special sizes. + switch (rawType) { + case RAW_TYPE_PACKED_SWITCH_DATA: + rawSize = (file.readUShort() * 2) + 4; + break; + case RAW_TYPE_SPARSE_SWITCH_DATA: + rawSize = (file.readUShort() * 4) + 2; + break; + case RAW_TYPE_FILL_ARRAY_DATA_DATA: + { + int elementWidth = file.readUShort(); + rawSize = ((file.readUInt() * elementWidth + 1) / 2) + 4; + break; + } + default: + Log.errorAndQuit("Unrecognised ident in data-payload instruction: " + rawType); + } + } + return opcode; + } + + @Override + public String toString() { + if (justRaw) { + switch (rawType) { + case RAW_TYPE_PACKED_SWITCH_DATA: + return "PACKED SWITCH DATA"; + case RAW_TYPE_SPARSE_SWITCH_DATA: + return "SPARSE SWITCH DATA"; + case RAW_TYPE_FILL_ARRAY_DATA_DATA: + return "FILL ARRAY DATA DATA"; + default: + } + + } + + String repr = info.name; + + AbstractFormat format = info.format; + + if (invokeFormatInfo != null) { + String vregs = ""; + + int numVregs = (int) vregA; + + if (numVregs > 5) { + Log.debug("vA in an 35c invoke was greater than 5? Assuming 5."); + numVregs = 5; + } else if (numVregs < 0) { + Log.debug("vA in an 35c invoke was less than 0? Assuming 0."); + numVregs = 0; + } + + switch (numVregs) { + case 5: + vregs = ", v" + invokeFormatInfo.vregG; + // fallthrough + case 4: + vregs = ", v" + invokeFormatInfo.vregF + vregs; + // fallthrough + case 3: + vregs = ", v" + invokeFormatInfo.vregE + vregs; + // fallthrough + case 2: + vregs = ", v" + invokeFormatInfo.vregD + vregs; + // fallthrough + case 1: + vregs = "v" + vregC + vregs; + break; + default: + } + + repr += "(" + vregs + ")"; + + long poolIndex = ((ContainsPoolIndex)format).getPoolIndex(this); + repr += " meth@" + poolIndex; + + return repr; + } + + + + if (format instanceof ContainsVRegs) { + String vregs = ""; + switch (((ContainsVRegs)format).getVRegCount()) { + case 3: + vregs = ", v" + vregC; + // fallthrough + case 2: + vregs = ", v" + vregB + vregs; + // fallthrough + case 1: + vregs = "v" + vregA + vregs; + break; + default: + Log.errorAndQuit("Invalid number of vregs reported by a Format."); + } + + repr += " " + vregs; + } + if (format instanceof ContainsConst) { + long constant = ((ContainsConst)format).getConst(this); + repr += " #" + constant; + } + if (format instanceof ContainsPoolIndex) { + long poolIndex = ((ContainsPoolIndex)format).getPoolIndex(this); + repr += " pool@" + poolIndex; + } + if (format instanceof ContainsTarget) { + long target = ((ContainsTarget)format).getTarget(this); + repr += " +" + target; + } + + return repr; + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + } + + // STATIC INSTRUCTION CODE + private static Map<Integer,OpcodeInfo> opcode_map_by_int = new HashMap<Integer,OpcodeInfo>(); + private static Map<Opcode,OpcodeInfo> opcode_map_by_enum = new HashMap<Opcode,OpcodeInfo>(); + + public static OpcodeInfo getOpcodeInfo(Opcode opcode) { + return opcode_map_by_enum.get(opcode); + } + + public static OpcodeInfo getOpcodeInfo(int opcodeValue) { + return opcode_map_by_int.get(opcodeValue); + } + + private static void addOpcodeInfo(Opcode opcode, String name, + int opcodeValue, AbstractFormat fmt) { + OpcodeInfo info = new OpcodeInfo(opcode, name, opcodeValue, fmt); + if (opcode.ordinal() != opcodeValue) { + Log.errorAndQuit(String.format("Opcode: %s (enum ordinal 0x%x) != (value 0x%x)", + opcode.toString(), opcode.ordinal(), opcodeValue)); + } + opcode_map_by_int.put(opcodeValue, info); + opcode_map_by_enum.put(opcode, info); + } + + static { + addOpcodeInfo(Opcode.NOP, "nop", 0x00, new Format10x()); + addOpcodeInfo(Opcode.MOVE, "move", 0x01, new Format12x()); + addOpcodeInfo(Opcode.MOVE_FROM16, "move/from16", 0x02, new Format22x()); + addOpcodeInfo(Opcode.MOVE_16, "move/16", 0x03, new Format32x()); + addOpcodeInfo(Opcode.MOVE_WIDE, "move-wide", 0x04, new Format12x()); + addOpcodeInfo(Opcode.MOVE_WIDE_FROM16, "move-wide/from16", 0x05, new Format22x()); + addOpcodeInfo(Opcode.MOVE_WIDE_16, "move-wide/16", 0x06, new Format32x()); + addOpcodeInfo(Opcode.MOVE_OBJECT, "move-object", 0x07, new Format12x()); + addOpcodeInfo(Opcode.MOVE_OBJECT_FROM16, "move-object/from16", 0x08, new Format22x()); + addOpcodeInfo(Opcode.MOVE_OBJECT_16, "move-object/16", 0x09, new Format32x()); + addOpcodeInfo(Opcode.MOVE_RESULT, "move-result", 0x0a, new Format11x()); + addOpcodeInfo(Opcode.MOVE_RESULT_WIDE, "move-result-wide", 0x0b, new Format11x()); + addOpcodeInfo(Opcode.MOVE_RESULT_OBJECT, "move-result-object", 0x0c, new Format11x()); + addOpcodeInfo(Opcode.MOVE_EXCEPTION, "move-exception", 0x0d, new Format11x()); + addOpcodeInfo(Opcode.RETURN_VOID, "return-void", 0x0e, new Format10x()); + addOpcodeInfo(Opcode.RETURN, "return", 0x0f, new Format11x()); + addOpcodeInfo(Opcode.RETURN_WIDE, "return-wide", 0x10, new Format11x()); + addOpcodeInfo(Opcode.RETURN_OBJECT, "return-object", 0x11, new Format11x()); + addOpcodeInfo(Opcode.CONST_4, "const/4", 0x12, new Format11n()); + addOpcodeInfo(Opcode.CONST_16, "const/16", 0x13, new Format21s()); + addOpcodeInfo(Opcode.CONST, "const", 0x14, new Format31i()); + addOpcodeInfo(Opcode.CONST_HIGH16, "const/high16", 0x15, new Format21h()); + addOpcodeInfo(Opcode.CONST_WIDE_16, "const-wide/16", 0x16, new Format21s()); + addOpcodeInfo(Opcode.CONST_WIDE_32, "const-wide/32", 0x17, new Format31i()); + addOpcodeInfo(Opcode.CONST_WIDE, "const-wide", 0x18, new Format51l()); + addOpcodeInfo(Opcode.CONST_WIDE_HIGH16, "const-wide/high16", 0x19, new Format21h()); + addOpcodeInfo(Opcode.CONST_STRING, "const-string", 0x1a, new Format21c()); + addOpcodeInfo(Opcode.CONST_STRING_JUMBO, "const-string/jumbo", 0x1b, new Format31c()); + addOpcodeInfo(Opcode.CONST_CLASS, "const-class", 0x1c, new Format21c()); + addOpcodeInfo(Opcode.MONITOR_ENTER, "monitor-enter", 0x1d, new Format11x()); + addOpcodeInfo(Opcode.MONITOR_EXIT, "monitor-exit", 0x1e, new Format11x()); + addOpcodeInfo(Opcode.CHECK_CAST, "check-cast", 0x1f, new Format21c()); + addOpcodeInfo(Opcode.INSTANCE_OF, "instance-of", 0x20, new Format22c()); + addOpcodeInfo(Opcode.ARRAY_LENGTH, "array-length", 0x21, new Format12x()); + addOpcodeInfo(Opcode.NEW_INSTANCE, "new-instance", 0x22, new Format21c()); + addOpcodeInfo(Opcode.NEW_ARRAY, "new-array", 0x23, new Format22c()); + addOpcodeInfo(Opcode.FILLED_NEW_ARRAY, "filled-new-array", 0x24, new Format35c()); + addOpcodeInfo(Opcode.FILLED_NEW_ARRAY_RANGE, "filled-new-array/range", + 0x25, new Format3rc()); + addOpcodeInfo(Opcode.FILL_ARRAY_DATA, "fill-array-data", 0x26, new Format31t()); + addOpcodeInfo(Opcode.THROW, "throw", 0x27, new Format11x()); + addOpcodeInfo(Opcode.GOTO, "goto", 0x28, new Format10t()); + addOpcodeInfo(Opcode.GOTO_16, "goto/16", 0x29, new Format20t()); + addOpcodeInfo(Opcode.GOTO_32, "goto/32", 0x2a, new Format30t()); + addOpcodeInfo(Opcode.PACKED_SWITCH, "packed-switch", 0x2b, new Format31t()); + addOpcodeInfo(Opcode.SPARSE_SWITCH, "sparse-switch", 0x2c, new Format31t()); + addOpcodeInfo(Opcode.CMPL_FLOAT, "cmpl-float", 0x2d, new Format23x()); + addOpcodeInfo(Opcode.CMPG_FLOAT, "cmpg-float", 0x2e, new Format23x()); + addOpcodeInfo(Opcode.CMPL_DOUBLE, "cmpl-double", 0x2f, new Format23x()); + addOpcodeInfo(Opcode.CMPG_DOUBLE, "cmpg-double", 0x30, new Format23x()); + addOpcodeInfo(Opcode.CMP_LONG, "cmp-long", 0x31, new Format23x()); + addOpcodeInfo(Opcode.IF_EQ, "if-eq", 0x32, new Format22t()); + addOpcodeInfo(Opcode.IF_NE, "if-ne", 0x33, new Format22t()); + addOpcodeInfo(Opcode.IF_LT, "if-lt", 0x34, new Format22t()); + addOpcodeInfo(Opcode.IF_GE, "if-ge", 0x35, new Format22t()); + addOpcodeInfo(Opcode.IF_GT, "if-gt", 0x36, new Format22t()); + addOpcodeInfo(Opcode.IF_LE, "if-le", 0x37, new Format22t()); + addOpcodeInfo(Opcode.IF_EQZ, "if-eqz", 0x38, new Format21t()); + addOpcodeInfo(Opcode.IF_NEZ, "if-nez", 0x39, new Format21t()); + addOpcodeInfo(Opcode.IF_LTZ, "if-ltz", 0x3a, new Format21t()); + addOpcodeInfo(Opcode.IF_GEZ, "if-gez", 0x3b, new Format21t()); + addOpcodeInfo(Opcode.IF_GTZ, "if-gtz", 0x3c, new Format21t()); + addOpcodeInfo(Opcode.IF_LEZ, "if-lez", 0x3d, new Format21t()); + addOpcodeInfo(Opcode.UNUSED_3E, "unused-3e", 0x3e, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_3F, "unused-3f", 0x3f, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_40, "unused-40", 0x40, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_41, "unused-41", 0x41, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_42, "unused-42", 0x42, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_43, "unused-43", 0x43, new Format10x()); + addOpcodeInfo(Opcode.AGET, "aget", 0x44, new Format23x()); + addOpcodeInfo(Opcode.AGET_WIDE, "aget-wide", 0x45, new Format23x()); + addOpcodeInfo(Opcode.AGET_WIDE, "aget-wide", 0x45, new Format23x()); + addOpcodeInfo(Opcode.AGET_OBJECT, "aget-object", 0x46, new Format23x()); + addOpcodeInfo(Opcode.AGET_BOOLEAN, "aget-boolean", 0x47, new Format23x()); + addOpcodeInfo(Opcode.AGET_BYTE, "aget-byte", 0x48, new Format23x()); + addOpcodeInfo(Opcode.AGET_CHAR, "aget-char", 0x49, new Format23x()); + addOpcodeInfo(Opcode.AGET_SHORT, "aget-short", 0x4a, new Format23x()); + addOpcodeInfo(Opcode.APUT, "aput", 0x4b, new Format23x()); + addOpcodeInfo(Opcode.APUT_WIDE, "aput-wide", 0x4c, new Format23x()); + addOpcodeInfo(Opcode.APUT_OBJECT, "aput-object", 0x4d, new Format23x()); + addOpcodeInfo(Opcode.APUT_BOOLEAN, "aput-boolean", 0x4e, new Format23x()); + addOpcodeInfo(Opcode.APUT_BYTE, "aput-byte", 0x4f, new Format23x()); + addOpcodeInfo(Opcode.APUT_CHAR, "aput-char", 0x50, new Format23x()); + addOpcodeInfo(Opcode.APUT_SHORT, "aput-short", 0x51, new Format23x()); + addOpcodeInfo(Opcode.IGET, "iget", 0x52, new Format22c()); + addOpcodeInfo(Opcode.IGET_WIDE, "iget-wide", 0x53, new Format22c()); + addOpcodeInfo(Opcode.IGET_OBJECT, "iget-object", 0x54, new Format22c()); + addOpcodeInfo(Opcode.IGET_BOOLEAN, "iget-boolean", 0x55, new Format22c()); + addOpcodeInfo(Opcode.IGET_BYTE, "iget-byte", 0x56, new Format22c()); + addOpcodeInfo(Opcode.IGET_CHAR, "iget-char", 0x57, new Format22c()); + addOpcodeInfo(Opcode.IGET_SHORT, "iget-short", 0x58, new Format22c()); + addOpcodeInfo(Opcode.IPUT, "iput", 0x59, new Format22c()); + addOpcodeInfo(Opcode.IPUT_WIDE, "iput-wide", 0x5a, new Format22c()); + addOpcodeInfo(Opcode.IPUT_OBJECT, "iput-object", 0x5b, new Format22c()); + addOpcodeInfo(Opcode.IPUT_BOOLEAN, "iput-boolean", 0x5c, new Format22c()); + addOpcodeInfo(Opcode.IPUT_BYTE, "iput-byte", 0x5d, new Format22c()); + addOpcodeInfo(Opcode.IPUT_CHAR, "iput-char", 0x5e, new Format22c()); + addOpcodeInfo(Opcode.IPUT_SHORT, "iput-short", 0x5f, new Format22c()); + addOpcodeInfo(Opcode.SGET, "sget", 0x60, new Format21c()); + addOpcodeInfo(Opcode.SGET_WIDE, "sget-wide", 0x61, new Format21c()); + addOpcodeInfo(Opcode.SGET_OBJECT, "sget-object", 0x62, new Format21c()); + addOpcodeInfo(Opcode.SGET_BOOLEAN, "sget-boolean", 0x63, new Format21c()); + addOpcodeInfo(Opcode.SGET_BYTE, "sget-byte", 0x64, new Format21c()); + addOpcodeInfo(Opcode.SGET_CHAR, "sget-char", 0x65, new Format21c()); + addOpcodeInfo(Opcode.SGET_SHORT, "sget-short", 0x66, new Format21c()); + addOpcodeInfo(Opcode.SPUT, "sput", 0x67, new Format21c()); + addOpcodeInfo(Opcode.SPUT_WIDE, "sput-wide", 0x68, new Format21c()); + addOpcodeInfo(Opcode.SPUT_OBJECT, "sput-object", 0x69, new Format21c()); + addOpcodeInfo(Opcode.SPUT_BOOLEAN, "sput-boolean", 0x6a, new Format21c()); + addOpcodeInfo(Opcode.SPUT_BYTE, "sput-byte", 0x6b, new Format21c()); + addOpcodeInfo(Opcode.SPUT_CHAR, "sput-char", 0x6c, new Format21c()); + addOpcodeInfo(Opcode.SPUT_SHORT, "sput-short", 0x6d, new Format21c()); + addOpcodeInfo(Opcode.INVOKE_VIRTUAL, "invoke-virtual", 0x6e, new Format35c()); + addOpcodeInfo(Opcode.INVOKE_SUPER, "invoke-super", 0x6f, new Format35c()); + addOpcodeInfo(Opcode.INVOKE_DIRECT, "invoke-direct", 0x70, new Format35c()); + addOpcodeInfo(Opcode.INVOKE_STATIC, "invoke-static", 0x71, new Format35c()); + addOpcodeInfo(Opcode.INVOKE_INTERFACE, "invoke-interface", 0x72, new Format35c()); + addOpcodeInfo(Opcode.RETURN_VOID_BARRIER, "return-void-barrier", 0x73, new Format10x()); + addOpcodeInfo(Opcode.INVOKE_VIRTUAL_RANGE, "invoke-virtual/range", 0x74, new Format3rc()); + addOpcodeInfo(Opcode.INVOKE_SUPER_RANGE, "invoke-super/range", 0x75, new Format3rc()); + addOpcodeInfo(Opcode.INVOKE_DIRECT_RANGE, "invoke-direct/range", 0x76, new Format3rc()); + addOpcodeInfo(Opcode.INVOKE_STATIC_RANGE, "invoke-static/range", 0x77, new Format3rc()); + addOpcodeInfo(Opcode.INVOKE_INTERFACE_RANGE, "invoke-interface/range", + 0x78, new Format3rc()); + addOpcodeInfo(Opcode.UNUSED_79, "unused-79", 0x79, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_7A, "unused-7a", 0x7a, new Format10x()); + addOpcodeInfo(Opcode.NEG_INT, "neg-int", 0x7b, new Format12x()); + addOpcodeInfo(Opcode.NOT_INT, "not-int", 0x7c, new Format12x()); + addOpcodeInfo(Opcode.NEG_LONG, "neg-long", 0x7d, new Format12x()); + addOpcodeInfo(Opcode.NOT_LONG, "not-long", 0x7e, new Format12x()); + addOpcodeInfo(Opcode.NEG_FLOAT, "neg-float", 0x7f, new Format12x()); + addOpcodeInfo(Opcode.NEG_DOUBLE, "neg-double", 0x80, new Format12x()); + addOpcodeInfo(Opcode.INT_TO_LONG, "int-to-long", 0x81, new Format12x()); + addOpcodeInfo(Opcode.INT_TO_FLOAT, "int-to-float", 0x82, new Format12x()); + addOpcodeInfo(Opcode.INT_TO_DOUBLE, "int-to-double", 0x83, new Format12x()); + addOpcodeInfo(Opcode.LONG_TO_INT, "long-to-int", 0x84, new Format12x()); + addOpcodeInfo(Opcode.LONG_TO_FLOAT, "long-to-float", 0x85, new Format12x()); + addOpcodeInfo(Opcode.LONG_TO_DOUBLE, "long-to-double", 0x86, new Format12x()); + addOpcodeInfo(Opcode.FLOAT_TO_INT, "float-to-int", 0x87, new Format12x()); + addOpcodeInfo(Opcode.FLOAT_TO_LONG, "float-to-long", 0x88, new Format12x()); + addOpcodeInfo(Opcode.FLOAT_TO_DOUBLE, "float-to-double", 0x89, new Format12x()); + addOpcodeInfo(Opcode.DOUBLE_TO_INT, "double-to-int", 0x8a, new Format12x()); + addOpcodeInfo(Opcode.DOUBLE_TO_LONG, "double-to-long", 0x8b, new Format12x()); + addOpcodeInfo(Opcode.DOUBLE_TO_FLOAT, "double-to-float", 0x8c, new Format12x()); + addOpcodeInfo(Opcode.INT_TO_BYTE, "int-to-byte", 0x8d, new Format12x()); + addOpcodeInfo(Opcode.INT_TO_CHAR, "int-to-char", 0x8e, new Format12x()); + addOpcodeInfo(Opcode.INT_TO_SHORT, "int-to-short", 0x8f, new Format12x()); + addOpcodeInfo(Opcode.ADD_INT, "add-int", 0x90, new Format23x()); + addOpcodeInfo(Opcode.SUB_INT, "sub-int", 0x91, new Format23x()); + addOpcodeInfo(Opcode.MUL_INT, "mul-int", 0x92, new Format23x()); + addOpcodeInfo(Opcode.DIV_INT, "div-int", 0x93, new Format23x()); + addOpcodeInfo(Opcode.REM_INT, "rem-int", 0x94, new Format23x()); + addOpcodeInfo(Opcode.AND_INT, "and-int", 0x95, new Format23x()); + addOpcodeInfo(Opcode.OR_INT, "or-int", 0x96, new Format23x()); + addOpcodeInfo(Opcode.XOR_INT, "xor-int", 0x97, new Format23x()); + addOpcodeInfo(Opcode.SHL_INT, "shl-int", 0x98, new Format23x()); + addOpcodeInfo(Opcode.SHR_INT, "shr-int", 0x99, new Format23x()); + addOpcodeInfo(Opcode.USHR_INT, "ushr-int", 0x9a, new Format23x()); + addOpcodeInfo(Opcode.ADD_LONG, "add-long", 0x9b, new Format23x()); + addOpcodeInfo(Opcode.SUB_LONG, "sub-long", 0x9c, new Format23x()); + addOpcodeInfo(Opcode.MUL_LONG, "mul-long", 0x9d, new Format23x()); + addOpcodeInfo(Opcode.DIV_LONG, "div-long", 0x9e, new Format23x()); + addOpcodeInfo(Opcode.REM_LONG, "rem-long", 0x9f, new Format23x()); + addOpcodeInfo(Opcode.AND_LONG, "and-long", 0xa0, new Format23x()); + addOpcodeInfo(Opcode.OR_LONG, "or-long", 0xa1, new Format23x()); + addOpcodeInfo(Opcode.XOR_LONG, "xor-long", 0xa2, new Format23x()); + addOpcodeInfo(Opcode.SHL_LONG, "shl-long", 0xa3, new Format23x()); + addOpcodeInfo(Opcode.SHR_LONG, "shr-long", 0xa4, new Format23x()); + addOpcodeInfo(Opcode.USHR_LONG, "ushr-long", 0xa5, new Format23x()); + addOpcodeInfo(Opcode.ADD_FLOAT, "add-float", 0xa6, new Format23x()); + addOpcodeInfo(Opcode.SUB_FLOAT, "sub-float", 0xa7, new Format23x()); + addOpcodeInfo(Opcode.MUL_FLOAT, "mul-float", 0xa8, new Format23x()); + addOpcodeInfo(Opcode.DIV_FLOAT, "div-float", 0xa9, new Format23x()); + addOpcodeInfo(Opcode.REM_FLOAT, "rem-float", 0xaa, new Format23x()); + addOpcodeInfo(Opcode.ADD_DOUBLE, "add-double", 0xab, new Format23x()); + addOpcodeInfo(Opcode.SUB_DOUBLE, "sub-double", 0xac, new Format23x()); + addOpcodeInfo(Opcode.MUL_DOUBLE, "mul-double", 0xad, new Format23x()); + addOpcodeInfo(Opcode.DIV_DOUBLE, "div-double", 0xae, new Format23x()); + addOpcodeInfo(Opcode.REM_DOUBLE, "rem-double", 0xaf, new Format23x()); + addOpcodeInfo(Opcode.ADD_INT_2ADDR, "add-int/2addr", 0xb0, new Format12x()); + addOpcodeInfo(Opcode.SUB_INT_2ADDR, "sub-int/2addr", 0xb1, new Format12x()); + addOpcodeInfo(Opcode.MUL_INT_2ADDR, "mul-int/2addr", 0xb2, new Format12x()); + addOpcodeInfo(Opcode.DIV_INT_2ADDR, "div-int/2addr", 0xb3, new Format12x()); + addOpcodeInfo(Opcode.REM_INT_2ADDR, "rem-int/2addr", 0xb4, new Format12x()); + addOpcodeInfo(Opcode.AND_INT_2ADDR, "and-int/2addr", 0xb5, new Format12x()); + addOpcodeInfo(Opcode.OR_INT_2ADDR, "or-int/2addr", 0xb6, new Format12x()); + addOpcodeInfo(Opcode.XOR_INT_2ADDR, "xor-int/2addr", 0xb7, new Format12x()); + addOpcodeInfo(Opcode.SHL_INT_2ADDR, "shl-int/2addr", 0xb8, new Format12x()); + addOpcodeInfo(Opcode.SHR_INT_2ADDR, "shr-int/2addr", 0xb9, new Format12x()); + addOpcodeInfo(Opcode.USHR_INT_2ADDR, "ushr-int/2addr", 0xba, new Format12x()); + addOpcodeInfo(Opcode.ADD_LONG_2ADDR, "add-long/2addr", 0xbb, new Format12x()); + addOpcodeInfo(Opcode.SUB_LONG_2ADDR, "sub-long/2addr", 0xbc, new Format12x()); + addOpcodeInfo(Opcode.MUL_LONG_2ADDR, "mul-long/2addr", 0xbd, new Format12x()); + addOpcodeInfo(Opcode.DIV_LONG_2ADDR, "div-long/2addr", 0xbe, new Format12x()); + addOpcodeInfo(Opcode.REM_LONG_2ADDR, "rem-long/2addr", 0xbf, new Format12x()); + addOpcodeInfo(Opcode.AND_LONG_2ADDR, "and-long/2addr", 0xc0, new Format12x()); + addOpcodeInfo(Opcode.OR_LONG_2ADDR, "or-long/2addr", 0xc1, new Format12x()); + addOpcodeInfo(Opcode.XOR_LONG_2ADDR, "xor-long/2addr", 0xc2, new Format12x()); + addOpcodeInfo(Opcode.SHL_LONG_2ADDR, "shl-long/2addr", 0xc3, new Format12x()); + addOpcodeInfo(Opcode.SHR_LONG_2ADDR, "shr-long/2addr", 0xc4, new Format12x()); + addOpcodeInfo(Opcode.USHR_LONG_2ADDR, "ushr-long/2addr", 0xc5, new Format12x()); + addOpcodeInfo(Opcode.ADD_FLOAT_2ADDR, "add-float/2addr", 0xc6, new Format12x()); + addOpcodeInfo(Opcode.SUB_FLOAT_2ADDR, "sub-float/2addr", 0xc7, new Format12x()); + addOpcodeInfo(Opcode.MUL_FLOAT_2ADDR, "mul-float/2addr", 0xc8, new Format12x()); + addOpcodeInfo(Opcode.DIV_FLOAT_2ADDR, "div-float/2addr", 0xc9, new Format12x()); + addOpcodeInfo(Opcode.REM_FLOAT_2ADDR, "rem-float/2addr", 0xca, new Format12x()); + addOpcodeInfo(Opcode.ADD_DOUBLE_2ADDR, "add-double/2addr", 0xcb, new Format12x()); + addOpcodeInfo(Opcode.SUB_DOUBLE_2ADDR, "sub-double/2addr", 0xcc, new Format12x()); + addOpcodeInfo(Opcode.MUL_DOUBLE_2ADDR, "mul-double/2addr", 0xcd, new Format12x()); + addOpcodeInfo(Opcode.DIV_DOUBLE_2ADDR, "div-double/2addr", 0xce, new Format12x()); + addOpcodeInfo(Opcode.REM_DOUBLE_2ADDR, "rem-double/2addr", 0xcf, new Format12x()); + addOpcodeInfo(Opcode.ADD_INT_LIT16, "add-int/lit16", 0xd0, new Format22s()); + addOpcodeInfo(Opcode.RSUB_INT, "rsub-int", 0xd1, new Format22s()); + addOpcodeInfo(Opcode.MUL_INT_LIT16, "mul-int/lit16", 0xd2, new Format22s()); + addOpcodeInfo(Opcode.DIV_INT_LIT16, "div-int/lit16", 0xd3, new Format22s()); + addOpcodeInfo(Opcode.REM_INT_LIT16, "rem-int/lit16", 0xd4, new Format22s()); + addOpcodeInfo(Opcode.AND_INT_LIT16, "and-int/lit16", 0xd5, new Format22s()); + addOpcodeInfo(Opcode.OR_INT_LIT16, "or-int/lit16", 0xd6, new Format22s()); + addOpcodeInfo(Opcode.XOR_INT_LIT16, "xor-int/lit16", 0xd7, new Format22s()); + addOpcodeInfo(Opcode.ADD_INT_LIT8, "add-int/lit8", 0xd8, new Format22b()); + addOpcodeInfo(Opcode.RSUB_INT_LIT8, "rsub-int/lit8", 0xd9, new Format22b()); + addOpcodeInfo(Opcode.MUL_INT_LIT8, "mul-int/lit8", 0xda, new Format22b()); + addOpcodeInfo(Opcode.DIV_INT_LIT8, "div-int/lit8", 0xdb, new Format22b()); + addOpcodeInfo(Opcode.REM_INT_LIT8, "rem-int/lit8", 0xdc, new Format22b()); + addOpcodeInfo(Opcode.AND_INT_LIT8, "and-int/lit8", 0xdd, new Format22b()); + addOpcodeInfo(Opcode.OR_INT_LIT8, "or-int/lit8", 0xde, new Format22b()); + addOpcodeInfo(Opcode.XOR_INT_LIT8, "xor-int/lit8", 0xdf, new Format22b()); + addOpcodeInfo(Opcode.SHL_INT_LIT8, "shl-int/lit8", 0xe0, new Format22b()); + addOpcodeInfo(Opcode.SHR_INT_LIT8, "shr-int/lit8", 0xe1, new Format22b()); + addOpcodeInfo(Opcode.USHR_INT_LIT8, "ushr-int/lit8", 0xe2, new Format22b()); + addOpcodeInfo(Opcode.IGET_QUICK, "+iget-quick", 0xe3, new Format22c()); + addOpcodeInfo(Opcode.IGET_WIDE_QUICK, "+iget-wide-quick", 0xe4, new Format22c()); + addOpcodeInfo(Opcode.IGET_OBJECT_QUICK, "+iget-object-quick", 0xe5, new Format22c()); + addOpcodeInfo(Opcode.IPUT_QUICK, "+iput-quick", 0xe6, new Format22c()); + addOpcodeInfo(Opcode.IPUT_WIDE_QUICK, "+iput-wide-quick", 0xe7, new Format22c()); + addOpcodeInfo(Opcode.IPUT_OBJECT_QUICK, "+iput-object-quick", 0xe8, new Format22c()); + addOpcodeInfo(Opcode.INVOKE_VIRTUAL_QUICK, "+invoke-virtual-quick", 0xe9, new Format35c()); + addOpcodeInfo(Opcode.INVOKE_VIRTUAL_QUICK_RANGE, "+invoke-virtual-quick/range", + 0xea, new Format3rc()); + addOpcodeInfo(Opcode.IPUT_BOOLEAN_QUICK, "+iput-boolean-quick", 0xeb, new Format22c()); + addOpcodeInfo(Opcode.IPUT_BYTE_QUICK, "+iput-byte-quick", 0xec, new Format22c()); + addOpcodeInfo(Opcode.IPUT_CHAR_QUICK, "+iput-char-quick", 0xed, new Format22c()); + addOpcodeInfo(Opcode.IPUT_SHORT_QUICK, "+iput-short-quick", 0xee, new Format22c()); + addOpcodeInfo(Opcode.UNUSED_EF, "unused-ef", 0xef, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_F0, "unused-f0", 0xf0, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_F1, "unused-f1", 0xf1, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_F2, "unused-f2", 0xf2, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_F3, "unused-f3", 0xf3, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_F4, "unused-f4", 0xf4, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_F5, "unused-f5", 0xf5, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_F6, "unused-f6", 0xf6, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_F7, "unused-f7", 0xf7, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_F8, "unused-f8", 0xf8, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_F9, "unused-f9", 0xf9, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_FA, "unused-fa", 0xfa, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_FB, "unused-fb", 0xfb, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_FC, "unused-fc", 0xfc, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_FD, "unused-fd", 0xfd, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_FE, "unused-fe", 0xfe, new Format10x()); + addOpcodeInfo(Opcode.UNUSED_FF, "unused-ff", 0xff, new Format10x()); + if (opcode_map_by_int.size() != 256) { + Log.errorAndQuit("Incorrect number of bytecodes defined."); + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/MapItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/MapItem.java new file mode 100644 index 0000000..4ca2463 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/MapItem.java @@ -0,0 +1,69 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class MapItem implements RawDexObject { + public static final int TYPE_HEADER_ITEM = 0x0; + public static final int TYPE_STRING_ID_ITEM = 0x1; + public static final int TYPE_TYPE_ID_ITEM = 0x2; + public static final int TYPE_PROTO_ID_ITEM = 0x3; + public static final int TYPE_FIELD_ID_ITEM = 0x4; + public static final int TYPE_METHOD_ID_ITEM = 0x5; + public static final int TYPE_CLASS_DEF_ITEM = 0x6; + public static final int TYPE_MAP_LIST = 0x1000; + public static final int TYPE_TYPE_LIST = 0x1001; + public static final int TYPE_ANNOTATION_SET_REF_LIST = 0x1002; + public static final int TYPE_ANNOTATION_SET_ITEM = 0x1003; + public static final int TYPE_CLASS_DATA_ITEM = 0x2000; + public static final int TYPE_CODE_ITEM = 0x2001; + public static final int TYPE_STRING_DATA_ITEM = 0x2002; + public static final int TYPE_DEBUG_INFO_ITEM = 0x2003; + public static final int TYPE_ANNOTATION_ITEM = 0x2004; + public static final int TYPE_ENCODED_ARRAY_ITEM = 0x2005; + public static final int TYPE_ANNOTATIONS_DIRECTORY_ITEM = 0x2006; + + public short type; + public int size; + public Offset offset; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + type = file.readUShort(); + file.readUShort(); // Unused padding. + size = file.readUInt(); + if (type == TYPE_HEADER_ITEM) { + offset = file.getOffsetTracker().getNewHeaderOffset(file.readUInt()); + } else { + offset = file.getOffsetTracker().getNewOffset(file.readUInt()); + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUShort(type); + file.writeUShort((short) 0); // Unused padding. + file.writeUInt(size); + file.getOffsetTracker().tryToWriteOffset(offset, file, false /* ULEB128 */); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/MapList.java b/tools/dexfuzz/src/dexfuzz/rawdex/MapList.java new file mode 100644 index 0000000..080b5a4 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/MapList.java @@ -0,0 +1,218 @@ +/* + * 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.rawdex; + +import dexfuzz.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class MapList implements RawDexObject { + + private RawDexFile rawDexFile; + + public int size; + public List<MapItem> mapItems; + + public MapList(RawDexFile rawDexFile) { + this.rawDexFile = rawDexFile; + } + + @Override + public void read(DexRandomAccessFile file) throws IOException { + // Find the map list. + file.seek(rawDexFile.header.mapOff.getOriginalOffset()); + + file.getOffsetTracker().getNewOffsettable(file, this); + + // Get the number of entries. + size = file.readUInt(); + + // Allocate and populate the array. + mapItems = new ArrayList<MapItem>(size); + for (int i = 0; i < size; i++) { + MapItem mapItem = new MapItem(); + mapItems.add(mapItem); + mapItem.read(file); + } + + file.getOffsetTracker().rememberPointAfterMapList(); + + // NB: We track the current index into the MapList, so when we encounter the DebugInfoItem + // MapItem, we know how to find the next MapItem, so we know how large the DebugInfo + // area is, so we can copy it as a blob. + int mapItemIdx = 0; + + // Iterate through the list, and create all the other data structures. + for (MapItem mapItem : mapItems) { + file.seek(mapItem.offset.getOriginalOffset()); + switch (mapItem.type) { + case MapItem.TYPE_HEADER_ITEM: + // Already read it; skip. + break; + case MapItem.TYPE_STRING_ID_ITEM: + for (int i = 0; i < mapItem.size; i++) { + StringIdItem newStringId = new StringIdItem(); + rawDexFile.stringIds.add(newStringId); + newStringId.read(file); + } + break; + case MapItem.TYPE_TYPE_ID_ITEM: + for (int i = 0; i < mapItem.size; i++) { + TypeIdItem newTypeId = new TypeIdItem(); + rawDexFile.typeIds.add(newTypeId); + newTypeId.read(file); + } + break; + case MapItem.TYPE_PROTO_ID_ITEM: + for (int i = 0; i < mapItem.size; i++) { + ProtoIdItem newProtoId = new ProtoIdItem(); + rawDexFile.protoIds.add(newProtoId); + newProtoId.read(file); + } + break; + case MapItem.TYPE_FIELD_ID_ITEM: + for (int i = 0; i < mapItem.size; i++) { + FieldIdItem newFieldId = new FieldIdItem(); + rawDexFile.fieldIds.add(newFieldId); + newFieldId.read(file); + } + break; + case MapItem.TYPE_METHOD_ID_ITEM: + for (int i = 0; i < mapItem.size; i++) { + MethodIdItem newMethodId = new MethodIdItem(); + rawDexFile.methodIds.add(newMethodId); + newMethodId.read(file); + } + break; + case MapItem.TYPE_CLASS_DEF_ITEM: + for (int i = 0; i < mapItem.size; i++) { + ClassDefItem newClassDef = new ClassDefItem(); + rawDexFile.classDefs.add(newClassDef); + newClassDef.read(file); + } + break; + case MapItem.TYPE_MAP_LIST: + // Already read it; skip. + break; + case MapItem.TYPE_TYPE_LIST: + rawDexFile.typeLists = new ArrayList<TypeList>(mapItem.size); + for (int i = 0; i < mapItem.size; i++) { + TypeList newTypeList = new TypeList(); + rawDexFile.typeLists.add(newTypeList); + newTypeList.read(file); + } + break; + case MapItem.TYPE_ANNOTATION_SET_REF_LIST: + rawDexFile.annotationSetRefLists = + new ArrayList<AnnotationSetRefList>(mapItem.size); + for (int i = 0; i < mapItem.size; i++) { + AnnotationSetRefList newAnnotationSetRefList = new AnnotationSetRefList(); + rawDexFile.annotationSetRefLists.add(newAnnotationSetRefList); + newAnnotationSetRefList.read(file); + } + break; + case MapItem.TYPE_ANNOTATION_SET_ITEM: + rawDexFile.annotationSetItems = new ArrayList<AnnotationSetItem>(mapItem.size); + for (int i = 0; i < mapItem.size; i++) { + AnnotationSetItem newAnnotationSetItem = new AnnotationSetItem(); + rawDexFile.annotationSetItems.add(newAnnotationSetItem); + newAnnotationSetItem.read(file); + } + break; + case MapItem.TYPE_CLASS_DATA_ITEM: + rawDexFile.classDatas = new ArrayList<ClassDataItem>(mapItem.size); + for (int i = 0; i < mapItem.size; i++) { + ClassDataItem newClassData = new ClassDataItem(); + rawDexFile.classDatas.add(newClassData); + newClassData.read(file); + } + break; + case MapItem.TYPE_CODE_ITEM: + rawDexFile.codeItems = new ArrayList<CodeItem>(mapItem.size); + for (int i = 0; i < mapItem.size; i++) { + CodeItem newCodeItem = new CodeItem(); + rawDexFile.codeItems.add(newCodeItem); + newCodeItem.read(file); + } + break; + case MapItem.TYPE_STRING_DATA_ITEM: + rawDexFile.stringDatas = new ArrayList<StringDataItem>(mapItem.size); + for (int i = 0; i < mapItem.size; i++) { + StringDataItem newStringData = new StringDataItem(); + rawDexFile.stringDatas.add(newStringData); + newStringData.read(file); + } + break; + case MapItem.TYPE_DEBUG_INFO_ITEM: + { + // We aren't interested in updating the debug data, so just read it as a blob. + int start = mapItem.offset.getOriginalOffset(); + int end = mapItems.get(mapItemIdx + 1).offset.getOriginalOffset(); + int size = end - start; + rawDexFile.debugInfoItem = new DebugInfoItem(size); + rawDexFile.debugInfoItem.read(file); + break; + } + case MapItem.TYPE_ANNOTATION_ITEM: + rawDexFile.annotationItems = new ArrayList<AnnotationItem>(mapItem.size); + for (int i = 0; i < mapItem.size; i++) { + AnnotationItem newAnnotationItem = new AnnotationItem(); + rawDexFile.annotationItems.add(newAnnotationItem); + newAnnotationItem.read(file); + } + break; + case MapItem.TYPE_ENCODED_ARRAY_ITEM: + rawDexFile.encodedArrayItems = new ArrayList<EncodedArrayItem>(mapItem.size); + for (int i = 0; i < mapItem.size; i++) { + EncodedArrayItem newEncodedArrayItem = new EncodedArrayItem(); + rawDexFile.encodedArrayItems.add(newEncodedArrayItem); + newEncodedArrayItem.read(file); + } + break; + case MapItem.TYPE_ANNOTATIONS_DIRECTORY_ITEM: + rawDexFile.annotationsDirectoryItems = + new ArrayList<AnnotationsDirectoryItem>(mapItem.size); + for (int i = 0; i < mapItem.size; i++) { + AnnotationsDirectoryItem newAnnotationsDirectoryItem = new AnnotationsDirectoryItem(); + rawDexFile.annotationsDirectoryItems.add(newAnnotationsDirectoryItem); + newAnnotationsDirectoryItem.read(file); + } + break; + default: + Log.errorAndQuit("Encountered unknown map item when reading map item list."); + } + mapItemIdx++; + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.alignForwards(4); + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeUInt(mapItems.size()); + for (MapItem mapItem : mapItems) { + mapItem.write(file); + } + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/MethodAnnotation.java b/tools/dexfuzz/src/dexfuzz/rawdex/MethodAnnotation.java new file mode 100644 index 0000000..1a24fb6 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/MethodAnnotation.java @@ -0,0 +1,43 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class MethodAnnotation implements RawDexObject { + public int methodIdx; + public Offset annotationsOff; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + methodIdx = file.readUInt(); + annotationsOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUInt(methodIdx); + file.getOffsetTracker().tryToWriteOffset(annotationsOff, file, false /* ULEB128 */); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.METHOD_ID && methodIdx >= insertedIdx) { + methodIdx++; + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/MethodIdItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/MethodIdItem.java new file mode 100644 index 0000000..12d0187 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/MethodIdItem.java @@ -0,0 +1,54 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class MethodIdItem implements RawDexObject { + public short classIdx; + public short protoIdx; + public int nameIdx; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().getNewOffsettable(file, this); + classIdx = file.readUShort(); + protoIdx = file.readUShort(); + nameIdx = file.readUInt(); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeUShort(classIdx); + file.writeUShort(protoIdx); + file.writeUInt(nameIdx); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.TYPE_ID && classIdx >= insertedIdx) { + classIdx++; + } + if (kind == IndexUpdateKind.PROTO_ID && protoIdx >= insertedIdx) { + protoIdx++; + } + if (kind == IndexUpdateKind.STRING_ID && nameIdx >= insertedIdx) { + nameIdx++; + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/Offset.java b/tools/dexfuzz/src/dexfuzz/rawdex/Offset.java new file mode 100644 index 0000000..b6718e3 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/Offset.java @@ -0,0 +1,191 @@ +/* + * 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.rawdex; + +import dexfuzz.Log; + +public class Offset { + /** + * The absolute value of this offset as it was originally read. + */ + private int originalOffset; + + /** + * The Offsettable that this Offset points to. + */ + private Offsettable offsettable; + + /** + * The location of this Offset in the new file, ONLY SET IF the Offset + * couldn't be written because what it points to hasn't been written + * yet. + */ + private int outputLocation; + + /** + * Was the output location for this Offset set?. + */ + private boolean outputLocationSet; + + /** + * Does this Offset need to be written out using ULEB128?. + */ + private boolean useUleb128; + + /** + * Was this Offset created after reading, during mutation?. + */ + private boolean isNewOffset; + + /** + * Only one Offset should have this flag set, the MapItem that points + * to the HeaderItem. + */ + private boolean pointsAtHeader; + + /** + * If an Offset pointed at 0 (because it is not actually a valid Offset), + * and it's not pointing at the header, then this is set. + */ + private boolean pointsAtNull; + + public Offset(boolean header) { + pointsAtHeader = header; + } + + public RawDexObject getPointedToItem() { + return offsettable.getItem(); + } + + public boolean pointsToSomething() { + return offsettable != null; + } + + public boolean pointsAtNull() { + return pointsAtNull; + } + + public boolean pointsAtHeader() { + return pointsAtHeader; + } + + /** + * Returns true if this Offset points at the provided RawDexObject. + */ + public boolean pointsToThisItem(RawDexObject thisItem) { + if (!pointsToSomething()) { + return false; + } + return (offsettable.getItem().equals(thisItem)); + } + + /** + * Returns true if this Offset points at the provided Offsettable. + */ + public boolean pointsToThisOffsettable(Offsettable thisOffsettable) { + if (!pointsToSomething()) { + return false; + } + return (offsettable.equals(thisOffsettable)); + } + + /** + * Makes this Offset point at a new Offsettable. + */ + public void pointTo(Offsettable offsettableItem) { + if (offsettable != null) { + Log.debug("Updating what an Offset points to..."); + } + offsettable = offsettableItem; + } + + /** + * Call this to make an Offset that pointed at null before now point at something. + * An Offset may have previously pointed at null before... + * Example: if there are no fields referred to in a DEX file, then header.field_ids_off + * will point at null. We distinguish when Offsets point at null, and are not pointing + * at the header (only the header MapItem should do this) with a flag. Therefore, this + * method is needed to indicate that this Offset now points at something. + */ + public void unsetNullAndPointTo(Offsettable offsettableItem) { + pointsAtNull = false; + if (offsettable != null) { + Log.debug("Updating what an Offset points to..."); + } + offsettable = offsettableItem; + } + + public void pointToNew(Offsettable offsettableItem) { + offsettable = offsettableItem; + isNewOffset = true; + } + + public int getNewPositionOfItem() { + return offsettable.getNewPosition(); + } + + public boolean usesUleb128() { + return useUleb128; + } + + /** + * Mark this Offset as using the ULEB128 encoding. + */ + public void setUsesUleb128() { + if (useUleb128) { + throw new Error("Offset is already marked as using ULEB128!"); + } + useUleb128 = true; + } + + public boolean isNewOffset() { + return isNewOffset; + } + + public void setPointsAtNull() { + pointsAtNull = true; + } + + public void setOutputLocation(int loc) { + outputLocation = loc; + outputLocationSet = true; + } + + /** + * Get the location in the output DEX file where this offset has been written. + * (This is used when patching Offsets when the Offsettable position was not + * known at the time of writing out the Offset.) + */ + public int getOutputLocation() { + if (!outputLocationSet) { + throw new Error("Output location was not set yet!"); + } + return outputLocation; + } + + public void setOriginalOffset(int offset) { + originalOffset = offset; + } + + public int getOriginalOffset() { + return originalOffset; + } + + public boolean readyForWriting() { + return offsettable.readyForFinalOffsetToBeWritten(); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/OffsetTracker.java b/tools/dexfuzz/src/dexfuzz/rawdex/OffsetTracker.java new file mode 100644 index 0000000..34d609a --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/OffsetTracker.java @@ -0,0 +1,513 @@ +/* + * 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.rawdex; + +import dexfuzz.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class allows the recording of both Offsettable items (that is, items that can be + * referred to by an offset somewhere else in the file - RawDexObjects) and Offsets. + * The idea in a nutshell is that for every Offsettable item we read, we remember + * its original position in the file using a map, and the order in which the Offsettables were + * written out. We also remember every Offset we read in, and its value. Then, after reading + * the whole file, we use the map to find the Offsettable it pointed at. + * Then, as we write out the file, for every Offsettable we write out, we record its new position, + * using the order we collected earlier. For every Offset we write out, we look at its Offsettable + * to see where it was written. If it hasn't been written yet, then we write out a blank value + * for the time being, remember where that blank value was written, and put the Offset into a + * table for patching once everything has been written out. + * There are some variables (index_after_map_list, restore_point) used for remembering certain + * points to jump forward and back to, because we cannot read and write the file out in exactly + * the same order. + * TODO: Perhaps it makes more sense to just reorder the offsettable_table once it's been read, + * in preparation for the order in which the file is written out? + * Finally, we provide methods for adding new Offsettable items into the right place in the order + * table. + */ +public class OffsetTracker { + /** + * A map from the original offset in the input DEX file to + * the Offsettable it points to. (That Offsettable will contain + * the actual item, and later on the new offset for the item when + * the item is written out. + */ + private Map<Integer, Offsettable> offsettableMap; + + /** + * A table of all Offsettables. We need to ensure we write out + * all items in the same order we read them in, to make sure we update + * the Offsettable.new_position field with the correct value wrt to + * the original_position field. + */ + private List<Offsettable> offsettableTable; + + /** + * A table of all offsets that is populated as we read in the DEX file. + * As the end, we find the correct Offsettable for the Offset in the above + * map, and associate them. + */ + private List<Offset> needsAssociationTable; + + /** + * A table of all offsets that we could not write out an updated offset for + * as we write out a DEX file. Will be checked after writing is complete, + * to allow specific patching of each offset's location as at that point + * all Offsettables will have been updated with their new position. + */ + private List<Offset> needsUpdateTable; + + /** + * Tracks how far we are through the offsettable_table as we write out the file. + */ + private int offsettableTableIdx; + + /** + * Because we write in a slightly different order to how we read + * (why? to read, we read the header, then the map list, and then use the map + * list to read everything else. + * when we write, we write the header, and then we cannot write the map list + * because we don't know where it will go yet, so we write everything else first) + * so: we remember the position in the offsettable_table after we read the map list, + * so we can skip there after we write out the header. + */ + private int indexAfterMapList; + + /** + * Related to index_after_map_list, this is the index we save when we're jumping back to + * write the map list. + */ + private int restorePoint; + + /** + * Create a new OffsetTracker. Should persist between parsing a DEX file, and outputting + * the mutated DEX file. + */ + public OffsetTracker() { + offsettableMap = new HashMap<Integer,Offsettable>(); + offsettableTable = new ArrayList<Offsettable>(); + needsAssociationTable = new ArrayList<Offset>(); + needsUpdateTable = new ArrayList<Offset>(); + } + + /** + * Lookup an Item by the offset it had in the input DEX file. + * @param offset The offset in the input DEX file. + * @return The corresponding Item. + */ + public RawDexObject getItemByOffset(int offset) { + return offsettableMap.get(offset).getItem(); + } + + /** + * As Items are read in, they call this function once they have word-aligned the file pointer, + * to record their position and themselves into an Offsettable object, that will be tracked. + * @param file Used for recording position into the new Offsettable. + * @param item Used for recording the relevant Item into the new Offsettable. + */ + public void getNewOffsettable(DexRandomAccessFile file, RawDexObject item) throws IOException { + Offsettable offsettable = new Offsettable(item, false); + offsettable.setOriginalPosition((int) file.getFilePointer()); + offsettableMap.put(offsettable.getOriginalPosition(), offsettable); + offsettableTable.add(offsettable); + } + + /** + * As Items read in Offsets, they call this function with the offset they originally + * read from the file, to allow later association with an Offsettable. + * @param originalOffset The original offset read from the input DEX file. + * @return An Offset that will later be associated with an Offsettable. + */ + public Offset getNewOffset(int originalOffset) throws IOException { + Offset offset = new Offset(false); + offset.setOriginalOffset(originalOffset); + needsAssociationTable.add(offset); + return offset; + } + + /** + * Only MapItem should call this method, when the MapItem that points to the header + * is read. + */ + public Offset getNewHeaderOffset(int originalOffset) throws IOException { + Offset offset = new Offset(true); + offset.setOriginalOffset(originalOffset); + needsAssociationTable.add(offset); + return offset; + } + + /** + * Call this after reading, to associate Offsets with Offsettables. + */ + public void associateOffsets() { + for (Offset offset : needsAssociationTable) { + if (offset.getOriginalOffset() == 0 && !(offset.pointsAtHeader())) { + offset.setPointsAtNull(); + } else { + offset.pointTo(offsettableMap.get(offset.getOriginalOffset())); + if (!offset.pointsToSomething()) { + Log.error(String.format("Couldn't find original offset 0x%x!", + offset.getOriginalOffset())); + } + } + } + needsAssociationTable.clear(); + } + + /** + * As Items are written out into the output DEX file, this function is called + * to update the next Offsettable with the file pointer's current position. + * This should allow the tracking of new offset locations. + * This also requires that reading and writing of all items happens in the same order + * (with the exception of the map list, see above) + * @param file Used for recording the new position. + */ + public void updatePositionOfNextOffsettable(DexRandomAccessFile file) throws IOException { + if (offsettableTableIdx == offsettableTable.size()) { + Log.errorAndQuit("Not all created Offsettable items have been added to the " + + "Offsettable Table!"); + } + Offsettable offsettable = offsettableTable.get(offsettableTableIdx); + offsettable.setNewPosition((int) file.getFilePointer()); + offsettableTableIdx++; + } + + /** + * As Items are written out, any writing out of an offset must call this function, passing + * in the relevant offset. This function will write out the offset, if the associated + * Offsettable has been updated with its new position, or else will write out a null value, and + * the Offset will be stored for writing after all Items have been written, and all + * Offsettables MUST have been updated. + * @param offset The offset received from getNewOffset(). + * @param file Used for writing out to the file. + * @param useUleb128 Whether or not the offset should be written in UINT or ULEB128 form. + */ + public void tryToWriteOffset(Offset offset, DexRandomAccessFile file, boolean useUleb128) + throws IOException { + if (!offset.isNewOffset() && (!offset.pointsToSomething())) { + if (useUleb128) { + file.writeUleb128(0); + } else { + file.writeUInt(0); + } + return; + } + + if (offset.readyForWriting()) { + if (useUleb128) { + file.writeUleb128(offset.getNewPositionOfItem()); + } else { + file.writeUInt(offset.getNewPositionOfItem()); + } + } else { + offset.setOutputLocation((int) file.getFilePointer()); + if (useUleb128) { + file.writeLargestUleb128(offset.getOriginalOffset()); + offset.setUsesUleb128(); + } else { + file.writeUInt(offset.getOriginalOffset()); + } + needsUpdateTable.add(offset); + } + } + + /** + * This is called after all writing has finished, to write out any Offsets + * that could not be written out during the original writing phase, because their + * associated Offsettables hadn't had their new positions calculated yet. + * @param file Used for writing out to the file. + */ + public void updateOffsets(DexRandomAccessFile file) throws IOException { + if (offsettableTableIdx != offsettableTable.size()) { + Log.errorAndQuit("Being asked to update dangling offsets but the " + + "correct number of offsettables has not been written out!"); + } + for (Offset offset : needsUpdateTable) { + file.seek(offset.getOutputLocation()); + if (offset.usesUleb128()) { + file.writeLargestUleb128(offset.getNewPositionOfItem()); + } else { + file.writeUInt(offset.getNewPositionOfItem()); + } + } + needsUpdateTable.clear(); + } + + /** + * Called after writing out the header, to skip to after the map list. + */ + public void skipToAfterMapList() { + offsettableTableIdx = indexAfterMapList; + } + + /** + * Called once the map list needs to be written out, to set the + * offsettable table index back to the right place. + */ + public void goBackToMapList() { + restorePoint = offsettableTableIdx; + offsettableTableIdx = (indexAfterMapList - 1); + } + + /** + * Called once the map list has been written out, to set the + * offsettable table index back to where it was before. + */ + public void goBackToPreviousPoint() { + if (offsettableTableIdx != indexAfterMapList) { + Log.errorAndQuit("Being asked to go to the point before the MapList was written out," + + " but we're not in the right place."); + } + offsettableTableIdx = restorePoint; + } + + /** + * Called after reading in the map list, to remember the point to be skipped + * to later. + */ + public void rememberPointAfterMapList() { + indexAfterMapList = offsettableTable.size(); + } + + private void updateHeaderOffsetIfValid(Offset offset, Offsettable previousFirst, + Offsettable newFirst, String offsetName) { + if (offset.pointsToThisOffsettable(previousFirst)) { + offset.pointToNew(newFirst); + } else { + Log.errorAndQuit("Header " + offsetName + " offset not pointing at first element?"); + } + } + + private void addTypeListsToMapFile(RawDexFile rawDexFile, Offsettable typeListOffsettable) { + // Create a MapItem for the TypeLists + MapItem typeListMapItem = new MapItem(); + typeListMapItem.offset = new Offset(false); + typeListMapItem.offset.pointToNew(typeListOffsettable); + typeListMapItem.type = MapItem.TYPE_TYPE_LIST; + typeListMapItem.size = 1; + + // Insert into the MapList. + // (first, find the MapItem that points to StringDataItems...) + int idx = 0; + for (MapItem mapItem : rawDexFile.mapList.mapItems) { + if (mapItem.type == MapItem.TYPE_STRING_DATA_ITEM) { + break; + } + idx++; + } + // (now insert the TypeList MapItem just before the StringDataItem one...) + rawDexFile.mapList.mapItems.add(idx, typeListMapItem); + } + + private void addFieldIdsToHeaderAndMapFile(RawDexFile rawDexFile, + Offsettable fieldOffsettable) { + // Add the field IDs to the header. + rawDexFile.header.fieldIdsOff.unsetNullAndPointTo(fieldOffsettable); + rawDexFile.header.fieldIdsSize = 1; + + // Create a MapItem for the field IDs. + MapItem fieldMapItem = new MapItem(); + fieldMapItem.offset = new Offset(false); + fieldMapItem.offset.pointToNew(fieldOffsettable); + fieldMapItem.type = MapItem.TYPE_FIELD_ID_ITEM; + fieldMapItem.size = 1; + + // Insert into the MapList. + // (first, find the MapItem that points to MethodIdItems...) + int idx = 0; + for (MapItem mapItem : rawDexFile.mapList.mapItems) { + if (mapItem.type == MapItem.TYPE_METHOD_ID_ITEM) { + break; + } + idx++; + } + // (now insert the FieldIdItem MapItem just before the MethodIdItem one...) + rawDexFile.mapList.mapItems.add(idx, fieldMapItem); + } + + + private void updateOffsetsInHeaderAndMapFile(RawDexFile rawDexFile, + Offsettable newFirstOffsettable) { + Offsettable prevFirstOffsettable = null; + for (int i = 0; i < offsettableTable.size(); i++) { + if (offsettableTable.get(i) == newFirstOffsettable) { + prevFirstOffsettable = offsettableTable.get(i + 1); + break; + } + } + if (prevFirstOffsettable == null) { + Log.errorAndQuit("When calling updateMapListOffsets, could not find new " + + "first offsettable?"); + } + + // Based on the type of the item we just added, check the relevant Offset in the header + // and if it pointed at the prev_first_offsettable, make it point at the new one. + // NB: if it isn't pointing at the prev one, something is wrong. + HeaderItem header = rawDexFile.header; + if (newFirstOffsettable.getItem() instanceof StringIdItem) { + updateHeaderOffsetIfValid(header.stringIdsOff, prevFirstOffsettable, + newFirstOffsettable, "StringID"); + } else if (newFirstOffsettable.getItem() instanceof TypeIdItem) { + updateHeaderOffsetIfValid(header.typeIdsOff, prevFirstOffsettable, + newFirstOffsettable, "TypeID"); + } else if (newFirstOffsettable.getItem() instanceof ProtoIdItem) { + updateHeaderOffsetIfValid(header.protoIdsOff, prevFirstOffsettable, + newFirstOffsettable, "ProtoID"); + } else if (newFirstOffsettable.getItem() instanceof FieldIdItem) { + updateHeaderOffsetIfValid(header.fieldIdsOff, prevFirstOffsettable, + newFirstOffsettable, "FieldID"); + } else if (newFirstOffsettable.getItem() instanceof MethodIdItem) { + updateHeaderOffsetIfValid(header.methodIdsOff, prevFirstOffsettable, + newFirstOffsettable, "MethodID"); + } else if (newFirstOffsettable.getItem() instanceof ClassDefItem) { + updateHeaderOffsetIfValid(header.classDefsOff, prevFirstOffsettable, + newFirstOffsettable, "ClassDef"); + } + + // Now iterate through the MapList's MapItems, and see if their Offsets pointed at the + // prev_first_offsettable, and if so, make them now point at the new_first_offsettable. + for (MapItem mapItem : rawDexFile.mapList.mapItems) { + if (mapItem.offset.pointsToThisOffsettable(prevFirstOffsettable)) { + Log.info("Updating offset in MapItem (type: " + mapItem.type + ") after " + + "we called insertNewOffsettableAsFirstOfType()"); + mapItem.offset.pointToNew(newFirstOffsettable); + } + } + } + + private void insertOffsettableAt(int idx, Offsettable offsettable) { + offsettableTable.add(idx, offsettable); + if (indexAfterMapList > idx) { + indexAfterMapList++; + } + if (restorePoint > idx) { + restorePoint++; + } + } + + /** + * If we're creating our first TypeList, then IdCreator has to call this method to + * ensure it gets put into the correct place in the offsettable table. + * This assumes TypeLists always come before StringDatas. + */ + public Offsettable insertNewOffsettableAsFirstEverTypeList(RawDexObject item, + RawDexFile rawDexFile) { + // We find the first StringDataItem, the type lists will come before this. + Log.info("Calling insertNewOffsettableAsFirstEverTypeList()"); + for (int i = 0; i < offsettableTable.size(); i++) { + if (offsettableTable.get(i).getItem() instanceof StringDataItem) { + Offsettable offsettable = new Offsettable(item, true); + insertOffsettableAt(i, offsettable); + addTypeListsToMapFile(rawDexFile, offsettable); + return offsettable; + } + } + Log.errorAndQuit("Could not find any StringDataItems to insert the type list before."); + return null; + } + + /** + * If we're creating our first FieldId, then IdCreator has to call this method to + * ensure it gets put into the correct place in the offsettable table. + * This assumes FieldIds always come before MethodIds. + */ + public Offsettable insertNewOffsettableAsFirstEverField(RawDexObject item, + RawDexFile rawDexFile) { + // We find the first MethodIdItem, the fields will come before this. + Log.info("Calling insertNewOffsettableAsFirstEverField()"); + for (int i = 0; i < offsettableTable.size(); i++) { + if (offsettableTable.get(i).getItem() instanceof MethodIdItem) { + Offsettable offsettable = new Offsettable(item, true); + insertOffsettableAt(i, offsettable); + addFieldIdsToHeaderAndMapFile(rawDexFile, offsettable); + return offsettable; + } + } + Log.errorAndQuit("Could not find any MethodIdItems to insert the field before."); + return null; + } + + /** + * If we're creating a new Item (such as FieldId, MethodId) that is placed into the + * first position of the relevant ID table, then IdCreator has to call this method to + * ensure it gets put into the correct place in the offsettable table. + */ + public Offsettable insertNewOffsettableAsFirstOfType(RawDexObject item, + RawDexFile rawDexFile) { + Log.debug("Calling insertNewOffsettableAsFirstOfType()"); + int index = getOffsettableIndexForFirstItemType(item); + if (index == -1) { + Log.errorAndQuit("Could not find any object of class: " + item.getClass()); + } + Offsettable offsettable = new Offsettable(item, true); + insertOffsettableAt(index, offsettable); + updateOffsetsInHeaderAndMapFile(rawDexFile, offsettable); + return offsettable; + } + + /** + * IdCreator has to call this method when it creates a new IdItem, to make sure it + * gets put into the correct place in the offsettable table. IdCreator should + * provide the IdItem that should come before this new IdItem. + */ + public Offsettable insertNewOffsettableAfter(RawDexObject item, RawDexObject itemBefore) { + Log.debug("Calling insertNewOffsettableAfter()"); + int index = getOffsettableIndexForItem(itemBefore); + if (index == -1) { + Log.errorAndQuit("Did not find specified 'after' object in offsettable table."); + } + Offsettable offsettable = new Offsettable(item, true); + insertOffsettableAt(index + 1, offsettable); + return offsettable; + } + + private int getOffsettableIndexForFirstItemType(RawDexObject item) { + Class<?> itemClass = item.getClass(); + for (int i = 0; i < offsettableTable.size(); i++) { + if (offsettableTable.get(i).getItem().getClass().equals(itemClass)) { + return i; + } + } + return -1; + } + + private int getOffsettableIndexForItem(RawDexObject item) { + for (int i = 0; i < offsettableTable.size(); i++) { + if (offsettableTable.get(i).getItem() == item) { + return i; + } + } + return -1; + } + + /** + * Given a RawDexObject, get the Offsettable that contains it. + */ + public Offsettable getOffsettableForItem(RawDexObject item) { + for (int i = 0; i < offsettableTable.size(); i++) { + if (offsettableTable.get(i).getItem() == item) { + return offsettableTable.get(i); + } + } + return null; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/Offsettable.java b/tools/dexfuzz/src/dexfuzz/rawdex/Offsettable.java new file mode 100644 index 0000000..1b8cb24 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/Offsettable.java @@ -0,0 +1,117 @@ +/* + * 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.rawdex; + +/** + * Tracks the original and updated positions of a RawDexObject when it is + * parsed in from a DEX file, and written out to a mutated DEX file. + */ +public class Offsettable { + /** + * The position of this Offsettable's item when it was read in. + */ + private int originalPosition; + + /** + * Set as we write out any Offsettable, so the Offset knows what its + * new value should be. + */ + private int newPosition; + + /** + * The actual Item this Offsettable contains. + */ + private RawDexObject item; + + /** + * Set either when getOriginalPosition() is called by the OffsetTracker + * to put the location in the offsettable map, so when Offsets are being + * associated, they know which Offsettable to point at. + * Or when an Offsettable is created that is marked as new, so we don't + * need to know its original position, because an Offset will be directly + * associated with it. + */ + private boolean originalPositionKnown; + + /** + * Set when we calculate the new position of this Offsettable as the file is + * being output. + */ + private boolean updated; + + /** + * Only the OffsetTracker should be able to create a new Offsettable. + */ + public Offsettable(RawDexObject item, boolean isNew) { + this.item = item; + if (isNew) { + // We no longer care about the original position of the Offsettable, because + // we are at the stage where we manually point Offsets at Offsettables, and + // don't need to use the OffsetTracker's offsettable map. + // So just lie and say we know it now. + originalPositionKnown = true; + } + } + + public RawDexObject getItem() { + return item; + } + + /** + * Gets the offset from the beginning of the file to the RawDexObject this Offsettable + * contains, when the file was originally read. + * Called when we're associating Offsets with Offsettables using the OffsetTracker's + * offsettable map. + */ + public int getOriginalPosition() { + if (!originalPositionKnown) { + throw new Error("Cannot get the original position of an Offsettable when not yet set."); + } + return originalPosition; + } + + public void setOriginalPosition(int pos) { + originalPosition = pos; + originalPositionKnown = true; + } + + /** + * Get the new position of this Offsettable, once it's been written out to the output file. + */ + public int getNewPosition() { + if (!updated) { + throw new Error("Cannot request new position before it has been set!"); + } + return newPosition; + } + + /** + * Record the new position of this Offsettable, as it is written out to the output file. + */ + public void setNewPosition(int pos) { + if (!updated) { + newPosition = pos; + updated = true; + } else { + throw new Error("Cannot update an Offsettable twice!"); + } + } + + public boolean readyForFinalOffsetToBeWritten() { + return (originalPositionKnown && updated); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/Opcode.java b/tools/dexfuzz/src/dexfuzz/rawdex/Opcode.java new file mode 100644 index 0000000..312e855 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/Opcode.java @@ -0,0 +1,280 @@ +/* + * 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.rawdex; + +public enum Opcode { + NOP, + MOVE, + MOVE_FROM16, + MOVE_16, + MOVE_WIDE, + MOVE_WIDE_FROM16, + MOVE_WIDE_16, + MOVE_OBJECT, + MOVE_OBJECT_FROM16, + MOVE_OBJECT_16, + MOVE_RESULT, + MOVE_RESULT_WIDE, + MOVE_RESULT_OBJECT, + MOVE_EXCEPTION, + RETURN_VOID, + RETURN, + RETURN_WIDE, + RETURN_OBJECT, + CONST_4, + CONST_16, + CONST, + CONST_HIGH16, + CONST_WIDE_16, + CONST_WIDE_32, + CONST_WIDE, + CONST_WIDE_HIGH16, + CONST_STRING, + CONST_STRING_JUMBO, + CONST_CLASS, + MONITOR_ENTER, + MONITOR_EXIT, + CHECK_CAST, + INSTANCE_OF, + ARRAY_LENGTH, + NEW_INSTANCE, + NEW_ARRAY, + FILLED_NEW_ARRAY, + FILLED_NEW_ARRAY_RANGE, + FILL_ARRAY_DATA, + THROW, + GOTO, + GOTO_16, + GOTO_32, + PACKED_SWITCH, + SPARSE_SWITCH, + CMPL_FLOAT, + CMPG_FLOAT, + CMPL_DOUBLE, + CMPG_DOUBLE, + CMP_LONG, + IF_EQ, + IF_NE, + IF_LT, + IF_GE, + IF_GT, + IF_LE, + IF_EQZ, + IF_NEZ, + IF_LTZ, + IF_GEZ, + IF_GTZ, + IF_LEZ, + UNUSED_3E, + UNUSED_3F, + UNUSED_40, + UNUSED_41, + UNUSED_42, + UNUSED_43, + AGET, + AGET_WIDE, + AGET_OBJECT, + AGET_BOOLEAN, + AGET_BYTE, + AGET_CHAR, + AGET_SHORT, + APUT, + APUT_WIDE, + APUT_OBJECT, + APUT_BOOLEAN, + APUT_BYTE, + APUT_CHAR, + APUT_SHORT, + IGET, + IGET_WIDE, + IGET_OBJECT, + IGET_BOOLEAN, + IGET_BYTE, + IGET_CHAR, + IGET_SHORT, + IPUT, + IPUT_WIDE, + IPUT_OBJECT, + IPUT_BOOLEAN, + IPUT_BYTE, + IPUT_CHAR, + IPUT_SHORT, + SGET, + SGET_WIDE, + SGET_OBJECT, + SGET_BOOLEAN, + SGET_BYTE, + SGET_CHAR, + SGET_SHORT, + SPUT, + SPUT_WIDE, + SPUT_OBJECT, + SPUT_BOOLEAN, + SPUT_BYTE, + SPUT_CHAR, + SPUT_SHORT, + INVOKE_VIRTUAL, + INVOKE_SUPER, + INVOKE_DIRECT, + INVOKE_STATIC, + INVOKE_INTERFACE, + RETURN_VOID_BARRIER, + INVOKE_VIRTUAL_RANGE, + INVOKE_SUPER_RANGE, + INVOKE_DIRECT_RANGE, + INVOKE_STATIC_RANGE, + INVOKE_INTERFACE_RANGE, + UNUSED_79, + UNUSED_7A, + NEG_INT, + NOT_INT, + NEG_LONG, + NOT_LONG, + NEG_FLOAT, + NEG_DOUBLE, + INT_TO_LONG, + INT_TO_FLOAT, + INT_TO_DOUBLE, + LONG_TO_INT, + LONG_TO_FLOAT, + LONG_TO_DOUBLE, + FLOAT_TO_INT, + FLOAT_TO_LONG, + FLOAT_TO_DOUBLE, + DOUBLE_TO_INT, + DOUBLE_TO_LONG, + DOUBLE_TO_FLOAT, + INT_TO_BYTE, + INT_TO_CHAR, + INT_TO_SHORT, + ADD_INT, + SUB_INT, + MUL_INT, + DIV_INT, + REM_INT, + AND_INT, + OR_INT, + XOR_INT, + SHL_INT, + SHR_INT, + USHR_INT, + ADD_LONG, + SUB_LONG, + MUL_LONG, + DIV_LONG, + REM_LONG, + AND_LONG, + OR_LONG, + XOR_LONG, + SHL_LONG, + SHR_LONG, + USHR_LONG, + ADD_FLOAT, + SUB_FLOAT, + MUL_FLOAT, + DIV_FLOAT, + REM_FLOAT, + ADD_DOUBLE, + SUB_DOUBLE, + MUL_DOUBLE, + DIV_DOUBLE, + REM_DOUBLE, + ADD_INT_2ADDR, + SUB_INT_2ADDR, + MUL_INT_2ADDR, + DIV_INT_2ADDR, + REM_INT_2ADDR, + AND_INT_2ADDR, + OR_INT_2ADDR, + XOR_INT_2ADDR, + SHL_INT_2ADDR, + SHR_INT_2ADDR, + USHR_INT_2ADDR, + ADD_LONG_2ADDR, + SUB_LONG_2ADDR, + MUL_LONG_2ADDR, + DIV_LONG_2ADDR, + REM_LONG_2ADDR, + AND_LONG_2ADDR, + OR_LONG_2ADDR, + XOR_LONG_2ADDR, + SHL_LONG_2ADDR, + SHR_LONG_2ADDR, + USHR_LONG_2ADDR, + ADD_FLOAT_2ADDR, + SUB_FLOAT_2ADDR, + MUL_FLOAT_2ADDR, + DIV_FLOAT_2ADDR, + REM_FLOAT_2ADDR, + ADD_DOUBLE_2ADDR, + SUB_DOUBLE_2ADDR, + MUL_DOUBLE_2ADDR, + DIV_DOUBLE_2ADDR, + REM_DOUBLE_2ADDR, + ADD_INT_LIT16, + RSUB_INT, + MUL_INT_LIT16, + DIV_INT_LIT16, + REM_INT_LIT16, + AND_INT_LIT16, + OR_INT_LIT16, + XOR_INT_LIT16, + ADD_INT_LIT8, + RSUB_INT_LIT8, + MUL_INT_LIT8, + DIV_INT_LIT8, + REM_INT_LIT8, + AND_INT_LIT8, + OR_INT_LIT8, + XOR_INT_LIT8, + SHL_INT_LIT8, + SHR_INT_LIT8, + USHR_INT_LIT8, + IGET_QUICK, + IGET_WIDE_QUICK, + IGET_OBJECT_QUICK, + IPUT_QUICK, + IPUT_WIDE_QUICK, + IPUT_OBJECT_QUICK, + INVOKE_VIRTUAL_QUICK, + INVOKE_VIRTUAL_QUICK_RANGE, + IPUT_BOOLEAN_QUICK, + IPUT_BYTE_QUICK, + IPUT_CHAR_QUICK, + IPUT_SHORT_QUICK, + UNUSED_EF, + UNUSED_F0, + UNUSED_F1, + UNUSED_F2, + UNUSED_F3, + UNUSED_F4, + UNUSED_F5, + UNUSED_F6, + UNUSED_F7, + UNUSED_F8, + UNUSED_F9, + UNUSED_FA, + UNUSED_FB, + UNUSED_FC, + UNUSED_FD, + UNUSED_FE, + UNUSED_FF; + + public static boolean isBetween(Opcode opcode, Opcode opcode1, Opcode opcode2) { + return (opcode.ordinal() >= opcode1.ordinal() && opcode.ordinal() <= opcode2.ordinal()); + } +}
\ No newline at end of file diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/OpcodeInfo.java b/tools/dexfuzz/src/dexfuzz/rawdex/OpcodeInfo.java new file mode 100644 index 0000000..fb1d093 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/OpcodeInfo.java @@ -0,0 +1,41 @@ +/* + * 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.rawdex; + +import dexfuzz.rawdex.formats.AbstractFormat; + +/** + * Every Instruction points to an OpcodeInfo object that holds useful information + * about that kind of instruction, including the Format that allows us to read the + * instructions fields correctly. + */ +public class OpcodeInfo { + public final Opcode opcode; + public final String name; + public final int value; + public final AbstractFormat format; + + /** + * Construct an OpcodeInfo. A static list of these is created in Instruction.java. + */ + public OpcodeInfo(Opcode opcode, String name, int opcodeValue, AbstractFormat fmt) { + this.opcode = opcode; + this.name = name; + this.value = opcodeValue; + this.format = fmt; + } +}
\ No newline at end of file diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/ParameterAnnotation.java b/tools/dexfuzz/src/dexfuzz/rawdex/ParameterAnnotation.java new file mode 100644 index 0000000..0db5efd --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/ParameterAnnotation.java @@ -0,0 +1,43 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class ParameterAnnotation implements RawDexObject { + public int methodIdx; + public Offset annotationsOff; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + methodIdx = file.readUInt(); + annotationsOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUInt(methodIdx); + file.getOffsetTracker().tryToWriteOffset(annotationsOff, file, false /* ULEB128 */); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.METHOD_ID && methodIdx >= insertedIdx) { + methodIdx++; + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/ProtoIdItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/ProtoIdItem.java new file mode 100644 index 0000000..3ccc82f --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/ProtoIdItem.java @@ -0,0 +1,51 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class ProtoIdItem implements RawDexObject { + public int shortyIdx; + public int returnTypeIdx; + public Offset parametersOff; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().getNewOffsettable(file, this); + shortyIdx = file.readUInt(); + returnTypeIdx = file.readUInt(); + parametersOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeUInt(shortyIdx); + file.writeUInt(returnTypeIdx); + file.getOffsetTracker().tryToWriteOffset(parametersOff, file, false /* ULEB128 */); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.STRING_ID && shortyIdx >= insertedIdx) { + shortyIdx++; + } + if (kind == IndexUpdateKind.TYPE_ID && returnTypeIdx >= insertedIdx) { + returnTypeIdx++; + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/RawDexFile.java b/tools/dexfuzz/src/dexfuzz/rawdex/RawDexFile.java new file mode 100644 index 0000000..483ed6c --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/RawDexFile.java @@ -0,0 +1,390 @@ +/* + * 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.rawdex; + +import dexfuzz.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class RawDexFile implements RawDexObject { + private OffsetTracker offsetTracker; + + public HeaderItem header; + + public MapList mapList; + + // Can be allocated after reading the header. + public List<StringIdItem> stringIds; + public List<TypeIdItem> typeIds; + public List<ProtoIdItem> protoIds; + public List<FieldIdItem> fieldIds; + public List<MethodIdItem> methodIds; + public List<ClassDefItem> classDefs; + + // Need to be allocated later (will be allocated in MapList.java) + public List<StringDataItem> stringDatas; + public List<ClassDataItem> classDatas; + public List<TypeList> typeLists; + public List<CodeItem> codeItems; + public DebugInfoItem debugInfoItem; + public List<AnnotationsDirectoryItem> annotationsDirectoryItems; + public List<AnnotationSetRefList> annotationSetRefLists; + public List<AnnotationSetItem> annotationSetItems; + public List<AnnotationItem> annotationItems; + public List<EncodedArrayItem> encodedArrayItems; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + // Get a reference to the OffsetTracker, so that IdCreator can use it. + offsetTracker = file.getOffsetTracker(); + + file.seek(0); + + // Read header. + (header = new HeaderItem()).read(file); + + // We can allocate all of these now. + stringIds = new ArrayList<StringIdItem>(header.stringIdsSize); + typeIds = new ArrayList<TypeIdItem>(header.typeIdsSize); + protoIds = new ArrayList<ProtoIdItem>(header.protoIdsSize); + fieldIds = new ArrayList<FieldIdItem>(header.fieldIdsSize); + methodIds = new ArrayList<MethodIdItem>(header.methodIdsSize); + classDefs = new ArrayList<ClassDefItem>(header.classDefsSize); + + mapList = new MapList(this); + mapList.read(file); + + file.getOffsetTracker().associateOffsets(); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.seek(0); + + // We read the header first, and then the map list, and then everything + // else. Therefore, when we get to the end of the header, tell OffsetTracker + // to skip past the map list offsets, and then when we get to the map list, + // tell OffsetTracker to skip back there, and then return to where it was previously. + + // Update the map items' sizes first + // - but only update the items that we expect to have changed size. + // ALSO update the header's table sizes! + for (MapItem mapItem : mapList.mapItems) { + switch (mapItem.type) { + case MapItem.TYPE_STRING_ID_ITEM: + if (mapItem.size != stringIds.size()) { + Log.debug("Updating StringIDs List size: " + stringIds.size()); + mapItem.size = stringIds.size(); + header.stringIdsSize = stringIds.size(); + } + break; + case MapItem.TYPE_STRING_DATA_ITEM: + if (mapItem.size != stringDatas.size()) { + Log.debug("Updating StringDatas List size: " + stringDatas.size()); + mapItem.size = stringDatas.size(); + } + break; + case MapItem.TYPE_METHOD_ID_ITEM: + if (mapItem.size != methodIds.size()) { + Log.debug("Updating MethodIDs List size: " + methodIds.size()); + mapItem.size = methodIds.size(); + header.methodIdsSize = methodIds.size(); + } + break; + case MapItem.TYPE_FIELD_ID_ITEM: + if (mapItem.size != fieldIds.size()) { + Log.debug("Updating FieldIDs List size: " + fieldIds.size()); + mapItem.size = fieldIds.size(); + header.fieldIdsSize = fieldIds.size(); + } + break; + case MapItem.TYPE_PROTO_ID_ITEM: + if (mapItem.size != protoIds.size()) { + Log.debug("Updating ProtoIDs List size: " + protoIds.size()); + mapItem.size = protoIds.size(); + header.protoIdsSize = protoIds.size(); + } + break; + case MapItem.TYPE_TYPE_ID_ITEM: + if (mapItem.size != typeIds.size()) { + Log.debug("Updating TypeIDs List size: " + typeIds.size()); + mapItem.size = typeIds.size(); + header.typeIdsSize = typeIds.size(); + } + break; + case MapItem.TYPE_TYPE_LIST: + if (mapItem.size != typeLists.size()) { + Log.debug("Updating TypeLists List size: " + typeLists.size()); + mapItem.size = typeLists.size(); + } + break; + default: + } + } + + // Use the map list to write the file. + for (MapItem mapItem : mapList.mapItems) { + switch (mapItem.type) { + case MapItem.TYPE_HEADER_ITEM: + header.write(file); + file.getOffsetTracker().skipToAfterMapList(); + break; + case MapItem.TYPE_STRING_ID_ITEM: + if (mapItem.size != stringIds.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches StringIDs table size " + stringIds.size()); + } + for (StringIdItem stringId : stringIds) { + stringId.write(file); + } + break; + case MapItem.TYPE_TYPE_ID_ITEM: + if (mapItem.size != typeIds.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches TypeIDs table size " + typeIds.size()); + } + for (TypeIdItem typeId : typeIds) { + typeId.write(file); + } + break; + case MapItem.TYPE_PROTO_ID_ITEM: + if (mapItem.size != protoIds.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches ProtoIDs table size " + protoIds.size()); + } + for (ProtoIdItem protoId : protoIds) { + protoId.write(file); + } + break; + case MapItem.TYPE_FIELD_ID_ITEM: + if (mapItem.size != fieldIds.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches FieldIDs table size " + fieldIds.size()); + } + for (FieldIdItem fieldId : fieldIds) { + fieldId.write(file); + } + break; + case MapItem.TYPE_METHOD_ID_ITEM: + if (mapItem.size != methodIds.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches MethodIDs table size " + methodIds.size()); + } + for (MethodIdItem methodId : methodIds) { + methodId.write(file); + } + break; + case MapItem.TYPE_CLASS_DEF_ITEM: + if (mapItem.size != classDefs.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches ClassDefs table size " + classDefs.size()); + } + for (ClassDefItem classDef : classDefs) { + classDef.write(file); + } + break; + case MapItem.TYPE_MAP_LIST: + file.getOffsetTracker().goBackToMapList(); + mapList.write(file); + file.getOffsetTracker().goBackToPreviousPoint(); + break; + case MapItem.TYPE_TYPE_LIST: + if (mapItem.size != typeLists.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches TypeLists table size " + typeLists.size()); + } + for (TypeList typeList : typeLists) { + typeList.write(file); + } + break; + case MapItem.TYPE_ANNOTATION_SET_REF_LIST: + if (mapItem.size != annotationSetRefLists.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches AnnotationSetRefLists table size " + + annotationSetRefLists.size()); + } + for (AnnotationSetRefList annotationSetRefList : annotationSetRefLists) { + annotationSetRefList.write(file); + } + break; + case MapItem.TYPE_ANNOTATION_SET_ITEM: + if (mapItem.size != annotationSetItems.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches AnnotationSetItems table size " + + annotationSetItems.size()); + } + for (AnnotationSetItem annotationSetItem : annotationSetItems) { + annotationSetItem.write(file); + } + break; + case MapItem.TYPE_CLASS_DATA_ITEM: + if (mapItem.size != classDatas.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches ClassDataItems table size " + classDatas.size()); + } + for (ClassDataItem classData : classDatas) { + classData.write(file); + } + break; + case MapItem.TYPE_CODE_ITEM: + if (mapItem.size != codeItems.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches CodeItems table size " + codeItems.size()); + } + for (CodeItem codeItem : codeItems) { + codeItem.write(file); + } + break; + case MapItem.TYPE_STRING_DATA_ITEM: + if (mapItem.size != stringDatas.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches StringDataItems table size " + + stringDatas.size()); + } + for (StringDataItem stringDataItem : stringDatas) { + stringDataItem.write(file); + } + break; + case MapItem.TYPE_DEBUG_INFO_ITEM: + debugInfoItem.write(file); + break; + case MapItem.TYPE_ANNOTATION_ITEM: + if (mapItem.size != annotationItems.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches AnnotationItems table size " + + annotationItems.size()); + } + for (AnnotationItem annotationItem : annotationItems) { + annotationItem.write(file); + } + break; + case MapItem.TYPE_ENCODED_ARRAY_ITEM: + if (mapItem.size != encodedArrayItems.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches EncodedArrayItems table size " + + encodedArrayItems.size()); + } + for (EncodedArrayItem encodedArrayItem : encodedArrayItems) { + encodedArrayItem.write(file); + } + break; + case MapItem.TYPE_ANNOTATIONS_DIRECTORY_ITEM: + if (mapItem.size != annotationsDirectoryItems.size()) { + Log.errorAndQuit("MapItem's size " + mapItem.size + + " no longer matches AnnotationDirectoryItems table size " + + annotationsDirectoryItems.size()); + } + for (AnnotationsDirectoryItem annotationsDirectory : annotationsDirectoryItems) { + annotationsDirectory.write(file); + } + break; + default: + Log.errorAndQuit("Encountered unknown map item in map item list."); + } + } + + file.getOffsetTracker().updateOffsets(file); + } + + /** + * Given a DexRandomAccessFile, calculate the correct adler32 checksum for it. + */ + private int calculateAdler32Checksum(DexRandomAccessFile file) throws IOException { + // Skip magic + checksum. + file.seek(12); + int a = 1; + int b = 0; + while (file.getFilePointer() < file.length()) { + a = (a + file.readUnsignedByte()) % 65521; + b = (b + a) % 65521; + } + return (b << 16) | a; + } + + /** + * Given a DexRandomAccessFile, update the file size, data size, and checksum. + */ + public void updateHeader(DexRandomAccessFile file) throws IOException { + // File size must be updated before checksum. + int newFileSize = (int) file.length(); + file.seek(32); + file.writeUInt(newFileSize); + + // Data size must be updated before checksum. + int newDataSize = newFileSize - header.dataOff.getNewPositionOfItem(); + file.seek(104); + file.writeUInt(newDataSize); + + // Now update the checksum. + int newChecksum = calculateAdler32Checksum(file); + file.seek(8); + file.writeUInt(newChecksum); + + header.fileSize = newFileSize; + header.dataSize = newDataSize; + header.checksum = newChecksum; + } + + /** + * This should only be called from NewItemCreator. + */ + public OffsetTracker getOffsetTracker() { + return offsetTracker; + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + for (TypeIdItem typeId : typeIds) { + typeId.incrementIndex(kind, insertedIdx); + } + for (ProtoIdItem protoId : protoIds) { + protoId.incrementIndex(kind, insertedIdx); + } + for (FieldIdItem fieldId : fieldIds) { + fieldId.incrementIndex(kind, insertedIdx); + } + for (MethodIdItem methodId : methodIds) { + methodId.incrementIndex(kind, insertedIdx); + } + for (ClassDefItem classDef : classDefs) { + classDef.incrementIndex(kind, insertedIdx); + } + for (ClassDataItem classData : classDatas) { + classData.incrementIndex(kind, insertedIdx); + } + if (typeLists != null) { + for (TypeList typeList : typeLists) { + typeList.incrementIndex(kind, insertedIdx); + } + } + for (CodeItem codeItem : codeItems) { + codeItem.incrementIndex(kind, insertedIdx); + } + if (annotationsDirectoryItems != null) { + for (AnnotationsDirectoryItem annotationsDirectoryItem : annotationsDirectoryItems) { + annotationsDirectoryItem.incrementIndex(kind, insertedIdx); + } + } + if (annotationItems != null) { + for (AnnotationItem annotationItem : annotationItems) { + annotationItem.incrementIndex(kind, insertedIdx); + } + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/RawDexObject.java b/tools/dexfuzz/src/dexfuzz/rawdex/RawDexObject.java new file mode 100644 index 0000000..0b67e9c --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/RawDexObject.java @@ -0,0 +1,53 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +/** + * Base class for any data structure that we may read or write from a DEX file. + */ +public interface RawDexObject { + /** + * Populate information for this DEX data from the file. + * @param file Input file, should already be "seeked" to the correct position. + * @throws IOException If there's a problem writing to the file. + */ + public void read(DexRandomAccessFile file) throws IOException; + + /** + * Write information for this DEX data to the file. + * @param file Output file, should already be "seeked" to the correct position. + * @throws IOException If there's a problem writing to the file. + */ + public void write(DexRandomAccessFile file) throws IOException; + + public static enum IndexUpdateKind { + STRING_ID, + TYPE_ID, + PROTO_ID, + FIELD_ID, + METHOD_ID + } + + /** + * When we insert a new string, type, proto, field or method into the DEX file, + * this must be called. We may have inserted something into the middle of a table, + * so any indices pointing afterwards must be updated. + */ + public void incrementIndex(IndexUpdateKind kind, int insertedIdx); +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/StringDataItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/StringDataItem.java new file mode 100644 index 0000000..87c603e --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/StringDataItem.java @@ -0,0 +1,88 @@ +/* + * 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.rawdex; + +import dexfuzz.Log; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class StringDataItem implements RawDexObject { + private int size; + private String data; + private byte[] dataAsBytes; + private boolean writeRawBytes; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().getNewOffsettable(file, this); + size = file.readUleb128(); + if (size != 0) { + dataAsBytes = file.readDexUtf(size); + data = new String(dataAsBytes, StandardCharsets.US_ASCII); + if (size != data.length()) { + Log.warn("Don't have full support for decoding MUTF-8 yet, DEX file " + + "may be incorrectly mutated. Avoid using this test case for now."); + writeRawBytes = true; + } + } else { + // Read past the null byte. + file.readByte(); + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeUleb128(size); + if (size > 0) { + if (writeRawBytes) { + file.writeDexUtf(dataAsBytes); + } else { + file.writeDexUtf(data.getBytes(StandardCharsets.US_ASCII)); + } + } else { + // Write out the null byte. + file.writeByte(0); + } + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + } + + public void setSize(int size) { + this.size = size; + } + + public int getSize() { + return size; + } + + public void setString(String data) { + this.data = data; + } + + public String getString() { + if (writeRawBytes) { + Log.warn("Reading a string that hasn't been properly decoded! Returning empty string."); + return ""; + } + return data; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/StringIdItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/StringIdItem.java new file mode 100644 index 0000000..da8c294 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/StringIdItem.java @@ -0,0 +1,40 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class StringIdItem implements RawDexObject { + public Offset stringDataOff; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().getNewOffsettable(file, this); + stringDataOff = file.getOffsetTracker().getNewOffset(file.readUInt()); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.getOffsetTracker().tryToWriteOffset(stringDataOff, file, false /* ULEB128 */); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/TryItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/TryItem.java new file mode 100644 index 0000000..99be6de --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/TryItem.java @@ -0,0 +1,44 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class TryItem implements RawDexObject { + public int startAddr; + public short insnCount; + public short handlerOff; // Not a global offset; don't need to adjust like an Offset. + + @Override + public void read(DexRandomAccessFile file) throws IOException { + startAddr = file.readUInt(); + insnCount = file.readUShort(); + handlerOff = file.readUShort(); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUInt(startAddr); + file.writeUShort(insnCount); + file.writeUShort(handlerOff); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + // Do nothing. + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/TypeIdItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/TypeIdItem.java new file mode 100644 index 0000000..bb3eff1 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/TypeIdItem.java @@ -0,0 +1,42 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class TypeIdItem implements RawDexObject { + public int descriptorIdx; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().getNewOffsettable(file, this); + descriptorIdx = file.readUInt(); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeUInt(descriptorIdx); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.STRING_ID && descriptorIdx >= insertedIdx) { + descriptorIdx++; + } + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/TypeItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/TypeItem.java new file mode 100644 index 0000000..a788b47 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/TypeItem.java @@ -0,0 +1,40 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class TypeItem implements RawDexObject { + public short typeIdx; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + typeIdx = file.readUShort(); + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.writeUShort(typeIdx); + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + if (kind == IndexUpdateKind.TYPE_ID && typeIdx >= insertedIdx) { + typeIdx++; + } + } +}
\ No newline at end of file diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/TypeList.java b/tools/dexfuzz/src/dexfuzz/rawdex/TypeList.java new file mode 100644 index 0000000..90a2b57 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/TypeList.java @@ -0,0 +1,71 @@ +/* + * 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.rawdex; + +import java.io.IOException; + +public class TypeList implements RawDexObject { + public int size; + public TypeItem[] list; + + @Override + public void read(DexRandomAccessFile file) throws IOException { + file.alignForwards(4); + file.getOffsetTracker().getNewOffsettable(file, this); + size = file.readUInt(); + list = new TypeItem[size]; + for (int i = 0; i < size; i++) { + (list[i] = new TypeItem()).read(file); + } + } + + @Override + public void write(DexRandomAccessFile file) throws IOException { + file.alignForwards(4); + file.getOffsetTracker().updatePositionOfNextOffsettable(file); + file.writeUInt(size); + for (TypeItem typeItem : list) { + typeItem.write(file); + } + } + + @Override + public void incrementIndex(IndexUpdateKind kind, int insertedIdx) { + for (TypeItem type : list) { + type.incrementIndex(kind, insertedIdx); + } + } + + /** + * Returns if this TypeList comes before the provided TypeList, considering the legal + * ordering of TypeLists in DEX files. + */ + public boolean comesBefore(TypeList other) { + int checkSize = Math.min(size, other.size); + for (int i = 0; i < checkSize; i++) { + if (list[i].typeIdx < other.list[i].typeIdx) { + return true; + } else if (list[i].typeIdx > other.list[i].typeIdx) { + return false; + } + } + if (size == other.size) { + return false; + } + return true; + } +}
\ No newline at end of file diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/AbstractFormat.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/AbstractFormat.java new file mode 100644 index 0000000..29b15ae --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/AbstractFormat.java @@ -0,0 +1,72 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +/** + * Every Format subclasses this AbstractFormat. The subclasses then implement these + * methods to write out a provided Instruction according to this format, and also methods + * to read the vregs from an Instruction's raw bytes. + * Hierarchy is as follows: + * AbstractFormat + * |____________Format1 + * | |_____Format10t + * | |_____Format10x + * | |_____Format11n + * | |_____Format11x + * | |_____Format12x + * |____________Format2 + * | |_____Format20bc + * | |_____Format20t + * etc... + */ +public abstract class AbstractFormat { + /** + * Get the size of an Instruction that has this format. + */ + public abstract int getSize(); + + /** + * Given a file handle and an instruction, write that Instruction out to the file + * correctly, considering the current format. + */ + public abstract void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException; + + /** + * Read the value of vA, considering this format. + */ + public abstract long getA(byte[] raw) throws IOException; + + /** + * Read the value of vB, considering this format. + */ + public abstract long getB(byte[] raw) throws IOException; + + /** + * Read the value of vC, considering this format. + */ + public abstract long getC(byte[] raw) throws IOException; + + /** + * Only Format35c should return true for this. + */ + public abstract boolean needsInvokeFormatInfo(); +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsConst.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsConst.java new file mode 100644 index 0000000..e2b164a --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsConst.java @@ -0,0 +1,32 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.Instruction; + +/** + * Every Format that contains a value that is a constant (that includes instructions like + * const/4, but also add-int/lit8) should implement this interface, to allow the constant + * part of a provided Instruction to be read and set correctly. + */ +public interface ContainsConst { + public long getConst(Instruction insn); + + public void setConst(Instruction insn, long constant); + + public long getConstRange(); +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsPoolIndex.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsPoolIndex.java new file mode 100644 index 0000000..5f66b0e --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsPoolIndex.java @@ -0,0 +1,41 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.OpcodeInfo; + +/** + * Every Format that contains a value that is an index into the ID tables of this DEX file + * should implement this interface, to allow the index part of a provided Instruction + * to be read and set correctly. + */ +public interface ContainsPoolIndex { + public enum PoolIndexKind { + Type, + Field, + String, + Method, + Invalid + } + + public int getPoolIndex(Instruction insn); + + public void setPoolIndex(Instruction insn, int poolIndex); + + public PoolIndexKind getPoolIndexKind(OpcodeInfo info); +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsTarget.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsTarget.java new file mode 100644 index 0000000..bb24e61 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsTarget.java @@ -0,0 +1,29 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.Instruction; + +/** + * Every Format that contains an offset to a target instruction + * should implement this interface, to allow the offset to be read and set correctly. + */ +public interface ContainsTarget { + public long getTarget(Instruction insn); + + public void setTarget(Instruction insn, long target); +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsVRegs.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsVRegs.java new file mode 100644 index 0000000..40ba5ac --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsVRegs.java @@ -0,0 +1,25 @@ +/* + * 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.rawdex.formats; + +/** + * Every Format that contains virtual registers should implement this interface, + * to allow the number of virtual registers specified by the format to be found. + */ +public interface ContainsVRegs { + public int getVRegCount(); +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format00x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format00x.java new file mode 100644 index 0000000..aae7469 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format00x.java @@ -0,0 +1,54 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format00x extends AbstractFormat { + @Override + public int getSize() { + return 0; + } + + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return 0; + } + + @Override + public long getB(byte[] raw) throws IOException { + return 0; + } + + @Override + public long getC(byte[] raw) throws IOException { + return 0; + } + + @Override + public boolean needsInvokeFormatInfo() { + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format1.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format1.java new file mode 100644 index 0000000..e503513 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format1.java @@ -0,0 +1,54 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format1 extends AbstractFormat { + @Override + public int getSize() { + return 1; + } + + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return 0; + } + + @Override + public long getB(byte[] raw) throws IOException { + return 0; + } + + @Override + public long getC(byte[] raw) throws IOException { + return 0; + } + + @Override + public boolean needsInvokeFormatInfo() { + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10t.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10t.java new file mode 100644 index 0000000..a9e13f1 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10t.java @@ -0,0 +1,56 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format10t extends Format1 implements ContainsTarget { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getSignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getTarget(Instruction insn) { + return insn.vregA; + } + + @Override + public void setTarget(Instruction insn, long target) { + insn.vregA = target; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10x.java new file mode 100644 index 0000000..aabf725 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10x.java @@ -0,0 +1,46 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format10x extends Format1 { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) 0); // padding + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getB(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11n.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11n.java new file mode 100644 index 0000000..4b8c35c --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11n.java @@ -0,0 +1,66 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format11n extends Format1 implements ContainsConst, ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) (insn.vregA | (insn.vregB << 4))); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getConst(Instruction insn) { + return insn.vregB; + } + + @Override + public void setConst(Instruction insn, long constant) { + insn.vregB = constant; + } + + @Override + public long getConstRange() { + return (1 << 4); + } + + @Override + public int getVRegCount() { + return 1; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11x.java new file mode 100644 index 0000000..e8963f1 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11x.java @@ -0,0 +1,51 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format11x extends Format1 implements ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public int getVRegCount() { + return 1; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format12x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format12x.java new file mode 100644 index 0000000..170ebe1 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format12x.java @@ -0,0 +1,51 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format12x extends Format1 implements ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) (insn.vregA | (insn.vregB << 4))); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public int getVRegCount() { + return 2; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format2.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format2.java new file mode 100644 index 0000000..5ddc124 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format2.java @@ -0,0 +1,54 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format2 extends AbstractFormat { + @Override + public int getSize() { + return 2; + } + + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return 0; + } + + @Override + public long getB(byte[] raw) throws IOException { + return 0; + } + + @Override + public long getC(byte[] raw) throws IOException { + return 0; + } + + @Override + public boolean needsInvokeFormatInfo() { + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20bc.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20bc.java new file mode 100644 index 0000000..4f21489 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20bc.java @@ -0,0 +1,51 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +// NB: This format is only used for statically determined verification errors, +// so we shouldn't encounter it in ART. (Or even before, as they are only written in during +// verification, which comes after our fuzzing...) +// Therefore, no need to say this implements ContainsPoolIndex, even though it is a *c format +public class Format20bc extends Format2 { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUShort((short) insn.vregB); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 1); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20t.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20t.java new file mode 100644 index 0000000..1e33c15 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20t.java @@ -0,0 +1,57 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format20t extends Format2 implements ContainsTarget { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) 0); // padding + file.writeUShort((short) insn.vregA); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getSignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getB(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getTarget(Instruction insn) { + return insn.vregA; + } + + @Override + public void setTarget(Instruction insn, long target) { + insn.vregA = target; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21c.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21c.java new file mode 100644 index 0000000..e60b54a --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21c.java @@ -0,0 +1,78 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.Opcode; +import dexfuzz.rawdex.OpcodeInfo; + +import java.io.IOException; + +public class Format21c extends Format2 implements ContainsVRegs, ContainsPoolIndex { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUShort((short) insn.vregB); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public int getVRegCount() { + return 1; + } + + @Override + public int getPoolIndex(Instruction insn) { + return (int) insn.vregB; + } + + @Override + public void setPoolIndex(Instruction insn, int poolIndex) { + insn.vregB = poolIndex; + } + + @Override + public PoolIndexKind getPoolIndexKind(OpcodeInfo info) { + if (info.opcode == Opcode.CONST_STRING) { + return PoolIndexKind.String; + } + if (info.opcode == Opcode.CONST_CLASS + || info.opcode == Opcode.CHECK_CAST + || info.opcode == Opcode.NEW_INSTANCE) { + return PoolIndexKind.Type; + } + // everything else is static field accesses + return PoolIndexKind.Field; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21h.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21h.java new file mode 100644 index 0000000..c279f6c --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21h.java @@ -0,0 +1,67 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format21h extends Format2 implements ContainsConst, ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUShort((short) insn.vregB); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getSignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getConst(Instruction insn) { + return insn.vregB; + } + + @Override + public void setConst(Instruction insn, long constant) { + insn.vregB = constant; + } + + @Override + public long getConstRange() { + return (1 << 16); + } + + @Override + public int getVRegCount() { + return 1; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21s.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21s.java new file mode 100644 index 0000000..594d1a7 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21s.java @@ -0,0 +1,67 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format21s extends Format2 implements ContainsConst, ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUShort((short) insn.vregB); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getSignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getConst(Instruction insn) { + return insn.vregB; + } + + @Override + public void setConst(Instruction insn, long constant) { + insn.vregB = constant; + } + + @Override + public long getConstRange() { + return (1 << 16); + } + + @Override + public int getVRegCount() { + return 1; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21t.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21t.java new file mode 100644 index 0000000..dc3d659 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21t.java @@ -0,0 +1,62 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format21t extends Format2 implements ContainsTarget, ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUShort((short) insn.vregB); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getSignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getTarget(Instruction insn) { + return insn.vregB; + } + + @Override + public void setTarget(Instruction insn, long target) { + insn.vregB = target; + } + + @Override + public int getVRegCount() { + return 1; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22b.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22b.java new file mode 100644 index 0000000..bbdc7e3 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22b.java @@ -0,0 +1,68 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format22b extends Format2 implements ContainsVRegs, ContainsConst { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeByte((byte) insn.vregB); + file.writeByte((byte) insn.vregC); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return RawInsnHelper.getSignedByteFromByte(raw, 3); + } + + @Override + public long getConst(Instruction insn) { + return insn.vregC; + } + + @Override + public void setConst(Instruction insn, long constant) { + insn.vregC = constant; + } + + @Override + public long getConstRange() { + return (1 << 8); + } + + @Override + public int getVRegCount() { + return 2; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22c.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22c.java new file mode 100644 index 0000000..4dff336 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22c.java @@ -0,0 +1,72 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.Opcode; +import dexfuzz.rawdex.OpcodeInfo; + +import java.io.IOException; + +public class Format22c extends Format2 implements ContainsVRegs, ContainsPoolIndex { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) (insn.vregA | (insn.vregB << 4))); + file.writeUShort((short) insn.vregC); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1); + } + + @Override + public long getC(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2); + } + + @Override + public int getVRegCount() { + return 2; + } + + @Override + public int getPoolIndex(Instruction insn) { + return (int) insn.vregC; + } + + @Override + public void setPoolIndex(Instruction insn, int poolIndex) { + insn.vregC = poolIndex; + } + + @Override + public PoolIndexKind getPoolIndexKind(OpcodeInfo info) { + if (info.opcode == Opcode.INSTANCE_OF || info.opcode == Opcode.NEW_ARRAY) { + return PoolIndexKind.Type; + } + return PoolIndexKind.Field; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22cs.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22cs.java new file mode 100644 index 0000000..b66e14c --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22cs.java @@ -0,0 +1,72 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.OpcodeInfo; + +import java.io.IOException; + +public class Format22cs extends Format2 implements ContainsVRegs, ContainsPoolIndex { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + throw new Error("Did not expect to have to write a 22cs instruction!"); + // If for some reason 22cs instructions were in DEX files in the future, uncomment: + //file.writeByte((byte) insn.info.value); + //file.writeByte((byte) (insn.vreg_a | (insn.vreg_b << 4))); + //file.writeUShort((short) insn.vreg_c); + //return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1); + } + + @Override + public long getC(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2); + } + + @Override + public int getVRegCount() { + return 2; + } + + @Override + public int getPoolIndex(Instruction insn) { + return (int) insn.vregC; + } + + @Override + public void setPoolIndex(Instruction insn, int poolIndex) { + insn.vregC = poolIndex; + } + + @Override + public PoolIndexKind getPoolIndexKind(OpcodeInfo info) { + // This should technically be a byte offset, but we should never encounter + // this format during DEX mutation anyway. (see writeToFile()) + return PoolIndexKind.Field; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22s.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22s.java new file mode 100644 index 0000000..9497179 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22s.java @@ -0,0 +1,67 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format22s extends Format2 implements ContainsVRegs, ContainsConst { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) (insn.vregA | (insn.vregB << 4))); + file.writeUShort((short) insn.vregC); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1); + } + + @Override + public long getC(byte[] raw) throws IOException { + return RawInsnHelper.getSignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getConst(Instruction insn) { + return insn.vregC; + } + + @Override + public void setConst(Instruction insn, long constant) { + insn.vregC = constant; + } + + @Override + public long getConstRange() { + return (1 << 16); + } + + @Override + public int getVRegCount() { + return 2; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22t.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22t.java new file mode 100644 index 0000000..5ea9579 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22t.java @@ -0,0 +1,62 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format22t extends Format2 implements ContainsTarget, ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) (insn.vregA | (insn.vregB << 4))); + file.writeUShort((short) insn.vregC); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1); + } + + @Override + public long getC(byte[] raw) throws IOException { + return RawInsnHelper.getSignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getTarget(Instruction insn) { + return insn.vregC; + } + + @Override + public void setTarget(Instruction insn, long target) { + insn.vregC = target; + } + + @Override + public int getVRegCount() { + return 2; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22x.java new file mode 100644 index 0000000..5cd3d73 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22x.java @@ -0,0 +1,52 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format22x extends Format2 implements ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUShort((short) insn.vregB); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public int getVRegCount() { + return 2; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format23x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format23x.java new file mode 100644 index 0000000..8ce7c7c --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format23x.java @@ -0,0 +1,53 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format23x extends Format2 implements ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeByte((byte) insn.vregB); + file.writeByte((byte) insn.vregC); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 3); + } + + @Override + public int getVRegCount() { + return 3; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3.java new file mode 100644 index 0000000..d9d7ce1 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3.java @@ -0,0 +1,54 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format3 extends AbstractFormat { + @Override + public int getSize() { + return 3; + } + + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return 0; + } + + @Override + public long getB(byte[] raw) throws IOException { + return 0; + } + + @Override + public long getC(byte[] raw) throws IOException { + return 0; + } + + @Override + public boolean needsInvokeFormatInfo() { + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format30t.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format30t.java new file mode 100644 index 0000000..0b62646 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format30t.java @@ -0,0 +1,57 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format30t extends Format3 implements ContainsTarget { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) 0); // padding + file.writeUInt((int) insn.vregA); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getSignedIntFromFourBytes(raw, 2); + } + + @Override + public long getB(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getTarget(Instruction insn) { + return insn.vregA; + } + + @Override + public void setTarget(Instruction insn, long target) { + insn.vregA = target; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31c.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31c.java new file mode 100644 index 0000000..435fa19 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31c.java @@ -0,0 +1,68 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.OpcodeInfo; + +import java.io.IOException; + +public class Format31c extends Format3 implements ContainsVRegs, ContainsPoolIndex { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUInt((int) insn.vregB); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedIntFromFourBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public int getVRegCount() { + return 1; + } + + @Override + public int getPoolIndex(Instruction insn) { + return (int) insn.vregB; + } + + @Override + public void setPoolIndex(Instruction insn, int poolIndex) { + insn.vregB = poolIndex; + } + + @Override + public PoolIndexKind getPoolIndexKind(OpcodeInfo info) { + return PoolIndexKind.String; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31i.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31i.java new file mode 100644 index 0000000..d54c074 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31i.java @@ -0,0 +1,67 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format31i extends Format3 implements ContainsConst, ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUInt((int) insn.vregB); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getSignedIntFromFourBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getConst(Instruction insn) { + return insn.vregB; + } + + @Override + public void setConst(Instruction insn, long constant) { + insn.vregB = constant; + } + + @Override + public long getConstRange() { + return (1L << 32); + } + + @Override + public int getVRegCount() { + return 1; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31t.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31t.java new file mode 100644 index 0000000..b74db8f --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31t.java @@ -0,0 +1,62 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format31t extends Format3 implements ContainsTarget, ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUInt((int) insn.vregB); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getSignedIntFromFourBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getTarget(Instruction insn) { + return insn.vregB; + } + + @Override + public void setTarget(Instruction insn, long target) { + insn.vregB = target; + } + + @Override + public int getVRegCount() { + return 1; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format32x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format32x.java new file mode 100644 index 0000000..2f46105 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format32x.java @@ -0,0 +1,53 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format32x extends Format3 implements ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) 0); // padding + file.writeUShort((short) insn.vregA); + file.writeUShort((short) insn.vregB); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 4); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public int getVRegCount() { + return 2; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35c.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35c.java new file mode 100644 index 0000000..e4a50ff --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35c.java @@ -0,0 +1,75 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.Opcode; +import dexfuzz.rawdex.OpcodeInfo; + +import java.io.IOException; + +public class Format35c extends Format3 implements ContainsPoolIndex { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) (insn.invokeFormatInfo.vregG | (insn.vregA << 4))); + file.writeUShort((short) insn.vregB); + file.writeByte((byte) ((insn.invokeFormatInfo.vregD << 4) | insn.vregC)); + file.writeByte((byte) ((insn.invokeFormatInfo.vregF << 4) + | insn.invokeFormatInfo.vregE)); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 4); + } + + @Override + public boolean needsInvokeFormatInfo() { + return true; + } + + @Override + public int getPoolIndex(Instruction insn) { + return (int) insn.vregB; + } + + @Override + public void setPoolIndex(Instruction insn, int poolIndex) { + insn.vregB = poolIndex; + } + + @Override + public PoolIndexKind getPoolIndexKind(OpcodeInfo info) { + if (info.opcode == Opcode.FILLED_NEW_ARRAY) { + return PoolIndexKind.Type; + } + return PoolIndexKind.Method; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35mi.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35mi.java new file mode 100644 index 0000000..f622385 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35mi.java @@ -0,0 +1,55 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format35mi extends Format3 { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) (insn.invokeFormatInfo.vregG | (insn.vregA << 4))); + file.writeUShort((short) insn.vregB); + file.writeByte((byte) ((insn.invokeFormatInfo.vregD << 4) | insn.vregC)); + file.writeByte((byte) ((insn.invokeFormatInfo.vregF << 4) + | insn.invokeFormatInfo.vregE)); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 4); + } + + @Override + public boolean needsInvokeFormatInfo() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35ms.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35ms.java new file mode 100644 index 0000000..3be9707 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35ms.java @@ -0,0 +1,55 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format35ms extends Format3 { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) (insn.invokeFormatInfo.vregG | (insn.vregA << 4))); + file.writeUShort((short) insn.vregB); + file.writeByte((byte) ((insn.invokeFormatInfo.vregD << 4) | insn.vregC)); + file.writeByte((byte) ((insn.invokeFormatInfo.vregF << 4) + | insn.invokeFormatInfo.vregE)); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 4); + } + + @Override + public boolean needsInvokeFormatInfo() { + return true; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rc.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rc.java new file mode 100644 index 0000000..630825d --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rc.java @@ -0,0 +1,68 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.Opcode; +import dexfuzz.rawdex.OpcodeInfo; + +import java.io.IOException; + +public class Format3rc extends Format3 implements ContainsPoolIndex { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUShort((short) insn.vregB); + file.writeUShort((short) insn.vregC); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 4); + } + + @Override + public int getPoolIndex(Instruction insn) { + return (int) insn.vregB; + } + + @Override + public void setPoolIndex(Instruction insn, int poolIndex) { + insn.vregB = poolIndex; + } + + @Override + public PoolIndexKind getPoolIndexKind(OpcodeInfo info) { + if (info.opcode == Opcode.FILLED_NEW_ARRAY_RANGE) { + return PoolIndexKind.Type; + } + return PoolIndexKind.Method; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rmi.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rmi.java new file mode 100644 index 0000000..7b6ceea --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rmi.java @@ -0,0 +1,48 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format3rmi extends Format3 { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUShort((short) insn.vregB); + file.writeUShort((short) insn.vregC); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 4); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rms.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rms.java new file mode 100644 index 0000000..17535f9 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rms.java @@ -0,0 +1,48 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format3rms extends Format3 { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUShort((short) insn.vregB); + file.writeUShort((short) insn.vregC); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 4); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format5.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format5.java new file mode 100644 index 0000000..bc141be --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format5.java @@ -0,0 +1,54 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format5 extends AbstractFormat { + @Override + public int getSize() { + return 5; + } + + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return 0; + } + + @Override + public long getB(byte[] raw) throws IOException { + return 0; + } + + @Override + public long getC(byte[] raw) throws IOException { + return 0; + } + + @Override + public boolean needsInvokeFormatInfo() { + return false; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format51l.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format51l.java new file mode 100644 index 0000000..fc2e0ed --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format51l.java @@ -0,0 +1,70 @@ +/* + * 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.rawdex.formats; + +import dexfuzz.rawdex.DexRandomAccessFile; +import dexfuzz.rawdex.Instruction; + +import java.io.IOException; + +public class Format51l extends Format5 implements ContainsConst, ContainsVRegs { + @Override + public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException { + file.writeByte((byte) insn.info.value); + file.writeByte((byte) insn.vregA); + file.writeUShort((short) (insn.vregB & 0xffff)); + file.writeUShort((short) ((insn.vregB & 0xffff0000) >> 16)); + file.writeUShort((short) ((insn.vregB & 0xffff00000000L) >> 32)); + file.writeUShort((short) ((insn.vregB & 0xffff000000000000L) >> 48)); + return; + } + + @Override + public long getA(byte[] raw) throws IOException { + return RawInsnHelper.getUnsignedByteFromByte(raw, 1); + } + + @Override + public long getB(byte[] raw) throws IOException { + return RawInsnHelper.getSignedLongFromEightBytes(raw, 2); + } + + @Override + public long getC(byte[] raw) throws IOException { + return (long) 0; + } + + @Override + public long getConst(Instruction insn) { + return insn.vregB; + } + + @Override + public void setConst(Instruction insn, long constant) { + insn.vregB = constant; + } + + @Override + public long getConstRange() { + return (1L << 63); + } + + @Override + public int getVRegCount() { + return 1; + } +} diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/RawInsnHelper.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/RawInsnHelper.java new file mode 100644 index 0000000..b16a1b5 --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/RawInsnHelper.java @@ -0,0 +1,119 @@ +/* + * 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.rawdex.formats; + +/** + * Class consisting of static methods used for common read/write operations + * perfomed in the Format classes. + */ +public class RawInsnHelper { + /** + * Read a signed byte from the idx into the raw array. + */ + public static long getSignedByteFromByte(byte[] raw, int idx) { + return (long) raw[idx]; + } + + /** + * Read an unsigned byte from the idx into the raw array. + */ + public static long getUnsignedByteFromByte(byte[] raw, int idx) { + return ((long) raw[idx]) & 0xff; + } + + /** + * Read an unsigned lower 4 bits from the idx into the raw array. + */ + public static long getUnsignedLowNibbleFromByte(byte[] raw, int idx) { + return ((long) raw[idx]) & 0xf; + } + + /** + * Read an unsigned higher 4 bits from the idx into the raw array. + */ + public static long getUnsignedHighNibbleFromByte(byte[] raw, int idx) { + return (((long) raw[idx]) >> 4) & 0xf; + } + + /** + * Read an unsigned 2 bytes as a short from the idx into the raw array. + */ + public static long getUnsignedShortFromTwoBytes(byte[] raw, int idx) { + return (long) ( (((long) raw[idx]) & 0xff) + | ((((long) raw[idx + 1]) & 0xff) << 8)); + } + + /** + * Read a signed 2 bytes as a short from the idx into the raw array. + */ + public static long getSignedShortFromTwoBytes(byte[] raw, int idx) { + return (long) ( (((long) raw[idx]) & 0xff) + | (((long) raw[idx + 1]) << 8)); + } + + /** + * Read an unsigned 4 bytes as an int from the idx into the raw array. + */ + public static long getUnsignedIntFromFourBytes(byte[] raw, int idx) { + return (long) ( (((long) raw[idx]) & 0xff) + | ((((long) raw[idx + 1]) & 0xff) << 8) + | ((((long) raw[idx + 2]) & 0xff) << 16) + | ((((long) raw[idx + 3]) & 0xff) << 24) ); + } + + /** + * Read a signed 4 bytes as an int from the idx into the raw array. + */ + public static long getSignedIntFromFourBytes(byte[] raw, int idx) { + return (long) ( (((long) raw[idx]) & 0xff) + | ((((long) raw[idx + 1]) & 0xff) << 8) + | ((((long) raw[idx + 2]) & 0xff) << 16) + | (((long) raw[idx + 3]) << 24) ); + } + + /** + * Read a signed 8 bytes as a long from the idx into the raw array. + */ + public static long getSignedLongFromEightBytes(byte[] raw, int idx) { + return (long) ( (((long) raw[idx]) & 0xff) + | ((((long) raw[idx + 1]) & 0xff) << 8) + | ((((long) raw[idx + 2]) & 0xff) << 16) + | ((((long) raw[idx + 3]) & 0xff) << 24) + | ((((long) raw[idx + 4]) & 0xff) << 32) + | ((((long) raw[idx + 5]) & 0xff) << 40) + | ((((long) raw[idx + 6]) & 0xff) << 48) + | (((long) raw[idx + 7]) << 56) ); + } + + /** + * Given an idx into a raw array, and an int, write that int into the array at that position. + */ + public static void writeUnsignedIntToFourBytes(byte[] raw, int idx, int value) { + raw[idx] = (byte) (value & 0xFF); + raw[idx + 1] = (byte) ((value & 0xFF00) >>> 8); + raw[idx + 2] = (byte) ((value & 0xFF0000) >>> 16); + raw[idx + 3] = (byte) ((value & 0xFF000000) >>> 24); + } + + /** + * Given an idx into a raw array, and a short, write that int into the array at that position. + */ + public static void writeUnsignedShortToTwoBytes(byte[] raw, int idx, int value) { + raw[idx] = (byte) (value & 0xFF); + raw[idx + 1] = (byte) ((value & 0xFF00) >>> 8); + } +} |