diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 61f183175677..5e618de534b8 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -3,8 +3,8 @@ This changelog summarizes major changes to GraalVM Native Image. ## GraalVM for JDK 24 (Internal Version 24.2.0) -* (GR-55708) (Alibaba contribution) Support for running premain of java agents at runtime as an experimental feature. At build time, `-H:PremainClasses= option` is used to set the premain classes. -At runtime, premain runtime options are set along with main class' arguments in the format of `-XX-premain:[class]:[options]`. +* (GR-55708) (Alibaba contribution) Support for running premain methods of Java agents at runtime as an experimental feature. At build time, `-H:PremainClasses` is used to set the premain classes. +At runtime, premain runtime options are set along with main class' arguments in the format of `-XXpremain:[class]:[options]`. * (GR-54476): Issue a deprecation warning on first use of a legacy `graal.` prefix (see GR-49960 in [Compiler changelog](../compiler/CHANGELOG.md)). The warning is planned to be replaced by an error in GraalVM for JDK 25. * (GR-48384) Added a GDB Python script (`gdb-debughelpers.py`) to improve the Native Image debugging experience. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java index f1ad4c4bc205..6df39db9fd5d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java @@ -166,9 +166,13 @@ public List getInputArguments() { } public static void invokeMain(String[] args) throws Throwable { - PreMainSupport preMainSupport = ImageSingletons.lookup(PreMainSupport.class); - String[] mainArgs = preMainSupport.retrievePremainArgs(args); - preMainSupport.invokePremain(); + String[] mainArgs = args; + if (ImageSingletons.contains(PreMainSupport.class)) { + PreMainSupport preMainSupport = ImageSingletons.lookup(PreMainSupport.class); + mainArgs = preMainSupport.retrievePremainArgs(args); + preMainSupport.invokePremain(); + } + JavaMainSupport javaMainSupport = ImageSingletons.lookup(JavaMainSupport.class); if (javaMainSupport.mainNonstatic) { Object instance = javaMainSupport.javaMainClassCtorHandle.invoke(); @@ -181,6 +185,7 @@ public static void invokeMain(String[] args) throws Throwable { if (javaMainSupport.mainWithoutArgs) { javaMainSupport.javaMainHandle.invokeExact(); } else { + /* We really need to pass a String[] without any casting. */ javaMainSupport.javaMainHandle.invokeExact(mainArgs); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/PreMainSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/PreMainSupport.java index 8adef58b5967..a3ad7c23b46f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/PreMainSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/PreMainSupport.java @@ -26,15 +26,6 @@ package com.oracle.svm.core; -import com.oracle.svm.core.c.NonmovableArrays; -import com.oracle.svm.core.code.CodeInfo; -import com.oracle.svm.core.code.CodeInfoAccess; -import com.oracle.svm.core.code.CodeInfoTable; -import com.oracle.svm.core.jdk.ModuleNative; -import com.oracle.svm.core.util.VMError; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; - import java.lang.instrument.ClassDefinition; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; @@ -49,42 +40,45 @@ import java.util.Set; import java.util.jar.JarFile; +import org.graalvm.nativeimage.ImageInfo; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.jdk.ModuleNative; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ModuleSupport; + /** - * Java agent can do instrumentation initialization work in premain phase. This class supports - * registering such premain methods at native image build time and invoking them at native image - * runtime.
- * JVM supports two kind of premain methods: + * Java agents can do initialization work before the main method is invoked. This class supports + * registering such premain methods at native image build-time and invoking them at native image + * run-time. + * + * Two different kinds of premain methods are supported: *
    *
  1. {@code public static void premain(String agentArgs, Instrumentation inst)}
  2. *
  3. {@code public static void premain(String agentArgs)}
  4. *
- * For the first one, at registration time we will set the second parameter with an instance of - * {@link NativeImageNoOpRuntimeInstrumentation} class which does not do any actual work as no - * instrumentation can do in native code at runtime. - *

- * Be noticed, the original agent premain method may not work well at native image runtime - * even if the input {@link Instrumentation} class is replaced with - * {@link NativeImageNoOpRuntimeInstrumentation}. - *

- *

- * It is the agent developers' responsibility to implement a native version of their agent premain - * method. It can be implemented in two ways: + * + * For the first one, we set the second parameter to an instance of + * {@link NativeImageNoOpRuntimeInstrumentation} as instrumentation is not supported at run-time. + * + *

+ * + * Please note: unmodified premain methods may not work well with native image. It is the + * agent developers' responsibility to implement a native image-specific version of their premain + * method. Below are a few approaches how to determine if the premain method is executed by native + * image: *

- *

+ * + * As a last resort, it is also possible to substitute a premain method with a native image-specific + * version. */ public class PreMainSupport { @@ -110,14 +104,17 @@ public void registerPremainMethod(String className, Method executable, Object... } /** - * Retrieve premain options from input args. Keep premain options and return the rest args as - * main args. Multiple agent options should be given in separated {@code -XX-premain:} leading - * arguments. The premain options format:
- * -XX-premain:[full.qualified.premain.class]:[premain options] - * -XX-premain:[full.qualified.premain.class2]:[premain options]
+ * Retrieves the premain options and stores them internally. Returns the remaining arguments so + * that they can be passed to the Java main method. If multiple Java agents are used, a separate + * {@code -XXpremain:} argument needs to be specified for each agent, e.g.: + * + *
+     * -XXpremain:[full.qualified.premain.class1]:[premain options]
+     * -XXpremain:[full.qualified.premain.class2]:[premain options]
+     * 
* - * @param args original arguments for premain and main - * @return arguments for main class + * @param args arguments for premain and main + * @return arguments for the Java main method */ public String[] retrievePremainArgs(String[] args) { if (args == null) { @@ -127,7 +124,7 @@ public String[] retrievePremainArgs(String[] args) { for (String arg : args) { if (arg.startsWith(PREMAIN_OPTION_PREFIX)) { String premainOptionKeyValue = arg.substring(PREMAIN_OPTION_PREFIX.length()); - String[] pair = premainOptionKeyValue.split(":"); + String[] pair = SubstrateUtil.split(premainOptionKeyValue, ":"); if (pair.length == 2) { premainOptions.put(pair[0], pair[1]); } @@ -140,7 +137,6 @@ public String[] retrievePremainArgs(String[] args) { public void invokePremain() { for (PremainMethod premainMethod : premainMethods) { - Object[] args = premainMethod.args; if (premainOptions.containsKey(premainMethod.className)) { args[0] = premainOptions.get(premainMethod.className); @@ -153,21 +149,17 @@ public void invokePremain() { if (t instanceof InvocationTargetException) { cause = t.getCause(); } - VMError.shouldNotReachHere("Fail to execute " + premainMethod.className + ".premain", cause); + throw VMError.shouldNotReachHere("Failed to execute " + premainMethod.className + ".premain", cause); } } } /** - * This class is a dummy implementation of {@link Instrumentation} interface. It serves as the - * registered premain method's second parameter. At native image runtime, no actual - * instrumentation work can do. So all the methods here are empty. + * At native image run-time, instrumentation is not possible. So, most operations either throw + * an error or are no-ops. */ public static class NativeImageNoOpRuntimeInstrumentation implements Instrumentation { - private static final Set systemModules = Set.of("org.graalvm.nativeimage.builder", "org.graalvm.nativeimage", "org.graalvm.nativeimage.base", "com.oracle.svm.svm_enterprise", - "org.graalvm.word", "jdk.internal.vm.ci", "jdk.graal.compiler", "com.oracle.graal.graal_enterprise"); - @Override public void addTransformer(ClassFileTransformer transformer, boolean canRetransform) { } @@ -209,28 +201,17 @@ public boolean isModifiableClass(Class theClass) { @Override public Class[] getAllLoadedClasses() { ArrayList> userClasses = new ArrayList<>(); - CodeInfo imageCodeInfo = CodeInfoTable.getFirstImageCodeInfo(); - while (imageCodeInfo.isNonNull()) { - Class[] classes = NonmovableArrays.heapCopyOfObjectArray(CodeInfoAccess.getClasses(imageCodeInfo)); - if (classes != null) { - for (Class clazz : classes) { - if (clazz != null) { - Module module = clazz.getModule(); - if (module == null || - module.getName() == null || - !isSystemClass(module)) { - userClasses.add(clazz); - } - } - } + Heap.getHeap().visitLoadedClasses(clazz -> { + Module module = clazz.getModule(); + if (module == null || module.getName() == null || !isSystemClass(module)) { + userClasses.add(clazz); } - imageCodeInfo = CodeInfoAccess.getNextImageCodeInfo(imageCodeInfo); - } + }); return userClasses.toArray(new Class[0]); } private static boolean isSystemClass(Module module) { - return systemModules.contains(module.getName()); + return ModuleSupport.SYSTEM_MODULES.contains(module.getName()); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InstrumentFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InstrumentFeature.java index bd15d4c6cb5a..2a8e4d47da3c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InstrumentFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InstrumentFeature.java @@ -26,32 +26,32 @@ package com.oracle.svm.hosted; +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; + import com.oracle.svm.core.PreMainSupport; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.SubstrateOptionsParser; +import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.util.UserError; import com.oracle.svm.hosted.reflect.ReflectionFeature; + import jdk.graal.compiler.options.Option; import jdk.graal.compiler.options.OptionStability; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.hosted.Feature; - -import java.lang.instrument.Instrumentation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; /** * This feature supports instrumentation in native image. */ @AutomaticallyRegisteredFeature public class InstrumentFeature implements InternalFeature { - private ClassLoader cl; - private PreMainSupport preMainSupport; - public static class Options { @Option(help = "Specify premain-class list. Multiple classes are separated by comma, and order matters. This is an experimental option.", stability = OptionStability.EXPERIMENTAL)// public static final HostedOptionKey PremainClasses = new HostedOptionKey<>( @@ -59,62 +59,62 @@ public static class Options { } - /** - * {@link ReflectionFeature} must come before this feature, because many instrumentation methods - * are called by reflection, e.g. premain. - */ + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return !Options.PremainClasses.getValue().values().isEmpty(); + } + @Override public List> getRequiredFeatures() { + /* Many instrumentation methods are called via reflection, e.g. premain. */ return List.of(ReflectionFeature.class); } @Override public void afterRegistration(AfterRegistrationAccess access) { FeatureImpl.AfterRegistrationAccessImpl a = (FeatureImpl.AfterRegistrationAccessImpl) access; - cl = a.getImageClassLoader().getClassLoader(); - ImageSingletons.add(PreMainSupport.class, preMainSupport = new PreMainSupport()); - if (Options.PremainClasses.hasBeenSet()) { - List premains = Options.PremainClasses.getValue().values(); - for (String premain : premains) { - addPremainClass(premain); - } + ClassLoader cl = a.getImageClassLoader().getClassLoader(); + PreMainSupport support = new PreMainSupport(); + ImageSingletons.add(PreMainSupport.class, support); + + List premainClasses = Options.PremainClasses.getValue().values(); + for (String clazz : premainClasses) { + addPremainClass(support, cl, clazz); } } - /** - * Find the premain method from the given class and register it for runtime usage. According to - * java.lang.instrument API - * doc, there are two premain methods: - *
    - *
  1. {@code public static void premain(String agentArgs, Instrumentation inst)}
  2. - *
  3. {@code public static void premain(String agentArgs)}
  4. - *
- * The first one is taken with higher priority. The second one is taken only when the first one - * is absent.
- * So this method looks for them in the same order. - */ - private void addPremainClass(String premainClass) { + private static void addPremainClass(PreMainSupport support, ClassLoader cl, String premainClass) { try { Class clazz = Class.forName(premainClass, false, cl); - Method premain = null; + Method premain = findPremainMethod(premainClass, clazz); + List args = new ArrayList<>(); - args.add(""); // First argument is options which will be set at runtime - try { - premain = clazz.getDeclaredMethod("premain", String.class, Instrumentation.class); + /* The first argument contains the premain options, which will be set at runtime. */ + args.add(""); + if (premain.getParameterCount() == 2) { args.add(new PreMainSupport.NativeImageNoOpRuntimeInstrumentation()); - } catch (NoSuchMethodException e) { - try { - premain = clazz.getDeclaredMethod("premain", String.class); - } catch (NoSuchMethodException e1) { - UserError.abort(e1, "Can't register agent premain method, because can't find the premain method from the given class %s. Please check your %s setting.", premainClass, - SubstrateOptionsParser.commandArgument(Options.PremainClasses, "")); - } } - preMainSupport.registerPremainMethod(premainClass, premain, args.toArray(new Object[0])); + + support.registerPremainMethod(premainClass, premain, args.toArray(new Object[0])); } catch (ClassNotFoundException e) { - UserError.abort(e, "Can't register agent premain method, because the given class %s is not found. Please check your %s setting.", premainClass, + UserError.abort("Could not register agent premain method because class %s was not found. Please check your %s setting.", premainClass, SubstrateOptionsParser.commandArgument(Options.PremainClasses, "")); } } + + /** Find the premain method from the given class. */ + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/java.instrument/share/classes/sun/instrument/InstrumentationImpl.java#L498-L565") + private static Method findPremainMethod(String premainClass, Class javaAgentClass) { + try { + return javaAgentClass.getDeclaredMethod("premain", String.class, Instrumentation.class); + } catch (NoSuchMethodException ignored) { + } + + try { + return javaAgentClass.getDeclaredMethod("premain", String.class); + } catch (NoSuchMethodException ignored) { + } + + throw UserError.abort("Could not register agent premain method: class %s neither declares 'premain(String, Instrumentation)' nor 'premain(String)'.", premainClass); + } }