|
27 | 27 | import static com.oracle.svm.core.util.VMError.guarantee;
|
28 | 28 | import static com.oracle.svm.jni.JNIObjectHandles.nullHandle;
|
29 | 29 | import static com.oracle.svm.jvmtiagentbase.Support.callObjectMethod;
|
| 30 | +import static com.oracle.svm.jvmtiagentbase.Support.callObjectMethodL; |
30 | 31 | import static com.oracle.svm.jvmtiagentbase.Support.check;
|
31 | 32 | import static com.oracle.svm.jvmtiagentbase.Support.checkJni;
|
32 | 33 | import static com.oracle.svm.jvmtiagentbase.Support.checkNoException;
|
|
39 | 40 | import static com.oracle.svm.jvmtiagentbase.Support.getClassNameOrNull;
|
40 | 41 | import static com.oracle.svm.jvmtiagentbase.Support.getDirectCallerClass;
|
41 | 42 | import static com.oracle.svm.jvmtiagentbase.Support.getMethodDeclaringClass;
|
| 43 | +import static com.oracle.svm.jvmtiagentbase.Support.getMethodFullNameAtFrame; |
42 | 44 | import static com.oracle.svm.jvmtiagentbase.Support.getObjectArgument;
|
43 | 45 | import static com.oracle.svm.jvmtiagentbase.Support.handleException;
|
44 | 46 | import static com.oracle.svm.jvmtiagentbase.Support.jniFunctions;
|
|
52 | 54 | import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_NATIVE_METHOD_BIND;
|
53 | 55 | import static org.graalvm.word.WordFactory.nullPointer;
|
54 | 56 |
|
| 57 | +import java.io.IOException; |
55 | 58 | import java.nio.ByteBuffer;
|
56 | 59 | import java.nio.ByteOrder;
|
| 60 | +import java.security.NoSuchAlgorithmException; |
57 | 61 | import java.util.ArrayList;
|
58 | 62 | import java.util.Arrays;
|
59 | 63 | import java.util.HashMap;
|
60 | 64 | import java.util.List;
|
61 | 65 | import java.util.Map;
|
| 66 | +import java.util.Set; |
62 | 67 | import java.util.concurrent.ConcurrentHashMap;
|
63 | 68 | import java.util.concurrent.ConcurrentMap;
|
64 | 69 | import java.util.concurrent.locks.ReentrantLock;
|
65 | 70 |
|
66 | 71 | import org.graalvm.compiler.core.common.NumUtil;
|
| 72 | +import org.graalvm.compiler.phases.common.LazyValue; |
67 | 73 | import org.graalvm.nativeimage.StackValue;
|
68 | 74 | import org.graalvm.nativeimage.UnmanagedMemory;
|
69 | 75 | import org.graalvm.nativeimage.c.function.CEntryPoint;
|
|
80 | 86 | import org.graalvm.nativeimage.c.type.WordPointer;
|
81 | 87 | import org.graalvm.word.WordFactory;
|
82 | 88 |
|
| 89 | +import com.oracle.svm.configure.trace.AccessAdvisor; |
83 | 90 | import com.oracle.svm.core.c.function.CEntryPointOptions;
|
84 | 91 | import com.oracle.svm.core.util.VMError;
|
85 | 92 | import com.oracle.svm.jni.JNIObjectHandles;
|
@@ -126,7 +133,8 @@ final class BreakpointInterceptor {
|
126 | 133 | private static NativeImageAgent agent;
|
127 | 134 |
|
128 | 135 | private static Map<Long, Breakpoint> installedBreakpoints;
|
129 |
| - |
| 136 | + private static Set<String> definedClasses = ConcurrentHashMap.newKeySet(); |
| 137 | + private static List<String> unsupportedExceptions = new ArrayList<>(); |
130 | 138 | /**
|
131 | 139 | * A map from {@link JNIMethodId} to entry point addresses for bound Java {@code native}
|
132 | 140 | * methods, NOT considering our intercepting functions, i.e., these are the original entry
|
@@ -169,6 +177,18 @@ private static void traceBreakpoint(JNIEnvironment env, JNIObjectHandle clazz, J
|
169 | 177 | }
|
170 | 178 | }
|
171 | 179 |
|
| 180 | + static void traceBreakpoint(JNIEnvironment env, JNIObjectHandle clazz, JNIObjectHandle declaringClass, |
| 181 | + JNIObjectHandle callerClass, String function, boolean allowWrite, boolean unsafeAccess, Object result, |
| 182 | + String fieldName) { |
| 183 | + if (traceWriter != null) { |
| 184 | + traceWriter.traceCall("reflect", function, getClassNameOr(env, clazz, null, TraceWriter.UNKNOWN_VALUE), |
| 185 | + getClassNameOr(env, declaringClass, null, TraceWriter.UNKNOWN_VALUE), |
| 186 | + getClassNameOr(env, callerClass, null, TraceWriter.UNKNOWN_VALUE), result, allowWrite, unsafeAccess, |
| 187 | + fieldName); |
| 188 | + guarantee(!testException(env)); |
| 189 | + } |
| 190 | + } |
| 191 | + |
172 | 192 | private static boolean forName(JNIEnvironment jni, Breakpoint bp) {
|
173 | 193 | JNIObjectHandle callerClass = getDirectCallerClass();
|
174 | 194 | JNIObjectHandle name = getObjectArgument(0);
|
@@ -573,6 +593,96 @@ private static boolean handleGetSystemResources(JNIEnvironment jni, Breakpoint b
|
573 | 593 | return true;
|
574 | 594 | }
|
575 | 595 |
|
| 596 | + /** |
| 597 | + * java.lang.ClassLoader.postDefineClass is always called in java.lang.ClassLoader.defineClass, |
| 598 | + * so intercepting postDefineClass is equivalent to intercepting defineClass but with extra |
| 599 | + * benefit of being always able to get defined class' name even if defineClass' classname |
| 600 | + * parameter is null. |
| 601 | + */ |
| 602 | + @SuppressWarnings("unused") |
| 603 | + private static boolean postDefineClass(JNIEnvironment jni, Breakpoint bp) { |
| 604 | + boolean isDynamicallyGenerated = false; |
| 605 | + |
| 606 | + // Get class name from the argument "name" of |
| 607 | + // defineClass(String name, byte[] b, int off, int len) |
| 608 | + // The first argument is implicitly "this", so "name" is the 2nd parameter. |
| 609 | + String nameFromDefineClassParam = fromJniString(jni, getObjectArgument(1, 1)); |
| 610 | + final String definedClassName; |
| 611 | + JNIObjectHandle self = getObjectArgument(0); |
| 612 | + // 1. Don't have a name for class before defining. |
| 613 | + // The class is dynamically generated. |
| 614 | + if (nameFromDefineClassParam == null) { |
| 615 | + isDynamicallyGenerated = true; |
| 616 | + // Get name from parameter "c" of method postDefineClass(Class<?> c, ProtectionDomain |
| 617 | + // pd) |
| 618 | + definedClassName = getClassNameOrNull(jni, getObjectArgument(1)); |
| 619 | + } else { |
| 620 | + definedClassName = nameFromDefineClassParam; |
| 621 | + // Filter out internal classes which are definitely not dynamically generated |
| 622 | + // CallerClass is always java.lang.ClassLoader, we only check the defined class |
| 623 | + AccessAdvisor postDefineCLassAccessAdvisor = new AccessAdvisor(); |
| 624 | + postDefineCLassAccessAdvisor.setInLivePhase(true); |
| 625 | + if (postDefineCLassAccessAdvisor.shouldIgnore(new LazyValue<>(() -> definedClassName), new LazyValue<>(() -> null))) { |
| 626 | + isDynamicallyGenerated = false; |
| 627 | + } |
| 628 | + |
| 629 | + // 2. Class with name starts with $ or contains $$ is usually dynamically generated |
| 630 | + String className = definedClassName.substring(definedClassName.lastIndexOf('.') + 1); |
| 631 | + if (className.startsWith("$") || className.contains("$$")) { |
| 632 | + isDynamicallyGenerated = true; |
| 633 | + } else { |
| 634 | + // 3. A dynamically defined class always return null |
| 635 | + // when call java.lang.ClassLoader.getResource(classname) |
| 636 | + // This is the accurate but slow way. |
| 637 | + String asResourceName = definedClassName.replace('.', '/') + ".class"; |
| 638 | + try (CCharPointerHolder resourceNameHolder = toCString(asResourceName);) { |
| 639 | + JNIObjectHandle resourceNameJString = jniFunctions().getNewStringUTF().invoke(jni, resourceNameHolder.get()); |
| 640 | + JNIObjectHandle returnValue = callObjectMethodL(jni, self, agent.handles().javaLangClassLoaderGetResource, resourceNameJString); |
| 641 | + isDynamicallyGenerated = returnValue.equal(nullHandle()); |
| 642 | + } |
| 643 | + } |
| 644 | + } |
| 645 | + |
| 646 | + // CallerClass is always java.lang.ClassLoader, we only check the defined class |
| 647 | + Object result = false; |
| 648 | + boolean justAdded = definedClasses.add(definedClassName); |
| 649 | + if (isDynamicallyGenerated) { |
| 650 | + if (!justAdded) { |
| 651 | + unsupportedExceptions.add("Class " + definedClassName + " has been defined before. Multiple definitions are not supported.\n" + |
| 652 | + ClassLoaderDefineClassSupport.getStackTrace(jni).toString()); |
| 653 | + return true; |
| 654 | + } |
| 655 | + // Check the caller is using byte array or directedBuffer |
| 656 | + String caller = getMethodFullNameAtFrame(jni, 1); |
| 657 | + boolean isByteArray = "java.lang.ClassLoader.defineClass(Ljava/lang/String;[BIILjava/security/ProtectionDomain;)Ljava/lang/Class;".equals(caller); |
| 658 | + // Verify if defineClass succeeds |
| 659 | + // As we hook on postDefineClass method which is the last step of defineClass, so if |
| 660 | + // it can execute successfully, the whole defineClass method can execute |
| 661 | + // successfully |
| 662 | + JNIValue args = StackValue.get(2, JNIValue.class); |
| 663 | + args.addressOf(0).setObject(getObjectArgument(1)); |
| 664 | + args.addressOf(1).setObject(getObjectArgument(2)); |
| 665 | + jniFunctions().getCallVoidMethodA().invoke(jni, self, bp.method, args); |
| 666 | + if (clearException(jni)) { |
| 667 | + // No need to proceed if any exception happens |
| 668 | + result = false; |
| 669 | + } else { |
| 670 | + result = true; |
| 671 | + } |
| 672 | + try { |
| 673 | + JNIObjectHandle callerClass = getDirectCallerClass(); |
| 674 | + ClassLoaderDefineClassSupport dynamicSupport = new ClassLoaderDefineClassSupport(jni, callerClass, |
| 675 | + definedClassName, traceWriter, agent, isByteArray); |
| 676 | + dynamicSupport.trace(result); |
| 677 | + return true; |
| 678 | + } catch (NoSuchAlgorithmException e) { |
| 679 | + throw new RuntimeException(e); |
| 680 | + } |
| 681 | + } else { |
| 682 | + return true; |
| 683 | + } |
| 684 | + } |
| 685 | + |
576 | 686 | private static boolean newProxyInstance(JNIEnvironment jni, Breakpoint bp) {
|
577 | 687 | JNIObjectHandle callerClass = getDirectCallerClass();
|
578 | 688 | JNIObjectHandle classLoader = getObjectArgument(0);
|
@@ -1269,15 +1379,29 @@ private static void bindNativeBreakpoint(JNIEnvironment jni, NativeBreakpoint bp
|
1269 | 1379 | }
|
1270 | 1380 | }
|
1271 | 1381 |
|
| 1382 | + public static void reportExceptions() { |
| 1383 | + if (!unsupportedExceptions.isEmpty()) { |
| 1384 | + System.err.println(unsupportedExceptions.size() + " unsupported features are detected "); |
| 1385 | + StringBuilder errorMsg = new StringBuilder(); |
| 1386 | + for (int i = 0; i < unsupportedExceptions.size(); i++) { |
| 1387 | + errorMsg.append(unsupportedExceptions.get(i)).append("\n"); |
| 1388 | + } |
| 1389 | + throw new UnsupportedOperationException(errorMsg.toString()); |
| 1390 | + } else { |
| 1391 | + unsupportedExceptions = null; |
| 1392 | + } |
| 1393 | + } |
| 1394 | + |
1272 | 1395 | public static void onUnload() {
|
1273 | 1396 | installedBreakpoints = null;
|
1274 | 1397 | nativeBreakpoints = null;
|
1275 | 1398 | observedExplicitLoadClassCallSites = null;
|
1276 | 1399 | traceWriter = null;
|
| 1400 | + definedClasses = null; |
1277 | 1401 | }
|
1278 | 1402 |
|
1279 | 1403 | private interface BreakpointHandler {
|
1280 |
| - boolean dispatch(JNIEnvironment jni, Breakpoint bp); |
| 1404 | + boolean dispatch(JNIEnvironment jni, Breakpoint bp) throws IOException; |
1281 | 1405 | }
|
1282 | 1406 |
|
1283 | 1407 | private static final BreakpointSpecification[] BREAKPOINT_SPECIFICATIONS = {
|
@@ -1319,6 +1443,10 @@ private interface BreakpointHandler {
|
1319 | 1443 | brk("java/lang/reflect/Proxy", "getProxyClass", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;)Ljava/lang/Class;", BreakpointInterceptor::getProxyClass),
|
1320 | 1444 | brk("java/lang/reflect/Proxy", "newProxyInstance",
|
1321 | 1445 | "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;", BreakpointInterceptor::newProxyInstance),
|
| 1446 | + /* |
| 1447 | + * For dumping dynamically generated classes |
| 1448 | + */ |
| 1449 | + brk("java/lang/ClassLoader", "postDefineClass", "(Ljava/lang/Class;Ljava/security/ProtectionDomain;)V", BreakpointInterceptor::postDefineClass), |
1322 | 1450 |
|
1323 | 1451 | brk("java/io/ObjectStreamClass", "<init>", "(Ljava/lang/Class;)V", BreakpointInterceptor::objectStreamClassConstructor),
|
1324 | 1452 | optionalBrk("java/util/ResourceBundle",
|
|
0 commit comments