1 /*
  2  * Copyright (c) 2023, 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     private static Matcher match(String line, Pattern pattern) {
179         Matcher m = pattern.matcher(line);
180         if (m.find()) {
181             return m;
182         } else {
183             return null;
184         }
185     }
186 
187     private static void parseHeapObject(String className, String oop, String narrowOop) throws IOException {
188         HeapObject heapObject = parseHeapObjectImpl(className, oop, narrowOop);
189         mapFile.add(heapObject);
190     }
191 
192     private static HeapObject parseHeapObjectImpl(String className, String oop, String narrowOop) throws IOException {
193         HeapObject heapObject = new HeapObject(className, oop, narrowOop);
194         Matcher m;
195 
196         nextLine();
197         while (line != null && match(line, rawDataPattern) != null) { // skip raw data
198             nextLine();
199         }
200 
201         if (line == null || !line.startsWith(" - ")) {
202             return heapObject;
203         }
204 
205         if ((m = match(line, instanceObjKlassPattern)) != null) {
206             heapObject.setKlass(m.group(1), m.group(2));
207             nextLine();
208             if ((m = match(line, fieldsWordsPattern)) == null) {
209                 throw new RuntimeException("Expected field size info");
210             }
211             while (true) {
212                 nextLine();
213                 if (line == null || !line.startsWith(" - ")) {
214                     return heapObject;
215                 }
216                 if (!line.contains("marked metadata pointer")) {
217                     if ((m = match(line, oopFieldPattern2)) != null) {
218                         heapObject.addOopField(m.group(1), m.group(2), m.group(3), m.group(4));
219                     } else if ((m = match(line, oopFieldPattern1)) != null) {
220                         heapObject.addOopField(m.group(1), m.group(2), m.group(3), null);
221                     }
222                 }
223             }
224         } else if ((m = match(line, typeArrayKlassPattern)) != null) {
225             heapObject.setKlass(m.group(1), m.group(2));
226             // TODO: read all the array elements
227             while (true) {
228                 nextLine();
229                 if (line == null || !line.startsWith(" - ")) {
230                     return heapObject;
231                 }
232             }
233         } else if ((m = match(line, objArrayKlassPattern)) != null) {
234             heapObject.setKlass(m.group(1), m.group(3));
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 {
243             throw new RuntimeException("Expected klass info");
244         }
245     }
246 
247     static MapFile mapFile;
248     static BufferedReader reader;
249     static String line = null; // current line being parsed
250     static int lineCount = 0;
251     static String nextLine()  throws IOException {
252         line = reader.readLine();
253         ++ lineCount;
254         return line;
255     }
256 
257     public static MapFile read(String fileName) {
258         mapFile = new MapFile();
259         lineCount = 0;
260 
261         try (BufferedReader r = new BufferedReader(new FileReader(fileName))) {
262             reader = r;
263             nextLine();
264 
265             Matcher m;
266             while (line != null) {
267                 if ((m = match(line, objPattern2)) != null) {
268                     parseHeapObject(m.group(3), m.group(1), m.group(2));
269                 } else if ((m = match(line, objPattern1)) != null) {
270                     parseHeapObject(m.group(2), m.group(1), null);
271                 } else {
272                     nextLine();
273                 }
274             }
275             return mapFile;
276         } catch (Throwable t) {
277             System.out.println("Error parsing line " + lineCount + ": " + line);
278             throw new RuntimeException(t);
279         } finally {
280             System.out.println("Parsed " + lineCount + " lines in " + fileName);
281             System.out.println("Found " + mapFile.heapObjectCount() + " heap objects ("
282                                + mapFile.stringCount + " strings)");
283             mapFile = null;
284             reader = null;
285             line = null;
286             lineCount = 0;
287         }
288     }
289 
290     private static void mustContain(HashMap<Long, HeapObject> allObjects, Field field, long pointer, boolean isNarrow) {
291         if (allObjects.get(pointer) == null) {
292             throw new RuntimeException((isNarrow ? "narrowOop" : "oop") + " pointer 0x" + Long.toHexString(pointer) +
293                                        " on line " + field.lineCount + " doesn't point to a valid heap object");
294         }
295     }
296 
297     // Check that each oop fields in the HeapObjects must point to a valid HeapObject.
298     public static void validate(MapFile mapFile) {
299         int count1 = 0;
300         int count2 = 0;
301         for (HeapObject heapObject : mapFile.heapObjects) {
302             if (heapObject.fields != null) {
303                 for (Field field : heapObject.fields) {
304                     HeapAddress referentAddress = field.referentAddress;
305                     long oop = referentAddress.oop;
306                     long narrowOop = referentAddress.narrowOop;
307                     // Is this test actually doing something?
308                     //     To see how an invalidate pointer may be found, change oop in the
309                     //     following line to oop+1
310                     if (oop != 0) {
311                         mustContain(mapFile.oopToObject, field, oop, false);
312                         count1 ++;
313                     }
314                     if (narrowOop != 0) {
315                         mustContain(mapFile.narrowOopToObject, field, narrowOop, true);
316                         count2 ++;
317                     }
318                 }
319             }
320         }
321         System.out.println("Found " + count1 + " non-null oop field references (normal)");
322         System.out.println("Found " + count2 + " non-null oop field references (narrow)");
323 
324         if (mapFile.heapObjectCount() > 0) {
325             // heapObjectCount() may be zero if the selected GC doesn't support heap object archiving.
326             if (mapFile.stringCount <= 0) {
327                 throw new RuntimeException("CDS map file should contain at least one string");
328             }
329             if (count1 < mapFile.stringCount) {
330                 throw new RuntimeException("CDS map file seems incorrect: " + mapFile.heapObjectCount() +
331                                            " objects (" + mapFile.stringCount + " strings). Each string should" +
332                                            " have one non-null oop field but we found only " + count1 +
333                                            " non-null oop field references");
334             }
335         }
336     }
337 
338     public static void main(String args[]) {
339         MapFile mapFile = read(args[0]);
340         validate(mapFile);
341     }
342 }