diff --git a/src/hotspot/share/cds/aotMapLogger.cpp b/src/hotspot/share/cds/aotMapLogger.cpp new file mode 100644 index 00000000000..712901a71ca --- /dev/null +++ b/src/hotspot/share/cds/aotMapLogger.cpp @@ -0,0 +1,930 @@ +/* + * Copyright (c) 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/aotMapLogger.hpp" +#include "cds/archiveHeapWriter.hpp" +#include "cds/cdsConfig.hpp" +#include "cds/filemap.hpp" +#include "classfile/systemDictionaryShared.hpp" +#include "classfile/vmClasses.hpp" +#include "logging/log.hpp" +#include "logging/logStream.hpp" +#include "memory/metaspaceClosure.hpp" +#include "memory/resourceArea.hpp" +#include "oops/method.hpp" +#include "oops/oop.inline.hpp" +#include "runtime/fieldDescriptor.inline.hpp" +#include "runtime/globals_extension.hpp" +#include "utilities/growableArray.hpp" + +bool AOTMapLogger::_is_logging_at_bootstrap; +bool AOTMapLogger::_is_runtime_logging; +intx AOTMapLogger::_buffer_to_requested_delta; +intx AOTMapLogger::_requested_to_mapped_metadata_delta; +size_t AOTMapLogger::_num_root_segments; +size_t AOTMapLogger::_num_obj_arrays_logged; +GrowableArrayCHeap* AOTMapLogger::_roots; +ArchiveHeapInfo* AOTMapLogger::_dumptime_heap_info; + +class AOTMapLogger::RequestedMetadataAddr { + address _raw_addr; + +public: + RequestedMetadataAddr(address raw_addr) : _raw_addr(raw_addr) {} + + address raw_addr() const { return _raw_addr; } + + Klass* to_real_klass() const { + if (_raw_addr == nullptr) { + return nullptr; + } + + if (_is_runtime_logging) { + return (Klass*)(_raw_addr + _requested_to_mapped_metadata_delta); + } else { + ArchiveBuilder* builder = ArchiveBuilder::current(); + address buffered_addr = builder->requested_to_buffered(_raw_addr); + address klass = builder->get_source_addr(buffered_addr); + return (Klass*)klass; + } + } +}; // AOTMapLogger::RequestedMetadataAddr + +void AOTMapLogger::ergo_initialize() { + if (!CDSConfig::is_dumping_archive() && CDSConfig::is_using_archive() && log_is_enabled(Info, aot, map)) { + _is_logging_at_bootstrap = true; + if (FLAG_IS_DEFAULT(ArchiveRelocationMode)) { + FLAG_SET_ERGO(ArchiveRelocationMode, 0); + } else if (ArchiveRelocationMode != 0) { + log_warning(aot, map)("Addresses in the AOT map may be incorrect for -XX:ArchiveRelocationMode=%d.", ArchiveRelocationMode); + } + } +} + +void AOTMapLogger::dumptime_log(ArchiveBuilder* builder, FileMapInfo* mapinfo, + ArchiveHeapInfo* heap_info, + char* bitmap, size_t bitmap_size_in_bytes) { + _is_runtime_logging = false; + _buffer_to_requested_delta = ArchiveBuilder::current()->buffer_to_requested_delta(); + _num_root_segments = mapinfo->heap_root_segments().count(); + _dumptime_heap_info = heap_info; + + log_file_header(mapinfo); + + DumpRegion* rw_region = &builder->_rw_region; + DumpRegion* ro_region = &builder->_ro_region; + + dumptime_log_metaspace_region("rw region", rw_region, &builder->_rw_src_objs); + dumptime_log_metaspace_region("ro region", ro_region, &builder->_ro_src_objs); + + address bitmap_end = address(bitmap + bitmap_size_in_bytes); + log_region_range("bitmap", address(bitmap), bitmap_end, nullptr); + log_as_hex((address)bitmap, bitmap_end, nullptr); + +#if INCLUDE_CDS_JAVA_HEAP + if (heap_info->is_used()) { + dumptime_log_heap_region(heap_info); + } +#endif + + log_info(aot, map)("[End of AOT cache map]"); +} + +// This class is used to find the location and type of all the +// archived metaspace objects. +class AOTMapLogger::RuntimeGatherArchivedMetaspaceObjs : public UniqueMetaspaceClosure { + GrowableArrayCHeap _objs; + + static int compare_objs_by_addr(ArchivedObjInfo* a, ArchivedObjInfo* b) { + intx diff = a->_src_addr - b->_src_addr; + if (diff < 0) { + return -1; + } else if (diff == 0) { + return 0; + } else { + return 1; + } + } + +public: + GrowableArrayCHeap* objs() { return &_objs; } + + virtual bool do_unique_ref(Ref* ref, bool read_only) { + ArchivedObjInfo info; + info._src_addr = ref->obj(); + info._buffered_addr = ref->obj(); + info._requested_addr = ref->obj(); + info._bytes = ref->size() * BytesPerWord; + info._type = ref->msotype(); + _objs.append(info); + + return true; // keep iterating + } + + void finish() { + UniqueMetaspaceClosure::finish(); + _objs.sort(compare_objs_by_addr); + } +}; // AOTMapLogger::RuntimeGatherArchivedMetaspaceObjs + +void AOTMapLogger::runtime_log(FileMapInfo* static_mapinfo, FileMapInfo* dynamic_mapinfo) { + _is_runtime_logging = true; + _requested_to_mapped_metadata_delta = static_mapinfo->relocation_delta(); + + ResourceMark rm; + RuntimeGatherArchivedMetaspaceObjs gatherer; + + if (log_is_enabled(Debug, aot, map)) { + // The metaspace objects in the AOT cache are stored as a stream of bytes. For space + // saving, we don't store a complete index that tells us where one object ends and + // another object starts. There's also no type information. + // + // However, we can rebuild our index by iterating over all the objects using + // MetaspaceClosure, starting from the dictionary of Klasses in SystemDictionaryShared. + GrowableArray klasses; + SystemDictionaryShared::get_all_archived_classes(/*is_static*/true, &klasses); + if (dynamic_mapinfo != nullptr) { + SystemDictionaryShared::get_all_archived_classes(/*is_static*/false, &klasses); + } + + for (int i = 0; i < klasses.length(); i++) { + gatherer.push(klasses.adr_at(i)); + } + gatherer.finish(); + } + + runtime_log(static_mapinfo, gatherer.objs()); + if (dynamic_mapinfo != nullptr) { + runtime_log(dynamic_mapinfo, gatherer.objs()); + } +} + +void AOTMapLogger::runtime_log(FileMapInfo* mapinfo, GrowableArrayCHeap* objs) { + log_file_header(mapinfo); + + runtime_log_metaspace_regions(mapinfo, objs); + +#if INCLUDE_CDS_JAVA_HEAP + if (mapinfo->has_heap_region() && CDSConfig::is_loading_heap()) { + _num_root_segments = mapinfo->heap_root_segments().count(); + runtime_log_heap_region(mapinfo); + } +#endif + + log_info(aot, map)("[End of map]"); +} + +void AOTMapLogger::dumptime_log_metaspace_region(const char* name, DumpRegion* region, + const ArchiveBuilder::SourceObjList* src_objs) { + address region_base = address(region->base()); + address region_top = address(region->top()); + log_region_range(name, region_base, region_top, region_base + _buffer_to_requested_delta); + if (log_is_enabled(Debug, aot, map)) { + GrowableArrayCHeap objs; + for (int i = 0; i < src_objs->objs()->length(); i++) { + ArchiveBuilder::SourceObjInfo* src_info = src_objs->at(i); + ArchivedObjInfo info; + info._src_addr = src_info->source_addr(); + info._buffered_addr = src_info->buffered_addr(); + info._requested_addr = info._buffered_addr + _buffer_to_requested_delta; + info._bytes = src_info->size_in_bytes(); + info._type = src_info->msotype(); + objs.append(info); + } + + log_metaspace_objects_impl(address(region->base()), address(region->end()), &objs, 0, objs.length()); + } +} + +void AOTMapLogger::runtime_log_metaspace_regions(FileMapInfo* mapinfo, GrowableArrayCHeap* objs) { + FileMapRegion* rw = mapinfo->region_at(MetaspaceShared::rw); + FileMapRegion* ro = mapinfo->region_at(MetaspaceShared::ro); + + address rw_base = address(rw->mapped_base()); + address rw_end = address(rw->mapped_end()); + address ro_base = address(ro->mapped_base()); + address ro_end = address(ro->mapped_end()); + + int first_rw_index = -1; + int first_ro_index = -1; + int last_ro_index = -1; + + if (log_is_enabled(Debug, aot, map)) { + int i = 0; + for (; i < objs->length(); i++) { + address p = objs->at(i)._src_addr; + if (p < rw_base) { + // We are printing the dynamic archive but found an object in the static archive + precond(!mapinfo->is_static()); + continue; + } + if (first_rw_index < 0) { + first_rw_index = i; + continue; + } + if (p < ro_base) { + continue; + } + if (first_ro_index < 0) { + first_ro_index = i; + continue; + } + if (p < ro_end) { + continue; + } else { + last_ro_index = i; + break; + } + } + } + + if (last_ro_index < 0) { + last_ro_index = objs->length(); + } + + log_region_range("rw", rw_base, rw_end, rw_base - _requested_to_mapped_metadata_delta); + if (log_is_enabled(Debug, aot, map)) { + log_metaspace_objects_impl(rw_base, rw_end, objs, first_rw_index, first_ro_index); + } + + log_region_range("ro", ro_base, ro_end, ro_base - _requested_to_mapped_metadata_delta); + if (log_is_enabled(Debug, aot, map)) { + log_metaspace_objects_impl(ro_base, ro_end, objs, first_ro_index, last_ro_index); + } +} + +void AOTMapLogger::log_file_header(FileMapInfo* mapinfo) { + const char* type; + if (mapinfo->is_static()) { + if (CDSConfig::new_aot_flags_used()) { + type = "AOT cache"; + } else { + type = "Static CDS archive"; + } + } else { + type = "Dynamic CDS archive"; + } + + log_info(aot, map)("%s map for %s", type, mapinfo->full_path()); + + address header = address(mapinfo->header()); + address header_end = header + mapinfo->header()->header_size(); + + log_region_range("header", header, header_end, nullptr); + LogStreamHandle(Info, aot, map) lsh; + mapinfo->print(&lsh); + log_as_hex(header, header_end, nullptr); +} + +// Log information about a region, whose address at dump time is [base .. top). At +// runtime, this region will be mapped to requested_base. requested_base is nullptr if this +// region will be mapped at os-selected addresses (such as the bitmap region), or will +// be accessed with os::read (the header). +void AOTMapLogger::log_region_range(const char* name, address base, address top, address requested_base) { + size_t size = top - base; + base = requested_base; + if (requested_base == nullptr) { + top = (address)size; + } else { + top = requested_base + size; + } + log_info(aot, map)("[%-18s " PTR_FORMAT " - " PTR_FORMAT " %9zu bytes]", + name, p2i(base), p2i(top), size); +} + +#define _LOG_PREFIX PTR_FORMAT ": @@ %-17s %d" + +void AOTMapLogger::log_metaspace_objects_impl(address region_base, address region_end, GrowableArrayCHeap* objs, + int start_idx, int end_idx) { + address last_obj_base = region_base; + address last_obj_end = region_base; + Thread* current = Thread::current(); + + for (int i = start_idx; i < end_idx; i++) { + ArchivedObjInfo& info = objs->at(i); + address src = info._src_addr; + address buffered_addr = info._buffered_addr; + address requested_addr = info._requested_addr; + int bytes = info._bytes; + MetaspaceObj::Type type = info._type; + const char* type_name = MetaspaceObj::type_name(type); + + log_as_hex(last_obj_base, buffered_addr, last_obj_base + _buffer_to_requested_delta); + + switch (type) { + case MetaspaceObj::ClassType: + log_klass((Klass*)src, requested_addr, type_name, bytes, current); + break; + case MetaspaceObj::ConstantPoolType: + log_constant_pool((ConstantPool*)src, requested_addr, type_name, bytes, current); + break; + case MetaspaceObj::ConstantPoolCacheType: + log_constant_pool_cache((ConstantPoolCache*)src, requested_addr, type_name, bytes, current); + break; + case MetaspaceObj::ConstMethodType: + log_const_method((ConstMethod*)src, requested_addr, type_name, bytes, current); + break; + case MetaspaceObj::MethodType: + log_method((Method*)src, requested_addr, type_name, bytes, current); + break; + case MetaspaceObj::SymbolType: + log_symbol((Symbol*)src, requested_addr, type_name, bytes, current); + break; + default: + log_debug(aot, map)(_LOG_PREFIX, p2i(requested_addr), type_name, bytes); + break; + } + + last_obj_base = buffered_addr; + last_obj_end = buffered_addr + bytes; + } + + log_as_hex(last_obj_base, last_obj_end, last_obj_base + _buffer_to_requested_delta); + if (last_obj_end < region_end) { + log_debug(aot, map)(PTR_FORMAT ": @@ Misc data %zu bytes", + p2i(last_obj_end + _buffer_to_requested_delta), + size_t(region_end - last_obj_end)); + log_as_hex(last_obj_end, region_end, last_obj_end + _buffer_to_requested_delta); + } +} + +void AOTMapLogger::log_constant_pool(ConstantPool* cp, address requested_addr, + const char* type_name, int bytes, Thread* current) { + ResourceMark rm(current); + log_debug(aot, map)(_LOG_PREFIX " %s", p2i(requested_addr), type_name, bytes, + cp->pool_holder()->external_name()); +} + +void AOTMapLogger::log_constant_pool_cache(ConstantPoolCache* cpc, address requested_addr, + const char* type_name, int bytes, Thread* current) { + ResourceMark rm(current); + log_debug(aot, map)(_LOG_PREFIX " %s", p2i(requested_addr), type_name, bytes, + cpc->constant_pool()->pool_holder()->external_name()); +} + +void AOTMapLogger::log_const_method(ConstMethod* cm, address requested_addr, const char* type_name, + int bytes, Thread* current) { + ResourceMark rm(current); + log_debug(aot, map)(_LOG_PREFIX " %s", p2i(requested_addr), type_name, bytes, cm->method()->external_name()); +} + +void AOTMapLogger::log_klass(Klass* k, address requested_addr, const char* type_name, + int bytes, Thread* current) { + ResourceMark rm(current); + log_debug(aot, map)(_LOG_PREFIX " %s", p2i(requested_addr), type_name, bytes, k->external_name()); +} + +void AOTMapLogger::log_method(Method* m, address requested_addr, const char* type_name, + int bytes, Thread* current) { + ResourceMark rm(current); + log_debug(aot, map)(_LOG_PREFIX " %s", p2i(requested_addr), type_name, bytes, m->external_name()); +} + +void AOTMapLogger::log_symbol(Symbol* s, address requested_addr, const char* type_name, + int bytes, Thread* current) { + ResourceMark rm(current); + log_debug(aot, map)(_LOG_PREFIX " %s", p2i(requested_addr), type_name, bytes, + s->as_quoted_ascii()); +} + +#undef _LOG_PREFIX + +// Log all the data [base...top). Pretend that the base address +// will be mapped to requested_base at run-time. +void AOTMapLogger::log_as_hex(address base, address top, address requested_base, bool is_heap) { + assert(top >= base, "must be"); + + LogStreamHandle(Trace, aot, map) lsh; + if (lsh.is_enabled()) { + int unitsize = sizeof(address); + if (is_heap && UseCompressedOops) { + // This makes the compressed oop pointers easier to read, but + // longs and doubles will be split into two words. + unitsize = sizeof(narrowOop); + } + os::print_hex_dump(&lsh, base, top, unitsize, /* print_ascii=*/true, /* bytes_per_line=*/32, requested_base); + } +} + +#if INCLUDE_CDS_JAVA_HEAP +// FakeOop (and subclasses FakeMirror, FakeString, FakeObjArray, FakeTypeArray) are used to traverse +// and print the (image of) heap objects stored in the AOT cache. These objects are different than regular oops: +// - They do not reside inside the range of the heap. +// - For +UseCompressedOops: pointers may use a different narrowOop encoding: see FakeOop::read_oop_at(narrowOop*) +// - For -UseCompressedOops: pointers are not direct: see FakeOop::read_oop_at(oop*) +// +// Hence, in general, we cannot use regular oop API (such as oopDesc::obj_field()) on these objects. There +// are a few rare case where regular oop API work, but these are all guarded with the raw_oop() method and +// should be used with care. +class AOTMapLogger::FakeOop { + static int _requested_shift; + static intx _buffer_to_requested_delta; + static address _buffer_start; + static address _buffer_end; + static uint64_t _buffer_start_narrow_oop; // The encoded narrow oop for the objects at _buffer_start + + address _buffer_addr; + + static void assert_range(address buffer_addr) { + assert(_buffer_start <= buffer_addr && buffer_addr < _buffer_end, "range check"); + } + + address* field_addr(int field_offset) { + return (address*)(_buffer_addr + field_offset); + } + +protected: + RequestedMetadataAddr metadata_field(int field_offset) { + return RequestedMetadataAddr(*(address*)(field_addr(field_offset))); + } + + // Return an "oop" pointer so we can use APIs that accept regular oops. This + // must be used with care, as only a limited number of APIs can work with oops that + // live outside of the range of the heap. + oop raw_oop() { return cast_to_oop(_buffer_addr); } + +public: + static void init_globals(address requested_base, address requested_start, int requested_shift, + address buffer_start, address buffer_end) { + _requested_shift = requested_shift; + _buffer_to_requested_delta = requested_start - buffer_start; + _buffer_start = buffer_start; + _buffer_end = buffer_end; + + precond(requested_start >= requested_base); + if (UseCompressedOops) { + _buffer_start_narrow_oop = (uint64_t)(pointer_delta(requested_start, requested_base, 1)) >> _requested_shift; + assert(_buffer_start_narrow_oop < 0xffffffff, "sanity"); + } else { + _buffer_start_narrow_oop = 0xdeadbeed; + } + } + + FakeOop() : _buffer_addr(nullptr) {} + + FakeOop(address buffer_addr) : _buffer_addr(buffer_addr) { + if (_buffer_addr != nullptr) { + assert_range(_buffer_addr); + } + } + + FakeMirror& as_mirror(); + FakeObjArray& as_obj_array(); + FakeString& as_string(); + FakeTypeArray& as_type_array(); + + RequestedMetadataAddr klass() { + address rk = (address)real_klass(); + if (_is_runtime_logging) { + return RequestedMetadataAddr(rk - _requested_to_mapped_metadata_delta); + } else { + ArchiveBuilder* builder = ArchiveBuilder::current(); + return builder->to_requested(builder->get_buffered_addr(rk)); + } + } + + Klass* real_klass() { + assert(UseCompressedClassPointers, "heap archiving requires UseCompressedClassPointers"); + if (_is_runtime_logging) { + return raw_oop()->klass(); + } else { + return ArchiveHeapWriter::real_klass_of_buffered_oop(_buffer_addr); + } + } + + // in heap words + size_t size() { + if (_is_runtime_logging) { + return raw_oop()->size_given_klass(real_klass()); + } else { + return ArchiveHeapWriter::size_of_buffered_oop(_buffer_addr); + } + } + + bool is_array() { return real_klass()->is_array_klass(); } + bool is_null() { return _buffer_addr == nullptr; } + + int array_length() { + precond(is_array()); + return arrayOop(raw_oop())->length(); + } + + address requested_addr() { + return _buffer_addr + _buffer_to_requested_delta; + } + + uint32_t as_narrow_oop_value() { + precond(UseCompressedOops); + if (_buffer_addr == nullptr) { + return 0; + } + uint64_t pd = (uint64_t)(pointer_delta(_buffer_addr, _buffer_start, 1)); + return checked_cast(_buffer_start_narrow_oop + (pd >> _requested_shift)); + } + + FakeOop read_oop_at(narrowOop* addr) { // +UseCompressedOops + uint64_t n = (uint64_t)(*addr); + if (n == 0) { + return FakeOop(nullptr); + } else { + precond(n >= _buffer_start_narrow_oop); + address value = _buffer_start + ((n - _buffer_start_narrow_oop) << _requested_shift); + return FakeOop(value); + } + } + + FakeOop read_oop_at(oop* addr) { // -UseCompressedOops + address requested_value = cast_from_oop
(*addr); + if (requested_value == nullptr) { + return FakeOop(nullptr); + } else { + return FakeOop(requested_value - _buffer_to_requested_delta); + } + } + + FakeOop obj_field(int field_offset) { + if (UseCompressedOops) { + return read_oop_at(raw_oop()->field_addr(field_offset)); + } else { + return read_oop_at(raw_oop()->field_addr(field_offset)); + } + } + + void print_non_oop_field(outputStream* st, fieldDescriptor* fd) { + // fd->print_on_for() works for non-oop fields in fake oops + precond(fd->field_type() != T_ARRAY && fd->field_type() != T_OBJECT); + fd->print_on_for(st, raw_oop()); + } +}; // AOTMapLogger::FakeOop + +class AOTMapLogger::FakeMirror : public AOTMapLogger::FakeOop { +public: + void print_class_signature_on(outputStream* st); + + Klass* real_mirrored_klass() { + RequestedMetadataAddr mirrored_klass = metadata_field(java_lang_Class::klass_offset()); + return mirrored_klass.to_real_klass(); + } + + int static_oop_field_count() { + return java_lang_Class::static_oop_field_count(raw_oop()); + } +}; // AOTMapLogger::FakeMirror + +class AOTMapLogger::FakeObjArray : public AOTMapLogger::FakeOop { + objArrayOop raw_objArrayOop() { + return (objArrayOop)raw_oop(); + } + +public: + int length() { + return raw_objArrayOop()->length(); + } + FakeOop obj_at(int i) { + if (UseCompressedOops) { + return read_oop_at(raw_objArrayOop()->obj_at_addr(i)); + } else { + return read_oop_at(raw_objArrayOop()->obj_at_addr(i)); + } + } +}; // AOTMapLogger::FakeObjArray + +class AOTMapLogger::FakeString : public AOTMapLogger::FakeOop { +public: + bool is_latin1() { + jbyte coder = raw_oop()->byte_field(java_lang_String::coder_offset()); + assert(CompactStrings || coder == java_lang_String::CODER_UTF16, "Must be UTF16 without CompactStrings"); + return coder == java_lang_String::CODER_LATIN1; + } + + FakeTypeArray value(); + + int length(); + void print_on(outputStream* st, int max_length = MaxStringPrintSize); +}; // AOTMapLogger::FakeString + +class AOTMapLogger::FakeTypeArray : public AOTMapLogger::FakeOop { + typeArrayOop raw_typeArrayOop() { + return (typeArrayOop)raw_oop(); + } + +public: + void print_elements_on(outputStream* st) { + TypeArrayKlass::cast(real_klass())->oop_print_elements_on(raw_typeArrayOop(), st); + } + + int length() { return raw_typeArrayOop()->length(); } + jbyte byte_at(int i) { return raw_typeArrayOop()->byte_at(i); } + jchar char_at(int i) { return raw_typeArrayOop()->char_at(i); } +}; // AOTMapLogger::FakeTypeArray + +AOTMapLogger::FakeMirror& AOTMapLogger::FakeOop::as_mirror() { + precond(real_klass() == vmClasses::Class_klass()); + return (FakeMirror&)*this; +} + +AOTMapLogger::FakeObjArray& AOTMapLogger::FakeOop::as_obj_array() { + precond(real_klass()->is_objArray_klass()); + return (FakeObjArray&)*this; +} + +AOTMapLogger::FakeTypeArray& AOTMapLogger::FakeOop::as_type_array() { + precond(real_klass()->is_typeArray_klass()); + return (FakeTypeArray&)*this; +} + +AOTMapLogger::FakeString& AOTMapLogger::FakeOop::as_string() { + precond(real_klass() == vmClasses::String_klass()); + return (FakeString&)*this; +} + +void AOTMapLogger::FakeMirror::print_class_signature_on(outputStream* st) { + ResourceMark rm; + RequestedMetadataAddr requested_klass = metadata_field(java_lang_Class::klass_offset()); + Klass* real_klass = requested_klass.to_real_klass(); + + if (real_klass == nullptr) { + // This is a primitive mirror (Java expressions of int.class, long.class, void.class, etc); + RequestedMetadataAddr requested_array_klass = metadata_field(java_lang_Class::array_klass_offset()); + Klass* real_array_klass = requested_array_klass.to_real_klass(); + if (real_array_klass == nullptr) { + st->print(" V"); // The special mirror for void.class that doesn't have any representation in C++ + } else { + precond(real_array_klass->is_typeArray_klass()); + st->print(" %c", real_array_klass->name()->char_at(1)); + } + } else { + const char* class_name = real_klass->name()->as_C_string(); + if (real_klass->is_instance_klass()) { + st->print(" L%s;", class_name); + } else { + st->print(" %s", class_name); + } + if (real_klass->has_aot_initialized_mirror()) { + st->print(" (aot-inited)"); + } + } +} + +AOTMapLogger::FakeTypeArray AOTMapLogger::FakeString::value() { + return obj_field(java_lang_String::value_offset()).as_type_array(); +} + +int AOTMapLogger::FakeString::length() { + FakeTypeArray v = value(); + if (v.is_null()) { + return 0; + } + int arr_length = v.length(); + if (!is_latin1()) { + assert((arr_length & 1) == 0, "should be even for UTF16 string"); + arr_length >>= 1; // convert number of bytes to number of elements + } + return arr_length; +} + +void AOTMapLogger::FakeString::print_on(outputStream* st, int max_length) { + FakeTypeArray v = value(); + int length = this->length(); + bool is_latin1 = this->is_latin1(); + bool abridge = length > max_length; + + st->print("\""); + for (int index = 0; index < length; index++) { + // If we need to abridge and we've printed half the allowed characters + // then jump to the tail of the string. + if (abridge && index >= max_length / 2) { + st->print(" ... (%d characters ommitted) ... ", length - 2 * (max_length / 2)); + index = length - (max_length / 2); + abridge = false; // only do this once + } + jchar c = (!is_latin1) ? v.char_at(index) : + ((jchar) v.byte_at(index)) & 0xff; + if (c < ' ') { + st->print("\\x%02X", c); // print control characters e.g. \x0A + } else { + st->print("%c", c); + } + } + st->print("\""); + + if (length > max_length) { + st->print(" (abridged) "); + } +} + +class AOTMapLogger::ArchivedFieldPrinter : public FieldClosure { + FakeOop _fake_oop; + outputStream* _st; +public: + ArchivedFieldPrinter(FakeOop fake_oop, outputStream* st) : _fake_oop(fake_oop), _st(st) {} + + void do_field(fieldDescriptor* fd) { + _st->print(" - "); + BasicType ft = fd->field_type(); + switch (ft) { + case T_ARRAY: + case T_OBJECT: + { + fd->print_on(_st); // print just the name and offset + FakeOop field_value = _fake_oop.obj_field(fd->offset()); + print_oop_info_cr(_st, field_value); + } + break; + default: + _fake_oop.print_non_oop_field(_st, fd); // name, offset, value + _st->cr(); + } + } +}; // AOTMapLogger::ArchivedFieldPrinter + +int AOTMapLogger::FakeOop::_requested_shift; +intx AOTMapLogger::FakeOop::_buffer_to_requested_delta; +address AOTMapLogger::FakeOop::_buffer_start; +address AOTMapLogger::FakeOop::_buffer_end; +uint64_t AOTMapLogger::FakeOop::_buffer_start_narrow_oop; + +void AOTMapLogger::dumptime_log_heap_region(ArchiveHeapInfo* heap_info) { + MemRegion r = heap_info->buffer_region(); + address buffer_start = address(r.start()); // start of the current oop inside the buffer + address buffer_end = address(r.end()); + + address requested_base = UseCompressedOops ? (address)CompressedOops::base() : (address)ArchiveHeapWriter::NOCOOPS_REQUESTED_BASE; + address requested_start = UseCompressedOops ? ArchiveHeapWriter::buffered_addr_to_requested_addr(buffer_start) : requested_base; + int requested_shift = CompressedOops::shift(); + + FakeOop::init_globals(requested_base, requested_start, requested_shift, buffer_start, buffer_end); + + log_region_range("heap", buffer_start, buffer_end, requested_start); + log_oops(buffer_start, buffer_end); +} + +void AOTMapLogger::runtime_log_heap_region(FileMapInfo* mapinfo) { + ResourceMark rm; + int heap_region_index = MetaspaceShared::hp; + FileMapRegion* r = mapinfo->region_at(heap_region_index); + size_t alignment = ObjectAlignmentInBytes; + + // Allocate a buffer and read the image of the archived heap region. This buffer is outside + // of the real Java heap, so we must use FakeOop to access the contents of the archived heap objects. + char* buffer = resource_allocate_bytes(r->used() + alignment); + address buffer_start = (address)align_up(buffer, alignment); + address buffer_end = buffer_start + r->used(); + if (!mapinfo->read_region(heap_region_index, (char*)buffer_start, r->used(), /* do_commit = */ false)) { + log_error(aot)("Cannot read heap region; AOT map logging of heap objects failed"); + return; + } + + address requested_base = UseCompressedOops ? (address)mapinfo->narrow_oop_base() : mapinfo->heap_region_requested_address(); + address requested_start = requested_base + r->mapping_offset(); + int requested_shift = mapinfo->narrow_oop_shift(); + + FakeOop::init_globals(requested_base, requested_start, requested_shift, buffer_start, buffer_end); + + log_region_range("heap", buffer_start, buffer_end, requested_start); + log_oops(buffer_start, buffer_end); +} + +void AOTMapLogger::log_oops(address buffer_start, address buffer_end) { + LogStreamHandle(Debug, aot, map) st; + if (!st.is_enabled()) { + return; + } + + _roots = new GrowableArrayCHeap(); + _num_obj_arrays_logged = 0; + + for (address fop = buffer_start; fop < buffer_end; ) { + FakeOop fake_oop(fop); + st.print(PTR_FORMAT ": @@ Object ", p2i(fake_oop.requested_addr())); + print_oop_info_cr(&st, fake_oop, /*print_requested_addr=*/false); + + LogStreamHandle(Trace, aot, map, oops) trace_st; + if (trace_st.is_enabled()) { + print_oop_details(fake_oop, &trace_st); + } + + address next_fop = fop + fake_oop.size() * BytesPerWord; + log_as_hex(fop, next_fop, fake_oop.requested_addr(), /*is_heap=*/true); + + fop = next_fop; + } + + delete _roots; +} + +void AOTMapLogger::print_oop_info_cr(outputStream* st, FakeOop fake_oop, bool print_requested_addr) { + if (fake_oop.is_null()) { + st->print_cr("null"); + } else { + ResourceMark rm; + Klass* real_klass = fake_oop.real_klass(); + address requested_addr = fake_oop.requested_addr(); + if (print_requested_addr) { + st->print(PTR_FORMAT " ", p2i(requested_addr)); + } + if (UseCompressedOops) { + st->print("(0x%08x) ", fake_oop.as_narrow_oop_value()); + } + if (fake_oop.is_array()) { + int array_len = fake_oop.array_length(); + st->print_cr("%s length: %d", real_klass->external_name(), array_len); + } else { + st->print("%s", real_klass->external_name()); + + if (real_klass == vmClasses::String_klass()) { + st->print(" "); + fake_oop.as_string().print_on(st); + } else if (real_klass == vmClasses::Class_klass()) { + fake_oop.as_mirror().print_class_signature_on(st); + } + + st->cr(); + } + } +} + +// Print the fields of instanceOops, or the elements of arrayOops +void AOTMapLogger::print_oop_details(FakeOop fake_oop, outputStream* st) { + Klass* real_klass = fake_oop.real_klass(); + + st->print(" - klass: "); + real_klass->print_value_on(st); + st->print(" " PTR_FORMAT, p2i(fake_oop.klass().raw_addr())); + st->cr(); + + if (real_klass->is_typeArray_klass()) { + fake_oop.as_type_array().print_elements_on(st); + } else if (real_klass->is_objArray_klass()) { + FakeObjArray fake_obj_array = fake_oop.as_obj_array(); + bool is_logging_root_segment = _num_obj_arrays_logged < _num_root_segments; + + for (int i = 0; i < fake_obj_array.length(); i++) { + FakeOop elm = fake_obj_array.obj_at(i); + if (is_logging_root_segment) { + st->print(" root[%4d]: ", _roots->length()); + _roots->append(elm); + } else { + st->print(" -%4d: ", i); + } + print_oop_info_cr(st, elm); + } + _num_obj_arrays_logged ++; + } else { + st->print_cr(" - fields (%zu words):", fake_oop.size()); + + ArchivedFieldPrinter print_field(fake_oop, st); + InstanceKlass::cast(real_klass)->print_nonstatic_fields(&print_field); + + if (real_klass == vmClasses::Class_klass()) { + FakeMirror fake_mirror = fake_oop.as_mirror(); + + st->print(" - signature: "); + fake_mirror.print_class_signature_on(st); + st->cr(); + + Klass* real_mirrored_klass = fake_mirror.real_mirrored_klass(); + if (real_mirrored_klass != nullptr && real_mirrored_klass->is_instance_klass()) { + InstanceKlass* real_mirrored_ik = InstanceKlass::cast(real_mirrored_klass); + + ConstantPoolCache* cp_cache = real_mirrored_ik->constants()->cache(); + if (!_is_runtime_logging) { + cp_cache = ArchiveBuilder::current()->get_buffered_addr(cp_cache); + } + int rr_root_index = cp_cache->archived_references_index(); + st->print(" - resolved_references: "); + if (rr_root_index >= 0) { + FakeOop resolved_references = _roots->at(rr_root_index); + print_oop_info_cr(st, resolved_references); + } else { + st->print("null"); + } + + st->print_cr("- ---- static fields (%d):", fake_mirror.static_oop_field_count()); + real_mirrored_ik->do_local_static_fields(&print_field); + } + } + } +} +#endif // INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/cds/aotMapLogger.hpp b/src/hotspot/share/cds/aotMapLogger.hpp new file mode 100644 index 00000000000..9cd67fb7ff6 --- /dev/null +++ b/src/hotspot/share/cds/aotMapLogger.hpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 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. + * + */ + +#ifndef SHARE_CDS_AOTMAPLOGGER_HPP +#define SHARE_CDS_AOTMAPLOGGER_HPP + +#include "cds/archiveBuilder.hpp" +#include "memory/allocation.hpp" +#include "memory/allStatic.hpp" +#include "oops/oopsHierarchy.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/growableArray.hpp" + +class ArchiveHeapInfo; +class DumpRegion; +class FileMapInfo; +class outputStream; + +// Write detailed info to a mapfile to analyze contents of the AOT cache/CDS archive. +// -Xlog:aot+map* can be used both when creating an AOT cache, or when using an AOT cache. +// +// Creating cache: +// java -XX:AOTCacheOutput=app.aot -Xlog:aot+map*=trace -cp app.jar App +// +// Using cache: +// java -XX:AOTCache=app.aot -Xlog:aot+map*=trace -cp app.jar App +// +// You can also print the map of a cache without executing the application by using the +// --version flag: +// java -XX:AOTCache=app.aot -Xlog:aot+map*=trace --version +// +// Because the output can be large, it's best to save it to a file +// java -XX:AOTCache=app.aot -Xlog:aot+map*=trace:file=aot.map:none:filesize=0 --version +class AOTMapLogger : AllStatic { + struct ArchivedObjInfo { + address _src_addr; + address _buffered_addr; + address _requested_addr; + int _bytes; + MetaspaceObj::Type _type; + }; + + // FakeOop and subtypes + class FakeOop; + class FakeMirror; + class FakeObjArray; + class FakeString; + class FakeTypeArray; + + class RequestedMetadataAddr; + class RuntimeGatherArchivedMetaspaceObjs; + + static bool _is_logging_at_bootstrap; + static bool _is_runtime_logging; + static size_t _num_root_segments; + static size_t _num_obj_arrays_logged; + static GrowableArrayCHeap* _roots; + static ArchiveHeapInfo* _dumptime_heap_info; + + static intx _buffer_to_requested_delta; + static intx _requested_to_mapped_metadata_delta; + + static void runtime_log(FileMapInfo* mapinfo, GrowableArrayCHeap* objs); + static void runtime_log_metaspace_regions(FileMapInfo* mapinfo, GrowableArrayCHeap* objs); + static void dumptime_log_metaspace_region(const char* name, DumpRegion* region, + const ArchiveBuilder::SourceObjList* src_objs); + + // Common code for dumptime/runtime + static void log_file_header(FileMapInfo* mapinfo); + static void log_region_range(const char* name, address base, address top, address requested_base); + static void log_metaspace_objects_impl(address region_base, address region_end, + GrowableArrayCHeap* objs, int start_idx, int end_idx); + static void log_as_hex(address base, address top, address requested_base, bool is_heap = false); + + // Metaspace object: type-specific logging + static void log_constant_pool(ConstantPool* cp, address requested_addr, const char* type_name, int bytes, Thread* current); + static void log_constant_pool_cache(ConstantPoolCache* cpc, address requested_addr, + const char* type_name, int bytes, Thread* current); + static void log_const_method(ConstMethod* cm, address requested_addr, const char* type_name, int bytes, Thread* current); + static void log_klass(Klass* k, address requested_addr, const char* type_name, int bytes, Thread* current); + static void log_method(Method* m, address requested_addr, const char* type_name, int bytes, Thread* current); + static void log_symbol(Symbol* s, address requested_addr, const char* type_name, int bytes, Thread* current); + + +#if INCLUDE_CDS_JAVA_HEAP + static void dumptime_log_heap_region(ArchiveHeapInfo* heap_info); + static void runtime_log_heap_region(FileMapInfo* mapinfo); + + static void print_oop_info_cr(outputStream* st, FakeOop fake_oop, bool print_requested_addr = true); + static void print_oop_details(FakeOop fake_oop, outputStream* st); + static void log_oops(address buf_start, address buf_end); + class ArchivedFieldPrinter; // to be replaced by ArchivedFieldPrinter2 +#endif + +public: + static void ergo_initialize(); + static bool is_logging_at_bootstrap() { return _is_logging_at_bootstrap; } + + static void dumptime_log(ArchiveBuilder* builder, FileMapInfo* mapinfo, + ArchiveHeapInfo* heap_info, + char* bitmap, size_t bitmap_size_in_bytes); + static void runtime_log(FileMapInfo* static_mapinfo, FileMapInfo* dynamic_mapinfo); +}; + +#endif // SHARE_CDS_AOTMAPLOGGER_HPP diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index 1d054561c76..1c807776e3c 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -26,6 +26,7 @@ #include "cds/aotClassLinker.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotLogging.hpp" +#include "cds/aotMapLogger.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveHeapWriter.hpp" #include "cds/archiveUtils.hpp" @@ -46,7 +47,6 @@ #include "interpreter/abstractInterpreter.hpp" #include "jvm.h" #include "logging/log.hpp" -#include "logging/logStream.hpp" #include "memory/allStatic.hpp" #include "memory/memoryReserver.hpp" #include "memory/memRegion.hpp" @@ -60,7 +60,6 @@ #include "oops/oopHandle.inline.hpp" #include "oops/trainingData.hpp" #include "runtime/arguments.hpp" -#include "runtime/fieldDescriptor.inline.hpp" #include "runtime/globals_extension.hpp" #include "runtime/javaThread.hpp" #include "runtime/sharedRuntime.hpp" @@ -1188,424 +1187,6 @@ void ArchiveBuilder::relocate_to_requested() { } } -// Write detailed info to a mapfile to analyze contents of the archive. -// static dump: -// java -Xshare:dump -Xlog:cds+map=trace:file=cds.map:none:filesize=0 -// dynamic dump: -// java -cp MyApp.jar -XX:ArchiveClassesAtExit=MyApp.jsa \ -// -Xlog:cds+map=trace:file=cds.map:none:filesize=0 MyApp -// -// We need to do some address translation because the buffers used at dump time may be mapped to -// a different location at runtime. At dump time, the buffers may be at arbitrary locations -// picked by the OS. At runtime, we try to map at a fixed location (SharedBaseAddress). For -// consistency, we log everything using runtime addresses. -class ArchiveBuilder::CDSMapLogger : AllStatic { - static intx buffer_to_runtime_delta() { - // Translate the buffers used by the RW/RO regions to their eventual (requested) locations - // at runtime. - return ArchiveBuilder::current()->buffer_to_requested_delta(); - } - - // rw/ro regions only - static void log_metaspace_region(const char* name, DumpRegion* region, - const ArchiveBuilder::SourceObjList* src_objs) { - address region_base = address(region->base()); - address region_top = address(region->top()); - log_region(name, region_base, region_top, region_base + buffer_to_runtime_delta()); - log_metaspace_objects(region, src_objs); - } - -#define _LOG_PREFIX PTR_FORMAT ": @@ %-17s %d" - - static void log_klass(Klass* k, address runtime_dest, const char* type_name, int bytes, Thread* current) { - ResourceMark rm(current); - log_debug(aot, map)(_LOG_PREFIX " %s", - p2i(runtime_dest), type_name, bytes, k->external_name()); - } - static void log_method(Method* m, address runtime_dest, const char* type_name, int bytes, Thread* current) { - ResourceMark rm(current); - log_debug(aot, map)(_LOG_PREFIX " %s", - p2i(runtime_dest), type_name, bytes, m->external_name()); - } - - // rw/ro regions only - static void log_metaspace_objects(DumpRegion* region, const ArchiveBuilder::SourceObjList* src_objs) { - address last_obj_base = address(region->base()); - address last_obj_end = address(region->base()); - address region_end = address(region->end()); - Thread* current = Thread::current(); - for (int i = 0; i < src_objs->objs()->length(); i++) { - SourceObjInfo* src_info = src_objs->at(i); - address src = src_info->source_addr(); - address dest = src_info->buffered_addr(); - log_as_hex(last_obj_base, dest, last_obj_base + buffer_to_runtime_delta()); - address runtime_dest = dest + buffer_to_runtime_delta(); - int bytes = src_info->size_in_bytes(); - - MetaspaceObj::Type type = src_info->msotype(); - const char* type_name = MetaspaceObj::type_name(type); - - switch (type) { - case MetaspaceObj::ClassType: - log_klass((Klass*)src, runtime_dest, type_name, bytes, current); - break; - case MetaspaceObj::ConstantPoolType: - log_klass(((ConstantPool*)src)->pool_holder(), - runtime_dest, type_name, bytes, current); - break; - case MetaspaceObj::ConstantPoolCacheType: - log_klass(((ConstantPoolCache*)src)->constant_pool()->pool_holder(), - runtime_dest, type_name, bytes, current); - break; - case MetaspaceObj::MethodType: - log_method((Method*)src, runtime_dest, type_name, bytes, current); - break; - case MetaspaceObj::ConstMethodType: - log_method(((ConstMethod*)src)->method(), runtime_dest, type_name, bytes, current); - break; - case MetaspaceObj::SymbolType: - { - ResourceMark rm(current); - Symbol* s = (Symbol*)src; - log_debug(aot, map)(_LOG_PREFIX " %s", p2i(runtime_dest), type_name, bytes, - s->as_quoted_ascii()); - } - break; - default: - log_debug(aot, map)(_LOG_PREFIX, p2i(runtime_dest), type_name, bytes); - break; - } - - last_obj_base = dest; - last_obj_end = dest + bytes; - } - - log_as_hex(last_obj_base, last_obj_end, last_obj_base + buffer_to_runtime_delta()); - if (last_obj_end < region_end) { - log_debug(aot, map)(PTR_FORMAT ": @@ Misc data %zu bytes", - p2i(last_obj_end + buffer_to_runtime_delta()), - size_t(region_end - last_obj_end)); - log_as_hex(last_obj_end, region_end, last_obj_end + buffer_to_runtime_delta()); - } - } - -#undef _LOG_PREFIX - - // Log information about a region, whose address at dump time is [base .. top). At - // runtime, this region will be mapped to requested_base. requested_base is nullptr if this - // region will be mapped at os-selected addresses (such as the bitmap region), or will - // be accessed with os::read (the header). - // - // Note: across -Xshare:dump runs, base may be different, but requested_base should - // be the same as the archive contents should be deterministic. - static void log_region(const char* name, address base, address top, address requested_base) { - size_t size = top - base; - base = requested_base; - if (requested_base == nullptr) { - top = (address)size; - } else { - top = requested_base + size; - } - log_info(aot, map)("[%-18s " PTR_FORMAT " - " PTR_FORMAT " %9zu bytes]", - name, p2i(base), p2i(top), size); - } - -#if INCLUDE_CDS_JAVA_HEAP - static void log_heap_region(ArchiveHeapInfo* heap_info) { - MemRegion r = heap_info->buffer_region(); - address start = address(r.start()); // start of the current oop inside the buffer - address end = address(r.end()); - log_region("heap", start, end, ArchiveHeapWriter::buffered_addr_to_requested_addr(start)); - - LogStreamHandle(Info, aot, map) st; - - HeapRootSegments segments = heap_info->heap_root_segments(); - assert(segments.base_offset() == 0, "Sanity"); - - for (size_t seg_idx = 0; seg_idx < segments.count(); seg_idx++) { - address requested_start = ArchiveHeapWriter::buffered_addr_to_requested_addr(start); - st.print_cr(PTR_FORMAT ": Heap roots segment [%d]", - p2i(requested_start), segments.size_in_elems(seg_idx)); - start += segments.size_in_bytes(seg_idx); - } - log_heap_roots(); - - while (start < end) { - size_t byte_size; - oop source_oop = ArchiveHeapWriter::buffered_addr_to_source_obj(start); - address requested_start = ArchiveHeapWriter::buffered_addr_to_requested_addr(start); - st.print(PTR_FORMAT ": @@ Object ", p2i(requested_start)); - - if (source_oop != nullptr) { - // This is a regular oop that got archived. - // Don't print the requested addr again as we have just printed it at the beginning of the line. - // Example: - // 0x00000007ffd27938: @@ Object (0xfffa4f27) java.util.HashMap - print_oop_info_cr(&st, source_oop, /*print_requested_addr=*/false); - byte_size = source_oop->size() * BytesPerWord; - } else if ((byte_size = ArchiveHeapWriter::get_filler_size_at(start)) > 0) { - // We have a filler oop, which also does not exist in BufferOffsetToSourceObjectTable. - // Example: - // 0x00000007ffc3ffd8: @@ Object filler 40 bytes - st.print_cr("filler %zu bytes", byte_size); - } else { - ShouldNotReachHere(); - } - - address oop_end = start + byte_size; - log_as_hex(start, oop_end, requested_start, /*is_heap=*/true); - - if (source_oop != nullptr) { - log_oop_details(heap_info, source_oop, /*buffered_addr=*/start); - } - start = oop_end; - } - } - - // ArchivedFieldPrinter is used to print the fields of archived objects. We can't - // use _source_obj->print_on(), because we want to print the oop fields - // in _source_obj with their requested addresses using print_oop_info_cr(). - class ArchivedFieldPrinter : public FieldClosure { - ArchiveHeapInfo* _heap_info; - outputStream* _st; - oop _source_obj; - address _buffered_addr; - public: - ArchivedFieldPrinter(ArchiveHeapInfo* heap_info, outputStream* st, oop src_obj, address buffered_addr) : - _heap_info(heap_info), _st(st), _source_obj(src_obj), _buffered_addr(buffered_addr) {} - - void do_field(fieldDescriptor* fd) { - _st->print(" - "); - BasicType ft = fd->field_type(); - switch (ft) { - case T_ARRAY: - case T_OBJECT: - { - fd->print_on(_st); // print just the name and offset - oop obj = _source_obj->obj_field(fd->offset()); - if (java_lang_Class::is_instance(obj)) { - obj = HeapShared::scratch_java_mirror(obj); - } - print_oop_info_cr(_st, obj); - } - break; - default: - if (ArchiveHeapWriter::is_marked_as_native_pointer(_heap_info, _source_obj, fd->offset())) { - print_as_native_pointer(fd); - } else { - fd->print_on_for(_st, cast_to_oop(_buffered_addr)); // name, offset, value - _st->cr(); - } - } - } - - void print_as_native_pointer(fieldDescriptor* fd) { - LP64_ONLY(assert(fd->field_type() == T_LONG, "must be")); - NOT_LP64 (assert(fd->field_type() == T_INT, "must be")); - - // We have a field that looks like an integer, but it's actually a pointer to a MetaspaceObj. - address source_native_ptr = (address) - LP64_ONLY(_source_obj->long_field(fd->offset())) - NOT_LP64( _source_obj->int_field (fd->offset())); - ArchiveBuilder* builder = ArchiveBuilder::current(); - - // The value of the native pointer at runtime. - address requested_native_ptr = builder->to_requested(builder->get_buffered_addr(source_native_ptr)); - - // The address of _source_obj at runtime - oop requested_obj = ArchiveHeapWriter::source_obj_to_requested_obj(_source_obj); - // The address of this field in the requested space - assert(requested_obj != nullptr, "Attempting to load field from null oop"); - address requested_field_addr = cast_from_oop
(requested_obj) + fd->offset(); - - fd->print_on(_st); - _st->print_cr(PTR_FORMAT " (marked metadata pointer @" PTR_FORMAT " )", - p2i(requested_native_ptr), p2i(requested_field_addr)); - } - }; - - // Print the fields of instanceOops, or the elements of arrayOops - static void log_oop_details(ArchiveHeapInfo* heap_info, oop source_oop, address buffered_addr) { - LogStreamHandle(Trace, aot, map, oops) st; - if (st.is_enabled()) { - Klass* source_klass = source_oop->klass(); - ArchiveBuilder* builder = ArchiveBuilder::current(); - Klass* requested_klass = builder->to_requested(builder->get_buffered_addr(source_klass)); - - st.print(" - klass: "); - source_klass->print_value_on(&st); - st.print(" " PTR_FORMAT, p2i(requested_klass)); - st.cr(); - - if (source_oop->is_typeArray()) { - TypeArrayKlass::cast(source_klass)->oop_print_elements_on(typeArrayOop(source_oop), &st); - } else if (source_oop->is_objArray()) { - objArrayOop source_obj_array = objArrayOop(source_oop); - for (int i = 0; i < source_obj_array->length(); i++) { - st.print(" -%4d: ", i); - oop obj = source_obj_array->obj_at(i); - if (java_lang_Class::is_instance(obj)) { - obj = HeapShared::scratch_java_mirror(obj); - } - print_oop_info_cr(&st, obj); - } - } else { - st.print_cr(" - fields (%zu words):", source_oop->size()); - ArchivedFieldPrinter print_field(heap_info, &st, source_oop, buffered_addr); - InstanceKlass::cast(source_klass)->print_nonstatic_fields(&print_field); - - if (java_lang_Class::is_instance(source_oop)) { - oop scratch_mirror = source_oop; - st.print(" - signature: "); - print_class_signature_for_mirror(&st, scratch_mirror); - st.cr(); - - Klass* src_klass = java_lang_Class::as_Klass(scratch_mirror); - if (src_klass != nullptr && src_klass->is_instance_klass()) { - oop rr = HeapShared::scratch_resolved_references(InstanceKlass::cast(src_klass)->constants()); - st.print(" - archived_resolved_references: "); - print_oop_info_cr(&st, rr); - - // We need to print the fields in the scratch_mirror, not the original mirror. - // (if a class is not aot-initialized, static fields in its scratch mirror will be cleared). - assert(scratch_mirror == HeapShared::scratch_java_mirror(src_klass->java_mirror()), "sanity"); - st.print_cr("- ---- static fields (%d):", java_lang_Class::static_oop_field_count(scratch_mirror)); - InstanceKlass::cast(src_klass)->do_local_static_fields(&print_field); - } - } - } - } - } - - static void print_class_signature_for_mirror(outputStream* st, oop scratch_mirror) { - assert(java_lang_Class::is_instance(scratch_mirror), "sanity"); - if (java_lang_Class::is_primitive(scratch_mirror)) { - for (int i = T_BOOLEAN; i < T_VOID+1; i++) { - BasicType bt = (BasicType)i; - if (!is_reference_type(bt) && scratch_mirror == HeapShared::scratch_java_mirror(bt)) { - oop orig_mirror = Universe::java_mirror(bt); - java_lang_Class::print_signature(orig_mirror, st); - return; - } - } - ShouldNotReachHere(); - } - java_lang_Class::print_signature(scratch_mirror, st); - } - - static void log_heap_roots() { - LogStreamHandle(Trace, aot, map, oops) st; - if (st.is_enabled()) { - for (int i = 0; i < HeapShared::pending_roots()->length(); i++) { - st.print("roots[%4d]: ", i); - print_oop_info_cr(&st, HeapShared::pending_roots()->at(i)); - } - } - } - - // Example output: - // - The first number is the requested address (if print_requested_addr == true) - // - The second number is the narrowOop version of the requested address (if UseCompressedOops == true) - // 0x00000007ffc7e840 (0xfff8fd08) java.lang.Class Ljava/util/Array; - // 0x00000007ffc000f8 (0xfff8001f) [B length: 11 - static void print_oop_info_cr(outputStream* st, oop source_oop, bool print_requested_addr = true) { - if (source_oop == nullptr) { - st->print_cr("null"); - } else { - ResourceMark rm; - oop requested_obj = ArchiveHeapWriter::source_obj_to_requested_obj(source_oop); - if (print_requested_addr) { - st->print(PTR_FORMAT " ", p2i(requested_obj)); - } - if (UseCompressedOops) { - st->print("(0x%08x) ", CompressedOops::narrow_oop_value(requested_obj)); - } - if (source_oop->is_array()) { - int array_len = arrayOop(source_oop)->length(); - st->print_cr("%s length: %d", source_oop->klass()->external_name(), array_len); - } else { - st->print("%s", source_oop->klass()->external_name()); - - if (java_lang_String::is_instance(source_oop)) { - st->print(" "); - java_lang_String::print(source_oop, st); - } else if (java_lang_Class::is_instance(source_oop)) { - oop scratch_mirror = source_oop; - - st->print(" "); - print_class_signature_for_mirror(st, scratch_mirror); - - Klass* src_klass = java_lang_Class::as_Klass(scratch_mirror); - if (src_klass != nullptr && src_klass->is_instance_klass()) { - InstanceKlass* buffered_klass = - ArchiveBuilder::current()->get_buffered_addr(InstanceKlass::cast(src_klass)); - if (buffered_klass->has_aot_initialized_mirror()) { - st->print(" (aot-inited)"); - } - } - } - st->cr(); - } - } - } -#endif // INCLUDE_CDS_JAVA_HEAP - - // Log all the data [base...top). Pretend that the base address - // will be mapped to requested_base at run-time. - static void log_as_hex(address base, address top, address requested_base, bool is_heap = false) { - assert(top >= base, "must be"); - - LogStreamHandle(Trace, aot, map) lsh; - if (lsh.is_enabled()) { - int unitsize = sizeof(address); - if (is_heap && UseCompressedOops) { - // This makes the compressed oop pointers easier to read, but - // longs and doubles will be split into two words. - unitsize = sizeof(narrowOop); - } - os::print_hex_dump(&lsh, base, top, unitsize, /* print_ascii=*/true, /* bytes_per_line=*/32, requested_base); - } - } - - static void log_header(FileMapInfo* mapinfo) { - LogStreamHandle(Info, aot, map) lsh; - if (lsh.is_enabled()) { - mapinfo->print(&lsh); - } - } - -public: - static void log(ArchiveBuilder* builder, FileMapInfo* mapinfo, - ArchiveHeapInfo* heap_info, - char* bitmap, size_t bitmap_size_in_bytes) { - log_info(aot, map)("%s CDS archive map for %s", CDSConfig::is_dumping_static_archive() ? "Static" : "Dynamic", mapinfo->full_path()); - - address header = address(mapinfo->header()); - address header_end = header + mapinfo->header()->header_size(); - log_region("header", header, header_end, nullptr); - log_header(mapinfo); - log_as_hex(header, header_end, nullptr); - - DumpRegion* rw_region = &builder->_rw_region; - DumpRegion* ro_region = &builder->_ro_region; - - log_metaspace_region("rw region", rw_region, &builder->_rw_src_objs); - log_metaspace_region("ro region", ro_region, &builder->_ro_src_objs); - - address bitmap_end = address(bitmap + bitmap_size_in_bytes); - log_region("bitmap", address(bitmap), bitmap_end, nullptr); - log_as_hex((address)bitmap, bitmap_end, nullptr); - -#if INCLUDE_CDS_JAVA_HEAP - if (heap_info->is_used()) { - log_heap_region(heap_info); - } -#endif - - log_info(aot, map)("[End of CDS archive map]"); - } -}; // end ArchiveBuilder::CDSMapLogger - void ArchiveBuilder::print_stats() { _alloc_stats.print_stats(int(_ro_region.used()), int(_rw_region.used())); } @@ -1645,8 +1226,7 @@ void ArchiveBuilder::write_archive(FileMapInfo* mapinfo, ArchiveHeapInfo* heap_i } if (log_is_enabled(Info, aot, map)) { - CDSMapLogger::log(this, mapinfo, heap_info, - bitmap, bitmap_size_in_bytes); + AOTMapLogger::dumptime_log(this, mapinfo, heap_info, bitmap, bitmap_size_in_bytes); } CDS_JAVA_HEAP_ONLY(HeapShared::destroy_archived_object_cache()); FREE_C_HEAP_ARRAY(char, bitmap); diff --git a/src/hotspot/share/cds/archiveBuilder.hpp b/src/hotspot/share/cds/archiveBuilder.hpp index 39cc1c1eb8c..170e61beba8 100644 --- a/src/hotspot/share/cds/archiveBuilder.hpp +++ b/src/hotspot/share/cds/archiveBuilder.hpp @@ -93,6 +93,8 @@ constexpr size_t SharedSpaceObjectAlignment = Metaspace::min_allocation_alignmen // buffered_address + _buffer_to_requested_delta == requested_address // class ArchiveBuilder : public StackObj { + friend class AOTMapLogger; + protected: DumpRegion* _current_dump_region; address _buffer_bottom; // for writing the contents of rw/ro regions @@ -202,8 +204,6 @@ private: SourceObjInfo* at(int i) const { return objs()->at(i); } }; - class CDSMapLogger; - static const int INITIAL_TABLE_SIZE = 15889; static const int MAX_TABLE_SIZE = 1000000; @@ -316,6 +316,12 @@ public: return (T)(address(obj) + _buffer_to_requested_delta); } + template T requested_to_buffered(T obj) const { + T b = (T)(address(obj) - _buffer_to_requested_delta); + assert(is_in_buffer_space(b), "must be"); + return b; + } + static intx get_buffer_to_requested_delta() { return current()->buffer_to_requested_delta(); } diff --git a/src/hotspot/share/cds/archiveHeapWriter.cpp b/src/hotspot/share/cds/archiveHeapWriter.cpp index 9c55b71a1b2..c7750c70f1b 100644 --- a/src/hotspot/share/cds/archiveHeapWriter.cpp +++ b/src/hotspot/share/cds/archiveHeapWriter.cpp @@ -162,6 +162,44 @@ oop ArchiveHeapWriter::buffered_addr_to_source_obj(address buffered_addr) { } } +Klass* ArchiveHeapWriter::real_klass_of_buffered_oop(address buffered_addr) { + oop p = buffered_addr_to_source_obj(buffered_addr); + if (p != nullptr) { + return p->klass(); + } else if (get_filler_size_at(buffered_addr) > 0) { + return Universe::fillerArrayKlass(); + } else { + // This is one of the root segments + return Universe::objectArrayKlass(); + } +} + +size_t ArchiveHeapWriter::size_of_buffered_oop(address buffered_addr) { + oop p = buffered_addr_to_source_obj(buffered_addr); + if (p != nullptr) { + return p->size(); + } + + size_t nbytes = get_filler_size_at(buffered_addr); + if (nbytes > 0) { + assert((nbytes % BytesPerWord) == 0, "should be aligned"); + return nbytes / BytesPerWord; + } + + address hrs = buffer_bottom(); + for (size_t seg_idx = 0; seg_idx < _heap_root_segments.count(); seg_idx++) { + nbytes = _heap_root_segments.size_in_bytes(seg_idx); + if (hrs == buffered_addr) { + assert((nbytes % BytesPerWord) == 0, "should be aligned"); + return nbytes / BytesPerWord; + } + hrs += nbytes; + } + + ShouldNotReachHere(); + return 0; +} + address ArchiveHeapWriter::buffered_addr_to_requested_addr(address buffered_addr) { return _requested_bottom + buffered_address_to_offset(buffered_addr); } @@ -709,27 +747,6 @@ void ArchiveHeapWriter::mark_native_pointer(oop src_obj, int field_offset) { } } -// Do we have a jlong/jint field that's actually a pointer to a MetaspaceObj? -bool ArchiveHeapWriter::is_marked_as_native_pointer(ArchiveHeapInfo* heap_info, oop src_obj, int field_offset) { - HeapShared::CachedOopInfo* p = HeapShared::archived_object_cache()->get(src_obj); - assert(p != nullptr, "must be"); - - // requested_field_addr = the address of this field in the requested space - oop requested_obj = requested_obj_from_buffer_offset(p->buffer_offset()); - Metadata** requested_field_addr = (Metadata**)(cast_from_oop
(requested_obj) + field_offset); - assert((Metadata**)_requested_bottom <= requested_field_addr && requested_field_addr < (Metadata**) _requested_top, "range check"); - - BitMap::idx_t idx = requested_field_addr - (Metadata**) _requested_bottom; - // Leading zeros have been removed so some addresses may not be in the ptrmap - size_t start_pos = FileMapInfo::current_info()->heap_ptrmap_start_pos(); - if (idx < start_pos) { - return false; - } else { - idx -= start_pos; - } - return (idx < heap_info->ptrmap()->size()) && (heap_info->ptrmap()->at(idx) == true); -} - void ArchiveHeapWriter::compute_ptrmap(ArchiveHeapInfo* heap_info) { int num_non_null_ptrs = 0; Metadata** bottom = (Metadata**) _requested_bottom; diff --git a/src/hotspot/share/cds/archiveHeapWriter.hpp b/src/hotspot/share/cds/archiveHeapWriter.hpp index f8f55a745ee..18e647912f1 100644 --- a/src/hotspot/share/cds/archiveHeapWriter.hpp +++ b/src/hotspot/share/cds/archiveHeapWriter.hpp @@ -236,11 +236,11 @@ public: static size_t get_filler_size_at(address buffered_addr); static void mark_native_pointer(oop src_obj, int offset); - static bool is_marked_as_native_pointer(ArchiveHeapInfo* heap_info, oop src_obj, int field_offset); static oop source_obj_to_requested_obj(oop src_obj); static oop buffered_addr_to_source_obj(address buffered_addr); static address buffered_addr_to_requested_addr(address buffered_addr); - + static Klass* real_klass_of_buffered_oop(address buffered_addr); + static size_t size_of_buffered_oop(address buffered_addr); }; #endif // INCLUDE_CDS_JAVA_HEAP #endif // SHARE_CDS_ARCHIVEHEAPWRITER_HPP diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 51899490a12..6922b66d7cd 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -23,6 +23,7 @@ */ #include "cds/aotLogging.hpp" +#include "cds/aotMapLogger.hpp" #include "cds/archiveHeapLoader.hpp" #include "cds/cdsConfig.hpp" #include "cds/classListWriter.hpp" @@ -104,6 +105,8 @@ void CDSConfig::ergo_initialize() { if (!is_dumping_heap()) { _is_dumping_full_module_graph = false; } + + AOTMapLogger::ergo_initialize(); } const char* CDSConfig::default_archive_path() { diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp index 5c55dea52c8..4d8f2b50017 100644 --- a/src/hotspot/share/cds/metaspaceShared.cpp +++ b/src/hotspot/share/cds/metaspaceShared.cpp @@ -29,6 +29,7 @@ #include "cds/aotConstantPoolResolver.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotLogging.hpp" +#include "cds/aotMapLogger.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveHeapLoader.hpp" @@ -325,6 +326,24 @@ void MetaspaceShared::initialize_for_static_dump() { // Called by universe_post_init() void MetaspaceShared::post_initialize(TRAPS) { if (CDSConfig::is_using_archive()) { + FileMapInfo *static_mapinfo = FileMapInfo::current_info(); + FileMapInfo *dynamic_mapinfo = FileMapInfo::dynamic_info(); + + if (AOTMapLogger::is_logging_at_bootstrap()) { + // The map logging needs to be done here, as it requires some stubs on Windows, + // which are not generated until the end of init_globals(). + AOTMapLogger::runtime_log(static_mapinfo, dynamic_mapinfo); + } + + // Close any open file descriptors. However, mmap'ed pages will remain in memory. + static_mapinfo->close(); + static_mapinfo->unmap_region(MetaspaceShared::bm); + + if (dynamic_mapinfo != nullptr) { + dynamic_mapinfo->close(); + dynamic_mapinfo->unmap_region(MetaspaceShared::bm); + } + int size = AOTClassLocationConfig::runtime()->length(); if (size > 0) { CDSProtectionDomain::allocate_shared_data_arrays(size, CHECK); @@ -1954,6 +1973,7 @@ class CountSharedSymbols : public SymbolClosure { void MetaspaceShared::initialize_shared_spaces() { FileMapInfo *static_mapinfo = FileMapInfo::current_info(); + FileMapInfo *dynamic_mapinfo = FileMapInfo::dynamic_info(); // Verify various attributes of the archive, plus initialize the // shared string/symbol tables. @@ -1969,19 +1989,11 @@ void MetaspaceShared::initialize_shared_spaces() { Universe::load_archived_object_instances(); AOTCodeCache::initialize(); - // Close the mapinfo file - static_mapinfo->close(); - - static_mapinfo->unmap_region(MetaspaceShared::bm); - - FileMapInfo *dynamic_mapinfo = FileMapInfo::dynamic_info(); if (dynamic_mapinfo != nullptr) { intptr_t* buffer = (intptr_t*)dynamic_mapinfo->serialized_data(); ReadClosure rc(&buffer, (intptr_t)SharedBaseAddress); ArchiveBuilder::serialize_dynamic_archivable_items(&rc); DynamicArchive::setup_array_klasses(); - dynamic_mapinfo->close(); - dynamic_mapinfo->unmap_region(MetaspaceShared::bm); } LogStreamHandle(Info, aot) lsh; diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index 1845c28d819..1be7a6db662 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -1179,6 +1179,12 @@ const char* SystemDictionaryShared::loader_type_for_shared_class(Klass* k) { } } +void SystemDictionaryShared::get_all_archived_classes(bool is_static_archive, GrowableArray* classes) { + get_archive(is_static_archive)->_builtin_dictionary.iterate([&] (const RunTimeClassInfo* record) { + classes->append(record->klass()); + }); +} + class SharedDictionaryPrinter : StackObj { outputStream* _st; int _index; diff --git a/src/hotspot/share/classfile/systemDictionaryShared.hpp b/src/hotspot/share/classfile/systemDictionaryShared.hpp index 5f6dd055fd6..e3c22ee11a0 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.hpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.hpp @@ -115,6 +115,8 @@ class DumpTimeSharedClassTable; class RunTimeClassInfo; class RunTimeSharedDictionary; +template class GrowableArray; + class SharedClassLoadingMark { private: Thread* THREAD; @@ -269,6 +271,7 @@ public: bool is_static_archive = true); static void serialize_vm_classes(class SerializeClosure* soc); static const char* loader_type_for_shared_class(Klass* k); + static void get_all_archived_classes(bool is_static_archive, GrowableArray* classes); static void print() { return print_on(tty); } static void print_on(outputStream* st) NOT_CDS_RETURN; static void print_shared_archive(outputStream* st, bool is_static = true) NOT_CDS_RETURN; diff --git a/src/hotspot/share/oops/cpCache.hpp b/src/hotspot/share/oops/cpCache.hpp index 83af4b88e32..e9e4f9a40e5 100644 --- a/src/hotspot/share/oops/cpCache.hpp +++ b/src/hotspot/share/oops/cpCache.hpp @@ -111,6 +111,7 @@ class ConstantPoolCache: public MetaspaceObj { oop archived_references() NOT_CDS_JAVA_HEAP_RETURN_(nullptr); void clear_archived_references() NOT_CDS_JAVA_HEAP_RETURN; + CDS_JAVA_HEAP_ONLY(int archived_references_index() { return _archived_references_index; }) inline objArrayOop resolved_references(); void set_resolved_references(OopHandle s) { _resolved_references = s; } diff --git a/src/hotspot/share/oops/objArrayOop.hpp b/src/hotspot/share/oops/objArrayOop.hpp index 8e39b897018..0539171f1ab 100644 --- a/src/hotspot/share/oops/objArrayOop.hpp +++ b/src/hotspot/share/oops/objArrayOop.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -44,6 +44,7 @@ class objArrayOopDesc : public arrayOopDesc { friend class Continuation; template friend class RawOopWriter; + friend class AOTMapLogger; template T* obj_at_addr(int index) const; diff --git a/test/hotspot/jtreg/runtime/cds/CDSMapTest.java b/test/hotspot/jtreg/runtime/cds/CDSMapTest.java index 5a5978a8633..c93b16c68b8 100644 --- a/test/hotspot/jtreg/runtime/cds/CDSMapTest.java +++ b/test/hotspot/jtreg/runtime/cds/CDSMapTest.java @@ -25,6 +25,7 @@ * @test * @bug 8308903 * @summary Test the contents of -Xlog:aot+map + * @requires vm.flagless * @requires vm.cds * @library /test/lib * @run driver CDSMapTest @@ -33,6 +34,8 @@ import jdk.test.lib.cds.CDSOptions; import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.Platform; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; import java.util.ArrayList; public class CDSMapTest { @@ -46,34 +49,57 @@ public class CDSMapTest { } public static void doTest(boolean compressed) throws Exception { - ArrayList dumpArgs = new ArrayList<>(); + ArrayList vmArgs = new ArrayList<>(); // Use the same heap size as make/Images.gmk - dumpArgs.add("-Xmx128M"); + vmArgs.add("-Xmx128M"); if (Platform.is64bit()) { // These options are available only on 64-bit. String sign = (compressed) ? "+" : "-"; - dumpArgs.add("-XX:" + sign + "UseCompressedOops"); + vmArgs.add("-XX:" + sign + "UseCompressedOops"); } - dump(dumpArgs); + String archiveFile = dump(vmArgs); + exec(vmArgs, archiveFile); + } static int id = 0; - static void dump(ArrayList args, String... more) throws Exception { + + // Create a map file when creating the archive + static String dump(ArrayList args) throws Exception { String logName = "SharedArchiveFile" + (id++); String archiveName = logName + ".jsa"; String mapName = logName + ".map"; CDSOptions opts = (new CDSOptions()) .addPrefix("-Xlog:cds=debug") + // filesize=0 ensures that a large map file not broken up in multiple files. .addPrefix("-Xlog:aot+map=debug,aot+map+oops=trace:file=" + mapName + ":none:filesize=0") .setArchiveName(archiveName) - .addSuffix(args) - .addSuffix(more); + .addSuffix(args); CDSTestUtils.createArchiveAndCheck(opts); CDSMapReader.MapFile mapFile = CDSMapReader.read(mapName); CDSMapReader.validate(mapFile); + + return archiveName; + } + + // Create a map file when using the archive + static void exec(ArrayList vmArgs, String archiveFile) throws Exception { + String mapName = archiveFile + ".exec.map"; + vmArgs.add("-XX:SharedArchiveFile=" + archiveFile); + vmArgs.add("-Xlog:cds=debug"); + vmArgs.add("-Xshare:on"); + vmArgs.add("-Xlog:aot+map=debug,aot+map+oops=trace:file=" + mapName + ":none:filesize=0"); + vmArgs.add("--version"); + String[] cmdLine = vmArgs.toArray(new String[vmArgs.size()]); + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(cmdLine); + OutputAnalyzer out = CDSTestUtils.executeAndLog(pb, "exec"); + out.shouldHaveExitValue(0); + + CDSMapReader.MapFile mapFile = CDSMapReader.read(mapName); + CDSMapReader.validate(mapFile); } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTMapTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTMapTest.java new file mode 100644 index 00000000000..f544b5653b7 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTMapTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 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. + */ + +/** + * @test id=aot + * @bug 8362566 + * @summary Test the contents of -Xlog:aot+map with AOT workflow + * @requires vm.flagless + * @requires vm.cds.supports.aot.class.linking + * @library /test/lib /test/hotspot/jtreg/runtime/cds + * @build AOTMapTest + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar AOTMapTestApp + * @run driver AOTMapTest AOT --two-step-training + */ + +/** + * @test id=dynamic + * @bug 8362566 + * @summary Test the contents of -Xlog:aot+map with AOT workflow + * @requires vm.flagless + * @requires vm.cds.supports.aot.class.linking + * @library /test/lib /test/hotspot/jtreg/runtime/cds + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @build AOTMapTest + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar AOTMapTestApp + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. AOTMapTest DYNAMIC + */ + + +import java.util.ArrayList; +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.Platform; + +public class AOTMapTest { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = "AOTMapTestApp"; + + public static void main(String[] args) throws Exception { + doTest(args, false); + + if (Platform.is64bit()) { + // There's no oop/klass compression on 32-bit. + doTest(args, true); + } + } + + public static void doTest(String[] args, boolean compressed) throws Exception { + Tester tester = new Tester(compressed); + tester.run(args); + + validate(tester.dumpMapFile); + validate(tester.runMapFile); + } + + static void validate(String mapFileName) { + CDSMapReader.MapFile mapFile = CDSMapReader.read(mapFileName); + CDSMapReader.validate(mapFile); + } + + static class Tester extends CDSAppTester { + boolean compressed; + String dumpMapFile; + String runMapFile; + + public Tester(boolean compressed) { + super(mainClass); + this.compressed = compressed; + + dumpMapFile = "test" + (compressed ? "0" : "1") + ".dump.aotmap"; + runMapFile = "test" + (compressed ? "0" : "1") + ".run.aotmap"; + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + ArrayList vmArgs = new ArrayList<>(); + + vmArgs.add("-Xmx128M"); + vmArgs.add("-Xlog:aot=debug"); + + if (Platform.is64bit()) { + // These options are available only on 64-bit. + String sign = (compressed) ? "+" : "-"; + vmArgs.add("-XX:" + sign + "UseCompressedOops"); + } + + // filesize=0 ensures that a large map file not broken up in multiple files. + String logMapPrefix = "-Xlog:aot+map=debug,aot+map+oops=trace:file="; + String logMapSuffix = ":none:filesize=0"; + + if (runMode == RunMode.ASSEMBLY || runMode == RunMode.DUMP_DYNAMIC) { + vmArgs.add(logMapPrefix + dumpMapFile + logMapSuffix); + } else if (runMode == RunMode.PRODUCTION) { + vmArgs.add(logMapPrefix + runMapFile + logMapSuffix); + } + + return vmArgs.toArray(new String[vmArgs.size()]); + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, + }; + } + } +} + +class AOTMapTestApp { + public static void main(String[] args) { + System.out.println("Hello AOTMapTestApp"); + } +}