| /* |
| * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <jvmti.h> |
| #include "agent_common.h" |
| |
| #include "JVMTITools.h" |
| #include "jvmti_tools.h" |
| #include "nsk_tools.h" |
| |
| /* ====================================================================== */ |
| |
| static const char *classSig = |
| "Lnsk/jvmti/scenarios/jni_interception/JI01/ji01t001;"; |
| |
| static jvmtiEnv *jvmti = NULL; |
| static jrawMonitorID eventLock; |
| static jvmtiEventCallbacks callbacks; |
| static jint result = NSK_STATUS_PASSED; |
| |
| /* the original JNI function table */ |
| static jniNativeInterface *orig_jni_functions = NULL; |
| |
| /* the redirected JNI function table */ |
| static jniNativeInterface *redir_jni_functions = NULL; |
| |
| /* number of the redirected JNI function calls */ |
| static volatile int fnd_calls = 0; |
| |
| /* ====================================================================== */ |
| /** redirected JNI functions **/ |
| jclass JNICALL MyFindClass(JNIEnv *env, const char *name) { |
| if (isThreadExpected(jvmti, NULL)) { |
| fnd_calls++; |
| |
| NSK_DISPLAY1("MyFindClass: the function was called successfully: number of calls so far = %d\n", fnd_calls); |
| } |
| |
| return orig_jni_functions->FindClass(env, name); |
| } |
| |
| /* ====================================================================== */ |
| static int getVMPhase(jvmtiEnv *jvmti) { |
| jvmtiPhase phase; |
| |
| if (!NSK_JVMTI_VERIFY( |
| NSK_CPP_STUB2( |
| GetPhase |
| , jvmti |
| , &phase |
| ) |
| ) |
| ) |
| exit(NSK_STATUS_FAILED); |
| |
| return phase; |
| } |
| |
| /* ====================================================================== */ |
| static void doRedirect(jvmtiEnv *jvmti, int phase) { |
| jvmtiError err; |
| NSK_DISPLAY0("doRedirect: obtaining the JNI function table ...\n"); |
| |
| // Store original function table |
| if (!NSK_VERIFY( |
| (err = NSK_CPP_STUB2( |
| GetJNIFunctionTable |
| , jvmti |
| , &orig_jni_functions |
| )) == JVMTI_ERROR_NONE || phase != JVMTI_PHASE_LIVE |
| ) |
| ) |
| { |
| NSK_COMPLAIN2("TEST FAILED: failed to get original JNI function table during %s: %s\n" |
| , TranslatePhase(phase) |
| , TranslateError(err) |
| ); |
| |
| result = NSK_STATUS_FAILED; |
| exit(NSK_STATUS_FAILED); |
| } |
| else { |
| NSK_DISPLAY3("CHECK PASSED: the original JNI function table %s during %s phase: %s\n" |
| , (err == JVMTI_ERROR_NONE) ? "has been obtained" : "hasn't been obtained" |
| , TranslatePhase(phase) |
| , TranslateError(err) |
| ); |
| } |
| |
| // Get a duplicate of the function table for future modification |
| if (!NSK_VERIFY( |
| (err = NSK_CPP_STUB2( |
| GetJNIFunctionTable |
| , jvmti |
| , &redir_jni_functions |
| )) == JVMTI_ERROR_NONE || phase != JVMTI_PHASE_LIVE |
| ) |
| ) |
| { |
| NSK_COMPLAIN2("TEST FAILED: failed to get JNI function table for interception during %s: %s\n" |
| , TranslatePhase(phase) |
| , TranslateError(err) |
| ); |
| |
| result = NSK_STATUS_FAILED; |
| exit(NSK_STATUS_FAILED); |
| } |
| else { |
| NSK_DISPLAY3("CHECK PASSED: the original JNI function table for interception %s during %s phase: %s\n" |
| , (err == JVMTI_ERROR_NONE) ? "has been obtained" : "hasn't been obtained" |
| , TranslatePhase(phase) |
| , TranslateError(err) |
| ); |
| } |
| |
| // Redefine desired JNI functions |
| if (phase == JVMTI_PHASE_LIVE) { |
| NSK_DISPLAY0("doRedirect: overwriting the function FindClass; ...\n"); |
| redir_jni_functions->FindClass = MyFindClass; |
| } |
| |
| // Set new JNI function table |
| if (!NSK_VERIFY( |
| (err = NSK_CPP_STUB2( |
| SetJNIFunctionTable |
| , jvmti |
| , redir_jni_functions |
| )) == JVMTI_ERROR_NONE || phase != JVMTI_PHASE_LIVE |
| ) |
| ) |
| { |
| NSK_COMPLAIN2("TEST FAILED: failed to set redirected JNI function table during %s: %s\n" |
| , TranslatePhase(phase) |
| , TranslateError(err) |
| ); |
| |
| result = NSK_STATUS_FAILED; |
| exit(NSK_STATUS_FAILED); |
| } |
| else { |
| NSK_DISPLAY3("CHECK PASSED: the redirected JNI function table %s during %s phase: %s\n" |
| , (err == JVMTI_ERROR_NONE) ? "has been set" : "hasn't been set" |
| , TranslatePhase(phase) |
| , TranslateError(err) |
| ); |
| } |
| } |
| |
| /* ====================================================================== */ |
| static void doRestore(jvmtiEnv *jvmti) { |
| NSK_DISPLAY0("doRestore: restoring the original JNI function table ...\n"); |
| |
| // Set new JNI function table |
| if (!NSK_JVMTI_VERIFY( |
| NSK_CPP_STUB2( |
| SetJNIFunctionTable |
| , jvmti |
| , orig_jni_functions |
| ) |
| ) |
| ) |
| { |
| NSK_COMPLAIN0("TEST FAILED: failed to restore original JNI function table\n"); |
| |
| result = NSK_STATUS_FAILED; |
| exit(NSK_STATUS_FAILED); |
| } |
| |
| NSK_DISPLAY0("doRestore: the original JNI function table is restored successfully\n"); |
| } |
| |
| /* ====================================================================== */ |
| static void lock(jvmtiEnv *jvmti) { |
| if (!NSK_JVMTI_VERIFY( |
| NSK_CPP_STUB2( |
| RawMonitorEnter |
| , jvmti |
| , eventLock |
| ) |
| ) |
| ) |
| { |
| result = NSK_STATUS_FAILED; |
| exit(NSK_STATUS_FAILED); |
| } |
| } |
| |
| /* ====================================================================== */ |
| static void unlock(jvmtiEnv *jvmti) { |
| if (!NSK_JVMTI_VERIFY( |
| NSK_CPP_STUB2( |
| RawMonitorExit |
| , jvmti |
| , eventLock |
| ) |
| ) |
| ) |
| { |
| result = NSK_STATUS_FAILED; |
| exit(NSK_STATUS_FAILED); |
| } |
| } |
| |
| /* ====================================================================== */ |
| static void checkCall(JNIEnv *env |
| , int step |
| , const char *callBackFunc |
| , const char *msg |
| , int exFndCalls |
| ) |
| { |
| jclass cls; |
| |
| NSK_TRACE( |
| (cls = NSK_CPP_STUB2( |
| FindClass |
| , env |
| , classSig |
| )) |
| ); |
| |
| NSK_TRACE( |
| NSK_CPP_STUB1(ExceptionClear, env) |
| ); |
| |
| // The check should pass if the actual number of invocations is not less that the expected number (fnd_calls >= exFndCalls). |
| // If the invocation is not expected (exFndCalls == 0), fnd_calls should be also == 0. |
| if ((exFndCalls > 0 && fnd_calls >= exFndCalls) || (fnd_calls == exFndCalls)) { |
| NSK_DISPLAY5("CHECK PASSED: %s: the %s JNI function FindClass() has been %s during %s phase\n\t%d intercepted call(s) as expected\n" |
| , callBackFunc |
| , (step==1) ? "tested" : "original" |
| , (step==1) ? "redirected" : "restored" |
| , msg |
| , fnd_calls |
| ); |
| |
| if (fnd_calls != exFndCalls) { |
| NSK_COMPLAIN2("WARNING: the number of occured calls (%d) exceeds the expected number of calls (%d).\n" |
| , fnd_calls |
| , exFndCalls |
| ); |
| } |
| } else { |
| result = NSK_STATUS_FAILED; |
| |
| NSK_COMPLAIN6("TEST FAILED: %s: the %s JNI function FindClass() has not been %s during %s phase\n\t%d intercepted call(s) instead of %d as expected\n" |
| , callBackFunc |
| , (step==1) ? "tested" : "original" |
| , (step==1) ? "redirected" : "restored" |
| , msg |
| , fnd_calls |
| , exFndCalls |
| ); |
| } |
| } |
| |
| /* ====================================================================== */ |
| // callback functions |
| void JNICALL |
| VMInit(jvmtiEnv *jvmti, JNIEnv *env, jthread thread) { |
| jvmtiPhase phase = getVMPhase(jvmti); |
| |
| NSK_TRACE(lock(jvmti)); |
| |
| NSK_DISPLAY1("b) VMInit: the current phase of VM execution %s\n" |
| , TranslatePhase(phase) |
| ); |
| |
| // check JNI function table interception |
| fnd_calls = 0; |
| NSK_TRACE(doRedirect(jvmti, phase)); |
| NSK_TRACE(checkCall(env, 1, "VMInit", TranslatePhase(phase), 1)); |
| |
| // check restored JNI function table |
| fnd_calls = 0; |
| NSK_TRACE(doRestore(jvmti)); |
| NSK_TRACE(checkCall(env, 2, "VMInit", TranslatePhase(phase), 0)); |
| |
| NSK_TRACE(unlock(jvmti)); |
| } |
| |
| /* ====================================================================== */ |
| void JNICALL |
| VMDeath(jvmtiEnv *jvmti, JNIEnv *env) { |
| jvmtiPhase phase = getVMPhase(jvmti); |
| |
| NSK_TRACE(lock(jvmti)); |
| |
| NSK_DISPLAY1("c) VMDeath: the current phase of VM execution %s\n" |
| , TranslatePhase(phase) |
| ); |
| |
| // check JNI function table interception |
| fnd_calls = 0; |
| NSK_TRACE(doRedirect(jvmti, phase)); |
| NSK_TRACE(checkCall(env, 1, "VMDeath", TranslatePhase(phase), 1)); |
| |
| // check restored JNI function table |
| fnd_calls = 0; |
| NSK_TRACE(doRestore(jvmti)); |
| NSK_TRACE(checkCall(env, 2, "VMDeath", TranslatePhase(phase), 0)); |
| |
| (void) memset(&callbacks, 0, sizeof(callbacks)); |
| |
| if (!NSK_JVMTI_VERIFY( |
| NSK_CPP_STUB3( |
| SetEventCallbacks |
| , jvmti |
| , &callbacks |
| , sizeof(callbacks) |
| ) |
| ) |
| ) |
| result = NSK_STATUS_FAILED; |
| |
| NSK_TRACE(unlock(jvmti)); |
| |
| if (result == NSK_STATUS_FAILED) { |
| exit(NSK_STATUS_FAILED); |
| } |
| } |
| |
| /* ====================================================================== */ |
| JNIEXPORT jint JNICALL |
| Java_nsk_jvmti_scenarios_jni_1interception_JI01_ji01t001_check(JNIEnv *env, jobject obj) { |
| return result; |
| } |
| |
| /* ====================================================================== */ |
| #ifdef STATIC_BUILD |
| JNIEXPORT jint JNICALL Agent_OnLoad_ji01t001(JavaVM *jvm, char *options, void *reserved) { |
| return Agent_Initialize(jvm, options, reserved); |
| } |
| JNIEXPORT jint JNICALL Agent_OnAttach_ji01t001(JavaVM *jvm, char *options, void *reserved) { |
| return Agent_Initialize(jvm, options, reserved); |
| } |
| JNIEXPORT jint JNI_OnLoad_ji01t001(JavaVM *jvm, char *options, void *reserved) { |
| return JNI_VERSION_1_8; |
| } |
| #endif |
| jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { |
| if (!NSK_VERIFY( |
| nsk_jvmti_parseOptions(options) |
| ) |
| ) |
| return JNI_ERR; |
| |
| |
| if (!NSK_VERIFY( |
| NSK_CPP_STUB3( |
| GetEnv |
| , jvm |
| , (void **) &jvmti |
| , JVMTI_VERSION_1_1 |
| ) == JNI_OK |
| && jvmti != NULL |
| ) |
| ) |
| return JNI_ERR; |
| |
| |
| if (!NSK_JVMTI_VERIFY( |
| NSK_CPP_STUB3( |
| CreateRawMonitor |
| , jvmti |
| , "_event_lock" |
| , &eventLock |
| ) |
| ) |
| ) |
| return JNI_ERR; |
| |
| NSK_DISPLAY1("a) Trying to intercept JNI functions during %s phase ...\n" |
| , TranslatePhase(getVMPhase(jvmti)) |
| ); |
| |
| NSK_TRACE(doRedirect(jvmti, getVMPhase(jvmti))); |
| |
| NSK_DISPLAY0("Setting event callbacks...\n"); |
| |
| (void) memset(&callbacks, 0, sizeof(callbacks)); |
| callbacks.VMInit = &VMInit; |
| callbacks.VMDeath = &VMDeath; |
| |
| if (!NSK_JVMTI_VERIFY( |
| NSK_CPP_STUB3( |
| SetEventCallbacks |
| , jvmti |
| , &callbacks |
| , sizeof(callbacks) |
| ) |
| ) |
| ) |
| return JNI_ERR; |
| |
| |
| NSK_DISPLAY0("Event callbacks are set\nEnabling events...\n"); |
| |
| if (!NSK_JVMTI_VERIFY( |
| NSK_CPP_STUB4( |
| SetEventNotificationMode |
| , jvmti |
| , JVMTI_ENABLE |
| , JVMTI_EVENT_VM_INIT |
| , NULL |
| ) |
| ) |
| ) |
| return JNI_ERR; |
| |
| |
| if (!NSK_JVMTI_VERIFY( |
| NSK_CPP_STUB4( |
| SetEventNotificationMode |
| , jvmti |
| , JVMTI_ENABLE |
| , JVMTI_EVENT_VM_DEATH |
| , NULL |
| ) |
| ) |
| ) |
| return JNI_ERR; |
| |
| NSK_DISPLAY0("Events are enabled\n"); |
| |
| return JNI_OK; |
| } |