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