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