From 8008e27c55030b397e2040bc3cf8408e47edf412 Mon Sep 17 00:00:00 2001 From: Ioi Lam <iklam@openjdk.org> Date: Mon, 24 Jul 2023 17:56:42 +0000 Subject: [PATCH] 8308903: Print detailed info for Java objects in -Xlog:cds+map Reviewed-by: stuefe, ccheung --- src/hotspot/share/cds/archiveBuilder.cpp | 179 +++++++++-- src/hotspot/share/cds/archiveHeapWriter.cpp | 40 ++- src/hotspot/share/cds/archiveHeapWriter.hpp | 4 +- src/hotspot/share/cds/heapShared.hpp | 1 + src/hotspot/share/oops/typeArrayKlass.cpp | 9 +- src/hotspot/share/oops/typeArrayKlass.hpp | 4 +- .../jtreg/runtime/cds/CDSMapReader.java | 301 ++++++++++++++++++ .../hotspot/jtreg/runtime/cds/CDSMapTest.java | 86 +++++ .../jtreg/runtime/cds/DeterministicDump.java | 2 +- 9 files changed, 585 insertions(+), 41 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/CDSMapReader.java create mode 100644 test/hotspot/jtreg/runtime/cds/CDSMapTest.java diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index 636a955ba74..dbf0583619b 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -32,6 +32,7 @@ #include "cds/metaspaceShared.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/classLoaderDataShared.hpp" +#include "classfile/javaClasses.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" @@ -47,6 +48,7 @@ #include "oops/objArrayOop.inline.hpp" #include "oops/oopHandle.inline.hpp" #include "runtime/arguments.hpp" +#include "runtime/fieldDescriptor.inline.hpp" #include "runtime/globals_extension.hpp" #include "runtime/javaThread.hpp" #include "runtime/sharedRuntime.hpp" @@ -965,7 +967,7 @@ class ArchiveBuilder::CDSMapLogger : AllStatic { SourceObjInfo* src_info = src_objs->at(i); address src = src_info->source_addr(); address dest = src_info->buffered_addr(); - log_data(last_obj_base, dest, last_obj_base + buffer_to_runtime_delta()); + 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(); @@ -1007,12 +1009,12 @@ class ArchiveBuilder::CDSMapLogger : AllStatic { last_obj_end = dest + bytes; } - log_data(last_obj_base, last_obj_end, last_obj_base + buffer_to_runtime_delta()); + log_as_hex(last_obj_base, last_obj_end, last_obj_base + buffer_to_runtime_delta()); if (last_obj_end < region_end) { log_debug(cds, map)(PTR_FORMAT ": @@ Misc data " SIZE_FORMAT " bytes", p2i(last_obj_end + buffer_to_runtime_delta()), size_t(region_end - last_obj_end)); - log_data(last_obj_end, region_end, last_obj_end + buffer_to_runtime_delta()); + log_as_hex(last_obj_end, region_end, last_obj_end + buffer_to_runtime_delta()); } } @@ -1038,44 +1040,167 @@ class ArchiveBuilder::CDSMapLogger : AllStatic { MemRegion r = heap_info->buffer_region(); address start = address(r.start()); address end = address(r.end()); - log_region("heap", start, end, to_requested(start)); + log_region("heap", start, end, ArchiveHeapWriter::buffered_addr_to_requested_addr(start)); + + LogStreamHandle(Info, cds, map) st; while (start < end) { size_t byte_size; - oop original_oop = ArchiveHeapWriter::buffered_addr_to_source_obj(start); - if (original_oop != nullptr) { - ResourceMark rm; - log_info(cds, map)(PTR_FORMAT ": @@ Object %s", - p2i(to_requested(start)), original_oop->klass()->external_name()); - byte_size = original_oop->size() * BytesPerWord; + 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. + print_oop_with_requested_addr_cr(&st, source_oop, false); + byte_size = source_oop->size() * BytesPerWord; } else if (start == ArchiveHeapWriter::buffered_heap_roots_addr()) { - // HeapShared::roots() is copied specially so it doesn't exist in - // HeapShared::OriginalObjectTable. See HeapShared::copy_roots(). - log_info(cds, map)(PTR_FORMAT ": @@ Object HeapShared::roots (ObjArray)", - p2i(to_requested(start))); + // HeapShared::roots() is copied specially, so it doesn't exist in + // ArchiveHeapWriter::BufferOffsetToSourceObjectTable. + // See ArchiveHeapWriter::copy_roots_to_buffer(). + st.print_cr("HeapShared::roots[%d]", HeapShared::pending_roots()->length()); byte_size = ArchiveHeapWriter::heap_roots_word_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. + st.print_cr("filler " SIZE_FORMAT " bytes", byte_size); } else { - // We have reached the end of the region, but have some unused space - // at the end. - log_info(cds, map)(PTR_FORMAT ": @@ Unused heap space " SIZE_FORMAT " bytes", - p2i(to_requested(start)), size_t(end - start)); - log_data(start, end, to_requested(start), /*is_heap=*/true); - break; + ShouldNotReachHere(); } + address oop_end = start + byte_size; - log_data(start, oop_end, to_requested(start), /*is_heap=*/true); + log_as_hex(start, oop_end, requested_start, /*is_heap=*/true); + + if (source_oop != nullptr) { + log_oop_details(heap_info, source_oop); + } else if (start == ArchiveHeapWriter::buffered_heap_roots_addr()) { + log_heap_roots(); + } start = oop_end; } } - static address to_requested(address p) { - return ArchiveHeapWriter::buffered_addr_to_requested_addr(p); + // 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_with_requested_addr_cr(). + class ArchivedFieldPrinter : public FieldClosure { + ArchiveHeapInfo* _heap_info; + outputStream* _st; + oop _source_obj; + public: + ArchivedFieldPrinter(ArchiveHeapInfo* heap_info, outputStream* st, oop src_obj) : + _heap_info(heap_info), _st(st), _source_obj(src_obj) {} + + 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 + print_oop_with_requested_addr_cr(_st, _source_obj->obj_field(fd->offset())); + 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, _source_obj); // 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 + address requested_field_addr = cast_from_oop<address>(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) { + LogStreamHandle(Trace, cds, 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); + print_oop_with_requested_addr_cr(&st, source_obj_array->obj_at(i)); + } + } else { + st.print_cr(" - fields (" SIZE_FORMAT " words):", source_oop->size()); + ArchivedFieldPrinter print_field(heap_info, &st, source_oop); + InstanceKlass::cast(source_klass)->print_nonstatic_fields(&print_field); + } + } } -#endif + + static void log_heap_roots() { + LogStreamHandle(Trace, cds, map, oops) st; + if (st.is_enabled()) { + for (int i = 0; i < HeapShared::pending_roots()->length(); i++) { + st.print("roots[%4d]: ", i); + print_oop_with_requested_addr_cr(&st, HeapShared::pending_roots()->at(i)); + } + } + } + + // The output looks like this. The first number is the requested address. The second number is + // the narrowOop version of the requested address. + // 0x00000007ffc7e840 (0xfff8fd08) java.lang.Class + // 0x00000007ffc000f8 (0xfff8001f) [B length: 11 + static void print_oop_with_requested_addr_cr(outputStream* st, oop source_oop, bool print_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_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_cr("%s", source_oop->klass()->external_name()); + } + } + } +#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_data(address base, address top, address requested_base, bool is_heap = false) { + static void log_as_hex(address base, address top, address requested_base, bool is_heap = false) { assert(top >= base, "must be"); LogStreamHandle(Trace, cds, map) lsh; @@ -1107,7 +1232,7 @@ public: address header_end = header + mapinfo->header()->header_size(); log_region("header", header, header_end, 0); log_header(mapinfo); - log_data(header, header_end, 0); + log_as_hex(header, header_end, 0); DumpRegion* rw_region = &builder->_rw_region; DumpRegion* ro_region = &builder->_ro_region; @@ -1117,7 +1242,7 @@ public: address bitmap_end = address(bitmap + bitmap_size_in_bytes); log_region("bitmap", address(bitmap), bitmap_end, 0); - log_data((address)bitmap, bitmap_end, 0); + log_as_hex((address)bitmap, bitmap_end, 0); #if INCLUDE_CDS_JAVA_HEAP if (heap_info->is_used()) { diff --git a/src/hotspot/share/cds/archiveHeapWriter.cpp b/src/hotspot/share/cds/archiveHeapWriter.cpp index 49170dac10c..c52814440af 100644 --- a/src/hotspot/share/cds/archiveHeapWriter.cpp +++ b/src/hotspot/share/cds/archiveHeapWriter.cpp @@ -64,12 +64,19 @@ GrowableArrayCHeap<oop, mtClassShared>* ArchiveHeapWriter::_source_objs; ArchiveHeapWriter::BufferOffsetToSourceObjectTable* ArchiveHeapWriter::_buffer_offset_to_source_obj_table = nullptr; + +typedef ResourceHashtable<address, size_t, + 127, // prime number + AnyObj::C_HEAP, + mtClassShared> FillersTable; +static FillersTable* _fillers; + void ArchiveHeapWriter::init() { if (HeapShared::can_write()) { Universe::heap()->collect(GCCause::_java_lang_system_gc); _buffer_offset_to_source_obj_table = new BufferOffsetToSourceObjectTable(); - + _fillers = new FillersTable(); _requested_bottom = nullptr; _requested_top = nullptr; @@ -255,7 +262,7 @@ int ArchiveHeapWriter::filler_array_length(size_t fill_bytes) { return -1; } -void ArchiveHeapWriter::init_filler_array_at_buffer_top(int array_length, size_t fill_bytes) { +HeapWord* ArchiveHeapWriter::init_filler_array_at_buffer_top(int array_length, size_t fill_bytes) { assert(UseCompressedClassPointers, "Archived heap only supported for compressed klasses"); Klass* oak = Universe::objectArrayKlassObj(); // already relocated to point to archived klass HeapWord* mem = offset_to_buffered_address<HeapWord*>(_buffer_used); @@ -264,6 +271,7 @@ void ArchiveHeapWriter::init_filler_array_at_buffer_top(int array_length, size_t narrowKlass nk = ArchiveBuilder::current()->get_requested_narrow_klass(oak); cast_to_oop(mem)->set_narrow_klass(nk); arrayOopDesc::set_length(mem, array_length); + return mem; } void ArchiveHeapWriter::maybe_fill_gc_region_gap(size_t required_byte_size) { @@ -293,9 +301,19 @@ void ArchiveHeapWriter::maybe_fill_gc_region_gap(size_t required_byte_size) { int array_length = filler_array_length(fill_bytes); log_info(cds, heap)("Inserting filler obj array of %d elements (" SIZE_FORMAT " bytes total) @ buffer offset " SIZE_FORMAT, array_length, fill_bytes, _buffer_used); - init_filler_array_at_buffer_top(array_length, fill_bytes); - + HeapWord* filler = init_filler_array_at_buffer_top(array_length, fill_bytes); _buffer_used = filler_end; + _fillers->put((address)filler, fill_bytes); + } +} + +size_t ArchiveHeapWriter::get_filler_size_at(address buffered_addr) { + size_t* p = _fillers->get(buffered_addr); + if (p != nullptr) { + assert(*p > 0, "filler must be larger than zero bytes"); + return *p; + } else { + return 0; // buffered_addr is not a filler } } @@ -514,6 +532,20 @@ 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<address>(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; + 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 546fcea2a2f..889646b78c8 100644 --- a/src/hotspot/share/cds/archiveHeapWriter.hpp +++ b/src/hotspot/share/cds/archiveHeapWriter.hpp @@ -188,7 +188,7 @@ private: static void maybe_fill_gc_region_gap(size_t required_byte_size); static size_t filler_array_byte_size(int length); static int filler_array_length(size_t fill_bytes); - static void init_filler_array_at_buffer_top(int array_length, size_t fill_bytes); + static HeapWord* init_filler_array_at_buffer_top(int array_length, size_t fill_bytes); static void set_requested_address(ArchiveHeapInfo* info); static void relocate_embedded_oops(GrowableArrayCHeap<oop, mtClassShared>* roots, ArchiveHeapInfo* info); @@ -225,8 +225,10 @@ public: static size_t heap_roots_word_size() { return _heap_roots_word_size; } + 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); diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/heapShared.hpp index 1a2a30f393e..d0f38219d85 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/heapShared.hpp @@ -381,6 +381,7 @@ private: // Dump-time only. Returns the index of the root, which can be used at run time to read // the root using get_root(index, ...). static int append_root(oop obj); + static GrowableArrayCHeap<oop, mtClassShared>* pending_roots() { return _pending_roots; } // Dump-time and runtime static objArrayOop roots(); diff --git a/src/hotspot/share/oops/typeArrayKlass.cpp b/src/hotspot/share/oops/typeArrayKlass.cpp index 899531d33cd..e79854147e5 100644 --- a/src/hotspot/share/oops/typeArrayKlass.cpp +++ b/src/hotspot/share/oops/typeArrayKlass.cpp @@ -276,8 +276,6 @@ void TypeArrayKlass::print_value_on(outputStream* st) const { st->print("}"); } -#ifndef PRODUCT - static void print_boolean_array(typeArrayOop ta, int print_len, outputStream* st) { for (int index = 0; index < print_len; index++) { st->print_cr(" - %3d: %s", index, (ta->bool_at(index) == 0) ? "false" : "true"); @@ -341,7 +339,10 @@ static void print_long_array(typeArrayOop ta, int print_len, outputStream* st) { void TypeArrayKlass::oop_print_on(oop obj, outputStream* st) { ArrayKlass::oop_print_on(obj, st); - typeArrayOop ta = typeArrayOop(obj); + oop_print_elements_on(typeArrayOop(obj), st); +} + +void TypeArrayKlass::oop_print_elements_on(typeArrayOop ta, outputStream* st) { int print_len = MIN2((intx) ta->length(), MaxElementPrintSize); switch (element_type()) { case T_BOOLEAN: print_boolean_array(ta, print_len, st); break; @@ -360,8 +361,6 @@ void TypeArrayKlass::oop_print_on(oop obj, outputStream* st) { } } -#endif // PRODUCT - const char* TypeArrayKlass::internal_name() const { return Klass::external_name(); } diff --git a/src/hotspot/share/oops/typeArrayKlass.hpp b/src/hotspot/share/oops/typeArrayKlass.hpp index 5d4a739936f..48198d55110 100644 --- a/src/hotspot/share/oops/typeArrayKlass.hpp +++ b/src/hotspot/share/oops/typeArrayKlass.hpp @@ -123,10 +123,8 @@ class TypeArrayKlass : public ArrayKlass { public: // Printing -#ifndef PRODUCT void oop_print_on(oop obj, outputStream* st); -#endif - + void oop_print_elements_on(typeArrayOop ta, outputStream* st); void print_on(outputStream* st) const; void print_value_on(outputStream* st) const; diff --git a/test/hotspot/jtreg/runtime/cds/CDSMapReader.java b/test/hotspot/jtreg/runtime/cds/CDSMapReader.java new file mode 100644 index 00000000000..8ad53dc6596 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/CDSMapReader.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2023, 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. + */ + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +// This is a simple parser for parsing the output of +// +// java -Xshare:dump -Xlog:cds+map=debug,cds+map+oops=trace:file=cds.map:none:filesize=0 +// +// Currently it just check the output related to JDK-8308903. +// I.e., each oop fields in the HeapObjects must point to a valid HeapObject. +// +// It can be extended to check for the other parts of the map file, or perform +// more analysis on the HeapObjects. +public class CDSMapReader { + public static class MapFile { + ArrayList<HeapObject> heapObjects = new ArrayList<>(); + HashMap<Long, HeapObject> oopToObject = new HashMap<>(); + HashMap<Long, HeapObject> narrowOopToObject = new HashMap<>(); + + void add(HeapObject heapObject) { + heapObjects.add(heapObject); + oopToObject.put(heapObject.address.oop, heapObject); + if (heapObject.address.narrowOop != 0) { + narrowOopToObject.put(heapObject.address.narrowOop, heapObject); + } + } + + public int heapObjectCount() { + return heapObjects.size(); + } + } + + public static class HeapAddress { + long oop; + long narrowOop; + + HeapAddress(String oopStr, String narrowOopStr) { + oop = Long.parseUnsignedLong(oopStr, 16); + if (narrowOopStr != null) { + narrowOop = Long.parseUnsignedLong(narrowOopStr, 16); + } + } + } + + public static class Klass { + long address; + String name; + + static Klass getKlass(String name, String addr) { + // TODO: look up from a table of known Klasses + Klass k = new Klass(); + k.name = name; + k.address = Long.parseUnsignedLong(addr, 16); + return k; + } + } + + public static class HeapObject { + HeapAddress address; + ArrayList<Field> fields; + String className; + Klass klass; + + HeapObject(String className, String oop, String narrowOop) { + this.className = className; + address = new HeapAddress(oop, narrowOop); + } + + void setKlass(String klassName, String address) { + klass = Klass.getKlass(klassName, address); + } + + void addOopField(String name, String offset, String oopStr, String narrowOopStr) { + if (fields == null) { + fields = new ArrayList<Field>(); + } + fields.add(new Field(name, offset, oopStr, narrowOopStr)); + } + } + + public static class Field { + String name; + int offset; + HeapAddress referentAddress; // non-null iff this is an object field + int lineCount; + + Field(String name, String offset, String oopStr, String narrowOopStr) { + this.name = name; + this.offset = Integer.parseInt(offset); + this.referentAddress = new HeapAddress(oopStr, narrowOopStr); + this.lineCount = CDSMapReader.lineCount; + } + } + + // 0x00000007ffc00000: 4a5b8701 00000063 00010290 00000000 00010100 fff80003 + static Pattern rawDataPattern = Pattern.compile("^0x([0-9a-f]+): *( [0-9a-f]+)+ *$"); + + // (one address) + // 0x00000007ffc00000: @@ Object java.lang.String + static Pattern objPattern1 = Pattern.compile("^0x([0-9a-f]+): @@ Object (.*)"); + + // (two addresses) + // 0x00000007ffc00000: @@ Object (0xfff80000) java.lang.String + static Pattern objPattern2 = Pattern.compile("^0x([0-9a-f]+): @@ Object [(]0x([0-9a-f]+)[)] (.*)"); + + // - klass: 'java/lang/String' 0x0000000800010290 + static Pattern instanceObjKlassPattern = Pattern.compile("^ - klass: '([^']+)' 0x([0-9a-f]+)"); + + // - klass: {type array byte} 0x00000008000024c8 + static Pattern typeArrayKlassPattern = Pattern.compile("^ - klass: [{]type array ([a-z]+)[}] 0x([0-9a-f]+)"); + + // - klass: 'java/lang/Object'[] 0x00000008000013e0 + static Pattern objArrayKlassPattern = Pattern.compile("^ - klass: ('[^']+'(\\[\\])+) 0x([0-9a-f]+)"); + + // - fields (3 words): + static Pattern fieldsWordsPattern = Pattern.compile("^ - fields [(]([0-9]+) words[)]:$"); + + // (one address) + // - final 'key' 'Ljava/lang/Object;' @16 0x00000007ffc68260 java.lang.String + static Pattern oopFieldPattern1 = Pattern.compile(" - [^']* '([^']+)'.*@([0-9]+) 0x([0-9a-f]+) (.*)"); + + // (two addresses) + // - final 'key' 'Ljava/lang/Object;' @16 0x00000007ffc68260 (0xfff8d04c) java.lang.String + static Pattern oopFieldPattern2 = Pattern.compile(" - [^']* '([^']+)'.*@([0-9]+) 0x([0-9a-f]+) [(]0x([0-9a-f]+)[)] (.*)"); + + private static Matcher match(String line, Pattern pattern) { + Matcher m = pattern.matcher(line); + if (m.find()) { + return m; + } else { + return null; + } + } + + private static void parseHeapObject(String className, String oop, String narrowOop) throws IOException { + HeapObject heapObject = parseHeapObjectImpl(className, oop, narrowOop); + mapFile.add(heapObject); + } + + private static HeapObject parseHeapObjectImpl(String className, String oop, String narrowOop) throws IOException { + HeapObject heapObject = new HeapObject(className, oop, narrowOop); + Matcher m; + + nextLine(); + while (line != null && match(line, rawDataPattern) != null) { // skip raw data + nextLine(); + } + + if (line == null || !line.startsWith(" - ")) { + return heapObject; + } + + if ((m = match(line, instanceObjKlassPattern)) != null) { + heapObject.setKlass(m.group(1), m.group(2)); + nextLine(); + if ((m = match(line, fieldsWordsPattern)) == null) { + throw new RuntimeException("Expected field size info"); + } + // TODO: read all the array elements + while (true) { + nextLine(); + if (line == null || !line.startsWith(" - ")) { + return heapObject; + } + if (!line.contains("marked metadata pointer")) { + if ((m = match(line, oopFieldPattern2)) != null) { + heapObject.addOopField(m.group(1), m.group(2), m.group(3), m.group(4)); + } else if ((m = match(line, oopFieldPattern1)) != null) { + heapObject.addOopField(m.group(1), m.group(2), m.group(3), null); + } + } + } + } else if ((m = match(line, typeArrayKlassPattern)) != null) { + heapObject.setKlass(m.group(1), m.group(2)); + // TODO: read all the array elements + while (true) { + nextLine(); + if (line == null || !line.startsWith(" - ")) { + return heapObject; + } + } + } else if ((m = match(line, objArrayKlassPattern)) != null) { + heapObject.setKlass(m.group(1), m.group(3)); + // TODO: read all the array elements + while (true) { + nextLine(); + if (line == null || !line.startsWith(" - ")) { + return heapObject; + } + } + } else { + throw new RuntimeException("Expected klass info"); + } + } + + static MapFile mapFile; + static BufferedReader reader; + static String line = null; // current line being parsed + static int lineCount = 0; + static String nextLine() throws IOException { + line = reader.readLine(); + ++ lineCount; + return line; + } + + public static MapFile read(String fileName) { + mapFile = new MapFile(); + + try (BufferedReader r = new BufferedReader(new FileReader(fileName))) { + reader = r; + nextLine(); + + Matcher m; + while (line != null) { + if ((m = match(line, objPattern2)) != null) { + parseHeapObject(m.group(3), m.group(1), m.group(2)); + } else if ((m = match(line, objPattern1)) != null) { + parseHeapObject(m.group(2), m.group(1), null); + } else { + nextLine(); + } + } + return mapFile; + } catch (Throwable t) { + System.out.println("Error parsing line " + lineCount + ": " + line); + throw new RuntimeException(t); + } finally { + System.out.println("Parsed " + lineCount + " lines in " + fileName); + System.out.println("Found " + mapFile.heapObjectCount() + " heap objects"); + mapFile = null; + reader = null; + line = null; + lineCount = 0; + } + } + + private static void mustContain(HashMap<Long, HeapObject> allObjects, Field field, long pointer, boolean isNarrow) { + if (allObjects.get(pointer) == null) { + throw new RuntimeException((isNarrow ? "narrowOop" : "oop") + " pointer 0x" + Long.toHexString(pointer) + + " on line " + field.lineCount + " doesn't point to a valid heap object"); + } + } + + // Check that each oop fields in the HeapObjects must point to a valid HeapObject. + public static int validate(MapFile mapFile) { + int count = 0; + for (HeapObject heapObject : mapFile.heapObjects) { + if (heapObject.fields != null) { + for (Field field : heapObject.fields) { + HeapAddress referentAddress = field.referentAddress; + long oop = referentAddress.oop; + long narrowOop = referentAddress.narrowOop; + // Is this test actually doing something? + // To see how an invalidate pointer may be found, change oop in the + // following line to oop+1 + mustContain(mapFile.oopToObject, field, oop, false); + count ++; + if (narrowOop != 0) { + mustContain(mapFile.narrowOopToObject, field, narrowOop, true); + count ++; + } + } + } + } + System.out.println("Checked " + count + " oop field references"); + return count; + } + + public static void main(String args[]) { + MapFile mapFile = read(args[0]); + validate(mapFile); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/CDSMapTest.java b/test/hotspot/jtreg/runtime/cds/CDSMapTest.java new file mode 100644 index 00000000000..c2a53cac42d --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/CDSMapTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2023, 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 + * @bug 8308903 + * @summary Test the contents of -Xlog:cds+map + * @requires vm.cds + * @library /test/lib + * @run driver CDSMapTest + */ + +import jdk.test.lib.cds.CDSOptions; +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.Platform; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; + +public class CDSMapTest { + public static void main(String[] args) throws Exception { + doTest(false); + + if (Platform.is64bit()) { + // There's no oop/klass compression on 32-bit. + doTest(true); + } + } + + public static void doTest(boolean compressed) throws Exception { + ArrayList<String> dumpArgs = new ArrayList<>(); + + // Use the same heap size as make/Images.gmk + dumpArgs.add("-Xmx128M"); + + if (Platform.is64bit()) { + // These options are available only on 64-bit. + String sign = (compressed) ? "+" : "-"; + dumpArgs.add("-XX:" + sign + "UseCompressedOops"); + } + + dump(dumpArgs); + } + + static int id = 0; + static void dump(ArrayList<String> args, String... more) throws Exception { + String logName = "SharedArchiveFile" + (id++); + String archiveName = logName + ".jsa"; + String mapName = logName + ".map"; + CDSOptions opts = (new CDSOptions()) + .addPrefix("-Xlog:cds=debug") + .addPrefix("-Xlog:cds+map=debug,cds+map+oops=trace:file=" + mapName + ":none:filesize=0") + .setArchiveName(archiveName) + .addSuffix(args) + .addSuffix(more); + CDSTestUtils.createArchiveAndCheck(opts); + + CDSMapReader.MapFile mapFile = CDSMapReader.read(mapName); + int oopFieldCount = CDSMapReader.validate(mapFile); + if (mapFile.heapObjectCount() > 0 && oopFieldCount < 10000) { + // heapObjectCount() may be zero if the selected GC doesn't support heap object archiving. + throw new RuntimeException("CDS map file seems incorrect: " + mapFile.heapObjectCount() + + " objects but only " + oopFieldCount + " oop field references"); + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/DeterministicDump.java b/test/hotspot/jtreg/runtime/cds/DeterministicDump.java index 25c6148d326..57e4000fe7a 100644 --- a/test/hotspot/jtreg/runtime/cds/DeterministicDump.java +++ b/test/hotspot/jtreg/runtime/cds/DeterministicDump.java @@ -80,7 +80,7 @@ public class DeterministicDump { String mapName = logName + ".map"; CDSOptions opts = (new CDSOptions()) .addPrefix("-Xlog:cds=debug") - .addPrefix("-Xlog:cds+map=trace:file=" + mapName + ":none:filesize=0") + .addPrefix("-Xlog:cds+map*=trace:file=" + mapName + ":none:filesize=0") .setArchiveName(archiveName) .addSuffix(args) .addSuffix(more);