1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dexfuzz.executors; 18 19 import dexfuzz.ExecutionResult; 20 import dexfuzz.Options; 21 import dexfuzz.StreamConsumer; 22 import dexfuzz.listeners.BaseListener; 23 24 /** 25 * Base class containing the common methods for executing a particular backend of ART. 26 */ 27 public abstract class Executor { 28 private StreamConsumer outputConsumer; 29 private StreamConsumer errorConsumer; 30 31 protected ExecutionResult executionResult; 32 protected String executeClass; 33 34 // Set by subclasses. 35 protected String name; 36 protected int timeout; 37 protected BaseListener listener; 38 protected String testLocation; 39 protected Architecture architecture; 40 protected Device device; 41 private boolean needsCleanCodeCache; 42 private boolean isBisectable; 43 Executor(String name, int timeout, BaseListener listener, Architecture architecture, Device device, boolean needsCleanCodeCache, boolean isBisectable)44 protected Executor(String name, int timeout, BaseListener listener, Architecture architecture, 45 Device device, boolean needsCleanCodeCache, boolean isBisectable) { 46 executeClass = Options.executeClass; 47 48 if (Options.shortTimeouts) { 49 this.timeout = 2; 50 } else { 51 this.timeout = timeout; 52 } 53 54 this.name = name; 55 this.listener = listener; 56 this.architecture = architecture; 57 this.device = device; 58 this.needsCleanCodeCache = needsCleanCodeCache; 59 this.isBisectable = isBisectable; 60 61 if (Options.executeOnHost) { 62 this.testLocation = System.getProperty("user.dir"); 63 } else { 64 this.testLocation = Options.executeDirectory; 65 } 66 67 outputConsumer = new StreamConsumer(); 68 outputConsumer.start(); 69 errorConsumer = new StreamConsumer(); 70 errorConsumer.start(); 71 } 72 73 /** 74 * Called by subclass Executors in their execute() implementations. 75 */ executeCommandWithTimeout(String command, boolean captureOutput)76 protected ExecutionResult executeCommandWithTimeout(String command, boolean captureOutput) { 77 String timeoutString = "timeout " + timeout + " "; 78 return device.executeCommand(timeoutString + device.getExecutionShellPrefix() + command, 79 captureOutput, outputConsumer, errorConsumer); 80 } 81 82 /** 83 * Call this to make sure the StreamConsumer threads are stopped. 84 */ shutdown()85 public void shutdown() { 86 outputConsumer.shutdown(); 87 errorConsumer.shutdown(); 88 } 89 90 /** 91 * Called by the Fuzzer after each execution has finished, to clear the results. 92 */ reset()93 public void reset() { 94 executionResult = null; 95 } 96 97 /** 98 * Called by the Fuzzer to verify the mutated program using the host-side dex2oat. 99 */ verifyOnHost(String programName)100 public boolean verifyOnHost(String programName) { 101 StringBuilder commandBuilder = new StringBuilder(); 102 commandBuilder.append("dex2oat "); 103 104 commandBuilder.append("--instruction-set=").append(architecture.asString()); 105 commandBuilder.append(" --instruction-set-features=default "); 106 107 // Select the correct boot image. 108 commandBuilder.append("--boot-image=").append(device.getAndroidProductOut()); 109 if (device.noBootImageAvailable()) { 110 commandBuilder.append("/data/art-test/core.art "); 111 } else { 112 commandBuilder.append("/system/framework/boot.art "); 113 } 114 115 commandBuilder.append("--oat-file=output.oat "); 116 commandBuilder.append("--android-root=").append(device.getAndroidHostOut()).append(" "); 117 commandBuilder.append("--dex-file=").append(programName).append(" "); 118 commandBuilder.append("--compiler-filter=quicken --runtime-arg -Xnorelocate "); 119 120 ExecutionResult verificationResult = device.executeCommand(commandBuilder.toString(), true, 121 outputConsumer, errorConsumer); 122 123 boolean success = true; 124 125 if (verificationResult.isSigabort()) { 126 listener.handleHostVerificationSigabort(verificationResult); 127 success = false; 128 } 129 130 if (success) { 131 // Search for a keyword that indicates verification was not successful. 132 // TODO: Determine if dex2oat crashed? 133 for (String line : verificationResult.error) { 134 if (line.contains("Verification error") 135 || line.contains("Failure to verify dex file")) { 136 success = false; 137 } 138 if (Options.dumpVerify) { 139 // Strip out the start of the log lines. 140 listener.handleDumpVerify(line.replaceFirst(".*(cc|h):\\d+] ", "")); 141 } 142 } 143 } 144 145 if (!success) { 146 listener.handleFailedHostVerification(verificationResult); 147 } 148 149 device.executeCommand("rm output.oat", false); 150 151 return success; 152 } 153 154 /** 155 * Called by the Fuzzer to upload the program to the target device. 156 */ prepareProgramForExecution(String programName)157 public void prepareProgramForExecution(String programName) { 158 if (!Options.executeOnHost) { 159 device.pushProgramToDevice(programName, testLocation); 160 } 161 162 if (needsCleanCodeCache) { 163 // Get the device to clean the code cache 164 device.cleanCodeCache(architecture, testLocation, programName); 165 } 166 } 167 168 /** 169 * Executor subclasses need to override this, to construct their arguments for dalvikvm 170 * invocation correctly. 171 */ constructCommand(String programName)172 protected abstract String constructCommand(String programName); 173 174 /** 175 * Executes runtime. 176 */ execute(String programName)177 public void execute(String programName) { 178 String command = ""; 179 String androidRoot = Options.androidRoot.trim(); 180 if (androidRoot.length() != 0) { 181 command = "PATH=" + androidRoot + "/bin "; 182 command += "ANDROID_ROOT=" + androidRoot + " "; 183 command += "LD_LIBRARY_PATH="+ androidRoot + "/lib:" + androidRoot + "/lib64 "; 184 } 185 command += constructCommand(programName); 186 executionResult = executeCommandWithTimeout(command, true); 187 } 188 189 /** 190 * Runs bisection bug search. 191 */ runBisectionSearch(String programName, String expectedOutputFile, String logFile)192 public ExecutionResult runBisectionSearch(String programName, String expectedOutputFile, String logFile) { 193 assert(isBisectable); 194 String runtimeCommand = constructCommand(programName); 195 StringBuilder commandBuilder = new StringBuilder(); 196 commandBuilder.append("bisection_search.py --raw-cmd '").append(runtimeCommand); 197 commandBuilder.append("' --expected-output=").append(expectedOutputFile); 198 commandBuilder.append(" --logfile=").append(logFile); 199 if (!device.isHost()) { 200 commandBuilder.append(" --device"); 201 if (device.isUsingSpecificDevice()) { 202 commandBuilder.append(" --specific-device=").append(device.getName()); 203 } 204 } 205 return device.executeCommand(commandBuilder.toString(), true, outputConsumer, errorConsumer); 206 } 207 208 /** 209 * Fuzzer.checkForArchitectureSplit() will use this determine the architecture of the Executor. 210 */ getArchitecture()211 public Architecture getArchitecture() { 212 return architecture; 213 } 214 215 /** 216 * Used by the Fuzzer to get result of execution. 217 */ getResult()218 public ExecutionResult getResult() { 219 return executionResult; 220 } 221 222 /** 223 * Because dex2oat can accept a program with soft errors on the host, and then fail after 224 * performing hard verification on the target, we need to check if the Executor detected 225 * a target verification failure, before doing anything else with the resulting output. 226 * Used by the Fuzzer. 227 */ didTargetVerify()228 public boolean didTargetVerify() { 229 // TODO: Remove this once host-verification can be forced to always fail? 230 String output = executionResult.getFlattenedAll(); 231 if (output.contains("VerifyError") || output.contains("Verification failed on class")) { 232 return false; 233 } 234 return true; 235 } 236 getName()237 public String getName() { 238 return name; 239 } 240 finishedWithProgramOnDevice()241 public void finishedWithProgramOnDevice() { 242 device.resetProgramPushed(); 243 } 244 isBisectable()245 public boolean isBisectable() { 246 return isBisectable; 247 } 248 } 249