07194195ce
Reviewed-by: ccheung, matsaave
352 lines
14 KiB
Java
352 lines
14 KiB
Java
/*
|
|
* Copyright (c) 2023, 2024, 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.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
|
|
|
|
The map file contains patterns like this for the heap objects:
|
|
|
|
======================================================================
|
|
0x00000000ffe00000: @@ Object (0xffe00000) java.lang.String
|
|
- klass: 'java/lang/String' 0x0000000800010220
|
|
- fields (3 words):
|
|
- private 'hash' 'I' @12 0 (0x00000000)
|
|
- private final 'coder' 'B' @16 0 (0x00)
|
|
- private 'hashIsZero' 'Z' @17 true (0x01)
|
|
- injected 'flags' 'B' @18 1 (0x01)
|
|
- private final 'value' '[B' @20 0x00000000ffe00018 (0xffe00018) [B length: 0
|
|
0x00000000ffe00018: @@ Object (0xffe00018) [B length: 0
|
|
- klass: {type array byte} 0x00000008000024d8
|
|
======================================================================
|
|
|
|
Currently this parser just check the output related to JDK-8308903.
|
|
I.e., each oop field must point to a valid HeapObject. For example, the 'value' field
|
|
in the String must point to a valid byte array.
|
|
|
|
This parser 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<>();
|
|
public int stringCount = 0;
|
|
|
|
void add(HeapObject heapObject) {
|
|
heapObjects.add(heapObject);
|
|
oopToObject.put(heapObject.address.oop, heapObject);
|
|
if (heapObject.address.narrowOop != 0) {
|
|
narrowOopToObject.put(heapObject.address.narrowOop, heapObject);
|
|
}
|
|
if (heapObject.className.equals("java.lang.String")) {
|
|
stringCount ++;
|
|
}
|
|
}
|
|
|
|
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]+)[)] (.*)");
|
|
|
|
// (injected module_entry)
|
|
// - injected 'module_entry' 'J' @16 0 (0x0000000000000000)
|
|
static Pattern moduleEntryPattern = Pattern.compile("- injected 'module_entry' 'J' @[0-9]+[ ]+([0-9]+)");
|
|
|
|
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");
|
|
}
|
|
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, moduleEntryPattern)) != null) {
|
|
String value = m.group(1);
|
|
if (!value.equals("0")) {
|
|
throw new RuntimeException("module_entry should be 0 but found: " + line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} 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();
|
|
lineCount = 0;
|
|
|
|
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.stringCount + " strings)");
|
|
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 void validate(MapFile mapFile) {
|
|
int count1 = 0;
|
|
int count2 = 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
|
|
if (oop != 0) {
|
|
mustContain(mapFile.oopToObject, field, oop, false);
|
|
count1 ++;
|
|
}
|
|
if (narrowOop != 0) {
|
|
mustContain(mapFile.narrowOopToObject, field, narrowOop, true);
|
|
count2 ++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
System.out.println("Found " + count1 + " non-null oop field references (normal)");
|
|
System.out.println("Found " + count2 + " non-null oop field references (narrow)");
|
|
|
|
if (mapFile.heapObjectCount() > 0) {
|
|
// heapObjectCount() may be zero if the selected GC doesn't support heap object archiving.
|
|
if (mapFile.stringCount <= 0) {
|
|
throw new RuntimeException("CDS map file should contain at least one string");
|
|
}
|
|
if (count1 < mapFile.stringCount) {
|
|
throw new RuntimeException("CDS map file seems incorrect: " + mapFile.heapObjectCount() +
|
|
" objects (" + mapFile.stringCount + " strings). Each string should" +
|
|
" have one non-null oop field but we found only " + count1 +
|
|
" non-null oop field references");
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void main(String args[]) {
|
|
MapFile mapFile = read(args[0]);
|
|
validate(mapFile);
|
|
}
|
|
}
|