1 /*
2 * Copyright (c) 2023, 2026, 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.HashSet;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32
33 /*
34
35 This is a simple parser for parsing the output of
36
37 java -Xshare:dump -Xlog:aot+map=debug,aot+map+oops=trace:file=aot.map:none:filesize=0
38
39 The map file contains patterns like this for the heap objects:
40
41 ======================================================================
42 0x00000000ffe00000: @@ Object (0xffe00000) java.lang.String ""
43 - klass: 'java/lang/String' 0x0000000800010220
44 - fields (3 words):
45 - private 'hash' 'I' @12 0 (0x00000000)
46 - private final 'coder' 'B' @16 0 (0x00)
47 - private 'hashIsZero' 'Z' @17 true (0x01)
48 - injected 'flags' 'B' @18 1 (0x01)
49 - private final 'value' '[B' @20 0x00000000ffe00018 (0xffe00018) [B length: 0
50 0x00000000ffe00018: @@ Object (0xffe00018) [B length: 0
51 - klass: {type array byte} 0x00000008000024d8
52 ======================================================================
53
54 Currently this parser just check the output related to JDK-8308903.
55 I.e., each oop field must point to a valid HeapObject. For example, the 'value' field
56 in the String must point to a valid byte array.
57
58 This parser can be extended to check for the other parts of the map file, or perform
59 more analysis on the HeapObjects.
60
61 */
62
63 public class AOTMapReader {
64 public static class MapFile {
65 HashSet<String> classes = new HashSet<>();
66 ArrayList<HeapObject> heapObjects = new ArrayList<>();
67 HashMap<Long, HeapObject> oopToObject = new HashMap<>();
68 HashMap<Long, HeapObject> narrowOopToObject = new HashMap<>();
69 public int stringCount = 0;
70
71 void add(HeapObject heapObject) {
72 heapObjects.add(heapObject);
73 oopToObject.put(heapObject.address.oop, heapObject);
74 if (heapObject.address.narrowOop != 0) {
75 narrowOopToObject.put(heapObject.address.narrowOop, heapObject);
76 }
77 if (heapObject.className.equals("java.lang.String")) {
78 stringCount ++;
79 }
80 }
81
82 public int heapObjectCount() {
83 return heapObjects.size();
84 }
85
86 void addClass(String className) {
87 classes.add(className);
88 }
89
90 public boolean hasClass(String className) {
91 return classes.contains(className);
92 }
93
94 public void shouldHaveClass(String className) {
95 if (!hasClass(className)) {
96 throw new RuntimeException("AOT map file is missing class " + className);
97 }
98 }
99 }
100
101 public static class HeapAddress {
102 long oop;
103 long narrowOop;
104
105 HeapAddress(String oopStr, String narrowOopStr) {
106 oop = Long.parseUnsignedLong(oopStr, 16);
107 if (narrowOopStr != null) {
108 narrowOop = Long.parseUnsignedLong(narrowOopStr, 16);
109 }
110 }
111 }
112
113 public static class Klass {
114 long address;
115 String name;
116
117 static Klass getKlass(String name, String addr) {
118 // TODO: look up from a table of known Klasses
119 Klass k = new Klass();
120 k.name = name;
121 k.address = Long.parseUnsignedLong(addr, 16);
122 return k;
123 }
124 }
125
126 public static class HeapObject {
127 HeapAddress address;
128 ArrayList<Field> fields;
129 String className;
130 Klass klass;
131
132 HeapObject(String className, String oop, String narrowOop) {
133 this.className = className;
134 address = new HeapAddress(oop, narrowOop);
135 }
136
137 void setKlass(String klassName, String address) {
138 klass = Klass.getKlass(klassName, address);
139 }
140
141 void addOopField(String name, String offset, String oopStr, String narrowOopStr) {
142 if (fields == null) {
143 fields = new ArrayList<Field>();
144 }
145 fields.add(new Field(name, offset, oopStr, narrowOopStr));
146 }
147 }
148
149 public static class Field {
150 String name;
151 int offset;
152 HeapAddress referentAddress; // non-null iff this is an object field
153 int lineCount;
154
155 Field(String name, String offset, String oopStr, String narrowOopStr) {
156 this.name = name;
157 this.offset = Integer.parseInt(offset);
158 this.referentAddress = new HeapAddress(oopStr, narrowOopStr);
159 this.lineCount = AOTMapReader.lineCount;
160 }
161 }
162
163 // 0x00000007ffc00000: 4a5b8701 00000063 00010290 00000000 00010100 fff80003
164 static Pattern rawDataPattern = Pattern.compile("^0x([0-9a-f]+): *( [0-9a-f]+)+ *$");
165
166 // -------------------------------------------------------------------------------
167 // Patterns for heap objects
168 // -------------------------------------------------------------------------------
169
170 // (one address)
171 // 0x00000007ffc00000: @@ Object java.lang.String
172 static Pattern objPattern1 = Pattern.compile("^0x([0-9a-f]+): @@ Object ([^ ]*)");
173
174 // (two addresses)
175 // 0x00000007ffc00000: @@ Object (0xfff80000) java.lang.String
176 static Pattern objPattern2 = Pattern.compile("^0x([0-9a-f]+): @@ Object [(]0x([0-9a-f]+)[)] ([^ ]*)");
177
178 // - klass: 'java/lang/String' 0x0000000800010290
179 static Pattern instanceObjKlassPattern = Pattern.compile("^ - klass: '([^']+)' 0x([0-9a-f]+)");
180
181 // - klass: {type array byte} 0x00000008000024c8
182 static Pattern typeArrayKlassPattern = Pattern.compile("^ - klass: [{]type array ([a-z]+)[}] 0x([0-9a-f]+)");
183
184 // - klass: 'java/lang/Object'[] 0x00000008000013e0
185 static Pattern objArrayKlassPattern = Pattern.compile("^ - klass: ('[^']+'(\\[\\])+) 0x([0-9a-f]+)");
186
187 // - fields (3 words):
188 static Pattern fieldsWordsPattern = Pattern.compile("^ - fields [(]([0-9]+) words[)]:$");
189
190 // (one address)
191 // - final 'key' 'Ljava/lang/Object;' @16 0x00000007ffc68260 java.lang.String
192 static Pattern oopFieldPattern1 = Pattern.compile(" - [^']* '([^']+)'.*@([0-9]+) 0x([0-9a-f]+) (.*)");
193
194 // (two addresses)
195 // - final 'key' 'Ljava/lang/Object;' @16 0x00000007ffc68260 (0xfff8d04c) java.lang.String
196 static Pattern oopFieldPattern2 = Pattern.compile(" - [^']* '([^']+)'.*@([0-9]+) 0x([0-9a-f]+) [(]0x([0-9a-f]+)[)] (.*)");
197
198 // - Flat inline type element 'java/lang/Integer': - Index 1 offset 24:
199 // or
200 // - Flat inline type field 'java/lang/Integer':
201 static Pattern flatFieldPattern = Pattern.compile(" - Flat inline type field '([^']+)':\\s*(null)?\\s*$");
202 static Pattern flatElementPattern = Pattern.compile(" - Flat inline type element '([^']+)':\\s*- Index\\s+(\\d+)\\s+offset\\s+(\\d+):\\s*(null)?\\s*$");
203
204 static Pattern flatNullFreeFieldPattern = Pattern.compile(" - Flat inline null-free type field '([^']+)':\\s*$");
205 static Pattern flatNullFreeElementPattern = Pattern.compile(" - Flat inline null-free type element '([^']+)':\\s*- Index\\s+(\\d+)\\s+offset\\s+(\\d+):\\s*$");
206
207 static Pattern nullMarkerPattern = Pattern.compile(" - \\[null_marker\\] @[0-9]+ Field marked as (.*)");
208
209 // (injected module_entry)
210 // - injected 'module_entry' 'J' @16 0 (0x0000000000000000)
211 static Pattern moduleEntryPattern = Pattern.compile("- injected 'module_entry' 'J' @[0-9]+[ ]+([0-9]+)");
212
213 // -------------------------------------------------------------------------------
214 // Patterns for metaspace objects
215 // -------------------------------------------------------------------------------
216
217 // 0x00000008000d1698: @@ Class 512 [Ljdk.internal.vm.FillerElement;
218 // 0x00000008000d18a0: @@ Class 520 java.lang.Cloneable
219 static Pattern classPattern = Pattern.compile("^0x([0-9a-f]+): @@ Class [ ]*([0-9]+) (.*)");
220
221
222 private static Matcher match(String line, Pattern pattern) {
223 Matcher m = pattern.matcher(line);
224 if (m.find()) {
225 return m;
226 } else {
227 return null;
228 }
229 }
230
231 private static void parseHeapObject(String className, String oop, String narrowOop) throws IOException {
232 HeapObject heapObject = parseHeapObjectImpl(className, oop, narrowOop);
233 mapFile.add(heapObject);
234 }
235
236 private static void parseFlatNullFree(Matcher m) throws IOException {
237 if (line == null || !line.matches("^\s*-.*")) {
238 throw new RuntimeException("Malformed logging of flat field: " + line);
239 } else if ((m = match(line, flatFieldPattern)) != null) {
240 parseFlatField(m);
241 } else if ((m = match(line, flatElementPattern)) != null) {
242 parseFlatElement(m);
243 } else {
244 // Nested field
245 }
246 }
247
248 private static void parseFlatFieldOrElementHelper(Matcher m, String value) throws IOException {
249 nextLine();
250 while (line == null || (m = match(line, nullMarkerPattern)) == null) {
251 parseFlatNullFree(m);
252 nextLine();
253 }
254
255 String nullMarker = m.group(1);
256 if (nullMarker == null) {
257 throw new RuntimeException("missing null_marker: " + line);
258 } else {
259 nullMarker = nullMarker.replace(" ", "");
260 if (value.equals("null") != nullMarker.equals("null")) {
261 throw new RuntimeException("Incorrect null_marker value: " + value + " vs " + nullMarker);
262 }
263 }
264 }
265
266 private static void parseFlatElement(Matcher m) throws IOException {
267 String value = m.group(4);
268 if (value != null) {
269 value = value.replace(" ", ""); // Remove spaces
270 } else {
271 value = "";
272 }
273 parseFlatFieldOrElementHelper(m, value);
274 }
275
276 private static void parseFlatField(Matcher m) throws IOException {
277 String value = m.group(2);
278 if (value != null) {
279 value = value.replace(" ", ""); // Remove spaces
280 } else {
281 value = "";
282 }
283 parseFlatFieldOrElementHelper(m, value);
284 }
285
286 private static HeapObject parseHeapObjectImpl(String className, String oop, String narrowOop) throws IOException {
287 HeapObject heapObject = new HeapObject(className, oop, narrowOop);
288 Matcher m;
289
290 nextLine();
291 while (line != null && match(line, rawDataPattern) != null) { // skip raw data
292 nextLine();
293 }
294
295 if (line == null || !line.startsWith(" - ")) {
296 return heapObject;
297 }
298
299 if ((m = match(line, instanceObjKlassPattern)) != null) {
300 heapObject.setKlass(m.group(1), m.group(2));
301 nextLine();
302 if ((m = match(line, fieldsWordsPattern)) == null) {
303 throw new RuntimeException("Expected field size info");
304 }
305 while (true) {
306 nextLine();
307 if (line == null || !line.matches("^\s*-.*")) {
308 return heapObject;
309 }
310 if (!line.contains("marked metadata pointer")) {
311 if ((m = match(line, oopFieldPattern2)) != null) {
312 heapObject.addOopField(m.group(1), m.group(2), m.group(3), m.group(4));
313 } else if ((m = match(line, oopFieldPattern1)) != null) {
314 heapObject.addOopField(m.group(1), m.group(2), m.group(3), null);
315 } else if ((m = match(line, flatFieldPattern)) != null) {
316 parseFlatField(m);
317 } else if ((m = match(line, flatElementPattern)) != null) {
318 parseFlatElement(m);
319 } else if ((m = match(line, flatNullFreeFieldPattern)) != null ||
320 (m = match(line, flatNullFreeElementPattern)) != null) {
321 parseFlatNullFree(m);
322 } else if ((m = match(line, moduleEntryPattern)) != null) {
323 String value = m.group(1);
324 if (!value.equals("0")) {
325 throw new RuntimeException("module_entry should be 0 but found: " + line);
326 }
327 }
328 }
329 }
330 } else if ((m = match(line, typeArrayKlassPattern)) != null) {
331 heapObject.setKlass(m.group(1), m.group(2));
332 // TODO: read all the array elements
333 while (true) {
334 nextLine();
335 if (line == null || !line.startsWith(" - ")) {
336 return heapObject;
337 }
338 }
339 } else if ((m = match(line, objArrayKlassPattern)) != null) {
340 heapObject.setKlass(m.group(1), m.group(3));
341 // TODO: read all the array elements
342 while (true) {
343 nextLine();
344 if (line == null || !line.matches("^\s*-.*")) { // Check for "-" with leading spaces
345 return heapObject;
346 }
347
348 if ((m = match(line, flatElementPattern)) != null) {
349 parseFlatElement(m);
350 } else if ((m = match(line, flatNullFreeElementPattern)) != null) {
351 parseFlatNullFree(m);
352 }
353 }
354 } else {
355 throw new RuntimeException("Expected klass info");
356 }
357 }
358
359 private static void parseClassObject(String className, String addr, String size) throws IOException {
360 mapFile.addClass(className);
361 nextLine();
362 }
363
364 static MapFile mapFile;
365 static BufferedReader reader;
366 static String line = null; // current line being parsed
367 static int lineCount = 0;
368 static String nextLine() throws IOException {
369 line = reader.readLine();
370 ++ lineCount;
371 return line;
372 }
373
374 public static MapFile read(String fileName) {
375 mapFile = new MapFile();
376 lineCount = 0;
377
378 try (BufferedReader r = new BufferedReader(new FileReader(fileName))) {
379 reader = r;
380 nextLine();
381
382 Matcher m;
383 while (line != null) {
384 if ((m = match(line, objPattern2)) != null) {
385 parseHeapObject(m.group(3), m.group(1), m.group(2));
386 } else if ((m = match(line, objPattern1)) != null) {
387 parseHeapObject(m.group(2), m.group(1), null);
388 } else if ((m = match(line, classPattern)) != null) {
389 parseClassObject(m.group(3), m.group(1), m.group(2)); // name, addr, size
390 } else {
391 nextLine();
392 }
393 }
394 return mapFile;
395 } catch (Throwable t) {
396 System.out.println("Error parsing line " + lineCount + ": " + line);
397 throw new RuntimeException(t);
398 } finally {
399 System.out.println("Parsed " + lineCount + " lines in " + fileName);
400 System.out.println("Found " + mapFile.heapObjectCount() + " heap objects ("
401 + mapFile.stringCount + " strings)");
402 mapFile = null;
403 reader = null;
404 line = null;
405 lineCount = 0;
406 }
407 }
408
409 private static void mustContain(HashMap<Long, HeapObject> allObjects, Field field, long pointer, boolean isNarrow) {
410 if (allObjects.get(pointer) == null) {
411 throw new RuntimeException((isNarrow ? "narrowOop" : "oop") + " pointer 0x" + Long.toHexString(pointer) +
412 " on line " + field.lineCount + " doesn't point to a valid heap object");
413 }
414 }
415
416 public static void validate(MapFile mapFile, String classLoadLogFile) throws IOException {
417 validateOops(mapFile);
418 if (classLoadLogFile != null) {
419 validateClasses(mapFile, classLoadLogFile);
420 }
421 }
422
423 // Check that each oop fields in the HeapObjects must point to a valid HeapObject.
424 static void validateOops(MapFile mapFile) {
425 int count1 = 0;
426 int count2 = 0;
427 for (HeapObject heapObject : mapFile.heapObjects) {
428 if (heapObject.fields != null) {
429 for (Field field : heapObject.fields) {
430 HeapAddress referentAddress = field.referentAddress;
431 long oop = referentAddress.oop;
432 long narrowOop = referentAddress.narrowOop;
433 // Is this test actually doing something?
434 // To see how an invalidate pointer may be found, change oop in the
435 // following line to oop+1
436 if (oop != 0) {
437 mustContain(mapFile.oopToObject, field, oop, false);
438 count1 ++;
439 }
440 if (narrowOop != 0) {
441 mustContain(mapFile.narrowOopToObject, field, narrowOop, true);
442 count2 ++;
443 }
444 }
445 }
446 }
447 System.out.println("Found " + count1 + " non-null oop field references (normal)");
448 System.out.println("Found " + count2 + " non-null oop field references (narrow)");
449
450 if (mapFile.heapObjectCount() > 0) {
451 // heapObjectCount() may be zero if the selected GC doesn't support heap object archiving.
452 if (mapFile.stringCount <= 0) {
453 throw new RuntimeException("AOT map file should contain at least one string");
454 }
455 if (count1 < mapFile.stringCount) {
456 throw new RuntimeException("AOT map file seems incorrect: " + mapFile.heapObjectCount() +
457 " objects (" + mapFile.stringCount + " strings). Each string should" +
458 " have one non-null oop field but we found only " + count1 +
459 " non-null oop field references");
460 }
461 }
462 }
463
464 // classLoadLogFile should be generated with -Xlog:class+load:file=<classLoadLogFile>:none:filesize=0
465 // Check that every class loaded from "source: shared objects file" have an entry inside the mapFile.
466 static void validateClasses(MapFile mapFile, String classLoadLogFile) throws IOException {
467 try (BufferedReader r = new BufferedReader(new FileReader(classLoadLogFile))) {
468 String line;
469 String suffix = " source: shared objects file";
470 int suffixLen = suffix.length();
471 while ((line = r.readLine()) != null) {
472 if (line.endsWith(suffix)) {
473 String className = line.substring(0, line.length() - suffixLen);
474 if (!mapFile.hasClass(className)) {
475 throw new RuntimeException("AOT map file is missing class " + className);
476 }
477 }
478 }
479 }
480 }
481
482 public static void main(String args[]) throws IOException {
483 MapFile mapFile = read(args[0]);
484 validate(mapFile, null);
485 }
486 }