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 
Executor(String name, int timeout, BaseListener listener, Architecture architecture, Device device, boolean needsCleanCodeCache, boolean isBisectable)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    */
executeCommandWithTimeout(String command, boolean captureOutput)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    */
shutdown()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    */
reset()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    */
verifyOnHost(String programName)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    */
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    */
constructCommand(String programName)172   protected abstract String constructCommand(String programName);
173 
174   /**
175    * Executes runtime.
176    */
execute(String programName)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    */
runBisectionSearch(String programName, String expectedOutputFile, String logFile)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    */
getArchitecture()211   public Architecture getArchitecture() {
212     return architecture;
213   }
214 
215   /**
216    * Used by the Fuzzer to get result of execution.
217    */
getResult()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    */
didTargetVerify()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 
getName()237   public String getName() {
238     return name;
239   }
240 
finishedWithProgramOnDevice()241   public void finishedWithProgramOnDevice() {
242     device.resetProgramPushed();
243   }
244 
isBisectable()245   public boolean isBisectable() {
246     return isBisectable;
247   }
248 }
249