diff options
| -rw-r--r-- | artd/Android.bp | 1 | ||||
| -rw-r--r-- | artd/artd.cc | 151 | ||||
| -rw-r--r-- | artd/artd.h | 11 | ||||
| -rw-r--r-- | artd/artd_test.cc | 115 | ||||
| -rw-r--r-- | artd/binder/com/android/server/art/IArtd.aidl | 9 |
5 files changed, 258 insertions, 29 deletions
diff --git a/artd/Android.bp b/artd/Android.bp index 6de6aef96d..5449cd90a6 100644 --- a/artd/Android.bp +++ b/artd/Android.bp @@ -39,6 +39,7 @@ cc_defaults { "libarttools", // Contains "libc++fs". "libbase", "libbinder_ndk", + "libdexfile", "libselinux", ], static_libs: [ diff --git a/artd/artd.cc b/artd/artd.cc index 8b46d91803..b96701c4d6 100644 --- a/artd/artd.cc +++ b/artd/artd.cc @@ -25,6 +25,7 @@ #include <climits> #include <csignal> +#include <cstddef> #include <cstdint> #include <cstring> #include <filesystem> @@ -62,9 +63,12 @@ #include "base/globals.h" #include "base/logging.h" #include "base/macros.h" +#include "base/mem_map.h" +#include "base/memfd.h" #include "base/os.h" #include "base/zip_archive.h" #include "cmdline_types.h" +#include "dex/dex_file_loader.h" #include "exec_utils.h" #include "file_utils.h" #include "fstab/fstab.h" @@ -101,6 +105,7 @@ using ::aidl::com::android::server::art::ProfilePath; using ::aidl::com::android::server::art::RuntimeArtifactsPath; using ::aidl::com::android::server::art::VdexPath; using ::android::base::Dirname; +using ::android::base::ErrnoError; using ::android::base::Error; using ::android::base::Join; using ::android::base::make_scope_guard; @@ -398,6 +403,74 @@ CopyAndRewriteProfileResult AnalyzeCopyAndRewriteProfileFailure( return bad_profile("The profile is in the wrong format or an I/O error has occurred"); } +// Returns the fd on success, or an invalid fd if the dex file contains no profile, or error if any +// error occurs. +Result<File> ExtractEmbeddedProfileToFd(const std::string& dex_path) { + std::unique_ptr<File> dex_file = OR_RETURN(OpenFileForReading(dex_path)); + + std::string error_msg; + uint32_t magic; + if (!ReadMagicAndReset(dex_file->Fd(), &magic, &error_msg)) { + return Error() << error_msg; + } + if (!IsZipMagic(magic)) { + if (DexFileLoader::IsMagicValid(magic)) { + // The dex file can be a plain dex file. This is expected. + return File(); + } + return Error() << "File is neither a zip file nor a plain dex file"; + } + + std::unique_ptr<ZipArchive> zip_archive( + ZipArchive::OpenFromOwnedFd(dex_file->Fd(), dex_path.c_str(), &error_msg)); + if (zip_archive == nullptr) { + return Error() << error_msg; + } + constexpr const char* kEmbeddedProfileEntry = "assets/art-profile/baseline.prof"; + std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(kEmbeddedProfileEntry, &error_msg)); + size_t size; + if (zip_entry == nullptr || (size = zip_entry->GetUncompressedLength()) == 0) { + // From system/libziparchive/zip_error.cpp. + constexpr const char* kEntryNotFound = "Entry not found"; + if (error_msg != kEntryNotFound) { + LOG(WARNING) << ART_FORMAT( + "Failed to find zip entry '{}' in '{}': {}", kEmbeddedProfileEntry, dex_path, error_msg); + } + // The dex file doesn't necessarily contain a profile. This is expected. + return File(); + } + + // The name is for debugging only. + std::string memfd_name = + ART_FORMAT("{} extracted in memory from {}", kEmbeddedProfileEntry, dex_path); + File memfd(memfd_create(memfd_name.c_str(), /*flags=*/0), + memfd_name, + /*check_usage=*/false); + if (!memfd.IsValid()) { + return ErrnoError() << "Failed to create memfd"; + } + if (ftruncate(memfd.Fd(), size) != 0) { + return ErrnoError() << "Failed to ftruncate memfd"; + } + // Map with MAP_SHARED because we're feeding the fd to profman. + MemMap mem_map = MemMap::MapFile(size, + PROT_READ | PROT_WRITE, + MAP_SHARED, + memfd.Fd(), + /*start=*/0, + /*low_4gb=*/false, + memfd_name.c_str(), + &error_msg); + if (!mem_map.IsValid()) { + return Errorf("Failed to mmap memfd: {}", error_msg); + } + if (!zip_entry->ExtractToMemory(mem_map.Begin(), &error_msg)) { + return Errorf("Failed to extract '{}': {}", kEmbeddedProfileEntry, error_msg); + } + + return memfd; +} + class FdLogger { public: void Add(const NewFile& file) { fd_mapping_.emplace_back(file.Fd(), file.TempPath()); } @@ -545,13 +618,12 @@ ndk::ScopedAStatus Artd::isProfileUsable(const ProfilePath& in_profile, return ScopedAStatus::ok(); } -ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src, - OutputProfile* in_dst, - const std::string& in_dexFile, - CopyAndRewriteProfileResult* _aidl_return) { - std::string src_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_src)); - std::string dst_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_dst->profilePath)); - OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); +ndk::ScopedAStatus Artd::CopyAndRewriteProfileImpl(File src, + OutputProfile* dst_aidl, + const std::string& dex_path, + CopyAndRewriteProfileResult* aidl_return) { + std::string dst_path = OR_RETURN_FATAL(BuildFinalProfilePath(dst_aidl->profilePath)); + OR_RETURN_FATAL(ValidateDexPath(dex_path)); FdLogger fd_logger; @@ -561,24 +633,15 @@ ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src, CmdlineBuilder args; args.Add(OR_RETURN_FATAL(GetProfman())).Add("--copy-and-update-profile-key"); - Result<std::unique_ptr<File>> src = OpenFileForReading(src_path); - if (!src.ok()) { - if (src.error().code() == ENOENT) { - _aidl_return->status = CopyAndRewriteProfileResult::Status::NO_PROFILE; - return ScopedAStatus::ok(); - } - return NonFatal( - ART_FORMAT("Failed to open src profile '{}': {}", src_path, src.error().message())); - } - args.Add("--profile-file-fd=%d", src.value()->Fd()); - fd_logger.Add(*src.value()); + args.Add("--profile-file-fd=%d", src.Fd()); + fd_logger.Add(src); - std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile)); + std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(dex_path)); args.Add("--apk-fd=%d", dex_file->Fd()); fd_logger.Add(*dex_file); std::unique_ptr<NewFile> dst = - OR_RETURN_NON_FATAL(NewFile::Create(dst_path, in_dst->fsPermission)); + OR_RETURN_NON_FATAL(NewFile::Create(dst_path, dst_aidl->fsPermission)); args.Add("--reference-profile-file-fd=%d", dst->Fd()); fd_logger.Add(*dst); @@ -596,8 +659,8 @@ ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src, if (result.value() == ProfmanResult::kCopyAndUpdateNoMatch || result.value() == ProfmanResult::kCopyAndUpdateErrorFailedToLoadProfile) { - *_aidl_return = AnalyzeCopyAndRewriteProfileFailure( - src->get(), static_cast<ProfmanResult::CopyAndUpdateResult>(result.value())); + *aidl_return = AnalyzeCopyAndRewriteProfileFailure( + &src, static_cast<ProfmanResult::CopyAndUpdateResult>(result.value())); return ScopedAStatus::ok(); } @@ -606,12 +669,49 @@ ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src, } OR_RETURN_NON_FATAL(dst->Keep()); - _aidl_return->status = CopyAndRewriteProfileResult::Status::SUCCESS; - in_dst->profilePath.id = dst->TempId(); - in_dst->profilePath.tmpPath = dst->TempPath(); + aidl_return->status = CopyAndRewriteProfileResult::Status::SUCCESS; + dst_aidl->profilePath.id = dst->TempId(); + dst_aidl->profilePath.tmpPath = dst->TempPath(); return ScopedAStatus::ok(); } +ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src, + OutputProfile* in_dst, + const std::string& in_dexFile, + CopyAndRewriteProfileResult* _aidl_return) { + std::string src_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_src)); + + Result<std::unique_ptr<File>> src = OpenFileForReading(src_path); + if (!src.ok()) { + if (src.error().code() == ENOENT) { + _aidl_return->status = CopyAndRewriteProfileResult::Status::NO_PROFILE; + return ScopedAStatus::ok(); + } + return NonFatal( + ART_FORMAT("Failed to open src profile '{}': {}", src_path, src.error().message())); + } + + return CopyAndRewriteProfileImpl(std::move(*src.value()), in_dst, in_dexFile, _aidl_return); +} + +ndk::ScopedAStatus Artd::copyAndRewriteEmbeddedProfile(OutputProfile* in_dst, + const std::string& in_dexFile, + CopyAndRewriteProfileResult* _aidl_return) { + OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); + + Result<File> src = ExtractEmbeddedProfileToFd(in_dexFile); + if (!src.ok()) { + return NonFatal(ART_FORMAT( + "Failed to extract profile from dex file '{}': {}", in_dexFile, src.error().message())); + } + if (!src->IsValid()) { + _aidl_return->status = CopyAndRewriteProfileResult::Status::NO_PROFILE; + return ScopedAStatus::ok(); + } + + return CopyAndRewriteProfileImpl(std::move(src.value()), in_dst, in_dexFile, _aidl_return); +} + ndk::ScopedAStatus Artd::commitTmpProfile(const TmpProfilePath& in_profile) { std::string tmp_profile_path = OR_RETURN_FATAL(BuildTmpProfilePath(in_profile)); std::string ref_profile_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_profile)); @@ -1181,6 +1281,7 @@ ScopedAStatus Artd::deleteRuntimeArtifacts(const RuntimeArtifactsPath& in_runtim Result<void> Artd::Start() { OR_RETURN(SetLogVerbosity()); + MemMap::Init(); ScopedAStatus status = ScopedAStatus::fromStatus( AServiceManager_registerLazyService(this->asBinder().get(), kServiceName)); diff --git a/artd/artd.h b/artd/artd.h index c78aaa9a05..c6708475bd 100644 --- a/artd/artd.h +++ b/artd/artd.h @@ -101,6 +101,11 @@ class Artd : public aidl::com::android::server::art::BnArtd { const std::string& in_dexFile, aidl::com::android::server::art::CopyAndRewriteProfileResult* _aidl_return) override; + ndk::ScopedAStatus copyAndRewriteEmbeddedProfile( + aidl::com::android::server::art::OutputProfile* in_dst, + const std::string& in_dexFile, + aidl::com::android::server::art::CopyAndRewriteProfileResult* _aidl_return) override; + ndk::ScopedAStatus commitTmpProfile( const aidl::com::android::server::art::ProfilePath::TmpProfilePath& in_profile) override; @@ -221,6 +226,12 @@ class Artd : public aidl::com::android::server::art::BnArtd { android::base::Result<struct stat> Fstat(const art::File& file) const; + ndk::ScopedAStatus CopyAndRewriteProfileImpl( + File src, + aidl::com::android::server::art::OutputProfile* dst_aidl, + const std::string& dex_path, + aidl::com::android::server::art::CopyAndRewriteProfileResult* aidl_return); + std::mutex cache_mu_; std::optional<std::vector<std::string>> cached_boot_image_locations_ GUARDED_BY(cache_mu_); std::optional<std::vector<std::string>> cached_boot_class_path_ GUARDED_BY(cache_mu_); diff --git a/artd/artd_test.cc b/artd/artd_test.cc index 4af8f17e5a..6b93cee99f 100644 --- a/artd/artd_test.cc +++ b/artd/artd_test.cc @@ -26,6 +26,7 @@ #include <condition_variable> #include <csignal> #include <cstdio> +#include <cstring> #include <filesystem> #include <functional> #include <memory> @@ -120,6 +121,8 @@ using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath; using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath; using TmpProfilePath = ProfilePath::TmpProfilePath; +using std::literals::operator""s; // NOLINT + ScopeGuard<std::function<void()>> ScopedSetLogger(android::base::LogFunction&& logger) { android::base::LogFunction old_logger = android::base::SetLogger(std::move(logger)); return make_scope_guard([old_logger = std::move(old_logger)]() mutable { @@ -429,11 +432,14 @@ class ArtdTest : public CommonArtTest { } } + template <bool kExpectOk> + using RunCopyAndRewriteProfileResult = Result< + std::pair<std::conditional_t<kExpectOk, CopyAndRewriteProfileResult, ndk::ScopedAStatus>, + OutputProfile>>; + // Runs `copyAndRewriteProfile` with `tmp_profile_path_` and `dex_file_`. template <bool kExpectOk = true> - Result<std::pair<std::conditional_t<kExpectOk, CopyAndRewriteProfileResult, ndk::ScopedAStatus>, - OutputProfile>> - RunCopyAndRewriteProfile() { + RunCopyAndRewriteProfileResult<kExpectOk> RunCopyAndRewriteProfile() { OutputProfile dst{.profilePath = tmp_profile_path_, .fsPermission = FsPermission{.uid = -1, .gid = -1}}; dst.profilePath.id = ""; @@ -452,6 +458,26 @@ class ArtdTest : public CommonArtTest { } } + // Runs `copyAndRewriteEmbeddedProfile` with `dex_file_`. + template <bool kExpectOk = true> + RunCopyAndRewriteProfileResult<kExpectOk> RunCopyAndRewriteEmbeddedProfile() { + OutputProfile dst{.profilePath = tmp_profile_path_, + .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + dst.profilePath.id = ""; + dst.profilePath.tmpPath = ""; + + CopyAndRewriteProfileResult result; + ndk::ScopedAStatus status = artd_->copyAndRewriteEmbeddedProfile(&dst, dex_file_, &result); + if constexpr (kExpectOk) { + if (!status.isOk()) { + return Error() << status.getMessage(); + } + return std::make_pair(std::move(result), std::move(dst)); + } else { + return std::make_pair(std::move(status), std::move(dst)); + } + } + void CreateFile(const std::string& filename, const std::string& content = "") { std::filesystem::path path(filename); std::filesystem::create_directories(path.parent_path()); @@ -461,8 +487,10 @@ class ArtdTest : public CommonArtTest { void CreateZipWithSingleEntry(const std::string& filename, const std::string& entry_name, const std::string& content = "") { + std::filesystem::path path(filename); + std::filesystem::create_directories(path.parent_path()); std::unique_ptr<File> file(OS::CreateEmptyFileWriteOnly(filename.c_str())); - ASSERT_NE(file, nullptr); + ASSERT_NE(file, nullptr) << strerror(errno); file->MarkUnchecked(); // `writer.Finish()` flushes the file and the destructor closes it. ZipWriter writer(fdopen(file->Fd(), "wb")); ASSERT_EQ(writer.StartEntry(entry_name, /*flags=*/0), 0); @@ -1534,6 +1562,85 @@ TEST_F(ArtdTest, copyAndRewriteProfileException) { EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty()); } +TEST_F(ArtdTest, copyAndRewriteEmbeddedProfileSuccess) { + CreateZipWithSingleEntry(dex_file_, "assets/art-profile/baseline.prof", "valid_profile"); + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + AllOf(WhenSplitBy( + "--", + AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")), + AllOf(Contains(art_root_ + "/bin/profman"), + Contains("--copy-and-update-profile-key"), + Contains(Flag("--profile-file-fd=", FdHasContent("valid_profile"))), + Contains(Flag("--apk-fd=", FdOf(dex_file_))))), + HasKeepFdsFor("--profile-file-fd=", "--reference-profile-file-fd=", "--apk-fd=")), + _, + _)) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "def")), + Return(ProfmanResult::kCopyAndUpdateSuccess))); + + auto [result, dst] = OR_FAIL(RunCopyAndRewriteEmbeddedProfile()); + + EXPECT_EQ(result.status, CopyAndRewriteProfileResult::Status::SUCCESS); + EXPECT_THAT(dst.profilePath.id, Not(IsEmpty())); + std::string real_path = OR_FATAL(BuildTmpProfilePath(dst.profilePath)); + EXPECT_EQ(dst.profilePath.tmpPath, real_path); + CheckContent(real_path, "def"); +} + +// The input is a plain dex file. +TEST_F(ArtdTest, copyAndRewriteEmbeddedProfileNoProfilePlainDex) { + constexpr const char* kDexMagic = "dex\n"; + CreateFile(dex_file_, kDexMagic + "dex_code"s); + + auto [result, dst] = OR_FAIL(RunCopyAndRewriteEmbeddedProfile()); + + EXPECT_EQ(result.status, CopyAndRewriteProfileResult::Status::NO_PROFILE); + EXPECT_THAT(dst.profilePath.id, IsEmpty()); + EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty()); +} + +// The input is neither a zip nor a plain dex file. +TEST_F(ArtdTest, copyAndRewriteEmbeddedProfileNotZipNotDex) { + CreateFile(dex_file_, "wrong_format"); + + auto [status, dst] = OR_FAIL(RunCopyAndRewriteEmbeddedProfile</*kExpectOk=*/false>()); + + EXPECT_FALSE(status.isOk()); + EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC); + EXPECT_THAT(status.getMessage(), HasSubstr("File is neither a zip file nor a plain dex file")); + EXPECT_THAT(dst.profilePath.id, IsEmpty()); + EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty()); +} + +// The input is a zip file without a profile entry. +TEST_F(ArtdTest, copyAndRewriteEmbeddedProfileNoProfileZipNoEntry) { + CreateZipWithSingleEntry(dex_file_, "classes.dex", "dex_code"); + + auto [result, dst] = OR_FAIL(RunCopyAndRewriteEmbeddedProfile()); + + EXPECT_EQ(result.status, CopyAndRewriteProfileResult::Status::NO_PROFILE); + EXPECT_THAT(dst.profilePath.id, IsEmpty()); + EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty()); +} + +// The input is a zip file with a profile entry that doesn't match itself. +TEST_F(ArtdTest, copyAndRewriteEmbeddedProfileBadProfileNoMatch) { + CreateZipWithSingleEntry(dex_file_, "assets/art-profile/baseline.prof", "no_match"); + + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)) + .WillOnce(Return(ProfmanResult::kCopyAndUpdateNoMatch)); + + auto [result, dst] = OR_FAIL(RunCopyAndRewriteEmbeddedProfile()); + + EXPECT_EQ(result.status, CopyAndRewriteProfileResult::Status::BAD_PROFILE); + EXPECT_THAT(result.errorMsg, HasSubstr("The profile does not match the APK")); + EXPECT_THAT(dst.profilePath.id, IsEmpty()); + EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty()); +} + TEST_F(ArtdTest, commitTmpProfile) { std::string tmp_profile_file = OR_FATAL(BuildTmpProfilePath(tmp_profile_path_)); CreateFile(tmp_profile_file); diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl index 014f63e3df..bc543efb3b 100644 --- a/artd/binder/com/android/server/art/IArtd.aidl +++ b/artd/binder/com/android/server/art/IArtd.aidl @@ -60,6 +60,15 @@ interface IArtd { inout com.android.server.art.OutputProfile dst, @utf8InCpp String dexFile); /** + * Similar to above. The difference is that the profile is not taken from a separate file but + * taken from `dexFile` itself. Specifically, if `dexFile` is a zip file, the profile is taken + * from `assets/art-profile/baseline.prof` in the zip. Returns `NO_PROFILE` if `dexFile` is not + * a zip file or it doesn't contain a profile. + */ + com.android.server.art.CopyAndRewriteProfileResult copyAndRewriteEmbeddedProfile( + inout com.android.server.art.OutputProfile dst, @utf8InCpp String dexFile); + + /** * Moves the temporary profile to the permanent location. * * Throws fatal and non-fatal errors. |