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 }