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 #ifndef ART_CMDLINE_CMDLINE_H_
18 #define ART_CMDLINE_CMDLINE_H_
19 
20 #include <stdio.h>
21 #include <stdlib.h>
22 
23 #include <fstream>
24 #include <iostream>
25 #include <string>
26 
27 #include "android-base/stringprintf.h"
28 
29 #include "base/logging.h"
30 #include "base/stringpiece.h"
31 #include "noop_compiler_callbacks.h"
32 #include "runtime.h"
33 
34 #if !defined(NDEBUG)
35 #define DBG_LOG LOG(INFO)
36 #else
37 #define DBG_LOG LOG(DEBUG)
38 #endif
39 
40 namespace art {
41 
42 // TODO: Move to <runtime/utils.h> and remove all copies of this function.
LocationToFilename(const std::string & location,InstructionSet isa,std::string * filename)43 static bool LocationToFilename(const std::string& location, InstructionSet isa,
44                                std::string* filename) {
45   bool has_system = false;
46   bool has_cache = false;
47   // image_location = /system/framework/boot.art
48   // system_image_filename = /system/framework/<image_isa>/boot.art
49   std::string system_filename(GetSystemImageFilename(location.c_str(), isa));
50   if (OS::FileExists(system_filename.c_str())) {
51     has_system = true;
52   }
53 
54   bool have_android_data = false;
55   bool dalvik_cache_exists = false;
56   bool is_global_cache = false;
57   std::string dalvik_cache;
58   GetDalvikCache(GetInstructionSetString(isa), false, &dalvik_cache,
59                  &have_android_data, &dalvik_cache_exists, &is_global_cache);
60 
61   std::string cache_filename;
62   if (have_android_data && dalvik_cache_exists) {
63     // Always set output location even if it does not exist,
64     // so that the caller knows where to create the image.
65     //
66     // image_location = /system/framework/boot.art
67     // *image_filename = /data/dalvik-cache/<image_isa>/boot.art
68     std::string error_msg;
69     if (GetDalvikCacheFilename(location.c_str(), dalvik_cache.c_str(),
70                                &cache_filename, &error_msg)) {
71       has_cache = true;
72     }
73   }
74   if (has_system) {
75     *filename = system_filename;
76     return true;
77   } else if (has_cache) {
78     *filename = cache_filename;
79     return true;
80   } else {
81     return false;
82   }
83 }
84 
StartRuntime(const char * boot_image_location,InstructionSet instruction_set)85 static Runtime* StartRuntime(const char* boot_image_location, InstructionSet instruction_set) {
86   CHECK(boot_image_location != nullptr);
87 
88   RuntimeOptions options;
89 
90   // We are more like a compiler than a run-time. We don't want to execute code.
91   {
92     static NoopCompilerCallbacks callbacks;
93     options.push_back(std::make_pair("compilercallbacks", &callbacks));
94   }
95 
96   // Boot image location.
97   {
98     std::string boot_image_option;
99     boot_image_option += "-Ximage:";
100     boot_image_option += boot_image_location;
101     options.push_back(std::make_pair(boot_image_option.c_str(), nullptr));
102   }
103 
104   // Instruction set.
105   options.push_back(
106       std::make_pair("imageinstructionset",
107                      reinterpret_cast<const void*>(GetInstructionSetString(instruction_set))));
108   // None of the command line tools need sig chain. If this changes we'll need
109   // to upgrade this option to a proper parameter.
110   options.push_back(std::make_pair("-Xno-sig-chain", nullptr));
111   if (!Runtime::Create(options, false)) {
112     fprintf(stderr, "Failed to create runtime\n");
113     return nullptr;
114   }
115 
116   // Runtime::Create acquired the mutator_lock_ that is normally given away when we Runtime::Start,
117   // give it away now and then switch to a more manageable ScopedObjectAccess.
118   Thread::Current()->TransitionFromRunnableToSuspended(kNative);
119 
120   return Runtime::Current();
121 }
122 
123 struct CmdlineArgs {
124   enum ParseStatus {
125     kParseOk,               // Parse successful. Do not set the error message.
126     kParseUnknownArgument,  // Unknown argument. Do not set the error message.
127     kParseError,            // Parse ok, but failed elsewhere. Print the set error message.
128   };
129 
ParseCmdlineArgs130   bool Parse(int argc, char** argv) {
131     // Skip over argv[0].
132     argv++;
133     argc--;
134 
135     if (argc == 0) {
136       fprintf(stderr, "No arguments specified\n");
137       PrintUsage();
138       return false;
139     }
140 
141     std::string error_msg;
142     for (int i = 0; i < argc; i++) {
143       const StringPiece option(argv[i]);
144       if (option.starts_with("--boot-image=")) {
145         boot_image_location_ = option.substr(strlen("--boot-image=")).data();
146       } else if (option.starts_with("--instruction-set=")) {
147         StringPiece instruction_set_str = option.substr(strlen("--instruction-set=")).data();
148         instruction_set_ = GetInstructionSetFromString(instruction_set_str.data());
149         if (instruction_set_ == kNone) {
150           fprintf(stderr, "Unsupported instruction set %s\n", instruction_set_str.data());
151           PrintUsage();
152           return false;
153         }
154       } else if (option.starts_with("--output=")) {
155         output_name_ = option.substr(strlen("--output=")).ToString();
156         const char* filename = output_name_.c_str();
157         out_.reset(new std::ofstream(filename));
158         if (!out_->good()) {
159           fprintf(stderr, "Failed to open output filename %s\n", filename);
160           PrintUsage();
161           return false;
162         }
163         os_ = out_.get();
164       } else {
165         ParseStatus parse_status = ParseCustom(option, &error_msg);
166 
167         if (parse_status == kParseUnknownArgument) {
168           fprintf(stderr, "Unknown argument %s\n", option.data());
169         }
170 
171         if (parse_status != kParseOk) {
172           fprintf(stderr, "%s\n", error_msg.c_str());
173           PrintUsage();
174           return false;
175         }
176       }
177     }
178 
179     DBG_LOG << "will call parse checks";
180 
181     {
182       ParseStatus checks_status = ParseChecks(&error_msg);
183       if (checks_status != kParseOk) {
184           fprintf(stderr, "%s\n", error_msg.c_str());
185           PrintUsage();
186           return false;
187       }
188     }
189 
190     return true;
191   }
192 
GetUsageCmdlineArgs193   virtual std::string GetUsage() const {
194     std::string usage;
195 
196     usage +=  // Required.
197         "  --boot-image=<file.art>: provide the image location for the boot class path.\n"
198         "      Do not include the arch as part of the name, it is added automatically.\n"
199         "      Example: --boot-image=/system/framework/boot.art\n"
200         "               (specifies /system/framework/<arch>/boot.art as the image file)\n"
201         "\n";
202     usage += android::base::StringPrintf(  // Optional.
203         "  --instruction-set=(arm|arm64|mips|mips64|x86|x86_64): for locating the image\n"
204         "      file based on the image location set.\n"
205         "      Example: --instruction-set=x86\n"
206         "      Default: %s\n"
207         "\n",
208         GetInstructionSetString(kRuntimeISA));
209     usage +=  // Optional.
210         "  --output=<file> may be used to send the output to a file.\n"
211         "      Example: --output=/tmp/oatdump.txt\n"
212         "\n";
213 
214     return usage;
215   }
216 
217   // Specified by --boot-image.
218   const char* boot_image_location_ = nullptr;
219   // Specified by --instruction-set.
220   InstructionSet instruction_set_ = kRuntimeISA;
221   // Specified by --output.
222   std::ostream* os_ = &std::cout;
223   std::unique_ptr<std::ofstream> out_;  // If something besides cout is used
224   std::string output_name_;
225 
~CmdlineArgsCmdlineArgs226   virtual ~CmdlineArgs() {}
227 
ParseCheckBootImageCmdlineArgs228   bool ParseCheckBootImage(std::string* error_msg) {
229     if (boot_image_location_ == nullptr) {
230       *error_msg = "--boot-image must be specified";
231       return false;
232     }
233 
234     DBG_LOG << "boot image location: " << boot_image_location_;
235 
236     // Checks for --boot-image location.
237     {
238       std::string boot_image_location = boot_image_location_;
239       size_t file_name_idx = boot_image_location.rfind('/');
240       if (file_name_idx == std::string::npos) {  // Prevent a InsertIsaDirectory check failure.
241         *error_msg = "Boot image location must have a / in it";
242         return false;
243       }
244 
245       // Don't let image locations with the 'arch' in it through, since it's not a location.
246       // This prevents a common error "Could not create an image space..." when initing the Runtime.
247       if (file_name_idx != std::string::npos) {
248         std::string no_file_name = boot_image_location.substr(0, file_name_idx);
249         size_t ancestor_dirs_idx = no_file_name.rfind('/');
250 
251         std::string parent_dir_name;
252         if (ancestor_dirs_idx != std::string::npos) {
253           parent_dir_name = no_file_name.substr(ancestor_dirs_idx + 1);
254         } else {
255           parent_dir_name = no_file_name;
256         }
257 
258         DBG_LOG << "boot_image_location parent_dir_name was " << parent_dir_name;
259 
260         if (GetInstructionSetFromString(parent_dir_name.c_str()) != kNone) {
261           *error_msg = "Do not specify the architecture as part of the boot image location";
262           return false;
263         }
264       }
265 
266       // Check that the boot image location points to a valid file name.
267       std::string file_name;
268       if (!LocationToFilename(boot_image_location, instruction_set_, &file_name)) {
269         *error_msg = android::base::StringPrintf("No corresponding file for location '%s' exists",
270                                                  boot_image_location.c_str());
271         return false;
272       }
273 
274       DBG_LOG << "boot_image_filename does exist: " << file_name;
275     }
276 
277     return true;
278   }
279 
PrintUsageCmdlineArgs280   void PrintUsage() {
281     fprintf(stderr, "%s", GetUsage().c_str());
282   }
283 
284  protected:
ParseCustomCmdlineArgs285   virtual ParseStatus ParseCustom(const StringPiece& option ATTRIBUTE_UNUSED,
286                                   std::string* error_msg ATTRIBUTE_UNUSED) {
287     return kParseUnknownArgument;
288   }
289 
ParseChecksCmdlineArgs290   virtual ParseStatus ParseChecks(std::string* error_msg ATTRIBUTE_UNUSED) {
291     return kParseOk;
292   }
293 };
294 
295 template <typename Args = CmdlineArgs>
296 struct CmdlineMain {
MainCmdlineMain297   int Main(int argc, char** argv) {
298     InitLogging(argv, Runtime::Aborter);
299     std::unique_ptr<Args> args = std::unique_ptr<Args>(CreateArguments());
300     args_ = args.get();
301 
302     DBG_LOG << "Try to parse";
303 
304     if (args_ == nullptr || !args_->Parse(argc, argv)) {
305       return EXIT_FAILURE;
306     }
307 
308     bool needs_runtime = NeedsRuntime();
309     std::unique_ptr<Runtime> runtime;
310 
311 
312     if (needs_runtime) {
313       std::string error_msg;
314       if (!args_->ParseCheckBootImage(&error_msg)) {
315         fprintf(stderr, "%s\n", error_msg.c_str());
316         args_->PrintUsage();
317         return EXIT_FAILURE;
318       }
319       runtime.reset(CreateRuntime(args.get()));
320       if (runtime == nullptr) {
321         return EXIT_FAILURE;
322       }
323       if (!ExecuteWithRuntime(runtime.get())) {
324         return EXIT_FAILURE;
325       }
326     } else {
327       if (!ExecuteWithoutRuntime()) {
328         return EXIT_FAILURE;
329       }
330     }
331 
332     if (!ExecuteCommon()) {
333       return EXIT_FAILURE;
334     }
335 
336     return EXIT_SUCCESS;
337   }
338 
339   // Override this function to create your own arguments.
340   // Usually will want to return a subtype of CmdlineArgs.
CreateArgumentsCmdlineMain341   virtual Args* CreateArguments() {
342     return new Args();
343   }
344 
345   // Override this function to do something else with the runtime.
ExecuteWithRuntimeCmdlineMain346   virtual bool ExecuteWithRuntime(Runtime* runtime) {
347     CHECK(runtime != nullptr);
348     // Do nothing
349     return true;
350   }
351 
352   // Does the code execution need a runtime? Sometimes it doesn't.
NeedsRuntimeCmdlineMain353   virtual bool NeedsRuntime() {
354     return true;
355   }
356 
357   // Do execution without having created a runtime.
ExecuteWithoutRuntimeCmdlineMain358   virtual bool ExecuteWithoutRuntime() {
359     return true;
360   }
361 
362   // Continue execution after ExecuteWith[out]Runtime
ExecuteCommonCmdlineMain363   virtual bool ExecuteCommon() {
364     return true;
365   }
366 
~CmdlineMainCmdlineMain367   virtual ~CmdlineMain() {}
368 
369  protected:
370   Args* args_ = nullptr;
371 
372  private:
CreateRuntimeCmdlineMain373   Runtime* CreateRuntime(CmdlineArgs* args) {
374     CHECK(args != nullptr);
375 
376     return StartRuntime(args->boot_image_location_, args->instruction_set_);
377   }
378 };
379 }  // namespace art
380 
381 #endif  // ART_CMDLINE_CMDLINE_H_
382