forked from oracle/graal
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add premain for static agent support
Java agent always has a premain method to initialize the agent. SVM needs the premain as well to support agent in native image. At compile 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]
- Loading branch information
Showing
5 changed files
with
364 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
234 changes: 234 additions & 0 deletions
234
substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/PreMainSupport.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
/* | ||
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. | ||
* Copyright (c) 2024, 2024, Alibaba Group Holding Limited. All rights reserved. | ||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
* | ||
* This code is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU General Public License version 2 only, as | ||
* published by the Free Software Foundation. Oracle designates this | ||
* particular file as subject to the "Classpath" exception as provided | ||
* by Oracle in the LICENSE file that accompanied this code. | ||
* | ||
* This code is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
* version 2 for more details (a copy is included in the LICENSE file that | ||
* accompanied this code). | ||
* | ||
* You should have received a copy of the GNU General Public License version | ||
* 2 along with this work; if not, write to the Free Software Foundation, | ||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
* | ||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
* or visit www.oracle.com if you need additional information or have any | ||
* questions. | ||
*/ | ||
|
||
package com.oracle.svm.core; | ||
|
||
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; | ||
import java.lang.instrument.UnmodifiableClassException; | ||
import java.lang.reflect.Method; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.jar.JarFile; | ||
|
||
/** | ||
* 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: | ||
* <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 SVMRuntimeInstrumentImpl} 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 SVMRuntimeInstrumentImpl}. | ||
* </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: | ||
* <ul> | ||
* <li>Isolate code by checking current runtime. For example: <code> | ||
* <pre> | ||
* String vm = System.getProperty("java.vm.name"); | ||
* if ("Substrate VM".equals(vm)) { | ||
* // native image runtime | ||
* } else{ | ||
* // JVM runtime | ||
* } | ||
* </pre> | ||
* </code></li> | ||
* <li>Use {@link com.oracle.svm.core.annotate.TargetClass} API to implement a native image version | ||
* premain.</li> | ||
* </ul> | ||
* </p> | ||
*/ | ||
public class PreMainSupport { | ||
|
||
private static final String PREMAIN_OPTION_PREFIX = "-XX-premain:"; | ||
|
||
class PremainMethod { | ||
String className; // full qualified premain class name | ||
Method method; | ||
Object[] args; | ||
|
||
PremainMethod(String className, Method method, Object[] args) { | ||
this.className = className; | ||
this.method = method; | ||
this.args = args; | ||
} | ||
} | ||
|
||
private Map<String, String> premainOptions = new HashMap<>(); | ||
// Order matters | ||
private List<PremainMethod> premainMethods = new ArrayList<>(); | ||
|
||
@Platforms({Platform.HOSTED_ONLY.class}) | ||
public void registerPremainMethod(String className, Method executable, Object... args) { | ||
premainMethods.add(new PremainMethod(className, executable, args)); | ||
} | ||
|
||
/** | ||
* Retrieve premain options from input args. | ||
* Keep premain options and return the rest args as main args. | ||
* The premain options format: | ||
* <br> | ||
* -XX-premain:[full.qualified.premain.class]:[premain options] | ||
* <br> | ||
* | ||
* @param args | ||
* @return | ||
*/ | ||
public String[] retrievePremainArgs(String[] args) { | ||
List<String> mainArgs = new ArrayList<>(); | ||
|
||
for (String arg : args) { | ||
if (arg.startsWith(PREMAIN_OPTION_PREFIX)) { | ||
String premainOptionKeyValue = arg.substring(PREMAIN_OPTION_PREFIX.length()); | ||
String[] pair = premainOptionKeyValue.split(":"); | ||
if (pair.length == 2) { | ||
premainOptions.put(pair[0], pair[1]); | ||
} | ||
} else { | ||
mainArgs.add(arg); | ||
} | ||
} | ||
return mainArgs.toArray(new String[0]); | ||
} | ||
|
||
public void invokePremain() { | ||
for (PremainMethod premainMethod : premainMethods) { | ||
try { | ||
Object[] args = premainMethod.args; | ||
if (premainOptions.containsKey(premainMethod.className)) { | ||
args[0] = premainOptions.get(premainMethod.className); | ||
} | ||
// premain method must be static | ||
premainMethod.method.invoke(null, args); | ||
} catch (Throwable t) { | ||
VMError.shouldNotReachHere("Fail to execute " + premainMethod.className + ".premain", t); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* 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. | ||
*/ | ||
public static class SVMRuntimeInstrumentImpl implements Instrumentation { | ||
|
||
@Override | ||
public void addTransformer(ClassFileTransformer transformer, boolean canRetransform) { | ||
} | ||
|
||
@Override | ||
public void addTransformer(ClassFileTransformer transformer) { | ||
} | ||
|
||
@Override | ||
public boolean removeTransformer(ClassFileTransformer transformer) { | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean isRetransformClassesSupported() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public void retransformClasses(Class<?>... classes) throws UnmodifiableClassException { | ||
} | ||
|
||
@Override | ||
public boolean isRedefineClassesSupported() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException { | ||
} | ||
|
||
@Override | ||
public boolean isModifiableClass(Class<?> theClass) { | ||
return false; | ||
} | ||
|
||
@Override | ||
public Class<?>[] getAllLoadedClasses() { | ||
return new Class<?>[0]; | ||
} | ||
|
||
@Override | ||
public Class<?>[] getInitiatedClasses(ClassLoader loader) { | ||
return new Class<?>[0]; | ||
} | ||
|
||
@Override | ||
public long getObjectSize(Object objectToSize) { | ||
return 0; | ||
} | ||
|
||
@Override | ||
public void appendToBootstrapClassLoaderSearch(JarFile jarfile) { | ||
} | ||
|
||
@Override | ||
public void appendToSystemClassLoaderSearch(JarFile jarfile) { | ||
} | ||
|
||
@Override | ||
public boolean isNativeMethodPrefixSupported() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix) { | ||
} | ||
|
||
@Override | ||
public void redefineModule(Module module, Set<Module> extraReads, Map<String, Set<Module>> extraExports, Map<String, Set<Module>> extraOpens, Set<Class<?>> extraUses, | ||
Map<Class<?>, List<Class<?>>> extraProvides) { | ||
} | ||
|
||
@Override | ||
public boolean isModifiableModule(Module module) { | ||
return false; | ||
} | ||
} | ||
} |
118 changes: 118 additions & 0 deletions
118
substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InstrumentFeature.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/* | ||
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. | ||
* Copyright (c) 2024, 2024, Alibaba Group Holding Limited. All rights reserved. | ||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
* | ||
* This code is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU General Public License version 2 only, as | ||
* published by the Free Software Foundation. Oracle designates this | ||
* particular file as subject to the "Classpath" exception as provided | ||
* by Oracle in the LICENSE file that accompanied this code. | ||
* | ||
* This code is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
* version 2 for more details (a copy is included in the LICENSE file that | ||
* accompanied this code). | ||
* | ||
* You should have received a copy of the GNU General Public License version | ||
* 2 along with this work; if not, write to the Free Software Foundation, | ||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
* | ||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
* or visit www.oracle.com if you need additional information or have any | ||
* questions. | ||
*/ | ||
|
||
package com.oracle.svm.hosted; | ||
|
||
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.HostedOptionKey; | ||
import com.oracle.svm.core.option.LocatableMultiOptionValue; | ||
import com.oracle.svm.core.util.VMError; | ||
import com.oracle.svm.hosted.reflect.ReflectionFeature; | ||
import jdk.graal.compiler.options.Option; | ||
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.")// | ||
public static final HostedOptionKey<LocatableMultiOptionValue.Strings> PremainClasses = new HostedOptionKey<>(null); | ||
|
||
} | ||
|
||
/** | ||
* {@link ReflectionFeature} must come before this feature, because many instrumentation methods | ||
* are called by reflection, e.g. premain. | ||
*/ | ||
@Override | ||
public List<Class<? extends Feature>> getRequiredFeatures() { | ||
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); | ||
} | ||
} | ||
} | ||
|
||
|
||
/** | ||
* 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) { | ||
try { | ||
Class<?> clazz = Class.forName(premainClass, false, cl); | ||
Method premain = null; | ||
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); | ||
args.add(new PreMainSupport.SVMRuntimeInstrumentImpl()); | ||
} catch (NoSuchMethodException e) { | ||
try { | ||
premain = clazz.getDeclaredMethod("premain", String.class); | ||
} catch (NoSuchMethodException e1) { | ||
VMError.shouldNotReachHere("Can't find premain method in " + premainClass, e1); | ||
} | ||
} | ||
preMainSupport.registerPremainMethod(premainClass, premain, args.toArray(new Object[0])); | ||
} catch (ClassNotFoundException e) { | ||
VMError.shouldNotReachHere("Can't register agent premain method, because the declaring class " + premainClass + " is not found", e); | ||
} | ||
} | ||
|
||
|
||
} |
Oops, something went wrong.