Add an artd method to copy the embedded profile from an apk file.
Context: go/platform-support-for-baseline-profiles
Bug: 257532944
Test: atest art_standalone_artd_tests
Change-Id: I16d638aa3f281fa0bdb437bf6a27826795394a86
Merged-In: I16d638aa3f281fa0bdb437bf6a27826795394a86
diff --git a/artd/Android.bp b/artd/Android.bp
index 6de6aef..5449cd9 100644
--- a/artd/Android.bp
+++ b/artd/Android.bp
@@ -39,6 +39,7 @@
"libarttools", // Contains "libc++fs".
"libbase",
"libbinder_ndk",
+ "libdexfile",
"libselinux",
],
static_libs: [
diff --git a/artd/artd.cc b/artd/artd.cc
index 8b46d91..b96701c 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::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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 c78aaa9..c670847 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -101,6 +101,11 @@
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 @@
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 4af8f17..6b93cee 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 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 @@
}
}
+ 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 @@
}
}
+ // 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 @@
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 @@
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 014f63e..bc543ef 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -60,6 +60,15 @@
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.