1 /*
2 * Copyright (c) 2023, 2025, 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 // (injected module_entry)
199 // - injected 'module_entry' 'J' @16 0 (0x0000000000000000)
200 static Pattern moduleEntryPattern = Pattern.compile("- injected 'module_entry' 'J' @[0-9]+[ ]+([0-9]+)");
201
202 // -------------------------------------------------------------------------------
203 // Patterns for metaspace objects
204 // -------------------------------------------------------------------------------
205
206 // 0x00000008000d1698: @@ Class 512 [Ljdk.internal.vm.FillerElement;
207 // 0x00000008000d18a0: @@ Class 520 java.lang.Cloneable
208 static Pattern classPattern = Pattern.compile("^0x([0-9a-f]+): @@ Class [ ]*([0-9]+) (.*)");
209
210
211 private static Matcher match(String line, Pattern pattern) {
212 Matcher m = pattern.matcher(line);
213 if (m.find()) {
214 return m;
215 } else {
216 return null;
217 }
218 }
219
220 private static void parseHeapObject(String className, String oop, String narrowOop) throws IOException {
221 HeapObject heapObject = parseHeapObjectImpl(className, oop, narrowOop);
222 mapFile.add(heapObject);
223 }
224
225 private static HeapObject parseHeapObjectImpl(String className, String oop, String narrowOop) throws IOException {
226 HeapObject heapObject = new HeapObject(className, oop, narrowOop);
227 Matcher m;
228
229 nextLine();
230 while (line != null && match(line, rawDataPattern) != null) { // skip raw data
231 nextLine();
232 }
233
234 if (line == null || !line.startsWith(" - ")) {
235 return heapObject;
236 }
237
238 if ((m = match(line, instanceObjKlassPattern)) != null) {
239 heapObject.setKlass(m.group(1), m.group(2));
240 nextLine();
241 if ((m = match(line, fieldsWordsPattern)) == null) {
242 throw new RuntimeException("Expected field size info");
243 }
244 while (true) {
245 nextLine();
246 if (line == null || !line.startsWith(" - ")) {
247 return heapObject;
248 }
249 if (!line.contains("marked metadata pointer")) {
250 if ((m = match(line, oopFieldPattern2)) != null) {
251 heapObject.addOopField(m.group(1), m.group(2), m.group(3), m.group(4));
252 } else if ((m = match(line, oopFieldPattern1)) != null) {
253 heapObject.addOopField(m.group(1), m.group(2), m.group(3), null);
254 } else if ((m = match(line, moduleEntryPattern)) != null) {
255 String value = m.group(1);
256 if (!value.equals("0")) {
257 throw new RuntimeException("module_entry should be 0 but found: " + line);
258 }
259 }
260 }
261 }
262 } else if ((m = match(line, typeArrayKlassPattern)) != null) {
263 heapObject.setKlass(m.group(1), m.group(2));
264 // TODO: read all the array elements
265 while (true) {
266 nextLine();
267 if (line == null || !line.startsWith(" - ")) {
268 return heapObject;
269 }
270 }
271 } else if ((m = match(line, objArrayKlassPattern)) != null) {
272 heapObject.setKlass(m.group(1), m.group(3));
273 // TODO: read all the array elements
274 while (true) {
275 nextLine();
276 if (line == null || !line.startsWith(" - ")) {
277 return heapObject;
278 }
279 }
280 } else {
281 throw new RuntimeException("Expected klass info");
282 }
283 }
284
285 private static void parseClassObject(String className, String addr, String size) throws IOException {
286 mapFile.addClass(className);
287 nextLine();
288 }
289
290 static MapFile mapFile;
291 static BufferedReader reader;
292 static String line = null; // current line being parsed
293 static int lineCount = 0;
294 static String nextLine() throws IOException {
295 line = reader.readLine();
296 ++ lineCount;
297 return line;
298 }
299
300 public static MapFile read(String fileName) {
301 mapFile = new MapFile();
302 lineCount = 0;
303
304 try (BufferedReader r = new BufferedReader(new FileReader(fileName))) {
305 reader = r;
306 nextLine();
307
308 Matcher m;
309 while (line != null) {
310 if ((m = match(line, objPattern2)) != null) {
311 parseHeapObject(m.group(3), m.group(1), m.group(2));
312 } else if ((m = match(line, objPattern1)) != null) {
313 parseHeapObject(m.group(2), m.group(1), null);
314 } else if ((m = match(line, classPattern)) != null) {
315 parseClassObject(m.group(3), m.group(1), m.group(2)); // name, addr, size
316 } else {
317 nextLine();
318 }
319 }
320 return mapFile;
321 } catch (Throwable t) {
322 System.out.println("Error parsing line " + lineCount + ": " + line);
323 throw new RuntimeException(t);
324 } finally {
325 System.out.println("Parsed " + lineCount + " lines in " + fileName);
326 System.out.println("Found " + mapFile.heapObjectCount() + " heap objects ("
327 + mapFile.stringCount + " strings)");
328 mapFile = null;
329 reader = null;
330 line = null;
331 lineCount = 0;
332 }
333 }
334
335 private static void mustContain(HashMap<Long, HeapObject> allObjects, Field field, long pointer, boolean isNarrow) {
336 if (pointer != 0x0 && allObjects.get(pointer) == null) {
337 throw new RuntimeException((isNarrow ? "narrowOop" : "oop") + " pointer 0x" + Long.toHexString(pointer) +
338 " on line " + field.lineCount + " doesn't point to a valid heap object");
339 }
340 }
341
342 public static void validate(MapFile mapFile, String classLoadLogFile) throws IOException {
343 validateOops(mapFile);
344 if (classLoadLogFile != null) {
345 validateClasses(mapFile, classLoadLogFile);
346 }
347 }
348
349 // Check that each oop fields in the HeapObjects must point to a valid HeapObject.
350 static void validateOops(MapFile mapFile) {
351 int count1 = 0;
352 int count2 = 0;
353 for (HeapObject heapObject : mapFile.heapObjects) {
354 if (heapObject.fields != null) {
355 for (Field field : heapObject.fields) {
356 HeapAddress referentAddress = field.referentAddress;
357 long oop = referentAddress.oop;
358 long narrowOop = referentAddress.narrowOop;
359 // Is this test actually doing something?
360 // To see how an invalidate pointer may be found, change oop in the
361 // following line to oop+1
362 if (oop != 0) {
363 mustContain(mapFile.oopToObject, field, oop, false);
364 count1 ++;
365 }
366 if (narrowOop != 0) {
367 mustContain(mapFile.narrowOopToObject, field, narrowOop, true);
368 count2 ++;
369 }
370 }
371 }
372 }
373 System.out.println("Found " + count1 + " non-null oop field references (normal)");
374 System.out.println("Found " + count2 + " non-null oop field references (narrow)");
375
376 if (mapFile.heapObjectCount() > 0) {
377 // heapObjectCount() may be zero if the selected GC doesn't support heap object archiving.
378 if (mapFile.stringCount <= 0) {
379 throw new RuntimeException("AOT map file should contain at least one string");
380 }
381 if (count1 < mapFile.stringCount) {
382 throw new RuntimeException("AOT map file seems incorrect: " + mapFile.heapObjectCount() +
383 " objects (" + mapFile.stringCount + " strings). Each string should" +
384 " have one non-null oop field but we found only " + count1 +
385 " non-null oop field references");
386 }
387 }
388 }
389
390 // classLoadLogFile should be generated with -Xlog:class+load:file=<classLoadLogFile>:none:filesize=0
391 // Check that every class loaded from "source: shared objects file" have an entry inside the mapFile.
392 static void validateClasses(MapFile mapFile, String classLoadLogFile) throws IOException {
393 try (BufferedReader r = new BufferedReader(new FileReader(classLoadLogFile))) {
394 String line;
395 String suffix = " source: shared objects file";
396 int suffixLen = suffix.length();
397 while ((line = r.readLine()) != null) {
398 if (line.endsWith(suffix)) {
399 String className = line.substring(0, line.length() - suffixLen);
400 if (!mapFile.hasClass(className)) {
401 throw new RuntimeException("AOT map file is missing class " + className);
402 }
403 }
404 }
405 }
406 }
407
408 public static void main(String args[]) throws IOException {
409 MapFile mapFile = read(args[0]);
410 validate(mapFile, null);
411 }
412 }