summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/Android.bp1
-rw-r--r--runtime/jit/profile_compilation_info.cc200
-rw-r--r--runtime/jit/profile_compilation_info.h78
-rw-r--r--runtime/jit/profile_compilation_info_test.cc106
4 files changed, 339 insertions, 46 deletions
diff --git a/runtime/Android.bp b/runtime/Android.bp
index e30a06c0a5..12e88293d9 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -709,6 +709,7 @@ art_cc_test {
],
shared_libs: [
"libbacktrace",
+ "libziparchive",
],
header_libs: [
"art_cmdlineparser_headers", // For parsed_options_test.
diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc
index 74bf237c31..1cc5aeb44a 100644
--- a/runtime/jit/profile_compilation_info.cc
+++ b/runtime/jit/profile_compilation_info.cc
@@ -47,6 +47,7 @@
#include "os.h"
#include "safe_map.h"
#include "utils.h"
+#include "zip_archive.h"
namespace art {
@@ -56,6 +57,10 @@ const uint8_t ProfileCompilationInfo::kProfileMagic[] = { 'p', 'r', 'o', '\0' };
// before corresponding method_encodings and class_ids.
const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '1', '0', '\0' };
+// The name of the profile entry in the dex metadata file.
+// DO NOT CHANGE THIS! (it's similar to classes.dex in the apk files).
+const char* ProfileCompilationInfo::kDexMetadataProfileEntry = "primary.prof";
+
static constexpr uint16_t kMaxDexFileKeyLength = PATH_MAX;
// Debug flag to ignore checksums when testing if a method or a class is present in the profile.
@@ -883,25 +888,13 @@ bool ProfileCompilationInfo::SafeBuffer::CompareAndAdvance(const uint8_t* data,
return false;
}
-ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::SafeBuffer::FillFromFd(
- int fd,
- const std::string& source,
- /*out*/std::string* error) {
+ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::SafeBuffer::Fill(
+ ProfileSource& source,
+ const std::string& debug_stage,
+ /*out*/ std::string* error) {
size_t byte_count = (ptr_end_ - ptr_current_) * sizeof(*ptr_current_);
uint8_t* buffer = ptr_current_;
- while (byte_count > 0) {
- int bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, byte_count));
- if (bytes_read == 0) {
- *error += "Profile EOF reached prematurely for " + source;
- return kProfileLoadBadData;
- } else if (bytes_read < 0) {
- *error += "Profile IO error for " + source + strerror(errno);
- return kProfileLoadIOError;
- }
- byte_count -= bytes_read;
- buffer += bytes_read;
- }
- return kProfileLoadSuccess;
+ return source.Read(buffer, byte_count, debug_stage, error);
}
size_t ProfileCompilationInfo::SafeBuffer::CountUnreadBytes() {
@@ -917,7 +910,7 @@ void ProfileCompilationInfo::SafeBuffer::Advance(size_t data_size) {
}
ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileHeader(
- int fd,
+ ProfileSource& source,
/*out*/uint8_t* number_of_dex_files,
/*out*/uint32_t* uncompressed_data_size,
/*out*/uint32_t* compressed_data_size,
@@ -932,7 +925,7 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileHead
SafeBuffer safe_buffer(kMagicVersionSize);
- ProfileLoadSatus status = safe_buffer.FillFromFd(fd, "ReadProfileHeader", error);
+ ProfileLoadSatus status = safe_buffer.Fill(source, "ReadProfileHeader", error);
if (status != kProfileLoadSuccess) {
return status;
}
@@ -1148,31 +1141,136 @@ bool ProfileCompilationInfo::VerifyProfileData(const std::vector<const DexFile*>
return true;
}
+ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::OpenSource(
+ int32_t fd,
+ /*out*/ std::unique_ptr<ProfileSource>* source,
+ /*out*/ std::string* error) {
+ if (IsProfileFile(fd)) {
+ source->reset(ProfileSource::Create(fd));
+ return kProfileLoadSuccess;
+ } else {
+ std::unique_ptr<ZipArchive> zip_archive(ZipArchive::OpenFromFd(fd, "profile", error));
+ if (zip_archive.get() == nullptr) {
+ *error = "Could not open the profile zip archive";
+ return kProfileLoadBadData;
+ }
+ std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(kDexMetadataProfileEntry, error));
+ if (zip_entry == nullptr) {
+ // Allow archives without the profile entry. In this case, create an empty profile.
+ // This gives more flexible when ure-using archives that may miss the entry.
+ // (e.g. dex metadata files)
+ LOG(WARNING) << std::string("Could not find entry ") + kDexMetadataProfileEntry +
+ " in the zip archive. Creating an empty profile.";
+ source->reset(ProfileSource::Create(nullptr));
+ return kProfileLoadSuccess;
+ }
+ if (zip_entry->GetUncompressedLength() == 0) {
+ *error = "Empty profile entry in the zip archive.";
+ return kProfileLoadBadData;
+ }
+
+ std::unique_ptr<MemMap> map;
+ if (zip_entry->IsUncompressed()) {
+ // Map uncompressed files within zip as file-backed to avoid a dirty copy.
+ map.reset(zip_entry->MapDirectlyFromFile(kDexMetadataProfileEntry, error));
+ if (map == nullptr) {
+ LOG(WARNING) << "Can't mmap profile directly; "
+ << "is your ZIP file corrupted? Falling back to extraction.";
+ // Try again with Extraction which still has a chance of recovery.
+ }
+ }
+
+ if (map == nullptr) {
+ // Default path for compressed ZIP entries, and fallback for stored ZIP entries.
+ // TODO(calin) pass along file names to assist with debugging.
+ map.reset(zip_entry->ExtractToMemMap("profile file", kDexMetadataProfileEntry, error));
+ }
+
+ if (map != nullptr) {
+ source->reset(ProfileSource::Create(std::move(map)));
+ return kProfileLoadSuccess;
+ } else {
+ return kProfileLoadBadData;
+ }
+ }
+}
+
+ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ProfileSource::Read(
+ uint8_t* buffer,
+ size_t byte_count,
+ const std::string& debug_stage,
+ std::string* error) {
+ if (IsMemMap()) {
+ if (mem_map_cur_ + byte_count > mem_map_->Size()) {
+ return kProfileLoadBadData;
+ }
+ for (size_t i = 0; i < byte_count; i++) {
+ buffer[i] = *(mem_map_->Begin() + mem_map_cur_);
+ mem_map_cur_++;
+ }
+ } else {
+ while (byte_count > 0) {
+ int bytes_read = TEMP_FAILURE_RETRY(read(fd_, buffer, byte_count));;
+ if (bytes_read == 0) {
+ *error += "Profile EOF reached prematurely for " + debug_stage;
+ return kProfileLoadBadData;
+ } else if (bytes_read < 0) {
+ *error += "Profile IO error for " + debug_stage + strerror(errno);
+ return kProfileLoadIOError;
+ }
+ byte_count -= bytes_read;
+ buffer += bytes_read;
+ }
+ }
+ return kProfileLoadSuccess;
+}
+
+bool ProfileCompilationInfo::ProfileSource::HasConsumedAllData() const {
+ return IsMemMap()
+ ? (mem_map_ == nullptr || mem_map_cur_ == mem_map_->Size())
+ : (testEOF(fd_) == 0);
+}
+
+bool ProfileCompilationInfo::ProfileSource::HasEmptyContent() const {
+ if (IsMemMap()) {
+ return mem_map_ == nullptr || mem_map_->Size() == 0;
+ } else {
+ struct stat stat_buffer;
+ if (fstat(fd_, &stat_buffer) != 0) {
+ return false;
+ }
+ return stat_buffer.st_size == 0;
+ }
+}
+
// TODO(calin): fail fast if the dex checksums don't match.
ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::LoadInternal(
- int fd, std::string* error, bool merge_classes) {
+ int32_t fd, std::string* error, bool merge_classes) {
ScopedTrace trace(__PRETTY_FUNCTION__);
DCHECK_GE(fd, 0);
- struct stat stat_buffer;
- if (fstat(fd, &stat_buffer) != 0) {
- return kProfileLoadIOError;
+ std::unique_ptr<ProfileSource> source;
+ ProfileLoadSatus status = OpenSource(fd, &source, error);
+ if (status != kProfileLoadSuccess) {
+ return status;
}
+
// We allow empty profile files.
// Profiles may be created by ActivityManager or installd before we manage to
// process them in the runtime or profman.
- if (stat_buffer.st_size == 0) {
+ if (source->HasEmptyContent()) {
return kProfileLoadSuccess;
}
+
// Read profile header: magic + version + number_of_dex_files.
uint8_t number_of_dex_files;
uint32_t uncompressed_data_size;
uint32_t compressed_data_size;
- ProfileLoadSatus status = ReadProfileHeader(fd,
- &number_of_dex_files,
- &uncompressed_data_size,
- &compressed_data_size,
- error);
+ status = ReadProfileHeader(*source,
+ &number_of_dex_files,
+ &uncompressed_data_size,
+ &compressed_data_size,
+ error);
if (status != kProfileLoadSuccess) {
return status;
@@ -1192,16 +1290,14 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::LoadInternal(
}
std::unique_ptr<uint8_t[]> compressed_data(new uint8_t[compressed_data_size]);
- bool bytes_read_success =
- android::base::ReadFully(fd, compressed_data.get(), compressed_data_size);
-
- if (testEOF(fd) != 0) {
- *error += "Unexpected data in the profile file.";
- return kProfileLoadBadData;
+ status = source->Read(compressed_data.get(), compressed_data_size, "ReadContent", error);
+ if (status != kProfileLoadSuccess) {
+ *error += "Unable to read compressed profile data";
+ return status;
}
- if (!bytes_read_success) {
- *error += "Unable to read compressed profile data";
+ if (!source->HasConsumedAllData()) {
+ *error += "Unexpected data in the profile file.";
return kProfileLoadBadData;
}
@@ -1904,4 +2000,34 @@ std::unordered_set<std::string> ProfileCompilationInfo::GetClassDescriptors(
return ret;
}
+bool ProfileCompilationInfo::IsProfileFile(int fd) {
+ // First check if it's an empty file as we allow empty profile files.
+ // Profiles may be created by ActivityManager or installd before we manage to
+ // process them in the runtime or profman.
+ struct stat stat_buffer;
+ if (fstat(fd, &stat_buffer) != 0) {
+ return false;
+ }
+
+ if (stat_buffer.st_size == 0) {
+ return true;
+ }
+
+ // The files is not empty. Check if it contains the profile magic.
+ size_t byte_count = sizeof(kProfileMagic);
+ uint8_t buffer[sizeof(kProfileMagic)];
+ if (!android::base::ReadFully(fd, buffer, byte_count)) {
+ return false;
+ }
+
+ // Reset the offset to prepare the file for reading.
+ off_t rc = TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET));
+ if (rc == static_cast<off_t>(-1)) {
+ PLOG(ERROR) << "Failed to reset the offset";
+ return false;
+ }
+
+ return memcmp(buffer, kProfileMagic, byte_count) == 0;
+}
+
} // namespace art
diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h
index 7c30dee0c0..5828563ad7 100644
--- a/runtime/jit/profile_compilation_info.h
+++ b/runtime/jit/profile_compilation_info.h
@@ -28,6 +28,7 @@
#include "dex/dex_file.h"
#include "dex/dex_file_types.h"
#include "method_reference.h"
+#include "mem_map.h"
#include "safe_map.h"
#include "type_reference.h"
@@ -71,6 +72,8 @@ class ProfileCompilationInfo {
static const uint8_t kProfileMagic[];
static const uint8_t kProfileVersion[];
+ static const char* kDexMetadataProfileEntry;
+
// Data structures for encoding the offline representation of inline caches.
// This is exposed as public in order to make it available to dex2oat compilations
// (see compiler/optimizing/inliner.cc).
@@ -410,6 +413,9 @@ class ProfileCompilationInfo {
// Return all of the class descriptors in the profile for a set of dex files.
std::unordered_set<std::string> GetClassDescriptors(const std::vector<const DexFile*>& dex_files);
+ // Return true if the fd points to a profile file.
+ bool IsProfileFile(int fd);
+
private:
enum ProfileLoadSatus {
kProfileLoadWouldOverwiteData,
@@ -577,6 +583,58 @@ class ProfileCompilationInfo {
uint32_t num_method_ids;
};
+ /**
+ * Encapsulate the source of profile data for loading.
+ * The source can be either a plain file or a zip file.
+ * For zip files, the profile entry will be extracted to
+ * the memory map.
+ */
+ class ProfileSource {
+ public:
+ /**
+ * Create a profile source for the given fd. The ownership of the fd
+ * remains to the caller; as this class will not attempt to close it at any
+ * point.
+ */
+ static ProfileSource* Create(int32_t fd) {
+ DCHECK_GT(fd, -1);
+ return new ProfileSource(fd, /*map*/ nullptr);
+ }
+
+ /**
+ * Create a profile source backed by a memory map. The map can be null in
+ * which case it will the treated as an empty source.
+ */
+ static ProfileSource* Create(std::unique_ptr<MemMap>&& mem_map) {
+ return new ProfileSource(/*fd*/ -1, std::move(mem_map));
+ }
+
+ /**
+ * Read bytes from this source.
+ * Reading will advance the current source position so subsequent
+ * invocations will read from the las position.
+ */
+ ProfileLoadSatus Read(uint8_t* buffer,
+ size_t byte_count,
+ const std::string& debug_stage,
+ std::string* error);
+
+ /** Return true if the source has 0 data. */
+ bool HasEmptyContent() const;
+ /** Return true if all the information from this source has been read. */
+ bool HasConsumedAllData() const;
+
+ private:
+ ProfileSource(int32_t fd, std::unique_ptr<MemMap>&& mem_map)
+ : fd_(fd), mem_map_(std::move(mem_map)), mem_map_cur_(0) {}
+
+ bool IsMemMap() const { return fd_ == -1; }
+
+ int32_t fd_; // The fd is not owned by this class.
+ std::unique_ptr<MemMap> mem_map_;
+ size_t mem_map_cur_; // Current position in the map to read from.
+ };
+
// A helper structure to make sure we don't read past our buffers in the loops.
struct SafeBuffer {
public:
@@ -586,13 +644,9 @@ class ProfileCompilationInfo {
}
// Reads the content of the descriptor at the current position.
- ProfileLoadSatus FillFromFd(int fd,
- const std::string& source,
- /*out*/std::string* error);
-
- ProfileLoadSatus FillFromBuffer(uint8_t* buffer_ptr,
- const std::string& source,
- /*out*/std::string* error);
+ ProfileLoadSatus Fill(ProfileSource& source,
+ const std::string& debug_stage,
+ /*out*/std::string* error);
// Reads an uint value (high bits to low bits) and advances the current pointer
// with the number of bits read.
@@ -620,12 +674,18 @@ class ProfileCompilationInfo {
uint8_t* ptr_current_;
};
+ ProfileLoadSatus OpenSource(int32_t fd,
+ /*out*/ std::unique_ptr<ProfileSource>* source,
+ /*out*/ std::string* error);
+
// Entry point for profile loding functionality.
- ProfileLoadSatus LoadInternal(int fd, std::string* error, bool merge_classes = true);
+ ProfileLoadSatus LoadInternal(int32_t fd,
+ std::string* error,
+ bool merge_classes = true);
// Read the profile header from the given fd and store the number of profile
// lines into number_of_dex_files.
- ProfileLoadSatus ReadProfileHeader(int fd,
+ ProfileLoadSatus ReadProfileHeader(ProfileSource& source,
/*out*/uint8_t* number_of_dex_files,
/*out*/uint32_t* size_uncompressed_data,
/*out*/uint32_t* size_compressed_data,
diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc
index 08042cc890..6ce9bcbce7 100644
--- a/runtime/jit/profile_compilation_info_test.cc
+++ b/runtime/jit/profile_compilation_info_test.cc
@@ -15,6 +15,7 @@
*/
#include <gtest/gtest.h>
+#include <stdio.h>
#include "art_method-inl.h"
#include "base/unix_file/fd_file.h"
@@ -29,6 +30,7 @@
#include "mirror/class_loader.h"
#include "scoped_thread_state_change-inl.h"
#include "type_reference.h"
+#include "ziparchive/zip_writer.h"
namespace art {
@@ -268,6 +270,50 @@ class ProfileCompilationInfoTest : public CommonRuntimeTest {
}
}
+ void TestProfileLoadFromZip(const char* zip_entry,
+ size_t zip_flags,
+ bool should_succeed,
+ bool should_succeed_with_empty_profile = false) {
+ // Create a valid profile.
+ ScratchFile profile;
+ ProfileCompilationInfo saved_info;
+ for (uint16_t i = 0; i < 10; i++) {
+ ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info));
+ ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info));
+ }
+ ASSERT_TRUE(saved_info.Save(GetFd(profile)));
+ ASSERT_EQ(0, profile.GetFile()->Flush());
+
+ // Prepare the profile content for zipping.
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ uint64_t data_size = profile.GetFile()->GetLength();
+ std::unique_ptr<uint8_t> data(new uint8_t[data_size]);
+ ASSERT_TRUE(profile.GetFile()->ReadFully(data.get(), data_size));
+
+ // Zip the profile content.
+ ScratchFile zip;
+ FILE* file = fopen(zip.GetFile()->GetPath().c_str(), "wb");
+ ZipWriter writer(file);
+ writer.StartEntry(zip_entry, zip_flags);
+ writer.WriteBytes(data.get(), data_size);
+ writer.FinishEntry();
+ writer.Finish();
+ fflush(file);
+ fclose(file);
+
+ // Verify loading from the zip archive.
+ ProfileCompilationInfo loaded_info;
+ ASSERT_TRUE(zip.GetFile()->ResetOffset());
+ ASSERT_EQ(should_succeed, loaded_info.Load(zip.GetFile()->GetPath(), false));
+ if (should_succeed) {
+ if (should_succeed_with_empty_profile) {
+ ASSERT_TRUE(loaded_info.IsEmpty());
+ } else {
+ ASSERT_TRUE(loaded_info.Equals(saved_info));
+ }
+ }
+ }
+
// Cannot sizeof the actual arrays so hard code the values here.
// They should not change anyway.
static constexpr int kProfileMagicSize = 4;
@@ -934,4 +980,64 @@ TEST_F(ProfileCompilationInfoTest, SampledMethodsTest) {
}
}
+TEST_F(ProfileCompilationInfoTest, LoadFromZipCompress) {
+ TestProfileLoadFromZip("primary.prof",
+ ZipWriter::kCompress | ZipWriter::kAlign32,
+ /*should_succeed*/true);
+}
+
+TEST_F(ProfileCompilationInfoTest, LoadFromZipUnCompress) {
+ TestProfileLoadFromZip("primary.prof",
+ ZipWriter::kAlign32,
+ /*should_succeed*/true);
+}
+
+TEST_F(ProfileCompilationInfoTest, LoadFromZipUnAligned) {
+ TestProfileLoadFromZip("primary.prof",
+ 0,
+ /*should_succeed*/true);
+}
+
+TEST_F(ProfileCompilationInfoTest, LoadFromZipFailBadZipEntry) {
+ TestProfileLoadFromZip("invalid.profile.entry",
+ 0,
+ /*should_succeed*/true,
+ /*should_succeed_with_empty_profile*/true);
+}
+
+TEST_F(ProfileCompilationInfoTest, LoadFromZipFailBadProfile) {
+ // Create a bad profile.
+ ScratchFile profile;
+ ASSERT_TRUE(profile.GetFile()->WriteFully(
+ ProfileCompilationInfo::kProfileMagic, kProfileMagicSize));
+ ASSERT_TRUE(profile.GetFile()->WriteFully(
+ ProfileCompilationInfo::kProfileVersion, kProfileVersionSize));
+ // Write that we have at least one line.
+ uint8_t line_number[] = { 0, 1 };
+ ASSERT_TRUE(profile.GetFile()->WriteFully(line_number, sizeof(line_number)));
+ ASSERT_EQ(0, profile.GetFile()->Flush());
+
+ // Prepare the profile content for zipping.
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ uint64_t data_size = profile.GetFile()->GetLength();
+ std::unique_ptr<uint8_t> data(new uint8_t[data_size]);
+ ASSERT_TRUE(profile.GetFile()->ReadFully(data.get(), data_size));
+
+ // Zip the profile content.
+ ScratchFile zip;
+ FILE* file = fopen(zip.GetFile()->GetPath().c_str(), "wb");
+ ZipWriter writer(file);
+ writer.StartEntry("primary.prof", ZipWriter::kCompress | ZipWriter::kAlign32);
+ writer.WriteBytes(data.get(), data_size);
+ writer.FinishEntry();
+ writer.Finish();
+ fflush(file);
+ fclose(file);
+
+ // Check that we failed to load.
+ ProfileCompilationInfo loaded_info;
+ ASSERT_TRUE(zip.GetFile()->ResetOffset());
+ ASSERT_FALSE(loaded_info.Load(GetFd(zip)));
+}
+
} // namespace art