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
(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* ArchiveHeapWriter::_source_objs;
ArchiveHeapWriter::BufferOffsetToSourceObjectTable*
ArchiveHeapWriter::_buffer_offset_to_source_obj_table = nullptr;
+
+typedef ResourceHashtable 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(_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(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* 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* 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 heapObjects = new ArrayList<>();
+ HashMap oopToObject = new HashMap<>();
+ HashMap 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 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();
+ }
+ 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 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 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 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);