diff options
| author | 2016-08-15 21:32:30 +0000 | |
|---|---|---|
| committer | 2016-08-15 21:32:30 +0000 | |
| commit | b23e3b2156ea326c5dea2f285e5f45b86bdb004c (patch) | |
| tree | f67a24382a512dd24068984a9b76d40267b5dd03 | |
| parent | 882c0e4899a2fb5ab8fbc739c4e38901beda8f2e (diff) | |
| parent | a463b6a920a2a0bf14f9cca20a561b412b9349d1 (diff) | |
Merge "ART: Refactor ImageSpace loading"
| -rw-r--r-- | runtime/gc/heap.cc | 5 | ||||
| -rw-r--r-- | runtime/gc/space/image_space.cc | 2197 | ||||
| -rw-r--r-- | runtime/gc/space/image_space.h | 34 | ||||
| -rw-r--r-- | runtime/oat_file_assistant.cc | 17 | ||||
| -rw-r--r-- | runtime/oat_file_assistant.h | 2 | ||||
| -rw-r--r-- | runtime/oat_file_manager.cc | 4 |
6 files changed, 1171 insertions, 1088 deletions
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index b574c3bf3a..aa3bf61da2 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -297,12 +297,13 @@ Heap::Heap(size_t initial_size, for (size_t index = 0; index < image_file_names.size(); ++index) { std::string& image_name = image_file_names[index]; std::string error_msg; - space::ImageSpace* boot_image_space = space::ImageSpace::CreateBootImage( + std::unique_ptr<space::ImageSpace> boot_image_space_uptr = space::ImageSpace::CreateBootImage( image_name.c_str(), image_instruction_set, index > 0, &error_msg); - if (boot_image_space != nullptr) { + if (boot_image_space_uptr != nullptr) { + space::ImageSpace* boot_image_space = boot_image_space_uptr.release(); AddSpace(boot_image_space); added_image_spaces.push_back(boot_image_space); // Oat files referenced by image files immediately follow them in memory, ensure alloc space diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index 6fcad295bb..4505c249fe 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -79,7 +79,12 @@ static int32_t ChooseRelocationOffsetDelta(int32_t min_delta, int32_t max_delta) return r; } -static bool GenerateImage(const std::string& image_filename, InstructionSet image_isa, +static int32_t ChooseRelocationOffsetDelta() { + return ChooseRelocationOffsetDelta(ART_BASE_ADDRESS_MIN_DELTA, ART_BASE_ADDRESS_MAX_DELTA); +} + +static bool GenerateImage(const std::string& image_filename, + InstructionSet image_isa, std::string* error_msg) { const std::string boot_class_path_string(Runtime::Current()->GetBootClassPathString()); std::vector<std::string> boot_class_path; @@ -118,8 +123,7 @@ static bool GenerateImage(const std::string& image_filename, InstructionSet imag CHECK_EQ(image_isa, kRuntimeISA) << "We should always be generating an image for the current isa."; - int32_t base_offset = ChooseRelocationOffsetDelta(ART_BASE_ADDRESS_MIN_DELTA, - ART_BASE_ADDRESS_MAX_DELTA); + int32_t base_offset = ChooseRelocationOffsetDelta(); LOG(INFO) << "Using an offset of 0x" << std::hex << base_offset << " from default " << "art base address of 0x" << std::hex << ART_BASE_ADDRESS; arg_vector.push_back(StringPrintf("--base=0x%x", ART_BASE_ADDRESS + base_offset)); @@ -138,14 +142,17 @@ static bool GenerateImage(const std::string& image_filename, InstructionSet imag return Exec(arg_vector, error_msg); } -bool ImageSpace::FindImageFilename(const char* image_location, - const InstructionSet image_isa, - std::string* system_filename, - bool* has_system, - std::string* cache_filename, - bool* dalvik_cache_exists, - bool* has_cache, - bool* is_global_cache) { +static bool FindImageFilenameImpl(const char* image_location, + const InstructionSet image_isa, + bool* has_system, + std::string* system_filename, + bool* dalvik_cache_exists, + std::string* dalvik_cache, + bool* is_global_cache, + bool* has_cache, + std::string* cache_filename) { + DCHECK(dalvik_cache != nullptr); + *has_system = false; *has_cache = false; // image_location = /system/framework/boot.art @@ -158,9 +165,12 @@ bool ImageSpace::FindImageFilename(const char* image_location, bool have_android_data = false; *dalvik_cache_exists = false; - std::string dalvik_cache; - GetDalvikCache(GetInstructionSetString(image_isa), true, &dalvik_cache, - &have_android_data, dalvik_cache_exists, is_global_cache); + GetDalvikCache(GetInstructionSetString(image_isa), + true, + dalvik_cache, + &have_android_data, + dalvik_cache_exists, + is_global_cache); if (have_android_data && *dalvik_cache_exists) { // Always set output location even if it does not exist, @@ -169,7 +179,10 @@ bool ImageSpace::FindImageFilename(const char* image_location, // image_location = /system/framework/boot.art // *image_filename = /data/dalvik-cache/<image_isa>/boot.art std::string error_msg; - if (!GetDalvikCacheFilename(image_location, dalvik_cache.c_str(), cache_filename, &error_msg)) { + if (!GetDalvikCacheFilename(image_location, + dalvik_cache->c_str(), + cache_filename, + &error_msg)) { LOG(WARNING) << error_msg; return *has_system; } @@ -178,6 +191,26 @@ bool ImageSpace::FindImageFilename(const char* image_location, return *has_system || *has_cache; } +bool ImageSpace::FindImageFilename(const char* image_location, + const InstructionSet image_isa, + std::string* system_filename, + bool* has_system, + std::string* cache_filename, + bool* dalvik_cache_exists, + bool* has_cache, + bool* is_global_cache) { + std::string dalvik_cache_unused; + return FindImageFilenameImpl(image_location, + image_isa, + has_system, + system_filename, + dalvik_cache_exists, + &dalvik_cache_unused, + is_global_cache, + has_cache, + cache_filename); +} + static bool ReadSpecificImageHeader(const char* filename, ImageHeader* image_header) { std::unique_ptr<File> image_file(OS::OpenFileForReading(filename)); if (image_file.get() == nullptr) { @@ -191,8 +224,10 @@ static bool ReadSpecificImageHeader(const char* filename, ImageHeader* image_hea } // Relocate the image at image_location to dest_filename and relocate it by a random amount. -static bool RelocateImage(const char* image_location, const char* dest_filename, - InstructionSet isa, std::string* error_msg) { +static bool RelocateImage(const char* image_location, + const char* dest_filename, + InstructionSet isa, + std::string* error_msg) { // We should clean up so we are more likely to have room for the image. if (Runtime::Current()->IsZygote()) { LOG(INFO) << "Pruning dalvik-cache since we are relocating an image and will need to recompile"; @@ -211,8 +246,7 @@ static bool RelocateImage(const char* image_location, const char* dest_filename, instruction_set_arg += GetInstructionSetString(isa); std::string base_offset_arg("--base-offset-delta="); - StringAppendF(&base_offset_arg, "%d", ChooseRelocationOffsetDelta(ART_BASE_ADDRESS_MIN_DELTA, - ART_BASE_ADDRESS_MAX_DELTA)); + StringAppendF(&base_offset_arg, "%d", ChooseRelocationOffsetDelta()); std::vector<std::string> argv; argv.push_back(patchoat); @@ -237,16 +271,6 @@ static ImageHeader* ReadSpecificImageHeader(const char* filename, std::string* e return hdr.release(); } -ImageHeader* ImageSpace::ReadImageHeaderOrDie(const char* image_location, - const InstructionSet image_isa) { - std::string error_msg; - ImageHeader* image_header = ReadImageHeader(image_location, image_isa, &error_msg); - if (image_header == nullptr) { - LOG(FATAL) << error_msg; - } - return image_header; -} - ImageHeader* ImageSpace::ReadImageHeader(const char* image_location, const InstructionSet image_isa, std::string* error_msg) { @@ -312,11 +336,31 @@ ImageHeader* ImageSpace::ReadImageHeader(const char* image_location, return nullptr; } -static bool ChecksumsMatch(const char* image_a, const char* image_b) { +static bool ChecksumsMatch(const char* image_a, const char* image_b, std::string* error_msg) { + DCHECK(error_msg != nullptr); + ImageHeader hdr_a; ImageHeader hdr_b; - return ReadSpecificImageHeader(image_a, &hdr_a) && ReadSpecificImageHeader(image_b, &hdr_b) - && hdr_a.GetOatChecksum() == hdr_b.GetOatChecksum(); + + if (!ReadSpecificImageHeader(image_a, &hdr_a)) { + *error_msg = StringPrintf("Cannot read header of %s", image_a); + return false; + } + if (!ReadSpecificImageHeader(image_b, &hdr_b)) { + *error_msg = StringPrintf("Cannot read header of %s", image_b); + return false; + } + + if (hdr_a.GetOatChecksum() != hdr_b.GetOatChecksum()) { + *error_msg = StringPrintf("Checksum mismatch: %u(%s) vs %u(%s)", + hdr_a.GetOatChecksum(), + image_a, + hdr_b.GetOatChecksum(), + image_b); + return false; + } + + return true; } static bool ImageCreationAllowed(bool is_global_cache, std::string* error_msg) { @@ -334,256 +378,6 @@ static bool ImageCreationAllowed(bool is_global_cache, std::string* error_msg) { return false; } -static constexpr uint64_t kLowSpaceValue = 50 * MB; -static constexpr uint64_t kTmpFsSentinelValue = 384 * MB; - -// Read the free space of the cache partition and make a decision whether to keep the generated -// image. This is to try to mitigate situations where the system might run out of space later. -static bool CheckSpace(const std::string& cache_filename, std::string* error_msg) { - // Using statvfs vs statvfs64 because of b/18207376, and it is enough for all practical purposes. - struct statvfs buf; - - int res = TEMP_FAILURE_RETRY(statvfs(cache_filename.c_str(), &buf)); - if (res != 0) { - // Could not stat. Conservatively tell the system to delete the image. - *error_msg = "Could not stat the filesystem, assuming low-memory situation."; - return false; - } - - uint64_t fs_overall_size = buf.f_bsize * static_cast<uint64_t>(buf.f_blocks); - // Zygote is privileged, but other things are not. Use bavail. - uint64_t fs_free_size = buf.f_bsize * static_cast<uint64_t>(buf.f_bavail); - - // Take the overall size as an indicator for a tmpfs, which is being used for the decryption - // environment. We do not want to fail quickening the boot image there, as it is beneficial - // for time-to-UI. - if (fs_overall_size > kTmpFsSentinelValue) { - if (fs_free_size < kLowSpaceValue) { - *error_msg = StringPrintf("Low-memory situation: only %4.2f megabytes available after image" - " generation, need at least %" PRIu64 ".", - static_cast<double>(fs_free_size) / MB, - kLowSpaceValue / MB); - return false; - } - } - return true; -} - -ImageSpace* ImageSpace::CreateBootImage(const char* image_location, - const InstructionSet image_isa, - bool secondary_image, - std::string* error_msg) { - ScopedTrace trace(__FUNCTION__); - std::string system_filename; - bool has_system = false; - std::string cache_filename; - bool has_cache = false; - bool dalvik_cache_exists = false; - bool is_global_cache = true; - bool found_image = FindImageFilename(image_location, image_isa, &system_filename, - &has_system, &cache_filename, &dalvik_cache_exists, - &has_cache, &is_global_cache); - - const bool is_zygote = Runtime::Current()->IsZygote(); - if (is_zygote && !secondary_image) { - MarkZygoteStart(image_isa, Runtime::Current()->GetZygoteMaxFailedBoots()); - } - - ImageSpace* space; - bool relocate = Runtime::Current()->ShouldRelocate(); - bool can_compile = Runtime::Current()->IsImageDex2OatEnabled(); - if (found_image) { - const std::string* image_filename; - bool is_system = false; - bool relocated_version_used = false; - if (relocate) { - if (!dalvik_cache_exists) { - *error_msg = StringPrintf("Requiring relocation for image '%s' at '%s' but we do not have " - "any dalvik_cache to find/place it in.", - image_location, system_filename.c_str()); - return nullptr; - } - if (has_system) { - if (has_cache && ChecksumsMatch(system_filename.c_str(), cache_filename.c_str())) { - // We already have a relocated version - image_filename = &cache_filename; - relocated_version_used = true; - } else { - // We cannot have a relocated version, Relocate the system one and use it. - - std::string reason; - bool success; - - // Check whether we are allowed to relocate. - if (!can_compile) { - reason = "Image dex2oat disabled by -Xnoimage-dex2oat."; - success = false; - } else if (!ImageCreationAllowed(is_global_cache, &reason)) { - // Whether we can write to the cache. - success = false; - } else if (secondary_image) { - if (is_zygote) { - // Secondary image is out of date. Clear cache and exit to let it retry from scratch. - LOG(ERROR) << "Cannot patch secondary image '" << image_location - << "', clearing dalvik_cache and restarting zygote."; - PruneDalvikCache(image_isa); - _exit(1); - } else { - reason = "Should not have to patch secondary image."; - success = false; - } - } else { - // Try to relocate. - success = RelocateImage(image_location, cache_filename.c_str(), image_isa, &reason); - } - - if (success) { - relocated_version_used = true; - image_filename = &cache_filename; - } else { - *error_msg = StringPrintf("Unable to relocate image '%s' from '%s' to '%s': %s", - image_location, system_filename.c_str(), - cache_filename.c_str(), reason.c_str()); - // We failed to create files, remove any possibly garbage output. - // Since ImageCreationAllowed was true above, we are the zygote - // and therefore the only process expected to generate these for - // the device. - PruneDalvikCache(image_isa); - return nullptr; - } - } - } else { - CHECK(has_cache); - // We can just use cache's since it should be fine. This might or might not be relocated. - image_filename = &cache_filename; - } - } else { - if (has_system && has_cache) { - // Check they have the same cksum. If they do use the cache. Otherwise system. - if (ChecksumsMatch(system_filename.c_str(), cache_filename.c_str())) { - image_filename = &cache_filename; - relocated_version_used = true; - } else { - image_filename = &system_filename; - is_system = true; - } - } else if (has_system) { - image_filename = &system_filename; - is_system = true; - } else { - CHECK(has_cache); - image_filename = &cache_filename; - } - } - { - // Note that we must not use the file descriptor associated with - // ScopedFlock::GetFile to Init the image file. We want the file - // descriptor (and the associated exclusive lock) to be released when - // we leave Create. - ScopedFlock image_lock; - // Should this be a RDWR lock? This is only a defensive measure, as at - // this point the image should exist. - // However, only the zygote can write into the global dalvik-cache, so - // restrict to zygote processes, or any process that isn't using - // /data/dalvik-cache (which we assume to be allowed to write there). - const bool rw_lock = is_zygote || !is_global_cache; - image_lock.Init(image_filename->c_str(), - rw_lock ? (O_CREAT | O_RDWR) : O_RDONLY /* flags */, - true /* block */, - error_msg); - VLOG(startup) << "Using image file " << image_filename->c_str() << " for image location " - << image_location; - // If we are in /system we can assume the image is good. We can also - // assume this if we are using a relocated image (i.e. image checksum - // matches) since this is only different by the offset. We need this to - // make sure that host tests continue to work. - // Since we are the boot image, pass null since we load the oat file from the boot image oat - // file name. - space = ImageSpace::Init(image_filename->c_str(), - image_location, - !(is_system || relocated_version_used), - /* oat_file */nullptr, - error_msg); - } - if (space != nullptr) { - // Check whether there is enough space left over in the data partition. Even if we can load - // the image, we need to be conservative, as some parts of the platform are not very tolerant - // of space constraints. - // ImageSpace doesn't know about the data partition per se, it relies on the FindImageFilename - // helper (which relies on GetDalvikCache). So for now, if we load an image out of /system, - // ignore the check (as it would test for free space in /system instead). - if (!is_system && !CheckSpace(*image_filename, error_msg)) { - // No. Delete the generated image and try to run out of the dex files. - PruneDalvikCache(image_isa); - return nullptr; - } - return space; - } - - if (relocated_version_used) { - // Something is wrong with the relocated copy (even though checksums match). Cleanup. - // This can happen if the .oat is corrupt, since the above only checks the .art checksums. - // TODO: Check the oat file validity earlier. - *error_msg = StringPrintf("Attempted to use relocated version of %s at %s generated from %s " - "but image failed to load: %s", - image_location, cache_filename.c_str(), system_filename.c_str(), - error_msg->c_str()); - PruneDalvikCache(image_isa); - return nullptr; - } else if (is_system) { - // If the /system file exists, it should be up-to-date, don't try to generate it. - *error_msg = StringPrintf("Failed to load /system image '%s': %s", - image_filename->c_str(), error_msg->c_str()); - return nullptr; - } else { - // Otherwise, log a warning and fall through to GenerateImage. - LOG(WARNING) << *error_msg; - } - } - - if (!can_compile) { - *error_msg = "Not attempting to compile image because -Xnoimage-dex2oat"; - return nullptr; - } else if (!dalvik_cache_exists) { - *error_msg = StringPrintf("No place to put generated image."); - return nullptr; - } else if (!ImageCreationAllowed(is_global_cache, error_msg)) { - return nullptr; - } else if (secondary_image) { - *error_msg = "Cannot compile a secondary image."; - return nullptr; - } else if (!GenerateImage(cache_filename, image_isa, error_msg)) { - *error_msg = StringPrintf("Failed to generate image '%s': %s", - cache_filename.c_str(), error_msg->c_str()); - // We failed to create files, remove any possibly garbage output. - // Since ImageCreationAllowed was true above, we are the zygote - // and therefore the only process expected to generate these for - // the device. - PruneDalvikCache(image_isa); - return nullptr; - } else { - // Check whether there is enough space left over after we have generated the image. - if (!CheckSpace(cache_filename, error_msg)) { - // No. Delete the generated image and try to run out of the dex files. - PruneDalvikCache(image_isa); - return nullptr; - } - - // Note that we must not use the file descriptor associated with - // ScopedFlock::GetFile to Init the image file. We want the file - // descriptor (and the associated exclusive lock) to be released when - // we leave Create. - ScopedFlock image_lock; - image_lock.Init(cache_filename.c_str(), error_msg); - space = ImageSpace::Init(cache_filename.c_str(), image_location, true, nullptr, error_msg); - if (space == nullptr) { - *error_msg = StringPrintf("Failed to load generated image '%s': %s", - cache_filename.c_str(), error_msg->c_str()); - } - return space; - } -} - void ImageSpace::VerifyImageAllocations() { uint8_t* current = Begin() + RoundUp(sizeof(ImageHeader), kObjectAlignment); while (current < End()) { @@ -652,863 +446,1166 @@ std::ostream& operator<<(std::ostream& os, const RelocationRange& reloc) { << reinterpret_cast<const void*>(reloc.Dest() + reloc.Length()) << ")"; } -class FixupVisitor : public ValueObject { +// Helper class encapsulating loading, so we can access private ImageSpace members (this is a +// friend class), but not declare functions in the header. +class ImageSpaceLoader { public: - FixupVisitor(const RelocationRange& boot_image, - const RelocationRange& boot_oat, - const RelocationRange& app_image, - const RelocationRange& app_oat) - : boot_image_(boot_image), - boot_oat_(boot_oat), - app_image_(app_image), - app_oat_(app_oat) {} - - // Return the relocated address of a heap object. - template <typename T> - ALWAYS_INLINE T* ForwardObject(T* src) const { - const uintptr_t uint_src = reinterpret_cast<uintptr_t>(src); - if (boot_image_.InSource(uint_src)) { - return reinterpret_cast<T*>(boot_image_.ToDest(uint_src)); + static std::unique_ptr<ImageSpace> Load(const char* image_location, + const std::string& image_filename, + bool is_zygote, + bool is_global_cache, + bool is_system, + bool relocated_version_used, + std::string* error_msg) + SHARED_REQUIRES(Locks::mutator_lock_) { + // Note that we must not use the file descriptor associated with + // ScopedFlock::GetFile to Init the image file. We want the file + // descriptor (and the associated exclusive lock) to be released when + // we leave Create. + ScopedFlock image_lock; + // Should this be a RDWR lock? This is only a defensive measure, as at + // this point the image should exist. + // However, only the zygote can write into the global dalvik-cache, so + // restrict to zygote processes, or any process that isn't using + // /data/dalvik-cache (which we assume to be allowed to write there). + const bool rw_lock = is_zygote || !is_global_cache; + image_lock.Init(image_filename.c_str(), + rw_lock ? (O_CREAT | O_RDWR) : O_RDONLY /* flags */, + true /* block */, + error_msg); + VLOG(startup) << "Using image file " << image_filename.c_str() << " for image location " + << image_location; + // If we are in /system we can assume the image is good. We can also + // assume this if we are using a relocated image (i.e. image checksum + // matches) since this is only different by the offset. We need this to + // make sure that host tests continue to work. + // Since we are the boot image, pass null since we load the oat file from the boot image oat + // file name. + return Init(image_filename.c_str(), + image_location, + !(is_system || relocated_version_used), + /* oat_file */nullptr, + error_msg); + } + + static std::unique_ptr<ImageSpace> Init(const char* image_filename, + const char* image_location, + bool validate_oat_file, + const OatFile* oat_file, + std::string* error_msg) + SHARED_REQUIRES(Locks::mutator_lock_) { + CHECK(image_filename != nullptr); + CHECK(image_location != nullptr); + + TimingLogger logger(__PRETTY_FUNCTION__, true, VLOG_IS_ON(image)); + VLOG(image) << "ImageSpace::Init entering image_filename=" << image_filename; + + std::unique_ptr<File> file; + { + TimingLogger::ScopedTiming timing("OpenImageFile", &logger); + file.reset(OS::OpenFileForReading(image_filename)); + if (file == nullptr) { + *error_msg = StringPrintf("Failed to open '%s'", image_filename); + return nullptr; + } + } + ImageHeader temp_image_header; + ImageHeader* image_header = &temp_image_header; + { + TimingLogger::ScopedTiming timing("ReadImageHeader", &logger); + bool success = file->ReadFully(image_header, sizeof(*image_header)); + if (!success || !image_header->IsValid()) { + *error_msg = StringPrintf("Invalid image header in '%s'", image_filename); + return nullptr; + } } - if (app_image_.InSource(uint_src)) { - return reinterpret_cast<T*>(app_image_.ToDest(uint_src)); + // Check that the file is larger or equal to the header size + data size. + const uint64_t image_file_size = static_cast<uint64_t>(file->GetLength()); + if (image_file_size < sizeof(ImageHeader) + image_header->GetDataSize()) { + *error_msg = StringPrintf("Image file truncated: %" PRIu64 " vs. %" PRIu64 ".", + image_file_size, + sizeof(ImageHeader) + image_header->GetDataSize()); + return nullptr; } - // Since we are fixing up the app image, there should only be pointers to the app image and - // boot image. - DCHECK(src == nullptr) << reinterpret_cast<const void*>(src); - return src; - } - // Return the relocated address of a code pointer (contained by an oat file). - ALWAYS_INLINE const void* ForwardCode(const void* src) const { - const uintptr_t uint_src = reinterpret_cast<uintptr_t>(src); - if (boot_oat_.InSource(uint_src)) { - return reinterpret_cast<const void*>(boot_oat_.ToDest(uint_src)); + if (oat_file != nullptr) { + // If we have an oat file, check the oat file checksum. The oat file is only non-null for the + // app image case. Otherwise, we open the oat file after the image and check the checksum there. + const uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum(); + const uint32_t image_oat_checksum = image_header->GetOatChecksum(); + if (oat_checksum != image_oat_checksum) { + *error_msg = StringPrintf("Oat checksum 0x%x does not match the image one 0x%x in image %s", + oat_checksum, + image_oat_checksum, + image_filename); + return nullptr; + } } - if (app_oat_.InSource(uint_src)) { - return reinterpret_cast<const void*>(app_oat_.ToDest(uint_src)); + + if (VLOG_IS_ON(startup)) { + LOG(INFO) << "Dumping image sections"; + for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) { + const auto section_idx = static_cast<ImageHeader::ImageSections>(i); + auto& section = image_header->GetImageSection(section_idx); + LOG(INFO) << section_idx << " start=" + << reinterpret_cast<void*>(image_header->GetImageBegin() + section.Offset()) << " " + << section; + } } - DCHECK(src == nullptr) << src; - return src; - } - // Must be called on pointers that already have been relocated to the destination relocation. - ALWAYS_INLINE bool IsInAppImage(mirror::Object* object) const { - return app_image_.InDest(reinterpret_cast<uintptr_t>(object)); - } + const auto& bitmap_section = image_header->GetImageSection(ImageHeader::kSectionImageBitmap); + // The location we want to map from is the first aligned page after the end of the stored + // (possibly compressed) data. + const size_t image_bitmap_offset = RoundUp(sizeof(ImageHeader) + image_header->GetDataSize(), + kPageSize); + const size_t end_of_bitmap = image_bitmap_offset + bitmap_section.Size(); + if (end_of_bitmap != image_file_size) { + *error_msg = StringPrintf( + "Image file size does not equal end of bitmap: size=%" PRIu64 " vs. %zu.", image_file_size, + end_of_bitmap); + return nullptr; + } - protected: - // Source section. - const RelocationRange boot_image_; - const RelocationRange boot_oat_; - const RelocationRange app_image_; - const RelocationRange app_oat_; -}; + std::unique_ptr<MemMap> map; + // GetImageBegin is the preferred address to map the image. If we manage to map the + // image at the image begin, the amount of fixup work required is minimized. + map.reset(LoadImageFile(image_filename, + image_location, + *image_header, + image_header->GetImageBegin(), + file->Fd(), + logger, + error_msg)); + // If the header specifies PIC mode, we can also map at a random low_4gb address since we can + // relocate in-place. + if (map == nullptr && image_header->IsPic()) { + map.reset(LoadImageFile(image_filename, + image_location, + *image_header, + /* address */ nullptr, + file->Fd(), + logger, + error_msg)); + } + // Were we able to load something and continue? + if (map == nullptr) { + DCHECK(!error_msg->empty()); + return nullptr; + } + DCHECK_EQ(0, memcmp(image_header, map->Begin(), sizeof(ImageHeader))); + + std::unique_ptr<MemMap> image_bitmap_map(MemMap::MapFileAtAddress(nullptr, + bitmap_section.Size(), + PROT_READ, MAP_PRIVATE, + file->Fd(), + image_bitmap_offset, + /*low_4gb*/false, + /*reuse*/false, + image_filename, + error_msg)); + if (image_bitmap_map == nullptr) { + *error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str()); + return nullptr; + } + // Loaded the map, use the image header from the file now in case we patch it with + // RelocateInPlace. + image_header = reinterpret_cast<ImageHeader*>(map->Begin()); + const uint32_t bitmap_index = ImageSpace::bitmap_index_.FetchAndAddSequentiallyConsistent(1); + std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u", + image_filename, + bitmap_index)); + // Bitmap only needs to cover until the end of the mirror objects section. + const ImageSection& image_objects = image_header->GetImageSection(ImageHeader::kSectionObjects); + // We only want the mirror object, not the ArtFields and ArtMethods. + uint8_t* const image_end = map->Begin() + image_objects.End(); + std::unique_ptr<accounting::ContinuousSpaceBitmap> bitmap; + { + TimingLogger::ScopedTiming timing("CreateImageBitmap", &logger); + bitmap.reset( + accounting::ContinuousSpaceBitmap::CreateFromMemMap( + bitmap_name, + image_bitmap_map.release(), + reinterpret_cast<uint8_t*>(map->Begin()), + image_objects.End())); + if (bitmap == nullptr) { + *error_msg = StringPrintf("Could not create bitmap '%s'", bitmap_name.c_str()); + return nullptr; + } + } + { + TimingLogger::ScopedTiming timing("RelocateImage", &logger); + if (!RelocateInPlace(*image_header, + map->Begin(), + bitmap.get(), + oat_file, + error_msg)) { + return nullptr; + } + } + // We only want the mirror object, not the ArtFields and ArtMethods. + std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename, + image_location, + map.release(), + bitmap.release(), + image_end)); + + // VerifyImageAllocations() will be called later in Runtime::Init() + // as some class roots like ArtMethod::java_lang_reflect_ArtMethod_ + // and ArtField::java_lang_reflect_ArtField_, which are used from + // Object::SizeOf() which VerifyImageAllocations() calls, are not + // set yet at this point. + if (oat_file == nullptr) { + TimingLogger::ScopedTiming timing("OpenOatFile", &logger); + space->oat_file_ = OpenOatFile(*space, image_filename, error_msg); + if (space->oat_file_ == nullptr) { + DCHECK(!error_msg->empty()); + return nullptr; + } + space->oat_file_non_owned_ = space->oat_file_.get(); + } else { + space->oat_file_non_owned_ = oat_file; + } -// Adapt for mirror::Class::FixupNativePointers. -class FixupObjectAdapter : public FixupVisitor { - public: - template<typename... Args> - explicit FixupObjectAdapter(Args... args) : FixupVisitor(args...) {} + if (validate_oat_file) { + TimingLogger::ScopedTiming timing("ValidateOatFile", &logger); + CHECK(space->oat_file_ != nullptr); + if (!ValidateOatFile(*space, *space->oat_file_, error_msg)) { + DCHECK(!error_msg->empty()); + return nullptr; + } + } - template <typename T> - T* operator()(T* obj) const { - return ForwardObject(obj); + Runtime* runtime = Runtime::Current(); + + // If oat_file is null, then it is the boot image space. Use oat_file_non_owned_ from the space + // to set the runtime methods. + CHECK_EQ(oat_file != nullptr, image_header->IsAppImage()); + if (image_header->IsAppImage()) { + CHECK_EQ(runtime->GetResolutionMethod(), + image_header->GetImageMethod(ImageHeader::kResolutionMethod)); + CHECK_EQ(runtime->GetImtConflictMethod(), + image_header->GetImageMethod(ImageHeader::kImtConflictMethod)); + CHECK_EQ(runtime->GetImtUnimplementedMethod(), + image_header->GetImageMethod(ImageHeader::kImtUnimplementedMethod)); + CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveAllCalleeSaves), + image_header->GetImageMethod(ImageHeader::kSaveAllCalleeSavesMethod)); + CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveRefsOnly), + image_header->GetImageMethod(ImageHeader::kSaveRefsOnlyMethod)); + CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveRefsAndArgs), + image_header->GetImageMethod(ImageHeader::kSaveRefsAndArgsMethod)); + CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveEverything), + image_header->GetImageMethod(ImageHeader::kSaveEverythingMethod)); + } else if (!runtime->HasResolutionMethod()) { + runtime->SetInstructionSet(space->oat_file_non_owned_->GetOatHeader().GetInstructionSet()); + runtime->SetResolutionMethod(image_header->GetImageMethod(ImageHeader::kResolutionMethod)); + runtime->SetImtConflictMethod(image_header->GetImageMethod(ImageHeader::kImtConflictMethod)); + runtime->SetImtUnimplementedMethod( + image_header->GetImageMethod(ImageHeader::kImtUnimplementedMethod)); + runtime->SetCalleeSaveMethod( + image_header->GetImageMethod(ImageHeader::kSaveAllCalleeSavesMethod), + Runtime::kSaveAllCalleeSaves); + runtime->SetCalleeSaveMethod( + image_header->GetImageMethod(ImageHeader::kSaveRefsOnlyMethod), Runtime::kSaveRefsOnly); + runtime->SetCalleeSaveMethod( + image_header->GetImageMethod(ImageHeader::kSaveRefsAndArgsMethod), + Runtime::kSaveRefsAndArgs); + runtime->SetCalleeSaveMethod( + image_header->GetImageMethod(ImageHeader::kSaveEverythingMethod), Runtime::kSaveEverything); + } + + VLOG(image) << "ImageSpace::Init exiting " << *space.get(); + if (VLOG_IS_ON(image)) { + logger.Dump(LOG(INFO)); + } + return space; } -}; -class FixupRootVisitor : public FixupVisitor { - public: - template<typename... Args> - explicit FixupRootVisitor(Args... args) : FixupVisitor(args...) {} + private: + static MemMap* LoadImageFile(const char* image_filename, + const char* image_location, + const ImageHeader& image_header, + uint8_t* address, + int fd, + TimingLogger& logger, + std::string* error_msg) { + TimingLogger::ScopedTiming timing("MapImageFile", &logger); + const ImageHeader::StorageMode storage_mode = image_header.GetStorageMode(); + if (storage_mode == ImageHeader::kStorageModeUncompressed) { + return MemMap::MapFileAtAddress(address, + image_header.GetImageSize(), + PROT_READ | PROT_WRITE, + MAP_PRIVATE, + fd, + 0, + /*low_4gb*/true, + /*reuse*/false, + image_filename, + error_msg); + } - ALWAYS_INLINE void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const - SHARED_REQUIRES(Locks::mutator_lock_) { - if (!root->IsNull()) { - VisitRoot(root); + if (storage_mode != ImageHeader::kStorageModeLZ4 && + storage_mode != ImageHeader::kStorageModeLZ4HC) { + *error_msg = StringPrintf("Invalid storage mode in image header %d", + static_cast<int>(storage_mode)); + return nullptr; } - } - ALWAYS_INLINE void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const - SHARED_REQUIRES(Locks::mutator_lock_) { - mirror::Object* ref = root->AsMirrorPtr(); - mirror::Object* new_ref = ForwardObject(ref); - if (ref != new_ref) { - root->Assign(new_ref); + // Reserve output and decompress into it. + std::unique_ptr<MemMap> map(MemMap::MapAnonymous(image_location, + address, + image_header.GetImageSize(), + PROT_READ | PROT_WRITE, + /*low_4gb*/true, + /*reuse*/false, + error_msg)); + if (map != nullptr) { + const size_t stored_size = image_header.GetDataSize(); + const size_t decompress_offset = sizeof(ImageHeader); // Skip the header. + std::unique_ptr<MemMap> temp_map(MemMap::MapFile(sizeof(ImageHeader) + stored_size, + PROT_READ, + MAP_PRIVATE, + fd, + /*offset*/0, + /*low_4gb*/false, + image_filename, + error_msg)); + if (temp_map == nullptr) { + DCHECK(!error_msg->empty()); + return nullptr; + } + memcpy(map->Begin(), &image_header, sizeof(ImageHeader)); + const uint64_t start = NanoTime(); + // LZ4HC and LZ4 have same internal format, both use LZ4_decompress. + TimingLogger::ScopedTiming timing2("LZ4 decompress image", &logger); + const size_t decompressed_size = LZ4_decompress_safe( + reinterpret_cast<char*>(temp_map->Begin()) + sizeof(ImageHeader), + reinterpret_cast<char*>(map->Begin()) + decompress_offset, + stored_size, + map->Size() - decompress_offset); + VLOG(image) << "Decompressing image took " << PrettyDuration(NanoTime() - start); + if (decompressed_size + sizeof(ImageHeader) != image_header.GetImageSize()) { + *error_msg = StringPrintf( + "Decompressed size does not match expected image size %zu vs %zu", + decompressed_size + sizeof(ImageHeader), + image_header.GetImageSize()); + return nullptr; + } } - } -}; -class FixupObjectVisitor : public FixupVisitor { - public: - template<typename... Args> - explicit FixupObjectVisitor(gc::accounting::ContinuousSpaceBitmap* visited, - const PointerSize pointer_size, - Args... args) - : FixupVisitor(args...), - pointer_size_(pointer_size), - visited_(visited) {} - - // Fix up separately since we also need to fix up method entrypoints. - ALWAYS_INLINE void VisitRootIfNonNull( - mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED) const {} - - ALWAYS_INLINE void VisitRoot(mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED) - const {} - - ALWAYS_INLINE void operator()(mirror::Object* obj, - MemberOffset offset, - bool is_static ATTRIBUTE_UNUSED) const - NO_THREAD_SAFETY_ANALYSIS { - // There could be overlap between ranges, we must avoid visiting the same reference twice. - // Avoid the class field since we already fixed it up in FixupClassVisitor. - if (offset.Uint32Value() != mirror::Object::ClassOffset().Uint32Value()) { - // Space is not yet added to the heap, don't do a read barrier. - mirror::Object* ref = obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>( - offset); - // Use SetFieldObjectWithoutWriteBarrier to avoid card marking since we are writing to the - // image. - obj->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>(offset, ForwardObject(ref)); + return map.release(); + } + + class FixupVisitor : public ValueObject { + public: + FixupVisitor(const RelocationRange& boot_image, + const RelocationRange& boot_oat, + const RelocationRange& app_image, + const RelocationRange& app_oat) + : boot_image_(boot_image), + boot_oat_(boot_oat), + app_image_(app_image), + app_oat_(app_oat) {} + + // Return the relocated address of a heap object. + template <typename T> + ALWAYS_INLINE T* ForwardObject(T* src) const { + const uintptr_t uint_src = reinterpret_cast<uintptr_t>(src); + if (boot_image_.InSource(uint_src)) { + return reinterpret_cast<T*>(boot_image_.ToDest(uint_src)); + } + if (app_image_.InSource(uint_src)) { + return reinterpret_cast<T*>(app_image_.ToDest(uint_src)); + } + // Since we are fixing up the app image, there should only be pointers to the app image and + // boot image. + DCHECK(src == nullptr) << reinterpret_cast<const void*>(src); + return src; } - } - // Visit a pointer array and forward corresponding native data. Ignores pointer arrays in the - // boot image. Uses the bitmap to ensure the same array is not visited multiple times. - template <typename Visitor> - void UpdatePointerArrayContents(mirror::PointerArray* array, const Visitor& visitor) const - NO_THREAD_SAFETY_ANALYSIS { - DCHECK(array != nullptr); - DCHECK(visitor.IsInAppImage(array)); - // The bit for the array contents is different than the bit for the array. Since we may have - // already visited the array as a long / int array from walking the bitmap without knowing it - // was a pointer array. - static_assert(kObjectAlignment == 8u, "array bit may be in another object"); - mirror::Object* const contents_bit = reinterpret_cast<mirror::Object*>( - reinterpret_cast<uintptr_t>(array) + kObjectAlignment); - // If the bit is not set then the contents have not yet been updated. - if (!visited_->Test(contents_bit)) { - array->Fixup<kVerifyNone, kWithoutReadBarrier>(array, pointer_size_, visitor); - visited_->Set(contents_bit); + // Return the relocated address of a code pointer (contained by an oat file). + ALWAYS_INLINE const void* ForwardCode(const void* src) const { + const uintptr_t uint_src = reinterpret_cast<uintptr_t>(src); + if (boot_oat_.InSource(uint_src)) { + return reinterpret_cast<const void*>(boot_oat_.ToDest(uint_src)); + } + if (app_oat_.InSource(uint_src)) { + return reinterpret_cast<const void*>(app_oat_.ToDest(uint_src)); + } + DCHECK(src == nullptr) << src; + return src; } - } - // java.lang.ref.Reference visitor. - void operator()(mirror::Class* klass ATTRIBUTE_UNUSED, mirror::Reference* ref) const - SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_) { - mirror::Object* obj = ref->GetReferent<kWithoutReadBarrier>(); - ref->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>( - mirror::Reference::ReferentOffset(), - ForwardObject(obj)); - } + // Must be called on pointers that already have been relocated to the destination relocation. + ALWAYS_INLINE bool IsInAppImage(mirror::Object* object) const { + return app_image_.InDest(reinterpret_cast<uintptr_t>(object)); + } - void operator()(mirror::Object* obj) const NO_THREAD_SAFETY_ANALYSIS { - if (visited_->Test(obj)) { - // Already visited. - return; + protected: + // Source section. + const RelocationRange boot_image_; + const RelocationRange boot_oat_; + const RelocationRange app_image_; + const RelocationRange app_oat_; + }; + + // Adapt for mirror::Class::FixupNativePointers. + class FixupObjectAdapter : public FixupVisitor { + public: + template<typename... Args> + explicit FixupObjectAdapter(Args... args) : FixupVisitor(args...) {} + + template <typename T> + T* operator()(T* obj) const { + return ForwardObject(obj); } - visited_->Set(obj); + }; - // Handle class specially first since we need it to be updated to properly visit the rest of - // the instance fields. - { - mirror::Class* klass = obj->GetClass<kVerifyNone, kWithoutReadBarrier>(); - DCHECK(klass != nullptr) << "Null class in image"; - // No AsClass since our fields aren't quite fixed up yet. - mirror::Class* new_klass = down_cast<mirror::Class*>(ForwardObject(klass)); - if (klass != new_klass) { - obj->SetClass<kVerifyNone>(new_klass); + class FixupRootVisitor : public FixupVisitor { + public: + template<typename... Args> + explicit FixupRootVisitor(Args... args) : FixupVisitor(args...) {} + + ALWAYS_INLINE void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const + SHARED_REQUIRES(Locks::mutator_lock_) { + if (!root->IsNull()) { + VisitRoot(root); + } + } + + ALWAYS_INLINE void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const + SHARED_REQUIRES(Locks::mutator_lock_) { + mirror::Object* ref = root->AsMirrorPtr(); + mirror::Object* new_ref = ForwardObject(ref); + if (ref != new_ref) { + root->Assign(new_ref); + } + } + }; + + class FixupObjectVisitor : public FixupVisitor { + public: + template<typename... Args> + explicit FixupObjectVisitor(gc::accounting::ContinuousSpaceBitmap* visited, + const PointerSize pointer_size, + Args... args) + : FixupVisitor(args...), + pointer_size_(pointer_size), + visited_(visited) {} + + // Fix up separately since we also need to fix up method entrypoints. + ALWAYS_INLINE void VisitRootIfNonNull( + mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED) const {} + + ALWAYS_INLINE void VisitRoot(mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED) + const {} + + ALWAYS_INLINE void operator()(mirror::Object* obj, + MemberOffset offset, + bool is_static ATTRIBUTE_UNUSED) const + NO_THREAD_SAFETY_ANALYSIS { + // There could be overlap between ranges, we must avoid visiting the same reference twice. + // Avoid the class field since we already fixed it up in FixupClassVisitor. + if (offset.Uint32Value() != mirror::Object::ClassOffset().Uint32Value()) { + // Space is not yet added to the heap, don't do a read barrier. + mirror::Object* ref = obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>( + offset); + // Use SetFieldObjectWithoutWriteBarrier to avoid card marking since we are writing to the + // image. + obj->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>(offset, ForwardObject(ref)); } - if (new_klass != klass && IsInAppImage(new_klass)) { - // Make sure the klass contents are fixed up since we depend on it to walk the fields. - operator()(new_klass); + } + + // Visit a pointer array and forward corresponding native data. Ignores pointer arrays in the + // boot image. Uses the bitmap to ensure the same array is not visited multiple times. + template <typename Visitor> + void UpdatePointerArrayContents(mirror::PointerArray* array, const Visitor& visitor) const + NO_THREAD_SAFETY_ANALYSIS { + DCHECK(array != nullptr); + DCHECK(visitor.IsInAppImage(array)); + // The bit for the array contents is different than the bit for the array. Since we may have + // already visited the array as a long / int array from walking the bitmap without knowing it + // was a pointer array. + static_assert(kObjectAlignment == 8u, "array bit may be in another object"); + mirror::Object* const contents_bit = reinterpret_cast<mirror::Object*>( + reinterpret_cast<uintptr_t>(array) + kObjectAlignment); + // If the bit is not set then the contents have not yet been updated. + if (!visited_->Test(contents_bit)) { + array->Fixup<kVerifyNone, kWithoutReadBarrier>(array, pointer_size_, visitor); + visited_->Set(contents_bit); } } - obj->VisitReferences</*visit native roots*/false, kVerifyNone, kWithoutReadBarrier>( - *this, - *this); - // Note that this code relies on no circular dependencies. - // We want to use our own class loader and not the one in the image. - if (obj->IsClass<kVerifyNone, kWithoutReadBarrier>()) { - mirror::Class* as_klass = obj->AsClass<kVerifyNone, kWithoutReadBarrier>(); - FixupObjectAdapter visitor(boot_image_, boot_oat_, app_image_, app_oat_); - as_klass->FixupNativePointers<kVerifyNone, kWithoutReadBarrier>(as_klass, - pointer_size_, - visitor); - // Deal with the pointer arrays. Use the helper function since multiple classes can reference - // the same arrays. - mirror::PointerArray* const vtable = as_klass->GetVTable<kVerifyNone, kWithoutReadBarrier>(); - if (vtable != nullptr && IsInAppImage(vtable)) { - operator()(vtable); - UpdatePointerArrayContents(vtable, visitor); + // java.lang.ref.Reference visitor. + void operator()(mirror::Class* klass ATTRIBUTE_UNUSED, mirror::Reference* ref) const + SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_) { + mirror::Object* obj = ref->GetReferent<kWithoutReadBarrier>(); + ref->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>( + mirror::Reference::ReferentOffset(), + ForwardObject(obj)); + } + + void operator()(mirror::Object* obj) const NO_THREAD_SAFETY_ANALYSIS { + if (visited_->Test(obj)) { + // Already visited. + return; + } + visited_->Set(obj); + + // Handle class specially first since we need it to be updated to properly visit the rest of + // the instance fields. + { + mirror::Class* klass = obj->GetClass<kVerifyNone, kWithoutReadBarrier>(); + DCHECK(klass != nullptr) << "Null class in image"; + // No AsClass since our fields aren't quite fixed up yet. + mirror::Class* new_klass = down_cast<mirror::Class*>(ForwardObject(klass)); + if (klass != new_klass) { + obj->SetClass<kVerifyNone>(new_klass); + } + if (new_klass != klass && IsInAppImage(new_klass)) { + // Make sure the klass contents are fixed up since we depend on it to walk the fields. + operator()(new_klass); + } } - mirror::IfTable* iftable = as_klass->GetIfTable<kVerifyNone, kWithoutReadBarrier>(); - // Ensure iftable arrays are fixed up since we need GetMethodArray to return the valid - // contents. - if (iftable != nullptr && IsInAppImage(iftable)) { - operator()(iftable); - for (int32_t i = 0, count = iftable->Count(); i < count; ++i) { - if (iftable->GetMethodArrayCount<kVerifyNone, kWithoutReadBarrier>(i) > 0) { - mirror::PointerArray* methods = - iftable->GetMethodArray<kVerifyNone, kWithoutReadBarrier>(i); - if (visitor.IsInAppImage(methods)) { - operator()(methods); - DCHECK(methods != nullptr); - UpdatePointerArrayContents(methods, visitor); + + obj->VisitReferences</*visit native roots*/false, kVerifyNone, kWithoutReadBarrier>( + *this, + *this); + // Note that this code relies on no circular dependencies. + // We want to use our own class loader and not the one in the image. + if (obj->IsClass<kVerifyNone, kWithoutReadBarrier>()) { + mirror::Class* as_klass = obj->AsClass<kVerifyNone, kWithoutReadBarrier>(); + FixupObjectAdapter visitor(boot_image_, boot_oat_, app_image_, app_oat_); + as_klass->FixupNativePointers<kVerifyNone, kWithoutReadBarrier>(as_klass, + pointer_size_, + visitor); + // Deal with the pointer arrays. Use the helper function since multiple classes can reference + // the same arrays. + mirror::PointerArray* const vtable = as_klass->GetVTable<kVerifyNone, kWithoutReadBarrier>(); + if (vtable != nullptr && IsInAppImage(vtable)) { + operator()(vtable); + UpdatePointerArrayContents(vtable, visitor); + } + mirror::IfTable* iftable = as_klass->GetIfTable<kVerifyNone, kWithoutReadBarrier>(); + // Ensure iftable arrays are fixed up since we need GetMethodArray to return the valid + // contents. + if (iftable != nullptr && IsInAppImage(iftable)) { + operator()(iftable); + for (int32_t i = 0, count = iftable->Count(); i < count; ++i) { + if (iftable->GetMethodArrayCount<kVerifyNone, kWithoutReadBarrier>(i) > 0) { + mirror::PointerArray* methods = + iftable->GetMethodArray<kVerifyNone, kWithoutReadBarrier>(i); + if (visitor.IsInAppImage(methods)) { + operator()(methods); + DCHECK(methods != nullptr); + UpdatePointerArrayContents(methods, visitor); + } } } } } } - } - private: - const PointerSize pointer_size_; - gc::accounting::ContinuousSpaceBitmap* const visited_; -}; - -class ForwardObjectAdapter { - public: - ALWAYS_INLINE explicit ForwardObjectAdapter(const FixupVisitor* visitor) : visitor_(visitor) {} + private: + const PointerSize pointer_size_; + gc::accounting::ContinuousSpaceBitmap* const visited_; + }; - template <typename T> - ALWAYS_INLINE T* operator()(T* src) const { - return visitor_->ForwardObject(src); - } + class ForwardObjectAdapter { + public: + ALWAYS_INLINE explicit ForwardObjectAdapter(const FixupVisitor* visitor) : visitor_(visitor) {} - private: - const FixupVisitor* const visitor_; -}; + template <typename T> + ALWAYS_INLINE T* operator()(T* src) const { + return visitor_->ForwardObject(src); + } -class ForwardCodeAdapter { - public: - ALWAYS_INLINE explicit ForwardCodeAdapter(const FixupVisitor* visitor) - : visitor_(visitor) {} + private: + const FixupVisitor* const visitor_; + }; - template <typename T> - ALWAYS_INLINE T* operator()(T* src) const { - return visitor_->ForwardCode(src); - } + class ForwardCodeAdapter { + public: + ALWAYS_INLINE explicit ForwardCodeAdapter(const FixupVisitor* visitor) + : visitor_(visitor) {} - private: - const FixupVisitor* const visitor_; -}; + template <typename T> + ALWAYS_INLINE T* operator()(T* src) const { + return visitor_->ForwardCode(src); + } -class FixupArtMethodVisitor : public FixupVisitor, public ArtMethodVisitor { - public: - template<typename... Args> - explicit FixupArtMethodVisitor(bool fixup_heap_objects, PointerSize pointer_size, Args... args) - : FixupVisitor(args...), - fixup_heap_objects_(fixup_heap_objects), - pointer_size_(pointer_size) {} - - virtual void Visit(ArtMethod* method) NO_THREAD_SAFETY_ANALYSIS { - // TODO: Separate visitor for runtime vs normal methods. - if (UNLIKELY(method->IsRuntimeMethod())) { - ImtConflictTable* table = method->GetImtConflictTable(pointer_size_); - if (table != nullptr) { - ImtConflictTable* new_table = ForwardObject(table); - if (table != new_table) { - method->SetImtConflictTable(new_table, pointer_size_); + private: + const FixupVisitor* const visitor_; + }; + + class FixupArtMethodVisitor : public FixupVisitor, public ArtMethodVisitor { + public: + template<typename... Args> + explicit FixupArtMethodVisitor(bool fixup_heap_objects, PointerSize pointer_size, Args... args) + : FixupVisitor(args...), + fixup_heap_objects_(fixup_heap_objects), + pointer_size_(pointer_size) {} + + virtual void Visit(ArtMethod* method) NO_THREAD_SAFETY_ANALYSIS { + // TODO: Separate visitor for runtime vs normal methods. + if (UNLIKELY(method->IsRuntimeMethod())) { + ImtConflictTable* table = method->GetImtConflictTable(pointer_size_); + if (table != nullptr) { + ImtConflictTable* new_table = ForwardObject(table); + if (table != new_table) { + method->SetImtConflictTable(new_table, pointer_size_); + } } + const void* old_code = method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_); + const void* new_code = ForwardCode(old_code); + if (old_code != new_code) { + method->SetEntryPointFromQuickCompiledCodePtrSize(new_code, pointer_size_); + } + } else { + if (fixup_heap_objects_) { + method->UpdateObjectsForImageRelocation(ForwardObjectAdapter(this), pointer_size_); + } + method->UpdateEntrypoints<kWithoutReadBarrier>(ForwardCodeAdapter(this), pointer_size_); } - const void* old_code = method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_); - const void* new_code = ForwardCode(old_code); - if (old_code != new_code) { - method->SetEntryPointFromQuickCompiledCodePtrSize(new_code, pointer_size_); - } - } else { - if (fixup_heap_objects_) { - method->UpdateObjectsForImageRelocation(ForwardObjectAdapter(this), pointer_size_); - } - method->UpdateEntrypoints<kWithoutReadBarrier>(ForwardCodeAdapter(this), pointer_size_); } - } - - private: - const bool fixup_heap_objects_; - const PointerSize pointer_size_; -}; -class FixupArtFieldVisitor : public FixupVisitor, public ArtFieldVisitor { - public: - template<typename... Args> - explicit FixupArtFieldVisitor(Args... args) : FixupVisitor(args...) {} + private: + const bool fixup_heap_objects_; + const PointerSize pointer_size_; + }; - virtual void Visit(ArtField* field) NO_THREAD_SAFETY_ANALYSIS { - field->UpdateObjects(ForwardObjectAdapter(this)); - } -}; + class FixupArtFieldVisitor : public FixupVisitor, public ArtFieldVisitor { + public: + template<typename... Args> + explicit FixupArtFieldVisitor(Args... args) : FixupVisitor(args...) {} -// Relocate an image space mapped at target_base which possibly used to be at a different base -// address. Only needs a single image space, not one for both source and destination. -// In place means modifying a single ImageSpace in place rather than relocating from one ImageSpace -// to another. -static bool RelocateInPlace(ImageHeader& image_header, - uint8_t* target_base, - accounting::ContinuousSpaceBitmap* bitmap, - const OatFile* app_oat_file, - std::string* error_msg) { - DCHECK(error_msg != nullptr); - if (!image_header.IsPic()) { - if (image_header.GetImageBegin() == target_base) { - return true; + virtual void Visit(ArtField* field) NO_THREAD_SAFETY_ANALYSIS { + field->UpdateObjects(ForwardObjectAdapter(this)); } - *error_msg = StringPrintf("Cannot relocate non-pic image for oat file %s", - (app_oat_file != nullptr) ? app_oat_file->GetLocation().c_str() : ""); - return false; - } - // Set up sections. - uint32_t boot_image_begin = 0; - uint32_t boot_image_end = 0; - uint32_t boot_oat_begin = 0; - uint32_t boot_oat_end = 0; - const PointerSize pointer_size = image_header.GetPointerSize(); - gc::Heap* const heap = Runtime::Current()->GetHeap(); - heap->GetBootImagesSize(&boot_image_begin, &boot_image_end, &boot_oat_begin, &boot_oat_end); - if (boot_image_begin == boot_image_end) { - *error_msg = "Can not relocate app image without boot image space"; - return false; - } - if (boot_oat_begin == boot_oat_end) { - *error_msg = "Can not relocate app image without boot oat file"; - return false; - } - const uint32_t boot_image_size = boot_image_end - boot_image_begin; - const uint32_t boot_oat_size = boot_oat_end - boot_oat_begin; - const uint32_t image_header_boot_image_size = image_header.GetBootImageSize(); - const uint32_t image_header_boot_oat_size = image_header.GetBootOatSize(); - if (boot_image_size != image_header_boot_image_size) { - *error_msg = StringPrintf("Boot image size %" PRIu64 " does not match expected size %" - PRIu64, - static_cast<uint64_t>(boot_image_size), - static_cast<uint64_t>(image_header_boot_image_size)); - return false; - } - if (boot_oat_size != image_header_boot_oat_size) { - *error_msg = StringPrintf("Boot oat size %" PRIu64 " does not match expected size %" - PRIu64, - static_cast<uint64_t>(boot_oat_size), - static_cast<uint64_t>(image_header_boot_oat_size)); - return false; - } - TimingLogger logger(__FUNCTION__, true, false); - RelocationRange boot_image(image_header.GetBootImageBegin(), - boot_image_begin, - boot_image_size); - RelocationRange boot_oat(image_header.GetBootOatBegin(), - boot_oat_begin, - boot_oat_size); - RelocationRange app_image(reinterpret_cast<uintptr_t>(image_header.GetImageBegin()), - reinterpret_cast<uintptr_t>(target_base), - image_header.GetImageSize()); - // Use the oat data section since this is where the OatFile::Begin is. - RelocationRange app_oat(reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin()), - // Not necessarily in low 4GB. - reinterpret_cast<uintptr_t>(app_oat_file->Begin()), - image_header.GetOatDataEnd() - image_header.GetOatDataBegin()); - VLOG(image) << "App image " << app_image; - VLOG(image) << "App oat " << app_oat; - VLOG(image) << "Boot image " << boot_image; - VLOG(image) << "Boot oat " << boot_oat; - // True if we need to fixup any heap pointers, otherwise only code pointers. - const bool fixup_image = boot_image.Delta() != 0 || app_image.Delta() != 0; - const bool fixup_code = boot_oat.Delta() != 0 || app_oat.Delta() != 0; - if (!fixup_image && !fixup_code) { - // Nothing to fix up. - return true; - } - ScopedDebugDisallowReadBarriers sddrb(Thread::Current()); - // Need to update the image to be at the target base. - const ImageSection& objects_section = image_header.GetImageSection(ImageHeader::kSectionObjects); - uintptr_t objects_begin = reinterpret_cast<uintptr_t>(target_base + objects_section.Offset()); - uintptr_t objects_end = reinterpret_cast<uintptr_t>(target_base + objects_section.End()); - FixupObjectAdapter fixup_adapter(boot_image, boot_oat, app_image, app_oat); - if (fixup_image) { - // Two pass approach, fix up all classes first, then fix up non class-objects. - // The visited bitmap is used to ensure that pointer arrays are not forwarded twice. - std::unique_ptr<gc::accounting::ContinuousSpaceBitmap> visited_bitmap( - gc::accounting::ContinuousSpaceBitmap::Create("Relocate bitmap", - target_base, - image_header.GetImageSize())); - FixupObjectVisitor fixup_object_visitor(visited_bitmap.get(), - pointer_size, - boot_image, - boot_oat, - app_image, - app_oat); - TimingLogger::ScopedTiming timing("Fixup classes", &logger); - // Fixup objects may read fields in the boot image, use the mutator lock here for sanity. Though - // its probably not required. - ScopedObjectAccess soa(Thread::Current()); - timing.NewTiming("Fixup objects"); - bitmap->VisitMarkedRange(objects_begin, objects_end, fixup_object_visitor); - // Fixup image roots. - CHECK(app_image.InSource(reinterpret_cast<uintptr_t>( - image_header.GetImageRoots<kWithoutReadBarrier>()))); - image_header.RelocateImageObjects(app_image.Delta()); - CHECK_EQ(image_header.GetImageBegin(), target_base); - // Fix up dex cache DexFile pointers. - auto* dex_caches = image_header.GetImageRoot<kWithoutReadBarrier>(ImageHeader::kDexCaches)-> - AsObjectArray<mirror::DexCache, kVerifyNone, kWithoutReadBarrier>(); - for (int32_t i = 0, count = dex_caches->GetLength(); i < count; ++i) { - mirror::DexCache* dex_cache = dex_caches->Get<kVerifyNone, kWithoutReadBarrier>(i); - // Fix up dex cache pointers. - GcRoot<mirror::String>* strings = dex_cache->GetStrings(); - if (strings != nullptr) { - GcRoot<mirror::String>* new_strings = fixup_adapter.ForwardObject(strings); - if (strings != new_strings) { - dex_cache->SetStrings(new_strings); - } - dex_cache->FixupStrings<kWithoutReadBarrier>(new_strings, fixup_adapter); + }; + + // Relocate an image space mapped at target_base which possibly used to be at a different base + // address. Only needs a single image space, not one for both source and destination. + // In place means modifying a single ImageSpace in place rather than relocating from one ImageSpace + // to another. + static bool RelocateInPlace(ImageHeader& image_header, + uint8_t* target_base, + accounting::ContinuousSpaceBitmap* bitmap, + const OatFile* app_oat_file, + std::string* error_msg) { + DCHECK(error_msg != nullptr); + if (!image_header.IsPic()) { + if (image_header.GetImageBegin() == target_base) { + return true; } - GcRoot<mirror::Class>* types = dex_cache->GetResolvedTypes(); - if (types != nullptr) { - GcRoot<mirror::Class>* new_types = fixup_adapter.ForwardObject(types); - if (types != new_types) { - dex_cache->SetResolvedTypes(new_types); - } - dex_cache->FixupResolvedTypes<kWithoutReadBarrier>(new_types, fixup_adapter); - } - ArtMethod** methods = dex_cache->GetResolvedMethods(); - if (methods != nullptr) { - ArtMethod** new_methods = fixup_adapter.ForwardObject(methods); - if (methods != new_methods) { - dex_cache->SetResolvedMethods(new_methods); + *error_msg = StringPrintf("Cannot relocate non-pic image for oat file %s", + (app_oat_file != nullptr) ? app_oat_file->GetLocation().c_str() : ""); + return false; + } + // Set up sections. + uint32_t boot_image_begin = 0; + uint32_t boot_image_end = 0; + uint32_t boot_oat_begin = 0; + uint32_t boot_oat_end = 0; + const PointerSize pointer_size = image_header.GetPointerSize(); + gc::Heap* const heap = Runtime::Current()->GetHeap(); + heap->GetBootImagesSize(&boot_image_begin, &boot_image_end, &boot_oat_begin, &boot_oat_end); + if (boot_image_begin == boot_image_end) { + *error_msg = "Can not relocate app image without boot image space"; + return false; + } + if (boot_oat_begin == boot_oat_end) { + *error_msg = "Can not relocate app image without boot oat file"; + return false; + } + const uint32_t boot_image_size = boot_image_end - boot_image_begin; + const uint32_t boot_oat_size = boot_oat_end - boot_oat_begin; + const uint32_t image_header_boot_image_size = image_header.GetBootImageSize(); + const uint32_t image_header_boot_oat_size = image_header.GetBootOatSize(); + if (boot_image_size != image_header_boot_image_size) { + *error_msg = StringPrintf("Boot image size %" PRIu64 " does not match expected size %" + PRIu64, + static_cast<uint64_t>(boot_image_size), + static_cast<uint64_t>(image_header_boot_image_size)); + return false; + } + if (boot_oat_size != image_header_boot_oat_size) { + *error_msg = StringPrintf("Boot oat size %" PRIu64 " does not match expected size %" + PRIu64, + static_cast<uint64_t>(boot_oat_size), + static_cast<uint64_t>(image_header_boot_oat_size)); + return false; + } + TimingLogger logger(__FUNCTION__, true, false); + RelocationRange boot_image(image_header.GetBootImageBegin(), + boot_image_begin, + boot_image_size); + RelocationRange boot_oat(image_header.GetBootOatBegin(), + boot_oat_begin, + boot_oat_size); + RelocationRange app_image(reinterpret_cast<uintptr_t>(image_header.GetImageBegin()), + reinterpret_cast<uintptr_t>(target_base), + image_header.GetImageSize()); + // Use the oat data section since this is where the OatFile::Begin is. + RelocationRange app_oat(reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin()), + // Not necessarily in low 4GB. + reinterpret_cast<uintptr_t>(app_oat_file->Begin()), + image_header.GetOatDataEnd() - image_header.GetOatDataBegin()); + VLOG(image) << "App image " << app_image; + VLOG(image) << "App oat " << app_oat; + VLOG(image) << "Boot image " << boot_image; + VLOG(image) << "Boot oat " << boot_oat; + // True if we need to fixup any heap pointers, otherwise only code pointers. + const bool fixup_image = boot_image.Delta() != 0 || app_image.Delta() != 0; + const bool fixup_code = boot_oat.Delta() != 0 || app_oat.Delta() != 0; + if (!fixup_image && !fixup_code) { + // Nothing to fix up. + return true; + } + ScopedDebugDisallowReadBarriers sddrb(Thread::Current()); + // Need to update the image to be at the target base. + const ImageSection& objects_section = image_header.GetImageSection(ImageHeader::kSectionObjects); + uintptr_t objects_begin = reinterpret_cast<uintptr_t>(target_base + objects_section.Offset()); + uintptr_t objects_end = reinterpret_cast<uintptr_t>(target_base + objects_section.End()); + FixupObjectAdapter fixup_adapter(boot_image, boot_oat, app_image, app_oat); + if (fixup_image) { + // Two pass approach, fix up all classes first, then fix up non class-objects. + // The visited bitmap is used to ensure that pointer arrays are not forwarded twice. + std::unique_ptr<gc::accounting::ContinuousSpaceBitmap> visited_bitmap( + gc::accounting::ContinuousSpaceBitmap::Create("Relocate bitmap", + target_base, + image_header.GetImageSize())); + FixupObjectVisitor fixup_object_visitor(visited_bitmap.get(), + pointer_size, + boot_image, + boot_oat, + app_image, + app_oat); + TimingLogger::ScopedTiming timing("Fixup classes", &logger); + // Fixup objects may read fields in the boot image, use the mutator lock here for sanity. Though + // its probably not required. + ScopedObjectAccess soa(Thread::Current()); + timing.NewTiming("Fixup objects"); + bitmap->VisitMarkedRange(objects_begin, objects_end, fixup_object_visitor); + // Fixup image roots. + CHECK(app_image.InSource(reinterpret_cast<uintptr_t>( + image_header.GetImageRoots<kWithoutReadBarrier>()))); + image_header.RelocateImageObjects(app_image.Delta()); + CHECK_EQ(image_header.GetImageBegin(), target_base); + // Fix up dex cache DexFile pointers. + auto* dex_caches = image_header.GetImageRoot<kWithoutReadBarrier>(ImageHeader::kDexCaches)-> + AsObjectArray<mirror::DexCache, kVerifyNone, kWithoutReadBarrier>(); + for (int32_t i = 0, count = dex_caches->GetLength(); i < count; ++i) { + mirror::DexCache* dex_cache = dex_caches->Get<kVerifyNone, kWithoutReadBarrier>(i); + // Fix up dex cache pointers. + GcRoot<mirror::String>* strings = dex_cache->GetStrings(); + if (strings != nullptr) { + GcRoot<mirror::String>* new_strings = fixup_adapter.ForwardObject(strings); + if (strings != new_strings) { + dex_cache->SetStrings(new_strings); + } + dex_cache->FixupStrings<kWithoutReadBarrier>(new_strings, fixup_adapter); } - for (size_t j = 0, num = dex_cache->NumResolvedMethods(); j != num; ++j) { - ArtMethod* orig = mirror::DexCache::GetElementPtrSize(new_methods, j, pointer_size); - ArtMethod* copy = fixup_adapter.ForwardObject(orig); - if (orig != copy) { - mirror::DexCache::SetElementPtrSize(new_methods, j, copy, pointer_size); + GcRoot<mirror::Class>* types = dex_cache->GetResolvedTypes(); + if (types != nullptr) { + GcRoot<mirror::Class>* new_types = fixup_adapter.ForwardObject(types); + if (types != new_types) { + dex_cache->SetResolvedTypes(new_types); } + dex_cache->FixupResolvedTypes<kWithoutReadBarrier>(new_types, fixup_adapter); } - } - ArtField** fields = dex_cache->GetResolvedFields(); - if (fields != nullptr) { - ArtField** new_fields = fixup_adapter.ForwardObject(fields); - if (fields != new_fields) { - dex_cache->SetResolvedFields(new_fields); + ArtMethod** methods = dex_cache->GetResolvedMethods(); + if (methods != nullptr) { + ArtMethod** new_methods = fixup_adapter.ForwardObject(methods); + if (methods != new_methods) { + dex_cache->SetResolvedMethods(new_methods); + } + for (size_t j = 0, num = dex_cache->NumResolvedMethods(); j != num; ++j) { + ArtMethod* orig = mirror::DexCache::GetElementPtrSize(new_methods, j, pointer_size); + ArtMethod* copy = fixup_adapter.ForwardObject(orig); + if (orig != copy) { + mirror::DexCache::SetElementPtrSize(new_methods, j, copy, pointer_size); + } + } } - for (size_t j = 0, num = dex_cache->NumResolvedFields(); j != num; ++j) { - ArtField* orig = mirror::DexCache::GetElementPtrSize(new_fields, j, pointer_size); - ArtField* copy = fixup_adapter.ForwardObject(orig); - if (orig != copy) { - mirror::DexCache::SetElementPtrSize(new_fields, j, copy, pointer_size); + ArtField** fields = dex_cache->GetResolvedFields(); + if (fields != nullptr) { + ArtField** new_fields = fixup_adapter.ForwardObject(fields); + if (fields != new_fields) { + dex_cache->SetResolvedFields(new_fields); + } + for (size_t j = 0, num = dex_cache->NumResolvedFields(); j != num; ++j) { + ArtField* orig = mirror::DexCache::GetElementPtrSize(new_fields, j, pointer_size); + ArtField* copy = fixup_adapter.ForwardObject(orig); + if (orig != copy) { + mirror::DexCache::SetElementPtrSize(new_fields, j, copy, pointer_size); + } } } } } - } - { - // Only touches objects in the app image, no need for mutator lock. - TimingLogger::ScopedTiming timing("Fixup methods", &logger); - FixupArtMethodVisitor method_visitor(fixup_image, - pointer_size, - boot_image, - boot_oat, - app_image, - app_oat); - image_header.VisitPackedArtMethods(&method_visitor, target_base, pointer_size); - } - if (fixup_image) { { // Only touches objects in the app image, no need for mutator lock. - TimingLogger::ScopedTiming timing("Fixup fields", &logger); - FixupArtFieldVisitor field_visitor(boot_image, boot_oat, app_image, app_oat); - image_header.VisitPackedArtFields(&field_visitor, target_base); + TimingLogger::ScopedTiming timing("Fixup methods", &logger); + FixupArtMethodVisitor method_visitor(fixup_image, + pointer_size, + boot_image, + boot_oat, + app_image, + app_oat); + image_header.VisitPackedArtMethods(&method_visitor, target_base, pointer_size); } - { - TimingLogger::ScopedTiming timing("Fixup imt", &logger); - image_header.VisitPackedImTables(fixup_adapter, target_base, pointer_size); - } - { - TimingLogger::ScopedTiming timing("Fixup conflict tables", &logger); - image_header.VisitPackedImtConflictTables(fixup_adapter, target_base, pointer_size); + if (fixup_image) { + { + // Only touches objects in the app image, no need for mutator lock. + TimingLogger::ScopedTiming timing("Fixup fields", &logger); + FixupArtFieldVisitor field_visitor(boot_image, boot_oat, app_image, app_oat); + image_header.VisitPackedArtFields(&field_visitor, target_base); + } + { + TimingLogger::ScopedTiming timing("Fixup imt", &logger); + image_header.VisitPackedImTables(fixup_adapter, target_base, pointer_size); + } + { + TimingLogger::ScopedTiming timing("Fixup conflict tables", &logger); + image_header.VisitPackedImtConflictTables(fixup_adapter, target_base, pointer_size); + } + // In the app image case, the image methods are actually in the boot image. + image_header.RelocateImageMethods(boot_image.Delta()); + const auto& class_table_section = image_header.GetImageSection(ImageHeader::kSectionClassTable); + if (class_table_section.Size() > 0u) { + // Note that we require that ReadFromMemory does not make an internal copy of the elements. + // This also relies on visit roots not doing any verification which could fail after we update + // the roots to be the image addresses. + ScopedObjectAccess soa(Thread::Current()); + WriterMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); + ClassTable temp_table; + temp_table.ReadFromMemory(target_base + class_table_section.Offset()); + FixupRootVisitor root_visitor(boot_image, boot_oat, app_image, app_oat); + temp_table.VisitRoots(root_visitor); + } } - // In the app image case, the image methods are actually in the boot image. - image_header.RelocateImageMethods(boot_image.Delta()); - const auto& class_table_section = image_header.GetImageSection(ImageHeader::kSectionClassTable); - if (class_table_section.Size() > 0u) { - // Note that we require that ReadFromMemory does not make an internal copy of the elements. - // This also relies on visit roots not doing any verification which could fail after we update - // the roots to be the image addresses. - ScopedObjectAccess soa(Thread::Current()); - WriterMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); - ClassTable temp_table; - temp_table.ReadFromMemory(target_base + class_table_section.Offset()); - FixupRootVisitor root_visitor(boot_image, boot_oat, app_image, app_oat); - temp_table.VisitRoots(root_visitor); + if (VLOG_IS_ON(image)) { + logger.Dump(LOG(INFO)); } - } - if (VLOG_IS_ON(image)) { - logger.Dump(LOG(INFO)); - } - return true; -} - -static MemMap* LoadImageFile(const char* image_filename, - const char* image_location, - const ImageHeader& image_header, - uint8_t* address, - int fd, - TimingLogger& logger, - std::string* error_msg) { - TimingLogger::ScopedTiming timing("MapImageFile", &logger); - const ImageHeader::StorageMode storage_mode = image_header.GetStorageMode(); - if (storage_mode == ImageHeader::kStorageModeUncompressed) { - return MemMap::MapFileAtAddress(address, - image_header.GetImageSize(), - PROT_READ | PROT_WRITE, - MAP_PRIVATE, - fd, - 0, - /*low_4gb*/true, - /*reuse*/false, - image_filename, - error_msg); - } - - if (storage_mode != ImageHeader::kStorageModeLZ4 && - storage_mode != ImageHeader::kStorageModeLZ4HC) { - *error_msg = StringPrintf("Invalid storage mode in image header %d", - static_cast<int>(storage_mode)); - return nullptr; + return true; } - // Reserve output and decompress into it. - std::unique_ptr<MemMap> map(MemMap::MapAnonymous(image_location, - address, - image_header.GetImageSize(), - PROT_READ | PROT_WRITE, - /*low_4gb*/true, - /*reuse*/false, - error_msg)); - if (map != nullptr) { - const size_t stored_size = image_header.GetDataSize(); - const size_t decompress_offset = sizeof(ImageHeader); // Skip the header. - std::unique_ptr<MemMap> temp_map(MemMap::MapFile(sizeof(ImageHeader) + stored_size, - PROT_READ, - MAP_PRIVATE, - fd, - /*offset*/0, - /*low_4gb*/false, - image_filename, - error_msg)); - if (temp_map == nullptr) { - DCHECK(!error_msg->empty()); + static std::unique_ptr<OatFile> OpenOatFile(const ImageSpace& image, + const char* image_path, + std::string* error_msg) { + const ImageHeader& image_header = image.GetImageHeader(); + std::string oat_filename = ImageHeader::GetOatLocationFromImageLocation(image_path); + + CHECK(image_header.GetOatDataBegin() != nullptr); + + std::unique_ptr<OatFile> oat_file(OatFile::Open(oat_filename, + oat_filename, + image_header.GetOatDataBegin(), + image_header.GetOatFileBegin(), + !Runtime::Current()->IsAotCompiler(), + /*low_4gb*/false, + nullptr, + error_msg)); + if (oat_file == nullptr) { + *error_msg = StringPrintf("Failed to open oat file '%s' referenced from image %s: %s", + oat_filename.c_str(), + image.GetName(), + error_msg->c_str()); return nullptr; } - memcpy(map->Begin(), &image_header, sizeof(ImageHeader)); - const uint64_t start = NanoTime(); - // LZ4HC and LZ4 have same internal format, both use LZ4_decompress. - TimingLogger::ScopedTiming timing2("LZ4 decompress image", &logger); - const size_t decompressed_size = LZ4_decompress_safe( - reinterpret_cast<char*>(temp_map->Begin()) + sizeof(ImageHeader), - reinterpret_cast<char*>(map->Begin()) + decompress_offset, - stored_size, - map->Size() - decompress_offset); - VLOG(image) << "Decompressing image took " << PrettyDuration(NanoTime() - start); - if (decompressed_size + sizeof(ImageHeader) != image_header.GetImageSize()) { - *error_msg = StringPrintf( - "Decompressed size does not match expected image size %zu vs %zu", - decompressed_size + sizeof(ImageHeader), - image_header.GetImageSize()); + uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum(); + uint32_t image_oat_checksum = image_header.GetOatChecksum(); + if (oat_checksum != image_oat_checksum) { + *error_msg = StringPrintf("Failed to match oat file checksum 0x%x to expected oat checksum 0x%x" + " in image %s", + oat_checksum, + image_oat_checksum, + image.GetName()); return nullptr; } - } - - return map.release(); -} - -ImageSpace* ImageSpace::Init(const char* image_filename, - const char* image_location, - bool validate_oat_file, - const OatFile* oat_file, - std::string* error_msg) { - CHECK(image_filename != nullptr); - CHECK(image_location != nullptr); - - TimingLogger logger(__PRETTY_FUNCTION__, true, VLOG_IS_ON(image)); - VLOG(image) << "ImageSpace::Init entering image_filename=" << image_filename; - - std::unique_ptr<File> file; - { - TimingLogger::ScopedTiming timing("OpenImageFile", &logger); - file.reset(OS::OpenFileForReading(image_filename)); - if (file == nullptr) { - *error_msg = StringPrintf("Failed to open '%s'", image_filename); + int32_t image_patch_delta = image_header.GetPatchDelta(); + int32_t oat_patch_delta = oat_file->GetOatHeader().GetImagePatchDelta(); + if (oat_patch_delta != image_patch_delta && !image_header.CompilePic()) { + // We should have already relocated by this point. Bail out. + *error_msg = StringPrintf("Failed to match oat file patch delta %d to expected patch delta %d " + "in image %s", + oat_patch_delta, + image_patch_delta, + image.GetName()); return nullptr; } - } - ImageHeader temp_image_header; - ImageHeader* image_header = &temp_image_header; - { - TimingLogger::ScopedTiming timing("ReadImageHeader", &logger); - bool success = file->ReadFully(image_header, sizeof(*image_header)); - if (!success || !image_header->IsValid()) { - *error_msg = StringPrintf("Invalid image header in '%s'", image_filename); - return nullptr; + + return oat_file; + } + + static bool ValidateOatFile(const ImageSpace& space, + const OatFile& oat_file, + std::string* error_msg) { + for (const OatFile::OatDexFile* oat_dex_file : oat_file.GetOatDexFiles()) { + const std::string& dex_file_location = oat_dex_file->GetDexFileLocation(); + uint32_t dex_file_location_checksum; + if (!DexFile::GetChecksum(dex_file_location.c_str(), &dex_file_location_checksum, error_msg)) { + *error_msg = StringPrintf("Failed to get checksum of dex file '%s' referenced by image %s: " + "%s", + dex_file_location.c_str(), + space.GetName(), + error_msg->c_str()); + return false; + } + if (dex_file_location_checksum != oat_dex_file->GetDexFileLocationChecksum()) { + *error_msg = StringPrintf("ValidateOatFile found checksum mismatch between oat file '%s' and " + "dex file '%s' (0x%x != 0x%x)", + oat_file.GetLocation().c_str(), + dex_file_location.c_str(), + oat_dex_file->GetDexFileLocationChecksum(), + dex_file_location_checksum); + return false; + } } + return true; } - // Check that the file is larger or equal to the header size + data size. - const uint64_t image_file_size = static_cast<uint64_t>(file->GetLength()); - if (image_file_size < sizeof(ImageHeader) + image_header->GetDataSize()) { - *error_msg = StringPrintf("Image file truncated: %" PRIu64 " vs. %" PRIu64 ".", - image_file_size, - sizeof(ImageHeader) + image_header->GetDataSize()); - return nullptr; - } +}; - if (oat_file != nullptr) { - // If we have an oat file, check the oat file checksum. The oat file is only non-null for the - // app image case. Otherwise, we open the oat file after the image and check the checksum there. - const uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum(); - const uint32_t image_oat_checksum = image_header->GetOatChecksum(); - if (oat_checksum != image_oat_checksum) { - *error_msg = StringPrintf("Oat checksum 0x%x does not match the image one 0x%x in image %s", - oat_checksum, - image_oat_checksum, - image_filename); - return nullptr; - } +static constexpr uint64_t kLowSpaceValue = 50 * MB; +static constexpr uint64_t kTmpFsSentinelValue = 384 * MB; + +// Read the free space of the cache partition and make a decision whether to keep the generated +// image. This is to try to mitigate situations where the system might run out of space later. +static bool CheckSpace(const std::string& cache_filename, std::string* error_msg) { + // Using statvfs vs statvfs64 because of b/18207376, and it is enough for all practical purposes. + struct statvfs buf; + + int res = TEMP_FAILURE_RETRY(statvfs(cache_filename.c_str(), &buf)); + if (res != 0) { + // Could not stat. Conservatively tell the system to delete the image. + *error_msg = "Could not stat the filesystem, assuming low-memory situation."; + return false; } - if (VLOG_IS_ON(startup)) { - LOG(INFO) << "Dumping image sections"; - for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) { - const auto section_idx = static_cast<ImageHeader::ImageSections>(i); - auto& section = image_header->GetImageSection(section_idx); - LOG(INFO) << section_idx << " start=" - << reinterpret_cast<void*>(image_header->GetImageBegin() + section.Offset()) << " " - << section; + uint64_t fs_overall_size = buf.f_bsize * static_cast<uint64_t>(buf.f_blocks); + // Zygote is privileged, but other things are not. Use bavail. + uint64_t fs_free_size = buf.f_bsize * static_cast<uint64_t>(buf.f_bavail); + + // Take the overall size as an indicator for a tmpfs, which is being used for the decryption + // environment. We do not want to fail quickening the boot image there, as it is beneficial + // for time-to-UI. + if (fs_overall_size > kTmpFsSentinelValue) { + if (fs_free_size < kLowSpaceValue) { + *error_msg = StringPrintf("Low-memory situation: only %4.2f megabytes available, need at " + "least %" PRIu64 ".", + static_cast<double>(fs_free_size) / MB, + kLowSpaceValue / MB); + return false; } } + return true; +} - const auto& bitmap_section = image_header->GetImageSection(ImageHeader::kSectionImageBitmap); - // The location we want to map from is the first aligned page after the end of the stored - // (possibly compressed) data. - const size_t image_bitmap_offset = RoundUp(sizeof(ImageHeader) + image_header->GetDataSize(), - kPageSize); - const size_t end_of_bitmap = image_bitmap_offset + bitmap_section.Size(); - if (end_of_bitmap != image_file_size) { - *error_msg = StringPrintf( - "Image file size does not equal end of bitmap: size=%" PRIu64 " vs. %zu.", image_file_size, - end_of_bitmap); - return nullptr; - } +std::unique_ptr<ImageSpace> ImageSpace::CreateBootImage(const char* image_location, + const InstructionSet image_isa, + bool secondary_image, + std::string* error_msg) { + ScopedTrace trace(__FUNCTION__); - std::unique_ptr<MemMap> map; - // GetImageBegin is the preferred address to map the image. If we manage to map the - // image at the image begin, the amount of fixup work required is minimized. - map.reset(LoadImageFile(image_filename, - image_location, - *image_header, - image_header->GetImageBegin(), - file->Fd(), - logger, - error_msg)); - // If the header specifies PIC mode, we can also map at a random low_4gb address since we can - // relocate in-place. - if (map == nullptr && image_header->IsPic()) { - map.reset(LoadImageFile(image_filename, - image_location, - *image_header, - /* address */ nullptr, - file->Fd(), - logger, - error_msg)); - } - // Were we able to load something and continue? - if (map == nullptr) { - DCHECK(!error_msg->empty()); - return nullptr; - } - DCHECK_EQ(0, memcmp(image_header, map->Begin(), sizeof(ImageHeader))); - - std::unique_ptr<MemMap> image_bitmap_map(MemMap::MapFileAtAddress(nullptr, - bitmap_section.Size(), - PROT_READ, MAP_PRIVATE, - file->Fd(), - image_bitmap_offset, - /*low_4gb*/false, - /*reuse*/false, - image_filename, - error_msg)); - if (image_bitmap_map == nullptr) { - *error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str()); - return nullptr; + // Step 0: Extra zygote work. + + // Step 0.a: If we're the zygote, mark boot. + const bool is_zygote = Runtime::Current()->IsZygote(); + if (is_zygote && !secondary_image) { + MarkZygoteStart(image_isa, Runtime::Current()->GetZygoteMaxFailedBoots()); } - // Loaded the map, use the image header from the file now in case we patch it with - // RelocateInPlace. - image_header = reinterpret_cast<ImageHeader*>(map->Begin()); - const uint32_t bitmap_index = bitmap_index_.FetchAndAddSequentiallyConsistent(1); - std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u", - image_filename, - bitmap_index)); - // Bitmap only needs to cover until the end of the mirror objects section. - const ImageSection& image_objects = image_header->GetImageSection(ImageHeader::kSectionObjects); - // We only want the mirror object, not the ArtFields and ArtMethods. - uint8_t* const image_end = map->Begin() + image_objects.End(); - std::unique_ptr<accounting::ContinuousSpaceBitmap> bitmap; - { - TimingLogger::ScopedTiming timing("CreateImageBitmap", &logger); - bitmap.reset( - accounting::ContinuousSpaceBitmap::CreateFromMemMap( - bitmap_name, - image_bitmap_map.release(), - reinterpret_cast<uint8_t*>(map->Begin()), - image_objects.End())); - if (bitmap == nullptr) { - *error_msg = StringPrintf("Could not create bitmap '%s'", bitmap_name.c_str()); - return nullptr; + + // Step 0.b: If we're the zygote, check for free space, and prune the cache preemptively, + // if necessary. While the runtime may be fine (it is pretty tolerant to + // out-of-disk-space situations), other parts of the platform are not. + // + // The advantage of doing this proactively is that the later steps are simplified, + // i.e., we do not need to code retries. + std::string system_filename; + bool has_system = false; + std::string cache_filename; + bool has_cache = false; + bool dalvik_cache_exists = false; + bool is_global_cache = true; + std::string dalvik_cache; + bool found_image = FindImageFilenameImpl(image_location, + image_isa, + &has_system, + &system_filename, + &dalvik_cache_exists, + &dalvik_cache, + &is_global_cache, + &has_cache, + &cache_filename); + + if (is_zygote && dalvik_cache_exists) { + DCHECK(!dalvik_cache.empty()); + std::string local_error_msg; + if (!CheckSpace(dalvik_cache, &local_error_msg)) { + LOG(WARNING) << local_error_msg << " Preemptively pruning the dalvik cache."; + PruneDalvikCache(image_isa); + + // Re-evaluate the image. + found_image = FindImageFilenameImpl(image_location, + image_isa, + &has_system, + &system_filename, + &dalvik_cache_exists, + &dalvik_cache, + &is_global_cache, + &has_cache, + &cache_filename); } } - { - TimingLogger::ScopedTiming timing("RelocateImage", &logger); - if (!RelocateInPlace(*image_header, - map->Begin(), - bitmap.get(), - oat_file, - error_msg)) { - return nullptr; + + // Collect all the errors. + std::vector<std::string> error_msgs; + + // Step 1: Check if we have an existing and relocated image. + + // Step 1.a: Have files in system and cache. Then they need to match. + if (found_image && has_system && has_cache) { + std::string local_error_msg; + // Check that the files are matching. + if (ChecksumsMatch(system_filename.c_str(), cache_filename.c_str(), &local_error_msg)) { + std::unique_ptr<ImageSpace> relocated_space = + ImageSpaceLoader::Load(image_location, + cache_filename, + is_zygote, + is_global_cache, + /* is_system */ false, + /* relocated_version_used */ true, + &local_error_msg); + if (relocated_space != nullptr) { + return relocated_space; + } } - } - // We only want the mirror object, not the ArtFields and ArtMethods. - std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename, - image_location, - map.release(), - bitmap.release(), - image_end)); - - // VerifyImageAllocations() will be called later in Runtime::Init() - // as some class roots like ArtMethod::java_lang_reflect_ArtMethod_ - // and ArtField::java_lang_reflect_ArtField_, which are used from - // Object::SizeOf() which VerifyImageAllocations() calls, are not - // set yet at this point. - if (oat_file == nullptr) { - TimingLogger::ScopedTiming timing("OpenOatFile", &logger); - space->oat_file_.reset(space->OpenOatFile(image_filename, error_msg)); - if (space->oat_file_ == nullptr) { - DCHECK(!error_msg->empty()); - return nullptr; + error_msgs.push_back(local_error_msg); + } + + // Step 1.b: Only have a cache file. + if (found_image && !has_system && has_cache) { + std::string local_error_msg; + std::unique_ptr<ImageSpace> cache_space = + ImageSpaceLoader::Load(image_location, + cache_filename, + is_zygote, + is_global_cache, + /* is_system */ false, + /* relocated_version_used */ true, + &local_error_msg); + if (cache_space != nullptr) { + return cache_space; } - space->oat_file_non_owned_ = space->oat_file_.get(); - } else { - space->oat_file_non_owned_ = oat_file; + error_msgs.push_back(local_error_msg); } - if (validate_oat_file) { - TimingLogger::ScopedTiming timing("ValidateOatFile", &logger); - if (!space->ValidateOatFile(error_msg)) { - DCHECK(!error_msg->empty()); - return nullptr; - } - } + // Step 2: We have an existing image in /system. - Runtime* runtime = Runtime::Current(); - - // If oat_file is null, then it is the boot image space. Use oat_file_non_owned_ from the space - // to set the runtime methods. - CHECK_EQ(oat_file != nullptr, image_header->IsAppImage()); - if (image_header->IsAppImage()) { - CHECK_EQ(runtime->GetResolutionMethod(), - image_header->GetImageMethod(ImageHeader::kResolutionMethod)); - CHECK_EQ(runtime->GetImtConflictMethod(), - image_header->GetImageMethod(ImageHeader::kImtConflictMethod)); - CHECK_EQ(runtime->GetImtUnimplementedMethod(), - image_header->GetImageMethod(ImageHeader::kImtUnimplementedMethod)); - CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveAllCalleeSaves), - image_header->GetImageMethod(ImageHeader::kSaveAllCalleeSavesMethod)); - CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveRefsOnly), - image_header->GetImageMethod(ImageHeader::kSaveRefsOnlyMethod)); - CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveRefsAndArgs), - image_header->GetImageMethod(ImageHeader::kSaveRefsAndArgsMethod)); - CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveEverything), - image_header->GetImageMethod(ImageHeader::kSaveEverythingMethod)); - } else if (!runtime->HasResolutionMethod()) { - runtime->SetInstructionSet(space->oat_file_non_owned_->GetOatHeader().GetInstructionSet()); - runtime->SetResolutionMethod(image_header->GetImageMethod(ImageHeader::kResolutionMethod)); - runtime->SetImtConflictMethod(image_header->GetImageMethod(ImageHeader::kImtConflictMethod)); - runtime->SetImtUnimplementedMethod( - image_header->GetImageMethod(ImageHeader::kImtUnimplementedMethod)); - runtime->SetCalleeSaveMethod( - image_header->GetImageMethod(ImageHeader::kSaveAllCalleeSavesMethod), - Runtime::kSaveAllCalleeSaves); - runtime->SetCalleeSaveMethod( - image_header->GetImageMethod(ImageHeader::kSaveRefsOnlyMethod), Runtime::kSaveRefsOnly); - runtime->SetCalleeSaveMethod( - image_header->GetImageMethod(ImageHeader::kSaveRefsAndArgsMethod), - Runtime::kSaveRefsAndArgs); - runtime->SetCalleeSaveMethod( - image_header->GetImageMethod(ImageHeader::kSaveEverythingMethod), Runtime::kSaveEverything); - } + // Step 2.a: We are not required to relocate it. Then we can use it directly. + bool relocate = Runtime::Current()->ShouldRelocate(); - VLOG(image) << "ImageSpace::Init exiting " << *space.get(); - if (VLOG_IS_ON(image)) { - logger.Dump(LOG(INFO)); + if (found_image && has_system && !relocate) { + std::string local_error_msg; + std::unique_ptr<ImageSpace> system_space = + ImageSpaceLoader::Load(image_location, + system_filename, + is_zygote, + is_global_cache, + /* is_system */ true, + /* relocated_version_used */ false, + &local_error_msg); + if (system_space != nullptr) { + return system_space; + } + error_msgs.push_back(local_error_msg); + } + + // Step 2.b: We require a relocated image. Then we must patch it. This step fails if this is a + // secondary image. + if (found_image && has_system && relocate) { + std::string local_error_msg; + if (!Runtime::Current()->IsImageDex2OatEnabled()) { + local_error_msg = "Patching disabled."; + } else if (secondary_image) { + local_error_msg = "Cannot patch a secondary image."; + } else if (ImageCreationAllowed(is_global_cache, &local_error_msg)) { + bool patch_success = + RelocateImage(image_location, cache_filename.c_str(), image_isa, &local_error_msg); + if (patch_success) { + std::unique_ptr<ImageSpace> patched_space = + ImageSpaceLoader::Load(image_location, + cache_filename, + is_zygote, + is_global_cache, + /* is_system */ false, + /* relocated_version_used */ true, + &local_error_msg); + if (patched_space != nullptr) { + return patched_space; + } + } + } + error_msgs.push_back(StringPrintf("Cannot relocate image %s to %s: %s", + image_location, + cache_filename.c_str(), + local_error_msg.c_str())); + } + + // Step 3: We do not have an existing image in /system, so generate an image into the dalvik + // cache. This step fails if this is a secondary image. + if (!has_system) { + std::string local_error_msg; + if (!Runtime::Current()->IsImageDex2OatEnabled()) { + local_error_msg = "Image compilation disabled."; + } else if (secondary_image) { + local_error_msg = "Cannot compile a secondary image."; + } else if (ImageCreationAllowed(is_global_cache, &local_error_msg)) { + bool compilation_success = GenerateImage(cache_filename, image_isa, &local_error_msg); + if (compilation_success) { + std::unique_ptr<ImageSpace> compiled_space = + ImageSpaceLoader::Load(image_location, + cache_filename, + is_zygote, + is_global_cache, + /* is_system */ false, + /* relocated_version_used */ true, + &local_error_msg); + if (compiled_space != nullptr) { + return compiled_space; + } + } + } + error_msgs.push_back(StringPrintf("Cannot compile image to %s: %s", + cache_filename.c_str(), + local_error_msg.c_str())); } - return space.release(); -} -OatFile* ImageSpace::OpenOatFile(const char* image_path, std::string* error_msg) const { - const ImageHeader& image_header = GetImageHeader(); - std::string oat_filename = ImageHeader::GetOatLocationFromImageLocation(image_path); - - CHECK(image_header.GetOatDataBegin() != nullptr); - - OatFile* oat_file = OatFile::Open(oat_filename, - oat_filename, - image_header.GetOatDataBegin(), - image_header.GetOatFileBegin(), - !Runtime::Current()->IsAotCompiler(), - /*low_4gb*/false, - nullptr, - error_msg); - if (oat_file == nullptr) { - *error_msg = StringPrintf("Failed to open oat file '%s' referenced from image %s: %s", - oat_filename.c_str(), GetName(), error_msg->c_str()); - return nullptr; - } - uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum(); - uint32_t image_oat_checksum = image_header.GetOatChecksum(); - if (oat_checksum != image_oat_checksum) { - *error_msg = StringPrintf("Failed to match oat file checksum 0x%x to expected oat checksum 0x%x" - " in image %s", oat_checksum, image_oat_checksum, GetName()); - return nullptr; - } - int32_t image_patch_delta = image_header.GetPatchDelta(); - int32_t oat_patch_delta = oat_file->GetOatHeader().GetImagePatchDelta(); - if (oat_patch_delta != image_patch_delta && !image_header.CompilePic()) { - // We should have already relocated by this point. Bail out. - *error_msg = StringPrintf("Failed to match oat file patch delta %d to expected patch delta %d " - "in image %s", oat_patch_delta, image_patch_delta, GetName()); - return nullptr; + // We failed. Prune the cache the free up space, create a compound error message and return no + // image. + PruneDalvikCache(image_isa); + + std::ostringstream oss; + bool first = true; + for (auto msg : error_msgs) { + if (!first) { + oss << "\n "; + } + oss << msg; } + *error_msg = oss.str(); - return oat_file; + return nullptr; } -bool ImageSpace::ValidateOatFile(std::string* error_msg) const { - CHECK(oat_file_.get() != nullptr); - for (const OatFile::OatDexFile* oat_dex_file : oat_file_->GetOatDexFiles()) { - const std::string& dex_file_location = oat_dex_file->GetDexFileLocation(); - uint32_t dex_file_location_checksum; - if (!DexFile::GetChecksum(dex_file_location.c_str(), &dex_file_location_checksum, error_msg)) { - *error_msg = StringPrintf("Failed to get checksum of dex file '%s' referenced by image %s: " - "%s", dex_file_location.c_str(), GetName(), error_msg->c_str()); - return false; - } - if (dex_file_location_checksum != oat_dex_file->GetDexFileLocationChecksum()) { - *error_msg = StringPrintf("ValidateOatFile found checksum mismatch between oat file '%s' and " - "dex file '%s' (0x%x != 0x%x)", - oat_file_->GetLocation().c_str(), dex_file_location.c_str(), - oat_dex_file->GetDexFileLocationChecksum(), - dex_file_location_checksum); - return false; - } - } - return true; +std::unique_ptr<ImageSpace> ImageSpace::CreateFromAppImage(const char* image, + const OatFile* oat_file, + std::string* error_msg) { + return ImageSpaceLoader::Init(image, + image, + /*validate_oat_file*/false, + oat_file, + /*out*/error_msg); } const OatFile* ImageSpace::GetOatFile() const { @@ -1570,16 +1667,6 @@ void ImageSpace::CreateMultiImageLocations(const std::string& input_image_file_n } } -ImageSpace* ImageSpace::CreateFromAppImage(const char* image, - const OatFile* oat_file, - std::string* error_msg) { - return gc::space::ImageSpace::Init(image, - image, - /*validate_oat_file*/false, - oat_file, - /*out*/error_msg); -} - void ImageSpace::DumpSections(std::ostream& os) const { const uint8_t* base = Begin(); const ImageHeader& header = GetImageHeader(); diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h index c9741d0648..534232d773 100644 --- a/runtime/gc/space/image_space.h +++ b/runtime/gc/space/image_space.h @@ -43,24 +43,19 @@ class ImageSpace : public MemMapSpace { // creation of the alloc space. The ReleaseOatFile will later be // used to transfer ownership of the OatFile to the ClassLinker when // it is initialized. - static ImageSpace* CreateBootImage(const char* image, + static std::unique_ptr<ImageSpace> CreateBootImage(const char* image, InstructionSet image_isa, bool secondary_image, std::string* error_msg) SHARED_REQUIRES(Locks::mutator_lock_); // Try to open an existing app image space. - static ImageSpace* CreateFromAppImage(const char* image, - const OatFile* oat_file, - std::string* error_msg) + static std::unique_ptr<ImageSpace> CreateFromAppImage(const char* image, + const OatFile* oat_file, + std::string* error_msg) SHARED_REQUIRES(Locks::mutator_lock_); // Reads the image header from the specified image location for the - // instruction set image_isa or dies trying. - static ImageHeader* ReadImageHeaderOrDie(const char* image_location, - InstructionSet image_isa); - - // Reads the image header from the specified image location for the // instruction set image_isa. Returns null on failure, with // reason in error_msg. static ImageHeader* ReadImageHeader(const char* image_location, @@ -158,21 +153,13 @@ class ImageSpace : public MemMapSpace { // relative to its DexFile inputs. Otherwise (for /data), validate the inputs and generate the // OatFile in /data/dalvik-cache if necessary. If the oat_file is null, it uses the oat file from // the image. - static ImageSpace* Init(const char* image_filename, - const char* image_location, - bool validate_oat_file, - const OatFile* oat_file, - std::string* error_msg) - SHARED_REQUIRES(Locks::mutator_lock_); - - OatFile* OpenOatFile(const char* image, std::string* error_msg) const - SHARED_REQUIRES(Locks::mutator_lock_); - - bool ValidateOatFile(std::string* error_msg) const + static std::unique_ptr<ImageSpace> Init(const char* image_filename, + const char* image_location, + bool validate_oat_file, + const OatFile* oat_file, + std::string* error_msg) SHARED_REQUIRES(Locks::mutator_lock_); - friend class Space; - static Atomic<uint32_t> bitmap_index_; std::unique_ptr<accounting::ContinuousSpaceBitmap> live_bitmap_; @@ -194,6 +181,9 @@ class ImageSpace : public MemMapSpace { const std::string image_location_; + friend class ImageSpaceLoader; + friend class Space; + private: DISALLOW_COPY_AND_ASSIGN(ImageSpace); }; diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index 0abee42cb1..fe6332dee2 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -780,8 +780,12 @@ const OatFileAssistant::ImageInfo* OatFileAssistant::GetImageInfo() { image_header.GetOatDataBegin()); cached_image_info_.patch_delta = image_header.GetPatchDelta(); } else { + std::string error_msg; std::unique_ptr<ImageHeader> image_header( - gc::space::ImageSpace::ReadImageHeaderOrDie(cached_image_info_.location.c_str(), isa_)); + gc::space::ImageSpace::ReadImageHeader(cached_image_info_.location.c_str(), + isa_, + &error_msg)); + CHECK(image_header != nullptr) << error_msg; cached_image_info_.oat_checksum = image_header->GetOatChecksum(); cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>( image_header->GetOatDataBegin()); @@ -807,8 +811,10 @@ uint32_t OatFileAssistant::CalculateCombinedImageChecksum(InstructionSet isa) { } else { for (gc::space::ImageSpace* image_space : image_spaces) { std::string location = image_space->GetImageLocation(); + std::string error_msg; std::unique_ptr<ImageHeader> image_header( - gc::space::ImageSpace::ReadImageHeaderOrDie(location.c_str(), isa)); + gc::space::ImageSpace::ReadImageHeader(location.c_str(), isa, &error_msg)); + CHECK(image_header != nullptr) << error_msg; checksum ^= image_header->GetOatChecksum(); } } @@ -822,7 +828,7 @@ uint32_t OatFileAssistant::GetCombinedImageChecksum() { return combined_image_checksum_; } -gc::space::ImageSpace* OatFileAssistant::OpenImageSpace(const OatFile* oat_file) { +std::unique_ptr<gc::space::ImageSpace> OatFileAssistant::OpenImageSpace(const OatFile* oat_file) { DCHECK(oat_file != nullptr); std::string art_file = ArtFileName(oat_file); if (art_file.empty()) { @@ -830,9 +836,8 @@ gc::space::ImageSpace* OatFileAssistant::OpenImageSpace(const OatFile* oat_file) } std::string error_msg; ScopedObjectAccess soa(Thread::Current()); - gc::space::ImageSpace* ret = gc::space::ImageSpace::CreateFromAppImage(art_file.c_str(), - oat_file, - &error_msg); + std::unique_ptr<gc::space::ImageSpace> 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; } diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index effeabb126..3f018dceff 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -180,7 +180,7 @@ class OatFileAssistant { std::unique_ptr<OatFile> GetBestOatFile(); // Open and returns an image space associated with the oat file. - static gc::space::ImageSpace* OpenImageSpace(const OatFile* oat_file); + static std::unique_ptr<gc::space::ImageSpace> OpenImageSpace(const OatFile* oat_file); // Loads the dex files in the given oat file for the given dex location. // The oat file should be up to date for the given dex location. diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index 7680517661..2e67ffe5ed 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -632,8 +632,8 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( if (source_oat_file != nullptr) { bool added_image_space = false; if (source_oat_file->IsExecutable()) { - std::unique_ptr<gc::space::ImageSpace> image_space( - kEnableAppImage ? oat_file_assistant.OpenImageSpace(source_oat_file) : nullptr); + std::unique_ptr<gc::space::ImageSpace> image_space = + kEnableAppImage ? oat_file_assistant.OpenImageSpace(source_oat_file) : nullptr; if (image_space != nullptr) { ScopedObjectAccess soa(self); StackHandleScope<1> hs(self); |