/* * 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 #include #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(image_begin_), RoundUp(object_section_size_, gc::accounting::CardTable::kCardSize)); for (uint32_t offset : object_offsets_) { DCHECK(IsAligned(image_begin_ + sizeof(ImageHeader) + offset)); image_bitmap_.Set( reinterpret_cast(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& 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& 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(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 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(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 T* GetOrComputeImageAddress(ObjPtr 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()) { // 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(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(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 T* FromImageOffsetToRuntimeContent(uint32_t offset) { if (offset == 0u || IsInBootImage(reinterpret_cast(offset))) { return reinterpret_cast(offset); } uint32_t vector_data_offset = FromImageOffsetToVectorOffset(offset); return reinterpret_cast(objects_.data() + vector_data_offset); } uint32_t FromImageOffsetToVectorOffset(uint32_t offset) const { DCHECK(!IsInBootImage(reinterpret_cast(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(hash); } size_t operator()(uint32_t entry) const NO_THREAD_SAFETY_ANALYSIS { return (*this)(helper_->FromImageOffsetToRuntimeContent(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(entry)); } bool operator()(uint32_t entry, uint32_t other) const NO_THREAD_SAFETY_ANALYSIS { return (*this)(entry, helper_->FromImageOffsetToRuntimeContent(other)); } private: RuntimeImageHelper* helper_; }; using InternTableSet = HashSet, 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(ptr))) { return reinterpret_cast32(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; // Helper class to collect classes that we will generate in the image. class ClassTableVisitor { public: ClassTableVisitor(Handle loader, VariableSizedHandleScope& handles) : loader_(loader), handles_(handles) {} bool operator()(ObjPtr 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 class_loader = klass->GetClassLoader(); if (klass->IsResolved() && (class_loader == loader_.Get() || class_loader == nullptr)) { handles_.NewHandle(klass); } return true; } private: Handle loader_; VariableSizedHandleScope& handles_; }; // Helper class visitor to filter out classes we cannot emit. class PruneVisitor { public: PruneVisitor(Thread* self, RuntimeImageHelper* helper, const ArenaSet& dex_files, ArenaVector>& classes, ArenaAllocator& allocator) : self_(self), helper_(helper), dex_files_(dex_files), visited_(allocator.Adapter()), classes_to_write_(classes) {} bool CanEmitHelper(Handle 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 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 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 temp = cls.Get(); while ((temp = temp->GetComponentType())->IsArrayClass()) {} StackHandleScope<1> hs(self_); Handle 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 obj) REQUIRES_SHARED(Locks::mutator_lock_) { MutableHandle 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& dex_files_; ArenaSafeMap visited_; ArenaVector>& classes_to_write_; }; void EmitClasses(Thread* self, Handle> dex_cache_array) REQUIRES_SHARED(Locks::mutator_lock_) { ScopedTrace trace("Emit strings and classes"); ArenaSet 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 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> 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 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(&objects_[entry.second]); mirror::GcRootArray* 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& data = (reloc_it->second.first == NativeRelocationKind::kFullNativeDexCacheArray) ? dex_cache_arrays_ : metadata_; mirror::GcRootArray* content_array = reinterpret_cast*>( data.data() + reloc_it->second.second); for (uint32_t i = 0; i < dex_file.NumTypeIds(); ++i) { ObjPtr 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( 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( 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 T* operator()(T* ptr, [[maybe_unused]] void** dest_addr) const { return helper_->NativeLocationInImage(ptr, /* must_have_relocation= */ true); } template T* operator()(T* ptr, bool must_have_relocation = true) const { return helper_->NativeLocationInImage(ptr, must_have_relocation); } private: RuntimeImageHelper* helper_; }; template 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(image_begin_ + offset + it->second.second); } case NativeRelocationKind::kArtFieldArray: { uint32_t offset = sections_[ImageHeader::kSectionArtFields].Offset(); return reinterpret_cast(image_begin_ + offset + it->second.second); } case NativeRelocationKind::kImTable: { uint32_t offset = sections_[ImageHeader::kSectionImTables].Offset(); return reinterpret_cast(image_begin_ + offset + it->second.second); } case NativeRelocationKind::kStartupNativeDexCacheArray: { uint32_t offset = sections_[ImageHeader::kSectionMetadata].Offset(); return reinterpret_cast(image_begin_ + offset + it->second.second); } case NativeRelocationKind::kFullNativeDexCacheArray: { uint32_t offset = sections_[ImageHeader::kSectionDexCacheArrays].Offset(); return reinterpret_cast(image_begin_ + offset + it->second.second); } } } template 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( reinterpret_cast32( klass->GetSuperClass().Ptr())); DCHECK(super != nullptr) << "j.l.Object should never be in an app runtime image"; mirror::PointerArray* vtable = FromImageOffsetToRuntimeContent( reinterpret_cast32(klass->GetVTable().Ptr())); mirror::PointerArray* super_vtable = FromImageOffsetToRuntimeContent( reinterpret_cast32(super->GetVTable().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( reinterpret_cast32( klass->GetIfTable().Ptr())); mirror::IfTable* super_iftable = FromImageOffsetToRuntimeContent( reinterpret_cast32( super->GetIfTable().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( reinterpret_cast32( iftable->GetMethodArrayOrNull(i).Ptr())); mirror::PointerArray* super_methods = (i < super_iftable_count) ? FromImageOffsetToRuntimeContent( reinterpret_cast32( super_iftable->GetMethodArrayOrNull(i).Ptr())) : nullptr; if (methods != super_methods) { DCHECK(!IsInBootImage(methods)); methods->Fixup(methods, kRuntimePointerSize, visitor); } } } template void RelocateNativeDexCacheArray(mirror::NativeArray* 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& data = (it->second.first == NativeRelocationKind::kFullNativeDexCacheArray) ? dex_cache_arrays_ : metadata_; mirror::NativeArray* content_array = reinterpret_cast*>(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 void RelocateDexCacheArrays(mirror::DexCache* cache, const DexFile& dex_file, const Visitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_) { mirror::NativeArray* old_method_array = cache->GetResolvedMethodsArray(); cache->SetResolvedMethodsArray(visitor(old_method_array)); RelocateNativeDexCacheArray(old_method_array, dex_file.NumMethodIds(), visitor); mirror::NativeArray* old_field_array = cache->GetResolvedFieldsArray(); cache->SetResolvedFieldsArray(visitor(old_field_array)); RelocateNativeDexCacheArray(old_field_array, dex_file.NumFieldIds(), visitor); mirror::GcRootArray* old_strings_array = cache->GetStringsArray(); cache->SetStringsArray(visitor(old_strings_array)); mirror::GcRootArray* 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(&objects_[entry.second]); cls->FixupNativePointers(cls, kRuntimePointerSize, visitor); RelocateMethodPointerArrays(cls, visitor); } for (auto&& entry : array_classes_) { mirror::Class* cls = reinterpret_cast(&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(im_tables_.data() + entry.second.second); RelocateImTable(im_table, visitor); } } for (auto&& entry : dex_caches_) { mirror::DexCache* cache = reinterpret_cast(&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 cls, uint32_t class_image_address) REQUIRES_SHARED(Locks::mutator_lock_) { LengthPrefixedArray* fields[] = { cls->GetSFieldsPtr(), cls->GetIFieldsPtr(), }; for (LengthPrefixedArray* cur_fields : fields) { if (cur_fields != nullptr) { // Copy the array. size_t number_of_fields = cur_fields->size(); size_t size = LengthPrefixedArray::ComputeSize(number_of_fields); size_t offset = art_fields_.size(); art_fields_.resize(offset + size); auto* dest_array = reinterpret_cast*>(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(class_image_address)); } } } } void CopyMethodArrays(ObjPtr 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::ComputeSize(number_of_methods); size_t offset = art_methods_.size(); art_methods_.resize(offset + size); auto* dest_array = reinterpret_cast*>(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 declaring_class = method->GetDeclaringClass(); if (declaring_class == cls) { copy->GetDeclaringClassAddressWithoutBarrier()->Assign( reinterpret_cast(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( image_begin_ + sizeof(ImageHeader) + classes_.Get(declaring_class->GetClassDef()))); } } // Record the native relocation of the method. uintptr_t copy_offset = reinterpret_cast(copy) - reinterpret_cast(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& 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->SetNativePointer(ArtMethod::EntryPointFromQuickCompiledCodeOffset(kRuntimePointerSize), entrypoint, kRuntimePointerSize); 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(method->GetCodeItem()); DCHECK_GE(code_item, method->GetDexFile()->DataBegin()); uint32_t code_item_offset = dchecked_integral_cast( code_item - method->GetDexFile()->DataBegin());; copy->SetDataPtrSize( reinterpret_cast(code_item_offset), kRuntimePointerSize); } } } void CopyImTable(ObjPtr 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>& 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 class_loader = hs.NewHandle(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* 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 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 object_array_class = handles.NewHandle( GetClassRoot>(class_linker)); Handle> image_roots = handles.NewHandle( mirror::ObjectArray::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> 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 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 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(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 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 isa_features = InstructionSetFeatures::FromCppDefines(); std::unique_ptr oat_header( OatHeader::Create(kRuntimeISA, isa_features.get(), number_of_dex_files, &key_value_store)); // Create the byte array containing the oat header and dex checksums. uint32_t checksums_size = checksums.size() * sizeof(uint32_t); Handle 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> dex_cache_array = handles.NewHandle( mirror::ObjectArray::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* root) const { LOG(FATAL) << "UNREACHABLE"; } void VisitRoot([[maybe_unused]] mirror::CompressedReference* root) const { LOG(FATAL) << "UNREACHABLE"; } void operator()(ObjPtr 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 ref = is_static ? nullptr : obj->GetFieldObject(offset); mirror::Object* address = image_->GetOrComputeImageAddress(ref); mirror::Object* copy = reinterpret_cast(image_->objects_.data() + copy_offset_); copy->GetFieldObjectReferenceAddr(offset)->Assign(address); } // java.lang.ref.Reference visitor. void operator()([[maybe_unused]] ObjPtr klass, ObjPtr ref) const REQUIRES_SHARED(Locks::mutator_lock_) { operator()(ref, mirror::Reference::ReferentOffset(), /* is_static */ false); } private: RuntimeImageHelper* image_; size_t copy_offset_; }; template void CopyNativeDexCacheArray(uint32_t num_entries, uint32_t max_entries, mirror::NativeArray* array) { if (array == nullptr) { return; } bool only_startup = !mirror::DexCache::ShouldAllocateFullArray(num_entries, max_entries); ArenaVector& 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(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* copy = reinterpret_cast*>(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 mirror::GcRootArray* CreateGcRootDexCacheArray(uint32_t num_entries, uint32_t max_entries, mirror::GcRootArray* array) { if (array == nullptr) { return nullptr; } bool only_startup = !mirror::DexCache::ShouldAllocateFullArray(num_entries, max_entries); ArenaVector& data = only_startup ? metadata_ : dex_cache_arrays_; NativeRelocationKind relocation_kind = only_startup ? NativeRelocationKind::kStartupNativeDexCacheArray : NativeRelocationKind::kFullNativeDexCacheArray; size_t size = num_entries * sizeof(GcRoot); // 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) == sizeof(uint32_t)); size_t offset = data.size() + sizeof(uint32_t); data.resize(data.size() + sizeof(uint32_t) + size); reinterpret_cast(data.data() + offset)[-1] = num_entries; native_relocations_.Put(array, std::make_pair(relocation_kind, offset)); return reinterpret_cast*>(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 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(objects_.data() + offset); reinterpret_cast(copy)->ResetNativeArrays(); reinterpret_cast(copy)->SetDexFile(nullptr); if (!EmitDexCacheArrays()) { return offset; } // Copy the ArtMethod array. mirror::NativeArray* 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(copy)->SetResolvedMethodsArray(resolved_methods); // Copy the ArtField array. mirror::NativeArray* 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(copy)->SetResolvedFieldsArray(resolved_fields); // Copy the type array. mirror::GcRootArray* 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(copy)->SetResolvedTypesArray(resolved_types); // Copy the string array. mirror::GcRootArray* strings = cache->GetStringsArray(); // Note: `new_strings` points to temporary data, and is only valid here. mirror::GcRootArray* 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(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 str = strings->Get(i); if (str == nullptr || IsInBootImage(str.Ptr())) { new_strings->Set(i, str.Ptr()); } else { uint32_t hash = static_cast(str->GetStoredHashCode()); DCHECK_EQ(hash, static_cast(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(address)); } else { new_strings->Set(i, reinterpret_cast(*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(); } } // 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 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->NumStaticFields() == 0u) { 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 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(offset, it.GetJavaValue().z); break; case EncodedArrayValueIterator::ValueType::kByte: copy->SetFieldByte(offset, it.GetJavaValue().b); break; case EncodedArrayValueIterator::ValueType::kShort: copy->SetFieldShort(offset, it.GetJavaValue().s); break; case EncodedArrayValueIterator::ValueType::kChar: copy->SetFieldChar(offset, it.GetJavaValue().c); break; case EncodedArrayValueIterator::ValueType::kInt: copy->SetField32(offset, it.GetJavaValue().i); break; case EncodedArrayValueIterator::ValueType::kLong: copy->SetField64(offset, it.GetJavaValue().j); break; case EncodedArrayValueIterator::ValueType::kFloat: copy->SetField32(offset, it.GetJavaValue().i); break; case EncodedArrayValueIterator::ValueType::kDouble: copy->SetField64(offset, it.GetJavaValue().j); break; case EncodedArrayValueIterator::ValueType::kNull: copy->SetFieldObject(offset, nullptr); break; case EncodedArrayValueIterator::ValueType::kString: { ObjPtr 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(str->GetStoredHashCode()); DCHECK_EQ(hash, static_cast(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(objects_.data() + class_offset); uint32_t address = image_begin_ + string_offset + sizeof(ImageHeader); intern_table_.InsertWithHash(address, hash); str_copy = reinterpret_cast(address); } else { str_copy = reinterpret_cast(*string_it); } string_offsets.emplace_back(sizeof(ImageHeader) + class_offset, offset.Int32Value()); } uint8_t* raw_addr = reinterpret_cast(copy) + offset.Int32Value(); mirror::HeapReference* objref_addr = reinterpret_cast*>(raw_addr); objref_addr->Assign(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 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( 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( image_begin_ + sizeof(ImageHeader) + class_it->second); } uint8_t* raw_addr = reinterpret_cast(copy) + offset.Int32Value(); mirror::HeapReference* objref_addr = reinterpret_cast*>(raw_addr); objref_addr->Assign(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 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(objects_.data() + offset); copy->SetClinitThreadId(static_cast(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); memset(objects_.data() + offset + static_offset.Uint32Value(), 0, cls->GetClassSize() - static_offset.Uint32Value()); }; clear_class(); bool is_class_initialized = TryInitializeClass(copy, cls, offset); // Reload the copy, it may have moved after `TryInitializeClass`. copy = reinterpret_cast(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::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 obj) REQUIRES_SHARED(Locks::mutator_lock_) { // Copy the object in `objects_`. size_t object_size = obj->SizeOf(); size_t offset = objects_.size(); DCHECK(IsAligned(offset)); object_offsets_.push_back(offset); objects_.resize(RoundUp(offset + object_size, kObjectAlignment)); mirror::Object* copy = reinterpret_cast(objects_.data() + offset); mirror::Object::CopyRawObjectData( reinterpret_cast(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(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(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 dex_cache) REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_) override { dex_caches_.push_back(handles_.NewHandle(dex_cache)); } const std::vector>& GetDexCaches() const { return dex_caches_; } private: VariableSizedHandleScope& handles_; std::vector> dex_caches_; }; // Find dex caches corresponding to the primary APK. void FindDexCaches(Thread* self, dchecked_vector>& 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 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 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(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 objects_; ArenaVector art_fields_; ArenaVector art_methods_; ArenaVector im_tables_; ArenaVector metadata_; ArenaVector dex_cache_arrays_; ArenaVector 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 sections_; // A list of offsets in `objects_` where objects begin. ArenaVector object_offsets_; ArenaSafeMap classes_; ArenaSafeMap array_classes_; ArenaSafeMap dex_caches_; ArenaSafeMap class_hashes_; ArenaSafeMap> 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, "art"); return GetRuntimeImageDir(app_data_dir) + isa + "/" + filename; } std::string RuntimeImage::GetRuntimeImagePath(const std::string& dex_location) { return GetRuntimeImagePath(Runtime::Current()->GetProcessDataDirectory(), dex_location, GetInstructionSetString(kRuntimeISA)); } 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 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 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(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