Skip to content

Commit 4daafb3

Browse files
committed
Support instrumentation class by agent
This commit supports the static class instrumentation by agent, i.e. the target class is transformed in Agent's preMain method. Instrumentation is supported in 3 stages: 1. Interception stage: native-image-agent records all the transfromed classes, dynamic generated classes, and the target agent's premain method. 2. Build time stage: Instrumented classes are prepended to the beginning of imageCp or patched to modules, so they will override their original classes at build time. 3. Runtime stage: Add premain in JavaMainWrapper before executing Java main. The recorded agent premain method shall be executed at this moment. It is the agent developer's responsibility to make sure the premain is compatible with native image. For example, the byte code transformation should be removed from the native premain.
1 parent 9761497 commit 4daafb3

File tree

34 files changed

+1367
-52
lines changed

34 files changed

+1367
-52
lines changed

substratevm/mx.substratevm/suite.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,9 @@
306306
"jdk.internal.vm",
307307
"jdk.internal.util",
308308
],
309+
"java.instrument":[
310+
"java.lang.instrument"
311+
],
309312
"java.management": [
310313
"com.sun.jmx.mbeanserver",
311314
"sun.management",
@@ -637,6 +640,9 @@
637640
"sun.util.locale",
638641
"sun.invoke.util",
639642
],
643+
"java.instrument":[
644+
"java.lang.instrument"
645+
],
640646
"java.management": [
641647
"com.sun.jmx.mbeanserver", # Needed for javadoc links (MXBeanIntrospector,DefaultMXBeanMappingFactory, MXBeanProxy)
642648
"sun.management", # Needed for javadoc links (MappedMXBeanType)
@@ -1334,7 +1340,11 @@
13341340
"requiresConcealed" : {
13351341
"jdk.internal.vm.ci": [
13361342
"jdk.vm.ci.meta",
1337-
]
1343+
],
1344+
"java.base": [
1345+
"jdk.internal.module",
1346+
"jdk.internal.org.objectweb.asm",
1347+
],
13381348
},
13391349
"checkstyle": "com.oracle.svm.hosted",
13401350
"workingSets": "SVM",
@@ -1822,6 +1832,9 @@
18221832
"jdk.internal.vm.ci" : [
18231833
"jdk.vm.ci.meta",
18241834
],
1835+
"java.base":[
1836+
"jdk.internal.module",
1837+
],
18251838
}
18261839
},
18271840
# vm: included as binary, tool descriptor intentionally not copied

substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java

Lines changed: 270 additions & 17 deletions
Large diffs are not rendered by default.

substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,12 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
136136
boolean builtinHeuristicFilter = true;
137137
List<String> callerFilterFiles = new ArrayList<>();
138138
List<String> accessFilterFiles = new ArrayList<>();
139+
List<String> predefineClassRules = new ArrayList<>();
139140
boolean experimentalClassLoaderSupport = true;
140141
boolean experimentalClassDefineSupport = false;
141142
boolean experimentalUnsafeAllocationSupport = false;
142143
boolean experimentalOmitClasspathConfig = false;
144+
boolean experimentalInstrumentSupport = false;
143145
boolean configurationWithOrigins = false;
144146
List<String> conditionalConfigUserPackageFilterFiles = new ArrayList<>();
145147
List<String> conditionalConfigClassNameFilterFiles = new ArrayList<>();
@@ -190,6 +192,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
190192
experimentalClassLoaderSupport = getBooleanTokenValue(token);
191193
} else if (isBooleanOption(token, "experimental-class-define-support")) {
192194
experimentalClassDefineSupport = getBooleanTokenValue(token);
195+
} else if (token.startsWith("predefine-class-rules=")) {
196+
predefineClassRules.add(getTokenValue(token));
193197
} else if (isBooleanOption(token, "experimental-unsafe-allocation-support")) {
194198
experimentalUnsafeAllocationSupport = getBooleanTokenValue(token);
195199
} else if (token.startsWith("config-write-period-secs=")) {
@@ -212,6 +216,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
212216
conditionalConfigPartialRun = getBooleanTokenValue(token);
213217
} else if (isBooleanOption(token, "track-reflection-metadata")) {
214218
trackReflectionMetadata = getBooleanTokenValue(token);
219+
} else if (isBooleanOption(token, "experimental-instrument")) {
220+
experimentalInstrumentSupport = getBooleanTokenValue(token);
215221
} else {
216222
return usage(1, "unknown option: '" + token + "'.");
217223
}
@@ -306,7 +312,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
306312
warn("Failed to load omitted config: " + e);
307313
return null;
308314
};
309-
omittedConfiguration = omittedConfigs.loadConfigurationSet(ignore, null, null);
315+
omittedConfiguration = omittedConfigs.loadConfigurationSet(ignore, null, null, null);
310316
shouldExcludeClassesWithHash = omittedConfiguration.getPredefinedClassesConfiguration()::containsClassWithHash;
311317
}
312318

@@ -340,6 +346,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
340346
}
341347
} else {
342348
Path[] predefinedClassDestDirs = {Files.createDirectories(configOutputDirPath.resolve(ConfigurationFile.PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR))};
349+
Path[] instrumentClassDestDirs = {Files.createDirectories(configOutputDirPath.resolve(ConfigurationFile.INSTRUMENT_CLASSES_SUBDIR))};
343350
Function<IOException, Exception> handler = e -> {
344351
if (e instanceof NoSuchFileException) {
345352
warn("file " + ((NoSuchFileException) e).getFile() + " for merging could not be found, skipping");
@@ -351,7 +358,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
351358
return e; // rethrow
352359
};
353360

354-
ConfigurationSet configuration = mergeConfigs.loadConfigurationSet(handler, predefinedClassDestDirs, shouldExcludeClassesWithHash);
361+
ConfigurationSet configuration = mergeConfigs.loadConfigurationSet(handler, predefinedClassDestDirs, shouldExcludeClassesWithHash, instrumentClassDestDirs);
355362
ConfigurationResultWriter writer = new ConfigurationResultWriter(processor, configuration, omittedConfiguration);
356363
tracer = writer;
357364
tracingResultWriter = writer;
@@ -373,7 +380,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
373380

374381
try {
375382
BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier,
376-
experimentalClassLoaderSupport, experimentalClassDefineSupport, experimentalUnsafeAllocationSupport, trackReflectionMetadata);
383+
experimentalClassLoaderSupport, experimentalClassDefineSupport, experimentalUnsafeAllocationSupport,
384+
trackReflectionMetadata, experimentalInstrumentSupport, predefineClassRules);
377385
} catch (Throwable t) {
378386
return error(3, t.toString());
379387
}

substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet {
4141
final JNIObjectHandle javaLangClassNotFoundException;
4242
final JNIMethodId javaLangClassGetName;
4343
final JNIMethodId javaLangClassGetInterfaces;
44+
final JNIFieldId javaLangClassModule;
4445

4546
final JNIMethodId javaLangReflectMemberGetName;
4647
final JNIMethodId javaLangReflectMemberGetDeclaringClass;
@@ -90,6 +91,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet {
9091
final JNIMethodId sunUtilResourcesBundlesCacheKeyGetLocale;
9192

9293
final JNIMethodId javaLangModuleGetName;
94+
final JNIFieldId javaLangModuleName;
9395

9496
NativeImageAgentJNIHandleSet(JNIEnvironment env) {
9597
super(env);
@@ -98,6 +100,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet {
98100
javaLangClassNotFoundException = newClassGlobalRef(env, "java/lang/ClassNotFoundException");
99101
javaLangClassGetName = getMethodId(env, javaLangClass, "getName", "()Ljava/lang/String;", false);
100102
javaLangClassGetInterfaces = getMethodId(env, javaLangClass, "getInterfaces", "()[Ljava/lang/Class;", false);
103+
javaLangClassModule = getFieldId(env, javaLangClass, "module", "Ljava/lang/Module;", false);
101104

102105
JNIObjectHandle javaLangReflectMember = findClass(env, "java/lang/reflect/Member");
103106
javaLangReflectMemberGetName = getMethodId(env, javaLangReflectMember, "getName", "()Ljava/lang/String;", false);
@@ -134,6 +137,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet {
134137

135138
JNIObjectHandle javaLangModule = findClass(env, "java/lang/Module");
136139
javaLangModuleGetName = getMethodId(env, javaLangModule, "getName", "()Ljava/lang/String;", false);
140+
javaLangModuleName = getFieldId(env, javaLangModule, "name", "Ljava/lang/String;", false);
137141
}
138142

139143
JNIMethodId getJavaLangReflectExecutableGetParameterTypes(JNIEnvironment env) {

substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ private static ConfigurationSet loadActualConfig() throws Exception {
8282
String configurationPath = System.getProperty(CONFIG_PATH_PROPERTY);
8383
ConfigurationFileCollection configurationFileCollection = new ConfigurationFileCollection();
8484
configurationFileCollection.addDirectory(Paths.get(configurationPath));
85-
return configurationFileCollection.loadConfigurationSet(e -> e, null, null);
85+
return configurationFileCollection.loadConfigurationSet(e -> e, null, null, null);
8686
}
8787

8888
private static ConfigurationSet loadExpectedConfig() throws Exception {
@@ -96,7 +96,7 @@ private static ConfigurationSet loadExpectedConfig() throws Exception {
9696
throw VMError.shouldNotReachHere("Unexpected error while locating the configuration files.", e);
9797
}
9898
});
99-
return configurationFileCollection.loadConfigurationSet(e -> e, null, null);
99+
return configurationFileCollection.loadConfigurationSet(e -> e, null, null, null);
100100
}
101101

102102
}

substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ private static ConfigurationSet loadTraceProcessorFromResourceDirectory(String r
8282
if (omittedConfig != null) {
8383
shouldExcludeClassesWithHash = omittedConfig.getPredefinedClassesConfiguration()::containsClassWithHash;
8484
}
85-
return configurationFileCollection.loadConfigurationSet(handler, null, shouldExcludeClassesWithHash);
85+
return configurationFileCollection.loadConfigurationSet(handler, null, shouldExcludeClassesWithHash, null);
8686
} catch (Exception e) {
8787
throw VMError.shouldNotReachHere("Unexpected error while loading the configuration files.", e);
8888
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/command/ConfigurationGenerateCommand.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,15 +264,22 @@ protected static void generate(Iterator<String> argumentsIterator, boolean accep
264264
ConfigurationSet omittedConfigurationSet;
265265

266266
try {
267-
omittedConfigurationSet = omittedCollection.loadConfigurationSet(ConfigurationFileCollection.FAIL_ON_EXCEPTION, null, null);
267+
omittedConfigurationSet = omittedCollection.loadConfigurationSet(ConfigurationFileCollection.FAIL_ON_EXCEPTION, null, null, null);
268268
List<Path> predefinedClassDestDirs = new ArrayList<>();
269269
for (URI pathUri : outputCollection.getPredefinedClassesConfigPaths()) {
270270
Path subdir = Files.createDirectories(Paths.get(pathUri).getParent().resolve(ConfigurationFile.PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR));
271271
subdir = Files.createDirectories(subdir);
272272
predefinedClassDestDirs.add(subdir);
273273
}
274+
List<Path> instrumentClassDestDirs = new ArrayList<>();
275+
for (URI pathUri : outputCollection.getInstrumentConfigPaths()) {
276+
Path subdir = Files.createDirectories(Paths.get(pathUri).getParent().resolve(ConfigurationFile.INSTRUMENT_CLASSES_SUBDIR));
277+
subdir = Files.createDirectories(subdir);
278+
instrumentClassDestDirs.add(subdir);
279+
}
274280
Predicate<String> shouldExcludeClassesWithHash = omittedConfigurationSet.getPredefinedClassesConfiguration()::containsClassWithHash;
275-
configurationSet = inputCollection.loadConfigurationSet(ConfigurationFileCollection.FAIL_ON_EXCEPTION, predefinedClassDestDirs.toArray(new Path[0]), shouldExcludeClassesWithHash);
281+
configurationSet = inputCollection.loadConfigurationSet(ConfigurationFileCollection.FAIL_ON_EXCEPTION, predefinedClassDestDirs.toArray(new Path[0]), shouldExcludeClassesWithHash,
282+
instrumentClassDestDirs.toArray(new Path[0]));
276283
} catch (IOException e) {
277284
throw e;
278285
} catch (Throwable t) {
@@ -333,6 +340,11 @@ protected static void generate(Iterator<String> argumentsIterator, boolean accep
333340
configurationSet.getPredefinedClassesConfiguration().printJson(writer);
334341
}
335342
}
343+
for (URI uri : outputCollection.getInstrumentConfigPaths()) {
344+
try (JsonWriter writer = new JsonWriter(Paths.get(uri))) {
345+
configurationSet.getInstrumentConfiguration().printJson(writer);
346+
}
347+
}
336348
}
337349

338350
private static void parseFilterFiles(ComplexFilter filter, List<URI> filterFiles) {

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class ConfigurationFileCollection {
4848
private final Set<URI> resourceConfigPaths = new LinkedHashSet<>();
4949
private final Set<URI> serializationConfigPaths = new LinkedHashSet<>();
5050
private final Set<URI> predefinedClassesConfigPaths = new LinkedHashSet<>();
51+
private final Set<URI> instrumentConfigPaths = new LinkedHashSet<>();
5152
private Set<URI> lockFilePaths;
5253

5354
public void addDirectory(Path path) {
@@ -57,6 +58,7 @@ public void addDirectory(Path path) {
5758
resourceConfigPaths.add(path.resolve(ConfigurationFile.RESOURCES.getFileName()).toUri());
5859
serializationConfigPaths.add(path.resolve(ConfigurationFile.SERIALIZATION.getFileName()).toUri());
5960
predefinedClassesConfigPaths.add(path.resolve(ConfigurationFile.PREDEFINED_CLASSES_NAME.getFileName()).toUri());
61+
instrumentConfigPaths.add(path.resolve(ConfigurationFile.INSTRUMENT.getFileName()).toUri());
6062
detectAgentLock(path.resolve(ConfigurationFile.LOCK_FILE_NAME), Files::exists, Path::toUri);
6163
}
6264

@@ -76,6 +78,7 @@ public void addDirectory(Function<String, URI> fileResolver) {
7678
resourceConfigPaths.add(fileResolver.apply(ConfigurationFile.RESOURCES.getFileName()));
7779
serializationConfigPaths.add(fileResolver.apply(ConfigurationFile.SERIALIZATION.getFileName()));
7880
predefinedClassesConfigPaths.add(fileResolver.apply(ConfigurationFile.PREDEFINED_CLASSES_NAME.getFileName()));
81+
instrumentConfigPaths.add(fileResolver.apply(ConfigurationFile.INSTRUMENT.getFileName()));
7982
detectAgentLock(fileResolver.apply(ConfigurationFile.LOCK_FILE_NAME), Objects::nonNull, Function.identity());
8083
}
8184

@@ -85,7 +88,8 @@ public Set<URI> getDetectedAgentLockPaths() {
8588

8689
public boolean isEmpty() {
8790
return jniConfigPaths.isEmpty() && reflectConfigPaths.isEmpty() && proxyConfigPaths.isEmpty() &&
88-
resourceConfigPaths.isEmpty() && serializationConfigPaths.isEmpty() && predefinedClassesConfigPaths.isEmpty();
91+
resourceConfigPaths.isEmpty() && serializationConfigPaths.isEmpty() &&
92+
predefinedClassesConfigPaths.isEmpty() && instrumentConfigPaths.isEmpty();
8993
}
9094

9195
public Set<URI> getJniConfigPaths() {
@@ -112,6 +116,10 @@ public Set<URI> getPredefinedClassesConfigPaths() {
112116
return predefinedClassesConfigPaths;
113117
}
114118

119+
public Set<URI> getInstrumentConfigPaths() {
120+
return instrumentConfigPaths;
121+
}
122+
115123
public TypeConfiguration loadJniConfig(Function<IOException, Exception> exceptionHandler) throws Exception {
116124
return loadTypeConfig(jniConfigPaths, exceptionHandler);
117125
}
@@ -133,6 +141,12 @@ public PredefinedClassesConfiguration loadPredefinedClassesConfig(Path[] classDe
133141
return predefinedClassesConfiguration;
134142
}
135143

144+
public InstrumentConfiguration loadInstrumentConfig(Path[] classDestinationDirs, Function<IOException, Exception> exceptionHandler) throws Exception {
145+
InstrumentConfiguration instrumentConfiguration = new InstrumentConfiguration(classDestinationDirs);
146+
loadConfig(instrumentConfigPaths, instrumentConfiguration.createParser(), exceptionHandler);
147+
return instrumentConfiguration;
148+
}
149+
136150
public ResourceConfiguration loadResourceConfig(Function<IOException, Exception> exceptionHandler) throws Exception {
137151
ResourceConfiguration resourceConfiguration = new ResourceConfiguration();
138152
loadConfig(resourceConfigPaths, resourceConfiguration.createParser(), exceptionHandler);
@@ -146,10 +160,11 @@ public SerializationConfiguration loadSerializationConfig(Function<IOException,
146160
}
147161

148162
public ConfigurationSet loadConfigurationSet(Function<IOException, Exception> exceptionHandler, Path[] predefinedConfigClassDestinationDirs,
149-
Predicate<String> predefinedConfigClassWithHashExclusionPredicate) throws Exception {
163+
Predicate<String> predefinedConfigClassWithHashExclusionPredicate, Path[] instrumentClassDestinationDirs) throws Exception {
150164
return new ConfigurationSet(loadReflectConfig(exceptionHandler), loadJniConfig(exceptionHandler), loadResourceConfig(exceptionHandler), loadProxyConfig(exceptionHandler),
151165
loadSerializationConfig(exceptionHandler),
152-
loadPredefinedClassesConfig(predefinedConfigClassDestinationDirs, predefinedConfigClassWithHashExclusionPredicate, exceptionHandler));
166+
loadPredefinedClassesConfig(predefinedConfigClassDestinationDirs, predefinedConfigClassWithHashExclusionPredicate, exceptionHandler),
167+
loadInstrumentConfig(instrumentClassDestinationDirs, exceptionHandler));
153168
}
154169

155170
private static TypeConfiguration loadTypeConfig(Collection<URI> uris, Function<IOException, Exception> exceptionHandler) throws Exception {

0 commit comments

Comments
 (0)