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 Jun 26, 2024
1 parent 133d179 commit bc9e8ad
Show file tree
Hide file tree
Showing 12 changed files with 677 additions and 2 deletions.
60 changes: 60 additions & 0 deletions substratevm/mx.substratevm/mx_substratevm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1717,6 1717,66 @@ def cinterfacetutorial(args):
native_image_context_run(_cinterfacetutorial, args)


@mx.command(suite.name, 'agenttest', 'Runs the ')
def agenttest(args):
def build_and_test_agent_image(native_image, args):
args = [] if args is None else args
build_dir = join(svmbuild_dir(), 'agenttest')

# clean / create output directory
if exists(build_dir):
mx.rmtree(build_dir)
mx_util.ensure_dir_exists(build_dir)
test_build_output = mx.dependency('com.oracle.svm.test').classpath_repr()

# create agent jar file
agent1 = join(build_dir, 'testagent1.jar')
mx.run([mx.get_jdk().jar, 'cmf', join(test_build_output, 'resources','agent1', 'MANIFEST.MF'),
agent1,
join('com', 'oracle', 'svm', 'test', 'agent', 'Agent.class'),
join('com', 'oracle', 'svm', 'test', 'agent', 'Agent$DemoTransformer.class'),
join('com', 'oracle', 'svm', 'test', 'agent', 'PremainSequence.class')],
cwd = test_build_output)

agent2 = join(build_dir, 'testagent2.jar')
mx.run([mx.get_jdk().jar, 'cmf', join(test_build_output, 'resources','agent2', 'MANIFEST.MF'),
agent2,
join('com', 'oracle', 'svm', 'test', 'agent', 'Agent2.class'),
join('com', 'oracle', 'svm', 'test', 'agent', 'PremainSequence.class')],
cwd = test_build_output)

# build test with agent
test_cp = os.pathsep.join([classpath('com.oracle.svm.test'), agent1, agent2])


nativeagent_premain_options=['-XX-premain:com.oracle.svm.test.agent.Agent:test.agent=true',
'-XX-premain:com.oracle.svm.test.agent.Agent2:test.agent2=true']

binary_path = join(build_dir, 'agenttest1')
native_image([
'-cp', test_cp,
'-J-ea', '-J-esa',
'-o', binary_path,
'-H: ReportExceptionStackTraces',
'-H:PremainClasses=com.oracle.svm.test.agent.Agent,com.oracle.svm.test.agent.Agent2',
'-H:Class=com.oracle.svm.test.agent.AgentTest',
] args)
mx.run([binary_path] nativeagent_premain_options)

# Switch the premain sequence of agent1 and agent2
binary_path = join(build_dir, 'agenttest2')
native_image([
'-cp', test_cp,
'-J-ea', '-J-esa',
'-o', binary_path,
'-H: ReportExceptionStackTraces',
'-H:PremainClasses=com.oracle.svm.test.agent.Agent2,com.oracle.svm.test.agent.Agent',
'-H:Class=com.oracle.svm.test.agent.AgentTest',
] args)
mx.run([binary_path] nativeagent_premain_options)

native_image_context_run(build_and_test_agent_image, args)

@mx.command(suite.name, 'clinittest', 'Runs the ')
def clinittest(args):
def build_and_test_clinittest_image(native_image, args):
Expand Down
7 changes: 7 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 Expand Up @@ -933,6 939,7 @@
"jdk.jfr",
"java.management",
"jdk.management.jfr",
"java.instrument",
"java.rmi",
],
"requiresConcealed" : {
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,231 @@
/*
* 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 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:
* <ul>
* <li>Isolate code by checking current runtime. For example: <code>
* <pre>
* if (ImageInfo.inImageRuntimeCode()) {
* // 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 {

// todo: Not finally decided, could be changed in the future
private static final String PREMAIN_OPTION_PREFIX = "-XX-premain:";

/**
* A record for premain method.
*
* @param className the full qualified class name that declares the premain method
* @param method the premain method
* @param args the arguments of premain method
*/
record PremainMethod(String className, Method method, Object[] 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. 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>
*
* @param args original arguments for premain and main
* @return arguments for main class
*/
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 NativeImageNoOpRuntimeInstrumentation 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;
}
}
}
Loading

0 comments on commit bc9e8ad

Please sign in to comment.