Skip to content

[GR-58488] [GR-58242] Cleanups and fixes for the premain support. #9747

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,13 @@ public List<String> 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();
Expand All @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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. <br>
* 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:
* <ol>
* <li>{@code public static void premain(String agentArgs, Instrumentation inst)}</li>
* <li>{@code public static void premain(String agentArgs)}</li>
* </ol>
* 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.
* <p>
* <b>Be noticed</b>, 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}.
* </p>
* <p>
* 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.
*
* <p/>
*
* <b>Please note:</b> 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:
* <ul>
* <li>Isolate code by checking current runtime. For example: <code>
* <pre>
* if ("runtime".equals(System.getProperty("org.graalvm.nativeimage.imagecode"))) {
* // native image runtime
* } else{
* // JVM runtime
* }
* </pre>
* </code> Alternatively: Instead of directly getting property,
* <code>ImageInfo.inImageRuntimeCode()</code> can also be used to check if current runtime is
* native image runtime, but it requires extra dependency.</li>
* <li>Use {@link com.oracle.svm.core.annotate.TargetClass} API to implement a native image version
* premain.</li>
* <li>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.</li>
* <li>Call {@link ImageInfo#inImageRuntimeCode}. However, note that this requires a dependency on
* the native image API.</li>
* </ul>
* </p>
*
* As a last resort, it is also possible to substitute a premain method with a native image-specific
* version.
*/
public class PreMainSupport {

Expand All @@ -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: <br>
* -XX-premain:[full.qualified.premain.class]:[premain options]
* -XX-premain:[full.qualified.premain.class2]:[premain options] <br>
* 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.:
*
* <pre>
* -XXpremain:[full.qualified.premain.class1]:[premain options]
* -XXpremain:[full.qualified.premain.class2]:[premain options]
* </pre>
*
* @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) {
Expand All @@ -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]);
}
Expand All @@ -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);
Expand All @@ -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<String> 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) {
}
Expand Down Expand Up @@ -209,28 +201,17 @@ public boolean isModifiableClass(Class<?> theClass) {
@Override
public Class<?>[] getAllLoadedClasses() {
ArrayList<Class<?>> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,95 +26,95 @@

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<AccumulatingLocatableMultiOptionValue.Strings> PremainClasses = new HostedOptionKey<>(
AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter());

}

/**
* {@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<Class<? extends Feature>> 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<String> 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<String> 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 <a
* href=https://docs.oracle.com/en/java/javase/17/docs/api/java.instrument/java/lang/instrument/package-summary.html>API
* doc</a>, there are two premain methods:
* <ol>
* <li>{@code public static void premain(String agentArgs, Instrumentation inst)}</li>
* <li>{@code public static void premain(String agentArgs)}</li>
* </ol>
* The first one is taken with higher priority. The second one is taken only when the first one
* is absent. <br>
* 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<Object> 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);
}
}