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