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.ArrayList; 22import java.util.List; 23import java.util.Map; 24import java.util.regex.Matcher; 25import java.util.regex.Pattern; 26 27import dexfuzz.ExecutionResult; 28import dexfuzz.Log; 29import dexfuzz.Options; 30import dexfuzz.StreamConsumer; 31 32/** 33 * Handles execution either on a remote target device, or on a local host computer. 34 */ 35public 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 */ 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 */ 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 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 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 81 private String getHostCoreImagePathNoArch() { 82 return androidHostOut + "/framework/core.art"; 83 } 84 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 */ 137 public String getName() { 138 assert(!isHost); 139 return deviceName; 140 } 141 142 public boolean isHost() { 143 return isHost; 144 } 145 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 */ 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 */ 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 */ 172 public String getHostExecutionFlags() { 173 return String.format("-Xnorelocate -Ximage:%s", getHostCoreImagePathNoArch()); 174 } 175 176 public String getAndroidHostOut() { 177 return androidHostOut; 178 } 179 180 public String getAndroidProductOut() { 181 return androidProductOut; 182 } 183 184 public ExecutionResult executeCommand(String command, boolean captureOutput) { 185 assert(!captureOutput); 186 return executeCommand(command, captureOutput, null, null); 187 } 188 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 */ 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 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 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 269 private String getOatFileName(String testLocation, String programName) { 270 // Converts e.g. /data/art-test/file.dex to data@art-test@file.dex 271 return (testLocation.replace("/", "@").substring(1) + "@" + programName); 272 } 273 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 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 292 public void resetProgramPushed() { 293 programPushed = false; 294 } 295} 296