Skip to content

Commit 94cf30e

Browse files
committed
Support agent by attaching it at build time
GraalVM now can apply the class transformation in the native image by attaching the java agent at build time. User can attach the java agent to native-image with -javaagent option.
1 parent ffbfca9 commit 94cf30e

File tree

12 files changed

+162
-34
lines changed

12 files changed

+162
-34
lines changed

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1736,10 +1736,15 @@ def cinterfacetutorial(args):
17361736
@mx.command(suite.name, 'javaagenttest', 'Runs tests for java agent with native image')
17371737
def java_agent_test(args):
17381738
def build_and_run(args, binary_path, native_image, agents, agents_arg):
1739-
test_cp = os.pathsep.join([classpath('com.oracle.svm.test')] + agents)
1739+
mx.log('Run agent with JVM as baseline')
1740+
test_cp = os.pathsep.join([classpath('com.oracle.svm.test')])
1741+
java_run_cp = os.pathsep.join([test_cp, mx.dependency('org.graalvm.nativeimage').classpath_repr()])
1742+
mx.run_java( agents_arg + ['--add-exports=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED',
1743+
'-cp', java_run_cp, 'com.oracle.svm.test.javaagent.AgentTest'])
1744+
test_cp = os.pathsep.join([test_cp] + agents)
17401745
native_agent_premain_options = ['-XXpremain:com.oracle.svm.test.javaagent.agent1.TestJavaAgent1:test.agent1=true', '-XXpremain:com.oracle.svm.test.javaagent.agent2.TestJavaAgent2:test.agent2=true']
1741-
image_args = ['-cp', test_cp, '-J-ea', '-J-esa', '-H:+ReportExceptionStackTraces', '-H:Class=com.oracle.svm.test.javaagent.AgentTest']
1742-
native_image(image_args + svm_experimental_options(['-H:PremainClasses=' + agents_arg]) + ['-o', binary_path] + args)
1746+
image_args = ['-cp', test_cp, '-J-ea', '-J-esa', '-H:+ReportExceptionStackTraces', '-J--add-exports=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED', '-H:Class=com.oracle.svm.test.javaagent.AgentTest']
1747+
native_image(image_args + svm_experimental_options(agents_arg) + ['-o', binary_path] + args)
17431748
mx.run([binary_path] + native_agent_premain_options)
17441749

17451750
def build_and_test_java_agent_image(native_image, args):
@@ -1756,18 +1761,23 @@ def build_and_test_java_agent_image(native_image, args):
17561761
# Note: we are not using MX here to avoid polluting the suite.py and requiring extra build flags
17571762
mx.log("Building agent jars from " + test_classpath)
17581763
agents = []
1759-
for i in range(1, 2):
1764+
for i in range(1, 3):
17601765
agent = join(tmp_dir, "testagent%d.jar" % (i))
1761-
agent_test_classpath = join(test_classpath, 'com', 'oracle', 'svm', 'test', 'javaagent', 'agent' + str(i))
1762-
class_list = [join(test_classpath, 'com', 'oracle', 'svm', 'test', 'javaagent', 'agent' + str(i), f) for f in os.listdir(agent_test_classpath) if os.path.isfile(os.path.join(agent_test_classpath, f)) and f.endswith(".class")]
1763-
mx.run([mx.get_jdk().jar, 'cmf', join(test_classpath, 'resources', 'javaagent' + str(i), 'MANIFEST.MF'), agent] + class_list, cwd = tmp_dir)
1766+
current_dir = os.getcwd()
1767+
# Change to test classpath to create agent jar file
1768+
os.chdir(test_classpath)
1769+
agent_test_classpath = join('com', 'oracle', 'svm', 'test', 'javaagent', 'agent' + str(i))
1770+
class_list = [join('com', 'oracle', 'svm', 'test', 'javaagent', 'agent' + str(i), f) for f in os.listdir(agent_test_classpath) if os.path.isfile(os.path.join(agent_test_classpath, f)) and f.endswith(".class")]
1771+
class_list.append(join('com', 'oracle', 'svm', 'test', 'javaagent', 'AgentPremainHelper.class'))
1772+
mx.run([mx.get_jdk().jar, 'cmf', join(test_classpath, 'resources', 'javaagent' + str(i), 'MANIFEST.MF'), agent] + class_list, cwd = test_classpath)
17641773
agents.append(agent)
1774+
os.chdir(current_dir)
17651775

17661776
mx.log("Building images with different agent orders ")
1767-
build_and_run(args, join(tmp_dir, 'agenttest1'), native_image, agents,'com.oracle.svm.test.javaagent.agent1.TestJavaAgent1,com.oracle.svm.test.javaagent.agent2.TestJavaAgent2')
1777+
build_and_run(args, join(tmp_dir, 'agenttest1'), native_image, agents,[f'-javaagent:{agents[0]}=test.agent1=true', f'-javaagent:{agents[1]}=test.agent2=true'])
17681778

17691779
# Switch the premain sequence of agent1 and agent2
1770-
build_and_run(args, join(tmp_dir, 'agenttest2'), native_image, agents, 'com.oracle.svm.test.javaagent.agent2.TestJavaAgent2,com.oracle.svm.test.javaagent.agent1.TestJavaAgent1')
1780+
build_and_run(args, join(tmp_dir, 'agenttest2'), native_image, agents, [f'-javaagent:{agents[1]}=test.agent2=true', f'-javaagent:{agents[0]}=test.agent1=true'])
17711781

17721782
native_image_context_run(build_and_test_java_agent_image, args)
17731783

substratevm/mx.substratevm/suite.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,7 @@
994994
"java.base" : [
995995
"jdk.internal.misc",
996996
"sun.security.jca",
997+
"jdk.internal.org.objectweb.asm"
997998
],
998999
},
9991000
"checkstyle": "com.oracle.svm.test",

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,10 @@ public boolean isClosedTypeWorld() {
336336
return true;
337337
}
338338

339+
public boolean isFromJavaAgent(Class<?> clazz) {
340+
return false;
341+
}
342+
339343
/**
340344
* Helpers to determine what analysis actions should be taken for a given Multi-Method version.
341345
*/

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626

2727
import static jdk.vm.ci.common.JVMCIError.unimplemented;
2828

29+
import java.util.Arrays;
2930
import java.util.List;
3031
import java.util.stream.Collectors;
3132

3233
import com.oracle.graal.pointsto.constraints.UnresolvedElementException;
34+
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
3335
import com.oracle.graal.pointsto.util.GraalAccess;
3436

3537
import jdk.graal.compiler.api.replacements.SnippetReflectionProvider;
@@ -41,6 +43,7 @@
4143
import jdk.vm.ci.meta.JavaType;
4244
import jdk.vm.ci.meta.ResolvedJavaMethod;
4345
import jdk.vm.ci.meta.ResolvedJavaType;
46+
import jdk.vm.ci.meta.UnresolvedJavaMethod;
4447

4548
public class WrappedConstantPool implements ConstantPool, ConstantPoolPatch {
4649

@@ -117,7 +120,26 @@ public JavaMethod lookupMethod(int cpi, int opcode) {
117120
@Override
118121
public JavaMethod lookupMethod(int cpi, int opcode, ResolvedJavaMethod caller) {
119122
try {
120-
return universe.lookupAllowUnresolved(wrapped.lookupMethod(cpi, opcode, OriginalMethodProvider.getOriginalMethod(caller)));
123+
JavaMethod ret = universe.lookupAllowUnresolved(wrapped.lookupMethod(cpi, opcode, OriginalMethodProvider.getOriginalMethod(caller)));
124+
/**
125+
* The java agent classes are loaded by appClassloader, but their dependencies could be
126+
* loaded by NativeImageClassloader. So if the required method could not be resolved, we
127+
* look further into classes loaded by nativeImageClassloader.
128+
*/
129+
if (ret instanceof UnresolvedJavaMethod && universe.hostVM().isFromJavaAgent(OriginalClassProvider.getJavaClass(caller.getDeclaringClass()))) {
130+
UnresolvedJavaMethod unresolvedResult = (UnresolvedJavaMethod) ret;
131+
String className = unresolvedResult.format("%H");
132+
String methodNameWithSignature = unresolvedResult.format("%n(%P)");
133+
try {
134+
Class<?> loadedClass = ((AnalysisUniverse) universe).getConcurrentAnalysisAccess().findClassByName(className);
135+
ResolvedJavaType resolvedType = ((AnalysisUniverse) universe).getOriginalMetaAccess().lookupJavaType(loadedClass);
136+
ResolvedJavaMethod resolvedMethod = Arrays.stream(resolvedType.getDeclaredMethods(false)).filter(m -> m.format("%n(%P)").equals(methodNameWithSignature)).findFirst().get();
137+
return universe.lookupAllowUnresolved(resolvedMethod);
138+
} catch (Exception e) {
139+
// Could not get the resolved method, get to the unresolved path
140+
}
141+
}
142+
return ret;
121143
} catch (Throwable ex) {
122144
Throwable cause = ex;
123145
if (ex instanceof ExceptionInInitializerError && ex.getCause() != null) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/PreMainSupport.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,12 @@ public String[] retrievePremainArgs(String[] args) {
140140

141141
public void invokePremain() {
142142
for (PremainMethod premainMethod : premainMethods) {
143-
144-
Object[] args = premainMethod.args;
145-
if (premainOptions.containsKey(premainMethod.className)) {
146-
args[0] = premainOptions.get(premainMethod.className);
147-
}
148143
try {
144+
Object[] args = premainMethod.args;
145+
// options set at runtime can override options set at build time
146+
if (premainOptions.containsKey(premainMethod.className)) {
147+
args[0] = premainOptions.get(premainMethod.className);
148+
}
149149
// premain method must be static
150150
premainMethod.method.invoke(null, args);
151151
} catch (Throwable t) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, String ol
202202
public static OptionEnabledHandler<Boolean> imageLayerEnabledHandler;
203203
public static OptionEnabledHandler<Boolean> imageLayerCreateEnabledHandler;
204204

205+
@APIOption(name = "-javaagent", valueSeparator = ':')//
206+
@Option(help = "Enable the given java agent in native image.", type = User, stability = OptionStability.EXPERIMENTAL)//
207+
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> JavaAgent = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build());
208+
205209
@Fold
206210
public static boolean getSourceLevelDebug() {
207211
return SourceLevelDebug.getValue();

substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ private static <T> String oR(OptionKey<T> option) {
272272
final String oHInspectServerContentPath = oH(PointstoOptions.InspectServerContentPath);
273273
final String oHDeadlockWatchdogInterval = oH(SubstrateOptions.DeadlockWatchdogInterval);
274274

275+
final String oHJavaAgent = oH(SubstrateOptions.JavaAgent);
276+
275277
final Map<String, String> imageBuilderEnvironment = new HashMap<>();
276278
private final ArrayList<String> imageBuilderArgs = new ArrayList<>();
277279
private final LinkedHashSet<Path> imageBuilderModulePath = new LinkedHashSet<>();
@@ -1415,6 +1417,7 @@ private List<String> getAgentArguments() {
14151417
String agentOptions = "";
14161418
List<ArgumentEntry> traceClassInitializationOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHTraceClassInitialization);
14171419
List<ArgumentEntry> traceObjectInstantiationOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHTraceObjectInstantiation);
1420+
List<ArgumentEntry> javaAgentOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHJavaAgent);
14181421
if (!traceClassInitializationOpts.isEmpty()) {
14191422
agentOptions = getAgentOptions(traceClassInitializationOpts, "c");
14201423
}
@@ -1433,6 +1436,12 @@ private List<String> getAgentArguments() {
14331436
args.add("-agentlib:native-image-diagnostics-agent=" + agentOptions);
14341437
}
14351438

1439+
if (!javaAgentOpts.isEmpty()) {
1440+
for (ArgumentEntry javaAgentOpt : javaAgentOpts) {
1441+
args.add("-javaagent:" + javaAgentOpt.value);
1442+
}
1443+
}
1444+
14361445
return args;
14371446
}
14381447

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InstrumentFeature.java

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
package com.oracle.svm.hosted;
2828

2929
import com.oracle.svm.core.PreMainSupport;
30+
import com.oracle.svm.core.SubstrateOptions;
3031
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
3132
import com.oracle.svm.core.feature.InternalFeature;
3233
import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue;
@@ -39,10 +40,12 @@
3940
import org.graalvm.nativeimage.ImageSingletons;
4041
import org.graalvm.nativeimage.hosted.Feature;
4142

43+
import java.io.IOException;
4244
import java.lang.instrument.Instrumentation;
4345
import java.lang.reflect.Method;
4446
import java.util.ArrayList;
4547
import java.util.List;
48+
import java.util.jar.JarFile;
4649

4750
/**
4851
* This feature supports instrumentation in native image.
@@ -73,10 +76,10 @@ public void afterRegistration(AfterRegistrationAccess access) {
7376
FeatureImpl.AfterRegistrationAccessImpl a = (FeatureImpl.AfterRegistrationAccessImpl) access;
7477
cl = a.getImageClassLoader().getClassLoader();
7578
ImageSingletons.add(PreMainSupport.class, preMainSupport = new PreMainSupport());
76-
if (Options.PremainClasses.hasBeenSet()) {
77-
List<String> premains = Options.PremainClasses.getValue().values();
78-
for (String premain : premains) {
79-
addPremainClass(premain);
79+
if (SubstrateOptions.JavaAgent.hasBeenSet()) {
80+
List<String> agentOptions = SubstrateOptions.JavaAgent.getValue().values();
81+
for (String agentOption : agentOptions) {
82+
addPremainClass(agentOption);
8083
}
8184
}
8285
}
@@ -94,12 +97,32 @@ public void afterRegistration(AfterRegistrationAccess access) {
9497
* is absent. <br>
9598
* So this method looks for them in the same order.
9699
*/
97-
private void addPremainClass(String premainClass) {
100+
private void addPremainClass(String javaagentOption) {
101+
int separatorIndex = javaagentOption.indexOf("=");
102+
String agent;
103+
String premainClass = null;
104+
String options = "";
105+
// Get the agent file
106+
if (separatorIndex == -1) {
107+
agent = javaagentOption;
108+
} else {
109+
agent = javaagentOption.substring(0, separatorIndex);
110+
options = javaagentOption.substring(separatorIndex + 1);
111+
}
112+
// Read MANIFEST in agent jar
113+
try {
114+
JarFile agentJarFile = new JarFile(agent);
115+
premainClass = agentJarFile.getManifest().getMainAttributes().getValue("Premain-Class");
116+
} catch (IOException e) {
117+
UserError.abort(e, "Can't read the agent jar %s. Please check option %s", agent,
118+
SubstrateOptionsParser.commandArgument(SubstrateOptions.JavaAgent, ""));
119+
}
120+
98121
try {
99122
Class<?> clazz = Class.forName(premainClass, false, cl);
100123
Method premain = null;
101124
List<Object> args = new ArrayList<>();
102-
args.add(""); // First argument is options which will be set at runtime
125+
args.add(options); // First argument is options which will be set at runtime
103126
try {
104127
premain = clazz.getDeclaredMethod("premain", String.class, Instrumentation.class);
105128
args.add(new PreMainSupport.NativeImageNoOpRuntimeInstrumentation());
@@ -108,13 +131,13 @@ private void addPremainClass(String premainClass) {
108131
premain = clazz.getDeclaredMethod("premain", String.class);
109132
} catch (NoSuchMethodException e1) {
110133
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,
111-
SubstrateOptionsParser.commandArgument(Options.PremainClasses, ""));
134+
SubstrateOptionsParser.commandArgument(SubstrateOptions.JavaAgent, ""));
112135
}
113136
}
114137
preMainSupport.registerPremainMethod(premainClass, premain, args.toArray(new Object[0]));
115138
} catch (ClassNotFoundException e) {
116139
UserError.abort(e, "Can't register agent premain method, because the given class %s is not found. Please check your %s setting.", premainClass,
117-
SubstrateOptionsParser.commandArgument(Options.PremainClasses, ""));
140+
SubstrateOptionsParser.commandArgument(SubstrateOptions.JavaAgent, ""));
118141
}
119142
}
120143
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,4 +1091,17 @@ public Set<AnalysisMethod> loadOpenTypeWorldDispatchTableMethods(AnalysisType ty
10911091
// return OpenTypeWorldFeature.loadDispatchTable(type);
10921092
return Set.of();
10931093
}
1094+
1095+
@Override
1096+
public boolean isFromJavaAgent(Class<?> clazz) {
1097+
if (SubstrateOptions.JavaAgent.hasBeenSet()) {
1098+
try {
1099+
String classLocation = clazz.getProtectionDomain().getCodeSource().getLocation().getFile();
1100+
return SubstrateOptions.JavaAgent.getValue().values().stream().map(s -> s.split("=")[0]).anyMatch(s -> s.equals(classLocation));
1101+
} catch (Exception e) {
1102+
return false;
1103+
}
1104+
}
1105+
return false;
1106+
}
10941107
}

substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,20 @@ private static void testPremainSequence() {
5858
}
5959
}
6060

61+
private static void testInstrumentation() {
62+
// The return value of getCounter() should be changed by agent
63+
Assert.assertEquals(11, getCounter());
64+
}
65+
66+
private static int getCounter() {
67+
return 10;
68+
}
69+
6170
public static void main(String[] args) {
6271
testPremain();
6372
testAgentOptions();
6473
testPremainSequence();
74+
testInstrumentation();
6575
System.out.println("Finished running Agent test.");
6676
}
6777
}

0 commit comments

Comments
 (0)