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 
Executor(String name, int timeout, BaseListener listener, Architecture architecture, Device device, boolean needsCleanCodeCache)43   protected Executor(String name, int timeout, BaseListener listener, Architecture architecture,
44       Device device, boolean needsCleanCodeCache) {
45     executeClass = Options.executeClass;
46 
47     if (Options.shortTimeouts) {
48       this.timeout = 2;
49     } else {
50       this.timeout = timeout;
51     }
52 
53     this.name = name;
54     this.listener = listener;
55     this.architecture = architecture;
56     this.device = device;
57     this.needsCleanCodeCache = needsCleanCodeCache;
58 
59     if (Options.executeOnHost) {
60       this.testLocation = System.getProperty("user.dir");
61     } else {
62       this.testLocation = Options.executeDirectory;
63     }
64 
65     outputConsumer = new StreamConsumer();
66     outputConsumer.start();
67     errorConsumer = new StreamConsumer();
68     errorConsumer.start();
69   }
70 
71   /**
72    * Called by subclass Executors in their execute() implementations.
73    */
executeCommandWithTimeout(String command, boolean captureOutput)74   protected ExecutionResult executeCommandWithTimeout(String command, boolean captureOutput) {
75     String timeoutString = "timeout " + timeout + " ";
76     return device.executeCommand(timeoutString + device.getExecutionShellPrefix() + command,
77         captureOutput, outputConsumer, errorConsumer);
78   }
79 
80   /**
81    * Call this to make sure the StreamConsumer threads are stopped.
82    */
shutdown()83   public void shutdown() {
84     outputConsumer.shutdown();
85     errorConsumer.shutdown();
86   }
87 
88   /**
89    * Called by the Fuzzer after each execution has finished, to clear the results.
90    */
reset()91   public void reset() {
92     executionResult = null;
93   }
94 
95   /**
96    * Called by the Fuzzer to verify the mutated program using the host-side dex2oat.
97    */
verifyOnHost(String programName)98   public boolean verifyOnHost(String programName) {
99     StringBuilder commandBuilder = new StringBuilder();
100     commandBuilder.append("dex2oat ");
101 
102     commandBuilder.append("--instruction-set=").append(architecture.asString());
103     commandBuilder.append(" --instruction-set-features=default ");
104 
105     // Select the correct boot image.
106     commandBuilder.append("--boot-image=").append(device.getAndroidProductOut());
107     if (device.noBootImageAvailable()) {
108       commandBuilder.append("/data/art-test/core.art ");
109     } else {
110       commandBuilder.append("/system/framework/boot.art ");
111     }
112 
113     commandBuilder.append("--oat-file=output.oat ");
114     commandBuilder.append("--android-root=").append(device.getAndroidHostOut()).append(" ");
115     commandBuilder.append("--runtime-arg -classpath ");
116     commandBuilder.append("--runtime-arg ").append(programName).append(" ");
117     commandBuilder.append("--dex-file=").append(programName).append(" ");
118     commandBuilder.append("--compiler-filter=interpret-only --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    */
prepareProgramForExecution(String programName)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    */
execute(String programName)172   public abstract void execute(String programName);
173 
174   /**
175    * Fuzzer.checkForArchitectureSplit() will use this determine the architecture of the Executor.
176    */
getArchitecture()177   public Architecture getArchitecture() {
178     return architecture;
179   }
180 
181   /**
182    * Used by the Fuzzer to get result of execution.
183    */
getResult()184   public ExecutionResult getResult() {
185     return executionResult;
186   }
187 
188   /**
189    * Because dex2oat can accept a program with soft errors on the host, and then fail after
190    * performing hard verification on the target, we need to check if the Executor detected
191    * a target verification failure, before doing anything else with the resulting output.
192    * Used by the Fuzzer.
193    */
didTargetVerify()194   public boolean didTargetVerify() {
195     // TODO: Remove this once host-verification can be forced to always fail?
196     String output = executionResult.getFlattenedAll();
197     if (output.contains("VerifyError") || output.contains("Verification failed on class")) {
198       return false;
199     }
200     return true;
201   }
202 
getName()203   public String getName() {
204     return name;
205   }
206 
finishedWithProgramOnDevice()207   public void finishedWithProgramOnDevice() {
208     device.resetProgramPushed();
209   }
210 }
211