Skip to content

Commit

Permalink
Add premain for static agent support
Browse files Browse the repository at this point in the history
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
ziyilin committed May 29, 2024
1 parent 4aaf7e3 commit ea2a24b
Show file tree
Hide file tree
Showing 5 changed files with 364 additions and 2 deletions.
6 changes: 6 additions & 0 deletions substratevm/mx.substratevm/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,9 @@
"jdk.internal.util",
"jdk.internal.org.objectweb.asm",
],
"java.instrument":[
"java.lang.instrument"
],
"java.management": [
"com.sun.jmx.mbeanserver",
"sun.management",
Expand Down Expand Up @@ -639,6 +642,9 @@
"sun.util.locale",
"sun.invoke.util",
],
"java.instrument":[
"java.lang.instrument"
],
"java.management": [
"com.sun.jmx.mbeanserver", # Needed for javadoc links (MXBeanIntrospector,DefaultMXBeanMappingFactory, MXBeanProxy)
"sun.management", # Needed for javadoc links (MappedMXBeanType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,22 @@ 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();
JavaMainSupport javaMainSupport = ImageSingletons.lookup(JavaMainSupport.class);
if (javaMainSupport.mainNonstatic) {
Object instance = javaMainSupport.javaMainClassCtorHandle.invoke();
if (javaMainSupport.mainWithoutArgs) {
javaMainSupport.javaMainHandle.invoke(instance);
} else {
javaMainSupport.javaMainHandle.invoke(instance, args);
javaMainSupport.javaMainHandle.invoke(instance, mainArgs);
}
} else {
if (javaMainSupport.mainWithoutArgs) {
javaMainSupport.javaMainHandle.invokeExact();
} else {
javaMainSupport.javaMainHandle.invokeExact(args);
javaMainSupport.javaMainHandle.invokeExact(mainArgs);
}
}
}
Expand Down
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;
}
}
}
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);
}
}


}
Loading

0 comments on commit ea2a24b

Please sign in to comment.