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:
*
*
{@code public static void premain(String agentArgs, Instrumentation inst)}
*
{@code public static void premain(String agentArgs)}
*
- * 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:
*
- *
Isolate code by checking current runtime. For example:
- *
- * Alternatively: Instead of directly getting property,
- * ImageInfo.inImageRuntimeCode() can also be used to check if current runtime is
- * native image runtime, but it requires extra dependency.
- *
Use {@link com.oracle.svm.core.annotate.TargetClass} API to implement a native image version
- * premain.
+ *
Check the system property {@code org.graalvm.nativeimage.imagecode}, e.g., if
+ * {@code "runtime".equals(System.getProperty("org.graalvm.nativeimage.imagecode"))} returns true,
+ * the code is executed by native image at run-time.
+ *
Call {@link ImageInfo#inImageRuntimeCode}. However, note that this requires a dependency on
+ * the native image API.
*
- *
+ *
+ * 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.:
+ *
+ *
*
- * @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:
- *
- *
{@code public static void premain(String agentArgs, Instrumentation inst)}
- *
{@code public static void premain(String agentArgs)}
- *
- * 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