Fall back to compiling the primary boot image on device.
After this change, when odrefresh fails to compile the full boot image,
it will fall back to compile a minimal boot image named
"boot_minimal.art", which is generated from BCP jars in the ART module.
Bug: 214954206
Test: atest art_standalone_odrefresh_tests
Test: atest odsign_e2e_tests
Change-Id: I7d1ccdee4990210a96c0acbcaff0c492dc6019c5
diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc
index a62d6de..139d904 100644
--- a/libartbase/base/file_utils.cc
+++ b/libartbase/base/file_utils.cc
@@ -290,14 +290,18 @@
std::string GetDefaultBootImageLocation(const std::string& android_root,
bool deny_art_apex_data_files) {
constexpr static const char* kEtcBootImageProf = "etc/boot-image.prof";
+ constexpr static const char* kBootImageStem = "boot";
+ constexpr static const char* kMinimalBootImageStem = "boot_minimal";
// If an update for the ART module has been been installed, a single boot image for the entire
// bootclasspath is in the ART APEX data directory.
if (kIsTargetBuild && !deny_art_apex_data_files) {
const std::string boot_image =
- GetApexDataDalvikCacheDirectory(InstructionSet::kNone) + "/boot.art";
+ GetApexDataDalvikCacheDirectory(InstructionSet::kNone) + "/" + kBootImageStem + ".art";
const std::string boot_image_filename = GetSystemImageFilename(boot_image.c_str(), kRuntimeISA);
if (OS::FileExists(boot_image_filename.c_str(), /*check_file_type=*/true)) {
+ // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot.art!/apex/com.android.art
+ // /etc/boot-image.prof!/system/etc/boot-image.prof".
return StringPrintf("%s!%s/%s!%s/%s",
boot_image.c_str(),
kAndroidArtApexDefaultPath,
@@ -308,15 +312,42 @@
// Additional warning for potential SELinux misconfiguration.
PLOG(ERROR) << "Default boot image check failed, could not stat: " << boot_image_filename;
}
+
+ // odrefresh can generate a minimal boot image, which only includes code from BCP jars in the
+ // ART module, when it fails to generate a single boot image for the entire bootclasspath (i.e.,
+ // full boot image). Use it if it exists.
+ const std::string minimal_boot_image = GetApexDataDalvikCacheDirectory(InstructionSet::kNone) +
+ "/" + kMinimalBootImageStem + ".art";
+ const std::string minimal_boot_image_filename =
+ GetSystemImageFilename(minimal_boot_image.c_str(), kRuntimeISA);
+ if (OS::FileExists(minimal_boot_image_filename.c_str(), /*check_file_type=*/true)) {
+ // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot_minimal.art!/apex
+ // /com.android.art/etc/boot-image.prof:/nonx/boot_minimal-framework.art!/system/etc
+ // /boot-image.prof".
+ return StringPrintf("%s!%s/%s:/nonx/%s-framework.art!%s/%s",
+ minimal_boot_image.c_str(),
+ kAndroidArtApexDefaultPath,
+ kEtcBootImageProf,
+ kMinimalBootImageStem,
+ android_root.c_str(),
+ kEtcBootImageProf);
+ } else if (errno == EACCES) {
+ // Additional warning for potential SELinux misconfiguration.
+ PLOG(ERROR) << "Minimal boot image check failed, could not stat: " << boot_image_filename;
+ }
}
// Boot image consists of two parts:
// - the primary boot image (contains the Core Libraries)
// - the boot image extensions (contains framework libraries)
- return StringPrintf("%s/boot.art!%s/%s:%s/framework/boot-framework.art!%s/%s",
+ // Typically "/apex/com.android.art/javalib/boot.art!/apex/com.android.art/etc/boot-image.prof:
+ // /system/framework/boot-framework.art!/system/etc/boot-image.prof".
+ return StringPrintf("%s/%s.art!%s/%s:%s/framework/%s-framework.art!%s/%s",
GetPrebuiltPrimaryBootImageDir().c_str(),
+ kBootImageStem,
kAndroidArtApexDefaultPath,
kEtcBootImageProf,
android_root.c_str(),
+ kBootImageStem,
android_root.c_str(),
kEtcBootImageProf);
}
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
index b44e4e2..d95ab96 100644
--- a/odrefresh/odr_config.h
+++ b/odrefresh/odr_config.h
@@ -64,6 +64,7 @@
std::string artifact_dir_;
std::string standalone_system_server_jars_;
bool compilation_os_mode_ = false;
+ bool minimal_ = false;
// Staging directory for artifacts. The directory must exist and will be automatically removed
// after compilation. If empty, use the default directory.
@@ -143,6 +144,7 @@
return staging_dir_;
}
bool GetCompilationOsMode() const { return compilation_os_mode_; }
+ bool GetMinimal() const { return minimal_; }
void SetApexInfoListFile(const std::string& file_path) { apex_info_list_file_ = file_path; }
void SetArtBinDir(const std::string& art_bin_dir) { art_bin_dir_ = art_bin_dir; }
@@ -192,6 +194,8 @@
void SetCompilationOsMode(bool value) { compilation_os_mode_ = value; }
+ void SetMinimal(bool value) { minimal_ = value; }
+
private:
// Returns a pair for the possible instruction sets for the configured instruction set
// architecture. The first item is the 32-bit architecture and the second item is the 64-bit
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index f0d3f19..2455772 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -109,6 +109,7 @@
constexpr mode_t kFileMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
constexpr const char* kFirstBootImageBasename = "boot.art";
+constexpr const char* kMinimalBootImageBasename = "boot_minimal.art";
void EraseFiles(const std::vector<std::unique_ptr<File>>& files) {
for (auto& file : files) {
@@ -728,19 +729,23 @@
});
}
-std::string OnDeviceRefresh::GetBootImage(bool on_system) const {
+std::string OnDeviceRefresh::GetBootImage(bool on_system, bool minimal) const {
+ DCHECK(!on_system || !minimal);
+ const char* basename = minimal ? kMinimalBootImageBasename : kFirstBootImageBasename;
if (on_system) {
// Typically "/system/framework/boot.art".
- return GetPrebuiltPrimaryBootImageDir() + "/" + kFirstBootImageBasename;
+ return GetPrebuiltPrimaryBootImageDir() + "/" + basename;
} else {
// Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot.art".
- return config_.GetArtifactDirectory() + "/" + kFirstBootImageBasename;
+ return config_.GetArtifactDirectory() + "/" + basename;
}
}
-std::string OnDeviceRefresh::GetBootImagePath(bool on_system, const InstructionSet isa) const {
+std::string OnDeviceRefresh::GetBootImagePath(bool on_system,
+ bool minimal,
+ const InstructionSet isa) const {
// Typically "/data/misc/apexdata/com.android.art/dalvik-cache/<isa>/boot.art".
- return GetSystemImageFilename(GetBootImage(on_system).c_str(), isa);
+ return GetSystemImageFilename(GetBootImage(on_system, minimal).c_str(), isa);
}
std::string OnDeviceRefresh::GetSystemBootImageExtension() const {
@@ -790,10 +795,11 @@
WARN_UNUSED bool OnDeviceRefresh::BootClasspathArtifactsExist(
bool on_system,
+ bool minimal,
const InstructionSet isa,
/*out*/ std::string* error_msg,
/*out*/ std::vector<std::string>* checked_artifacts) const {
- std::string path = GetBootImagePath(on_system, isa);
+ std::string path = GetBootImagePath(on_system, minimal, isa);
OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path);
if (!ArtifactsExist(artifacts, /*check_art_file=*/true, error_msg, checked_artifacts)) {
return false;
@@ -841,7 +847,7 @@
// ART is not updated, so we can use the artifacts on /system. Check if they exist.
std::string error_msg;
- if (BootClasspathArtifactsExist(/*on_system=*/true, isa, &error_msg)) {
+ if (BootClasspathArtifactsExist(/*on_system=*/true, /*minimal=*/false, isa, &error_msg)) {
return true;
}
@@ -924,9 +930,17 @@
// Cache info looks good, check all compilation artifacts exist.
std::string error_msg;
- if (!BootClasspathArtifactsExist(/*on_system=*/false, isa, &error_msg, checked_artifacts)) {
+ if (!BootClasspathArtifactsExist(
+ /*on_system=*/false, /*minimal=*/false, isa, &error_msg, checked_artifacts)) {
LOG(INFO) << "Incomplete boot classpath artifacts. " << error_msg;
metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
+ // Add the minimal boot image to `checked_artifacts` if exists. This is to prevent the minimal
+ // boot image from being deleted. It does not affect the return value because we should still
+ // attempt to generate a full boot image even if the minimal one exists.
+ if (BootClasspathArtifactsExist(
+ /*on_system=*/false, /*minimal=*/true, isa, &error_msg, checked_artifacts)) {
+ LOG(INFO) << "Found minimal boot classpath artifacts.";
+ }
return false;
}
@@ -1301,6 +1315,7 @@
const std::string& staging_dir,
OdrMetrics& metrics,
const std::function<void()>& on_dex2oat_success,
+ bool minimal,
std::string* error_msg) const {
ScopedOdrCompilationTimer compilation_timer(metrics);
std::vector<std::string> args;
@@ -1334,7 +1349,16 @@
}
// Add boot classpath jars to compile.
- for (const std::string& component : boot_classpath_compilable_jars_) {
+ std::vector<std::string> jars_to_compile = boot_classpath_compilable_jars_;
+ if (minimal) {
+ auto end =
+ std::remove_if(jars_to_compile.begin(), jars_to_compile.end(), [](const std::string& jar) {
+ return !android::base::StartsWith(jar, GetArtRoot());
+ });
+ jars_to_compile.erase(end, jars_to_compile.end());
+ }
+
+ for (const std::string& component : jars_to_compile) {
std::string actual_path = AndroidRootRewrite(component);
args.emplace_back("--dex-file=" + component);
std::unique_ptr<File> file(OS::OpenFileForReading(actual_path.c_str()));
@@ -1343,13 +1367,12 @@
}
args.emplace_back("--runtime-arg");
- args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetDex2oatBootClasspath()}));
- auto bcp_jars = android::base::Split(config_.GetDex2oatBootClasspath(), ":");
- if (!AddBootClasspathFds(args, readonly_files_raii, bcp_jars)) {
+ args.emplace_back(Concatenate({"-Xbootclasspath:", android::base::Join(jars_to_compile, ":")}));
+ if (!AddBootClasspathFds(args, readonly_files_raii, jars_to_compile)) {
return false;
}
- const std::string image_location = GetBootImagePath(/*on_system=*/false, isa);
+ const std::string image_location = GetBootImagePath(/*on_system=*/false, minimal, isa);
const OdrArtifacts artifacts = OdrArtifacts::ForBootImage(image_location);
args.emplace_back("--oat-location=" + artifacts.OatPath());
@@ -1388,8 +1411,11 @@
const time_t timeout = GetSubprocessTimeout();
const std::string cmd_line = android::base::Join(args, ' ');
- LOG(INFO) << "Compiling boot classpath (" << isa << "): " << cmd_line << " [timeout " << timeout
- << "s]";
+ LOG(INFO) << android::base::StringPrintf("Compiling boot classpath (%s%s): %s [timeout %lds]",
+ GetInstructionSetString(isa),
+ minimal ? ", minimal" : "",
+ cmd_line.c_str(),
+ timeout);
if (config_.GetDryRun()) {
LOG(INFO) << "Compilation skipped (dry-run).";
return true;
@@ -1506,17 +1532,19 @@
std::string unused_error_msg;
// If the boot classpath artifacts are not on /data, then the boot classpath are not re-compiled
// and the artifacts must exist on /system.
- bool boot_image_on_system =
- !BootClasspathArtifactsExist(/*on_system=*/false, isa, &unused_error_msg);
+ bool boot_image_on_system = !BootClasspathArtifactsExist(
+ /*on_system=*/false, /*minimal=*/false, isa, &unused_error_msg);
AddCompiledBootClasspathFdsIfAny(
args,
readonly_files_raii,
bcp_jars,
isa,
boot_image_on_system ? GetSystemBootImageDir() : config_.GetArtifactDirectory());
- args.emplace_back(Concatenate({"--boot-image=", boot_image_on_system
- ? GetBootImage(/*on_system=*/true) + ":" + GetSystemBootImageExtension()
- : GetBootImage(/*on_system=*/false)}));
+ args.emplace_back(
+ Concatenate({"--boot-image=",
+ boot_image_on_system ? GetBootImage(/*on_system=*/true, /*minimal=*/false) +
+ ":" + GetSystemBootImageExtension() :
+ GetBootImage(/*on_system=*/false, /*minimal=*/false)}));
const std::string context_path = android::base::Join(classloader_context, ':');
if (art::ContainsElement(systemserver_classpath_jars_, jar)) {
@@ -1616,25 +1644,63 @@
const auto& bcp_instruction_sets = config_.GetBootClasspathIsas();
DCHECK(!bcp_instruction_sets.empty() && bcp_instruction_sets.size() <= 2);
+ bool full_compilation_failed = false;
for (const InstructionSet isa : compilation_options.compile_boot_classpath_for_isas) {
auto stage = (isa == bcp_instruction_sets.front()) ? OdrMetrics::Stage::kPrimaryBootClasspath :
OdrMetrics::Stage::kSecondaryBootClasspath;
metrics.SetStage(stage);
- if (!CheckCompilationSpace()) {
- metrics.SetStatus(OdrMetrics::Status::kNoSpace);
- // Return kCompilationFailed so odsign will keep and sign whatever we have been able to
- // compile.
- return ExitCode::kCompilationFailed;
+ if (!config_.GetMinimal()) {
+ if (CheckCompilationSpace()) {
+ if (CompileBootClasspathArtifacts(isa,
+ staging_dir,
+ metrics,
+ advance_animation_progress,
+ /*minimal=*/false,
+ &error_msg)) {
+ // Remove the minimal boot image only if the full boot image is successfully generated.
+ std::string path = GetBootImagePath(/*on_system=*/false, /*minimal=*/true, isa);
+ OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path);
+ unlink(artifacts.ImagePath().c_str());
+ unlink(artifacts.OatPath().c_str());
+ unlink(artifacts.VdexPath().c_str());
+ continue;
+ }
+ LOG(ERROR) << "Compilation of BCP failed: " << error_msg;
+ } else {
+ metrics.SetStatus(OdrMetrics::Status::kNoSpace);
+ }
}
- if (!CompileBootClasspathArtifacts(
- isa, staging_dir, metrics, advance_animation_progress, &error_msg)) {
- LOG(ERROR) << "Compilation of BCP failed: " << error_msg;
- if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) {
- return ExitCode::kCleanupFailed;
- }
- return ExitCode::kCompilationFailed;
+ // Fall back to generating a minimal boot image.
+ // The compilation of the full boot image will be retried on later reboots with a backoff time,
+ // and the minimal boot image will be removed once the compilation of the full boot image
+ // succeeds.
+ full_compilation_failed = true;
+ std::string ignored_error_msg;
+ if (BootClasspathArtifactsExist(
+ /*on_system=*/false, /*minimal=*/true, isa, &ignored_error_msg)) {
+ continue;
}
+ if (CompileBootClasspathArtifacts(isa,
+ staging_dir,
+ metrics,
+ advance_animation_progress,
+ /*minimal=*/true,
+ &error_msg)) {
+ continue;
+ }
+ LOG(ERROR) << "Compilation of minimal BCP failed: " << error_msg;
+ if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) {
+ return ExitCode::kCleanupFailed;
+ }
+ return ExitCode::kCompilationFailed;
+ }
+
+ if (full_compilation_failed) {
+ if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) {
+ return ExitCode::kCleanupFailed;
+ }
+ return ExitCode::kCompilationFailed;
}
if (!compilation_options.system_server_jars_to_compile.empty()) {
diff --git a/odrefresh/odrefresh.h b/odrefresh/odrefresh.h
index f8bfefb..4558344 100644
--- a/odrefresh/odrefresh.h
+++ b/odrefresh/odrefresh.h
@@ -92,11 +92,13 @@
std::vector<com::android::art::SystemServerComponent> GenerateSystemServerComponents() const;
- // Returns the symbolic boot image location (without ISA).
- std::string GetBootImage(bool on_system) const;
+ // Returns the symbolic boot image location (without ISA). If `minimal` is true, returns the
+ // symbolic location of the minimal boot image.
+ std::string GetBootImage(bool on_system, bool minimal) const;
- // Returns the real boot image location (with ISA).
- std::string GetBootImagePath(bool on_system, const InstructionSet isa) const;
+ // Returns the real boot image location (with ISA). If `minimal` is true, returns the
+ // symbolic location of the minimal boot image.
+ std::string GetBootImagePath(bool on_system, bool minimal, const InstructionSet isa) const;
// Returns the symbolic boot image extension location (without ISA). Note that this only applies
// to boot images on /system.
@@ -119,9 +121,11 @@
// Checks whether all boot classpath artifacts are present. Returns true if all are present, false
// otherwise.
+ // If `minimal` is true, checks the minimal boot image.
// If `checked_artifacts` is present, adds checked artifacts to `checked_artifacts`.
WARN_UNUSED bool BootClasspathArtifactsExist(
bool on_system,
+ bool minimal,
const InstructionSet isa,
/*out*/ std::string* error_msg,
/*out*/ std::vector<std::string>* checked_artifacts = nullptr) const;
@@ -157,10 +161,12 @@
/*out*/ std::set<std::string>* jars_to_compile,
/*out*/ std::vector<std::string>* checked_artifacts) const;
+ // Compiles boot classpath. If `minimal` is true, only compiles the jars in the ART module.
WARN_UNUSED bool CompileBootClasspathArtifacts(const InstructionSet isa,
const std::string& staging_dir,
OdrMetrics& metrics,
const std::function<void()>& on_dex2oat_success,
+ bool minimal,
std::string* error_msg) const;
WARN_UNUSED bool CompileSystemServerArtifacts(
diff --git a/odrefresh/odrefresh_main.cc b/odrefresh/odrefresh_main.cc
index efcae2c..6dbe372 100644
--- a/odrefresh/odrefresh_main.cc
+++ b/odrefresh/odrefresh_main.cc
@@ -145,6 +145,8 @@
config->SetPartialCompilation(true);
} else if (ArgumentEquals(arg, "--no-refresh")) {
config->SetRefresh(false);
+ } else if (ArgumentEquals(arg, "--minimal")) {
+ config->SetMinimal(true);
} else {
ArgumentError("Unrecognized argument: '%s'", arg);
}
@@ -198,6 +200,7 @@
UsageMsg("--system-server-compiler-filter=<STRING>");
UsageMsg(" Compiler filter that overrides");
UsageMsg(" dalvik.vm.systemservercompilerfilter");
+ UsageMsg("--minimal Generate a minimal boot image only.");
exit(EX_USAGE);
}
diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
index 35c4c2d..ae7cc78 100644
--- a/odrefresh/odrefresh_test.cc
+++ b/odrefresh/odrefresh_test.cc
@@ -50,6 +50,7 @@
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::HasSubstr;
+using ::testing::Not;
using ::testing::Return;
constexpr int kReplace = 1;
@@ -268,6 +269,32 @@
ExitCode::kCompilationSuccess);
}
+TEST_F(OdRefreshTest, BootClasspathJarsFallback) {
+ // Simulate the case where dex2oat fails when generating the full boot image.
+ EXPECT_CALL(*mock_exec_utils_,
+ DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", core_oj_jar_})),
+ Contains(Concatenate({"--dex-file=", framework_jar_})))))
+ .Times(2)
+ .WillRepeatedly(Return(1));
+
+ // It should fall back to generating a minimal boot image.
+ EXPECT_CALL(
+ *mock_exec_utils_,
+ DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", core_oj_jar_})),
+ Not(Contains(Concatenate({"--dex-file=", framework_jar_}))))))
+ .Times(2)
+ .WillOnce(Return(0));
+
+ EXPECT_EQ(
+ odrefresh_->Compile(
+ *metrics_,
+ CompilationOptions{
+ .compile_boot_classpath_for_isas = {InstructionSet::kX86, InstructionSet::kX86_64},
+ .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
+ }),
+ ExitCode::kCompilationFailed);
+}
+
TEST_F(OdRefreshTest, AllSystemServerJars) {
EXPECT_CALL(*mock_exec_utils_,
DoExecAndReturnCode(AllOf(
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index fc2aa00..9302ca2 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -52,6 +52,7 @@
#include "gc/accounting/space_bitmap-inl.h"
#include "gc/task_processor.h"
#include "image-inl.h"
+#include "image.h"
#include "intern_table-inl.h"
#include "mirror/class-inl.h"
#include "mirror/executable-inl.h"
@@ -2890,11 +2891,12 @@
size_t num_spaces = spaces.size();
const ImageHeader& primary_header = spaces.front()->GetImageHeader();
size_t primary_image_count = primary_header.GetImageSpaceCount();
+ size_t primary_image_component_count = primary_header.GetComponentCount();
DCHECK_LE(primary_image_count, num_spaces);
// The primary boot image can be generated with `--single-image` on device, when generated
// in-memory or with odrefresh.
- DCHECK(primary_image_count == primary_header.GetComponentCount() || primary_image_count == 1);
- size_t component_count = primary_image_count;
+ DCHECK(primary_image_count == primary_image_component_count || primary_image_count == 1);
+ size_t component_count = primary_image_component_count;
size_t space_pos = primary_image_count;
while (space_pos != num_spaces) {
const ImageHeader& current_header = spaces[space_pos]->GetImageHeader();
@@ -2905,7 +2907,7 @@
if (dependency_component_count < component_count) {
// There shall be no duplicate strings with the components that this space depends on.
// Find the end of the dependencies, i.e. start of non-dependency images.
- size_t start_component_count = primary_image_count;
+ size_t start_component_count = primary_image_component_count;
size_t start_pos = primary_image_count;
while (start_component_count != dependency_component_count) {
const ImageHeader& dependency_header = spaces[start_pos]->GetImageHeader();
@@ -3172,6 +3174,7 @@
std::vector<std::string> filenames =
ExpandMultiImageLocations(requested_bcp_locations, chunk.base_filename, is_extension);
DCHECK_EQ(locations.size(), filenames.size());
+ size_t max_dependency_count = spaces->size();
for (size_t i = 0u, size = locations.size(); i != size; ++i) {
spaces->push_back(Load(locations[i],
filenames[i],
@@ -3217,9 +3220,23 @@
}
}
DCHECK_GE(max_image_space_dependencies, chunk.boot_image_component_count);
+ size_t dependency_count = 0;
+ size_t dependency_component_count = 0;
+ while (dependency_component_count < chunk.boot_image_component_count &&
+ dependency_count < max_dependency_count) {
+ const ImageHeader& current_header = (*spaces)[dependency_count]->GetImageHeader();
+ dependency_component_count += current_header.GetComponentCount();
+ dependency_count += current_header.GetImageSpaceCount();
+ }
+ if (dependency_component_count != chunk.boot_image_component_count) {
+ *error_msg = StringPrintf(
+ "Unable to find dependencies from image spaces; boot_image_component_count: %u",
+ chunk.boot_image_component_count);
+ return false;
+ }
ArrayRef<const std::unique_ptr<ImageSpace>> dependencies =
ArrayRef<const std::unique_ptr<ImageSpace>>(*spaces).SubArray(
- /*pos=*/ 0u, chunk.boot_image_component_count);
+ /*pos=*/ 0u, dependency_count);
for (size_t i = 0u, size = locations.size(); i != size; ++i) {
ImageSpace* space = (*spaces)[spaces->size() - chunk.image_space_count + i].get();
size_t bcp_chunk_size = (chunk.image_space_count == 1u) ? chunk.component_count : 1u;
@@ -3809,9 +3826,14 @@
return false;
}
const size_t num_image_spaces = image_spaces.size();
- if (num_image_spaces != oat_bcp_size) {
- *error_msg = StringPrintf("Image header records more dependencies (%zu) than BCP (%zu)",
- num_image_spaces,
+ size_t dependency_component_count = 0;
+ for (const std::unique_ptr<ImageSpace>& space : image_spaces) {
+ dependency_component_count += space->GetComponentCount();
+ }
+ if (dependency_component_count != oat_bcp_size) {
+ *error_msg = StringPrintf("Image header records %s dependencies (%zu) than BCP (%zu)",
+ dependency_component_count < oat_bcp_size ? "less" : "more",
+ dependency_component_count,
oat_bcp_size);
return false;
}
@@ -3842,7 +3864,7 @@
CHECK(!DexFileLoader::IsMultiDexLocation(main_location.c_str()));
size_t num_base_locations = 1u;
for (size_t i = 1u; i != num_dex_files; ++i) {
- if (DexFileLoader::IsMultiDexLocation(
+ if (!DexFileLoader::IsMultiDexLocation(
oat_file->GetOatDexFiles()[i]->GetDexFileLocation().c_str())) {
CHECK_EQ(image_space_count, 1u); // We can find base locations only for --single-image.
++num_base_locations;
diff --git a/test/odsign/test-src/com/android/tests/odsign/ActivationTest.java b/test/odsign/test-src/com/android/tests/odsign/ActivationTest.java
index 374e28b..ffcbc41 100644
--- a/test/odsign/test-src/com/android/tests/odsign/ActivationTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/ActivationTest.java
@@ -27,11 +27,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
/**
* Test to check if odrefresh, odsign, fs-verity, and ART runtime work together properly.
@@ -83,8 +80,8 @@
// artifacts compiled and signed by odrefresh and odsign. We check both here rather than
// having a separate test because the device reboots between each @Test method and
// that is an expensive use of time.
- verifyZygotesLoadedArtifacts();
- verifySystemServerLoadedArtifacts();
+ mTestUtils.verifyZygotesLoadedArtifacts("boot");
+ mTestUtils.verifySystemServerLoadedArtifacts();
}
@Test
@@ -106,85 +103,4 @@
mTestUtils.reboot();
verifyGeneratedArtifactsLoaded();
}
-
- private String[] getListFromEnvironmentVariable(String name) throws Exception {
- String systemServerClasspath = getDevice().executeShellCommand("echo $" + name).trim();
- if (!systemServerClasspath.isEmpty()) {
- return systemServerClasspath.split(":");
- }
- return new String[0];
- }
-
- private String getSystemServerIsa(String mappedArtifact) {
- // Artifact path for system server artifacts has the form:
- // ART_APEX_DALVIK_CACHE_DIRNAME + "/<arch>/system@framework@some.jar@classes.odex"
- String[] pathComponents = mappedArtifact.split("/");
- return pathComponents[pathComponents.length - 2];
- }
-
- private void verifySystemServerLoadedArtifacts() throws Exception {
- String[] classpathElements = getListFromEnvironmentVariable("SYSTEMSERVERCLASSPATH");
- assertTrue("SYSTEMSERVERCLASSPATH is empty", classpathElements.length > 0);
- String[] standaloneJars = getListFromEnvironmentVariable("STANDALONE_SYSTEMSERVER_JARS");
- String[] allSystemServerJars = Stream
- .concat(Arrays.stream(classpathElements), Arrays.stream(standaloneJars))
- .toArray(String[]::new);
-
- final Set<String> mappedArtifacts = mTestUtils.getSystemServerLoadedArtifacts();
- assertTrue(
- "No mapped artifacts under " + OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME,
- mappedArtifacts.size() > 0);
- final String isa = getSystemServerIsa(mappedArtifacts.iterator().next());
- final String isaCacheDirectory =
- String.format("%s/%s", OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME, isa);
-
- // Check components in the system_server classpath have mapped artifacts.
- for (String element : allSystemServerJars) {
- String escapedPath = element.substring(1).replace('/', '@');
- for (String extension : OdsignTestUtils.APP_ARTIFACT_EXTENSIONS) {
- final String fullArtifactPath =
- String.format("%s/%s@classes%s", isaCacheDirectory, escapedPath, extension);
- assertTrue("Missing " + fullArtifactPath, mappedArtifacts.contains(fullArtifactPath));
- }
- }
-
- for (String mappedArtifact : mappedArtifacts) {
- // Check the mapped artifact has a .art, .odex or .vdex extension.
- final boolean knownArtifactKind =
- OdsignTestUtils.APP_ARTIFACT_EXTENSIONS.stream().anyMatch(
- e -> mappedArtifact.endsWith(e));
- assertTrue("Unknown artifact kind: " + mappedArtifact, knownArtifactKind);
- }
- }
-
- private void verifyZygoteLoadedArtifacts(String zygoteName, Set<String> mappedArtifacts)
- throws Exception {
- final String bootImageStem = "boot";
-
- assertTrue("Expect 3 bootclasspath artifacts", mappedArtifacts.size() == 3);
-
- String allArtifacts = mappedArtifacts.stream().collect(Collectors.joining(","));
- for (String extension : OdsignTestUtils.BCP_ARTIFACT_EXTENSIONS) {
- final String artifact = bootImageStem + extension;
- final boolean found = mappedArtifacts.stream().anyMatch(a -> a.endsWith(artifact));
- assertTrue(zygoteName + " " + artifact + " not found: '" + allArtifacts + "'", found);
- }
- }
-
- private void verifyZygotesLoadedArtifacts() throws Exception {
- // There are potentially two zygote processes "zygote" and "zygote64". These are
- // instances 32-bit and 64-bit unspecialized app_process processes.
- // (frameworks/base/cmds/app_process).
- int zygoteCount = 0;
- for (String zygoteName : OdsignTestUtils.ZYGOTE_NAMES) {
- final Optional<Set<String>> mappedArtifacts =
- mTestUtils.getZygoteLoadedArtifacts(zygoteName);
- if (!mappedArtifacts.isPresent()) {
- continue;
- }
- verifyZygoteLoadedArtifacts(zygoteName, mappedArtifacts.get());
- zygoteCount += 1;
- }
- assertTrue("No zygote processes found", zygoteCount > 0);
- }
}
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
index c53cead..7708aaa 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
@@ -50,6 +50,8 @@
private static final String ODREFRESH_BIN = "odrefresh";
private static final String ODREFRESH_COMMAND =
ODREFRESH_BIN + " --partial-compilation --no-refresh --compile";
+ private static final String ODREFRESH_MINIMAL_COMMAND =
+ ODREFRESH_BIN + " --partial-compilation --no-refresh --minimal --compile";
private static final String TAG = "OdrefreshHostTest";
private static final String ZYGOTE_ARTIFACTS_KEY = TAG + ":ZYGOTE_ARTIFACTS";
@@ -205,6 +207,48 @@
assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
}
+ @Test
+ public void verifyMinimalCompilation() throws Exception {
+ mTestUtils.removeCompilationLogToAvoidBackoff();
+ getDevice().executeShellV2Command(
+ "rm -rf " + OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME);
+ getDevice().executeShellV2Command(ODREFRESH_MINIMAL_COMMAND);
+
+ mTestUtils.restartZygote();
+
+ // The minimal boot image should be loaded.
+ Set<String> minimalZygoteArtifacts =
+ mTestUtils.verifyZygotesLoadedArtifacts("boot_minimal");
+
+ // Running the command again should not overwrite the minimal boot image.
+ mTestUtils.removeCompilationLogToAvoidBackoff();
+ long timeMs = getCurrentTimeMs();
+ getDevice().executeShellV2Command(ODREFRESH_MINIMAL_COMMAND);
+
+ assertArtifactsNotModifiedAfter(minimalZygoteArtifacts, timeMs);
+
+ // `odrefresh --check` should keep the minimal boot image.
+ mTestUtils.removeCompilationLogToAvoidBackoff();
+ timeMs = getCurrentTimeMs();
+ getDevice().executeShellV2Command(ODREFRESH_BIN + " --check");
+
+ assertArtifactsNotModifiedAfter(minimalZygoteArtifacts, timeMs);
+
+ // A normal odrefresh invocation should replace the minimal boot image with a full one.
+ mTestUtils.removeCompilationLogToAvoidBackoff();
+ timeMs = getCurrentTimeMs();
+ getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+
+ for (String artifact : minimalZygoteArtifacts) {
+ assertFalse(
+ String.format(
+ "Artifact %s should be cleaned up while it still exists", artifact),
+ getDevice().doesFileExist(artifact));
+ }
+
+ assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
+ }
+
/**
* Checks the input line by line and replaces all lines that match the regex with the given
* replacement.
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
index edb9fa4..48644e7 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
@@ -29,10 +29,13 @@
import com.android.tradefed.util.CommandResult;
import java.time.Duration;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
public class OdsignTestUtils {
public static final String ART_APEX_DALVIK_CACHE_DIRNAME =
@@ -49,6 +52,7 @@
"/data/misc/odrefresh/compilation-log.txt";
private static final Duration BOOT_COMPLETE_TIMEOUT = Duration.ofMinutes(2);
+ private static final Duration RESTART_ZYGOTE_COMPLETE_TIMEOUT = Duration.ofMinutes(1);
private static final String TAG = "OdsignTestUtils";
private static final String WAS_ADB_ROOT_KEY = TAG + ":WAS_ADB_ROOT";
@@ -129,6 +133,72 @@
return getMappedArtifacts(systemServerPid, grepPattern);
}
+ public void verifyZygoteLoadedArtifacts(String zygoteName, Set<String> mappedArtifacts,
+ String bootImageStem) throws Exception {
+ assertTrue("Expect 3 bootclasspath artifacts", mappedArtifacts.size() == 3);
+
+ String allArtifacts = mappedArtifacts.stream().collect(Collectors.joining(","));
+ for (String extension : BCP_ARTIFACT_EXTENSIONS) {
+ final String artifact = bootImageStem + extension;
+ final boolean found = mappedArtifacts.stream().anyMatch(a -> a.endsWith(artifact));
+ assertTrue(zygoteName + " " + artifact + " not found: '" + allArtifacts + "'", found);
+ }
+ }
+
+ // Verifies that boot image files with the given stem are loaded by Zygote for each instruction
+ // set. Returns the verified files.
+ public HashSet<String> verifyZygotesLoadedArtifacts(String bootImageStem) throws Exception {
+ // There are potentially two zygote processes "zygote" and "zygote64". These are
+ // instances 32-bit and 64-bit unspecialized app_process processes.
+ // (frameworks/base/cmds/app_process).
+ int zygoteCount = 0;
+ HashSet<String> verifiedArtifacts = new HashSet<>();
+ for (String zygoteName : ZYGOTE_NAMES) {
+ final Optional<Set<String>> mappedArtifacts = getZygoteLoadedArtifacts(zygoteName);
+ if (!mappedArtifacts.isPresent()) {
+ continue;
+ }
+ verifyZygoteLoadedArtifacts(zygoteName, mappedArtifacts.get(), bootImageStem);
+ zygoteCount += 1;
+ verifiedArtifacts.addAll(mappedArtifacts.get());
+ }
+ assertTrue("No zygote processes found", zygoteCount > 0);
+ return verifiedArtifacts;
+ }
+
+ public void verifySystemServerLoadedArtifacts() throws Exception {
+ String[] classpathElements = getListFromEnvironmentVariable("SYSTEMSERVERCLASSPATH");
+ assertTrue("SYSTEMSERVERCLASSPATH is empty", classpathElements.length > 0);
+ String[] standaloneJars = getListFromEnvironmentVariable("STANDALONE_SYSTEMSERVER_JARS");
+ String[] allSystemServerJars = Stream
+ .concat(Arrays.stream(classpathElements), Arrays.stream(standaloneJars))
+ .toArray(String[]::new);
+
+ final Set<String> mappedArtifacts = getSystemServerLoadedArtifacts();
+ assertTrue(
+ "No mapped artifacts under " + ART_APEX_DALVIK_CACHE_DIRNAME,
+ mappedArtifacts.size() > 0);
+ final String isa = getSystemServerIsa(mappedArtifacts.iterator().next());
+ final String isaCacheDirectory = String.format("%s/%s", ART_APEX_DALVIK_CACHE_DIRNAME, isa);
+
+ // Check components in the system_server classpath have mapped artifacts.
+ for (String element : allSystemServerJars) {
+ String escapedPath = element.substring(1).replace('/', '@');
+ for (String extension : APP_ARTIFACT_EXTENSIONS) {
+ final String fullArtifactPath =
+ String.format("%s/%s@classes%s", isaCacheDirectory, escapedPath, extension);
+ assertTrue("Missing " + fullArtifactPath, mappedArtifacts.contains(fullArtifactPath));
+ }
+ }
+
+ for (String mappedArtifact : mappedArtifacts) {
+ // Check the mapped artifact has a .art, .odex or .vdex extension.
+ final boolean knownArtifactKind =
+ APP_ARTIFACT_EXTENSIONS.stream().anyMatch(e -> mappedArtifact.endsWith(e));
+ assertTrue("Unknown artifact kind: " + mappedArtifact, knownArtifactKind);
+ }
+ }
+
public boolean haveCompilationLog() throws Exception {
CommandResult result =
mTestInfo.getDevice().executeShellV2Command("stat " + ODREFRESH_COMPILATION_LOG);
@@ -146,6 +216,16 @@
assertWithMessage("Device didn't boot in %s", BOOT_COMPLETE_TIMEOUT).that(success).isTrue();
}
+ public void restartZygote() throws Exception {
+ // `waitForBootComplete` relies on `dev.bootcomplete`.
+ mTestInfo.getDevice().executeShellCommand("setprop dev.bootcomplete 0");
+ mTestInfo.getDevice().executeShellCommand("setprop ctl.restart zygote");
+ boolean success = mTestInfo.getDevice()
+ .waitForBootComplete(RESTART_ZYGOTE_COMPLETE_TIMEOUT.toMillis());
+ assertWithMessage("Zygote didn't start in %s", BOOT_COMPLETE_TIMEOUT).that(success)
+ .isTrue();
+ }
+
/**
* Enables adb root or skips the test if adb root is not supported.
*/
@@ -179,4 +259,20 @@
private void setBoolean(String key, boolean value) {
mTestInfo.properties().put(key, Boolean.toString(value));
}
+
+ private String[] getListFromEnvironmentVariable(String name) throws Exception {
+ String systemServerClasspath =
+ mTestInfo.getDevice().executeShellCommand("echo $" + name).trim();
+ if (!systemServerClasspath.isEmpty()) {
+ return systemServerClasspath.split(":");
+ }
+ return new String[0];
+ }
+
+ private String getSystemServerIsa(String mappedArtifact) {
+ // Artifact path for system server artifacts has the form:
+ // ART_APEX_DALVIK_CACHE_DIRNAME + "/<arch>/system@framework@some.jar@classes.odex"
+ String[] pathComponents = mappedArtifact.split("/");
+ return pathComponents[pathComponents.length - 2];
+ }
}