JVMTI provides a mechanism for intercepting JNI calls. For example, to intercept FindClass calls, you can do the following:
jvmti->GetJNIFunctionTable(&redir_jni_functions);
redir_jni_functions->FindClass = MyFindClass;
jvmti->SetJNIFunctionTable(redir_jni_functions);
I noticed in a couple of JVMTI tests, there is some confusion over the current thread when the intercept call is made. If the current thread is a virtual thread, for some reason GetThreadInfo(NULL) returns the name of the carrier thread, even though GetStackTrace(NULL) properly returns the stack trace of the virtual thread. This can be observed in the FindClass callback in the following test:
vmTestbase/nsk/jvmti/scenarios/jni_interception/JI01/ji01t001/
If you add the following to the MyFindClass method:
nsk_printf("MyFindClass(%s)\n",classname);
jvmtiThreadInfo threadinfo;
NSK_JVMTI_VERIFY(jvmti->GetThreadInfo(NULL, &threadinfo));
nsk_printf("thread name: %s\n", threadinfo.name);
jvmtiError err;
jvmtiFrameInfo f[30];
jclass callerClass;
char *sigClass, *name, *sig, *generic;
jint i, count;
err = jvmti->GetStackTrace(NULL, 0, 30, f, &count);
for (i = 0; i < count; i++) {
jvmti->GetMethodDeclaringClass(f[i].method, &callerClass);
jvmti->GetClassSignature(callerClass, &sigClass, &generic);
jvmti->GetMethodName(f[i].method, &name, &sig, &generic);
nsk_printf(">>> frame %d: %s %s%s\n", i, sigClass, name, sig);
}
And run with:
make test TEST=open/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/jni_interception/JI01/ji01t001/ JTREG_MAIN_WRAPPER=Virtual
You will see the following in the output:
MyFindClass(Lnsk/jvmti/scenarios/jni_interception/JI01/ji01t001;)
thread name: ForkJoinPool-1-worker-1
>>> frame 0: Ljava/lang/Shutdown; halt0(I)V
>>> frame 1: Ljava/lang/Shutdown; halt(I)V
>>> frame 2: Ljava/lang/Shutdown; exit(I)V
>>> frame 3: Ljava/lang/Runtime; exit(I)V
>>> frame 4: Ljava/lang/System; exit(I)V
>>> frame 5: Lnsk/jvmti/scenarios/jni_interception/JI01/ji01t001; main([Ljava/lang/String;)V
>>> frame 6: Ljava/lang/invoke/LambdaForm$DMH.0x0000000801003c00; invokeStatic(Ljava/lang/Object;Ljava/lang/Object;)V
>>> frame 7: Ljava/lang/invoke/LambdaForm$MH.0x0000000801004c00; invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
>>> frame 8: Ljava/lang/invoke/Invokers$Holder; invokeExact_MT(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
>>> frame 9: Ljdk/internal/reflect/DirectMethodHandleAccessor; invokeImpl(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
>>> frame 10: Ljdk/internal/reflect/DirectMethodHandleAccessor; invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
>>> frame 11: Ljava/lang/reflect/Method; invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
>>> frame 12: Lcom/sun/javatest/regtest/agent/MainWrapper$MainTask; run()V
>>> frame 13: Ljava/lang/VirtualThread; run(Ljava/lang/Runnable;)V
>>> frame 14: Ljava/lang/VirtualThread$VThreadContinuation; lambda$new$0(Ljava/lang/VirtualThread;Ljava/lang/Runnable;)V
>>> frame 15: Ljava/lang/VirtualThread$VThreadContinuation$$Lambda$6.0x000000080104b170; run()V
>>> frame 16: Ljdk/internal/vm/Continuation; enter0()V
>>> frame 17: Ljdk/internal/vm/Continuation; enter(Ljdk/internal/vm/Continuation;Z)V
Notice the following:
- classname is for a class the test is trying to load
- the thread name is for a carrier thread
- the thread stack trace is for a virtual thread
- we are in the middle of a VM shutdown.