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 }
--- EOF ---