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