/* * 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.Options; import dexfuzz.StreamConsumer; import dexfuzz.listeners.BaseListener; /** * Base class containing the common methods for executing a particular backend of ART. */ public abstract class Executor { 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; private boolean needsCleanCodeCache; protected Executor(String name, int timeout, BaseListener listener, Architecture architecture, Device device, boolean needsCleanCodeCache) { 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.needsCleanCodeCache = needsCleanCodeCache; if (Options.executeOnHost) { this.testLocation = System.getProperty("user.dir"); } else { this.testLocation = Options.executeDirectory; } outputConsumer = new StreamConsumer(); outputConsumer.start(); errorConsumer = new StreamConsumer(); errorConsumer.start(); } /** * Called by subclass Executors in their execute() implementations. */ protected ExecutionResult executeCommandWithTimeout(String command, boolean captureOutput) { String timeoutString = "timeout " + timeout + " "; return device.executeCommand(timeoutString + device.getExecutionShellPrefix() + command, captureOutput, outputConsumer, errorConsumer); } /** * 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 "); commandBuilder.append("--instruction-set=").append(architecture.asString()); commandBuilder.append(" --instruction-set-features=default "); // Select the correct boot image. commandBuilder.append("--boot-image=").append(device.getAndroidProductOut()); 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(device.getAndroidHostOut()).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 = device.executeCommand(commandBuilder.toString(), true, outputConsumer, errorConsumer); 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); } device.executeCommand("rm output.oat", false); return success; } /** * Called by the Fuzzer to upload the program to the target device. */ public void prepareProgramForExecution(String programName) { if (!Options.executeOnHost) { device.pushProgramToDevice(programName, testLocation); } if (needsCleanCodeCache) { // Get the device to clean the code cache device.cleanCodeCache(architecture, testLocation, programName); } } /** * Executor subclasses need to override this, to construct their arguments for dalvikvm * invocation correctly. */ public abstract void execute(String programName); /** * Fuzzer.checkForArchitectureSplit() will use this determine the architecture of the Executor. */ public Architecture getArchitecture() { return architecture; } /** * 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 didTargetVerify() { // TODO: Remove this once host-verification can be forced to always fail? String output = executionResult.getFlattenedAll(); if (output.contains("VerifyError") || output.contains("Verification failed on class")) { return false; } return true; } public String getName() { return name; } public void finishedWithProgramOnDevice() { device.resetProgramPushed(); } }