/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "oat_file_assistant.h" #include #include #include #include #include #include "android-base/file.h" #include "android-base/logging.h" #include "android-base/properties.h" #include "android-base/stringprintf.h" #include "android-base/strings.h" #include "arch/instruction_set.h" #include "base/array_ref.h" #include "base/compiler_filter.h" #include "base/file_utils.h" #include "base/globals.h" #include "base/logging.h" // For VLOG. #include "base/macros.h" #include "base/os.h" #include "base/stl_util.h" #include "base/systrace.h" #include "base/utils.h" #include "base/zip_archive.h" #include "class_linker.h" #include "class_loader_context.h" #include "dex/art_dex_file_loader.h" #include "dex/dex_file_loader.h" #include "exec_utils.h" #include "gc/heap.h" #include "gc/space/image_space.h" #include "image.h" #include "oat.h" #include "oat_file_assistant_context.h" #include "runtime.h" #include "runtime_globals.h" #include "scoped_thread_state_change-inl.h" #include "vdex_file.h" #include "zlib.h" namespace art HIDDEN { using ::android::base::ConsumePrefix; using ::android::base::StringPrintf; static constexpr const char* kAnonymousDexPrefix = "Anonymous-DexFile@"; std::ostream& operator<<(std::ostream& stream, const OatFileAssistant::OatStatus status) { switch (status) { case OatFileAssistant::kOatCannotOpen: stream << "kOatCannotOpen"; break; case OatFileAssistant::kOatDexOutOfDate: stream << "kOatDexOutOfDate"; break; case OatFileAssistant::kOatBootImageOutOfDate: stream << "kOatBootImageOutOfDate"; break; case OatFileAssistant::kOatUpToDate: stream << "kOatUpToDate"; break; case OatFileAssistant::kOatContextOutOfDate: stream << "kOatContextOutOfDate"; break; } return stream; } OatFileAssistant::OatFileAssistant(const char* dex_location, const InstructionSet isa, ClassLoaderContext* context, bool load_executable, bool only_load_trusted_executable, OatFileAssistantContext* ofa_context) : OatFileAssistant(dex_location, isa, context, load_executable, only_load_trusted_executable, ofa_context, /*vdex_fd=*/-1, /*oat_fd=*/-1, /*zip_fd=*/-1) {} OatFileAssistant::OatFileAssistant(const char* dex_location, const InstructionSet isa, ClassLoaderContext* context, bool load_executable, bool only_load_trusted_executable, OatFileAssistantContext* ofa_context, int vdex_fd, int oat_fd, int zip_fd) : context_(context), isa_(isa), load_executable_(load_executable), only_load_trusted_executable_(only_load_trusted_executable), odex_(this, /*is_oat_location=*/false), oat_(this, /*is_oat_location=*/true), vdex_for_odex_(this, /*is_oat_location=*/false), vdex_for_oat_(this, /*is_oat_location=*/true), dm_for_odex_(this, /*is_oat_location=*/false), dm_for_oat_(this, /*is_oat_location=*/true), zip_fd_(zip_fd) { CHECK(dex_location != nullptr) << "OatFileAssistant: null dex location"; CHECK_IMPLIES(load_executable, context != nullptr) << "Loading executable without a context"; if (zip_fd < 0) { CHECK_LE(oat_fd, 0) << "zip_fd must be provided with valid oat_fd. zip_fd=" << zip_fd << " oat_fd=" << oat_fd; CHECK_LE(vdex_fd, 0) << "zip_fd must be provided with valid vdex_fd. zip_fd=" << zip_fd << " vdex_fd=" << vdex_fd; CHECK(!UseFdToReadFiles()); } else { CHECK(UseFdToReadFiles()); } dex_location_.assign(dex_location); Runtime* runtime = Runtime::Current(); if (load_executable_ && runtime == nullptr) { LOG(WARNING) << "OatFileAssistant: Load executable specified, " << "but no active runtime is found. Will not attempt to load executable."; load_executable_ = false; } if (load_executable_ && isa != kRuntimeQuickCodeISA) { LOG(WARNING) << "OatFileAssistant: Load executable specified, " << "but isa is not kRuntimeQuickCodeISA. Will not attempt to load executable."; load_executable_ = false; } if (ofa_context == nullptr) { CHECK(runtime != nullptr) << "runtime_options is not provided, and no active runtime is found."; ofa_context_ = std::make_unique(runtime); } else { ofa_context_ = ofa_context; } if (runtime == nullptr) { // We need `MemMap` for mapping files. We don't have to initialize it when there is a runtime // because the runtime initializes it. MemMap::Init(); } // Get the odex filename. std::string error_msg; std::string odex_file_name; if (DexLocationToOdexFilename(dex_location_, isa_, &odex_file_name, &error_msg)) { odex_.Reset(odex_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd); std::string vdex_file_name = GetVdexFilename(odex_file_name); // We dup FDs as the odex_ will claim ownership. vdex_for_odex_.Reset(vdex_file_name, UseFdToReadFiles(), DupCloexec(zip_fd), DupCloexec(vdex_fd), DupCloexec(oat_fd)); std::string dm_file_name = GetDmFilename(dex_location_); dm_for_odex_.Reset(dm_file_name, UseFdToReadFiles(), DupCloexec(zip_fd), DupCloexec(vdex_fd), DupCloexec(oat_fd)); } else { LOG(WARNING) << "Failed to determine odex file name: " << error_msg; } if (!UseFdToReadFiles()) { // Get the oat filename. std::string oat_file_name; if (DexLocationToOatFilename(dex_location_, isa_, GetRuntimeOptions().deny_art_apex_data_files, &oat_file_name, &error_msg)) { oat_.Reset(oat_file_name, /*use_fd=*/false); std::string vdex_file_name = GetVdexFilename(oat_file_name); vdex_for_oat_.Reset(vdex_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd); std::string dm_file_name = GetDmFilename(dex_location); dm_for_oat_.Reset(dm_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd); } else if (kIsTargetAndroid) { // No need to warn on host. We are probably in oatdump, where we only need OatFileAssistant to // validate BCP checksums. LOG(WARNING) << "Failed to determine oat file name for dex location " << dex_location_ << ": " << error_msg; } } // Check if the dex directory is writable. // This will be needed in most uses of OatFileAssistant and so it's OK to // compute it eagerly. (the only use which will not make use of it is // OatFileAssistant::GetStatusDump()) size_t pos = dex_location_.rfind('/'); if (pos == std::string::npos) { LOG(WARNING) << "Failed to determine dex file parent directory: " << dex_location_; } else if (!UseFdToReadFiles()) { // We cannot test for parent access when using file descriptors. That's ok // because in this case we will always pick the odex file anyway. std::string parent = dex_location_.substr(0, pos); if (access(parent.c_str(), W_OK) == 0) { dex_parent_writable_ = true; } else { VLOG(oat) << "Dex parent of " << dex_location_ << " is not writable: " << strerror(errno); } } } std::unique_ptr OatFileAssistant::Create( const std::string& filename, const std::string& isa_str, const std::optional& context_str, bool load_executable, bool only_load_trusted_executable, OatFileAssistantContext* ofa_context, /*out*/ std::unique_ptr* context, /*out*/ std::string* error_msg) { InstructionSet isa = GetInstructionSetFromString(isa_str.c_str()); if (isa == InstructionSet::kNone) { *error_msg = StringPrintf("Instruction set '%s' is invalid", isa_str.c_str()); return nullptr; } std::unique_ptr tmp_context = nullptr; if (context_str.has_value()) { tmp_context = ClassLoaderContext::Create(context_str.value()); if (tmp_context == nullptr) { *error_msg = StringPrintf("Class loader context '%s' is invalid", context_str->c_str()); return nullptr; } if (!tmp_context->OpenDexFiles(android::base::Dirname(filename), /*context_fds=*/{}, /*only_read_checksums=*/true)) { *error_msg = StringPrintf("Failed to load class loader context files for '%s' with context '%s'", filename.c_str(), context_str->c_str()); return nullptr; } } auto assistant = std::make_unique(filename.c_str(), isa, tmp_context.get(), load_executable, only_load_trusted_executable, ofa_context); *context = std::move(tmp_context); return assistant; } bool OatFileAssistant::UseFdToReadFiles() { return zip_fd_ >= 0; } bool OatFileAssistant::IsInBootClassPath() { // Note: We check the current boot class path, regardless of the ISA // specified by the user. This is okay, because the boot class path should // be the same for all ISAs. // TODO: Can we verify the boot class path is the same for all ISAs? for (const std::string& boot_class_path_location : GetRuntimeOptions().boot_class_path_locations) { if (boot_class_path_location == dex_location_) { VLOG(oat) << "Dex location " << dex_location_ << " is in boot class path"; return true; } } return false; } OatFileAssistant::DexOptTrigger OatFileAssistant::GetDexOptTrigger( CompilerFilter::Filter target_compiler_filter, bool profile_changed, bool downgrade) { if (downgrade) { // The caller's intention is to downgrade the compiler filter. We should only re-compile if the // target compiler filter is worse than the current one. return DexOptTrigger{.targetFilterIsWorse = true}; } // This is the usual case. The caller's intention is to see if a better oat file can be generated. DexOptTrigger dexopt_trigger{ .targetFilterIsBetter = true, .primaryBootImageBecomesUsable = true, .needExtraction = true}; if (profile_changed && CompilerFilter::DependsOnProfile(target_compiler_filter)) { // Since the profile has been changed, we should re-compile even if the compilation does not // make the compiler filter better. dexopt_trigger.targetFilterIsSame = true; } return dexopt_trigger; } int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter, bool profile_changed, bool downgrade) { OatFileInfo& info = GetBestInfo(); if (info.CheckDisableCompactDex()) { // TODO(b/256664509): Clean this up. VLOG(oat) << "Should recompile: disable cdex"; return kDex2OatFromScratch; } DexOptNeeded dexopt_needed = info.GetDexOptNeeded( target_compiler_filter, GetDexOptTrigger(target_compiler_filter, profile_changed, downgrade)); if (dexopt_needed != kNoDexOptNeeded && (&info == &dm_for_oat_ || &info == &dm_for_odex_)) { // The usable vdex file is in the DM file. This information cannot be encoded in the integer. // Return kDex2OatFromScratch so that neither the vdex in the "oat" location nor the vdex in the // "odex" location will be picked by installd. return kDex2OatFromScratch; } if (info.IsOatLocation() || dexopt_needed == kDex2OatFromScratch) { return dexopt_needed; } return -dexopt_needed; } bool OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter, DexOptTrigger dexopt_trigger, /*out*/ DexOptStatus* dexopt_status) { OatFileInfo& info = GetBestInfo(); if (info.CheckDisableCompactDex()) { // TODO(b/256664509): Clean this up. dexopt_status->location_ = kLocationNoneOrError; return true; } DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target_compiler_filter, dexopt_trigger); dexopt_status->location_ = GetLocation(info); return dexopt_needed != kNoDexOptNeeded; } bool OatFileAssistant::IsUpToDate() { return GetBestInfo().Status() == kOatUpToDate; } std::unique_ptr OatFileAssistant::GetBestOatFile() { return GetBestInfo().ReleaseFileForUse(); } std::string OatFileAssistant::GetStatusDump() { std::ostringstream status; bool oat_file_exists = false; bool odex_file_exists = false; if (oat_.Status() != kOatCannotOpen) { // If we can open the file, Filename should not return null. CHECK(oat_.Filename() != nullptr); oat_file_exists = true; status << *oat_.Filename() << "[status=" << oat_.Status() << ", "; const OatFile* file = oat_.GetFile(); if (file == nullptr) { // If the file is null even though the status is not kOatCannotOpen, it // means we must have a vdex file with no corresponding oat file. In // this case we cannot determine the compilation filter. Indicate that // we have only the vdex file instead. status << "vdex-only"; } else { status << "compilation_filter=" << CompilerFilter::NameOfFilter(file->GetCompilerFilter()); } } if (odex_.Status() != kOatCannotOpen) { // If we can open the file, Filename should not return null. CHECK(odex_.Filename() != nullptr); odex_file_exists = true; if (oat_file_exists) { status << "] "; } status << *odex_.Filename() << "[status=" << odex_.Status() << ", "; const OatFile* file = odex_.GetFile(); if (file == nullptr) { status << "vdex-only"; } else { status << "compilation_filter=" << CompilerFilter::NameOfFilter(file->GetCompilerFilter()); } } if (!oat_file_exists && !odex_file_exists) { status << "invalid["; } status << "]"; return status.str(); } std::vector> OatFileAssistant::LoadDexFiles( const OatFile& oat_file, const char* dex_location) { std::vector> dex_files; if (LoadDexFiles(oat_file, dex_location, &dex_files)) { return dex_files; } else { return std::vector>(); } } bool OatFileAssistant::LoadDexFiles(const OatFile& oat_file, const std::string& dex_location, std::vector>* out_dex_files) { // Load the main dex file. std::string error_msg; const OatDexFile* oat_dex_file = oat_file.GetOatDexFile(dex_location.c_str(), &error_msg); if (oat_dex_file == nullptr) { LOG(WARNING) << error_msg; return false; } std::unique_ptr dex_file = oat_dex_file->OpenDexFile(&error_msg); if (dex_file.get() == nullptr) { LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg; return false; } out_dex_files->push_back(std::move(dex_file)); // Load the rest of the multidex entries for (size_t i = 1;; i++) { std::string multidex_dex_location = DexFileLoader::GetMultiDexLocation(i, dex_location.c_str()); oat_dex_file = oat_file.GetOatDexFile(multidex_dex_location.c_str()); if (oat_dex_file == nullptr) { // There are no more multidex entries to load. break; } dex_file = oat_dex_file->OpenDexFile(&error_msg); if (dex_file.get() == nullptr) { LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg; return false; } out_dex_files->push_back(std::move(dex_file)); } return true; } std::optional OatFileAssistant::HasDexFiles(std::string* error_msg) { ScopedTrace trace("HasDexFiles"); std::optional checksum; if (!GetRequiredDexChecksum(&checksum, error_msg)) { return std::nullopt; } return checksum.has_value(); } OatFileAssistant::OatStatus OatFileAssistant::OdexFileStatus() { return odex_.Status(); } OatFileAssistant::OatStatus OatFileAssistant::OatFileStatus() { return oat_.Status(); } bool OatFileAssistant::DexChecksumUpToDate(const OatFile& file, std::string* error_msg) { if (!file.ContainsDexCode()) { // We've already checked during oat file creation that the dex files loaded // from external files have the same checksums as the ones in the vdex file. return true; } ScopedTrace trace("DexChecksumUpToDate"); std::optional dex_checksum; if (!GetRequiredDexChecksum(&dex_checksum, error_msg)) { return false; } if (!dex_checksum.has_value()) { LOG(WARNING) << "Required dex checksums not found. Assuming dex checksums are up to date."; return true; } std::vector oat_dex_files; uint32_t number_of_dex_files = file.GetOatHeader().GetDexFileCount(); for (uint32_t i = 0; i < number_of_dex_files; i++) { std::string dex = DexFileLoader::GetMultiDexLocation(i, dex_location_.c_str()); const OatDexFile* oat_dex_file = file.GetOatDexFile(dex.c_str()); if (oat_dex_file == nullptr) { *error_msg = StringPrintf("failed to find %s in %s", dex.c_str(), file.GetLocation().c_str()); return false; } oat_dex_files.push_back(oat_dex_file); } uint32_t oat_checksum = DexFileLoader::GetMultiDexChecksum(oat_dex_files); CHECK(dex_checksum.has_value()); if (dex_checksum != oat_checksum) { VLOG(oat) << "Checksum does not match: " << std::hex << file.GetLocation() << " (" << oat_checksum << ") vs " << dex_location_ << " (" << *dex_checksum << ")"; return false; } return true; } OatFileAssistant::OatStatus OatFileAssistant::GivenOatFileStatus(const OatFile& file) { // Verify the ART_USE_READ_BARRIER state. // TODO: Don't fully reject files due to read barrier state. If they contain // compiled code and are otherwise okay, we should return something like // kOatRelocationOutOfDate. If they don't contain compiled code, the read // barrier state doesn't matter. if (file.GetOatHeader().IsConcurrentCopying() != gUseReadBarrier) { return kOatCannotOpen; } // Verify the dex checksum. std::string error_msg; if (!DexChecksumUpToDate(file, &error_msg)) { LOG(ERROR) << error_msg; return kOatDexOutOfDate; } CompilerFilter::Filter current_compiler_filter = file.GetCompilerFilter(); // Verify the image checksum if (file.IsBackedByVdexOnly()) { VLOG(oat) << "Image checksum test skipped for vdex file " << file.GetLocation(); } else if (CompilerFilter::DependsOnImageChecksum(current_compiler_filter)) { if (!ValidateBootClassPathChecksums(file)) { VLOG(oat) << "Oat image checksum does not match image checksum."; return kOatBootImageOutOfDate; } if (!gc::space::ImageSpace::ValidateApexVersions( file.GetOatHeader(), GetOatFileAssistantContext()->GetApexVersions(), file.GetLocation(), &error_msg)) { VLOG(oat) << error_msg; return kOatBootImageOutOfDate; } } else { VLOG(oat) << "Image checksum test skipped for compiler filter " << current_compiler_filter; } // The constraint is only enforced if the zip has uncompressed dex code. if (only_load_trusted_executable_ && !LocationIsTrusted(file.GetLocation(), !GetRuntimeOptions().deny_art_apex_data_files) && file.ContainsDexCode() && ZipFileOnlyContainsUncompressedDex()) { LOG(ERROR) << "Not loading " << dex_location_ << ": oat file has dex code, but APK has uncompressed dex code"; return kOatDexOutOfDate; } if (!ClassLoaderContextIsOkay(file)) { return kOatContextOutOfDate; } return kOatUpToDate; } bool OatFileAssistant::AnonymousDexVdexLocation(const std::vector& headers, InstructionSet isa, /* out */ std::string* dex_location, /* out */ std::string* vdex_filename) { // Normally, OatFileAssistant should not assume that there is an active runtime. However, we // reference the runtime here. This is okay because we are in a static function that is unrelated // to other parts of OatFileAssistant. DCHECK(Runtime::Current() != nullptr); uint32_t checksum = adler32(0L, Z_NULL, 0); for (const DexFile::Header* header : headers) { checksum = adler32_combine( checksum, header->checksum_, header->file_size_ - DexFile::kNumNonChecksumBytes); } const std::string& data_dir = Runtime::Current()->GetProcessDataDirectory(); if (data_dir.empty() || Runtime::Current()->IsZygote()) { *dex_location = StringPrintf("%s%u", kAnonymousDexPrefix, checksum); return false; } *dex_location = StringPrintf("%s/%s%u.jar", data_dir.c_str(), kAnonymousDexPrefix, checksum); std::string odex_filename; std::string error_msg; if (!DexLocationToOdexFilename(*dex_location, isa, &odex_filename, &error_msg)) { LOG(WARNING) << "Could not get odex filename for " << *dex_location << ": " << error_msg; return false; } *vdex_filename = GetVdexFilename(odex_filename); return true; } bool OatFileAssistant::IsAnonymousVdexBasename(const std::string& basename) { DCHECK(basename.find('/') == std::string::npos); // `basename` must have format: if (basename.size() < strlen(kAnonymousDexPrefix) + strlen(kVdexExtension) + 1 || !basename.starts_with(kAnonymousDexPrefix) || !basename.ends_with(kVdexExtension)) { return false; } // Check that all characters between the prefix and extension are decimal digits. for (size_t i = strlen(kAnonymousDexPrefix); i < basename.size() - strlen(kVdexExtension); ++i) { if (!std::isdigit(basename[i])) { return false; } } return true; } bool OatFileAssistant::DexLocationToOdexFilename(const std::string& location, InstructionSet isa, std::string* odex_filename, std::string* error_msg) { CHECK(odex_filename != nullptr); CHECK(error_msg != nullptr); // For a DEX file on /apex, check if there is an odex file on /system. If so, and the file exists, // use it. if (LocationIsOnApex(location)) { const std::string system_file = GetSystemOdexFilenameForApex(location, isa); if (OS::FileExists(system_file.c_str(), /*check_file_type=*/true)) { *odex_filename = system_file; return true; } else if (errno != ENOENT) { PLOG(ERROR) << "Could not check odex file " << system_file; } } // The odex file name is formed by replacing the dex_location extension with // .odex and inserting an oat/ directory. For example: // location = /foo/bar/baz.jar // odex_location = /foo/bar/oat//baz.odex // Find the directory portion of the dex location and add the oat/ // directory. size_t pos = location.rfind('/'); if (pos == std::string::npos) { *error_msg = "Dex location " + location + " has no directory."; return false; } std::string dir = location.substr(0, pos + 1); // Add the oat directory. dir += "oat"; // Add the isa directory dir += "/" + std::string(GetInstructionSetString(isa)); // Get the base part of the file without the extension. std::string file = location.substr(pos + 1); pos = file.rfind('.'); std::string base = pos != std::string::npos ? file.substr(0, pos) : file; *odex_filename = dir + "/" + base + kOdexExtension; return true; } bool OatFileAssistant::DexLocationToOatFilename(const std::string& location, InstructionSet isa, std::string* oat_filename, std::string* error_msg) { DCHECK(Runtime::Current() != nullptr); return DexLocationToOatFilename( location, isa, Runtime::Current()->DenyArtApexDataFiles(), oat_filename, error_msg); } bool OatFileAssistant::DexLocationToOatFilename(const std::string& location, InstructionSet isa, bool deny_art_apex_data_files, std::string* oat_filename, std::string* error_msg) { CHECK(oat_filename != nullptr); CHECK(error_msg != nullptr); // Check if `location` could have an oat file in the ART APEX data directory. If so, and the // file exists, use it. const std::string apex_data_file = GetApexDataOdexFilename(location, isa); if (!apex_data_file.empty() && !deny_art_apex_data_files) { if (OS::FileExists(apex_data_file.c_str(), /*check_file_type=*/true)) { *oat_filename = apex_data_file; return true; } else if (errno != ENOENT) { PLOG(ERROR) << "Could not check odex file " << apex_data_file; } } // If ANDROID_DATA is not set, return false instead of aborting. // This can occur for preopt when using a class loader context. if (GetAndroidDataSafe(error_msg).empty()) { *error_msg = "GetAndroidDataSafe failed: " + *error_msg; return false; } std::string dalvik_cache; bool have_android_data = false; bool dalvik_cache_exists = false; bool is_global_cache = false; GetDalvikCache(GetInstructionSetString(isa), /*create_if_absent=*/true, &dalvik_cache, &have_android_data, &dalvik_cache_exists, &is_global_cache); if (!dalvik_cache_exists) { *error_msg = "Dalvik cache directory does not exist"; return false; } // TODO: The oat file assistant should be the definitive place for // determining the oat file name from the dex location, not // GetDalvikCacheFilename. return GetDalvikCacheFilename(location, dalvik_cache, oat_filename, error_msg); } bool OatFileAssistant::GetRequiredDexChecksum(std::optional* checksum, std::string* error) { if (!required_dex_checksums_attempted_) { required_dex_checksums_attempted_ = true; File file(zip_fd_, /*check_usage=*/false); ArtDexFileLoader dex_loader(&file, dex_location_); std::optional checksum2; std::string error2; if (dex_loader.GetMultiDexChecksum( &checksum2, &error2, &zip_file_only_contains_uncompressed_dex_)) { cached_required_dex_checksums_ = checksum2; cached_required_dex_checksums_error_ = std::nullopt; } else { cached_required_dex_checksums_ = std::nullopt; cached_required_dex_checksums_error_ = error2; } file.Release(); // Don't close the file yet (we have only read the checksum). } if (cached_required_dex_checksums_error_.has_value()) { *error = cached_required_dex_checksums_error_.value(); DCHECK(!error->empty()); return false; } if (!cached_required_dex_checksums_.has_value()) { // The only valid case here is for APKs without dex files. VLOG(oat) << "No dex file found in " << dex_location_; } *checksum = cached_required_dex_checksums_; return true; } bool OatFileAssistant::ValidateBootClassPathChecksums(OatFileAssistantContext* ofa_context, InstructionSet isa, std::string_view oat_checksums, std::string_view oat_boot_class_path, /*out*/ std::string* error_msg) { const std::vector& bcp_locations = ofa_context->GetRuntimeOptions().boot_class_path_locations; if (oat_checksums.empty() || oat_boot_class_path.empty()) { *error_msg = oat_checksums.empty() ? "Empty checksums" : "Empty boot class path"; return false; } size_t oat_bcp_size = gc::space::ImageSpace::CheckAndCountBCPComponents( oat_boot_class_path, ArrayRef(bcp_locations), error_msg); if (oat_bcp_size == static_cast(-1)) { DCHECK(!error_msg->empty()); return false; } DCHECK_LE(oat_bcp_size, bcp_locations.size()); size_t bcp_index = 0; size_t boot_image_index = 0; bool found_d = false; while (bcp_index < oat_bcp_size) { static_assert(gc::space::ImageSpace::kImageChecksumPrefix == 'i', "Format prefix check"); static_assert(gc::space::ImageSpace::kDexFileChecksumPrefix == 'd', "Format prefix check"); if (oat_checksums.starts_with("i") && !found_d) { const std::vector& boot_image_info_list = ofa_context->GetBootImageInfoList(isa); if (boot_image_index >= boot_image_info_list.size()) { *error_msg = StringPrintf("Missing boot image for %s, remaining checksums: %s", bcp_locations[bcp_index].c_str(), std::string(oat_checksums).c_str()); return false; } const OatFileAssistantContext::BootImageInfo& boot_image_info = boot_image_info_list[boot_image_index]; if (!ConsumePrefix(&oat_checksums, boot_image_info.checksum)) { *error_msg = StringPrintf("Image checksum mismatch, expected %s to start with %s", std::string(oat_checksums).c_str(), boot_image_info.checksum.c_str()); return false; } bcp_index += boot_image_info.component_count; boot_image_index++; } else if (oat_checksums.starts_with("d")) { found_d = true; const std::vector* bcp_checksums = ofa_context->GetBcpChecksums(bcp_index, error_msg); if (bcp_checksums == nullptr) { return false; } oat_checksums.remove_prefix(1u); for (const std::string& checksum : *bcp_checksums) { if (!ConsumePrefix(&oat_checksums, checksum)) { *error_msg = StringPrintf( "Dex checksum mismatch for bootclasspath file %s, expected %s to start with %s", bcp_locations[bcp_index].c_str(), std::string(oat_checksums).c_str(), checksum.c_str()); return false; } } bcp_index++; } else { *error_msg = StringPrintf("Unexpected checksums, expected %s to start with %s", std::string(oat_checksums).c_str(), found_d ? "'d'" : "'i' or 'd'"); return false; } if (bcp_index < oat_bcp_size) { if (!ConsumePrefix(&oat_checksums, ":")) { if (oat_checksums.empty()) { *error_msg = StringPrintf("Checksum too short, missing %zu components", oat_bcp_size - bcp_index); } else { *error_msg = StringPrintf("Missing ':' separator at start of %s", std::string(oat_checksums).c_str()); } return false; } } } if (!oat_checksums.empty()) { *error_msg = StringPrintf("Checksum too long, unexpected tail: %s", std::string(oat_checksums).c_str()); return false; } return true; } bool OatFileAssistant::ValidateBootClassPathChecksums(const OatFile& oat_file) { // Get the checksums and the BCP from the oat file. const char* oat_boot_class_path_checksums = oat_file.GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey); const char* oat_boot_class_path = oat_file.GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathKey); if (oat_boot_class_path_checksums == nullptr || oat_boot_class_path == nullptr) { return false; } std::string error_msg; bool result = ValidateBootClassPathChecksums(GetOatFileAssistantContext(), isa_, oat_boot_class_path_checksums, oat_boot_class_path, &error_msg); if (!result) { VLOG(oat) << "Failed to verify checksums of oat file " << oat_file.GetLocation() << " error: " << error_msg; return false; } return true; } bool OatFileAssistant::IsPrimaryBootImageUsable() { return !GetOatFileAssistantContext()->GetBootImageInfoList(isa_).empty(); } OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() { ScopedTrace trace("GetBestInfo"); // TODO(calin): Document the side effects of class loading when // running dalvikvm command line. if (dex_parent_writable_ || UseFdToReadFiles()) { // If the parent of the dex file is writable it means that we can // create the odex file. In this case we unconditionally pick the odex // as the best oat file. This corresponds to the regular use case when // apps gets installed or when they load private, secondary dex file. // For apps on the system partition the odex location will not be // writable and thus the oat location might be more up to date. // If the odex is not useable, and we have a useable vdex, return the vdex // instead. VLOG(oat) << ART_FORMAT("GetBestInfo checking odex next to the dex file ({})", odex_.DisplayFilename()); if (!odex_.IsUseable()) { VLOG(oat) << ART_FORMAT("GetBestInfo checking vdex next to the dex file ({})", vdex_for_odex_.DisplayFilename()); if (vdex_for_odex_.IsUseable()) { return vdex_for_odex_; } VLOG(oat) << ART_FORMAT("GetBestInfo checking dm ({})", dm_for_odex_.DisplayFilename()); if (dm_for_odex_.IsUseable()) { return dm_for_odex_; } } return odex_; } // We cannot write to the odex location. This must be a system app. // If the oat location is useable take it. VLOG(oat) << ART_FORMAT("GetBestInfo checking odex in dalvik-cache ({})", oat_.DisplayFilename()); if (oat_.IsUseable()) { return oat_; } // The oat file is not useable but the odex file might be up to date. // This is an indication that we are dealing with an up to date prebuilt // (that doesn't need relocation). VLOG(oat) << ART_FORMAT("GetBestInfo checking odex next to the dex file ({})", odex_.DisplayFilename()); if (odex_.IsUseable()) { return odex_; } // Look for a useable vdex file. VLOG(oat) << ART_FORMAT("GetBestInfo checking vdex in dalvik-cache ({})", vdex_for_oat_.DisplayFilename()); if (vdex_for_oat_.IsUseable()) { return vdex_for_oat_; } VLOG(oat) << ART_FORMAT("GetBestInfo checking vdex next to the dex file ({})", vdex_for_odex_.DisplayFilename()); if (vdex_for_odex_.IsUseable()) { return vdex_for_odex_; } VLOG(oat) << ART_FORMAT("GetBestInfo checking dm ({})", dm_for_oat_.DisplayFilename()); if (dm_for_oat_.IsUseable()) { return dm_for_oat_; } // TODO(jiakaiz): Is this the same as above? VLOG(oat) << ART_FORMAT("GetBestInfo checking dm ({})", dm_for_odex_.DisplayFilename()); if (dm_for_odex_.IsUseable()) { return dm_for_odex_; } // We got into the worst situation here: // - the oat location is not useable // - the prebuild odex location is not up to date // - the vdex-only file is not useable // - and we don't have the original dex file anymore (stripped). // Pick the odex if it exists, or the oat if not. VLOG(oat) << "GetBestInfo no usable artifacts"; return (odex_.Status() == kOatCannotOpen) ? oat_ : odex_; } std::unique_ptr OatFileAssistant::OpenImageSpace(const OatFile* oat_file) { DCHECK(oat_file != nullptr); std::string art_file = ReplaceFileExtension(oat_file->GetLocation(), kArtExtension); if (art_file.empty()) { return nullptr; } std::string error_msg; std::unique_ptr ret = gc::space::ImageSpace::CreateFromAppImage(art_file.c_str(), oat_file, &error_msg); if (ret == nullptr && (VLOG_IS_ON(image) || OS::FileExists(art_file.c_str()))) { LOG(INFO) << "Failed to open app image " << art_file.c_str() << " " << error_msg; } return ret; } OatFileAssistant::OatFileInfo::OatFileInfo(OatFileAssistant* oat_file_assistant, bool is_oat_location) : oat_file_assistant_(oat_file_assistant), is_oat_location_(is_oat_location) {} bool OatFileAssistant::OatFileInfo::IsOatLocation() { return is_oat_location_; } const std::string* OatFileAssistant::OatFileInfo::Filename() { return filename_provided_ ? &filename_ : nullptr; } const char* OatFileAssistant::OatFileInfo::DisplayFilename() { return filename_provided_ ? filename_.c_str() : "unknown"; } bool OatFileAssistant::OatFileInfo::IsUseable() { ScopedTrace trace("IsUseable"); switch (Status()) { case kOatCannotOpen: case kOatDexOutOfDate: case kOatContextOutOfDate: case kOatBootImageOutOfDate: return false; case kOatUpToDate: return true; } } OatFileAssistant::OatStatus OatFileAssistant::OatFileInfo::Status() { ScopedTrace trace("Status"); if (!status_attempted_) { status_attempted_ = true; const OatFile* file = GetFile(); if (file == nullptr) { status_ = kOatCannotOpen; } else { status_ = oat_file_assistant_->GivenOatFileStatus(*file); VLOG(oat) << file->GetLocation() << " is " << status_ << " with filter " << file->GetCompilerFilter(); } } return status_; } OatFileAssistant::DexOptNeeded OatFileAssistant::OatFileInfo::GetDexOptNeeded( CompilerFilter::Filter target_compiler_filter, const DexOptTrigger dexopt_trigger) { if (IsUseable()) { return ShouldRecompileForFilter(target_compiler_filter, dexopt_trigger) ? kDex2OatForFilter : kNoDexOptNeeded; } // In this case, the oat file is not usable. If the caller doesn't seek for a better compiler // filter (e.g., the caller wants to downgrade), then we should not recompile. if (!dexopt_trigger.targetFilterIsBetter) { return kNoDexOptNeeded; } if (Status() == kOatBootImageOutOfDate) { return kDex2OatForBootImage; } std::string error_msg; std::optional has_dex_files = oat_file_assistant_->HasDexFiles(&error_msg); if (has_dex_files.has_value()) { if (*has_dex_files) { return kDex2OatFromScratch; } else { // No dex file, so there is nothing we need to do. return kNoDexOptNeeded; } } else { // Unable to open the dex file, so there is nothing we can do. LOG(WARNING) << error_msg; return kNoDexOptNeeded; } } const OatFile* OatFileAssistant::OatFileInfo::GetFile() { CHECK(!file_released_) << "GetFile called after oat file released."; if (load_attempted_) { return file_.get(); } load_attempted_ = true; if (!filename_provided_) { return nullptr; } if (LocationIsOnArtApexData(filename_) && oat_file_assistant_->GetRuntimeOptions().deny_art_apex_data_files) { LOG(WARNING) << "OatFileAssistant rejected file " << filename_ << ": ART apexdata is untrusted."; return nullptr; } std::string error_msg; bool executable = oat_file_assistant_->load_executable_; if (filename_.ends_with(kVdexExtension)) { executable = false; // Check to see if there is a vdex file we can make use of. std::unique_ptr vdex; if (use_fd_) { if (vdex_fd_ >= 0) { struct stat s; int rc = TEMP_FAILURE_RETRY(fstat(vdex_fd_, &s)); if (rc == -1) { error_msg = StringPrintf("Failed getting length of the vdex file %s.", strerror(errno)); } else { vdex = VdexFile::Open(vdex_fd_, s.st_size, filename_, /*writable=*/false, /*low_4gb=*/false, &error_msg); } } } else { vdex = VdexFile::Open(filename_, /*writable=*/false, /*low_4gb=*/false, &error_msg); } if (vdex == nullptr) { VLOG(oat) << "unable to open vdex file " << filename_ << ": " << error_msg; } else { file_.reset(OatFile::OpenFromVdex(zip_fd_, std::move(vdex), oat_file_assistant_->dex_location_, oat_file_assistant_->context_, &error_msg)); } } else if (filename_.ends_with(kDmExtension)) { executable = false; // Check to see if there is a vdex file we can make use of. std::unique_ptr dm_file(ZipArchive::Open(filename_.c_str(), &error_msg)); if (dm_file != nullptr) { std::unique_ptr vdex(VdexFile::OpenFromDm(filename_, *dm_file)); if (vdex != nullptr) { file_.reset(OatFile::OpenFromVdex(zip_fd_, std::move(vdex), oat_file_assistant_->dex_location_, oat_file_assistant_->context_, &error_msg)); } } } else { if (executable && oat_file_assistant_->only_load_trusted_executable_) { executable = LocationIsTrusted(filename_, /*trust_art_apex_data_files=*/true); } VLOG(oat) << "Loading " << filename_ << " with executable: " << executable; if (gPageSize != kMinPageSize) { LOG(WARNING) << "Loading odex files is only supported on devices with 4K page size"; return nullptr; } if (use_fd_) { if (oat_fd_ >= 0 && vdex_fd_ >= 0) { ArrayRef dex_locations(&oat_file_assistant_->dex_location_, /*size=*/1u); file_.reset(OatFile::Open(zip_fd_, vdex_fd_, oat_fd_, filename_, executable, /*low_4gb=*/false, dex_locations, /*dex_files=*/{}, /*reservation=*/nullptr, &error_msg)); } } else { file_.reset(OatFile::Open(/*zip_fd=*/-1, filename_, filename_, executable, /*low_4gb=*/false, oat_file_assistant_->dex_location_, &error_msg)); } } if (file_.get() == nullptr) { VLOG(oat) << "OatFileAssistant test for existing oat file " << filename_ << ": " << error_msg; } else { VLOG(oat) << "Successfully loaded " << filename_ << " with executable: " << executable; } return file_.get(); } bool OatFileAssistant::OatFileInfo::ShouldRecompileForFilter(CompilerFilter::Filter target, const DexOptTrigger dexopt_trigger) { const OatFile* file = GetFile(); DCHECK(file != nullptr); if (CompilerFilter::IsBetter(target, CompilerFilter::kVerify) && gPageSize != kMinPageSize) { // Prevent infinite recompilations during background dexopt on 16K page devices. VLOG(oat) << "Adjusting target filter to 'verify' because loading odex files is only supported " "on devices with 4K page size"; target = CompilerFilter::kVerify; } CompilerFilter::Filter current = file->GetCompilerFilter(); if (dexopt_trigger.targetFilterIsBetter && CompilerFilter::IsBetter(target, current)) { VLOG(oat) << ART_FORMAT("Should recompile: targetFilterIsBetter (current: {}, target: {})", CompilerFilter::NameOfFilter(current), CompilerFilter::NameOfFilter(target)); return true; } if (dexopt_trigger.targetFilterIsSame && current == target) { VLOG(oat) << ART_FORMAT("Should recompile: targetFilterIsSame (current: {}, target: {})", CompilerFilter::NameOfFilter(current), CompilerFilter::NameOfFilter(target)); return true; } if (dexopt_trigger.targetFilterIsWorse && CompilerFilter::IsBetter(current, target)) { VLOG(oat) << ART_FORMAT("Should recompile: targetFilterIsWorse (current: {}, target: {})", CompilerFilter::NameOfFilter(current), CompilerFilter::NameOfFilter(target)); return true; } // Don't regress the compiler filter for the triggers handled below. if (CompilerFilter::IsBetter(current, target)) { VLOG(oat) << "Should not recompile: current filter is better"; return false; } if (dexopt_trigger.primaryBootImageBecomesUsable && CompilerFilter::IsAotCompilationEnabled(current)) { // If the oat file has been compiled without an image, and the runtime is // now running with an image loaded from disk, return that we need to // re-compile. The recompilation will generate a better oat file, and with an app // image for profile guided compilation. // However, don't recompile for "verify". Although verification depends on the boot image, the // penalty of being verified without a boot image is low. Consider the case where a dex file // is verified by "ab-ota", we don't want it to be re-verified by "boot-after-ota". const char* oat_boot_class_path_checksums = file->GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey); if (oat_boot_class_path_checksums != nullptr && oat_boot_class_path_checksums[0] != 'i' && oat_file_assistant_->IsPrimaryBootImageUsable()) { DCHECK(!file->GetOatHeader().RequiresImage()); VLOG(oat) << "Should recompile: primaryBootImageBecomesUsable"; return true; } } if (dexopt_trigger.needExtraction && !file->ContainsDexCode() && !oat_file_assistant_->ZipFileOnlyContainsUncompressedDex()) { VLOG(oat) << "Should recompile: needExtraction"; return true; } VLOG(oat) << "Should not recompile"; return false; } bool OatFileAssistant::ClassLoaderContextIsOkay(const OatFile& oat_file) const { if (context_ == nullptr) { // The caller requests to skip the check. return true; } if (oat_file.IsBackedByVdexOnly()) { // Only a vdex file, we don't depend on the class loader context. return true; } if (!CompilerFilter::IsVerificationEnabled(oat_file.GetCompilerFilter())) { // If verification is not enabled we don't need to verify the class loader context and we // assume it's ok. return true; } ClassLoaderContext::VerificationResult matches = context_->VerifyClassLoaderContextMatch(oat_file.GetClassLoaderContext(), /*verify_names=*/true, /*verify_checksums=*/true); if (matches == ClassLoaderContext::VerificationResult::kMismatch) { VLOG(oat) << "ClassLoaderContext check failed. Context was " << oat_file.GetClassLoaderContext() << ". The expected context is " << context_->EncodeContextForOatFile(android::base::Dirname(dex_location_)); return false; } return true; } bool OatFileAssistant::OatFileInfo::IsExecutable() { const OatFile* file = GetFile(); return (file != nullptr && file->IsExecutable()); } void OatFileAssistant::OatFileInfo::Reset() { load_attempted_ = false; file_.reset(); status_attempted_ = false; } void OatFileAssistant::OatFileInfo::Reset( const std::string& filename, bool use_fd, int zip_fd, int vdex_fd, int oat_fd) { filename_provided_ = true; filename_ = filename; use_fd_ = use_fd; zip_fd_ = zip_fd; vdex_fd_ = vdex_fd; oat_fd_ = oat_fd; Reset(); } std::unique_ptr OatFileAssistant::OatFileInfo::ReleaseFile() { file_released_ = true; return std::move(file_); } std::unique_ptr OatFileAssistant::OatFileInfo::ReleaseFileForUse() { ScopedTrace trace("ReleaseFileForUse"); if (Status() == kOatUpToDate) { return ReleaseFile(); } return std::unique_ptr(); } // Check if we should reject vdex containing cdex code as part of the cdex // deprecation. // TODO(b/256664509): Clean this up. bool OatFileAssistant::OatFileInfo::CheckDisableCompactDex() { const OatFile* oat_file = GetFile(); if (oat_file == nullptr) { return false; } const VdexFile* vdex_file = oat_file->GetVdexFile(); return vdex_file != nullptr && vdex_file->HasDexSection() && !vdex_file->HasOnlyStandardDexFiles(); } // TODO(calin): we could provide a more refined status here // (e.g. run from uncompressed apk, run with vdex but not oat etc). It will allow us to // track more experiments but adds extra complexity. void OatFileAssistant::GetOptimizationStatus(const std::string& filename, InstructionSet isa, std::string* out_compilation_filter, std::string* out_compilation_reason, OatFileAssistantContext* ofa_context) { // It may not be possible to load an oat file executable (e.g., selinux restrictions). Load // non-executable and check the status manually. OatFileAssistant oat_file_assistant(filename.c_str(), isa, /*context=*/nullptr, /*load_executable=*/false, /*only_load_trusted_executable=*/false, ofa_context); std::string out_odex_location; // unused std::string out_odex_status; // unused Location out_location; // unused oat_file_assistant.GetOptimizationStatus(&out_odex_location, out_compilation_filter, out_compilation_reason, &out_odex_status, &out_location); } void OatFileAssistant::GetOptimizationStatus(std::string* out_odex_location, std::string* out_compilation_filter, std::string* out_compilation_reason, std::string* out_odex_status, Location* out_location) { OatFileInfo& oat_file_info = GetBestInfo(); const OatFile* oat_file = oat_file_info.GetFile(); *out_location = GetLocation(oat_file_info); if (oat_file == nullptr) { std::string error_msg; std::optional has_dex_files = HasDexFiles(&error_msg); if (!has_dex_files.has_value()) { *out_odex_location = "error"; *out_compilation_filter = "unknown"; *out_compilation_reason = "unknown"; // This happens when we cannot open the APK/JAR. *out_odex_status = "io-error-no-apk"; } else if (!has_dex_files.value()) { *out_odex_location = "none"; *out_compilation_filter = "unknown"; *out_compilation_reason = "unknown"; // This happens when the APK/JAR doesn't contain any DEX file. *out_odex_status = "no-dex-code"; } else { *out_odex_location = "error"; *out_compilation_filter = "run-from-apk"; *out_compilation_reason = "unknown"; // This mostly happens when we cannot open the oat file. // Note that it's different than kOatCannotOpen. // TODO: The design of getting the BestInfo is not ideal, as it's not very clear what's the // difference between a nullptr and kOatcannotOpen. The logic should be revised and improved. *out_odex_status = "io-error-no-oat"; } return; } *out_odex_location = oat_file->GetLocation(); OatStatus status = oat_file_info.Status(); const char* reason = oat_file->GetCompilationReason(); *out_compilation_reason = reason == nullptr ? "unknown" : reason; // If the oat file is invalid, the vdex file will be picked, so the status is `kOatUpToDate`. If // the vdex file is also invalid, then either `oat_file` is nullptr, or `status` is // `kOatDexOutOfDate`. DCHECK(status == kOatUpToDate || status == kOatDexOutOfDate); switch (status) { case kOatUpToDate: *out_compilation_filter = CompilerFilter::NameOfFilter(oat_file->GetCompilerFilter()); *out_odex_status = "up-to-date"; return; case kOatCannotOpen: case kOatBootImageOutOfDate: case kOatContextOutOfDate: // These should never happen, but be robust. *out_compilation_filter = "unexpected"; *out_compilation_reason = "unexpected"; *out_odex_status = "unexpected"; return; case kOatDexOutOfDate: *out_compilation_filter = "run-from-apk-fallback"; *out_odex_status = "apk-more-recent"; return; } LOG(FATAL) << "Unreachable"; UNREACHABLE(); } bool OatFileAssistant::ZipFileOnlyContainsUncompressedDex() { // zip_file_only_contains_uncompressed_dex_ is only set during fetching the dex checksums. std::optional checksum; std::string error_msg; if (!GetRequiredDexChecksum(&checksum, &error_msg)) { LOG(ERROR) << error_msg; } return zip_file_only_contains_uncompressed_dex_; } OatFileAssistant::Location OatFileAssistant::GetLocation(OatFileInfo& info) { if (info.IsUseable()) { if (&info == &dm_for_oat_ || &info == &dm_for_odex_) { return kLocationDm; } else if (info.IsOatLocation()) { return kLocationOat; } else { return kLocationOdex; } } else { return kLocationNoneOrError; } } } // namespace art