1 /*
2 * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 import java.io.BufferedReader;
25 import java.io.FileReader;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32 /*
33
34 This is a simple parser for parsing the output of
35
36 java -Xshare:dump -Xlog:cds+map=debug,cds+map+oops=trace:file=cds.map:none:filesize=0
37
38 The map file contains patterns like this for the heap objects:
39
40 ======================================================================
41 0x00000000ffe00000: @@ Object (0xffe00000) java.lang.String ""
42 - klass: 'java/lang/String' 0x0000000800010220
43 - fields (3 words):
44 - private 'hash' 'I' @12 0 (0x00000000)
45 - private final 'coder' 'B' @16 0 (0x00)
46 - private 'hashIsZero' 'Z' @17 true (0x01)
47 - injected 'flags' 'B' @18 1 (0x01)
48 - private final 'value' '[B' @20 0x00000000ffe00018 (0xffe00018) [B length: 0
49 0x00000000ffe00018: @@ Object (0xffe00018) [B length: 0
50 - klass: {type array byte} 0x00000008000024d8
51 ======================================================================
52
53 Currently this parser just check the output related to JDK-8308903.
54 I.e., each oop field must point to a valid HeapObject. For example, the 'value' field
55 in the String must point to a valid byte array.
56
57 This parser can be extended to check for the other parts of the map file, or perform
58 more analysis on the HeapObjects.
59
60 */
61
62 public class CDSMapReader {
63 public static class MapFile {
64 ArrayList<HeapObject> heapObjects = new ArrayList<>();
65 HashMap<Long, HeapObject> oopToObject = new HashMap<>();
66 HashMap<Long, HeapObject> narrowOopToObject = new HashMap<>();
67 public int stringCount = 0;
68
69 void add(HeapObject heapObject) {
70 heapObjects.add(heapObject);
71 oopToObject.put(heapObject.address.oop, heapObject);
72 if (heapObject.address.narrowOop != 0) {
73 narrowOopToObject.put(heapObject.address.narrowOop, heapObject);
74 }
75 if (heapObject.className.equals("java.lang.String")) {
76 stringCount ++;
77 }
78 }
79
80 public int heapObjectCount() {
81 return heapObjects.size();
82 }
83 }
84
85 public static class HeapAddress {
86 long oop;
87 long narrowOop;
88
89 HeapAddress(String oopStr, String narrowOopStr) {
90 oop = Long.parseUnsignedLong(oopStr, 16);
91 if (narrowOopStr != null) {
92 narrowOop = Long.parseUnsignedLong(narrowOopStr, 16);
93 }
94 }
95 }
96
97 public static class Klass {
98 long address;
99 String name;
100
101 static Klass getKlass(String name, String addr) {
102 // TODO: look up from a table of known Klasses
103 Klass k = new Klass();
104 k.name = name;
105 k.address = Long.parseUnsignedLong(addr, 16);
106 return k;
107 }
108 }
109
110 public static class HeapObject {
111 HeapAddress address;
112 ArrayList<Field> fields;
113 String className;
114 Klass klass;
115
116 HeapObject(String className, String oop, String narrowOop) {
117 this.className = className;
118 address = new HeapAddress(oop, narrowOop);
119 }
120
121 void setKlass(String klassName, String address) {
122 klass = Klass.getKlass(klassName, address);
123 }
124
125 void addOopField(String name, String offset, String oopStr, String narrowOopStr) {
126 if (fields == null) {
127 fields = new ArrayList<Field>();
128 }
129 fields.add(new Field(name, offset, oopStr, narrowOopStr));
130 }
131 }
132
133 public static class Field {
134 String name;
135 int offset;
136 HeapAddress referentAddress; // non-null iff this is an object field
137 int lineCount;
138
139 Field(String name, String offset, String oopStr, String narrowOopStr) {
140 this.name = name;
141 this.offset = Integer.parseInt(offset);
142 this.referentAddress = new HeapAddress(oopStr, narrowOopStr);
143 this.lineCount = CDSMapReader.lineCount;
144 }
145 }
146
147 // 0x00000007ffc00000: 4a5b8701 00000063 00010290 00000000 00010100 fff80003
148 static Pattern rawDataPattern = Pattern.compile("^0x([0-9a-f]+): *( [0-9a-f]+)+ *$");
149
150 // (one address)
151 // 0x00000007ffc00000: @@ Object java.lang.String
152 static Pattern objPattern1 = Pattern.compile("^0x([0-9a-f]+): @@ Object ([^ ]*)");
153
154 // (two addresses)
155 // 0x00000007ffc00000: @@ Object (0xfff80000) java.lang.String
156 static Pattern objPattern2 = Pattern.compile("^0x([0-9a-f]+): @@ Object [(]0x([0-9a-f]+)[)] ([^ ]*)");
157
158 // - klass: 'java/lang/String' 0x0000000800010290
159 static Pattern instanceObjKlassPattern = Pattern.compile("^ - klass: '([^']+)' 0x([0-9a-f]+)");
160
161 // - klass: {type array byte} 0x00000008000024c8
162 static Pattern typeArrayKlassPattern = Pattern.compile("^ - klass: [{]type array ([a-z]+)[}] 0x([0-9a-f]+)");
163
164 // - klass: 'java/lang/Object'[] 0x00000008000013e0
165 static Pattern objArrayKlassPattern = Pattern.compile("^ - klass: ('[^']+'(\\[\\])+) 0x([0-9a-f]+)");
166
167 // - fields (3 words):
168 static Pattern fieldsWordsPattern = Pattern.compile("^ - fields [(]([0-9]+) words[)]:$");
169
170 // (one address)
171 // - final 'key' 'Ljava/lang/Object;' @16 0x00000007ffc68260 java.lang.String
172 static Pattern oopFieldPattern1 = Pattern.compile(" - [^']* '([^']+)'.*@([0-9]+) 0x([0-9a-f]+) (.*)");
173
174 // (two addresses)
175 // - final 'key' 'Ljava/lang/Object;' @16 0x00000007ffc68260 (0xfff8d04c) java.lang.String
176 static Pattern oopFieldPattern2 = Pattern.compile(" - [^']* '([^']+)'.*@([0-9]+) 0x([0-9a-f]+) [(]0x([0-9a-f]+)[)] (.*)");
177
178 // (injected module_entry)
179 // - injected 'module_entry' 'J' @16 0 (0x0000000000000000)
180 static Pattern moduleEntryPattern = Pattern.compile("- injected 'module_entry' 'J' @[0-9]+[ ]+([0-9]+)");
181
182 private static Matcher match(String line, Pattern pattern) {
183 Matcher m = pattern.matcher(line);
184 if (m.find()) {
185 return m;
186 } else {
187 return null;
188 }
189 }
190
191 private static void parseHeapObject(String className, String oop, String narrowOop) throws IOException {
192 HeapObject heapObject = parseHeapObjectImpl(className, oop, narrowOop);
193 mapFile.add(heapObject);
194 }
195
196 private static HeapObject parseHeapObjectImpl(String className, String oop, String narrowOop) throws IOException {
197 HeapObject heapObject = new HeapObject(className, oop, narrowOop);
198 Matcher m;
199
200 nextLine();
201 while (line != null && match(line, rawDataPattern) != null) { // skip raw data
202 nextLine();
203 }
204
205 if (line == null || !line.startsWith(" - ")) {
206 return heapObject;
207 }
208
209 if ((m = match(line, instanceObjKlassPattern)) != null) {
210 heapObject.setKlass(m.group(1), m.group(2));
211 nextLine();
212 if ((m = match(line, fieldsWordsPattern)) == null) {
213 throw new RuntimeException("Expected field size info");
214 }
215 while (true) {
216 nextLine();
217 if (line == null || !line.startsWith(" - ")) {
218 return heapObject;
219 }
220 if (!line.contains("marked metadata pointer")) {
221 if ((m = match(line, oopFieldPattern2)) != null) {
222 heapObject.addOopField(m.group(1), m.group(2), m.group(3), m.group(4));
223 } else if ((m = match(line, oopFieldPattern1)) != null) {
224 heapObject.addOopField(m.group(1), m.group(2), m.group(3), null);
225 } else if ((m = match(line, moduleEntryPattern)) != null) {
226 String value = m.group(1);
227 if (!value.equals("0")) {
228 throw new RuntimeException("module_entry should be 0 but found: " + line);
229 }
230 }
231 }
232 }
233 } else if ((m = match(line, typeArrayKlassPattern)) != null) {
234 heapObject.setKlass(m.group(1), m.group(2));
235 // TODO: read all the array elements
236 while (true) {
237 nextLine();
238 if (line == null || !line.startsWith(" - ")) {
239 return heapObject;
240 }
241 }
242 } else if ((m = match(line, objArrayKlassPattern)) != null) {
243 heapObject.setKlass(m.group(1), m.group(3));
244 // TODO: read all the array elements
245 while (true) {
246 nextLine();
247 if (line == null || !line.startsWith(" - ")) {
248 return heapObject;
249 }
250 }
251 } else {
252 throw new RuntimeException("Expected klass info");
253 }
254 }
255
256 static MapFile mapFile;
257 static BufferedReader reader;
258 static String line = null; // current line being parsed
259 static int lineCount = 0;
260 static String nextLine() throws IOException {
261 line = reader.readLine();
262 ++ lineCount;
263 return line;
264 }
265
266 public static MapFile read(String fileName) {
267 mapFile = new MapFile();
268 lineCount = 0;
269
270 try (BufferedReader r = new BufferedReader(new FileReader(fileName))) {
271 reader = r;
272 nextLine();
273
274 Matcher m;
275 while (line != null) {
276 if ((m = match(line, objPattern2)) != null) {
277 parseHeapObject(m.group(3), m.group(1), m.group(2));
278 } else if ((m = match(line, objPattern1)) != null) {
279 parseHeapObject(m.group(2), m.group(1), null);
280 } else {
281 nextLine();
282 }
283 }
284 return mapFile;
285 } catch (Throwable t) {
286 System.out.println("Error parsing line " + lineCount + ": " + line);
287 throw new RuntimeException(t);
288 } finally {
289 System.out.println("Parsed " + lineCount + " lines in " + fileName);
290 System.out.println("Found " + mapFile.heapObjectCount() + " heap objects ("
291 + mapFile.stringCount + " strings)");
292 mapFile = null;
293 reader = null;
294 line = null;
295 lineCount = 0;
296 }
297 }
298
299 private static void mustContain(HashMap<Long, HeapObject> allObjects, Field field, long pointer, boolean isNarrow) {
300 if (pointer != 0x0 && allObjects.get(pointer) == null) {
301 throw new RuntimeException((isNarrow ? "narrowOop" : "oop") + " pointer 0x" + Long.toHexString(pointer) +
302 " on line " + field.lineCount + " doesn't point to a valid heap object");
303 }
304 }
305
306 // Check that each oop fields in the HeapObjects must point to a valid HeapObject.
307 public static void validate(MapFile mapFile) {
308 int count1 = 0;
309 int count2 = 0;
310 for (HeapObject heapObject : mapFile.heapObjects) {
311 if (heapObject.fields != null) {
312 for (Field field : heapObject.fields) {
313 HeapAddress referentAddress = field.referentAddress;
314 long oop = referentAddress.oop;
315 long narrowOop = referentAddress.narrowOop;
316 // Is this test actually doing something?
317 // To see how an invalidate pointer may be found, change oop in the
318 // following line to oop+1
319 if (oop != 0) {
320 mustContain(mapFile.oopToObject, field, oop, false);
321 count1 ++;
322 }
323 if (narrowOop != 0) {
324 mustContain(mapFile.narrowOopToObject, field, narrowOop, true);
325 count2 ++;
326 }
327 }
328 }
329 }
330 System.out.println("Found " + count1 + " non-null oop field references (normal)");
331 System.out.println("Found " + count2 + " non-null oop field references (narrow)");
332
333 if (mapFile.heapObjectCount() > 0) {
334 // heapObjectCount() may be zero if the selected GC doesn't support heap object archiving.
335 if (mapFile.stringCount <= 0) {
336 throw new RuntimeException("CDS map file should contain at least one string");
337 }
338 if (count1 < mapFile.stringCount) {
339 throw new RuntimeException("CDS map file seems incorrect: " + mapFile.heapObjectCount() +
340 " objects (" + mapFile.stringCount + " strings). Each string should" +
341 " have one non-null oop field but we found only " + count1 +
342 " non-null oop field references");
343 }
344 }
345 }
346
347 public static void main(String args[]) {
348 MapFile mapFile = read(args[0]);
349 validate(mapFile);
350 }
351 }
--- EOF ---