1 /* 2 * Copyright (c) 2020, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.internal.misc; 27 28 import java.io.BufferedReader; 29 import java.io.File; 30 import java.io.InputStreamReader; 31 import java.io.InputStream; 32 import java.io.IOException; 33 import java.io.PrintStream; 34 import java.util.Arrays; 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Objects; 39 import java.util.stream.Stream; 40 41 import jdk.internal.access.SharedSecrets; 42 import jdk.internal.util.StaticProperty; 43 44 public class CDS { 45 // Must be in sync with cdsConfig.hpp 46 private static final int IS_DUMPING_ARCHIVE = 1 << 0; 47 private static final int IS_DUMPING_STATIC_ARCHIVE = 1 << 1; 48 private static final int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 2; 49 private static final int IS_USING_ARCHIVE = 1 << 3; 50 private static final int IS_DUMPING_HEAP = 1 << 4; 51 private static final int IS_LOGGING_DYNAMIC_PROXIES = 1 << 5; 52 private static final int IS_DUMPING_PACKAGES = 1 << 6; 53 private static final int IS_DUMPING_PROTECTION_DOMAINS = 1 << 7; 54 private static final int configStatus = getCDSConfigStatus(); 55 56 /** 57 * Should we log the use of lambda form invokers? 58 */ 59 public static boolean isLoggingLambdaFormInvokers() { 60 return (configStatus & IS_LOGGING_LAMBDA_FORM_INVOKERS) != 0; 61 } 62 63 /** 64 * Is the VM writing to a (static or dynamic) CDS archive. 65 */ 66 public static boolean isDumpingArchive() { 67 return (configStatus & IS_DUMPING_ARCHIVE) != 0; 68 } 69 70 /** 71 * Is the VM using at least one CDS archive? 72 */ 73 public static boolean isUsingArchive() { 74 return (configStatus & IS_USING_ARCHIVE) != 0; 75 } 76 77 /** 78 * Is dumping static archive. 79 */ 80 public static boolean isDumpingStaticArchive() { 81 return (configStatus & IS_DUMPING_STATIC_ARCHIVE) != 0; 82 } 83 84 public static boolean isDumpingHeap() { 85 return (configStatus & IS_DUMPING_HEAP) != 0; 86 } 87 88 public static boolean isLoggingDynamicProxies() { 89 return (configStatus & IS_LOGGING_DYNAMIC_PROXIES) != 0; 90 } 91 92 public static boolean isDumpingPackages() { 93 return (configStatus & IS_DUMPING_PACKAGES) != 0; 94 } 95 96 public static boolean isDumpingProtectionDomains() { 97 return (configStatus & IS_DUMPING_PROTECTION_DOMAINS) != 0; 98 } 99 100 private static native int getCDSConfigStatus(); 101 private static native void logLambdaFormInvoker(String line); 102 103 /** 104 * Initialize archived static fields in the given Class using archived 105 * values from CDS dump time. Also initialize the classes of objects in 106 * the archived graph referenced by those fields. 107 * 108 * Those static fields remain as uninitialized if there is no mapped CDS 109 * java heap data or there is any error during initialization of the 110 * object class in the archived graph. 111 */ 112 public static native void initializeFromArchive(Class<?> c); 113 114 /** 115 * Ensure that the native representation of all archived java.lang.Module objects 116 * are properly restored. 117 */ 118 public static native void defineArchivedModules(ClassLoader platformLoader, ClassLoader systemLoader); 119 120 /** 121 * Returns a predictable "random" seed derived from the VM's build ID and version, 122 * to be used by java.util.ImmutableCollections to ensure that archived 123 * ImmutableCollections are always sorted the same order for the same VM build. 124 */ 125 public static native long getRandomSeedForDumping(); 126 127 /** 128 * log lambda form invoker holder, name and method type 129 */ 130 public static void logLambdaFormInvoker(String prefix, String holder, String name, String type) { 131 if (isLoggingLambdaFormInvokers()) { 132 logLambdaFormInvoker(prefix + " " + holder + " " + name + " " + type); 133 } 134 } 135 136 /** 137 * log species 138 */ 139 public static void logSpeciesType(String prefix, String cn) { 140 if (isLoggingLambdaFormInvokers()) { 141 logLambdaFormInvoker(prefix + " " + cn); 142 } 143 } 144 145 public static void logDynamicProxy(ClassLoader loader, String proxyName, 146 Class<?>[] interfaces, int accessFlags) { 147 Objects.requireNonNull(proxyName); 148 Objects.requireNonNull(interfaces); 149 logDynamicProxy0(loader, proxyName, interfaces, accessFlags); 150 } 151 private static native void logDynamicProxy0(ClassLoader loader, String proxyName, 152 Class<?>[] interfaces, int accessFlags); 153 154 static final String DIRECT_HOLDER_CLASS_NAME = "java.lang.invoke.DirectMethodHandle$Holder"; 155 static final String DELEGATING_HOLDER_CLASS_NAME = "java.lang.invoke.DelegatingMethodHandle$Holder"; 156 static final String BASIC_FORMS_HOLDER_CLASS_NAME = "java.lang.invoke.LambdaForm$Holder"; 157 static final String INVOKERS_HOLDER_CLASS_NAME = "java.lang.invoke.Invokers$Holder"; 158 159 private static boolean isValidHolderName(String name) { 160 return name.equals(DIRECT_HOLDER_CLASS_NAME) || 161 name.equals(DELEGATING_HOLDER_CLASS_NAME) || 162 name.equals(BASIC_FORMS_HOLDER_CLASS_NAME) || 163 name.equals(INVOKERS_HOLDER_CLASS_NAME); 164 } 165 166 private static boolean isBasicTypeChar(char c) { 167 return "LIJFDV".indexOf(c) >= 0; 168 } 169 170 private static boolean isValidMethodType(String type) { 171 String[] typeParts = type.split("_"); 172 // check return type (second part) 173 if (typeParts.length != 2 || typeParts[1].length() != 1 174 || !isBasicTypeChar(typeParts[1].charAt(0))) { 175 return false; 176 } 177 // first part 178 if (!isBasicTypeChar(typeParts[0].charAt(0))) { 179 return false; 180 } 181 for (int i = 1; i < typeParts[0].length(); i++) { 182 char c = typeParts[0].charAt(i); 183 if (!isBasicTypeChar(c)) { 184 if (!(c >= '0' && c <= '9')) { 185 return false; 186 } 187 } 188 } 189 return true; 190 } 191 192 // Throw exception on invalid input 193 private static void validateInputLines(String[] lines) { 194 for (String s: lines) { 195 if (!s.startsWith("[LF_RESOLVE]") && !s.startsWith("[SPECIES_RESOLVE]")) { 196 throw new IllegalArgumentException("Wrong prefix: " + s); 197 } 198 199 String[] parts = s.split(" "); 200 boolean isLF = s.startsWith("[LF_RESOLVE]"); 201 202 if (isLF) { 203 if (parts.length != 4) { 204 throw new IllegalArgumentException("Incorrect number of items in the line: " + parts.length); 205 } 206 if (!isValidHolderName(parts[1])) { 207 throw new IllegalArgumentException("Invalid holder class name: " + parts[1]); 208 } 209 if (!isValidMethodType(parts[3])) { 210 throw new IllegalArgumentException("Invalid method type: " + parts[3]); 211 } 212 } else { 213 if (parts.length != 2) { 214 throw new IllegalArgumentException("Incorrect number of items in the line: " + parts.length); 215 } 216 } 217 } 218 } 219 220 /** 221 * called from vm to generate MethodHandle holder classes 222 * @return {@code Object[]} if holder classes can be generated. 223 * @param lines in format of LF_RESOLVE or SPECIES_RESOLVE output 224 */ 225 private static Object[] generateLambdaFormHolderClasses(String[] lines) { 226 Objects.requireNonNull(lines); 227 validateInputLines(lines); 228 Stream<String> lineStream = Arrays.stream(lines); 229 Map<String, byte[]> result = SharedSecrets.getJavaLangInvokeAccess().generateHolderClasses(lineStream); 230 int size = result.size(); 231 Object[] retArray = new Object[size * 2]; 232 int index = 0; 233 for (Map.Entry<String, byte[]> entry : result.entrySet()) { 234 retArray[index++] = entry.getKey(); 235 retArray[index++] = entry.getValue(); 236 }; 237 return retArray; 238 } 239 240 private static native void dumpClassList(String listFileName); 241 private static native void dumpDynamicArchive(String archiveFileName); 242 243 private static String drainOutput(InputStream stream, long pid, String tail, List<String> cmds) { 244 String fileName = "java_pid" + pid + "_" + tail; 245 new Thread( ()-> { 246 try (InputStreamReader isr = new InputStreamReader(stream); 247 BufferedReader rdr = new BufferedReader(isr); 248 PrintStream prt = new PrintStream(fileName)) { 249 prt.println("Command:"); 250 for (String s : cmds) { 251 prt.print(s + " "); 252 } 253 prt.println(""); 254 String line; 255 while((line = rdr.readLine()) != null) { 256 prt.println(line); 257 } 258 } catch (IOException e) { 259 throw new RuntimeException("IOException happens during drain stream to file " + 260 fileName + ": " + e.getMessage()); 261 }}).start(); 262 return fileName; 263 } 264 265 private static String[] excludeFlags = { 266 "-XX:DumpLoadedClassList=", 267 "-XX:+RecordDynamicDumpInfo", 268 "-Xshare:", 269 "-XX:SharedClassListFile=", 270 "-XX:SharedArchiveFile=", 271 "-XX:ArchiveClassesAtExit="}; 272 private static boolean containsExcludedFlags(String testStr) { 273 for (String e : excludeFlags) { 274 if (testStr.contains(e)) { 275 return true; 276 } 277 } 278 return false; 279 } 280 281 /** 282 * called from jcmd VM.cds to dump static or dynamic shared archive 283 * @param isStatic true for dump static archive or false for dynnamic archive. 284 * @param fileName user input archive name, can be null. 285 * @return The archive name if successfully dumped. 286 */ 287 private static String dumpSharedArchive(boolean isStatic, String fileName) throws Exception { 288 String cwd = new File("").getAbsolutePath(); // current dir used for printing message. 289 String currentPid = String.valueOf(ProcessHandle.current().pid()); 290 String archiveFileName = fileName != null ? fileName : 291 "java_pid" + currentPid + (isStatic ? "_static.jsa" : "_dynamic.jsa"); 292 293 String tempArchiveFileName = archiveFileName + ".temp"; 294 File tempArchiveFile = new File(tempArchiveFileName); 295 // The operation below may cause exception if the file or its dir is protected. 296 if (!tempArchiveFile.exists()) { 297 tempArchiveFile.createNewFile(); 298 } 299 tempArchiveFile.delete(); 300 301 if (isStatic) { 302 String listFileName = archiveFileName + ".classlist"; 303 File listFile = new File(listFileName); 304 if (listFile.exists()) { 305 listFile.delete(); 306 } 307 dumpClassList(listFileName); 308 String jdkHome = StaticProperty.javaHome(); 309 String classPath = System.getProperty("java.class.path"); 310 List<String> cmds = new ArrayList<String>(); 311 cmds.add(jdkHome + File.separator + "bin" + File.separator + "java"); // java 312 cmds.add("-cp"); 313 cmds.add(classPath); 314 cmds.add("-Xlog:cds"); 315 cmds.add("-Xshare:dump"); 316 cmds.add("-XX:SharedClassListFile=" + listFileName); 317 cmds.add("-XX:SharedArchiveFile=" + tempArchiveFileName); 318 319 // All runtime args. 320 String[] vmArgs = VM.getRuntimeArguments(); 321 if (vmArgs != null) { 322 for (String arg : vmArgs) { 323 if (arg != null && !containsExcludedFlags(arg)) { 324 cmds.add(arg); 325 } 326 } 327 } 328 329 Process proc = Runtime.getRuntime().exec(cmds.toArray(new String[0])); 330 331 // Drain stdout/stderr to files in new threads. 332 String stdOutFileName = drainOutput(proc.getInputStream(), proc.pid(), "stdout", cmds); 333 String stdErrFileName = drainOutput(proc.getErrorStream(), proc.pid(), "stderr", cmds); 334 335 proc.waitFor(); 336 // done, delete classlist file. 337 listFile.delete(); 338 339 // Check if archive has been successfully dumped. We won't reach here if exception happens. 340 // Throw exception if file is not created. 341 if (!tempArchiveFile.exists()) { 342 throw new RuntimeException("Archive file " + tempArchiveFileName + 343 " is not created, please check stdout file " + 344 cwd + File.separator + stdOutFileName + " or stderr file " + 345 cwd + File.separator + stdErrFileName + " for more detail"); 346 } 347 } else { 348 dumpDynamicArchive(tempArchiveFileName); 349 if (!tempArchiveFile.exists()) { 350 throw new RuntimeException("Archive file " + tempArchiveFileName + 351 " is not created, please check current working directory " + 352 cwd + " for process " + 353 currentPid + " output for more detail"); 354 } 355 } 356 // Override the existing archive file 357 File archiveFile = new File(archiveFileName); 358 if (archiveFile.exists()) { 359 archiveFile.delete(); 360 } 361 if (!tempArchiveFile.renameTo(archiveFile)) { 362 throw new RuntimeException("Cannot rename temp file " + tempArchiveFileName + " to archive file" + archiveFileName); 363 } 364 // Everything goes well, print out the file name. 365 String archiveFilePath = new File(archiveFileName).getAbsolutePath(); 366 System.out.println("The process was attached by jcmd and dumped a " + (isStatic ? "static" : "dynamic") + " archive " + archiveFilePath); 367 return archiveFilePath; 368 } 369 }