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