Skip to content

Commit aa36797

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 48a61fe commit aa36797

File tree

14 files changed

+171
-59
lines changed

14 files changed

+171
-59
lines changed

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1784,10 +1784,15 @@ def cinterfacetutorial(args):
17841784
@mx.command(suite.name, 'javaagenttest', 'Runs tests for java agent with native image')
17851785
def java_agent_test(args):
17861786
def build_and_run(args, binary_path, native_image, agents, agents_arg):
1787-
test_cp = os.pathsep.join([classpath('com.oracle.svm.test')] + agents)
1787+
mx.log('Run agent with JVM as baseline')
1788+
test_cp = os.pathsep.join([classpath('com.oracle.svm.test')])
1789+
java_run_cp = os.pathsep.join([test_cp, mx.dependency('org.graalvm.nativeimage').classpath_repr()])
1790+
mx.run_java( agents_arg + ['--add-exports=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED',
1791+
'-cp', java_run_cp, 'com.oracle.svm.test.javaagent.AgentTest'])
1792+
test_cp = os.pathsep.join([test_cp] + agents)
17881793
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']
1789-
image_args = ['-cp', test_cp, '-J-ea', '-J-esa', '-H:+ReportExceptionStackTraces', '-H:Class=com.oracle.svm.test.javaagent.AgentTest']
1790-
native_image(image_args + svm_experimental_options(['-H:PremainClasses=' + agents_arg]) + ['-o', binary_path] + args)
1794+
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']
1795+
native_image(image_args + svm_experimental_options(agents_arg) + ['-o', binary_path] + args)
17911796
mx.run([binary_path] + native_agent_premain_options)
17921797

17931798
def build_and_test_java_agent_image(native_image, args):
@@ -1804,18 +1809,23 @@ def build_and_test_java_agent_image(native_image, args):
18041809
# Note: we are not using MX here to avoid polluting the suite.py and requiring extra build flags
18051810
mx.log("Building agent jars from " + test_classpath)
18061811
agents = []
1807-
for i in range(1, 2):
1812+
for i in range(1, 3):
18081813
agent = join(tmp_dir, "testagent%d.jar" % (i))
1809-
agent_test_classpath = join(test_classpath, 'com', 'oracle', 'svm', 'test', 'javaagent', 'agent' + str(i))
1810-
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")]
1811-
mx.run([mx.get_jdk().jar, 'cmf', join(test_classpath, 'resources', 'javaagent' + str(i), 'MANIFEST.MF'), agent] + class_list, cwd = tmp_dir)
1814+
current_dir = os.getcwd()
1815+
# Change to test classpath to create agent jar file
1816+
os.chdir(test_classpath)
1817+
agent_test_classpath = join('com', 'oracle', 'svm', 'test', 'javaagent', 'agent' + str(i))
1818+
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")]
1819+
class_list.append(join('com', 'oracle', 'svm', 'test', 'javaagent', 'AgentPremainHelper.class'))
1820+
mx.run([mx.get_jdk().jar, 'cmf', join(test_classpath, 'resources', 'javaagent' + str(i), 'MANIFEST.MF'), agent] + class_list, cwd = test_classpath)
18121821
agents.append(agent)
1822+
os.chdir(current_dir)
18131823

18141824
mx.log("Building images with different agent orders ")
1815-
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')
1825+
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'])
18161826

18171827
# Switch the premain sequence of agent1 and agent2
1818-
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')
1828+
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'])
18191829

18201830
native_image_context_run(build_and_test_java_agent_image, args)
18211831

substratevm/mx.substratevm/suite.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,7 @@
10281028
"java.base" : [
10291029
"jdk.internal.misc",
10301030
"sun.security.jca",
1031+
"jdk.internal.org.objectweb.asm"
10311032
],
10321033
},
10331034
"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
@@ -343,6 +343,10 @@ public boolean enableTrackAcrossLayers() {
343343
return false;
344344
}
345345

346+
public boolean isFromJavaAgent(@SuppressWarnings("unused") Class<?> clazz) {
347+
return false;
348+
}
349+
346350
/**
347351
* Helpers to determine what analysis actions should be taken for a given Multi-Method version.
348352
*/

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, String ol
206206
public static OptionEnabledHandler<Boolean> imageLayerEnabledHandler;
207207
public static OptionEnabledHandler<Boolean> imageLayerCreateEnabledHandler;
208208

209+
@APIOption(name = "-javaagent", valueSeparator = ':')//
210+
@Option(help = "Enable the specified java agent in native image. Usage: -javaagent:<jarpath>[=<options>]. " +
211+
"The java agent will run at image build time to take its effects in the output native image. " +
212+
"Be noticed: The java agent's premain method will be re-executed at native image runtime. " +
213+
"The agent should isolate the executions according to runtime environment.", type = User, stability = OptionStability.EXPERIMENTAL)//
214+
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> JavaAgent = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build());
215+
209216
@Fold
210217
public static boolean getSourceLevelDebug() {
211218
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
@@ -270,6 +270,8 @@ private static <T> String oR(OptionKey<T> option) {
270270
final String oHInspectServerContentPath = oH(PointstoOptions.InspectServerContentPath);
271271
final String oHDeadlockWatchdogInterval = oH(SubstrateOptions.DeadlockWatchdogInterval);
272272

273+
final String oHJavaAgent = oH(SubstrateOptions.JavaAgent);
274+
273275
final Map<String, String> imageBuilderEnvironment = new HashMap<>();
274276
private final ArrayList<String> imageBuilderArgs = new ArrayList<>();
275277
private final Set<String> imageBuilderUniqueLeftoverArgs = Collections.newSetFromMap(new IdentityHashMap<>());
@@ -1476,6 +1478,7 @@ private List<String> getAgentArguments() {
14761478
String agentOptions = "";
14771479
List<ArgumentEntry> traceClassInitializationOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHTraceClassInitialization);
14781480
List<ArgumentEntry> traceObjectInstantiationOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHTraceObjectInstantiation);
1481+
List<ArgumentEntry> javaAgentOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHJavaAgent);
14791482
if (!traceClassInitializationOpts.isEmpty()) {
14801483
agentOptions = getAgentOptions(traceClassInitializationOpts, "c");
14811484
}
@@ -1494,6 +1497,12 @@ private List<String> getAgentArguments() {
14941497
args.add("-agentlib:native-image-diagnostics-agent=" + agentOptions);
14951498
}
14961499

1500+
if (!javaAgentOpts.isEmpty()) {
1501+
for (ArgumentEntry javaAgentOpt : javaAgentOpts) {
1502+
args.add("-javaagent:" + javaAgentOpt.value);
1503+
}
1504+
}
1505+
14971506
return args;
14981507
}
14991508

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

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,33 +35,25 @@
3535
import org.graalvm.nativeimage.hosted.Feature;
3636

3737
import com.oracle.svm.core.PreMainSupport;
38+
import com.oracle.svm.core.SubstrateOptions;
3839
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
3940
import com.oracle.svm.core.feature.InternalFeature;
40-
import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue;
41-
import com.oracle.svm.core.option.HostedOptionKey;
4241
import com.oracle.svm.core.option.SubstrateOptionsParser;
4342
import com.oracle.svm.core.util.BasedOnJDKFile;
4443
import com.oracle.svm.core.util.UserError;
4544
import com.oracle.svm.hosted.reflect.ReflectionFeature;
4645

47-
import jdk.graal.compiler.options.Option;
48-
import jdk.graal.compiler.options.OptionStability;
46+
import java.io.IOException;
47+
import java.util.jar.JarFile;
4948

5049
/**
5150
* This feature supports instrumentation in native image.
5251
*/
5352
@AutomaticallyRegisteredFeature
5453
public class InstrumentFeature implements InternalFeature {
55-
public static class Options {
56-
@Option(help = "Specify premain-class list. Multiple classes are separated by comma, and order matters. This is an experimental option.", stability = OptionStability.EXPERIMENTAL)//
57-
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> PremainClasses = new HostedOptionKey<>(
58-
AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter());
59-
60-
}
61-
6254
@Override
6355
public boolean isInConfiguration(IsInConfigurationAccess access) {
64-
return !Options.PremainClasses.getValue().values().isEmpty();
56+
return !SubstrateOptions.JavaAgent.getValue().values().isEmpty();
6557
}
6658

6759
@Override
@@ -77,28 +69,50 @@ public void afterRegistration(AfterRegistrationAccess access) {
7769
PreMainSupport support = new PreMainSupport();
7870
ImageSingletons.add(PreMainSupport.class, support);
7971

80-
List<String> premainClasses = Options.PremainClasses.getValue().values();
81-
for (String clazz : premainClasses) {
82-
addPremainClass(support, cl, clazz);
72+
List<String> agentOptions = SubstrateOptions.JavaAgent.getValue().values();
73+
for (String agentOption : agentOptions) {
74+
addPremainClass(support, cl, agentOption);
8375
}
8476
}
8577

86-
private static void addPremainClass(PreMainSupport support, ClassLoader cl, String premainClass) {
78+
private static void addPremainClass(PreMainSupport support, ClassLoader cl, String javaagentOption) {
79+
int separatorIndex = javaagentOption.indexOf("=");
80+
String agent;
81+
String premainClass = null;
82+
String options = "";
83+
// Get the agent file
84+
if (separatorIndex == -1) {
85+
agent = javaagentOption;
86+
} else {
87+
agent = javaagentOption.substring(0, separatorIndex);
88+
options = javaagentOption.substring(separatorIndex + 1);
89+
}
90+
// Read MANIFEST in agent jar
91+
try {
92+
JarFile agentJarFile = new JarFile(agent);
93+
premainClass = agentJarFile.getManifest().getMainAttributes().getValue("Premain-Class");
94+
} catch (IOException e) {
95+
// This should never happen because the image build process (HotSpot) already loaded the
96+
// agent during startup.
97+
throw UserError.abort(e, "Can't read the agent jar %s. Please check option %s", agent,
98+
SubstrateOptionsParser.commandArgument(SubstrateOptions.JavaAgent, ""));
99+
}
100+
87101
try {
88102
Class<?> clazz = Class.forName(premainClass, false, cl);
89103
Method premain = findPremainMethod(premainClass, clazz);
90104

91105
List<Object> args = new ArrayList<>();
92106
/* The first argument contains the premain options, which will be set at runtime. */
93-
args.add("");
107+
args.add(options);
94108
if (premain.getParameterCount() == 2) {
95109
args.add(new PreMainSupport.NativeImageNoOpRuntimeInstrumentation());
96110
}
97111

98112
support.registerPremainMethod(premainClass, premain, args.toArray(new Object[0]));
99113
} catch (ClassNotFoundException e) {
100-
UserError.abort("Could not register agent premain method because class %s was not found. Please check your %s setting.", premainClass,
101-
SubstrateOptionsParser.commandArgument(Options.PremainClasses, ""));
114+
throw UserError.abort("Could not register agent premain method because class %s was not found. Please check your %s setting.", premainClass,
115+
SubstrateOptionsParser.commandArgument(SubstrateOptions.JavaAgent, ""));
102116
}
103117
}
104118

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ public final class NativeImageClassLoader extends SecureClassLoader {
8989
private final ClassLoader parent;
9090

9191
/* Unmodifiable maps used by this loader */
92-
private final Map<String, ModuleReference> localNameToModule;
93-
private final Map<String, LoadedModule> localPackageToModule;
94-
private final Map<String, ClassLoader> remotePackageToLoader;
92+
private Map<String, ModuleReference> localNameToModule = new HashMap<>();
93+
private Map<String, LoadedModule> localPackageToModule = new HashMap<>();
94+
private Map<String, ClassLoader> remotePackageToLoader = new HashMap<>();
9595

9696
/* Modifiable map used by this loader */
9797
private final ConcurrentHashMap<ModuleReference, ModuleReader> moduleToReader;
@@ -141,12 +141,20 @@ CodeSource codeSource() {
141141
* See {@code jdk.internal.loader.Loader#Loader} and
142142
* {@code java.net.URLClassLoader#URLClassLoader}.
143143
*/
144-
NativeImageClassLoader(List<Path> classpath, Configuration configuration, ClassLoader parent) {
144+
NativeImageClassLoader(List<Path> classpath, ClassLoader parent) {
145145
super(parent);
146146

147147
Objects.requireNonNull(parent);
148148
this.parent = parent;
149149

150+
/* The only map that gets updated concurrently during the lifetime of this loader. */
151+
moduleToReader = new ConcurrentHashMap<>();
152+
153+
/* Initialize URLClassPath that is used to lookup classes from class-path. */
154+
ucp = new URLClassPath(classpath.stream().map(NativeImageClassLoader::toURL).toArray(URL[]::new), null);
155+
}
156+
157+
public void update(List<Path> classpath, Configuration configuration) {
150158
Map<String, ModuleReference> nameToModule = new HashMap<>();
151159
Map<String, LoadedModule> packageToModule = new HashMap<>();
152160
for (ResolvedModule resolvedModule : configuration.modules()) {
@@ -168,11 +176,7 @@ CodeSource codeSource() {
168176
*/
169177
remotePackageToLoader = initRemotePackageMap(configuration, List.of(ModuleLayer.boot()));
170178

171-
/* The only map that gets updated concurrently during the lifetime of this loader. */
172-
moduleToReader = new ConcurrentHashMap<>();
173-
174-
/* Initialize URLClassPath that is used to lookup classes from class-path. */
175-
ucp = new URLClassPath(classpath.stream().map(NativeImageClassLoader::toURL).toArray(URL[]::new), null);
179+
classpath.stream().map(NativeImageClassLoader::toURL).forEach(url -> ucp.addURL(url));
176180
}
177181

178182
public static URL toURL(Path p) {
@@ -786,4 +790,8 @@ private static boolean isOpen(ModuleReference mref, String pn) {
786790
}
787791
return false;
788792
}
793+
794+
public void appendToClassPathForInstrumentation(String classPathEntry) {
795+
ucp.addFile(classPathEntry);
796+
}
789797
}

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public class NativeImageClassLoaderSupport {
132132
private final Set<Class<?>> classesToIncludeUnconditionally = Collections.newSetFromMap(new ConcurrentHashMap<>());
133133

134134
@SuppressWarnings("this-escape")
135-
protected NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, String[] classpath, String[] modulePath) {
135+
protected NativeImageClassLoaderSupport(ClassLoader nativeImageClassLoader, String[] classpath, String[] modulePath) {
136136

137137
classes = EconomicMap.create();
138138
packages = EconomicMap.create();
@@ -184,8 +184,12 @@ protected NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, St
184184
* {@link Configuration#resolve(ModuleFinder, ModuleFinder, Collection)}.
185185
*/
186186
Configuration configuration = ModuleLayer.boot().configuration().resolve(modulePathsFinder, upgradeAndSystemModuleFinder, moduleNames);
187-
188-
classLoader = new NativeImageClassLoader(imagecp, configuration, defaultSystemClassLoader);
187+
if (nativeImageClassLoader instanceof NativeImageClassLoader) {
188+
classLoader = (NativeImageClassLoader) nativeImageClassLoader;
189+
} else {
190+
classLoader = new NativeImageClassLoader(imagecp, nativeImageClassLoader);
191+
}
192+
classLoader.update(imagecp, configuration);
189193

190194
ModuleLayer moduleLayer = ModuleLayer.defineModules(configuration, List.of(ModuleLayer.boot()), ignored -> classLoader).layer();
191195
adjustBootLayerQualifiedExports(moduleLayer);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ public static void uninstallNativeImageClassLoader() {
282282
*/
283283
public static ImageClassLoader installNativeImageClassLoader(String[] classpath, String[] modulepath, List<String> arguments) {
284284
NativeImageSystemClassLoader nativeImageSystemClassLoader = NativeImageSystemClassLoader.singleton();
285-
NativeImageClassLoaderSupport nativeImageClassLoaderSupport = new NativeImageClassLoaderSupport(nativeImageSystemClassLoader.defaultSystemClassLoader, classpath, modulepath);
285+
NativeImageClassLoaderSupport nativeImageClassLoaderSupport = new NativeImageClassLoaderSupport(nativeImageSystemClassLoader.getActiveClassLoader(), classpath, modulepath);
286286
nativeImageClassLoaderSupport.setupHostedOptionParser(arguments);
287287
/* Perform additional post-processing with the created nativeImageClassLoaderSupport */
288288
for (NativeImageClassLoaderPostProcessing postProcessing : ServiceLoader.load(NativeImageClassLoaderPostProcessing.class)) {

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727
import java.io.IOException;
2828
import java.lang.reflect.Method;
2929
import java.net.URL;
30+
import java.nio.file.Paths;
3031
import java.security.SecureClassLoader;
3132
import java.util.Collections;
3233
import java.util.Enumeration;
34+
import java.util.List;
3335
import java.util.Set;
3436
import java.util.WeakHashMap;
3537

@@ -177,7 +179,7 @@ public String toString() {
177179
'}';
178180
}
179181

180-
private ClassLoader getActiveClassLoader() {
182+
public ClassLoader getActiveClassLoader() {
181183
ClassLoader activeClassLoader = nativeImageClassLoader;
182184
if (activeClassLoader != null) {
183185
return activeClassLoader;
@@ -195,7 +197,10 @@ private ClassLoader getActiveClassLoader() {
195197
*/
196198
@SuppressWarnings("unused") // no direct use from Java
197199
private void appendToClassPathForInstrumentation(String classPathEntry) {
198-
Method method = ReflectionUtil.lookupMethod(getParent().getClass(), "appendToClassPathForInstrumentation", String.class);
199-
ReflectionUtil.invokeMethod(method, getParent(), classPathEntry);
200+
if (nativeImageClassLoader == null) {
201+
nativeImageClassLoader = new NativeImageClassLoader(List.of(Paths.get(classPathEntry)), defaultSystemClassLoader);
202+
} else {
203+
((NativeImageClassLoader) nativeImageClassLoader).appendToClassPathForInstrumentation(classPathEntry);
204+
}
200205
}
201206
}

0 commit comments

Comments
 (0)