| /* | |
| * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| * | |
| */ | |
| #include "cds/archiveHeapLoader.inline.hpp" | |
| #include "cds/cdsConfig.hpp" | |
| #include "cds/heapShared.hpp" | |
| #include "cds/metaspaceShared.hpp" | |
| #include "classfile/classLoaderDataShared.hpp" | |
| #include "classfile/systemDictionaryShared.hpp" | |
| #include "gc/shared/collectedHeap.hpp" | |
| #include "logging/log.hpp" | |
| #include "memory/iterator.inline.hpp" | |
| #include "memory/resourceArea.hpp" | |
| #include "memory/universe.hpp" | |
| #include "sanitizers/ub.hpp" | |
| #include "utilities/bitMap.inline.hpp" | |
| #include "utilities/copy.hpp" | |
| #if INCLUDE_CDS_JAVA_HEAP | |
| bool ArchiveHeapLoader::_is_mapped = false; | |
| bool ArchiveHeapLoader::_is_loaded = false; | |
| bool ArchiveHeapLoader::_narrow_oop_base_initialized = false; | |
| address ArchiveHeapLoader::_narrow_oop_base; | |
| int ArchiveHeapLoader::_narrow_oop_shift; | |
| // Support for loaded heap. | |
| uintptr_t ArchiveHeapLoader::_loaded_heap_bottom = 0; | |
| uintptr_t ArchiveHeapLoader::_loaded_heap_top = 0; | |
| uintptr_t ArchiveHeapLoader::_dumptime_base = UINTPTR_MAX; | |
| uintptr_t ArchiveHeapLoader::_dumptime_top = 0; | |
| intx ArchiveHeapLoader::_runtime_offset = 0; | |
| bool ArchiveHeapLoader::_loading_failed = false; | |
| // Support for mapped heap. | |
| uintptr_t ArchiveHeapLoader::_mapped_heap_bottom = 0; | |
| bool ArchiveHeapLoader::_mapped_heap_relocation_initialized = false; | |
| ptrdiff_t ArchiveHeapLoader::_mapped_heap_delta = 0; | |
| // Every mapped region is offset by _mapped_heap_delta from its requested address. | |
| // See FileMapInfo::heap_region_requested_address(). | |
| ATTRIBUTE_NO_UBSAN | |
| void ArchiveHeapLoader::init_mapped_heap_info(address mapped_heap_bottom, ptrdiff_t delta, int dumptime_oop_shift) { | |
| assert(!_mapped_heap_relocation_initialized, "only once"); | |
| if (!UseCompressedOops) { | |
| assert(dumptime_oop_shift == 0, "sanity"); | |
| } | |
| assert(can_map(), "sanity"); | |
| init_narrow_oop_decoding(CompressedOops::base() + delta, dumptime_oop_shift); | |
| _mapped_heap_bottom = (intptr_t)mapped_heap_bottom; | |
| _mapped_heap_delta = delta; | |
| _mapped_heap_relocation_initialized = true; | |
| } | |
| void ArchiveHeapLoader::init_narrow_oop_decoding(address base, int shift) { | |
| assert(!_narrow_oop_base_initialized, "only once"); | |
| _narrow_oop_base_initialized = true; | |
| _narrow_oop_base = base; | |
| _narrow_oop_shift = shift; | |
| } | |
| void ArchiveHeapLoader::fixup_region() { | |
| FileMapInfo* mapinfo = FileMapInfo::current_info(); | |
| if (is_mapped()) { | |
| mapinfo->fixup_mapped_heap_region(); | |
| } else if (_loading_failed) { | |
| fill_failed_loaded_heap(); | |
| } | |
| if (is_in_use()) { | |
| if (!CDSConfig::is_using_full_module_graph()) { | |
| // Need to remove all the archived java.lang.Module objects from HeapShared::roots(). | |
| ClassLoaderDataShared::clear_archived_oops(); | |
| } | |
| } | |
| } | |
| // ------------------ Support for Region MAPPING ----------------------------------------- | |
| // Patch all the embedded oop pointers inside an archived heap region, | |
| // to be consistent with the runtime oop encoding. | |
| class PatchCompressedEmbeddedPointers: public BitMapClosure { | |
| narrowOop* _start; | |
| public: | |
| PatchCompressedEmbeddedPointers(narrowOop* start) : _start(start) {} | |
| bool do_bit(size_t offset) { | |
| narrowOop* p = _start + offset; | |
| narrowOop v = *p; | |
| assert(!CompressedOops::is_null(v), "null oops should have been filtered out at dump time"); | |
| oop o = ArchiveHeapLoader::decode_from_mapped_archive(v); | |
| RawAccess<IS_NOT_NULL>::oop_store(p, o); | |
| return true; | |
| } | |
| }; | |
| class PatchCompressedEmbeddedPointersQuick: public BitMapClosure { | |
| narrowOop* _start; | |
| uint32_t _delta; | |
| public: | |
| PatchCompressedEmbeddedPointersQuick(narrowOop* start, uint32_t delta) : _start(start), _delta(delta) {} | |
| bool do_bit(size_t offset) { | |
| narrowOop* p = _start + offset; | |
| narrowOop v = *p; | |
| assert(!CompressedOops::is_null(v), "null oops should have been filtered out at dump time"); | |
| narrowOop new_v = CompressedOops::narrow_oop_cast(CompressedOops::narrow_oop_value(v) + _delta); | |
| assert(!CompressedOops::is_null(new_v), "should never relocate to narrowOop(0)"); | |
| #ifdef ASSERT | |
| oop o1 = ArchiveHeapLoader::decode_from_mapped_archive(v); | |
| oop o2 = CompressedOops::decode_not_null(new_v); | |
| assert(o1 == o2, "quick delta must work"); | |
| #endif | |
| RawAccess<IS_NOT_NULL>::oop_store(p, new_v); | |
| return true; | |
| } | |
| }; | |
| class PatchUncompressedEmbeddedPointers: public BitMapClosure { | |
| oop* _start; | |
| intptr_t _delta; | |
| public: | |
| PatchUncompressedEmbeddedPointers(oop* start, intx runtime_offset) : | |
| _start(start), | |
| _delta(runtime_offset) {} | |
| PatchUncompressedEmbeddedPointers(oop* start) : | |
| _start(start), | |
| _delta(ArchiveHeapLoader::mapped_heap_delta()) {} | |
| bool do_bit(size_t offset) { | |
| oop* p = _start + offset; | |
| intptr_t dumptime_oop = (intptr_t)((void*)*p); | |
| assert(dumptime_oop != 0, "null oops should have been filtered out at dump time"); | |
| intptr_t runtime_oop = dumptime_oop + _delta; | |
| RawAccess<IS_NOT_NULL>::oop_store(p, cast_to_oop(runtime_oop)); | |
| return true; | |
| } | |
| }; | |
| void ArchiveHeapLoader::patch_compressed_embedded_pointers(BitMapView bm, | |
| FileMapInfo* info, | |
| MemRegion region) { | |
| narrowOop dt_encoded_bottom = info->encoded_heap_region_dumptime_address(); | |
| narrowOop rt_encoded_bottom = CompressedOops::encode_not_null(cast_to_oop(region.start())); | |
| log_info(aot)("patching heap embedded pointers: narrowOop 0x%8x -> 0x%8x", | |
| (uint)dt_encoded_bottom, (uint)rt_encoded_bottom); | |
| // Optimization: if dumptime shift is the same as runtime shift, we can perform a | |
| // quick conversion from "dumptime narrowOop" -> "runtime narrowOop". | |
| narrowOop* patching_start = (narrowOop*)region.start() + FileMapInfo::current_info()->heap_oopmap_start_pos(); | |
| if (_narrow_oop_shift == CompressedOops::shift()) { | |
| uint32_t quick_delta = (uint32_t)rt_encoded_bottom - (uint32_t)dt_encoded_bottom; | |
| log_info(aot)("heap data relocation quick delta = 0x%x", quick_delta); | |
| if (quick_delta == 0) { | |
| log_info(aot)("heap data relocation unnecessary, quick_delta = 0"); | |
| } else { | |
| PatchCompressedEmbeddedPointersQuick patcher(patching_start, quick_delta); | |
| bm.iterate(&patcher); | |
| } | |
| } else { | |
| log_info(aot)("heap data quick relocation not possible"); | |
| PatchCompressedEmbeddedPointers patcher(patching_start); | |
| bm.iterate(&patcher); | |
| } | |
| } | |
| // Patch all the non-null pointers that are embedded in the archived heap objects | |
| // in this (mapped) region | |
| void ArchiveHeapLoader::patch_embedded_pointers(FileMapInfo* info, | |
| MemRegion region, address oopmap, | |
| size_t oopmap_size_in_bits) { | |
| BitMapView bm((BitMap::bm_word_t*)oopmap, oopmap_size_in_bits); | |
| if (UseCompressedOops) { | |
| patch_compressed_embedded_pointers(bm, info, region); | |
| } else { | |
| PatchUncompressedEmbeddedPointers patcher((oop*)region.start() + FileMapInfo::current_info()->heap_oopmap_start_pos()); | |
| bm.iterate(&patcher); | |
| } | |
| } | |
| // ------------------ Support for Region LOADING ----------------------------------------- | |
| // The CDS archive remembers each heap object by its address at dump time, but | |
| // the heap object may be loaded at a different address at run time. This structure is used | |
| // to translate the dump time addresses for all objects in FileMapInfo::space_at(region_index) | |
| // to their runtime addresses. | |
| struct LoadedArchiveHeapRegion { | |
| int _region_index; // index for FileMapInfo::space_at(index) | |
| size_t _region_size; // number of bytes in this region | |
| uintptr_t _dumptime_base; // The dump-time (decoded) address of the first object in this region | |
| intx _runtime_offset; // If an object's dump time address P is within in this region, its | |
| // runtime address is P + _runtime_offset | |
| uintptr_t top() { | |
| return _dumptime_base + _region_size; | |
| } | |
| }; | |
| void ArchiveHeapLoader::init_loaded_heap_relocation(LoadedArchiveHeapRegion* loaded_region) { | |
| _dumptime_base = loaded_region->_dumptime_base; | |
| _dumptime_top = loaded_region->top(); | |
| _runtime_offset = loaded_region->_runtime_offset; | |
| } | |
| bool ArchiveHeapLoader::can_load() { | |
| return Universe::heap()->can_load_archived_objects(); | |
| } | |
| class ArchiveHeapLoader::PatchLoadedRegionPointers: public BitMapClosure { | |
| narrowOop* _start; | |
| intx _offset; | |
| uintptr_t _base; | |
| uintptr_t _top; | |
| public: | |
| PatchLoadedRegionPointers(narrowOop* start, LoadedArchiveHeapRegion* loaded_region) | |
| : _start(start), | |
| _offset(loaded_region->_runtime_offset), | |
| _base(loaded_region->_dumptime_base), | |
| _top(loaded_region->top()) {} | |
| bool do_bit(size_t offset) { | |
| assert(UseCompressedOops, "PatchLoadedRegionPointers for uncompressed oops is unimplemented"); | |
| narrowOop* p = _start + offset; | |
| narrowOop v = *p; | |
| assert(!CompressedOops::is_null(v), "null oops should have been filtered out at dump time"); | |
| uintptr_t o = cast_from_oop<uintptr_t>(ArchiveHeapLoader::decode_from_archive(v)); | |
| assert(_base <= o && o < _top, "must be"); | |
| o += _offset; | |
| ArchiveHeapLoader::assert_in_loaded_heap(o); | |
| RawAccess<IS_NOT_NULL>::oop_store(p, cast_to_oop(o)); | |
| return true; | |
| } | |
| }; | |
| bool ArchiveHeapLoader::init_loaded_region(FileMapInfo* mapinfo, LoadedArchiveHeapRegion* loaded_region, | |
| MemRegion& archive_space) { | |
| size_t total_bytes = 0; | |
| FileMapRegion* r = mapinfo->region_at(MetaspaceShared::hp); | |
| r->assert_is_heap_region(); | |
| if (r->used() == 0) { | |
| return false; | |
| } | |
| assert(is_aligned(r->used(), HeapWordSize), "must be"); | |
| total_bytes += r->used(); | |
| loaded_region->_region_index = MetaspaceShared::hp; | |
| loaded_region->_region_size = r->used(); | |
| loaded_region->_dumptime_base = (uintptr_t)mapinfo->heap_region_dumptime_address(); | |
| assert(is_aligned(total_bytes, HeapWordSize), "must be"); | |
| size_t word_size = total_bytes / HeapWordSize; | |
| HeapWord* buffer = Universe::heap()->allocate_loaded_archive_space(word_size); | |
| if (buffer == nullptr) { | |
| return false; | |
| } | |
| archive_space = MemRegion(buffer, word_size); | |
| _loaded_heap_bottom = (uintptr_t)archive_space.start(); | |
| _loaded_heap_top = _loaded_heap_bottom + total_bytes; | |
| loaded_region->_runtime_offset = _loaded_heap_bottom - loaded_region->_dumptime_base; | |
| return true; | |
| } | |
| bool ArchiveHeapLoader::load_heap_region_impl(FileMapInfo* mapinfo, LoadedArchiveHeapRegion* loaded_region, | |
| uintptr_t load_address) { | |
| uintptr_t bitmap_base = (uintptr_t)mapinfo->map_bitmap_region(); | |
| if (bitmap_base == 0) { | |
| _loading_failed = true; | |
| return false; // OOM or CRC error | |
| } | |
| FileMapRegion* r = mapinfo->region_at(loaded_region->_region_index); | |
| if (!mapinfo->read_region(loaded_region->_region_index, (char*)load_address, r->used(), /* do_commit = */ false)) { | |
| // There's no easy way to free the buffer, so we will fill it with zero later | |
| // in fill_failed_loaded_heap(), and it will eventually be GC'ed. | |
| log_warning(aot)("Loading of heap region %d has failed. Archived objects are disabled", loaded_region->_region_index); | |
| _loading_failed = true; | |
| return false; | |
| } | |
| assert(r->mapped_base() == (char*)load_address, "sanity"); | |
| log_info(aot)("Loaded heap region #%d at base " INTPTR_FORMAT " top " INTPTR_FORMAT | |
| " size %6zu delta %zd", | |
| loaded_region->_region_index, load_address, load_address + loaded_region->_region_size, | |
| loaded_region->_region_size, loaded_region->_runtime_offset); | |
| uintptr_t oopmap = bitmap_base + r->oopmap_offset(); | |
| BitMapView bm((BitMap::bm_word_t*)oopmap, r->oopmap_size_in_bits()); | |
| if (UseCompressedOops) { | |
| PatchLoadedRegionPointers patcher((narrowOop*)load_address + FileMapInfo::current_info()->heap_oopmap_start_pos(), loaded_region); | |
| bm.iterate(&patcher); | |
| } else { | |
| PatchUncompressedEmbeddedPointers patcher((oop*)load_address + FileMapInfo::current_info()->heap_oopmap_start_pos(), loaded_region->_runtime_offset); | |
| bm.iterate(&patcher); | |
| } | |
| return true; | |
| } | |
| bool ArchiveHeapLoader::load_heap_region(FileMapInfo* mapinfo) { | |
| assert(can_load(), "loaded heap for must be supported"); | |
| init_narrow_oop_decoding(mapinfo->narrow_oop_base(), mapinfo->narrow_oop_shift()); | |
| LoadedArchiveHeapRegion loaded_region; | |
| memset(&loaded_region, 0, sizeof(loaded_region)); | |
| MemRegion archive_space; | |
| if (!init_loaded_region(mapinfo, &loaded_region, archive_space)) { | |
| return false; | |
| } | |
| if (!load_heap_region_impl(mapinfo, &loaded_region, (uintptr_t)archive_space.start())) { | |
| assert(_loading_failed, "must be"); | |
| return false; | |
| } | |
| init_loaded_heap_relocation(&loaded_region); | |
| _is_loaded = true; | |
| return true; | |
| } | |
| class VerifyLoadedHeapEmbeddedPointers: public BasicOopIterateClosure { | |
| ResourceHashtable<uintptr_t, bool>* _table; | |
| public: | |
| VerifyLoadedHeapEmbeddedPointers(ResourceHashtable<uintptr_t, bool>* table) : _table(table) {} | |
| virtual void do_oop(narrowOop* p) { | |
| // This should be called before the loaded region is modified, so all the embedded pointers | |
| // must be null, or must point to a valid object in the loaded region. | |
| narrowOop v = *p; | |
| if (!CompressedOops::is_null(v)) { | |
| oop o = CompressedOops::decode_not_null(v); | |
| uintptr_t u = cast_from_oop<uintptr_t>(o); | |
| ArchiveHeapLoader::assert_in_loaded_heap(u); | |
| guarantee(_table->contains(u), "must point to beginning of object in loaded archived region"); | |
| } | |
| } | |
| virtual void do_oop(oop* p) { | |
| oop v = *p; | |
| if(v != nullptr) { | |
| uintptr_t u = cast_from_oop<uintptr_t>(v); | |
| ArchiveHeapLoader::assert_in_loaded_heap(u); | |
| guarantee(_table->contains(u), "must point to beginning of object in loaded archived region"); | |
| } | |
| } | |
| }; | |
| void ArchiveHeapLoader::finish_initialization() { | |
| if (is_loaded()) { | |
| // These operations are needed only when the heap is loaded (not mapped). | |
| finish_loaded_heap(); | |
| if (VerifyArchivedFields > 0) { | |
| verify_loaded_heap(); | |
| } | |
| } | |
| if (is_in_use()) { | |
| patch_native_pointers(); | |
| intptr_t bottom = is_loaded() ? _loaded_heap_bottom : _mapped_heap_bottom; | |
| // The heap roots are stored in one or more segments that are laid out consecutively. | |
| // The size of each segment (except for the last one) is max_size_in_{elems,bytes}. | |
| HeapRootSegments segments = FileMapInfo::current_info()->heap_root_segments(); | |
| HeapShared::init_root_segment_sizes(segments.max_size_in_elems()); | |
| intptr_t first_segment_addr = bottom + segments.base_offset(); | |
| for (size_t c = 0; c < segments.count(); c++) { | |
| oop segment_oop = cast_to_oop(first_segment_addr + (c * segments.max_size_in_bytes())); | |
| assert(segment_oop->is_objArray(), "Must be"); | |
| HeapShared::add_root_segment((objArrayOop)segment_oop); | |
| } | |
| } | |
| } | |
| void ArchiveHeapLoader::finish_loaded_heap() { | |
| HeapWord* bottom = (HeapWord*)_loaded_heap_bottom; | |
| HeapWord* top = (HeapWord*)_loaded_heap_top; | |
| MemRegion archive_space = MemRegion(bottom, top); | |
| Universe::heap()->complete_loaded_archive_space(archive_space); | |
| } | |
| void ArchiveHeapLoader::verify_loaded_heap() { | |
| log_info(aot, heap)("Verify all oops and pointers in loaded heap"); | |
| ResourceMark rm; | |
| ResourceHashtable<uintptr_t, bool> table; | |
| VerifyLoadedHeapEmbeddedPointers verifier(&table); | |
| HeapWord* bottom = (HeapWord*)_loaded_heap_bottom; | |
| HeapWord* top = (HeapWord*)_loaded_heap_top; | |
| for (HeapWord* p = bottom; p < top; ) { | |
| oop o = cast_to_oop(p); | |
| table.put(cast_from_oop<uintptr_t>(o), true); | |
| p += o->size(); | |
| } | |
| for (HeapWord* p = bottom; p < top; ) { | |
| oop o = cast_to_oop(p); | |
| o->oop_iterate(&verifier); | |
| p += o->size(); | |
| } | |
| } | |
| void ArchiveHeapLoader::fill_failed_loaded_heap() { | |
| assert(_loading_failed, "must be"); | |
| if (_loaded_heap_bottom != 0) { | |
| assert(_loaded_heap_top != 0, "must be"); | |
| HeapWord* bottom = (HeapWord*)_loaded_heap_bottom; | |
| HeapWord* top = (HeapWord*)_loaded_heap_top; | |
| Universe::heap()->fill_with_objects(bottom, top - bottom); | |
| } | |
| } | |
| class PatchNativePointers: public BitMapClosure { | |
| Metadata** _start; | |
| public: | |
| PatchNativePointers(Metadata** start) : _start(start) {} | |
| bool do_bit(size_t offset) { | |
| Metadata** p = _start + offset; | |
| *p = (Metadata*)(address(*p) + MetaspaceShared::relocation_delta()); | |
| return true; | |
| } | |
| }; | |
| void ArchiveHeapLoader::patch_native_pointers() { | |
| if (MetaspaceShared::relocation_delta() == 0) { | |
| return; | |
| } | |
| FileMapRegion* r = FileMapInfo::current_info()->region_at(MetaspaceShared::hp); | |
| if (r->mapped_base() != nullptr && r->has_ptrmap()) { | |
| log_info(aot, heap)("Patching native pointers in heap region"); | |
| BitMapView bm = FileMapInfo::current_info()->ptrmap_view(MetaspaceShared::hp); | |
| PatchNativePointers patcher((Metadata**)r->mapped_base() + FileMapInfo::current_info()->heap_ptrmap_start_pos()); | |
| bm.iterate(&patcher); | |
| } | |
| } | |
| #endif // INCLUDE_CDS_JAVA_HEAP |