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