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 }