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