Skip to content

Commit a13778c

Browse files
committed
Support dynamic class loading
Support dynamic class loading for native image
1 parent 05ce413 commit a13778c

File tree

25 files changed

+1016
-44
lines changed

25 files changed

+1016
-44
lines changed

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

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static com.oracle.svm.core.util.VMError.guarantee;
2828
import static com.oracle.svm.jni.JNIObjectHandles.nullHandle;
2929
import static com.oracle.svm.jvmtiagentbase.Support.callObjectMethod;
30+
import static com.oracle.svm.jvmtiagentbase.Support.callObjectMethodL;
3031
import static com.oracle.svm.jvmtiagentbase.Support.check;
3132
import static com.oracle.svm.jvmtiagentbase.Support.checkJni;
3233
import static com.oracle.svm.jvmtiagentbase.Support.checkNoException;
@@ -50,6 +51,7 @@
5051
import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_BREAKPOINT;
5152
import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_CLASS_PREPARE;
5253
import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_NATIVE_METHOD_BIND;
54+
import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_CLASS_FILE_LOAD_HOOK;
5355
import static org.graalvm.word.WordFactory.nullPointer;
5456

5557
import java.nio.ByteBuffer;
@@ -63,6 +65,7 @@
6365
import java.util.concurrent.ConcurrentMap;
6466
import java.util.concurrent.locks.ReentrantLock;
6567

68+
import com.oracle.svm.core.util.JavaClassUtil;
6669
import org.graalvm.compiler.core.common.NumUtil;
6770
import org.graalvm.nativeimage.StackValue;
6871
import org.graalvm.nativeimage.UnmanagedMemory;
@@ -126,7 +129,7 @@ final class BreakpointInterceptor {
126129
private static NativeImageAgent agent;
127130

128131
private static Map<Long, Breakpoint> installedBreakpoints;
129-
132+
private static List<String> unsupportedExceptions = new ArrayList<>();
130133
/**
131134
* A map from {@link JNIMethodId} to entry point addresses for bound Java {@code native}
132135
* methods, NOT considering our intercepting functions, i.e., these are the original entry
@@ -1085,6 +1088,52 @@ private static void installBreakpointIfClassLoader(JNIEnvironment jni, JNIObject
10851088
}
10861089
}
10871090

1091+
@CEntryPoint
1092+
@CEntryPointOptions(prologue = AgentIsolate.Prologue.class)
1093+
private static void onClassFileLoadHook(@SuppressWarnings("unused") JvmtiEnv jvmti, JNIEnvironment jni,
1094+
@SuppressWarnings("unused") JNIObjectHandle classBeingRedefined, JNIObjectHandle loader, CCharPointer name, @SuppressWarnings("unused") JNIObjectHandle protectionDomain,
1095+
int classDataLen,
1096+
CCharPointer classData, @SuppressWarnings("unused") CIntPointer newClassDataLen, @SuppressWarnings("unused") CCharPointerPointer newClassData) {
1097+
boolean nameIsNull = name.isNull();
1098+
if (isDynamicallyGenerated(jni, loader, nameIsNull, nameIsNull ? "" : fromCString(name))) {
1099+
byte[] contents = new byte[classDataLen];
1100+
CTypeConversion.asByteBuffer(classData, classDataLen).get(contents);
1101+
String definedClassName = nameIsNull ? JavaClassUtil.getClassName(contents) : fromCString(name);
1102+
ClassLoaderDefineClassSupport.trace(traceWriter, contents, definedClassName, true);
1103+
}
1104+
}
1105+
1106+
private static boolean isDynamicallyGenerated(JNIEnvironment jni, JNIObjectHandle classLoader, boolean inputNameIsNull, String definedClassName) {
1107+
boolean isDynamicallyGenerated;
1108+
// 1. Classloader is null, it's a system class.
1109+
// The class is not dynamically generated.
1110+
if (classLoader.equal(nullHandle())) {
1111+
isDynamicallyGenerated = false;
1112+
} else {
1113+
// 2. Don't have a name for class before defining.
1114+
// The class is dynamically generated.
1115+
if (inputNameIsNull) {
1116+
isDynamicallyGenerated = true;
1117+
} else {
1118+
// 3. A dynamically defined class always return null
1119+
// when call java.lang.ClassLoader.getResource(classname)
1120+
// This is the accurate but slow way.
1121+
String asResourceName = definedClassName.replace('.', '/') + ".class";
1122+
try (CCharPointerHolder resourceNameHolder = toCString(asResourceName);) {
1123+
JNIObjectHandle resourceNameJString = jniFunctions().getNewStringUTF().invoke(jni, resourceNameHolder.get());
1124+
if (agent.handles() == null) {
1125+
// agent's handles is created at onVMStart.
1126+
isDynamicallyGenerated = false;
1127+
} else {
1128+
JNIObjectHandle returnValue = callObjectMethodL(jni, classLoader, agent.handles().javaLangClassLoaderGetResource, resourceNameJString);
1129+
isDynamicallyGenerated = returnValue.equal(nullHandle());
1130+
}
1131+
}
1132+
}
1133+
}
1134+
return isDynamicallyGenerated;
1135+
}
1136+
10881137
private static final CEntryPointLiteral<CFunctionPointer> onBreakpointLiteral = CEntryPointLiteral.create(BreakpointInterceptor.class, "onBreakpoint",
10891138
JvmtiEnv.class, JNIEnvironment.class, JNIObjectHandle.class, JNIMethodId.class, long.class);
10901139

@@ -1094,6 +1143,10 @@ private static void installBreakpointIfClassLoader(JNIEnvironment jni, JNIObject
10941143
private static final CEntryPointLiteral<CFunctionPointer> onClassPrepareLiteral = CEntryPointLiteral.create(BreakpointInterceptor.class, "onClassPrepare",
10951144
JvmtiEnv.class, JNIEnvironment.class, JNIObjectHandle.class, JNIObjectHandle.class);
10961145

1146+
private static final CEntryPointLiteral<CFunctionPointer> onClassFileLoadHookLiteral = CEntryPointLiteral.create(BreakpointInterceptor.class, "onClassFileLoadHook",
1147+
JvmtiEnv.class, JNIEnvironment.class, JNIObjectHandle.class, JNIObjectHandle.class, CCharPointer.class, JNIObjectHandle.class, int.class, CCharPointer.class, CIntPointer.class,
1148+
CCharPointerPointer.class);
1149+
10971150
public static void onLoad(JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, TraceWriter writer, NativeImageAgent nativeImageTracingAgent,
10981151
boolean exptlClassLoaderSupport) {
10991152

@@ -1106,6 +1159,7 @@ public static void onLoad(JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, TraceWr
11061159
capabilities.setCanGenerateBreakpointEvents(1);
11071160
capabilities.setCanAccessLocalVariables(1);
11081161
capabilities.setCanGenerateNativeMethodBindEvents(1);
1162+
capabilities.setCanGenerateAllClassHookEvents(1);
11091163
if (exptlClassLoaderSupport) {
11101164
capabilities.setCanGetBytecodes(1);
11111165
capabilities.setCanGetConstantPool(1);
@@ -1125,6 +1179,9 @@ public static void onLoad(JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, TraceWr
11251179

11261180
BreakpointInterceptor.boundNativeMethods = new HashMap<>();
11271181
Support.check(jvmti.getFunctions().SetEventNotificationMode().invoke(jvmti, JvmtiEventMode.JVMTI_ENABLE, JVMTI_EVENT_NATIVE_METHOD_BIND, nullHandle()));
1182+
1183+
callbacks.setClassFileLoadHook(onClassFileLoadHookLiteral.getFunctionPointer());
1184+
Support.check(jvmti.getFunctions().SetEventNotificationMode().invoke(jvmti, JvmtiEventMode.JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, nullHandle()));
11281185
}
11291186

11301187
public static void onVMInit(JvmtiEnv jvmti, JNIEnvironment jni) {
@@ -1269,6 +1326,19 @@ private static void bindNativeBreakpoint(JNIEnvironment jni, NativeBreakpoint bp
12691326
}
12701327
}
12711328

1329+
public static void reportExceptions() {
1330+
if (!unsupportedExceptions.isEmpty()) {
1331+
System.err.println(unsupportedExceptions.size() + " unsupported features are detected ");
1332+
StringBuilder errorMsg = new StringBuilder();
1333+
for (int i = 0; i < unsupportedExceptions.size(); i++) {
1334+
errorMsg.append(unsupportedExceptions.get(i)).append("\n");
1335+
}
1336+
throw new UnsupportedOperationException(errorMsg.toString());
1337+
} else {
1338+
unsupportedExceptions = null;
1339+
}
1340+
}
1341+
12721342
public static void onUnload() {
12731343
installedBreakpoints = null;
12741344
nativeBreakpoints = null;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
3+
* Copyright (c) 2020, 2021, Alibaba Group Holding Limited. All rights reserved.
4+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
*
6+
* This code is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 only, as
8+
* published by the Free Software Foundation. Oracle designates this
9+
* particular file as subject to the "Classpath" exception as provided
10+
* by Oracle in the LICENSE file that accompanied this code.
11+
*
12+
* This code is distributed in the hope that it will be useful, but WITHOUT
13+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15+
* version 2 for more details (a copy is included in the LICENSE file that
16+
* accompanied this code).
17+
*
18+
* You should have received a copy of the GNU General Public License version
19+
* 2 along with this work; if not, write to the Free Software Foundation,
20+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21+
*
22+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23+
* or visit www.oracle.com if you need additional information or have any
24+
* questions.
25+
*/
26+
package com.oracle.svm.agent;
27+
28+
import com.oracle.svm.core.util.JavaClassUtil;
29+
import com.oracle.svm.jni.nativeapi.JNIEnvironment;
30+
31+
import java.security.NoSuchAlgorithmException;
32+
33+
import static com.oracle.svm.jvmtiagentbase.Support.getMethodFullNameAtFrame;
34+
35+
/**
36+
* Support dynamic class loading that is implemented by java.lang.ClassLoader.defineClass.
37+
*/
38+
public class ClassLoaderDefineClassSupport {
39+
40+
private static String calculateGeneratedClassSHA(byte[] values) {
41+
String generatedClassHashCode;
42+
try {
43+
generatedClassHashCode = JavaClassUtil.getSHAWithoutSourceFileInfo(values);
44+
} catch (NoSuchAlgorithmException e) {
45+
generatedClassHashCode = null;
46+
}
47+
return generatedClassHashCode;
48+
}
49+
50+
public static void trace(TraceWriter traceWriter, byte[] classContents, String generatedClassName, Object result) {
51+
assert classContents != null;
52+
if (generatedClassName != null && result != null) {
53+
// Trace dynamically generated class in config file
54+
traceWriter.traceCall("classDefiner", "onClassFileLoadHook", null, null, null, result, generatedClassName.replace('/', '.'), calculateGeneratedClassSHA(classContents), classContents);
55+
}
56+
}
57+
58+
public static StringBuilder getStackTrace(JNIEnvironment jni) {
59+
StringBuilder trace = new StringBuilder();
60+
int i = 0;
61+
int maxDepth = 20;
62+
while (i < maxDepth) {
63+
String methodName = getMethodFullNameAtFrame(jni, i++);
64+
if (methodName == null) {
65+
break;
66+
}
67+
trace.append(" ").append(methodName).append("\n");
68+
}
69+
if (i >= maxDepth) {
70+
trace.append(" ").append("...").append("\n");
71+
}
72+
return trace;
73+
}
74+
}

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import java.util.concurrent.TimeUnit;
4747
import java.util.function.Function;
4848
import java.util.regex.Pattern;
49+
import java.util.stream.Collectors;
4950

5051
import org.graalvm.nativeimage.ProcessProperties;
5152
import org.graalvm.nativeimage.hosted.Feature;
@@ -216,7 +217,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
216217
// They should use the same filter sets, however.
217218
AccessAdvisor advisor = createAccessAdvisor(builtinHeuristicFilter, callerFilter, accessFilter);
218219
TraceProcessor processor = new TraceProcessor(advisor, mergeConfigs.loadJniConfig(handler), mergeConfigs.loadReflectConfig(handler),
219-
mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler), mergeConfigs.loadSerializationConfig(handler));
220+
mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler), mergeConfigs.loadSerializationConfig(handler),
221+
mergeConfigs.loadDynamicClassesConfig(handler));
220222
traceWriter = new TraceProcessorWriterAdapter(processor);
221223
} catch (Throwable t) {
222224
return error(2, t.toString());
@@ -425,6 +427,7 @@ private void writeConfigurationFiles() {
425427
allConfigFiles.put(ConfigurationFiles.DYNAMIC_PROXY_NAME, p.getProxyConfiguration());
426428
allConfigFiles.put(ConfigurationFiles.RESOURCES_NAME, p.getResourceConfiguration());
427429
allConfigFiles.put(ConfigurationFiles.SERIALIZATION_NAME, p.getSerializationConfiguration());
430+
allConfigFiles.put(ConfigurationFiles.DYNAMIC_CLASSES_NAME, p.getDynamicClassesConfiguration());
428431

429432
for (Map.Entry<String, JsonPrintable> configFile : allConfigFiles.entrySet()) {
430433
Path tempPath = tempDirectory.resolve(configFile.getKey());
@@ -439,6 +442,18 @@ private void writeConfigurationFiles() {
439442
tryAtomicMove(source, target);
440443
}
441444

445+
Path dumpedClassSrc = tempDirectory.resolve(ConfigurationFiles.DUMP_CLASSES_DIR);
446+
Path dumpedClassTarget = configOutputDirPath.resolve(ConfigurationFiles.DUMP_CLASSES_DIR);
447+
// Move the entire directory if the target directory does not exist
448+
if (Files.notExists(dumpedClassTarget)) {
449+
tryAtomicMove(dumpedClassSrc, dumpedClassTarget);
450+
} else {
451+
// Move each file inside the source directory if the target directory exists
452+
for (Path filePath : Files.list(dumpedClassSrc).collect(Collectors.toList())) {
453+
tryAtomicMove(filePath, dumpedClassTarget.resolve(filePath.getFileName()));
454+
}
455+
}
456+
442457
compulsoryDelete(tempDirectory);
443458
} catch (IOException e) {
444459
warnUpToLimit(currentFailuresWritingConfigs++, MAX_WARNINGS_FOR_WRITING_CONFIGS_FAILURES, "Error when writing configuration files: " + e.toString());
@@ -509,7 +524,7 @@ protected int onUnloadCallback(JNIJavaVM vm) {
509524
* (unless another JVM is launched in this process).
510525
*/
511526
// cleanupOnUnload(vm);
512-
527+
BreakpointInterceptor.reportExceptions();
513528
/*
514529
* The epilogue of this method does not tear down our VM: we don't seem to observe all
515530
* threads that end and therefore can't detach them, so we would wait forever for them.

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,15 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet {
3939
final JNIObjectHandle javaLangClass;
4040
final JNIMethodId javaLangClassForName3;
4141
final JNIMethodId javaUtilEnumerationNextElement;
42-
final JNIMethodId javaLangClassGetDeclaredMethod;
43-
final JNIMethodId javaLangClassGetDeclaredConstructor;
44-
final JNIMethodId javaLangClassGetDeclaredField;
45-
final JNIMethodId javaLangClassGetName;
46-
4742
final JNIMethodId javaLangReflectMemberGetName;
4843
final JNIMethodId javaLangReflectMemberGetDeclaringClass;
49-
5044
final JNIMethodId javaUtilEnumerationHasMoreElements;
51-
45+
final JNIMethodId javaLangClassLoaderGetResource;
5246
final JNIObjectHandle javaLangClassLoader;
47+
final JNIMethodId javaLangClassGetDeclaredMethod;
48+
final JNIMethodId javaLangClassGetDeclaredConstructor;
49+
final JNIMethodId javaLangClassGetDeclaredField;
50+
final JNIMethodId javaLangClassGetName;
5351

5452
final JNIMethodId javaLangObjectGetClass;
5553

@@ -78,6 +76,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet {
7876
javaLangClassGetDeclaredField = getMethodId(env, javaLangClass, "getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;", false);
7977
javaLangClassGetName = getMethodId(env, javaLangClass, "getName", "()Ljava/lang/String;", false);
8078

79+
javaLangClassLoaderGetResource = getMethodId(env, findClass(env, "java/lang/ClassLoader"), "getResource", "(Ljava/lang/String;)Ljava/net/URL;", false);
8180
JNIObjectHandle javaLangReflectMember = findClass(env, "java/lang/reflect/Member");
8281
javaLangReflectMemberGetName = getMethodId(env, javaLangReflectMember, "getName", "()Ljava/lang/String;", false);
8382
javaLangReflectMemberGetDeclaringClass = getMethodId(env, javaLangReflectMember, "getDeclaringClass", "()Ljava/lang/Class;", false);

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,12 @@ private static void generate(Iterator<String> argsIter, boolean acceptTraceFileA
195195
set.getSerializationConfigPaths().add(requirePathUri(current, value));
196196
break;
197197

198+
case "--dynamic-classes-input":
199+
set = inputSet; // fall through
200+
case "--dynamic-classes-output":
201+
set.getDynamicClassesConfigPaths().add(requirePathUri(current, value));
202+
break;
203+
198204
case "--trace-input":
199205
traceInputs.add(requirePathUri(current, value));
200206
break;
@@ -256,7 +262,8 @@ private static void generate(Iterator<String> argsIter, boolean acceptTraceFileA
256262
try {
257263
p = new TraceProcessor(advisor, inputSet.loadJniConfig(ConfigurationSet.FAIL_ON_EXCEPTION), inputSet.loadReflectConfig(ConfigurationSet.FAIL_ON_EXCEPTION),
258264
inputSet.loadProxyConfig(ConfigurationSet.FAIL_ON_EXCEPTION), inputSet.loadResourceConfig(ConfigurationSet.FAIL_ON_EXCEPTION),
259-
inputSet.loadSerializationConfig(ConfigurationSet.FAIL_ON_EXCEPTION));
265+
inputSet.loadSerializationConfig(ConfigurationSet.FAIL_ON_EXCEPTION),
266+
inputSet.loadDynamicClassesConfig(ConfigurationSet.FAIL_ON_EXCEPTION));
260267
} catch (IOException e) {
261268
throw e;
262269
} catch (Throwable t) {
@@ -299,6 +306,11 @@ private static void generate(Iterator<String> argsIter, boolean acceptTraceFileA
299306
p.getSerializationConfiguration().printJson(writer);
300307
}
301308
}
309+
for (URI uri : outputSet.getDynamicClassesConfigPaths()) {
310+
try (JsonWriter writer = new JsonWriter(Paths.get(uri))) {
311+
p.getDynamicClassesConfiguration().printJson(writer);
312+
}
313+
}
302314
}
303315

304316
private static void generateFilterRules(Iterator<String> argsIter) throws IOException {

0 commit comments

Comments
 (0)