diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index f1fd53da29c35..2655fea01f85f 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -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", @@ -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) 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 57d0bb30441e8..f1ad4c4bc2054 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,19 +166,22 @@ public List 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); } } } 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 new file mode 100644 index 0000000000000..24378dbe27666 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/PreMainSupport.java @@ -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.
+ * JVM supports two kind of premain methods: + *
    + *
  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 SVMRuntimeInstrumentImpl} 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 SVMRuntimeInstrumentImpl}. + *

+ *

+ * It is the agent developers' responsibility to implement a native version of their agent premain + * method. It can be implemented in two ways: + *

+ *

+ */ +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 premainOptions = new HashMap<>(); + // Order matters + private List 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: + *
+ * -XX-premain:[full.qualified.premain.class]:[premain options] + *
+ * + * @param args + * @return + */ + public String[] retrievePremainArgs(String[] args) { + List 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 extraReads, Map> extraExports, Map> extraOpens, Set> extraUses, + Map, List>> extraProvides) { + } + + @Override + public boolean isModifiableModule(Module module) { + return false; + } + } +} 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 new file mode 100644 index 0000000000000..68f2efef44da8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InstrumentFeature.java @@ -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 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> 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 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 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) { + try { + Class clazz = Class.forName(premainClass, false, cl); + Method premain = null; + 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); + 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); + } + } + + +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGeneratorRunner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGeneratorRunner.java index b2ce35238117b..53bd97a958102 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGeneratorRunner.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGeneratorRunner.java @@ -189,6 +189,7 @@ private static void checkBootModuleDependencies(boolean verbose) { Set expectedBuilderDependencies = Set.of( "java.base", + "java.instrument", "java.management", "java.logging", // workaround for GR-47773 on the module-path which requires java.sql (like