• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 #include <sys/stat.h>
18 #include <sysexits.h>
19 
20 #include <algorithm>
21 #include <cstdlib>
22 #include <iostream>
23 #include <iterator>
24 #include <string>
25 #include <vector>
26 
27 #include "android-base/parsebool.h"
28 #include "android-base/stringprintf.h"
29 #include "android-base/strings.h"
30 #include "arch/instruction_set.h"
31 #include "base/file_utils.h"
32 #include "base/globals.h"
33 #include "base/macros.h"
34 #include "base/os.h"
35 #include "base/testing.h"
36 
37 namespace art {
38 namespace gbi {
39 
40 namespace {
41 
42 using ::android::base::ConsumePrefix;
43 using ::android::base::ConsumeSuffix;
44 using ::android::base::Join;
45 using ::android::base::ParseBool;
46 using ::android::base::ParseBoolResult;
47 using ::android::base::StringPrintf;
48 using ::art::testing::GetLibCoreDexFileNames;
49 
50 constexpr const char* kUsage = R"(
51 A commandline tool to generate a primary boot image for testing.
52 
53 Usage: generate-boot-image --output-dir=OUTPUT_DIR [OPTIONS]... [-- [DEX2OAT_OPTIONS]...]
54 
55 Supported options:
56   --help: Print this text.
57   --output-dir=OUTPUT_DIR: The directory to output the boot image. Required.
58   --compiler-filter=COMPILER_FILTER: The compiler filter option to pass to dex2oat. Default: verify
59   --use-profile=true|false: If true, use a profile. Default: true
60   --dex2oat-bin=DEX2OAT_BIN: The path to the dex2oat binary. Required when running on host. Default
61       on target: /apex/com.android.art/bin/dex2oat{32,64,32d,64d}
62   --android-root=ANDROID_ROOT: The root directory to search for bootclasspath jars. The file
63       structure under the root must be in the form of:
64       /apex
65         /com.android.art
66           /javalib
67             /core-oj.jar
68             ...
69         /com.android.i18n
70           /javalib
71             ...
72         /com.android.conscrypt
73           /javalib
74             ...
75       Required when running on host. Default on target: /
76   --profile-file=PROFILE_FILE: The path to the profile file. Required when running on host and
77       --use-profile is true. Default on target: /apex/com.android.art/etc/boot-image.prof
78   --instruction-set=ISA: The instruction set option to pass to dex2oat. Required when running on
79       host. The default on target is based on the ISA of this binary.
80   --core-only=true|false: If true, only compile ART jars. Otherwise, also compile core-icu4j and
81       conscrypt. Default: false
82   --android-root-for-location=true|false: If true, use --android-root as a prefix to the dex
83       locations. This allows non-device paths to the bootclasspath jars to be used, for example: to
84       generate a boot image on host that can be used on host. Default: false
85   --: Arguments following '--' are directly passed to dex2oat.
86 )";
87 
88 struct Options {
89   std::string output_dir = "";
90   // Set the compiler filter to `verify` by default to make test preparation
91   // faster.
92   std::string compiler_filter = "verify";
93   bool use_profile = true;
94   std::string dex2oat_bin = "";
95   std::string android_root = "";
96   std::string profile_file = "";
97   std::string instruction_set = "";
98   bool core_only = false;
99   bool android_root_for_location = false;
100   std::vector<std::string> dex2oat_options;
101 };
102 
Usage(const std::string & message)103 [[noreturn]] void Usage(const std::string& message) {
104   LOG(ERROR) << message << '\n';
105   std::cerr << message << "\n" << kUsage << "\n";
106   exit(EX_USAGE);
107 }
108 
GetCompilerExecutable()109 std::string GetCompilerExecutable() {
110   std::string compiler_executable = GetArtBinDir() + "/dex2oat";
111   if (kIsDebugBuild) {
112     compiler_executable += 'd';
113   }
114   compiler_executable += Is64BitInstructionSet(kRuntimeISA) ? "64" : "32";
115   return compiler_executable;
116 }
117 
118 // Joins a list of commandline args into a single string, where each part is quoted with double
119 // quotes. Note that this is a naive implementation that does NOT escape existing double quotes,
120 // which is fine since we don't have existing double quotes in the args in this particular use case
121 // and this code is never used in production.
BuildCommand(const std::vector<std::string> & args)122 std::string BuildCommand(const std::vector<std::string>& args) {
123   std::string command = "";
124   for (const std::string& arg : args) {
125     if (!command.empty()) {
126       command += " ";
127     }
128     command += '"' + arg + '"';
129   }
130   return command;
131 }
132 
GenerateBootImage(const Options & options)133 int GenerateBootImage(const Options& options) {
134   std::vector<std::string> args;
135   args.push_back(options.dex2oat_bin);
136 
137   std::vector<std::string> dex_files =
138       GetLibCoreDexFileNames(options.android_root, options.core_only);
139   std::vector<std::string> dex_locations;
140   if (options.android_root_for_location) {
141     dex_locations = dex_files;
142   } else {
143     dex_locations = GetLibCoreDexFileNames(/*prefix=*/"", options.core_only);
144   }
145   args.push_back("--runtime-arg");
146   args.push_back("-Xbootclasspath:" + Join(dex_files, ":"));
147   args.push_back("--runtime-arg");
148   args.push_back("-Xbootclasspath-locations:" + Join(dex_locations, ":"));
149   for (const std::string& file : dex_files) {
150     args.push_back("--dex-file=" + file);
151   }
152   for (const std::string& location : dex_locations) {
153     args.push_back("--dex-location=" + location);
154   }
155 
156   args.push_back("--instruction-set=" + options.instruction_set);
157   args.push_back(StringPrintf("--base=0x%08x", ART_BASE_ADDRESS));
158   args.push_back("--compiler-filter=" + options.compiler_filter);
159   if (options.use_profile) {
160     args.push_back("--profile-file=" + options.profile_file);
161   }
162   args.push_back("--avoid-storing-invocation");
163   args.push_back("--generate-debug-info");
164   args.push_back("--generate-build-id");
165   args.push_back("--image-format=lz4hc");
166   args.push_back("--strip");
167   args.push_back("--android-root=out/empty");
168 
169   std::string path = ART_FORMAT("{}/{}", options.output_dir, options.instruction_set);
170   if (!OS::DirectoryExists(path.c_str())) {
171     CHECK_EQ(mkdir(path.c_str(), S_IRWXU), 0);
172   }
173   args.push_back(StringPrintf("--image=%s/boot.art", path.c_str()));
174   args.push_back(StringPrintf("--oat-file=%s/boot.oat", path.c_str()));
175 
176   std::move(
177       options.dex2oat_options.begin(), options.dex2oat_options.end(), std::back_inserter(args));
178 
179   int exit_code = system(BuildCommand(args).c_str());
180   if (exit_code != 0) {
181     LOG(ERROR) << "dex2oat invocation failed. Exit code: " << exit_code;
182   }
183   return exit_code;
184 }
185 
Main(int argc,char ** argv)186 int Main(int argc, char** argv) {
187   android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
188 
189   Options options;
190   for (int i = 1; i < argc; i++) {
191     std::string_view arg{argv[i]};
192     if (arg == "--help") {
193       std::cerr << kUsage << "\n";
194       exit(0);
195     } else if (ConsumePrefix(&arg, "--output-dir=")) {
196       options.output_dir = arg;
197     } else if (ConsumePrefix(&arg, "--compiler-filter=")) {
198       options.compiler_filter = arg;
199     } else if (ConsumePrefix(&arg, "--use-profile=")) {
200       ParseBoolResult result = ParseBool(arg);
201       if (result == ParseBoolResult::kError) {
202         Usage(ART_FORMAT("Unrecognized --use-profile value: '{}'", arg));
203       }
204       options.use_profile = result == ParseBoolResult::kTrue;
205     } else if (ConsumePrefix(&arg, "--dex2oat-bin=")) {
206       options.dex2oat_bin = arg;
207     } else if (ConsumePrefix(&arg, "--android-root=")) {
208       ConsumeSuffix(&arg, "/");
209       options.android_root = arg;
210     } else if (ConsumePrefix(&arg, "--profile-file=")) {
211       options.profile_file = arg;
212     } else if (ConsumePrefix(&arg, "--instruction-set=")) {
213       options.instruction_set = arg;
214     } else if (ConsumePrefix(&arg, "--core-only=")) {
215       ParseBoolResult result = ParseBool(arg);
216       if (result == ParseBoolResult::kError) {
217         Usage(ART_FORMAT("Unrecognized --core-only value: '{}'", arg));
218       }
219       options.core_only = result == ParseBoolResult::kTrue;
220     } else if (ConsumePrefix(&arg, "--android-root-for-location=")) {
221       ParseBoolResult result = ParseBool(arg);
222       if (result == ParseBoolResult::kError) {
223         Usage(ART_FORMAT("Unrecognized --android-root-for-location value: '{}'", arg));
224       }
225       options.android_root_for_location = result == ParseBoolResult::kTrue;
226     } else if (arg == "--") {
227       for (i++; i < argc; i++) {
228         options.dex2oat_options.push_back(argv[i]);
229       }
230     } else {
231       Usage(ART_FORMAT("Unrecognized argument: '{}'", argv[i]));
232     }
233   }
234 
235   if (options.output_dir.empty()) {
236     Usage("--output-dir must be specified");
237   }
238 
239   if (options.dex2oat_bin.empty()) {
240     if (kIsTargetBuild) {
241       options.dex2oat_bin = GetCompilerExecutable();
242     } else {
243       Usage("--dex2oat-bin must be specified when running on host");
244     }
245   }
246 
247   if (options.android_root.empty()) {
248     if (!kIsTargetBuild) {
249       Usage("--android-root must be specified when running on host");
250     }
251   }
252 
253   if (options.use_profile && options.profile_file.empty()) {
254     if (kIsTargetBuild) {
255       options.profile_file = ART_FORMAT("{}/etc/boot-image.prof", GetArtRoot());
256     } else {
257       Usage("--profile-file must be specified when running on host and --use-profile is true");
258     }
259   }
260 
261   if (options.instruction_set.empty()) {
262     if (kIsTargetBuild) {
263       options.instruction_set = GetInstructionSetString(kRuntimeISA);
264     } else {
265       Usage("--instruction-set must be specified when running on host");
266     }
267   }
268 
269   return GenerateBootImage(options);
270 }
271 
272 }  // namespace
273 
274 }  // namespace gbi
275 }  // namespace art
276 
main(int argc,char ** argv)277 int main(int argc, char** argv) { return art::gbi::Main(argc, argv); }
278