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 <memory>
26 #include <string>
27 #include <string_view>
28 #include <vector>
29
30 #include "android-base/stringprintf.h"
31 #include "android-base/strings.h"
32 #include "base/file_utils.h"
33 #include "base/logging.h"
34 #include "base/mutex.h"
35 #include "base/utils.h"
36 #include "noop_compiler_callbacks.h"
37 #include "oat/oat_file_assistant_context.h"
38 #include "runtime.h"
39
40 #if !defined(NDEBUG)
41 #define DBG_LOG LOG(INFO)
42 #else
43 #define DBG_LOG LOG(DEBUG)
44 #endif
45
46 namespace art {
47
StartRuntime(const std::vector<std::string> & boot_image_locations,InstructionSet instruction_set,const std::vector<const char * > & runtime_args)48 static Runtime* StartRuntime(const std::vector<std::string>& boot_image_locations,
49 InstructionSet instruction_set,
50 const std::vector<const char*>& runtime_args) {
51 RuntimeOptions options;
52
53 // We are more like a compiler than a run-time. We don't want to execute code.
54 {
55 static NoopCompilerCallbacks callbacks;
56 options.push_back(std::make_pair("compilercallbacks", &callbacks));
57 }
58
59 // Boot image location.
60 {
61 std::string boot_image_option = "-Ximage:";
62 if (!boot_image_locations.empty()) {
63 boot_image_option += android::base::Join(boot_image_locations, ':');
64 } else {
65 boot_image_option += GetJitZygoteBootImageLocation();
66 }
67 options.push_back(std::make_pair(boot_image_option, nullptr));
68 }
69
70 // Instruction set.
71 options.push_back(
72 std::make_pair("imageinstructionset",
73 reinterpret_cast<const void*>(GetInstructionSetString(instruction_set))));
74
75 // Explicit runtime args.
76 for (const char* runtime_arg : runtime_args) {
77 options.push_back(std::make_pair(runtime_arg, nullptr));
78 }
79
80 // None of the command line tools need sig chain. If this changes we'll need
81 // to upgrade this option to a proper parameter.
82 options.push_back(std::make_pair("-Xno-sig-chain", nullptr));
83 if (!Runtime::Create(options, false)) {
84 fprintf(stderr, "Failed to create runtime\n");
85 return nullptr;
86 }
87
88 // Runtime::Create acquired the mutator_lock_ that is normally given away when we Runtime::Start,
89 // give it away now and then switch to a more manageable ScopedObjectAccess.
90 Thread::Current()->TransitionFromRunnableToSuspended(ThreadState::kNative);
91
92 return Runtime::Current();
93 }
94
95 struct CmdlineArgs {
96 enum ParseStatus {
97 kParseOk, // Parse successful. Do not set the error message.
98 kParseUnknownArgument, // Unknown argument. Do not set the error message.
99 kParseError, // Parse ok, but failed elsewhere. Print the set error message.
100 };
101
ParseCmdlineArgs102 bool Parse(int argc, char** argv) {
103 // Skip over argv[0].
104 argv++;
105 argc--;
106
107 if (argc == 0) {
108 fprintf(stderr, "No arguments specified\n");
109 PrintUsage();
110 return false;
111 }
112
113 std::string error_msg;
114 for (int i = 0; i < argc; i++) {
115 const char* const raw_option = argv[i];
116 const std::string_view option(raw_option);
117 if (option.starts_with("--boot-image=")) {
118 Split(raw_option + strlen("--boot-image="), ':', &boot_image_locations_);
119 } else if (option.starts_with("--instruction-set=")) {
120 const char* const instruction_set_str = raw_option + strlen("--instruction-set=");
121 instruction_set_ = GetInstructionSetFromString(instruction_set_str);
122 if (instruction_set_ == InstructionSet::kNone) {
123 fprintf(stderr, "Unsupported instruction set %s\n", instruction_set_str);
124 PrintUsage();
125 return false;
126 }
127 } else if (option == "--runtime-arg") {
128 if (i + 1 == argc) {
129 fprintf(stderr, "Missing argument for --runtime-arg\n");
130 PrintUsage();
131 return false;
132 }
133 ++i;
134 runtime_args_.push_back(argv[i]);
135 } else if (option.starts_with("--output=")) {
136 output_name_ = std::string(option.substr(strlen("--output=")));
137 const char* filename = output_name_.c_str();
138 out_.reset(new std::ofstream(filename));
139 if (!out_->good()) {
140 fprintf(stderr, "Failed to open output filename %s\n", filename);
141 PrintUsage();
142 return false;
143 }
144 os_ = out_.get();
145 } else {
146 ParseStatus parse_status = ParseCustom(raw_option, option.length(), &error_msg);
147
148 if (parse_status == kParseUnknownArgument) {
149 fprintf(stderr, "Unknown argument %s\n", option.data());
150 }
151
152 if (parse_status != kParseOk) {
153 fprintf(stderr, "%s\n", error_msg.c_str());
154 PrintUsage();
155 return false;
156 }
157 }
158 }
159
160 if (instruction_set_ == InstructionSet::kNone) {
161 LOG(WARNING) << "No instruction set given, assuming " << GetInstructionSetString(kRuntimeISA);
162 instruction_set_ = kRuntimeISA;
163 }
164
165 DBG_LOG << "will call parse checks";
166
167 {
168 ParseStatus checks_status = ParseChecks(&error_msg);
169 if (checks_status != kParseOk) {
170 fprintf(stderr, "%s\n", error_msg.c_str());
171 PrintUsage();
172 return false;
173 }
174 }
175
176 return true;
177 }
178
GetUsageCmdlineArgs179 virtual std::string GetUsage() const {
180 std::string usage;
181
182 usage += // Required.
183 " --boot-image=<file.art>: provide the image location for the boot class path.\n"
184 " Do not include the arch as part of the name, it is added automatically.\n"
185 " Example: --boot-image=/system/framework/boot.art\n"
186 " (specifies /system/framework/<arch>/boot.art as the image file)\n"
187 "\n";
188 usage += android::base::StringPrintf( // Optional.
189 " --instruction-set=(arm|arm64|x86|x86_64): for locating the image\n"
190 " file based on the image location set.\n"
191 " Example: --instruction-set=x86\n"
192 " Default: %s\n"
193 "\n",
194 GetInstructionSetString(kRuntimeISA));
195 usage +=
196 " --runtime-arg <argument> used to specify various arguments for the runtime\n"
197 " such as initial heap size, maximum heap size, and verbose output.\n"
198 " Use a separate --runtime-arg switch for each argument.\n"
199 " Example: --runtime-arg -Xms256m\n"
200 "\n";
201 usage += // Optional.
202 " --output=<file> may be used to send the output to a file.\n"
203 " Example: --output=/tmp/oatdump.txt\n"
204 "\n";
205
206 return usage;
207 }
208
209 // Specified by --runtime-arg -Xbootclasspath or default.
210 std::vector<std::string> boot_class_path_;
211 // Specified by --runtime-arg -Xbootclasspath-locations or default.
212 std::vector<std::string> boot_class_path_locations_;
213 // True if `boot_class_path_` is the default one.
214 bool is_default_boot_class_path_ = false;
215 // Specified by --boot-image or inferred.
216 std::vector<std::string> boot_image_locations_;
217 // Specified by --instruction-set.
218 InstructionSet instruction_set_ = InstructionSet::kNone;
219 // Runtime arguments specified by --runtime-arg.
220 std::vector<const char*> runtime_args_;
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
228 // Checks for --boot-image location.
ParseCheckBootImageCmdlineArgs229 bool ParseCheckBootImage(std::string* error_msg) {
230 if (boot_image_locations_.empty()) {
231 LOG(WARNING) << "--boot-image not specified. Starting runtime in imageless mode";
232 return true;
233 }
234
235 const std::string& boot_image_location = boot_image_locations_[0];
236 size_t file_name_idx = boot_image_location.rfind('/');
237 if (file_name_idx == std::string::npos) { // Prevent a InsertIsaDirectory check failure.
238 *error_msg = "Boot image location must have a / in it";
239 return false;
240 }
241
242 // Don't let image locations with the 'arch' in it through, since it's not a location.
243 // This prevents a common error "Could not create an image space..." when initing the Runtime.
244 if (file_name_idx > 0) {
245 size_t ancestor_dirs_idx = boot_image_location.rfind('/', file_name_idx - 1);
246
247 std::string parent_dir_name;
248 if (ancestor_dirs_idx != std::string::npos) {
249 parent_dir_name = boot_image_location.substr(/*pos=*/ancestor_dirs_idx + 1,
250 /*n=*/file_name_idx - ancestor_dirs_idx - 1);
251 } else {
252 parent_dir_name = boot_image_location.substr(/*pos=*/0,
253 /*n=*/file_name_idx);
254 }
255
256 DBG_LOG << "boot_image_location parent_dir_name was " << parent_dir_name;
257
258 if (GetInstructionSetFromString(parent_dir_name.c_str()) != InstructionSet::kNone) {
259 *error_msg = "Do not specify the architecture as part of the boot image location";
260 return false;
261 }
262 }
263
264 return true;
265 }
266
PrintUsageCmdlineArgs267 void PrintUsage() { fprintf(stderr, "%s", GetUsage().c_str()); }
268
GetOatFileAssistantContextCmdlineArgs269 std::unique_ptr<OatFileAssistantContext> GetOatFileAssistantContext(std::string* error_msg) {
270 if (boot_class_path_.empty()) {
271 *error_msg = "Boot classpath is empty";
272 return nullptr;
273 }
274
275 CHECK(!boot_class_path_locations_.empty());
276
277 return std::make_unique<OatFileAssistantContext>(
278 std::make_unique<OatFileAssistantContext::RuntimeOptions>(
279 OatFileAssistantContext::RuntimeOptions{
280 .image_locations = boot_image_locations_,
281 .boot_class_path = boot_class_path_,
282 .boot_class_path_locations = boot_class_path_locations_,
283 }));
284 }
285
286 protected:
ParseCustomCmdlineArgs287 virtual ParseStatus ParseCustom([[maybe_unused]] const char* raw_option,
288 [[maybe_unused]] size_t raw_option_length,
289 [[maybe_unused]] std::string* error_msg) {
290 return kParseUnknownArgument;
291 }
292
ParseChecksCmdlineArgs293 virtual ParseStatus ParseChecks([[maybe_unused]] std::string* error_msg) {
294 ParseBootclasspath();
295 if (boot_image_locations_.empty()) {
296 InferBootImage();
297 }
298 return kParseOk;
299 }
300
301 private:
ParseBootclasspathCmdlineArgs302 void ParseBootclasspath() {
303 std::optional<std::string_view> bcp_str = std::nullopt;
304 std::optional<std::string_view> bcp_location_str = std::nullopt;
305 for (std::string_view arg : runtime_args_) {
306 if (arg.starts_with("-Xbootclasspath:")) {
307 bcp_str = arg.substr(strlen("-Xbootclasspath:"));
308 }
309 if (arg.starts_with("-Xbootclasspath-locations:")) {
310 bcp_location_str = arg.substr(strlen("-Xbootclasspath-locations:"));
311 }
312 }
313
314 if (bcp_str.has_value() && bcp_location_str.has_value()) {
315 Split(*bcp_str, ':', &boot_class_path_);
316 Split(*bcp_location_str, ':', &boot_class_path_locations_);
317 } else if (bcp_str.has_value()) {
318 Split(*bcp_str, ':', &boot_class_path_);
319 boot_class_path_locations_ = boot_class_path_;
320 } else {
321 // Try the default.
322 const char* env_value = getenv("BOOTCLASSPATH");
323 if (env_value != nullptr && strlen(env_value) > 0) {
324 Split(env_value, ':', &boot_class_path_);
325 boot_class_path_locations_ = boot_class_path_;
326 is_default_boot_class_path_ = true;
327 }
328 }
329 }
330
331 // Infers the boot image on a best-effort basis.
332 // The inference logic aligns with installd/artd + dex2oat.
InferBootImageCmdlineArgs333 void InferBootImage() {
334 // The boot image inference only makes sense on device.
335 if (!kIsTargetAndroid) {
336 return;
337 }
338
339 // The inferred boot image can only be used with the default bootclasspath.
340 if (boot_class_path_.empty() || !is_default_boot_class_path_) {
341 return;
342 }
343
344 std::string error_msg;
345 std::string boot_image = GetBootImageLocationForDefaultBcpRespectingSysProps(&error_msg);
346 if (boot_image.empty()) {
347 LOG(WARNING) << "Failed to infer boot image: " << error_msg;
348 return;
349 }
350
351 LOG(INFO) << "Inferred boot image: " << boot_image;
352 Split(boot_image, ':', &boot_image_locations_);
353
354 // Verify the inferred boot image.
355 std::unique_ptr<OatFileAssistantContext> ofa_context = GetOatFileAssistantContext(&error_msg);
356 CHECK_NE(ofa_context, nullptr);
357 size_t verified_boot_image_count = ofa_context->GetBootImageInfoList(instruction_set_).size();
358 if (verified_boot_image_count != boot_image_locations_.size()) {
359 LOG(WARNING) << "Failed to verify inferred boot image";
360 boot_image_locations_.resize(verified_boot_image_count);
361 }
362 }
363 };
364
365 template <typename Args = CmdlineArgs>
366 struct CmdlineMain {
MainCmdlineMain367 int Main(int argc, char** argv) {
368 Locks::Init();
369 InitLogging(argv, Runtime::Abort);
370 std::unique_ptr<Args> args = std::unique_ptr<Args>(CreateArguments());
371 args_ = args.get();
372
373 DBG_LOG << "Try to parse";
374
375 if (args_ == nullptr || !args_->Parse(argc, argv)) {
376 return EXIT_FAILURE;
377 }
378
379 bool needs_runtime = NeedsRuntime();
380 std::unique_ptr<Runtime> runtime;
381
382
383 if (needs_runtime) {
384 std::string error_msg;
385 if (!args_->ParseCheckBootImage(&error_msg)) {
386 fprintf(stderr, "%s\n", error_msg.c_str());
387 args_->PrintUsage();
388 return EXIT_FAILURE;
389 }
390 runtime.reset(CreateRuntime(args.get()));
391 if (runtime == nullptr) {
392 return EXIT_FAILURE;
393 }
394 if (!ExecuteWithRuntime(runtime.get())) {
395 return EXIT_FAILURE;
396 }
397 } else {
398 if (!ExecuteWithoutRuntime()) {
399 return EXIT_FAILURE;
400 }
401 }
402
403 if (!ExecuteCommon()) {
404 return EXIT_FAILURE;
405 }
406
407 return EXIT_SUCCESS;
408 }
409
410 // Override this function to create your own arguments.
411 // Usually will want to return a subtype of CmdlineArgs.
CreateArgumentsCmdlineMain412 virtual Args* CreateArguments() {
413 return new Args();
414 }
415
416 // Override this function to do something else with the runtime.
ExecuteWithRuntimeCmdlineMain417 virtual bool ExecuteWithRuntime(Runtime* runtime) {
418 CHECK(runtime != nullptr);
419 // Do nothing
420 return true;
421 }
422
423 // Does the code execution need a runtime? Sometimes it doesn't.
NeedsRuntimeCmdlineMain424 virtual bool NeedsRuntime() {
425 return true;
426 }
427
428 // Do execution without having created a runtime.
ExecuteWithoutRuntimeCmdlineMain429 virtual bool ExecuteWithoutRuntime() {
430 return true;
431 }
432
433 // Continue execution after ExecuteWith[out]Runtime
ExecuteCommonCmdlineMain434 virtual bool ExecuteCommon() {
435 return true;
436 }
437
~CmdlineMainCmdlineMain438 virtual ~CmdlineMain() {}
439
440 protected:
441 Args* args_ = nullptr;
442
443 private:
CreateRuntimeCmdlineMain444 Runtime* CreateRuntime(CmdlineArgs* args) {
445 CHECK(args != nullptr);
446
447 return StartRuntime(args->boot_image_locations_, args->instruction_set_, args_->runtime_args_);
448 }
449 };
450 } // namespace art
451
452 #endif // ART_CMDLINE_CMDLINE_H_
453