Home | History | Annotate | Download | only in executors
      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    */
     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 (at) 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