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 java.io.IOException;
20 import java.io.File;
21 import java.util.Map;
22 
23 import dexfuzz.ExecutionResult;
24 import dexfuzz.Log;
25 import dexfuzz.Options;
26 import dexfuzz.StreamConsumer;
27 
28 /**
29  * Handles execution either on a remote target device, or on a local host computer.
30  */
31 public 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    */
Device()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    */
Device(String deviceName, boolean noBootImage)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 
checkForEnvVar(Map<String, String> envVars, String key)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 
getHostCoreImagePath()71   private String getHostCoreImagePath() {
72     return androidHostOut + "/framework/core.art";
73   }
74 
setup()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    */
getName()127   public String getName() {
128     assert(!isHost);
129     return deviceName;
130   }
131 
isHost()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    */
noBootImageAvailable()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    */
getExecutionShellPrefix()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    */
getHostExecutionFlags()158   public String getHostExecutionFlags() {
159     return String.format("-Xnorelocate -Ximage:%s", getHostCoreImagePath());
160   }
161 
getAndroidHostOut()162   public String getAndroidHostOut() {
163     return androidHostOut;
164   }
165 
getAndroidProductOut()166   public String getAndroidProductOut() {
167     return androidProductOut;
168   }
169 
executeCommand(String command, boolean captureOutput)170   public ExecutionResult executeCommand(String command, boolean captureOutput) {
171     assert(!captureOutput);
172     return executeCommand(command, captureOutput, null, null);
173   }
174 
executeCommand(String command, boolean captureOutput, StreamConsumer outputConsumer, StreamConsumer errorConsumer)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 
getExecutionPrefixWithAdb(String command)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 
getCacheLocation(Architecture architecture)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 
getOatFileName(String testLocation, String programName)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 
cleanCodeCache(Architecture architecture, String testLocation, String programName)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 
pushProgramToDevice(String programName, String testLocation)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 
resetProgramPushed()263   public void resetProgramPushed() {
264     programPushed = false;
265   }
266 }
267