Executor.java revision 959ffdf65f280ee90b7944a8dd610564e7f99e69
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 17package dexfuzz.executors; 18 19import dexfuzz.ExecutionResult; 20import dexfuzz.Log; 21import dexfuzz.Options; 22import dexfuzz.StreamConsumer; 23import dexfuzz.listeners.BaseListener; 24 25import java.io.IOException; 26import java.util.Map; 27 28/** 29 * Base class containing the common methods for executing a particular backend of ART. 30 */ 31public abstract class Executor { 32 private String androidHostOut; 33 private String androidProductOut; 34 35 private StreamConsumer outputConsumer; 36 private StreamConsumer errorConsumer; 37 38 protected ExecutionResult executionResult; 39 protected String executeClass; 40 41 // Set by subclasses. 42 protected String name; 43 protected int timeout; 44 protected BaseListener listener; 45 protected String testLocation; 46 protected Architecture architecture; 47 protected Device device; 48 49 protected Executor(String name, int timeout, BaseListener listener, Architecture architecture, 50 Device device) { 51 executeClass = Options.executeClass; 52 53 if (Options.shortTimeouts) { 54 this.timeout = 2; 55 } else { 56 this.timeout = timeout; 57 } 58 59 this.name = name; 60 this.listener = listener; 61 this.architecture = architecture; 62 this.device = device; 63 64 this.testLocation = Options.executeDirectory; 65 66 Map<String, String> envVars = System.getenv(); 67 androidProductOut = checkForEnvVar(envVars, "ANDROID_PRODUCT_OUT"); 68 androidHostOut = checkForEnvVar(envVars, "ANDROID_HOST_OUT"); 69 70 outputConsumer = new StreamConsumer(); 71 outputConsumer.start(); 72 errorConsumer = new StreamConsumer(); 73 errorConsumer.start(); 74 75 if (!device.isLocal()) { 76 // Check for ADB. 77 try { 78 ProcessBuilder pb = new ProcessBuilder(); 79 pb.command("adb", "devices"); 80 Process process = pb.start(); 81 int exitValue = process.waitFor(); 82 if (exitValue != 0) { 83 Log.errorAndQuit("Problem executing ADB - is it in your $PATH?"); 84 } 85 } catch (IOException e) { 86 Log.errorAndQuit("IOException when executing ADB, is it working?"); 87 } catch (InterruptedException e) { 88 Log.errorAndQuit("InterruptedException when executing ADB, is it working?"); 89 } 90 91 // Check we can run something on ADB. 92 ExecutionResult result = executeOnDevice("true", true); 93 if (result.getFlattenedAll().contains("device not found")) { 94 Log.errorAndQuit("Couldn't connect to specified ADB device: " + device.getName()); 95 } 96 } 97 } 98 99 private String checkForEnvVar(Map<String, String> envVars, String key) { 100 if (!envVars.containsKey(key)) { 101 Log.errorAndQuit("Cannot run a fuzzed program if $" + key + " is not set!"); 102 } 103 return envVars.get(key); 104 } 105 106 private ExecutionResult executeCommand(String command, boolean captureOutput) { 107 ExecutionResult result = new ExecutionResult(); 108 109 Log.info("Executing: " + command); 110 111 try { 112 ProcessBuilder processBuilder = new ProcessBuilder(command.split(" ")); 113 processBuilder.environment().put("ANDROID_ROOT", androidHostOut); 114 Process process = processBuilder.start(); 115 116 if (captureOutput) { 117 // Give the streams to the StreamConsumers. 118 outputConsumer.giveStreamAndStartConsuming(process.getInputStream()); 119 errorConsumer.giveStreamAndStartConsuming(process.getErrorStream()); 120 } 121 122 // Wait until the process is done - the StreamConsumers will keep the 123 // buffers drained, so this shouldn't block indefinitely. 124 // Get the return value as well. 125 result.returnValue = process.waitFor(); 126 127 Log.info("Return value: " + result.returnValue); 128 129 if (captureOutput) { 130 // Tell the StreamConsumers to stop consuming, and wait for them to finish 131 // so we know we have all of the output. 132 outputConsumer.processFinished(); 133 errorConsumer.processFinished(); 134 result.output = outputConsumer.getOutput(); 135 result.error = errorConsumer.getOutput(); 136 137 // Always explicitly indicate the return code in the text output now. 138 // NB: adb shell doesn't actually return exit codes currently, but this will 139 // be useful if/when it does. 140 result.output.add("RETURN CODE: " + result.returnValue); 141 } 142 143 } catch (IOException e) { 144 Log.errorAndQuit("ExecutionResult.execute() caught an IOException"); 145 } catch (InterruptedException e) { 146 Log.errorAndQuit("ExecutionResult.execute() caught an InterruptedException"); 147 } 148 149 return result; 150 } 151 152 /** 153 * Called by subclass Executors in their execute() implementations. 154 */ 155 protected ExecutionResult executeOnDevice(String command, boolean captureOutput) { 156 String timeoutString = "timeout " + timeout + " "; 157 return executeCommand(timeoutString + device.getExecutionShellPrefix() + command, 158 captureOutput); 159 } 160 161 private ExecutionResult pushToDevice(String command) { 162 return executeCommand(device.getExecutionPushPrefix() + command, false); 163 } 164 165 /** 166 * Call this to make sure the StreamConsumer threads are stopped. 167 */ 168 public void shutdown() { 169 outputConsumer.shutdown(); 170 errorConsumer.shutdown(); 171 } 172 173 /** 174 * Called by the Fuzzer after each execution has finished, to clear the results. 175 */ 176 public void reset() { 177 executionResult = null; 178 } 179 180 /** 181 * Called by the Fuzzer to verify the mutated program using the host-side dex2oat. 182 */ 183 public boolean verifyOnHost(String programName) { 184 StringBuilder commandBuilder = new StringBuilder(); 185 commandBuilder.append("dex2oat "); 186 187 // This assumes that the Architecture enum's name, when reduced to lower-case, 188 // matches what dex2oat would expect. 189 commandBuilder.append("--instruction-set=").append(architecture.toString().toLowerCase()); 190 commandBuilder.append(" --instruction-set-features=default "); 191 192 // Select the correct boot image. 193 commandBuilder.append("--boot-image=").append(androidProductOut); 194 if (device.noBootImageAvailable()) { 195 commandBuilder.append("/data/art-test/core.art "); 196 } else { 197 commandBuilder.append("/system/framework/boot.art "); 198 } 199 200 commandBuilder.append("--oat-file=output.oat "); 201 commandBuilder.append("--android-root=").append(androidHostOut).append(" "); 202 commandBuilder.append("--runtime-arg -classpath "); 203 commandBuilder.append("--runtime-arg ").append(programName).append(" "); 204 commandBuilder.append("--dex-file=").append(programName).append(" "); 205 commandBuilder.append("--compiler-filter=interpret-only --runtime-arg -Xnorelocate "); 206 207 ExecutionResult verificationResult = executeCommand(commandBuilder.toString(), true); 208 209 boolean success = true; 210 211 if (verificationResult.isSigabort()) { 212 listener.handleHostVerificationSigabort(verificationResult); 213 success = false; 214 } 215 216 if (success) { 217 // Search for a keyword that indicates verification was not successful. 218 // TODO: Determine if dex2oat crashed? 219 for (String line : verificationResult.error) { 220 if (line.contains("Verification error") 221 || line.contains("Failure to verify dex file")) { 222 success = false; 223 } 224 if (Options.dumpVerify) { 225 // Strip out the start of the log lines. 226 listener.handleDumpVerify(line.replaceFirst(".*(cc|h):\\d+] ", "")); 227 } 228 } 229 } 230 231 if (!success) { 232 listener.handleFailedHostVerification(verificationResult); 233 } 234 235 executeCommand("rm output.oat", false); 236 237 return success; 238 } 239 240 /** 241 * Called by the Fuzzer to upload the program to the target device. 242 * TODO: Check if we're executing on a local device, and don't do this? 243 */ 244 public void uploadToTarget(String programName) { 245 pushToDevice(programName + " " + testLocation); 246 } 247 248 /** 249 * Executor subclasses need to override this, to construct their arguments for dalvikvm 250 * invocation correctly. 251 */ 252 public abstract void execute(String programName); 253 254 /** 255 * Executor subclasses need to override this, to delete their generated OAT file correctly. 256 */ 257 public abstract void deleteGeneratedOatFile(String programName); 258 259 /** 260 * Executor subclasses need to override this, to report if they need a cleaned code cache. 261 */ 262 public abstract boolean needsCleanCodeCache(); 263 264 /** 265 * Fuzzer.checkForArchitectureSplit() will use this determine the architecture of the Executor. 266 */ 267 public Architecture getArchitecture() { 268 return architecture; 269 } 270 271 /** 272 * Used in each subclass of Executor's deleteGeneratedOatFile() method, to know what to delete. 273 */ 274 protected String getOatFileName(String programName) { 275 // Converts e.g. /data/art-test/file.dex to data@art-test@file.dex 276 return (testLocation.replace("/", "@").substring(1) + "@" + programName); 277 } 278 279 /** 280 * Used by the Fuzzer to get result of execution. 281 */ 282 public ExecutionResult getResult() { 283 return executionResult; 284 } 285 286 /** 287 * Because dex2oat can accept a program with soft errors on the host, and then fail after 288 * performing hard verification on the target, we need to check if the Executor detected 289 * a target verification failure, before doing anything else with the resulting output. 290 * Used by the Fuzzer. 291 */ 292 public boolean verifyOnTarget() { 293 // TODO: Remove this once host-verification can be forced to always fail? 294 if (executionResult.getFlattenedOutput().contains("VerifyError")) { 295 return false; 296 } 297 return true; 298 } 299 300 public String getName() { 301 return name; 302 } 303} 304