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("--dex-file=").append(programName).append(" ");
    118     commandBuilder.append("--compiler-filter=quicken --runtime-arg -Xnorelocate ");
    119 
    120     ExecutionResult verificationResult = device.executeCommand(commandBuilder.toString(), true,
    121         outputConsumer, errorConsumer);
    122 
    123     boolean success = true;
    124 
    125     if (verificationResult.isSigabort()) {
    126       listener.handleHostVerificationSigabort(verificationResult);
    127       success = false;
    128     }
    129 
    130     if (success) {
    131       // Search for a keyword that indicates verification was not successful.
    132       // TODO: Determine if dex2oat crashed?
    133       for (String line : verificationResult.error) {
    134         if (line.contains("Verification error")
    135             || line.contains("Failure to verify dex file")) {
    136           success = false;
    137         }
    138         if (Options.dumpVerify) {
    139           // Strip out the start of the log lines.
    140           listener.handleDumpVerify(line.replaceFirst(".*(cc|h):\\d+] ",  ""));
    141         }
    142       }
    143     }
    144 
    145     if (!success) {
    146       listener.handleFailedHostVerification(verificationResult);
    147     }
    148 
    149     device.executeCommand("rm output.oat", false);
    150 
    151     return success;
    152   }
    153 
    154   /**
    155    * Called by the Fuzzer to upload the program to the target device.
    156    */
    157   public void prepareProgramForExecution(String programName) {
    158     if (!Options.executeOnHost) {
    159       device.pushProgramToDevice(programName, testLocation);
    160     }
    161 
    162     if (needsCleanCodeCache) {
    163       // Get the device to clean the code cache
    164       device.cleanCodeCache(architecture, testLocation, programName);
    165     }
    166   }
    167 
    168   /**
    169    * Executor subclasses need to override this, to construct their arguments for dalvikvm
    170    * invocation correctly.
    171    */
    172   protected abstract String constructCommand(String programName);
    173 
    174   /**
    175    * Executes runtime.
    176    */
    177   public void execute(String programName) {
    178     String command = "";
    179     String androidRoot = Options.androidRoot.trim();
    180     if (androidRoot.length() != 0) {
    181       command = "PATH=" + androidRoot + "/bin ";
    182       command += "ANDROID_ROOT=" + androidRoot + " ";
    183       command += "LD_LIBRARY_PATH="+ androidRoot + "/lib:" + androidRoot + "/lib64 ";
    184     }
    185     command += constructCommand(programName);
    186     executionResult = executeCommandWithTimeout(command, true);
    187   }
    188 
    189   /**
    190    * Runs bisection bug search.
    191    */
    192   public ExecutionResult runBisectionSearch(String programName, String expectedOutputFile, String logFile) {
    193     assert(isBisectable);
    194     String runtimeCommand = constructCommand(programName);
    195     StringBuilder commandBuilder = new StringBuilder();
    196     commandBuilder.append("bisection_search.py --raw-cmd '").append(runtimeCommand);
    197     commandBuilder.append("' --expected-output=").append(expectedOutputFile);
    198     commandBuilder.append(" --logfile=").append(logFile);
    199     if (!device.isHost()) {
    200       commandBuilder.append(" --device");
    201       if (device.isUsingSpecificDevice()) {
    202         commandBuilder.append(" --specific-device=").append(device.getName());
    203       }
    204     }
    205     return device.executeCommand(commandBuilder.toString(), true, outputConsumer, errorConsumer);
    206   }
    207 
    208   /**
    209    * Fuzzer.checkForArchitectureSplit() will use this determine the architecture of the Executor.
    210    */
    211   public Architecture getArchitecture() {
    212     return architecture;
    213   }
    214 
    215   /**
    216    * Used by the Fuzzer to get result of execution.
    217    */
    218   public ExecutionResult getResult() {
    219     return executionResult;
    220   }
    221 
    222   /**
    223    * Because dex2oat can accept a program with soft errors on the host, and then fail after
    224    * performing hard verification on the target, we need to check if the Executor detected
    225    * a target verification failure, before doing anything else with the resulting output.
    226    * Used by the Fuzzer.
    227    */
    228   public boolean didTargetVerify() {
    229     // TODO: Remove this once host-verification can be forced to always fail?
    230     String output = executionResult.getFlattenedAll();
    231     if (output.contains("VerifyError") || output.contains("Verification failed on class")) {
    232       return false;
    233     }
    234     return true;
    235   }
    236 
    237   public String getName() {
    238     return name;
    239   }
    240 
    241   public void finishedWithProgramOnDevice() {
    242     device.resetProgramPushed();
    243   }
    244 
    245   public boolean isBisectable() {
    246     return isBisectable;
    247   }
    248 }
    249