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 java.io.IOException;
20import java.io.File;
21import java.util.Map;
22
23import dexfuzz.ExecutionResult;
24import dexfuzz.Log;
25import dexfuzz.Options;
26import dexfuzz.StreamConsumer;
27
28/**
29 * Handles execution either on a remote target device, or on a local host computer.
30 */
31public class Device {
32  private boolean isHost;
33  private String deviceName;
34  private boolean usingSpecificDevice;
35  private boolean noBootImage;
36
37  private String androidHostOut;
38  private String androidProductOut;
39  private String androidData;
40
41  private boolean programPushed;
42
43  /**
44   * The constructor for a host "device".
45   */
46  public Device() {
47    this.isHost = true;
48    this.deviceName = "[HostDevice]";
49    setup();
50  }
51
52  /**
53   * The constructor for an ADB connected device.
54   */
55  public Device(String deviceName, boolean noBootImage) {
56    if (!deviceName.isEmpty()) {
57      this.deviceName = deviceName;
58      this.usingSpecificDevice = true;
59    }
60    this.noBootImage = noBootImage;
61    setup();
62  }
63
64  private String checkForEnvVar(Map<String, String> envVars, String key) {
65    if (!envVars.containsKey(key)) {
66      Log.errorAndQuit("Cannot run a fuzzed program if $" + key + " is not set!");
67    }
68    return envVars.get(key);
69  }
70
71  private String getHostCoreImagePath() {
72    return androidHostOut + "/framework/core.art";
73  }
74
75  private void setup() {
76    programPushed = false;
77
78    Map<String, String> envVars = System.getenv();
79    androidProductOut = checkForEnvVar(envVars, "ANDROID_PRODUCT_OUT");
80    androidHostOut = checkForEnvVar(envVars, "ANDROID_HOST_OUT");
81
82    if (Options.executeOnHost) {
83      File coreImage = new File(getHostCoreImagePath());
84      if (!coreImage.exists()) {
85        Log.errorAndQuit("Host core image not found at " + coreImage.getPath()
86            + ". Did you forget to build it?");
87      }
88    }
89    if (!isHost) {
90      // Create temporary consumers for the initial test.
91      StreamConsumer outputConsumer = new StreamConsumer();
92      outputConsumer.start();
93      StreamConsumer errorConsumer = new StreamConsumer();
94      errorConsumer.start();
95
96      // Check for ADB.
97      try {
98        ProcessBuilder pb = new ProcessBuilder();
99        pb.command("adb", "devices");
100        Process process = pb.start();
101        int exitValue = process.waitFor();
102        if (exitValue != 0) {
103          Log.errorAndQuit("Problem executing ADB - is it in your $PATH?");
104        }
105      } catch (IOException e) {
106        Log.errorAndQuit("IOException when executing ADB, is it working?");
107      } catch (InterruptedException e) {
108        Log.errorAndQuit("InterruptedException when executing ADB, is it working?");
109      }
110
111      // Check we can run something on ADB.
112      ExecutionResult result = executeCommand("true", true, outputConsumer, errorConsumer);
113      if (result.getFlattenedAll().contains("device not found")) {
114        Log.errorAndQuit("Couldn't connect to specified ADB device: " + deviceName);
115      }
116
117      outputConsumer.shutdown();
118      errorConsumer.shutdown();
119    } else {
120      androidData = checkForEnvVar(envVars, "ANDROID_DATA");
121    }
122  }
123
124  /**
125   * Get the name that would be provided to adb -s to communicate specifically with this device.
126   */
127  public String getName() {
128    assert(!isHost);
129    return deviceName;
130  }
131
132  public boolean isHost() {
133    return isHost;
134  }
135
136  /**
137   * Certain AOSP builds of Android may not have a full boot.art built. This will be set if
138   * we use --no-boot-image, and is used by Executors when deciding the arguments for dalvikvm
139   * and dex2oat when performing host-side verification.
140   */
141  public boolean noBootImageAvailable() {
142    return noBootImage;
143  }
144
145  /**
146   * Get the command prefix for this device if we want to use adb shell.
147   */
148  public String getExecutionShellPrefix() {
149    if (isHost) {
150      return "";
151    }
152    return getExecutionPrefixWithAdb("shell");
153  }
154
155  /**
156   * Get any extra flags required to execute ART on the host.
157   */
158  public String getHostExecutionFlags() {
159    return String.format("-Xnorelocate -Ximage:%s", getHostCoreImagePath());
160  }
161
162  public String getAndroidHostOut() {
163    return androidHostOut;
164  }
165
166  public String getAndroidProductOut() {
167    return androidProductOut;
168  }
169
170  public ExecutionResult executeCommand(String command, boolean captureOutput) {
171    assert(!captureOutput);
172    return executeCommand(command, captureOutput, null, null);
173  }
174
175  public ExecutionResult executeCommand(String command, boolean captureOutput,
176      StreamConsumer outputConsumer, StreamConsumer errorConsumer) {
177
178    ExecutionResult result = new ExecutionResult();
179
180    Log.info("Executing: " + command);
181
182    try {
183      ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
184      processBuilder.environment().put("ANDROID_ROOT", androidHostOut);
185      if (Options.executeOnHost) {
186        processBuilder.environment().put("ANDROID_DATA", androidData);
187      }
188      Process process = processBuilder.start();
189
190      if (captureOutput) {
191        // Give the streams to the StreamConsumers.
192        outputConsumer.giveStreamAndStartConsuming(process.getInputStream());
193        errorConsumer.giveStreamAndStartConsuming(process.getErrorStream());
194      }
195
196      // Wait until the process is done - the StreamConsumers will keep the
197      // buffers drained, so this shouldn't block indefinitely.
198      // Get the return value as well.
199      result.returnValue = process.waitFor();
200
201      Log.info("Return value: " + result.returnValue);
202
203      if (captureOutput) {
204        // Tell the StreamConsumers to stop consuming, and wait for them to finish
205        // so we know we have all of the output.
206        outputConsumer.processFinished();
207        errorConsumer.processFinished();
208        result.output = outputConsumer.getOutput();
209        result.error = errorConsumer.getOutput();
210
211        // Always explicitly indicate the return code in the text output now.
212        // NB: adb shell doesn't actually return exit codes currently, but this will
213        // be useful if/when it does.
214        result.output.add("RETURN CODE: " + result.returnValue);
215      }
216
217    } catch (IOException e) {
218      Log.errorAndQuit("ExecutionResult.execute() caught an IOException");
219    } catch (InterruptedException e) {
220      Log.errorAndQuit("ExecutionResult.execute() caught an InterruptedException");
221    }
222
223    return result;
224  }
225
226  private String getExecutionPrefixWithAdb(String command) {
227    if (usingSpecificDevice) {
228      return String.format("adb -s %s %s ", deviceName, command);
229    } else {
230      return String.format("adb %s ", command);
231    }
232  }
233
234  private String getCacheLocation(Architecture architecture) {
235    String cacheLocation = "";
236    if (isHost) {
237      cacheLocation = androidData + "/dalvik-cache/" + architecture.asString() + "/";
238    } else {
239      cacheLocation = "/data/dalvik-cache/" + architecture.asString() + "/";
240    }
241    return cacheLocation;
242  }
243
244  private String getOatFileName(String testLocation, String programName) {
245    // Converts e.g. /data/art-test/file.dex to data@art-test@file.dex
246    return (testLocation.replace("/", "@").substring(1) + "@" + programName);
247  }
248
249  public void cleanCodeCache(Architecture architecture, String testLocation, String programName) {
250    String command = "rm -f " + getCacheLocation(architecture)
251        + getOatFileName(testLocation, programName);
252    executeCommand(command, false);
253  }
254
255  public void pushProgramToDevice(String programName, String testLocation) {
256    assert(!isHost);
257    if (!programPushed) {
258      executeCommand(getExecutionPrefixWithAdb("push") + programName + " " + testLocation, false);
259      programPushed = true;
260    }
261  }
262
263  public void resetProgramPushed() {
264    programPushed = false;
265  }
266}
267