diff options
author | 2024-12-24 09:03:35 +0000 | |
---|---|---|
committer | 2025-03-21 09:37:10 -0700 | |
commit | f1cf622ecc1a27d95236dd0ec1da0936182ecafd (patch) | |
tree | c1c78799a3a1b64ec9e0a31e3fc74d75311bd2f4 | |
parent | 751ffe4a74b5b7aa11f8449ed5d48e5f1a23e0fa (diff) |
Support loading SDM files at runtime.
This is the last piece of platform support for Cloud Compilation.
Bug: 377474232
Test: m test-art-host-gtest-art_runtime_tests
Test: atest art_standalone_runtime_tests
Change-Id: I3a7c1774c75334e55e9c98610745707dfcec35a2
-rw-r--r-- | artd/artd.cc | 4 | ||||
-rw-r--r-- | libartbase/base/file_utils.cc | 9 | ||||
-rw-r--r-- | libartbase/base/file_utils.h | 7 | ||||
-rw-r--r-- | runtime/dex2oat_environment_test.h | 52 | ||||
-rw-r--r-- | runtime/dexopt_test.cc | 32 | ||||
-rw-r--r-- | runtime/dexopt_test.h | 10 | ||||
-rw-r--r-- | runtime/oat/oat_file.cc | 123 | ||||
-rw-r--r-- | runtime/oat/oat_file.h | 7 | ||||
-rw-r--r-- | runtime/oat/oat_file_assistant.cc | 42 | ||||
-rw-r--r-- | runtime/oat/oat_file_assistant.h | 36 | ||||
-rw-r--r-- | runtime/oat/oat_file_assistant_test.cc | 305 |
11 files changed, 613 insertions, 14 deletions
diff --git a/artd/artd.cc b/artd/artd.cc index 59f6b88d9f..4ef1036fe1 100644 --- a/artd/artd.cc +++ b/artd/artd.cc @@ -238,6 +238,10 @@ ArtifactsLocation ArtifactsLocationToAidl(OatFileAssistant::Location location) { return ArtifactsLocation::NEXT_TO_DEX; case OatFileAssistant::Location::kLocationDm: return ArtifactsLocation::DM; + case OatFileAssistant::Location::kLocationSdmOat: + return ArtifactsLocation::SDM_DALVIK_CACHE; + case OatFileAssistant::Location::kLocationSdmOdex: + return ArtifactsLocation::SDM_NEXT_TO_DEX; // No default. All cases should be explicitly handled, or the compilation will fail. } // This should never happen. Just in case we get a non-enumerator value. diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc index 2acebb9b4f..4253fa1ce7 100644 --- a/libartbase/base/file_utils.cc +++ b/libartbase/base/file_utils.cc @@ -715,6 +715,15 @@ std::string GetDmFilename(const std::string& dex_location) { return ReplaceFileExtension(dex_location, kDmExtension); } +std::string GetSdmFilename(const std::string& dex_location, InstructionSet isa) { + return ReplaceFileExtension(dex_location, + StringPrintf("%s%s", GetInstructionSetString(isa), kSdmExtension)); +} + +std::string GetSdcFilename(const std::string& oat_location) { + return ReplaceFileExtension(oat_location, kSdcExtension); +} + // check for the file in /system, followed by /system_ext std::string GetSystemOdexFilenameForApex(std::string_view location, InstructionSet isa) { DCHECK(LocationIsOnApex(location)); diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h index e8aa5f663a..89f2420aa4 100644 --- a/libartbase/base/file_utils.h +++ b/libartbase/base/file_utils.h @@ -39,6 +39,7 @@ static constexpr const char* kVdexExtension = ".vdex"; static constexpr const char* kArtExtension = ".art"; static constexpr const char* kDmExtension = ".dm"; static constexpr const char* kSdmExtension = ".sdm"; +static constexpr const char* kSdcExtension = ".sdc"; // These methods return the Android Root, which is the historical location of // the Android "system" directory, containing the built Android artifacts. On @@ -174,6 +175,12 @@ std::string GetVdexFilename(const std::string& oat_filename); // Returns the dm filename for the given dex location. std::string GetDmFilename(const std::string& dex_location); +// Returns the sdm filename for the given dex location. +std::string GetSdmFilename(const std::string& dex_location, InstructionSet isa); + +// Returns the sdc filename for the given oat filename. +std::string GetSdcFilename(const std::string& oat_filename); + // Returns the odex location on /system for a DEX file on /apex. The caller must make sure that // `location` is on /apex. std::string GetSystemOdexFilenameForApex(std::string_view location, InstructionSet isa); diff --git a/runtime/dex2oat_environment_test.h b/runtime/dex2oat_environment_test.h index 7e8378aa67..53598bcfa0 100644 --- a/runtime/dex2oat_environment_test.h +++ b/runtime/dex2oat_environment_test.h @@ -20,13 +20,16 @@ #include <sys/wait.h> #include <fstream> +#include <memory> #include <optional> #include <string> #include <vector> +#include "android-base/file.h" #include "android-base/result.h" #include "android-base/strings.h" #include "base/file_utils.h" +#include "base/globals.h" #include "base/macros.h" #include "base/os.h" #include "base/stl_util.h" @@ -40,6 +43,7 @@ #include "gc/space/image_space.h" #include "gtest/gtest.h" #include "oat/oat_file_assistant.h" +#include "oat/sdc_file.h" #include "runtime.h" #include "ziparchive/zip_writer.h" @@ -256,7 +260,9 @@ class Dex2oatEnvironmentTest : public Dex2oatScratchDirs, public CommonRuntimeTe return WEXITSTATUS(res.status_code); } - void CreateDexMetadata(const std::string& vdex, const std::string& out_dm) { + void CreateDexMetadata(const std::string& vdex, + const std::string& out_dm, + bool page_aligned = false) { // Read the vdex bytes. std::unique_ptr<File> vdex_file(OS::OpenFileForReading(vdex.c_str())); std::vector<uint8_t> data(vdex_file->GetLength()); @@ -265,13 +271,55 @@ class Dex2oatEnvironmentTest : public Dex2oatScratchDirs, public CommonRuntimeTe // Zip the content. FILE* file = fopen(out_dm.c_str(), "wbe"); ZipWriter writer(file); - writer.StartEntry("primary.vdex", ZipWriter::kAlign32); + writer.StartAlignedEntry( + "primary.vdex", /*flags=*/0, /*alignment=*/page_aligned ? kMaxPageSize : 4); writer.WriteBytes(data.data(), data.size()); writer.FinishEntry(); writer.Finish(); fflush(file); fclose(file); } + + void CreateSecureDexMetadata(const std::string& odex, + const std::string& art, + const std::string& out_sdm) { + // Zip the content. + std::unique_ptr<File> sdm_file(OS::CreateEmptyFileWriteOnly(out_sdm.c_str())); + ASSERT_NE(sdm_file, nullptr); + ZipWriter writer(fdopen(sdm_file->Fd(), "wb")); + + std::string odex_data; + ASSERT_TRUE(android::base::ReadFileToString(odex, &odex_data)); + writer.StartAlignedEntry("primary.odex", /*flags=*/0, /*alignment=*/kMaxPageSize); + writer.WriteBytes(odex_data.data(), odex_data.size()); + writer.FinishEntry(); + + if (!art.empty()) { + std::string art_data; + ASSERT_TRUE(android::base::ReadFileToString(art, &art_data)); + writer.StartAlignedEntry("primary.art", /*flags=*/0, /*alignment=*/kMaxPageSize); + writer.WriteBytes(art_data.data(), art_data.size()); + writer.FinishEntry(); + } + + writer.Finish(); + ASSERT_EQ(sdm_file->FlushClose(), 0); + } + + void CreateSecureDexMetadataCompanion(const std::string& sdm, + const std::string& apex_versions, + const std::string& out_sdc) { + struct stat sdm_st; + ASSERT_EQ(stat(sdm.c_str(), &sdm_st), 0); + + std::unique_ptr<File> sdc_file(OS::CreateEmptyFileWriteOnly(out_sdc.c_str())); + ASSERT_NE(sdc_file, nullptr); + SdcWriter sdc_writer(std::move(*sdc_file)); + sdc_writer.SetSdmTimestampNs(TimeSpecToNs(sdm_st.st_mtim)); + sdc_writer.SetApexVersions(apex_versions); + std::string error_msg; + ASSERT_TRUE(sdc_writer.Save(&error_msg)) << error_msg; + } }; } // namespace art diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc index 40e10142d7..6239f7de87 100644 --- a/runtime/dexopt_test.cc +++ b/runtime/dexopt_test.cc @@ -202,6 +202,38 @@ void DexoptTest::GenerateOatForTest(const char* dex_location, CompilerFilter::Fi GenerateOatForTest(dex_location, filter, /*with_alternate_image=*/false); } +void DexoptTest::GenerateSdmDmForTest(const std::string& dex_location, + const std::string& sdm_location, + const std::string& dm_location, + CompilerFilter::Filter filter, + bool include_app_image, + const char* compilation_reason, + const std::vector<std::string>& extra_args) { + std::string tmp_dir = GetScratchDir() + "/sdm_tmp"; + ASSERT_EQ(0, mkdir(tmp_dir.c_str(), 0700)); + + std::string odex_location = tmp_dir + "/TestDex.odex"; + std::string vdex_location = tmp_dir + "/TestDex.vdex"; + std::string art_location; + + std::vector<std::string> extra_args_with_app_image = extra_args; + if (include_app_image) { + art_location = tmp_dir + "/TestDex.art"; + extra_args_with_app_image.push_back("--app-image-file=" + art_location); + } + + // Generate temporary ODEX, VDEX, and ART files in order to create the SDM and DM files from. + ASSERT_NO_FATAL_FAILURE(GenerateOdexForTest( + dex_location, odex_location, filter, compilation_reason, extra_args_with_app_image)); + + // Create the SDM and DM files. + ASSERT_NO_FATAL_FAILURE(CreateSecureDexMetadata(odex_location, art_location, sdm_location)); + ASSERT_NO_FATAL_FAILURE(CreateDexMetadata(vdex_location, dm_location, /*page_aligned=*/true)); + + // Cleanup the temporary files. + ASSERT_NO_FATAL_FAILURE(ClearDirectory(tmp_dir.c_str())); +} + void DexoptTest::ReserveImageSpace() { MemMap::Init(); diff --git a/runtime/dexopt_test.h b/runtime/dexopt_test.h index cf32785c0b..fa18fdd94b 100644 --- a/runtime/dexopt_test.h +++ b/runtime/dexopt_test.h @@ -64,6 +64,16 @@ class DexoptTest : public Dex2oatEnvironmentTest { // Generate a standard oat file in the oat location. void GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter); + // Generate sdm and dm files for the purposes of test. + // If `include_app_image` is true, generates an app image and includes it in the sdm file. + void GenerateSdmDmForTest(const std::string& dex_location, + const std::string& sdm_location, + const std::string& dm_location, + CompilerFilter::Filter filter, + bool include_app_image, + const char* compilation_reason = nullptr, + const std::vector<std::string>& extra_args = {}); + bool Dex2Oat(const std::vector<std::string>& args, std::string* error_msg); private: diff --git a/runtime/oat/oat_file.cc b/runtime/oat/oat_file.cc index 3f72401f43..ec845cd0c1 100644 --- a/runtime/oat/oat_file.cc +++ b/runtime/oat/oat_file.cc @@ -20,25 +20,34 @@ #include <sys/stat.h> #include <unistd.h> +#include <cstddef> #include <cstdint> +#include <cstdio> #include <cstdlib> #include <cstring> +#include <memory> #include <optional> #include <sstream> +#include <string> #include <type_traits> +#include "android-base/file.h" #include "android-base/logging.h" #include "android-base/stringprintf.h" #include "arch/instruction_set_features.h" #include "art_method.h" +#include "base/array_ref.h" #include "base/bit_vector.h" #include "base/file_utils.h" +#include "base/globals.h" #include "base/logging.h" // For VLOG_IS_ON. +#include "base/macros.h" #include "base/mem_map.h" #include "base/os.h" #include "base/pointer_size.h" #include "base/stl_util.h" #include "base/systrace.h" +#include "base/time_utils.h" #include "base/unix_file/fd_file.h" #include "base/utils.h" #include "base/zip_archive.h" @@ -59,6 +68,7 @@ #include "mirror/class.h" #include "mirror/object-inl.h" #include "oat.h" +#include "oat/sdc_file.h" #include "oat_file-inl.h" #include "oat_file_manager.h" #include "runtime-inl.h" @@ -174,6 +184,14 @@ class OatFileBase : public OatFile { /*inout*/ MemMap* reservation, // Where to load if not null. /*out*/ std::string* error_msg); + template <typename kOatFileBaseSubType> + static OatFileBase* OpenOatFileFromSdm(const std::string& sdm_filename, + const std::string& sdc_filename, + const std::string& dm_filename, + const std::string& dex_filename, + bool executable, + /*out*/ std::string* error_msg); + protected: OatFileBase(const std::string& filename, bool executable) : OatFile(filename, executable) {} @@ -316,6 +334,61 @@ OatFileBase* OatFileBase::OpenOatFile(int zip_fd, return ret.release(); } +template <typename kOatFileBaseSubType> +OatFileBase* OatFileBase::OpenOatFileFromSdm(const std::string& sdm_filename, + const std::string& sdc_filename, + const std::string& dm_filename, + const std::string& dex_filename, + bool executable, + /*out*/ std::string* error_msg) { + std::string elf_filename = sdm_filename + kZipSeparator + "primary.odex"; + std::unique_ptr<OatFileBase> ret(new kOatFileBaseSubType(elf_filename, executable)); + + struct stat sdm_st; + if (stat(sdm_filename.c_str(), &sdm_st) != 0) { + *error_msg = ART_FORMAT("Failed to stat sdm file '{}': {}", sdm_filename, strerror(errno)); + return nullptr; + } + + std::unique_ptr<SdcReader> sdc_reader = SdcReader::Load(sdc_filename, error_msg); + if (sdc_reader == nullptr) { + return nullptr; + } + if (sdc_reader->GetSdmTimestampNs() != TimeSpecToNs(sdm_st.st_mtim)) { + // The sdm file had been replaced after the sdc file was created. + *error_msg = ART_FORMAT("Obsolete sdc file '{}'", sdc_filename); + return nullptr; + } + // The apex-versions value in the sdc file, written by ART Service, is the value of + // `Runtime::GetApexVersions` at the time where the sdm file was first seen on device. We use it + // to override the APEX versions in the oat header. This is for detecting samegrade placebos. + ret->override_apex_versions_ = sdc_reader->GetApexVersions(); + + if (!ret->Load(elf_filename, executable, /*low_4gb=*/false, /*reservation=*/nullptr, error_msg)) { + return nullptr; + } + + if (!ret->ComputeFields(elf_filename, error_msg)) { + return nullptr; + } + + ret->PreSetup(elf_filename); + + ret->vdex_ = VdexFile::OpenFromDm(dm_filename, ret->vdex_begin_, ret->vdex_end_, error_msg); + if (ret->vdex_ == nullptr) { + return nullptr; + } + + if (!ret->Setup(/*zip_fd=*/-1, + ArrayRef<const std::string>(&dex_filename, /*size=*/1u), + /*dex_files=*/{}, + error_msg)) { + return nullptr; + } + + return ret.release(); +} + bool OatFileBase::LoadVdex(const std::string& vdex_filename, bool low_4gb, std::string* error_msg) { vdex_ = VdexFile::OpenAtAddress(vdex_begin_, vdex_end_ - vdex_begin_, @@ -1330,11 +1403,11 @@ bool DlOpenOatFile::Dlopen(const std::string& elf_filename, return false; #else { - UniqueCPtr<char> absolute_path(realpath(elf_filename.c_str(), nullptr)); - if (absolute_path == nullptr) { - *error_msg = StringPrintf("Failed to find absolute path for '%s'", elf_filename.c_str()); - return false; - } + // `elf_filename` is in the format of `/path/to/oat` or `/path/to/zip!/primary.odex`. We can + // reuse `GetDexCanonicalLocation` to resolve the real path of the part before "!" even though + // `elf_filename` does not refer to a dex file. + static_assert(std::string_view(kZipSeparator).starts_with(DexFileLoader::kMultiDexSeparator)); + std::string absolute_path = DexFileLoader::GetDexCanonicalLocation(elf_filename.c_str()); #ifdef ART_TARGET_ANDROID android_dlextinfo extinfo = {}; extinfo.flags = ANDROID_DLEXT_FORCE_LOAD; // Force-load, don't reuse handle @@ -1350,9 +1423,9 @@ bool DlOpenOatFile::Dlopen(const std::string& elf_filename, } if (strncmp(kAndroidArtApexDefaultPath, - absolute_path.get(), + absolute_path.c_str(), sizeof(kAndroidArtApexDefaultPath) - 1) != 0 || - absolute_path.get()[sizeof(kAndroidArtApexDefaultPath) - 1] != '/') { + absolute_path.c_str()[sizeof(kAndroidArtApexDefaultPath) - 1] != '/') { // Use the system namespace for OAT files outside the ART APEX. Search // paths and links don't matter here, but permitted paths do, and the // system namespace is configured to allow loading from all appropriate @@ -1361,7 +1434,7 @@ bool DlOpenOatFile::Dlopen(const std::string& elf_filename, extinfo.library_namespace = GetSystemLinkerNamespace(); } - dlopen_handle_ = android_dlopen_ext(absolute_path.get(), RTLD_NOW, &extinfo); + dlopen_handle_ = android_dlopen_ext(absolute_path.c_str(), RTLD_NOW, &extinfo); if (reservation != nullptr && dlopen_handle_ != nullptr) { // Find used pages from the reservation. struct dl_iterate_context { @@ -1435,7 +1508,7 @@ bool DlOpenOatFile::Dlopen(const std::string& elf_filename, return false; } MutexLock mu(Thread::Current(), *Locks::host_dlopen_handles_lock_); - dlopen_handle_ = dlopen(absolute_path.get(), RTLD_NOW); + dlopen_handle_ = dlopen(absolute_path.c_str(), RTLD_NOW); if (dlopen_handle_ != nullptr) { if (!host_dlopen_handles_.insert(dlopen_handle_).second) { dlclose(dlopen_handle_); @@ -2034,6 +2107,38 @@ OatFile* OatFile::OpenFromVdex(int zip_fd, return OatFileBackedByVdex::Open(zip_fd, std::move(vdex_file), location, context, error_msg); } +OatFile* OatFile::OpenFromSdm(const std::string& sdm_filename, + const std::string& sdc_filename, + const std::string& dm_filename, + const std::string& dex_filename, + bool executable, + /*out*/ std::string* error_msg) { + ScopedTrace trace("Open sdm file " + sdm_filename); + CHECK(!sdm_filename.empty()); + CHECK(!sdc_filename.empty()); + CHECK(!dm_filename.empty()); + CHECK(!dex_filename.empty()); + + // Check if the dm file exists, to fail fast. The dm file contains the vdex that is essential for + // using the odex in the sdm file. + if (!OS::FileExists(dm_filename.c_str())) { + *error_msg = + ART_FORMAT("Not loading sdm file because dm file '{}' does not exist", dm_filename); + return nullptr; + } + + // Try dlopen first, as it is required for native debuggability. This will fail fast if dlopen is + // disabled. + OatFile* with_dlopen = OatFileBase::OpenOatFileFromSdm<DlOpenOatFile>( + sdm_filename, sdc_filename, dm_filename, dex_filename, executable, error_msg); + if (with_dlopen != nullptr) { + return with_dlopen; + } + + return OatFileBase::OpenOatFileFromSdm<ElfOatFile>( + sdm_filename, sdc_filename, dm_filename, dex_filename, executable, error_msg); +} + OatFile::OatFile(const std::string& location, bool is_executable) : location_(location), vdex_(nullptr), diff --git a/runtime/oat/oat_file.h b/runtime/oat/oat_file.h index 53f1d173d1..33645fa4ba 100644 --- a/runtime/oat/oat_file.h +++ b/runtime/oat/oat_file.h @@ -187,6 +187,13 @@ class OatFile { ClassLoaderContext* context, std::string* error_msg); + static OatFile* OpenFromSdm(const std::string& sdm_filename, + const std::string& sdc_filename, + const std::string& dm_filename, + const std::string& dex_filename, + bool executable, + /*out*/ std::string* error_msg); + // Set the start of the app image. // Needed for initializing app image relocations in the .data.img.rel.ro section. void SetAppImageBegin(uint8_t* app_image_begin) const { diff --git a/runtime/oat/oat_file_assistant.cc b/runtime/oat/oat_file_assistant.cc index 8df8459589..53f412908d 100644 --- a/runtime/oat/oat_file_assistant.cc +++ b/runtime/oat/oat_file_assistant.cc @@ -191,6 +191,12 @@ OatFileAssistant::OatFileAssistant(const char* dex_location, oat_file_name, /*is_oat_location=*/true, /*use_fd=*/false)); + info_list_.push_back( + std::make_unique<OatFileInfoBackedBySdm>(this, + GetSdmFilename(dex_location_, isa), + /*is_oat_location=*/true, + GetDmFilename(dex_location_), + GetSdcFilename(oat_file_name))); } if (!odex_file_name.empty()) { @@ -202,6 +208,12 @@ OatFileAssistant::OatFileAssistant(const char* dex_location, zip_fd, vdex_fd, oat_fd)); + info_list_.push_back( + std::make_unique<OatFileInfoBackedBySdm>(this, + GetSdmFilename(dex_location_, isa), + /*is_oat_location=*/false, + GetDmFilename(dex_location_), + GetSdcFilename(odex_file_name))); } // When there is no odex/oat available (e.g., they are both out of date), we look for a useable @@ -324,7 +336,8 @@ int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target_compiler_fil OatFileInfo& info = GetBestInfo(); DexOptNeeded dexopt_needed = info.GetDexOptNeeded( target_compiler_filter, GetDexOptTrigger(target_compiler_filter, profile_changed, downgrade)); - if (dexopt_needed != kNoDexOptNeeded && info.GetType() == OatFileType::kDm) { + if (dexopt_needed != kNoDexOptNeeded && + (info.GetType() == OatFileType::kDm || info.GetType() == OatFileType::kSdm)) { // 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. @@ -956,6 +969,10 @@ bool OatFileAssistant::OatFileInfoBackedByOat::FileExists() const { return use_fd_ || OatFileInfo::FileExists(); } +bool OatFileAssistant::OatFileInfoBackedBySdm::FileExists() const { + return OatFileInfo::FileExists() && OS::FileExists(sdc_filename_.c_str()); +} + bool OatFileAssistant::OatFileInfoBackedByVdex::FileExists() const { return use_fd_ || OatFileInfo::FileExists(); } @@ -1015,6 +1032,21 @@ std::unique_ptr<OatFile> OatFileAssistant::OatFileInfoBackedByOat::LoadFile( } } +std::unique_ptr<OatFile> OatFileAssistant::OatFileInfoBackedBySdm::LoadFile( + std::string* error_msg) const { + bool executable = oat_file_assistant_->load_executable_; + if (executable && oat_file_assistant_->only_load_trusted_executable_) { + executable = LocationIsTrusted(filename_, /*trust_art_apex_data_files=*/true); + } + + return std::unique_ptr<OatFile>(OatFile::OpenFromSdm(filename_, + sdc_filename_, + dm_filename_, + oat_file_assistant_->dex_location_, + executable, + error_msg)); +} + std::unique_ptr<OatFile> OatFileAssistant::OatFileInfoBackedByVdex::LoadFile( std::string* error_msg) const { // Check to see if there is a vdex file we can make use of. @@ -1289,7 +1321,13 @@ bool OatFileAssistant::ZipFileOnlyContainsUncompressedDex() { OatFileAssistant::Location OatFileAssistant::GetLocation(OatFileInfo& info) { if (info.IsUseable()) { - if (info.GetType() == OatFileType::kDm) { + if (info.GetType() == OatFileType::kSdm) { + if (info.IsOatLocation()) { + return kLocationSdmOat; + } else { + return kLocationSdmOdex; + } + } else if (info.GetType() == OatFileType::kDm) { return kLocationDm; } else if (info.IsOatLocation()) { return kLocationOat; diff --git a/runtime/oat/oat_file_assistant.h b/runtime/oat/oat_file_assistant.h index bcb0c8e9db..80eabba34b 100644 --- a/runtime/oat/oat_file_assistant.h +++ b/runtime/oat/oat_file_assistant.h @@ -123,8 +123,14 @@ class OatFileAssistant { kLocationOat = 1, // In the "oat" folder next to the dex file. kLocationOdex = 2, - // In the DM file. This means the only usable file is the vdex file. + // In the dm file. This means the only usable file is the vdex file. kLocationDm = 3, + // The oat and art files are in the sdm file next to the dex file. The vdex file is in the dm + // file next to the dex file. The sdc file is in the global "dalvik-cache" folder. + kLocationSdmOat = 4, + // The oat and art files are in the sdm file next to the dex file. The vdex file is in the dm + // file next to the dex file. The sdc file is next to the dex file. + kLocationSdmOdex = 5, }; // Represents the status of the current oat file and/or vdex file. @@ -381,6 +387,7 @@ class OatFileAssistant { enum class OatFileType { kNone, kOat, + kSdm, kVdex, kDm, }; @@ -529,6 +536,33 @@ class OatFileAssistant { const int oat_fd_; }; + class OatFileInfoBackedBySdm : public OatFileInfo { + public: + OatFileInfoBackedBySdm(OatFileAssistant* oat_file_assistant, + const std::string& sdm_filename, + bool is_oat_location, + const std::string& dm_filename, + const std::string& sdc_filename) + : OatFileInfo(oat_file_assistant, sdm_filename, is_oat_location), + dm_filename_(dm_filename), + sdc_filename_(sdc_filename) {} + + OatFileType GetType() override { return OatFileType::kSdm; } + + const char* GetLocationDebugString() override { + return IsOatLocation() ? "sdm with sdc in dalvik-cache" : "sdm with sdc next to the dex file"; + } + + bool FileExists() const override; + + protected: + std::unique_ptr<OatFile> LoadFile(std::string* error_msg) const override; + + private: + const std::string dm_filename_; + const std::string sdc_filename_; + }; + class OatFileInfoBackedByVdex : public OatFileInfo { public: OatFileInfoBackedByVdex(OatFileAssistant* oat_file_assistant, diff --git a/runtime/oat/oat_file_assistant_test.cc b/runtime/oat/oat_file_assistant_test.cc index 5bd9d1ed32..c3e2f63e24 100644 --- a/runtime/oat/oat_file_assistant_test.cc +++ b/runtime/oat/oat_file_assistant_test.cc @@ -33,6 +33,7 @@ #include "android-base/strings.h" #include "arch/instruction_set.h" #include "art_field-inl.h" +#include "base/file_utils.h" #include "base/os.h" #include "base/utils.h" #include "class_linker.h" @@ -2183,6 +2184,310 @@ TEST_P(OatFileAssistantTest, ShouldRecompileForImageFromSpeedProfile) { oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify)); } +// Case: We have SDM, DM, and SDC files for an uncompressed DEX file, and the SDC file is in odex +// location. +// Expect: The best artifact location should be kLocationSdmOdex. Dexopt should be performed only if +// the compiler filter is better than "speed-profile". +// +// The legacy version should return kDex2OatFromScratch if the target compiler filter is better than +// "verify". +TEST_P(OatFileAssistantTest, SdmUpToDate) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + std::string sdc_location = GetOdexDir() + "/TestDex.sdc"; + Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/true, + /*compilation_reason=*/"cloud")); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location)); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance(&oat_file_assistant, + "speed-profile", + "cloud", + "up-to-date", + OatFileAssistant::kLocationSdmOdex); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kSpeed, + default_trigger_, + /*expected_dexopt_needed=*/true, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationSdmOdex); + EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kSpeedProfile, + default_trigger_, + /*expected_dexopt_needed=*/false, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationSdmOdex); + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile)); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kVerify, + default_trigger_, + /*expected_dexopt_needed=*/false, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationSdmOdex); + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify)); +} + +// Case: We have SDM, DM, and SDC files for an uncompressed DEX file, and the SDC file is in oat +// location. +// Expect: The best artifact location should be kLocationSdmOat. +TEST_P(OatFileAssistantTest, SdmUpToDateSdcInOatLocation) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location); + + std::string oat_location; + std::string error_msg; + ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename( + dex_location, kRuntimeISA, &oat_location, &error_msg)) + << error_msg; + std::string sdc_location = GetSdcFilename(oat_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/true, + /*compilation_reason=*/"cloud")); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location)); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance(&oat_file_assistant, + "speed-profile", + "cloud", + "up-to-date", + OatFileAssistant::kLocationSdmOat); +} + +// Case: We have SDM, DM, and SDC files for an uncompressed DEX file, and the SDM file contains no +// ART file. +// Expect: The best artifact location should be kLocationSdmOdex. +TEST_P(OatFileAssistantTest, SdmUpToDateNoArt) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + std::string sdc_location = GetOdexDir() + "/TestDex.sdc"; + Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/false, + /*compilation_reason=*/"cloud")); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location)); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance(&oat_file_assistant, + "speed-profile", + "cloud", + "up-to-date", + OatFileAssistant::kLocationSdmOdex); +} + +// Case: We have SDM, DM, and SDC files for an uncompressed DEX file. Meanwhile, we have an ODEX +// file that is also up to date. +// Expect: The ODEX file is preferred over the SDM file. The best artifact location should be +// kLocationOdex. +TEST_P(OatFileAssistantTest, SdmAndOdexUpToDate) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + std::string sdc_location = GetOdexDir() + "/TestDex.sdc"; + std::string odex_location = GetOdexDir() + "/TestDex.odex"; + Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/true, + /*compilation_reason=*/"cloud")); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location)); + + ASSERT_NO_FATAL_FAILURE(GenerateOdexForTest(dex_location, + odex_location, + CompilerFilter::kSpeedProfile, + /*compilation_reason=*/"bg-dexopt")); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance(&oat_file_assistant, + "speed-profile", + "bg-dexopt", + "up-to-date", + OatFileAssistant::kLocationOdex); +} + +// Case: We have SDM, DM, and SDC files for an uncompressed DEX file. Meanwhile, we have a VDEX +// file that is also up to date. +// Expect: The SDM file is preferred over the VDEX file. The best artifact location should be +// kLocationSdmOdex. +TEST_P(OatFileAssistantTest, SdmAndVdexUpToDate) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + std::string sdc_location = GetOdexDir() + "/TestDex.sdc"; + std::string odex_location = GetOdexDir() + "/TestDex.odex"; + Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/true, + /*compilation_reason=*/"cloud")); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location)); + + ASSERT_NO_FATAL_FAILURE(GenerateOdexForTest(dex_location, + odex_location, + CompilerFilter::kSpeedProfile, + /*compilation_reason=*/"bg-dexopt")); + ASSERT_EQ(0, unlink(odex_location.c_str())); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance(&oat_file_assistant, + "speed-profile", + "cloud", + "up-to-date", + OatFileAssistant::kLocationSdmOdex); +} + +// Case: We have SDM, DM, and SDC files for a compressed DEX file. +// Expect: The SDM file is still picked. Dexopt should be performed if the compiler filter is +// "speed-profile" or above. +TEST_P(OatFileAssistantTest, SdmUpToDateCompressedDex) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + std::string sdc_location = GetOdexDir() + "/TestDex.sdc"; + Copy(GetMultiDexSrc1(), dex_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/true, + /*compilation_reason=*/"cloud", + /*extra_args=*/{"--copy-dex-files=false"})); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location)); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance(&oat_file_assistant, + "speed-profile", + "cloud", + "up-to-date", + OatFileAssistant::kLocationSdmOdex); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kSpeedProfile, + default_trigger_, + /*expected_dexopt_needed=*/true, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationSdmOdex); + EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile)); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kVerify, + default_trigger_, + /*expected_dexopt_needed=*/false, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationSdmOdex); + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify)); +} + +// Case: We have SDM, DM, and SDC files for an uncompressed DEX file, but the SDC file contains the +// wrong APEX versions. +// Expect: The SDM file is rejected, while the DM file is still picked. Dexopt should be performed +// if the compiler filter is better than "verify". +TEST_P(OatFileAssistantTest, SdmApexVersionMismatch) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + std::string sdc_location = GetOdexDir() + "/TestDex.sdc"; + Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/true, + /*compilation_reason=*/"cloud")); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, "wrong-apex-version", sdc_location)); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance( + &oat_file_assistant, "verify", "vdex", "up-to-date", OatFileAssistant::kLocationDm); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kSpaceProfile, + default_trigger_, + /*expected_dexopt_needed=*/true, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationDm); + EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpaceProfile)); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kVerify, + default_trigger_, + /*expected_dexopt_needed=*/false, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationDm); + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify)); +} + class CollectDexCacheVisitor : public DexCacheVisitor { public: explicit CollectDexCacheVisitor( |