| /* | |
| * Copyright (C) 2022 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "runtime_image.h" | |
| #include <lz4.h> | |
| #include <unistd.h> | |
| #include "android-base/file.h" | |
| #include "android-base/stringprintf.h" | |
| #include "android-base/strings.h" | |
| #include "arch/instruction_set.h" | |
| #include "arch/instruction_set_features.h" | |
| #include "base/arena_allocator.h" | |
| #include "base/arena_containers.h" | |
| #include "base/bit_utils.h" | |
| #include "base/file_utils.h" | |
| #include "base/length_prefixed_array.h" | |
| #include "base/scoped_flock.h" | |
| #include "base/stl_util.h" | |
| #include "base/systrace.h" | |
| #include "base/unix_file/fd_file.h" | |
| #include "base/utils.h" | |
| #include "class_loader_context.h" | |
| #include "class_loader_utils.h" | |
| #include "class_root-inl.h" | |
| #include "dex/class_accessor-inl.h" | |
| #include "gc/space/image_space.h" | |
| #include "mirror/object-inl.h" | |
| #include "mirror/object-refvisitor-inl.h" | |
| #include "mirror/object_array-alloc-inl.h" | |
| #include "mirror/object_array-inl.h" | |
| #include "mirror/object_array.h" | |
| #include "mirror/string-inl.h" | |
| #include "nterp_helpers.h" | |
| #include "oat/image.h" | |
| #include "oat/oat.h" | |
| #include "profile/profile_compilation_info.h" | |
| #include "scoped_thread_state_change-inl.h" | |
| #include "vdex_file.h" | |
| namespace art HIDDEN { | |
| using android::base::StringPrintf; | |
| /** | |
| * The native data structures that we store in the image. | |
| */ | |
| enum class NativeRelocationKind { | |
| kArtFieldArray, | |
| kArtMethodArray, | |
| kArtMethod, | |
| kImTable, | |
| // For dex cache arrays which can stay in memory even after startup. Those are | |
| // dex cache arrays whose size is below a given threshold, defined by | |
| // DexCache::ShouldAllocateFullArray. | |
| kFullNativeDexCacheArray, | |
| // For dex cache arrays which we will want to release after app startup. | |
| kStartupNativeDexCacheArray, | |
| }; | |
| /** | |
| * Helper class to generate an app image at runtime. | |
| */ | |
| class RuntimeImageHelper { | |
| public: | |
| explicit RuntimeImageHelper(gc::Heap* heap) : | |
| allocator_(Runtime::Current()->GetArenaPool()), | |
| objects_(allocator_.Adapter()), | |
| art_fields_(allocator_.Adapter()), | |
| art_methods_(allocator_.Adapter()), | |
| im_tables_(allocator_.Adapter()), | |
| metadata_(allocator_.Adapter()), | |
| dex_cache_arrays_(allocator_.Adapter()), | |
| string_reference_offsets_(allocator_.Adapter()), | |
| sections_(ImageHeader::kSectionCount, allocator_.Adapter()), | |
| object_offsets_(allocator_.Adapter()), | |
| classes_(allocator_.Adapter()), | |
| array_classes_(allocator_.Adapter()), | |
| dex_caches_(allocator_.Adapter()), | |
| class_hashes_(allocator_.Adapter()), | |
| native_relocations_(allocator_.Adapter()), | |
| boot_image_begin_(heap->GetBootImagesStartAddress()), | |
| boot_image_size_(heap->GetBootImagesSize()), | |
| image_begin_(boot_image_begin_ + boot_image_size_), | |
| // Note: image relocation considers the image header in the bitmap. | |
| object_section_size_(sizeof(ImageHeader)), | |
| intern_table_(InternStringHash(this), InternStringEquals(this)), | |
| class_table_(ClassDescriptorHash(this), ClassDescriptorEquals()) {} | |
| bool Generate(std::string* error_msg) { | |
| if (!WriteObjects(error_msg)) { | |
| return false; | |
| } | |
| // Generate the sections information stored in the header. | |
| CreateImageSections(); | |
| // Now that all sections have been created and we know their offset and | |
| // size, relocate native pointers inside classes and ImTables. | |
| RelocateNativePointers(); | |
| // Generate the bitmap section, stored kElfSegmentAlignment-aligned after the sections data and | |
| // of size `object_section_size_` rounded up to kCardSize to match the bitmap size expected by | |
| // Loader::Init at art::gc::space::ImageSpace. | |
| size_t sections_end = sections_[ImageHeader::kSectionMetadata].End(); | |
| image_bitmap_ = gc::accounting::ContinuousSpaceBitmap::Create( | |
| "image bitmap", | |
| reinterpret_cast<uint8_t*>(image_begin_), | |
| RoundUp(object_section_size_, gc::accounting::CardTable::kCardSize)); | |
| for (uint32_t offset : object_offsets_) { | |
| DCHECK(IsAligned<kObjectAlignment>(image_begin_ + sizeof(ImageHeader) + offset)); | |
| image_bitmap_.Set( | |
| reinterpret_cast<mirror::Object*>(image_begin_ + sizeof(ImageHeader) + offset)); | |
| } | |
| const size_t bitmap_bytes = image_bitmap_.Size(); | |
| auto* bitmap_section = §ions_[ImageHeader::kSectionImageBitmap]; | |
| // The offset of the bitmap section should be aligned to kElfSegmentAlignment to enable mapping | |
| // the section from file to memory. However the section size doesn't have to be rounded up as | |
| // it is located at the end of the file. When mapping file contents to memory, if the last page | |
| // of the mapping is only partially filled with data, the rest will be zero-filled. | |
| *bitmap_section = ImageSection(RoundUp(sections_end, kElfSegmentAlignment), bitmap_bytes); | |
| // Compute boot image checksum and boot image components, to be stored in | |
| // the header. | |
| gc::Heap* const heap = Runtime::Current()->GetHeap(); | |
| uint32_t boot_image_components = 0u; | |
| uint32_t boot_image_checksums = 0u; | |
| const std::vector<gc::space::ImageSpace*>& image_spaces = heap->GetBootImageSpaces(); | |
| for (size_t i = 0u, size = image_spaces.size(); i != size; ) { | |
| const ImageHeader& header = image_spaces[i]->GetImageHeader(); | |
| boot_image_components += header.GetComponentCount(); | |
| boot_image_checksums ^= header.GetImageChecksum(); | |
| DCHECK_LE(header.GetImageSpaceCount(), size - i); | |
| i += header.GetImageSpaceCount(); | |
| } | |
| header_ = ImageHeader( | |
| /* image_reservation_size= */ RoundUp(sections_end, kElfSegmentAlignment), | |
| /* component_count= */ 1, | |
| image_begin_, | |
| sections_end, | |
| sections_.data(), | |
| /* image_roots= */ image_begin_ + sizeof(ImageHeader), | |
| /* oat_checksum= */ 0, | |
| /* oat_file_begin= */ 0, | |
| /* oat_data_begin= */ 0, | |
| /* oat_data_end= */ 0, | |
| /* oat_file_end= */ 0, | |
| heap->GetBootImagesStartAddress(), | |
| heap->GetBootImagesSize(), | |
| boot_image_components, | |
| boot_image_checksums, | |
| kRuntimePointerSize); | |
| // Data size includes everything except the bitmap and the header. | |
| header_.data_size_ = sections_end - sizeof(ImageHeader); | |
| // Write image methods - needs to happen after creation of the header. | |
| WriteImageMethods(); | |
| return true; | |
| } | |
| void FillData(std::vector<uint8_t>& data) { | |
| // Note we don't put the header, we only have it reserved in `data` as | |
| // Image::WriteData expects the object section to contain the image header. | |
| auto compute_dest = [&](const ImageSection& section) { | |
| return data.data() + section.Offset(); | |
| }; | |
| auto objects_section = header_.GetImageSection(ImageHeader::kSectionObjects); | |
| memcpy(compute_dest(objects_section) + sizeof(ImageHeader), objects_.data(), objects_.size()); | |
| auto fields_section = header_.GetImageSection(ImageHeader::kSectionArtFields); | |
| memcpy(compute_dest(fields_section), art_fields_.data(), fields_section.Size()); | |
| auto methods_section = header_.GetImageSection(ImageHeader::kSectionArtMethods); | |
| memcpy(compute_dest(methods_section), art_methods_.data(), methods_section.Size()); | |
| auto im_tables_section = header_.GetImageSection(ImageHeader::kSectionImTables); | |
| memcpy(compute_dest(im_tables_section), im_tables_.data(), im_tables_section.Size()); | |
| auto intern_section = header_.GetImageSection(ImageHeader::kSectionInternedStrings); | |
| intern_table_.WriteToMemory(compute_dest(intern_section)); | |
| auto class_table_section = header_.GetImageSection(ImageHeader::kSectionClassTable); | |
| class_table_.WriteToMemory(compute_dest(class_table_section)); | |
| auto string_offsets_section = | |
| header_.GetImageSection(ImageHeader::kSectionStringReferenceOffsets); | |
| memcpy(compute_dest(string_offsets_section), | |
| string_reference_offsets_.data(), | |
| string_offsets_section.Size()); | |
| auto dex_cache_section = header_.GetImageSection(ImageHeader::kSectionDexCacheArrays); | |
| memcpy(compute_dest(dex_cache_section), dex_cache_arrays_.data(), dex_cache_section.Size()); | |
| auto metadata_section = header_.GetImageSection(ImageHeader::kSectionMetadata); | |
| memcpy(compute_dest(metadata_section), metadata_.data(), metadata_section.Size()); | |
| DCHECK_EQ(metadata_section.Offset() + metadata_section.Size(), data.size()); | |
| } | |
| ImageHeader* GetHeader() { | |
| return &header_; | |
| } | |
| const gc::accounting::ContinuousSpaceBitmap& GetImageBitmap() const { | |
| return image_bitmap_; | |
| } | |
| const std::string& GetDexLocation() const { | |
| return dex_location_; | |
| } | |
| private: | |
| bool IsInBootImage(const void* obj) const { | |
| return reinterpret_cast<uintptr_t>(obj) - boot_image_begin_ < boot_image_size_; | |
| } | |
| // Returns the image contents for `cls`. If `cls` is in the boot image, the | |
| // method just returns it. | |
| mirror::Class* GetClassContent(ObjPtr<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) { | |
| if (cls == nullptr || IsInBootImage(cls.Ptr())) { | |
| return cls.Ptr(); | |
| } | |
| const dex::ClassDef* class_def = cls->GetClassDef(); | |
| DCHECK(class_def != nullptr) << cls->PrettyClass(); | |
| auto it = classes_.find(class_def); | |
| DCHECK(it != classes_.end()) << cls->PrettyClass(); | |
| mirror::Class* result = reinterpret_cast<mirror::Class*>(objects_.data() + it->second); | |
| DCHECK(result->GetClass()->IsClass()); | |
| return result; | |
| } | |
| // Returns a pointer that can be stored in `objects_`: | |
| // - The pointer itself for boot image objects, | |
| // - The offset in the image for all other objects. | |
| template <typename T> T* GetOrComputeImageAddress(ObjPtr<T> object) | |
| REQUIRES_SHARED(Locks::mutator_lock_) { | |
| if (object == nullptr || IsInBootImage(object.Ptr())) { | |
| DCHECK(object == nullptr || Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(object)); | |
| return object.Ptr(); | |
| } | |
| if (object->IsClassLoader()) { | |
| // DexCache and Class point to class loaders. For runtime-generated app | |
| // images, we don't encode the class loader. It will be set when the | |
| // runtime is loading the image. | |
| return nullptr; | |
| } | |
| if (object->GetClass() == GetClassRoot<mirror::ClassExt>()) { | |
| // No need to encode `ClassExt`. If needed, it will be reconstructed at | |
| // runtime. | |
| return nullptr; | |
| } | |
| uint32_t offset = 0u; | |
| if (object->IsClass()) { | |
| offset = CopyClass(object->AsClass()); | |
| } else if (object->IsDexCache()) { | |
| offset = CopyDexCache(object->AsDexCache()); | |
| } else { | |
| offset = CopyObject(object); | |
| } | |
| return reinterpret_cast<T*>(image_begin_ + sizeof(ImageHeader) + offset); | |
| } | |
| void CreateImageSections() { | |
| sections_[ImageHeader::kSectionObjects] = ImageSection(0u, object_section_size_); | |
| sections_[ImageHeader::kSectionArtFields] = | |
| ImageSection(sections_[ImageHeader::kSectionObjects].End(), art_fields_.size()); | |
| // Round up to the alignment for ArtMethod. | |
| static_assert(IsAligned<sizeof(void*)>(ArtMethod::Size(kRuntimePointerSize))); | |
| size_t cur_pos = RoundUp(sections_[ImageHeader::kSectionArtFields].End(), sizeof(void*)); | |
| sections_[ImageHeader::kSectionArtMethods] = ImageSection(cur_pos, art_methods_.size()); | |
| // Round up to the alignment for ImTables. | |
| cur_pos = RoundUp(sections_[ImageHeader::kSectionArtMethods].End(), sizeof(void*)); | |
| sections_[ImageHeader::kSectionImTables] = ImageSection(cur_pos, im_tables_.size()); | |
| // Round up to the alignment for conflict tables. | |
| cur_pos = RoundUp(sections_[ImageHeader::kSectionImTables].End(), sizeof(void*)); | |
| sections_[ImageHeader::kSectionIMTConflictTables] = ImageSection(cur_pos, 0u); | |
| sections_[ImageHeader::kSectionRuntimeMethods] = | |
| ImageSection(sections_[ImageHeader::kSectionIMTConflictTables].End(), 0u); | |
| // Round up to the alignment the string table expects. See HashSet::WriteToMemory. | |
| cur_pos = RoundUp(sections_[ImageHeader::kSectionRuntimeMethods].End(), sizeof(uint64_t)); | |
| size_t intern_table_bytes = intern_table_.WriteToMemory(nullptr); | |
| sections_[ImageHeader::kSectionInternedStrings] = ImageSection(cur_pos, intern_table_bytes); | |
| // Obtain the new position and round it up to the appropriate alignment. | |
| cur_pos = RoundUp(sections_[ImageHeader::kSectionInternedStrings].End(), sizeof(uint64_t)); | |
| size_t class_table_bytes = class_table_.WriteToMemory(nullptr); | |
| sections_[ImageHeader::kSectionClassTable] = ImageSection(cur_pos, class_table_bytes); | |
| // Round up to the alignment of the offsets we are going to store. | |
| cur_pos = RoundUp(sections_[ImageHeader::kSectionClassTable].End(), sizeof(uint32_t)); | |
| sections_[ImageHeader::kSectionStringReferenceOffsets] = ImageSection( | |
| cur_pos, string_reference_offsets_.size() * sizeof(string_reference_offsets_[0])); | |
| // Round up to the alignment dex caches arrays expects. | |
| cur_pos = | |
| RoundUp(sections_[ImageHeader::kSectionStringReferenceOffsets].End(), sizeof(void*)); | |
| sections_[ImageHeader::kSectionDexCacheArrays] = | |
| ImageSection(cur_pos, dex_cache_arrays_.size()); | |
| // Round up to the alignment expected for the metadata, which holds dex | |
| // cache arrays. | |
| cur_pos = RoundUp(sections_[ImageHeader::kSectionDexCacheArrays].End(), sizeof(void*)); | |
| sections_[ImageHeader::kSectionMetadata] = ImageSection(cur_pos, metadata_.size()); | |
| } | |
| // Returns the copied mirror Object if in the image, or the object directly if | |
| // in the boot image. For the copy, this is really its content, it should not | |
| // be returned as an `ObjPtr` (as it's not a GC object), nor stored anywhere. | |
| template<typename T> T* FromImageOffsetToRuntimeContent(uint32_t offset) { | |
| if (offset == 0u || IsInBootImage(reinterpret_cast<const void*>(offset))) { | |
| return reinterpret_cast<T*>(offset); | |
| } | |
| uint32_t vector_data_offset = FromImageOffsetToVectorOffset(offset); | |
| return reinterpret_cast<T*>(objects_.data() + vector_data_offset); | |
| } | |
| uint32_t FromImageOffsetToVectorOffset(uint32_t offset) const { | |
| DCHECK(!IsInBootImage(reinterpret_cast<const void*>(offset))); | |
| return offset - sizeof(ImageHeader) - image_begin_; | |
| } | |
| class InternStringHash { | |
| public: | |
| explicit InternStringHash(RuntimeImageHelper* helper) : helper_(helper) {} | |
| // NO_THREAD_SAFETY_ANALYSIS as these helpers get passed to `HashSet`. | |
| size_t operator()(mirror::String* str) const NO_THREAD_SAFETY_ANALYSIS { | |
| int32_t hash = str->GetStoredHashCode(); | |
| DCHECK_EQ(hash, str->ComputeHashCode()); | |
| // An additional cast to prevent undesired sign extension. | |
| return static_cast<uint32_t>(hash); | |
| } | |
| size_t operator()(uint32_t entry) const NO_THREAD_SAFETY_ANALYSIS { | |
| return (*this)(helper_->FromImageOffsetToRuntimeContent<mirror::String>(entry)); | |
| } | |
| private: | |
| RuntimeImageHelper* helper_; | |
| }; | |
| class InternStringEquals { | |
| public: | |
| explicit InternStringEquals(RuntimeImageHelper* helper) : helper_(helper) {} | |
| // NO_THREAD_SAFETY_ANALYSIS as these helpers get passed to `HashSet`. | |
| bool operator()(uint32_t entry, mirror::String* other) const NO_THREAD_SAFETY_ANALYSIS { | |
| if (kIsDebugBuild) { | |
| Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); | |
| } | |
| return other->Equals(helper_->FromImageOffsetToRuntimeContent<mirror::String>(entry)); | |
| } | |
| bool operator()(uint32_t entry, uint32_t other) const NO_THREAD_SAFETY_ANALYSIS { | |
| return (*this)(entry, helper_->FromImageOffsetToRuntimeContent<mirror::String>(other)); | |
| } | |
| private: | |
| RuntimeImageHelper* helper_; | |
| }; | |
| using InternTableSet = | |
| HashSet<uint32_t, DefaultEmptyFn<uint32_t>, InternStringHash, InternStringEquals>; | |
| class ClassDescriptorHash { | |
| public: | |
| explicit ClassDescriptorHash(RuntimeImageHelper* helper) : helper_(helper) {} | |
| uint32_t operator()(const ClassTable::TableSlot& slot) const NO_THREAD_SAFETY_ANALYSIS { | |
| uint32_t ptr = slot.NonHashData(); | |
| if (helper_->IsInBootImage(reinterpret_cast32<const void*>(ptr))) { | |
| return reinterpret_cast32<mirror::Class*>(ptr)->DescriptorHash(); | |
| } | |
| return helper_->class_hashes_.Get(helper_->FromImageOffsetToVectorOffset(ptr)); | |
| } | |
| private: | |
| RuntimeImageHelper* helper_; | |
| }; | |
| class ClassDescriptorEquals { | |
| public: | |
| ClassDescriptorEquals() {} | |
| bool operator()(const ClassTable::TableSlot& a, const ClassTable::TableSlot& b) | |
| const NO_THREAD_SAFETY_ANALYSIS { | |
| // No need to fetch the descriptor: we know the classes we are inserting | |
| // in the ClassTable are unique. | |
| return a.Data() == b.Data(); | |
| } | |
| }; | |
| using ClassTableSet = HashSet<ClassTable::TableSlot, | |
| ClassTable::TableSlotEmptyFn, | |
| ClassDescriptorHash, | |
| ClassDescriptorEquals>; | |
| // Helper class to collect classes that we will generate in the image. | |
| class ClassTableVisitor { | |
| public: | |
| ClassTableVisitor(Handle<mirror::ClassLoader> loader, VariableSizedHandleScope& handles) | |
| : loader_(loader), handles_(handles) {} | |
| bool operator()(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) { | |
| // Record app classes and boot classpath classes: app classes will be | |
| // generated in the image and put in the class table, boot classpath | |
| // classes will be put in the class table. | |
| ObjPtr<mirror::ClassLoader> class_loader = klass->GetClassLoader(); | |
| if (klass->IsResolved() && (class_loader == loader_.Get() || class_loader == nullptr)) { | |
| handles_.NewHandle(klass); | |
| } | |
| return true; | |
| } | |
| private: | |
| Handle<mirror::ClassLoader> loader_; | |
| VariableSizedHandleScope& handles_; | |
| }; | |
| // Helper class visitor to filter out classes we cannot emit. | |
| class PruneVisitor { | |
| public: | |
| PruneVisitor(Thread* self, | |
| RuntimeImageHelper* helper, | |
| const ArenaSet<const DexFile*>& dex_files, | |
| ArenaVector<Handle<mirror::Class>>& classes, | |
| ArenaAllocator& allocator) | |
| : self_(self), | |
| helper_(helper), | |
| dex_files_(dex_files), | |
| visited_(allocator.Adapter()), | |
| classes_to_write_(classes) {} | |
| bool CanEmitHelper(Handle<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) { | |
| // If the class comes from a dex file which is not part of the primary | |
| // APK, don't encode it. | |
| if (!ContainsElement(dex_files_, &cls->GetDexFile())) { | |
| return false; | |
| } | |
| // Ensure pointers to classes in `cls` can also be emitted. | |
| StackHandleScope<1> hs(self_); | |
| MutableHandle<mirror::Class> other_class = hs.NewHandle(cls->GetSuperClass()); | |
| if (!CanEmit(other_class)) { | |
| return false; | |
| } | |
| other_class.Assign(cls->GetComponentType()); | |
| if (!CanEmit(other_class)) { | |
| return false; | |
| } | |
| for (size_t i = 0, num_interfaces = cls->NumDirectInterfaces(); i < num_interfaces; ++i) { | |
| other_class.Assign(cls->GetDirectInterface(i)); | |
| DCHECK(other_class != nullptr); | |
| if (!CanEmit(other_class)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool CanEmit(Handle<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) { | |
| if (cls == nullptr) { | |
| return true; | |
| } | |
| DCHECK(cls->IsResolved()); | |
| // Only emit classes that are resolved and not erroneous. | |
| if (cls->IsErroneous()) { | |
| return false; | |
| } | |
| // Proxy classes are generated at runtime, so don't emit them. | |
| if (cls->IsProxyClass()) { | |
| return false; | |
| } | |
| // Classes in the boot image can be trivially encoded directly. | |
| if (helper_->IsInBootImage(cls.Get())) { | |
| return true; | |
| } | |
| if (cls->IsBootStrapClassLoaded()) { | |
| // We cannot encode classes that are part of the boot classpath. | |
| return false; | |
| } | |
| DCHECK(!cls->IsPrimitive()); | |
| if (cls->IsArrayClass()) { | |
| if (cls->IsBootStrapClassLoaded()) { | |
| // For boot classpath arrays, we can only emit them if they are | |
| // in the boot image already. | |
| return helper_->IsInBootImage(cls.Get()); | |
| } | |
| ObjPtr<mirror::Class> temp = cls.Get(); | |
| while ((temp = temp->GetComponentType())->IsArrayClass()) {} | |
| StackHandleScope<1> hs(self_); | |
| Handle<mirror::Class> other_class = hs.NewHandle(temp); | |
| return CanEmit(other_class); | |
| } | |
| const dex::ClassDef* class_def = cls->GetClassDef(); | |
| DCHECK_NE(class_def, nullptr); | |
| auto existing = visited_.find(class_def); | |
| if (existing != visited_.end()) { | |
| // Already processed; | |
| return existing->second == VisitState::kCanEmit; | |
| } | |
| visited_.Put(class_def, VisitState::kVisiting); | |
| if (CanEmitHelper(cls)) { | |
| visited_.Overwrite(class_def, VisitState::kCanEmit); | |
| return true; | |
| } else { | |
| visited_.Overwrite(class_def, VisitState::kCannotEmit); | |
| return false; | |
| } | |
| } | |
| void Visit(Handle<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_) { | |
| MutableHandle<mirror::Class> cls(obj.GetReference()); | |
| if (CanEmit(cls)) { | |
| if (cls->IsBootStrapClassLoaded()) { | |
| DCHECK(helper_->IsInBootImage(cls.Get())); | |
| // Insert the bootclasspath class in the class table. | |
| uint32_t hash = cls->DescriptorHash(); | |
| helper_->class_table_.InsertWithHash(ClassTable::TableSlot(cls.Get(), hash), hash); | |
| } else { | |
| classes_to_write_.push_back(cls); | |
| } | |
| } | |
| } | |
| private: | |
| enum class VisitState { | |
| kVisiting, | |
| kCanEmit, | |
| kCannotEmit, | |
| }; | |
| Thread* const self_; | |
| RuntimeImageHelper* const helper_; | |
| const ArenaSet<const DexFile*>& dex_files_; | |
| ArenaSafeMap<const dex::ClassDef*, VisitState> visited_; | |
| ArenaVector<Handle<mirror::Class>>& classes_to_write_; | |
| }; | |
| void EmitClasses(Thread* self, Handle<mirror::ObjectArray<mirror::Object>> dex_cache_array) | |
| REQUIRES_SHARED(Locks::mutator_lock_) { | |
| ScopedTrace trace("Emit strings and classes"); | |
| ArenaSet<const DexFile*> dex_files(allocator_.Adapter()); | |
| for (int32_t i = 0; i < dex_cache_array->GetLength(); ++i) { | |
| dex_files.insert(dex_cache_array->Get(i)->AsDexCache()->GetDexFile()); | |
| } | |
| StackHandleScope<1> hs(self); | |
| Handle<mirror::ClassLoader> loader = hs.NewHandle( | |
| dex_cache_array->Get(0)->AsDexCache()->GetClassLoader()); | |
| ClassTable* const class_table = loader->GetClassTable(); | |
| if (class_table == nullptr) { | |
| return; | |
| } | |
| VariableSizedHandleScope handles(self); | |
| { | |
| ClassTableVisitor class_table_visitor(loader, handles); | |
| class_table->Visit(class_table_visitor); | |
| } | |
| ArenaVector<Handle<mirror::Class>> classes_to_write(allocator_.Adapter()); | |
| classes_to_write.reserve(class_table->Size()); | |
| { | |
| PruneVisitor prune_visitor(self, this, dex_files, classes_to_write, allocator_); | |
| handles.VisitHandles(prune_visitor); | |
| } | |
| for (Handle<mirror::Class> cls : classes_to_write) { | |
| { | |
| ScopedAssertNoThreadSuspension sants("Writing class"); | |
| CopyClass(cls.Get()); | |
| } | |
| self->AllowThreadSuspension(); | |
| } | |
| // Relocate the type array entries. We do this now before creating image | |
| // sections because we may add new boot image classes into our | |
| // `class_table`_. | |
| for (auto entry : dex_caches_) { | |
| const DexFile& dex_file = *entry.first; | |
| mirror::DexCache* cache = reinterpret_cast<mirror::DexCache*>(&objects_[entry.second]); | |
| mirror::GcRootArray<mirror::Class>* old_types_array = cache->GetResolvedTypesArray(); | |
| if (HasNativeRelocation(old_types_array)) { | |
| auto reloc_it = native_relocations_.find(old_types_array); | |
| DCHECK(reloc_it != native_relocations_.end()); | |
| ArenaVector<uint8_t>& data = | |
| (reloc_it->second.first == NativeRelocationKind::kFullNativeDexCacheArray) | |
| ? dex_cache_arrays_ : metadata_; | |
| mirror::GcRootArray<mirror::Class>* content_array = | |
| reinterpret_cast<mirror::GcRootArray<mirror::Class>*>( | |
| data.data() + reloc_it->second.second); | |
| for (uint32_t i = 0; i < dex_file.NumTypeIds(); ++i) { | |
| ObjPtr<mirror::Class> cls = old_types_array->Get(i); | |
| if (cls == nullptr) { | |
| content_array->Set(i, nullptr); | |
| } else if (IsInBootImage(cls.Ptr())) { | |
| if (!cls->IsPrimitive()) { | |
| // The dex cache is concurrently updated by the app. If the class | |
| // collection logic in `PruneVisitor` did not see this class, insert it now. | |
| // Note that application class tables do not contain primitive | |
| // classes. | |
| uint32_t hash = cls->DescriptorHash(); | |
| class_table_.InsertWithHash(ClassTable::TableSlot(cls.Ptr(), hash), hash); | |
| } | |
| content_array->Set(i, cls.Ptr()); | |
| } else if (cls->IsArrayClass()) { | |
| std::string class_name; | |
| cls->GetDescriptor(&class_name); | |
| auto class_it = array_classes_.find(class_name); | |
| if (class_it == array_classes_.end()) { | |
| content_array->Set(i, nullptr); | |
| } else { | |
| mirror::Class* ptr = reinterpret_cast<mirror::Class*>( | |
| image_begin_ + sizeof(ImageHeader) + class_it->second); | |
| content_array->Set(i, ptr); | |
| } | |
| } else { | |
| DCHECK(!cls->IsPrimitive()); | |
| DCHECK(!cls->IsProxyClass()); | |
| const dex::ClassDef* class_def = cls->GetClassDef(); | |
| DCHECK_NE(class_def, nullptr); | |
| auto class_it = classes_.find(class_def); | |
| if (class_it == classes_.end()) { | |
| content_array->Set(i, nullptr); | |
| } else { | |
| mirror::Class* ptr = reinterpret_cast<mirror::Class*>( | |
| image_begin_ + sizeof(ImageHeader) + class_it->second); | |
| content_array->Set(i, ptr); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Helper visitor returning the location of a native pointer in the image. | |
| class NativePointerVisitor { | |
| public: | |
| explicit NativePointerVisitor(RuntimeImageHelper* helper) : helper_(helper) {} | |
| template <typename T> | |
| T* operator()(T* ptr, [[maybe_unused]] void** dest_addr) const { | |
| return helper_->NativeLocationInImage(ptr, /* must_have_relocation= */ true); | |
| } | |
| template <typename T> T* operator()(T* ptr, bool must_have_relocation = true) const { | |
| return helper_->NativeLocationInImage(ptr, must_have_relocation); | |
| } | |
| private: | |
| RuntimeImageHelper* helper_; | |
| }; | |
| template <typename T> T* NativeLocationInImage(T* ptr, bool must_have_relocation) const { | |
| if (ptr == nullptr || IsInBootImage(ptr)) { | |
| return ptr; | |
| } | |
| auto it = native_relocations_.find(ptr); | |
| if (it == native_relocations_.end()) { | |
| DCHECK(!must_have_relocation); | |
| return nullptr; | |
| } | |
| switch (it->second.first) { | |
| case NativeRelocationKind::kArtMethod: | |
| case NativeRelocationKind::kArtMethodArray: { | |
| uint32_t offset = sections_[ImageHeader::kSectionArtMethods].Offset(); | |
| return reinterpret_cast<T*>(image_begin_ + offset + it->second.second); | |
| } | |
| case NativeRelocationKind::kArtFieldArray: { | |
| uint32_t offset = sections_[ImageHeader::kSectionArtFields].Offset(); | |
| return reinterpret_cast<T*>(image_begin_ + offset + it->second.second); | |
| } | |
| case NativeRelocationKind::kImTable: { | |
| uint32_t offset = sections_[ImageHeader::kSectionImTables].Offset(); | |
| return reinterpret_cast<T*>(image_begin_ + offset + it->second.second); | |
| } | |
| case NativeRelocationKind::kStartupNativeDexCacheArray: { | |
| uint32_t offset = sections_[ImageHeader::kSectionMetadata].Offset(); | |
| return reinterpret_cast<T*>(image_begin_ + offset + it->second.second); | |
| } | |
| case NativeRelocationKind::kFullNativeDexCacheArray: { | |
| uint32_t offset = sections_[ImageHeader::kSectionDexCacheArrays].Offset(); | |
| return reinterpret_cast<T*>(image_begin_ + offset + it->second.second); | |
| } | |
| } | |
| } | |
| template <typename Visitor> | |
| void RelocateMethodPointerArrays(mirror::Class* klass, const Visitor& visitor) | |
| REQUIRES_SHARED(Locks::mutator_lock_) { | |
| // A bit of magic here: we cast contents from our buffer to mirror::Class, | |
| // and do pointer comparison between 1) these classes, and 2) boot image objects. | |
| // Both kinds do not move. | |
| // See if we need to fixup the vtable field. | |
| mirror::Class* super = FromImageOffsetToRuntimeContent<mirror::Class>( | |
| reinterpret_cast32<uint32_t>( | |
| klass->GetSuperClass<kVerifyNone, kWithoutReadBarrier>().Ptr())); | |
| DCHECK(super != nullptr) << "j.l.Object should never be in an app runtime image"; | |
| mirror::PointerArray* vtable = FromImageOffsetToRuntimeContent<mirror::PointerArray>( | |
| reinterpret_cast32<uint32_t>(klass->GetVTable<kVerifyNone, kWithoutReadBarrier>().Ptr())); | |
| mirror::PointerArray* super_vtable = FromImageOffsetToRuntimeContent<mirror::PointerArray>( | |
| reinterpret_cast32<uint32_t>(super->GetVTable<kVerifyNone, kWithoutReadBarrier>().Ptr())); | |
| if (vtable != nullptr && vtable != super_vtable) { | |
| DCHECK(!IsInBootImage(vtable)); | |
| vtable->Fixup(vtable, kRuntimePointerSize, visitor); | |
| } | |
| // See if we need to fixup entries in the IfTable. | |
| mirror::IfTable* iftable = FromImageOffsetToRuntimeContent<mirror::IfTable>( | |
| reinterpret_cast32<uint32_t>( | |
| klass->GetIfTable<kVerifyNone, kWithoutReadBarrier>().Ptr())); | |
| mirror::IfTable* super_iftable = FromImageOffsetToRuntimeContent<mirror::IfTable>( | |
| reinterpret_cast32<uint32_t>( | |
| super->GetIfTable<kVerifyNone, kWithoutReadBarrier>().Ptr())); | |
| int32_t iftable_count = iftable->Count(); | |
| int32_t super_iftable_count = super_iftable->Count(); | |
| for (int32_t i = 0; i < iftable_count; ++i) { | |
| mirror::PointerArray* methods = FromImageOffsetToRuntimeContent<mirror::PointerArray>( | |
| reinterpret_cast32<uint32_t>( | |
| iftable->GetMethodArrayOrNull<kVerifyNone, kWithoutReadBarrier>(i).Ptr())); | |
| mirror::PointerArray* super_methods = (i < super_iftable_count) | |
| ? FromImageOffsetToRuntimeContent<mirror::PointerArray>( | |
| reinterpret_cast32<uint32_t>( | |
| super_iftable->GetMethodArrayOrNull<kVerifyNone, kWithoutReadBarrier>(i).Ptr())) | |
| : nullptr; | |
| if (methods != super_methods) { | |
| DCHECK(!IsInBootImage(methods)); | |
| methods->Fixup(methods, kRuntimePointerSize, visitor); | |
| } | |
| } | |
| } | |
| template <typename Visitor, typename T> | |
| void RelocateNativeDexCacheArray(mirror::NativeArray<T>* old_method_array, | |
| uint32_t num_ids, | |
| const Visitor& visitor) | |
| REQUIRES_SHARED(Locks::mutator_lock_) { | |
| if (old_method_array == nullptr) { | |
| return; | |
| } | |
| auto it = native_relocations_.find(old_method_array); | |
| DCHECK(it != native_relocations_.end()); | |
| ArenaVector<uint8_t>& data = | |
| (it->second.first == NativeRelocationKind::kFullNativeDexCacheArray) | |
| ? dex_cache_arrays_ : metadata_; | |
| mirror::NativeArray<T>* content_array = | |
| reinterpret_cast<mirror::NativeArray<T>*>(data.data() + it->second.second); | |
| for (uint32_t i = 0; i < num_ids; ++i) { | |
| // We may not have relocations for some entries, in which case we'll | |
| // just store null. | |
| content_array->Set(i, visitor(content_array->Get(i), /* must_have_relocation= */ false)); | |
| } | |
| } | |
| template <typename Visitor> | |
| void RelocateDexCacheArrays(mirror::DexCache* cache, | |
| const DexFile& dex_file, | |
| const Visitor& visitor) | |
| REQUIRES_SHARED(Locks::mutator_lock_) { | |
| mirror::NativeArray<ArtMethod>* old_method_array = cache->GetResolvedMethodsArray(); | |
| cache->SetResolvedMethodsArray(visitor(old_method_array)); | |
| RelocateNativeDexCacheArray(old_method_array, dex_file.NumMethodIds(), visitor); | |
| mirror::NativeArray<ArtField>* old_field_array = cache->GetResolvedFieldsArray(); | |
| cache->SetResolvedFieldsArray(visitor(old_field_array)); | |
| RelocateNativeDexCacheArray(old_field_array, dex_file.NumFieldIds(), visitor); | |
| mirror::GcRootArray<mirror::String>* old_strings_array = cache->GetStringsArray(); | |
| cache->SetStringsArray(visitor(old_strings_array)); | |
| mirror::GcRootArray<mirror::Class>* old_types_array = cache->GetResolvedTypesArray(); | |
| cache->SetResolvedTypesArray(visitor(old_types_array)); | |
| } | |
| void RelocateNativePointers() { | |
| ScopedTrace relocate_native_pointers("Relocate native pointers"); | |
| ScopedObjectAccess soa(Thread::Current()); | |
| NativePointerVisitor visitor(this); | |
| for (auto&& entry : classes_) { | |
| mirror::Class* cls = reinterpret_cast<mirror::Class*>(&objects_[entry.second]); | |
| cls->FixupNativePointers(cls, kRuntimePointerSize, visitor); | |
| RelocateMethodPointerArrays(cls, visitor); | |
| } | |
| for (auto&& entry : array_classes_) { | |
| mirror::Class* cls = reinterpret_cast<mirror::Class*>(&objects_[entry.second]); | |
| cls->FixupNativePointers(cls, kRuntimePointerSize, visitor); | |
| RelocateMethodPointerArrays(cls, visitor); | |
| } | |
| for (auto&& entry : native_relocations_) { | |
| if (entry.second.first == NativeRelocationKind::kImTable) { | |
| ImTable* im_table = reinterpret_cast<ImTable*>(im_tables_.data() + entry.second.second); | |
| RelocateImTable(im_table, visitor); | |
| } | |
| } | |
| for (auto&& entry : dex_caches_) { | |
| mirror::DexCache* cache = reinterpret_cast<mirror::DexCache*>(&objects_[entry.second]); | |
| RelocateDexCacheArrays(cache, *entry.first, visitor); | |
| } | |
| } | |
| void RelocateImTable(ImTable* im_table, const NativePointerVisitor& visitor) { | |
| for (size_t i = 0; i < ImTable::kSize; ++i) { | |
| ArtMethod* method = im_table->Get(i, kRuntimePointerSize); | |
| ArtMethod* new_method = nullptr; | |
| if (method->IsRuntimeMethod() && !IsInBootImage(method)) { | |
| // New IMT conflict method: just use the boot image version. | |
| // TODO: Consider copying the new IMT conflict method. | |
| new_method = Runtime::Current()->GetImtConflictMethod(); | |
| DCHECK(IsInBootImage(new_method)); | |
| } else { | |
| new_method = visitor(method); | |
| } | |
| if (method != new_method) { | |
| im_table->Set(i, new_method, kRuntimePointerSize); | |
| } | |
| } | |
| } | |
| void CopyFieldArrays(ObjPtr<mirror::Class> cls, uint32_t class_image_address) | |
| REQUIRES_SHARED(Locks::mutator_lock_) { | |
| LengthPrefixedArray<ArtField>* cur_fields = cls->GetFieldsPtr(); | |
| if (cur_fields != nullptr) { | |
| // Copy the array. | |
| size_t number_of_fields = cur_fields->size(); | |
| size_t size = LengthPrefixedArray<ArtField>::ComputeSize(number_of_fields); | |
| size_t offset = art_fields_.size(); | |
| art_fields_.resize(offset + size); | |
| auto* dest_array = | |
| reinterpret_cast<LengthPrefixedArray<ArtField>*>(art_fields_.data() + offset); | |
| memcpy(dest_array, cur_fields, size); | |
| native_relocations_.Put(cur_fields, | |
| std::make_pair(NativeRelocationKind::kArtFieldArray, offset)); | |
| // Update the class pointer of individual fields. | |
| for (size_t i = 0; i != number_of_fields; ++i) { | |
| dest_array->At(i).GetDeclaringClassAddressWithoutBarrier()->Assign( | |
| reinterpret_cast<mirror::Class*>(class_image_address)); | |
| } | |
| } | |
| } | |
| void CopyMethodArrays(ObjPtr<mirror::Class> cls, | |
| uint32_t class_image_address, | |
| bool is_class_initialized) | |
| REQUIRES_SHARED(Locks::mutator_lock_) { | |
| size_t number_of_methods = cls->NumMethods(); | |
| if (number_of_methods == 0) { | |
| return; | |
| } | |
| size_t size = LengthPrefixedArray<ArtMethod>::ComputeSize(number_of_methods); | |
| size_t offset = art_methods_.size(); | |
| art_methods_.resize(offset + size); | |
| auto* dest_array = | |
| reinterpret_cast<LengthPrefixedArray<ArtMethod>*>(art_methods_.data() + offset); | |
| memcpy(dest_array, cls->GetMethodsPtr(), size); | |
| native_relocations_.Put(cls->GetMethodsPtr(), | |
| std::make_pair(NativeRelocationKind::kArtMethodArray, offset)); | |
| for (size_t i = 0; i != number_of_methods; ++i) { | |
| ArtMethod* method = &cls->GetMethodsPtr()->At(i); | |
| ArtMethod* copy = &dest_array->At(i); | |
| // Update the class pointer. | |
| ObjPtr<mirror::Class> declaring_class = method->GetDeclaringClass(); | |
| if (declaring_class == cls) { | |
| copy->GetDeclaringClassAddressWithoutBarrier()->Assign( | |
| reinterpret_cast<mirror::Class*>(class_image_address)); | |
| } else { | |
| DCHECK(method->IsCopied()); | |
| if (!IsInBootImage(declaring_class.Ptr())) { | |
| DCHECK(classes_.find(declaring_class->GetClassDef()) != classes_.end()); | |
| copy->GetDeclaringClassAddressWithoutBarrier()->Assign( | |
| reinterpret_cast<mirror::Class*>( | |
| image_begin_ + | |
| sizeof(ImageHeader) + | |
| classes_.Get(declaring_class->GetClassDef()))); | |
| } | |
| } | |
| // Record the native relocation of the method. | |
| uintptr_t copy_offset = | |
| reinterpret_cast<uintptr_t>(copy) - reinterpret_cast<uintptr_t>(art_methods_.data()); | |
| native_relocations_.Put(method, | |
| std::make_pair(NativeRelocationKind::kArtMethod, copy_offset)); | |
| // Ignore the single-implementation info for abstract method. | |
| if (method->IsAbstract()) { | |
| copy->SetHasSingleImplementation(false); | |
| copy->SetSingleImplementation(nullptr, kRuntimePointerSize); | |
| } | |
| // Set the entrypoint and data pointer of the method. | |
| StubType stub; | |
| if (method->IsNative()) { | |
| stub = StubType::kQuickGenericJNITrampoline; | |
| } else if (!cls->IsVerified()) { | |
| stub = StubType::kQuickToInterpreterBridge; | |
| } else if (!is_class_initialized && method->NeedsClinitCheckBeforeCall()) { | |
| stub = StubType::kQuickResolutionTrampoline; | |
| } else if (interpreter::IsNterpSupported() && CanMethodUseNterp(method)) { | |
| stub = StubType::kNterpTrampoline; | |
| } else { | |
| stub = StubType::kQuickToInterpreterBridge; | |
| } | |
| const std::vector<gc::space::ImageSpace*>& image_spaces = | |
| Runtime::Current()->GetHeap()->GetBootImageSpaces(); | |
| DCHECK(!image_spaces.empty()); | |
| const OatFile* oat_file = image_spaces[0]->GetOatFile(); | |
| DCHECK(oat_file != nullptr); | |
| const OatHeader& header = oat_file->GetOatHeader(); | |
| const void* entrypoint = header.GetOatAddress(stub); | |
| if (method->IsNative() && (is_class_initialized || !method->NeedsClinitCheckBeforeCall())) { | |
| // Use boot JNI stub if found. | |
| ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); | |
| const void* boot_jni_stub = class_linker->FindBootJniStub(method); | |
| if (boot_jni_stub != nullptr) { | |
| entrypoint = boot_jni_stub; | |
| } | |
| } | |
| copy->SetEntryPointFromQuickCompiledCode(entrypoint); | |
| if (method->IsNative()) { | |
| StubType stub_type = method->IsCriticalNative() | |
| ? StubType::kJNIDlsymLookupCriticalTrampoline | |
| : StubType::kJNIDlsymLookupTrampoline; | |
| copy->SetEntryPointFromJni(header.GetOatAddress(stub_type)); | |
| } else if (method->HasCodeItem()) { | |
| const uint8_t* code_item = reinterpret_cast<const uint8_t*>(method->GetCodeItem()); | |
| DCHECK_GE(code_item, method->GetDexFile()->DataBegin()); | |
| uint32_t code_item_offset = dchecked_integral_cast<uint32_t>( | |
| code_item - method->GetDexFile()->DataBegin());; | |
| copy->SetDataPtrSize( | |
| reinterpret_cast<const void*>(code_item_offset), kRuntimePointerSize); | |
| } | |
| } | |
| } | |
| void CopyImTable(ObjPtr<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) { | |
| ImTable* table = cls->GetImt(kRuntimePointerSize); | |
| // If the table is null or shared and/or already emitted, we can skip. | |
| if (table == nullptr || IsInBootImage(table) || HasNativeRelocation(table)) { | |
| return; | |
| } | |
| const size_t size = ImTable::SizeInBytes(kRuntimePointerSize); | |
| size_t offset = im_tables_.size(); | |
| im_tables_.resize(offset + size); | |
| uint8_t* dest = im_tables_.data() + offset; | |
| memcpy(dest, table, size); | |
| native_relocations_.Put(table, std::make_pair(NativeRelocationKind::kImTable, offset)); | |
| } | |
| bool HasNativeRelocation(void* ptr) const { | |
| return native_relocations_.find(ptr) != native_relocations_.end(); | |
| } | |
| static void LoadClassesFromReferenceProfile( | |
| Thread* self, | |
| const dchecked_vector<Handle<mirror::DexCache>>& dex_caches) | |
| REQUIRES_SHARED(Locks::mutator_lock_) { | |
| AppInfo* app_info = Runtime::Current()->GetAppInfo(); | |
| std::string profile_file = app_info->GetPrimaryApkReferenceProfile(); | |
| if (profile_file.empty()) { | |
| return; | |
| } | |
| // Lock the file, it could be concurrently updated by the system. Don't block | |
| // as this is app startup sensitive. | |
| std::string error; | |
| ScopedFlock profile = | |
| LockedFile::Open(profile_file.c_str(), O_RDONLY, /*block=*/false, &error); | |
| if (profile == nullptr) { | |
| LOG(DEBUG) << "Couldn't lock the profile file " << profile_file << ": " << error; | |
| return; | |
| } | |
| ProfileCompilationInfo profile_info(/* for_boot_image= */ false); | |
| if (!profile_info.Load(profile->Fd())) { | |
| LOG(DEBUG) << "Could not load profile file"; | |
| return; | |
| } | |
| StackHandleScope<1> hs(self); | |
| Handle<mirror::ClassLoader> class_loader = | |
| hs.NewHandle<mirror::ClassLoader>(dex_caches[0]->GetClassLoader()); | |
| ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); | |
| ScopedTrace loading_classes("Loading classes from profile"); | |
| for (auto dex_cache : dex_caches) { | |
| const DexFile* dex_file = dex_cache->GetDexFile(); | |
| const ArenaSet<dex::TypeIndex>* class_types = profile_info.GetClasses(*dex_file); | |
| if (class_types == nullptr) { | |
| // This means the profile file did not reference the dex file, which is the case | |
| // if there's no classes and methods of that dex file in the profile. | |
| continue; | |
| } | |
| for (dex::TypeIndex idx : *class_types) { | |
| // The index is greater or equal to NumTypeIds if the type is an extra | |
| // descriptor, not referenced by the dex file. | |
| if (idx.index_ < dex_file->NumTypeIds()) { | |
| ObjPtr<mirror::Class> klass = class_linker->ResolveType(idx, dex_cache, class_loader); | |
| if (klass == nullptr) { | |
| self->ClearException(); | |
| LOG(DEBUG) << "Failed to preload " << dex_file->PrettyType(idx); | |
| continue; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| bool WriteObjects(std::string* error_msg) { | |
| ScopedTrace write_objects("Writing objects"); | |
| ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); | |
| ScopedObjectAccess soa(Thread::Current()); | |
| VariableSizedHandleScope handles(soa.Self()); | |
| Handle<mirror::Class> object_array_class = handles.NewHandle( | |
| GetClassRoot<mirror::ObjectArray<mirror::Object>>(class_linker)); | |
| Handle<mirror::ObjectArray<mirror::Object>> image_roots = handles.NewHandle( | |
| mirror::ObjectArray<mirror::Object>::Alloc( | |
| soa.Self(), object_array_class.Get(), ImageHeader::kImageRootsMax)); | |
| if (image_roots == nullptr) { | |
| DCHECK(soa.Self()->IsExceptionPending()); | |
| soa.Self()->ClearException(); | |
| *error_msg = "Out of memory when trying to generate a runtime app image"; | |
| return false; | |
| } | |
| // Find the dex files that will be used for generating the app image. | |
| dchecked_vector<Handle<mirror::DexCache>> dex_caches; | |
| FindDexCaches(soa.Self(), dex_caches, handles); | |
| if (dex_caches.size() == 0) { | |
| *error_msg = "Did not find dex caches to generate an app image"; | |
| return false; | |
| } | |
| const OatDexFile* oat_dex_file = dex_caches[0]->GetDexFile()->GetOatDexFile(); | |
| VdexFile* vdex_file = oat_dex_file->GetOatFile()->GetVdexFile(); | |
| // The first entry in `dex_caches` contains the location of the primary APK. | |
| dex_location_ = oat_dex_file->GetDexFileLocation(); | |
| size_t number_of_dex_files = vdex_file->GetNumberOfDexFiles(); | |
| if (number_of_dex_files != dex_caches.size()) { | |
| // This means some dex files haven't been executed. For simplicity, just | |
| // register them and recollect dex caches. | |
| Handle<mirror::ClassLoader> loader = handles.NewHandle(dex_caches[0]->GetClassLoader()); | |
| VisitClassLoaderDexFiles(soa.Self(), loader, [&](const art::DexFile* dex_file) | |
| REQUIRES_SHARED(Locks::mutator_lock_) { | |
| class_linker->RegisterDexFile(*dex_file, dex_caches[0]->GetClassLoader()); | |
| return true; // Continue with other dex files. | |
| }); | |
| dex_caches.clear(); | |
| FindDexCaches(soa.Self(), dex_caches, handles); | |
| if (number_of_dex_files != dex_caches.size()) { | |
| *error_msg = "Number of dex caches does not match number of dex files in the primary APK"; | |
| return false; | |
| } | |
| } | |
| // If classes referenced in the reference profile are not loaded, preload | |
| // them. This makes sure we generate a good runtime app image, even if this | |
| // current app run did not load all startup classes. | |
| LoadClassesFromReferenceProfile(soa.Self(), dex_caches); | |
| // We store the checksums of the dex files used at runtime. These can be | |
| // different compared to the vdex checksums due to compact dex. | |
| std::vector<uint32_t> checksums(number_of_dex_files); | |
| uint32_t checksum_index = 0; | |
| for (const OatDexFile* current_oat_dex_file : oat_dex_file->GetOatFile()->GetOatDexFiles()) { | |
| const DexFile::Header* header = | |
| reinterpret_cast<const DexFile::Header*>(current_oat_dex_file->GetDexFilePointer()); | |
| checksums[checksum_index++] = header->checksum_; | |
| } | |
| DCHECK_EQ(checksum_index, number_of_dex_files); | |
| // Create the fake OatHeader to store the dependencies of the image. | |
| SafeMap<std::string, std::string> key_value_store; | |
| Runtime* runtime = Runtime::Current(); | |
| key_value_store.Put(OatHeader::kApexVersionsKey, runtime->GetApexVersions()); | |
| key_value_store.Put(OatHeader::kBootClassPathKey, | |
| android::base::Join(runtime->GetBootClassPathLocations(), ':')); | |
| key_value_store.Put(OatHeader::kBootClassPathChecksumsKey, | |
| runtime->GetBootClassPathChecksums()); | |
| key_value_store.Put(OatHeader::kClassPathKey, | |
| oat_dex_file->GetOatFile()->GetClassLoaderContext()); | |
| key_value_store.Put(OatHeader::kConcurrentCopying, | |
| gUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue); | |
| std::unique_ptr<const InstructionSetFeatures> isa_features = | |
| InstructionSetFeatures::FromCppDefines(); | |
| std::unique_ptr<OatHeader, decltype(&OatHeader::Delete)> oat_header( | |
| OatHeader::Create(kRuntimeQuickCodeISA, | |
| isa_features.get(), | |
| number_of_dex_files, | |
| &key_value_store), | |
| &OatHeader::Delete); | |
| // Create the byte array containing the oat header and dex checksums. | |
| uint32_t checksums_size = checksums.size() * sizeof(uint32_t); | |
| Handle<mirror::ByteArray> header_data = handles.NewHandle( | |
| mirror::ByteArray::Alloc(soa.Self(), oat_header->GetHeaderSize() + checksums_size)); | |
| if (header_data == nullptr) { | |
| DCHECK(soa.Self()->IsExceptionPending()); | |
| soa.Self()->ClearException(); | |
| *error_msg = "Out of memory when trying to generate a runtime app image"; | |
| return false; | |
| } | |
| memcpy(header_data->GetData(), oat_header.get(), oat_header->GetHeaderSize()); | |
| memcpy(header_data->GetData() + oat_header->GetHeaderSize(), checksums.data(), checksums_size); | |
| // Create and populate the dex caches aray. | |
| Handle<mirror::ObjectArray<mirror::Object>> dex_cache_array = handles.NewHandle( | |
| mirror::ObjectArray<mirror::Object>::Alloc( | |
| soa.Self(), object_array_class.Get(), dex_caches.size())); | |
| if (dex_cache_array == nullptr) { | |
| DCHECK(soa.Self()->IsExceptionPending()); | |
| soa.Self()->ClearException(); | |
| *error_msg = "Out of memory when trying to generate a runtime app image"; | |
| return false; | |
| } | |
| for (uint32_t i = 0; i < dex_caches.size(); ++i) { | |
| dex_cache_array->Set(i, dex_caches[i].Get()); | |
| } | |
| image_roots->Set(ImageHeader::kDexCaches, dex_cache_array.Get()); | |
| image_roots->Set(ImageHeader::kClassRoots, class_linker->GetClassRoots()); | |
| image_roots->Set(ImageHeader::kAppImageOatHeader, header_data.Get()); | |
| { | |
| // Now that we have created all objects needed for the `image_roots`, copy | |
| // it into the buffer. Note that this will recursively copy all objects | |
| // contained in `image_roots`. That's acceptable as we don't have cycles, | |
| // nor a deep graph. | |
| ScopedAssertNoThreadSuspension sants("Writing runtime app image"); | |
| CopyObject(image_roots.Get()); | |
| } | |
| // Emit classes defined in the app class loader (which will also indirectly | |
| // emit dex caches and their arrays). | |
| EmitClasses(soa.Self(), dex_cache_array); | |
| return true; | |
| } | |
| class FixupVisitor { | |
| public: | |
| FixupVisitor(RuntimeImageHelper* image, size_t copy_offset) | |
| : image_(image), copy_offset_(copy_offset) {} | |
| // We do not visit native roots. These are handled with other logic. | |
| void VisitRootIfNonNull( | |
| [[maybe_unused]] mirror::CompressedReference<mirror::Object>* root) const { | |
| LOG(FATAL) << "UNREACHABLE"; | |
| } | |
| void VisitRoot([[maybe_unused]] mirror::CompressedReference<mirror::Object>* root) const { | |
| LOG(FATAL) << "UNREACHABLE"; | |
| } | |
| void operator()(ObjPtr<mirror::Object> obj, | |
| MemberOffset offset, | |
| bool is_static) const | |
| REQUIRES_SHARED(Locks::mutator_lock_) { | |
| // We don't copy static fields, they are being handled when we try to | |
| // initialize the class. | |
| ObjPtr<mirror::Object> ref = | |
| is_static ? nullptr : obj->GetFieldObject<mirror::Object>(offset); | |
| mirror::Object* address = image_->GetOrComputeImageAddress(ref); | |
| mirror::Object* copy = | |
| reinterpret_cast<mirror::Object*>(image_->objects_.data() + copy_offset_); | |
| copy->GetFieldObjectReferenceAddr<kVerifyNone>(offset)->Assign(address); | |
| } | |
| // java.lang.ref.Reference visitor. | |
| void operator()([[maybe_unused]] ObjPtr<mirror::Class> klass, | |
| ObjPtr<mirror::Reference> ref) const REQUIRES_SHARED(Locks::mutator_lock_) { | |
| operator()(ref, mirror::Reference::ReferentOffset(), /* is_static */ false); | |
| } | |
| private: | |
| RuntimeImageHelper* image_; | |
| size_t copy_offset_; | |
| }; | |
| template <typename T> | |
| void CopyNativeDexCacheArray(uint32_t num_entries, | |
| uint32_t max_entries, | |
| mirror::NativeArray<T>* array) { | |
| if (array == nullptr) { | |
| return; | |
| } | |
| bool only_startup = !mirror::DexCache::ShouldAllocateFullArray(num_entries, max_entries); | |
| ArenaVector<uint8_t>& data = only_startup ? metadata_ : dex_cache_arrays_; | |
| NativeRelocationKind relocation_kind = only_startup | |
| ? NativeRelocationKind::kStartupNativeDexCacheArray | |
| : NativeRelocationKind::kFullNativeDexCacheArray; | |
| size_t size = num_entries * sizeof(void*); | |
| // We need to reserve space to store `num_entries` because ImageSpace doesn't have | |
| // access to the dex files when relocating dex caches. | |
| size_t offset = RoundUp(data.size(), sizeof(void*)) + sizeof(uintptr_t); | |
| data.resize(RoundUp(data.size(), sizeof(void*)) + sizeof(uintptr_t) + size); | |
| reinterpret_cast<uintptr_t*>(data.data() + offset)[-1] = num_entries; | |
| // Copy each entry individually. We cannot use memcpy, as the entries may be | |
| // updated concurrently by other mutator threads. | |
| mirror::NativeArray<T>* copy = reinterpret_cast<mirror::NativeArray<T>*>(data.data() + offset); | |
| for (uint32_t i = 0; i < num_entries; ++i) { | |
| copy->Set(i, array->Get(i)); | |
| } | |
| native_relocations_.Put(array, std::make_pair(relocation_kind, offset)); | |
| } | |
| template <typename T> | |
| mirror::GcRootArray<T>* CreateGcRootDexCacheArray(uint32_t num_entries, | |
| uint32_t max_entries, | |
| mirror::GcRootArray<T>* array) { | |
| if (array == nullptr) { | |
| return nullptr; | |
| } | |
| bool only_startup = !mirror::DexCache::ShouldAllocateFullArray(num_entries, max_entries); | |
| ArenaVector<uint8_t>& data = only_startup ? metadata_ : dex_cache_arrays_; | |
| NativeRelocationKind relocation_kind = only_startup | |
| ? NativeRelocationKind::kStartupNativeDexCacheArray | |
| : NativeRelocationKind::kFullNativeDexCacheArray; | |
| size_t size = num_entries * sizeof(GcRoot<T>); | |
| // We need to reserve space to store `num_entries` because ImageSpace doesn't have | |
| // access to the dex files when relocating dex caches. | |
| static_assert(sizeof(GcRoot<T>) == sizeof(uint32_t)); | |
| size_t offset = data.size() + sizeof(uint32_t); | |
| data.resize(data.size() + sizeof(uint32_t) + size); | |
| reinterpret_cast<uint32_t*>(data.data() + offset)[-1] = num_entries; | |
| native_relocations_.Put(array, std::make_pair(relocation_kind, offset)); | |
| return reinterpret_cast<mirror::GcRootArray<T>*>(data.data() + offset); | |
| } | |
| static bool EmitDexCacheArrays() { | |
| // We need to treat dex cache arrays specially in an image for userfaultfd. | |
| // Disable for now. See b/270936884. | |
| return !gUseUserfaultfd; | |
| } | |
| uint32_t CopyDexCache(ObjPtr<mirror::DexCache> cache) REQUIRES_SHARED(Locks::mutator_lock_) { | |
| auto it = dex_caches_.find(cache->GetDexFile()); | |
| if (it != dex_caches_.end()) { | |
| return it->second; | |
| } | |
| uint32_t offset = CopyObject(cache); | |
| dex_caches_.Put(cache->GetDexFile(), offset); | |
| // For dex caches, clear pointers to data that will be set at runtime. | |
| mirror::Object* copy = reinterpret_cast<mirror::Object*>(objects_.data() + offset); | |
| reinterpret_cast<mirror::DexCache*>(copy)->ResetNativeArrays(); | |
| reinterpret_cast<mirror::DexCache*>(copy)->SetDexFile(nullptr); | |
| if (!EmitDexCacheArrays()) { | |
| return offset; | |
| } | |
| // Copy the ArtMethod array. | |
| mirror::NativeArray<ArtMethod>* resolved_methods = cache->GetResolvedMethodsArray(); | |
| CopyNativeDexCacheArray(cache->GetDexFile()->NumMethodIds(), | |
| mirror::DexCache::kDexCacheMethodCacheSize, | |
| resolved_methods); | |
| // Store the array pointer in the dex cache, which will be relocated at the end. | |
| reinterpret_cast<mirror::DexCache*>(copy)->SetResolvedMethodsArray(resolved_methods); | |
| // Copy the ArtField array. | |
| mirror::NativeArray<ArtField>* resolved_fields = cache->GetResolvedFieldsArray(); | |
| CopyNativeDexCacheArray(cache->GetDexFile()->NumFieldIds(), | |
| mirror::DexCache::kDexCacheFieldCacheSize, | |
| resolved_fields); | |
| // Store the array pointer in the dex cache, which will be relocated at the end. | |
| reinterpret_cast<mirror::DexCache*>(copy)->SetResolvedFieldsArray(resolved_fields); | |
| // Copy the type array. | |
| mirror::GcRootArray<mirror::Class>* resolved_types = cache->GetResolvedTypesArray(); | |
| CreateGcRootDexCacheArray(cache->GetDexFile()->NumTypeIds(), | |
| mirror::DexCache::kDexCacheTypeCacheSize, | |
| resolved_types); | |
| // Store the array pointer in the dex cache, which will be relocated at the end. | |
| reinterpret_cast<mirror::DexCache*>(copy)->SetResolvedTypesArray(resolved_types); | |
| // Copy the string array. | |
| mirror::GcRootArray<mirror::String>* strings = cache->GetStringsArray(); | |
| // Note: `new_strings` points to temporary data, and is only valid here. | |
| mirror::GcRootArray<mirror::String>* new_strings = | |
| CreateGcRootDexCacheArray(cache->GetDexFile()->NumStringIds(), | |
| mirror::DexCache::kDexCacheStringCacheSize, | |
| strings); | |
| // Store the array pointer in the dex cache, which will be relocated at the end. | |
| reinterpret_cast<mirror::DexCache*>(copy)->SetStringsArray(strings); | |
| // The code below copies new objects, so invalidate the address we have for | |
| // `copy`. | |
| copy = nullptr; | |
| if (strings != nullptr) { | |
| for (uint32_t i = 0; i < cache->GetDexFile()->NumStringIds(); ++i) { | |
| ObjPtr<mirror::String> str = strings->Get(i); | |
| if (str == nullptr || IsInBootImage(str.Ptr())) { | |
| new_strings->Set(i, str.Ptr()); | |
| } else { | |
| uint32_t hash = static_cast<uint32_t>(str->GetStoredHashCode()); | |
| DCHECK_EQ(hash, static_cast<uint32_t>(str->ComputeHashCode())) | |
| << "Dex cache strings should be interned"; | |
| auto it2 = intern_table_.FindWithHash(str.Ptr(), hash); | |
| if (it2 == intern_table_.end()) { | |
| uint32_t string_offset = CopyObject(str); | |
| uint32_t address = image_begin_ + string_offset + sizeof(ImageHeader); | |
| intern_table_.InsertWithHash(address, hash); | |
| new_strings->Set(i, reinterpret_cast<mirror::String*>(address)); | |
| } else { | |
| new_strings->Set(i, reinterpret_cast<mirror::String*>(*it2)); | |
| } | |
| // To not confuse string references from the dex cache object and | |
| // string references from the array, we put an offset bigger than the | |
| // size of a DexCache object. ClassLinker::VisitInternedStringReferences | |
| // knows how to decode this offset. | |
| string_reference_offsets_.emplace_back( | |
| sizeof(ImageHeader) + offset, sizeof(mirror::DexCache) + i); | |
| } | |
| } | |
| } | |
| return offset; | |
| } | |
| bool IsInitialized(mirror::Class* cls) REQUIRES_SHARED(Locks::mutator_lock_) { | |
| if (IsInBootImage(cls)) { | |
| const OatDexFile* oat_dex_file = cls->GetDexFile().GetOatDexFile(); | |
| DCHECK(oat_dex_file != nullptr) << "We should always have an .oat file for a boot image"; | |
| uint16_t class_def_index = cls->GetDexClassDefIndex(); | |
| ClassStatus oat_file_class_status = oat_dex_file->GetOatClass(class_def_index).GetStatus(); | |
| return oat_file_class_status == ClassStatus::kVisiblyInitialized; | |
| } else { | |
| return cls->IsVisiblyInitialized<kVerifyNone>(); | |
| } | |
| } | |
| // Try to initialize `copy`. Note that `cls` may not be initialized. | |
| // This is called after the image generation logic has visited super classes | |
| // and super interfaces, so we can just check those directly. | |
| bool TryInitializeClass(mirror::Class* copy, ObjPtr<mirror::Class> cls, uint32_t class_offset) | |
| REQUIRES_SHARED(Locks::mutator_lock_) { | |
| if (!cls->IsVerified()) { | |
| return false; | |
| } | |
| if (cls->IsArrayClass()) { | |
| return true; | |
| } | |
| // Check if we have been able to initialize the super class. | |
| mirror::Class* super = GetClassContent(cls->GetSuperClass()); | |
| DCHECK(super != nullptr) | |
| << "App image classes should always have a super class: " << cls->PrettyClass(); | |
| if (!IsInitialized(super)) { | |
| return false; | |
| } | |
| // We won't initialize class with class initializers. | |
| if (cls->FindClassInitializer(kRuntimePointerSize) != nullptr) { | |
| return false; | |
| } | |
| // For non-interface classes, we require all implemented interfaces to be | |
| // initialized. | |
| if (!cls->IsInterface()) { | |
| for (size_t i = 0; i < cls->NumDirectInterfaces(); i++) { | |
| mirror::Class* itf = GetClassContent(cls->GetDirectInterface(i)); | |
| if (!IsInitialized(itf)) { | |
| return false; | |
| } | |
| } | |
| } | |
| // Trivial case: no static fields. | |
| if (!cls->HasStaticFields()) { | |
| return true; | |
| } | |
| // Go over all static fields and try to initialize them. | |
| EncodedStaticFieldValueIterator it(cls->GetDexFile(), *cls->GetClassDef()); | |
| if (!it.HasNext()) { | |
| return true; | |
| } | |
| // Temporary string offsets in case we failed to initialize the class. We | |
| // will add the offsets at the end of this method if we are successful. | |
| ArenaVector<AppImageReferenceOffsetInfo> string_offsets(allocator_.Adapter()); | |
| ClassLinker* linker = Runtime::Current()->GetClassLinker(); | |
| ClassAccessor accessor(cls->GetDexFile(), *cls->GetClassDef()); | |
| for (const ClassAccessor::Field& field : accessor.GetStaticFields()) { | |
| if (!it.HasNext()) { | |
| break; | |
| } | |
| ArtField* art_field = linker->LookupResolvedField(field.GetIndex(), | |
| cls->GetDexCache(), | |
| cls->GetClassLoader(), | |
| /* is_static= */ true); | |
| DCHECK_NE(art_field, nullptr); | |
| MemberOffset offset(art_field->GetOffset()); | |
| switch (it.GetValueType()) { | |
| case EncodedArrayValueIterator::ValueType::kBoolean: | |
| copy->SetFieldBoolean<false>(offset, it.GetJavaValue().z); | |
| break; | |
| case EncodedArrayValueIterator::ValueType::kByte: | |
| copy->SetFieldByte<false>(offset, it.GetJavaValue().b); | |
| break; | |
| case EncodedArrayValueIterator::ValueType::kShort: | |
| copy->SetFieldShort<false>(offset, it.GetJavaValue().s); | |
| break; | |
| case EncodedArrayValueIterator::ValueType::kChar: | |
| copy->SetFieldChar<false>(offset, it.GetJavaValue().c); | |
| break; | |
| case EncodedArrayValueIterator::ValueType::kInt: | |
| copy->SetField32<false>(offset, it.GetJavaValue().i); | |
| break; | |
| case EncodedArrayValueIterator::ValueType::kLong: | |
| copy->SetField64<false>(offset, it.GetJavaValue().j); | |
| break; | |
| case EncodedArrayValueIterator::ValueType::kFloat: | |
| copy->SetField32<false>(offset, it.GetJavaValue().i); | |
| break; | |
| case EncodedArrayValueIterator::ValueType::kDouble: | |
| copy->SetField64<false>(offset, it.GetJavaValue().j); | |
| break; | |
| case EncodedArrayValueIterator::ValueType::kNull: | |
| copy->SetFieldObject<false>(offset, nullptr); | |
| break; | |
| case EncodedArrayValueIterator::ValueType::kString: { | |
| ObjPtr<mirror::String> str = | |
| linker->LookupString(dex::StringIndex(it.GetJavaValue().i), cls->GetDexCache()); | |
| mirror::String* str_copy = nullptr; | |
| if (str == nullptr) { | |
| // String wasn't created yet. | |
| return false; | |
| } else if (IsInBootImage(str.Ptr())) { | |
| str_copy = str.Ptr(); | |
| } else { | |
| uint32_t hash = static_cast<uint32_t>(str->GetStoredHashCode()); | |
| DCHECK_EQ(hash, static_cast<uint32_t>(str->ComputeHashCode())) | |
| << "Dex cache strings should be interned"; | |
| auto string_it = intern_table_.FindWithHash(str.Ptr(), hash); | |
| if (string_it == intern_table_.end()) { | |
| // The string must be interned. | |
| uint32_t string_offset = CopyObject(str); | |
| // Reload the class copy after having copied the string. | |
| copy = reinterpret_cast<mirror::Class*>(objects_.data() + class_offset); | |
| uint32_t address = image_begin_ + string_offset + sizeof(ImageHeader); | |
| intern_table_.InsertWithHash(address, hash); | |
| str_copy = reinterpret_cast<mirror::String*>(address); | |
| } else { | |
| str_copy = reinterpret_cast<mirror::String*>(*string_it); | |
| } | |
| string_offsets.emplace_back(sizeof(ImageHeader) + class_offset, offset.Int32Value()); | |
| } | |
| uint8_t* raw_addr = reinterpret_cast<uint8_t*>(copy) + offset.Int32Value(); | |
| mirror::HeapReference<mirror::Object>* objref_addr = | |
| reinterpret_cast<mirror::HeapReference<mirror::Object>*>(raw_addr); | |
| objref_addr->Assign</* kIsVolatile= */ false>(str_copy); | |
| break; | |
| } | |
| case EncodedArrayValueIterator::ValueType::kType: { | |
| // Note that it may be that the referenced type hasn't been processed | |
| // yet by the image generation logic. In this case we bail out for | |
| // simplicity. | |
| ObjPtr<mirror::Class> type = | |
| linker->LookupResolvedType(dex::TypeIndex(it.GetJavaValue().i), cls); | |
| mirror::Class* type_copy = nullptr; | |
| if (type == nullptr) { | |
| // Class wasn't resolved yet. | |
| return false; | |
| } else if (IsInBootImage(type.Ptr())) { | |
| // Make sure the type is in our class table. | |
| uint32_t hash = type->DescriptorHash(); | |
| class_table_.InsertWithHash(ClassTable::TableSlot(type.Ptr(), hash), hash); | |
| type_copy = type.Ptr(); | |
| } else if (type->IsArrayClass()) { | |
| std::string class_name; | |
| type->GetDescriptor(&class_name); | |
| auto class_it = array_classes_.find(class_name); | |
| if (class_it == array_classes_.end()) { | |
| return false; | |
| } | |
| type_copy = reinterpret_cast<mirror::Class*>( | |
| image_begin_ + sizeof(ImageHeader) + class_it->second); | |
| } else { | |
| const dex::ClassDef* class_def = type->GetClassDef(); | |
| DCHECK_NE(class_def, nullptr); | |
| auto class_it = classes_.find(class_def); | |
| if (class_it == classes_.end()) { | |
| return false; | |
| } | |
| type_copy = reinterpret_cast<mirror::Class*>( | |
| image_begin_ + sizeof(ImageHeader) + class_it->second); | |
| } | |
| uint8_t* raw_addr = reinterpret_cast<uint8_t*>(copy) + offset.Int32Value(); | |
| mirror::HeapReference<mirror::Object>* objref_addr = | |
| reinterpret_cast<mirror::HeapReference<mirror::Object>*>(raw_addr); | |
| objref_addr->Assign</* kIsVolatile= */ false>(type_copy); | |
| break; | |
| } | |
| default: | |
| LOG(FATAL) << "Unreachable"; | |
| } | |
| it.Next(); | |
| } | |
| // We have successfully initialized the class, we can now record the string | |
| // offsets. | |
| string_reference_offsets_.insert( | |
| string_reference_offsets_.end(), string_offsets.begin(), string_offsets.end()); | |
| return true; | |
| } | |
| uint32_t CopyClass(ObjPtr<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) { | |
| DCHECK(!cls->IsBootStrapClassLoaded()); | |
| uint32_t offset = 0u; | |
| if (cls->IsArrayClass()) { | |
| std::string class_name; | |
| cls->GetDescriptor(&class_name); | |
| auto it = array_classes_.find(class_name); | |
| if (it != array_classes_.end()) { | |
| return it->second; | |
| } | |
| offset = CopyObject(cls); | |
| array_classes_.Put(class_name, offset); | |
| } else { | |
| const dex::ClassDef* class_def = cls->GetClassDef(); | |
| auto it = classes_.find(class_def); | |
| if (it != classes_.end()) { | |
| return it->second; | |
| } | |
| offset = CopyObject(cls); | |
| classes_.Put(class_def, offset); | |
| } | |
| uint32_t hash = cls->DescriptorHash(); | |
| // Save the hash, the `HashSet` implementation requires to find it. | |
| class_hashes_.Put(offset, hash); | |
| uint32_t class_image_address = image_begin_ + sizeof(ImageHeader) + offset; | |
| bool inserted = | |
| class_table_.InsertWithHash(ClassTable::TableSlot(class_image_address, hash), hash).second; | |
| DCHECK(inserted) << "Class " << cls->PrettyDescriptor() | |
| << " (" << cls.Ptr() << ") already inserted"; | |
| // Clear internal state. | |
| mirror::Class* copy = reinterpret_cast<mirror::Class*>(objects_.data() + offset); | |
| copy->SetClinitThreadId(static_cast<pid_t>(0u)); | |
| if (cls->IsArrayClass()) { | |
| DCHECK(copy->IsVisiblyInitialized()); | |
| } else { | |
| copy->SetStatusInternal(cls->IsVerified() ? ClassStatus::kVerified : ClassStatus::kResolved); | |
| } | |
| // Clear static field values. | |
| auto clear_class = [&] () REQUIRES_SHARED(Locks::mutator_lock_) { | |
| MemberOffset static_offset = cls->GetFirstReferenceStaticFieldOffset(kRuntimePointerSize); | |
| uint32_t ref_offsets = cls->GetReferenceInstanceOffsets(); | |
| size_t size = cls->GetClassSize() - static_offset.Uint32Value(); | |
| // Adjust for overflow instance-offset bitmap, which is after the static | |
| // fields. | |
| if ((ref_offsets & mirror::Class::kVisitReferencesSlowpathMask) != 0) { | |
| ref_offsets &= ~mirror::Class::kVisitReferencesSlowpathMask; | |
| size -= ref_offsets * sizeof(uint32_t); | |
| } | |
| memset(objects_.data() + offset + static_offset.Uint32Value(), 0, size); | |
| }; | |
| clear_class(); | |
| bool is_class_initialized = TryInitializeClass(copy, cls, offset); | |
| // Reload the copy, it may have moved after `TryInitializeClass`. | |
| copy = reinterpret_cast<mirror::Class*>(objects_.data() + offset); | |
| if (is_class_initialized) { | |
| copy->SetStatusInternal(ClassStatus::kVisiblyInitialized); | |
| if (!cls->IsArrayClass() && !cls->IsFinalizable()) { | |
| copy->SetObjectSizeAllocFastPath(RoundUp(cls->GetObjectSize(), kObjectAlignment)); | |
| } | |
| if (cls->IsInterface()) { | |
| copy->SetAccessFlags(copy->GetAccessFlags() | kAccRecursivelyInitialized); | |
| } | |
| } else { | |
| // If we fail to initialize, remove initialization related flags and | |
| // clear again. | |
| copy->SetObjectSizeAllocFastPath(std::numeric_limits<uint32_t>::max()); | |
| copy->SetAccessFlags(copy->GetAccessFlags() & ~kAccRecursivelyInitialized); | |
| clear_class(); | |
| } | |
| CopyFieldArrays(cls, class_image_address); | |
| CopyMethodArrays(cls, class_image_address, is_class_initialized); | |
| if (cls->ShouldHaveImt()) { | |
| CopyImTable(cls); | |
| } | |
| return offset; | |
| } | |
| // Copy `obj` in `objects_` and relocate references. Returns the offset | |
| // within our buffer. | |
| uint32_t CopyObject(ObjPtr<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_) { | |
| // Copy the object in `objects_`. | |
| size_t object_size = obj->SizeOf(); | |
| size_t offset = objects_.size(); | |
| DCHECK(IsAligned<kObjectAlignment>(offset)); | |
| object_offsets_.push_back(offset); | |
| objects_.resize(RoundUp(offset + object_size, kObjectAlignment)); | |
| mirror::Object* copy = reinterpret_cast<mirror::Object*>(objects_.data() + offset); | |
| mirror::Object::CopyRawObjectData( | |
| reinterpret_cast<uint8_t*>(copy), obj, object_size - sizeof(mirror::Object)); | |
| // Clear any lockword data. | |
| copy->SetLockWord(LockWord::Default(), /* as_volatile= */ false); | |
| copy->SetClass(obj->GetClass()); | |
| // Fixup reference pointers. | |
| FixupVisitor visitor(this, offset); | |
| obj->VisitReferences</*kVisitNativeRoots=*/ false>(visitor, visitor); | |
| if (obj->IsString()) { | |
| // Ensure a string always has a hashcode stored. This is checked at | |
| // runtime because boot images don't want strings dirtied due to hashcode. | |
| reinterpret_cast<mirror::String*>(copy)->GetHashCode(); | |
| } | |
| object_section_size_ += RoundUp(object_size, kObjectAlignment); | |
| return offset; | |
| } | |
| class CollectDexCacheVisitor : public DexCacheVisitor { | |
| public: | |
| explicit CollectDexCacheVisitor(VariableSizedHandleScope& handles) : handles_(handles) {} | |
| void Visit(ObjPtr<mirror::DexCache> dex_cache) | |
| REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_) override { | |
| dex_caches_.push_back(handles_.NewHandle(dex_cache)); | |
| } | |
| const std::vector<Handle<mirror::DexCache>>& GetDexCaches() const { | |
| return dex_caches_; | |
| } | |
| private: | |
| VariableSizedHandleScope& handles_; | |
| std::vector<Handle<mirror::DexCache>> dex_caches_; | |
| }; | |
| // Find dex caches corresponding to the primary APK. | |
| void FindDexCaches(Thread* self, | |
| dchecked_vector<Handle<mirror::DexCache>>& dex_caches, | |
| VariableSizedHandleScope& handles) | |
| REQUIRES_SHARED(Locks::mutator_lock_) { | |
| ScopedTrace trace("Find dex caches"); | |
| DCHECK(dex_caches.empty()); | |
| // Collect all dex caches. | |
| ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); | |
| CollectDexCacheVisitor visitor(handles); | |
| { | |
| ReaderMutexLock mu(self, *Locks::dex_lock_); | |
| class_linker->VisitDexCaches(&visitor); | |
| } | |
| // Find the primary APK. | |
| AppInfo* app_info = Runtime::Current()->GetAppInfo(); | |
| for (Handle<mirror::DexCache> cache : visitor.GetDexCaches()) { | |
| if (app_info->GetRegisteredCodeType(cache->GetDexFile()->GetLocation()) == | |
| AppInfo::CodeType::kPrimaryApk) { | |
| dex_caches.push_back(handles.NewHandle(cache.Get())); | |
| break; | |
| } | |
| } | |
| if (dex_caches.empty()) { | |
| return; | |
| } | |
| const OatDexFile* oat_dex_file = dex_caches[0]->GetDexFile()->GetOatDexFile(); | |
| if (oat_dex_file == nullptr) { | |
| // We need a .oat file for loading an app image; | |
| dex_caches.clear(); | |
| return; | |
| } | |
| // Store the dex caches in the order in which their corresponding dex files | |
| // are stored in the oat file. When we check for checksums at the point of | |
| // loading the image, we rely on this order. | |
| for (const OatDexFile* current : oat_dex_file->GetOatFile()->GetOatDexFiles()) { | |
| if (current != oat_dex_file) { | |
| for (Handle<mirror::DexCache> cache : visitor.GetDexCaches()) { | |
| if (cache->GetDexFile()->GetOatDexFile() == current) { | |
| dex_caches.push_back(handles.NewHandle(cache.Get())); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| static uint64_t PointerToUint64(void* ptr) { | |
| return reinterpret_cast64<uint64_t>(ptr); | |
| } | |
| void WriteImageMethods() { | |
| ScopedObjectAccess soa(Thread::Current()); | |
| // We can just use plain runtime pointers. | |
| Runtime* runtime = Runtime::Current(); | |
| header_.image_methods_[ImageHeader::kResolutionMethod] = | |
| PointerToUint64(runtime->GetResolutionMethod()); | |
| header_.image_methods_[ImageHeader::kImtConflictMethod] = | |
| PointerToUint64(runtime->GetImtConflictMethod()); | |
| header_.image_methods_[ImageHeader::kImtUnimplementedMethod] = | |
| PointerToUint64(runtime->GetImtUnimplementedMethod()); | |
| header_.image_methods_[ImageHeader::kSaveAllCalleeSavesMethod] = | |
| PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveAllCalleeSaves)); | |
| header_.image_methods_[ImageHeader::kSaveRefsOnlyMethod] = | |
| PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsOnly)); | |
| header_.image_methods_[ImageHeader::kSaveRefsAndArgsMethod] = | |
| PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs)); | |
| header_.image_methods_[ImageHeader::kSaveEverythingMethod] = | |
| PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverything)); | |
| header_.image_methods_[ImageHeader::kSaveEverythingMethodForClinit] = | |
| PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForClinit)); | |
| header_.image_methods_[ImageHeader::kSaveEverythingMethodForSuspendCheck] = | |
| PointerToUint64( | |
| runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForSuspendCheck)); | |
| } | |
| // Header for the image, created at the end once we know the size of all | |
| // sections. | |
| ImageHeader header_; | |
| // Allocator for the various data structures to allocate while generating the | |
| // image. | |
| ArenaAllocator allocator_; | |
| // Contents of the various sections. | |
| ArenaVector<uint8_t> objects_; | |
| ArenaVector<uint8_t> art_fields_; | |
| ArenaVector<uint8_t> art_methods_; | |
| ArenaVector<uint8_t> im_tables_; | |
| ArenaVector<uint8_t> metadata_; | |
| ArenaVector<uint8_t> dex_cache_arrays_; | |
| ArenaVector<AppImageReferenceOffsetInfo> string_reference_offsets_; | |
| // Bitmap of live objects in `objects_`. Populated from `object_offsets_` | |
| // once we know `object_section_size`. | |
| gc::accounting::ContinuousSpaceBitmap image_bitmap_; | |
| // Sections stored in the header. | |
| ArenaVector<ImageSection> sections_; | |
| // A list of offsets in `objects_` where objects begin. | |
| ArenaVector<uint32_t> object_offsets_; | |
| ArenaSafeMap<const dex::ClassDef*, uint32_t> classes_; | |
| ArenaSafeMap<std::string, uint32_t> array_classes_; | |
| ArenaSafeMap<const DexFile*, uint32_t> dex_caches_; | |
| ArenaSafeMap<uint32_t, uint32_t> class_hashes_; | |
| ArenaSafeMap<void*, std::pair<NativeRelocationKind, uint32_t>> native_relocations_; | |
| // Cached values of boot image information. | |
| const uint32_t boot_image_begin_; | |
| const uint32_t boot_image_size_; | |
| // Where the image begins: just after the boot image. | |
| const uint32_t image_begin_; | |
| // Size of the `kSectionObjects` section. | |
| size_t object_section_size_; | |
| // The location of the primary APK / dex file. | |
| std::string dex_location_; | |
| // The intern table for strings that we will write to disk. | |
| InternTableSet intern_table_; | |
| // The class table holding classes that we will write to disk. | |
| ClassTableSet class_table_; | |
| friend class ClassDescriptorHash; | |
| friend class PruneVisitor; | |
| friend class NativePointerVisitor; | |
| }; | |
| std::string RuntimeImage::GetRuntimeImageDir(const std::string& app_data_dir) { | |
| if (app_data_dir.empty()) { | |
| // The data directory is empty for tests. | |
| return ""; | |
| } | |
| return app_data_dir + "/cache/oat_primary/"; | |
| } | |
| // Note: this may return a relative path for tests. | |
| std::string RuntimeImage::GetRuntimeImagePath(const std::string& app_data_dir, | |
| const std::string& dex_location, | |
| const std::string& isa) { | |
| std::string basename = android::base::Basename(dex_location); | |
| std::string filename = ReplaceFileExtension(basename, kArtExtension); | |
| return GetRuntimeImageDir(app_data_dir) + isa + "/" + filename; | |
| } | |
| std::string RuntimeImage::GetRuntimeImagePath(const std::string& dex_location) { | |
| return GetRuntimeImagePath(Runtime::Current()->GetProcessDataDirectory(), | |
| dex_location, | |
| GetInstructionSetString(kRuntimeQuickCodeISA)); | |
| } | |
| static bool EnsureDirectoryExists(const std::string& directory, std::string* error_msg) { | |
| if (!OS::DirectoryExists(directory.c_str())) { | |
| static constexpr mode_t kDirectoryMode = S_IRWXU | S_IRGRP | S_IXGRP| S_IROTH | S_IXOTH; | |
| if (mkdir(directory.c_str(), kDirectoryMode) != 0) { | |
| *error_msg = | |
| StringPrintf("Could not create directory %s: %s", directory.c_str(), strerror(errno)); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool RuntimeImage::WriteImageToDisk(std::string* error_msg) { | |
| gc::Heap* heap = Runtime::Current()->GetHeap(); | |
| if (!heap->HasBootImageSpace()) { | |
| *error_msg = "Cannot generate an app image without a boot image"; | |
| return false; | |
| } | |
| std::string oat_path = GetRuntimeImageDir(Runtime::Current()->GetProcessDataDirectory()); | |
| if (!oat_path.empty() && !EnsureDirectoryExists(oat_path, error_msg)) { | |
| return false; | |
| } | |
| ScopedTrace generate_image_trace("Generating runtime image"); | |
| std::unique_ptr<RuntimeImageHelper> image(new RuntimeImageHelper(heap)); | |
| if (!image->Generate(error_msg)) { | |
| return false; | |
| } | |
| ScopedTrace write_image_trace("Writing runtime image to disk"); | |
| const std::string path = GetRuntimeImagePath(image->GetDexLocation()); | |
| if (!EnsureDirectoryExists(android::base::Dirname(path), error_msg)) { | |
| return false; | |
| } | |
| // We first generate the app image in a temporary file, which we will then | |
| // move to `path`. | |
| const std::string temp_path = ReplaceFileExtension(path, std::to_string(getpid()) + ".tmp"); | |
| ImageFileGuard image_file; | |
| image_file.reset(OS::CreateEmptyFileWriteOnly(temp_path.c_str())); | |
| if (image_file == nullptr) { | |
| *error_msg = "Could not open " + temp_path + " for writing"; | |
| return false; | |
| } | |
| std::vector<uint8_t> full_data(image->GetHeader()->GetImageSize()); | |
| image->FillData(full_data); | |
| // Specify default block size of 512K to enable parallel image decompression. | |
| static constexpr size_t kMaxImageBlockSize = 524288; | |
| // Use LZ4 as good compromise between CPU time and compression. LZ4HC | |
| // empirically takes 10x more time compressing. | |
| static constexpr ImageHeader::StorageMode kImageStorageMode = ImageHeader::kStorageModeLZ4; | |
| // Note: no need to update the checksum of the runtime app image: we have no | |
| // use for it, and computing it takes CPU time. | |
| if (!image->GetHeader()->WriteData( | |
| image_file, | |
| full_data.data(), | |
| reinterpret_cast<const uint8_t*>(image->GetImageBitmap().Begin()), | |
| kImageStorageMode, | |
| kMaxImageBlockSize, | |
| /* update_checksum= */ false, | |
| error_msg)) { | |
| return false; | |
| } | |
| if (!image_file.WriteHeaderAndClose(temp_path, image->GetHeader(), error_msg)) { | |
| return false; | |
| } | |
| if (rename(temp_path.c_str(), path.c_str()) != 0) { | |
| *error_msg = | |
| "Failed to move runtime app image to " + path + ": " + std::string(strerror(errno)); | |
| // Unlink directly: we cannot use `out` as we have closed it. | |
| unlink(temp_path.c_str()); | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace art |