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 dexfuzz.ExecutionResult;
     20 import dexfuzz.Options;
     21 import dexfuzz.StreamConsumer;
     22 import dexfuzz.listeners.BaseListener;
     23 
     24 /**
     25  * Base class containing the common methods for executing a particular backend of ART.
     26  */
     27 public abstract class Executor {
     28   private StreamConsumer outputConsumer;
     29   private StreamConsumer errorConsumer;
     30 
     31   protected ExecutionResult executionResult;
     32   protected String executeClass;
     33 
     34   // Set by subclasses.
     35   protected String name;
     36   protected int timeout;
     37   protected BaseListener listener;
     38   protected String testLocation;
     39   protected Architecture architecture;
     40   protected Device device;
     41   private boolean needsCleanCodeCache;
     42   private boolean isBisectable;
     43 
     44   protected Executor(String name, int timeout, BaseListener listener, Architecture architecture,
     45       Device device, boolean needsCleanCodeCache, boolean isBisectable) {
     46     executeClass = Options.executeClass;
     47 
     48     if (Options.shortTimeouts) {
     49       this.timeout = 2;
     50     } else {
     51       this.timeout = timeout;
     52     }
     53 
     54     this.name = name;
     55     this.listener = listener;
     56     this.architecture = architecture;
     57     this.device = device;
     58     this.needsCleanCodeCache = needsCleanCodeCache;
     59     this.isBisectable = isBisectable;
     60 
     61     if (Options.executeOnHost) {
     62       this.testLocation = System.getProperty("user.dir");
     63     } else {
     64       this.testLocation = Options.executeDirectory;
     65     }
     66 
     67     outputConsumer = new StreamConsumer();
     68     outputConsumer.start();
     69     errorConsumer = new StreamConsumer();
     70     errorConsumer.start();
     71   }
     72 
     73   /**
     74    * Called by subclass Executors in their execute() implementations.
     75    */
     76   protected ExecutionResult executeCommandWithTimeout(String command, boolean captureOutput) {
     77     String timeoutString = "timeout " + timeout + " ";
     78     return device.executeCommand(timeoutString + device.getExecutionShellPrefix() + command,
     79         captureOutput, outputConsumer, errorConsumer);
     80   }
     81 
     82   /**
     83    * Call this to make sure the StreamConsumer threads are stopped.
     84    */
     85   public void shutdown() {
     86     outputConsumer.shutdown();
     87     errorConsumer.shutdown();
     88   }
     89 
     90   /**
     91    * Called by the Fuzzer after each execution has finished, to clear the results.
     92    */
     93   public void reset() {
     94     executionResult = null;
     95   }
     96 
     97   /**
     98    * Called by the Fuzzer to verify the mutated program using the host-side dex2oat.
     99    */
    100   public boolean verifyOnHost(String programName) {
    101     StringBuilder commandBuilder = new StringBuilder();
    102     commandBuilder.append("dex2oat ");
    103 
    104     commandBuilder.append("--instruction-set=").append(architecture.asString());
    105     commandBuilder.append(" --instruction-set-features=default ");
    106 
    107     // Select the correct boot image.
    108     commandBuilder.append("--boot-image=").append(device.getAndroidProductOut());
    109     if (device.noBootImageAvailable()) {
    110       commandBuilder.append("/data/art-test/core.art ");
    111     } else {
    112       commandBuilder.append("/system/framework/boot.art ");
    113     }
    114 
    115     commandBuilder.append("--oat-file=output.oat ");
    116     commandBuilder.append("--android-root=").append(device.getAndroidHostOut()).append(" ");
    117     commandBuilder.append("--runtime-arg -classpath ");
    118     commandBuilder.append("--runtime-arg ").append(programName).append(" ");
    119     commandBuilder.append("--dex-file=").append(programName).append(" ");
    120     commandBuilder.append("--compiler-filter=quicken --runtime-arg -Xnorelocate ");
    121 
    122     ExecutionResult verificationResult = device.executeCommand(commandBuilder.toString(), true,
    123         outputConsumer, errorConsumer);
    124 
    125     boolean success = true;
    126 
    127     if (verificationResult.isSigabort()) {
    128       listener.handleHostVerificationSigabort(verificationResult);
    129       success = false;
    130     }
    131 
    132     if (success) {
    133       // Search for a keyword that indicates verification was not successful.
    134       // TODO: Determine if dex2oat crashed?
    135       for (String line : verificationResult.error) {
    136         if (line.contains("Verification error")
    137             || line.contains("Failure to verify dex file")) {
    138           success = false;
    139         }
    140         if (Options.dumpVerify) {
    141           // Strip out the start of the log lines.
    142           listener.handleDumpVerify(line.replaceFirst(".*(cc|h):\\d+] ",  ""));
    143         }
    144       }
    145     }
    146 
    147     if (!success) {
    148       listener.handleFailedHostVerification(verificationResult);
    149     }
    150 
    151     device.executeCommand("rm output.oat", false);
    152 
    153     return success;
    154   }
    155 
    156   /**
    157    * Called by the Fuzzer to upload the program to the target device.
    158    */
    159   public void prepareProgramForExecution(String programName) {
    160     if (!Options.executeOnHost) {
    161       device.pushProgramToDevice(programName, testLocation);
    162     }
    163 
    164     if (needsCleanCodeCache) {
    165       // Get the device to clean the code cache
    166       device.cleanCodeCache(architecture, testLocation, programName);
    167     }
    168   }
    169 
    170   /**
    171    * Executor subclasses need to override this, to construct their arguments for dalvikvm
    172    * invocation correctly.
    173    */
    174   protected abstract String constructCommand(String programName);
    175 
    176   /**
    177    * Executes runtime.
    178    */
    179   public void execute(String programName) {
    180     String command = "";
    181     String androidRoot = Options.androidRoot.trim();
    182     if (androidRoot.length() != 0) {
    183       command = "PATH=" + androidRoot + "/bin ";
    184       command += "ANDROID_ROOT=" + androidRoot + " ";
    185       command += "LD_LIBRARY_PATH="+ androidRoot + "/lib:" + androidRoot + "/lib64 ";
    186     }
    187     command += constructCommand(programName);
    188     executionResult = executeCommandWithTimeout(command, true);
    189   }
    190 
    191   /**
    192    * Runs bisection bug search.
    193    */
    194   public ExecutionResult runBisectionSearch(String programName, String expectedOutputFile, String logFile) {
    195     assert(isBisectable);
    196     String runtimeCommand = constructCommand(programName);
    197     StringBuilder commandBuilder = new StringBuilder();
    198     commandBuilder.append("bisection_search.py --raw-cmd '").append(runtimeCommand);
    199     commandBuilder.append("' --expected-output=").append(expectedOutputFile);
    200     commandBuilder.append(" --logfile=").append(logFile);
    201     if (!device.isHost()) {
    202       commandBuilder.append(" --device");
    203       if (device.isUsingSpecificDevice()) {
    204         commandBuilder.append(" --specific-device=").append(device.getName());
    205       }
    206     }
    207     return device.executeCommand(commandBuilder.toString(), true, outputConsumer, errorConsumer);
    208   }
    209 
    210   /**
    211    * Fuzzer.checkForArchitectureSplit() will use this determine the architecture of the Executor.
    212    */
    213   public Architecture getArchitecture() {
    214     return architecture;
    215   }
    216 
    217   /**
    218    * Used by the Fuzzer to get result of execution.
    219    */
    220   public ExecutionResult getResult() {
    221     return executionResult;
    222   }
    223 
    224   /**
    225    * Because dex2oat can accept a program with soft errors on the host, and then fail after
    226    * performing hard verification on the target, we need to check if the Executor detected
    227    * a target verification failure, before doing anything else with the resulting output.
    228    * Used by the Fuzzer.
    229    */
    230   public boolean didTargetVerify() {
    231     // TODO: Remove this once host-verification can be forced to always fail?
    232     String output = executionResult.getFlattenedAll();
    233     if (output.contains("VerifyError") || output.contains("Verification failed on class")) {
    234       return false;
    235     }
    236     return true;
    237   }
    238 
    239   public String getName() {
    240     return name;
    241   }
    242 
    243   public void finishedWithProgramOnDevice() {
    244     device.resetProgramPushed();
    245   }
    246 
    247   public boolean isBisectable() {
    248     return isBisectable;
    249   }
    250 }
    251