/* * Copyright (c) 2003, 2019, 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 #include #include #include #include /*************************************************************/ #include "jvmti.h" /*************************************************************/ #include "nsk_tools.h" #include "jni_tools.h" #include "jvmti_tools.h" #include "JVMTITools.h" /*************************************************************/ extern "C" { /*************************************************************/ #define NSK_JVMTI_WAITTIME 2 #define NSK_JVMTI_MAX_OPTIONS 10 #define NSK_JVMTI_OPTION_START '-' #define NSK_JVMTI_OPT_PATH_TO_NEW_BYTE_CODE "pathToNewByteCode" #define PATH_FORMAT "%s%02d/%s" #define DIR_NAME "newclass" static volatile int redefineAttempted = NSK_FALSE; static volatile int redefineSucceed = NSK_FALSE; static volatile int agentFailed = NSK_FALSE; static struct { struct { int count; char* names[NSK_JVMTI_MAX_OPTIONS]; char* values[NSK_JVMTI_MAX_OPTIONS]; char* string; } options; int waittime; } context; /*************************************************************/ static int check_option(int dashed, const char name[], const char value[]) { if (strcmp("verbose", name) == 0) { if (strlen(value) > 0) { nsk_complain("nsk_jvmti_parseOptions(): unexpected value in option: %s=%s\n", name, value); return NSK_FALSE; } nsk_setVerboseMode(NSK_TRUE); } else if (strcmp("trace", name) == 0) { if (strlen(value) <= 0) { nsk_complain("nsk_jvmti_parseOptions(): no value for option: %s\n", name); return NSK_FALSE; } if (strcmp("none", value) == 0) { nsk_setTraceMode(NSK_TRACE_NONE); } else if (strcmp("before", value) == 0) { nsk_setTraceMode(NSK_TRACE_BEFORE); } else if (strcmp("after", value) == 0) { nsk_setTraceMode(NSK_TRACE_AFTER); } else if (strcmp("all", value) == 0) { nsk_setTraceMode(NSK_TRACE_ALL); } else { nsk_complain("nsk_jvmti_parseOptions(): uexpected value in option: %s=%s\n", name, value); return NSK_FALSE; } nsk_setVerboseMode(NSK_TRUE); } else if (strcmp("waittime", name) == 0) { if (strlen(value) <= 0) { nsk_complain("nsk_jvmti_parseOptions(): no value for option: %s\n", name); return NSK_FALSE; } { char* end = NULL; long n = strtol(value, &end, 10); if (end == NULL || end == value || *end != '\0') { nsk_complain("nsk_jvmti_parseOptions(): not integer value in option: %s=%s\n", name, value); return NSK_FALSE; } if (n < 0) { nsk_complain("nsk_jvmti_parseOptions(): negative value in option: %s=%s\n", name, value); return NSK_FALSE; } context.waittime = (int)n; } } else if (dashed) { nsk_complain("nsk_jvmti_parseOptions(): unknown option: %c%s\n", NSK_JVMTI_OPTION_START, name); return NSK_FALSE; } return NSK_TRUE; } static int add_option(const char opt[], int opt_len, const char val[], int val_len) { char* name; char* value; int success = NSK_TRUE; int dashed_opt = NSK_FALSE; if (opt[0] == NSK_JVMTI_OPTION_START) { dashed_opt = NSK_TRUE; opt++; opt_len--; } if (opt_len <= 0) { nsk_complain("nsk_jvmti_parseOptions(): found empty option\n"); return NSK_FALSE; } name = (char*)malloc(opt_len + 1); value = (char*)malloc(val_len + 1); if (name == NULL || value == NULL) { nsk_complain("nsk_jvmti_parseOptions(): out of memory\n"); success = NSK_FALSE; } else { strncpy(name, opt, opt_len); name[opt_len] = '\0'; strncpy(value, val, val_len); value[val_len] = '\0'; if (!check_option(dashed_opt, name, value)) { success = NSK_FALSE; } } if (success) { if (context.options.count >= NSK_JVMTI_MAX_OPTIONS) { nsk_complain("nsk_jvmti_parseOptions(): too many options for parsing\n"); success = NSK_FALSE; } else { context.options.names[context.options.count] = name; context.options.values[context.options.count] = value; context.options.count++; } } if (!success) { if (name != NULL) free(name); if (value != NULL) free(value); } return success; } static void nsk_jvmti_free() { if (context.options.count > 0) { int i; for (i = 0; i < context.options.count; i++) { free(context.options.names[i]); free(context.options.values[i]); } context.options.count = 0; } if (context.options.string != NULL) { free(context.options.string); context.options.string = NULL; } } /* * Tokenize a string based on a list of delimiters. */ static char* token(char **s, const char *delim) { char *p; char *start = *s; if (s == NULL || *s == NULL) { return NULL; } p = strpbrk(*s, delim); if (p != NULL) { /* Advance to next token. */ *p = '\0'; *s = p + 1; } else { /* End of tokens. */ *s = NULL; } return start; } int nsk_jvmti_parseOptions(const char options[]) { int success = NSK_TRUE; char *str = NULL; char *name = NULL; char *value = NULL; const char *delimiters = " ,"; if (options == NULL) return success; /* * Save a copy of the full options string for * ArgumentHandler.getAgentOptionsString(). */ context.options.string = strdup(options); /* Create a temporary copy of the options string to be tokenized. */ str = strdup(options); while ((name = token(&str, delimiters)) != NULL) { value = strchr(name, '='); if (value != NULL) { *value++ = '\0'; } if (!add_option(name, (int)strlen(name), value, value ? (int)strlen(value) : 0)) { success = NSK_FALSE; break; } } if (!success) { nsk_jvmti_free(); } if (str != NULL) { free(str); } return success; } /*************************************************************/ /** * Returns value of given option name; or NULL if no such option found. * If search name is NULL then complains an error and returns NULL. */ const char* nsk_jvmti_findOptionValue(const char name[]) { int i; if (name == NULL) { nsk_complain("nsk_jvmti_findOptionValue(): option name is NULL\n"); return NULL; } for (i = 0; i < context.options.count; i++) { if (strcmp(name, context.options.names[i]) == 0) return context.options.values[i]; } return NULL; } /** * Returns string value of given option; or defaultValue if no such option found. * If options is specified but has empty value then complains an error and returns NULL. */ const char* nsk_jvmti_findOptionStringValue(const char name[], const char* defaultValue) { const char* value; if (name == NULL) { nsk_complain("nsk_jvmti_findOptionStringValue(): option name is NULL\n"); return NULL; } value = nsk_jvmti_findOptionValue(name); if (value == NULL) { return defaultValue; } if (strlen(value) <= 0) { nsk_complain("nsk_jvmti_findOptionStringValue(): empty value of option: %s=%s\n", name, value); return NULL; } return value; } /** * Returns integer value of given option; or defaultValue if no such option found. * If options is specified but has no integer value then complains an error and returns -1. */ int nsk_jvmti_findOptionIntValue(const char name[], int defaultValue) { const char* value; if (name == NULL) { nsk_complain("nsk_jvmti_findOptionIntValue(): option name is NULL\n"); return -1; } value = nsk_jvmti_findOptionValue(name); if (value == NULL) { return defaultValue; } if (strlen(value) <= 0) { nsk_complain("nsk_jvmti_findOptionIntValue(): empty value of option: %s=%s\n", name, value); return -1; } { char* endptr = NULL; int n = strtol(value, &endptr, 10); if (endptr == NULL || *endptr != '\0') { nsk_complain("nsk_jvmti_findOptionIntValue(): not integer value of option: %s=%s\n", name, value); return -1; } return n; } } /** * Returns number of parsed options. */ int nsk_jvmti_getOptionsCount() { return context.options.count; } /** * Returns name of i-th parsed option. * If no such option then complains an error and returns NULL. */ const char* nsk_jvmti_getOptionName(int i) { if (i < 0 || i >= context.options.count) { nsk_complain("nsk_jvmti_getOptionName(): option index out of bounds: %d\n", i); return NULL; } return context.options.names[i]; } /** * Returns value of i-th parsed option. * If no such option then complains an error and returns NULL. */ const char* nsk_jvmti_getOptionValue(int i) { if (i < 0 || i >= context.options.count) { nsk_complain("nsk_jvmti_getOptionValue(): option index out of bounds: %d\n", i); return NULL; } return context.options.values[i]; } /*************************************************************/ /** * Returns value of -waittime option or default value if not specified. */ int nsk_jvmti_getWaitTime() { return context.waittime; } /** * Sets specified waittime value. */ void nsk_jvmti_setWaitTime(int waittime) { context.waittime = waittime; } /*************************************************************/ int nsk_jvmti_lverify(int positive, jvmtiError error, jvmtiError expected, const char file[], int line, const char format[], ...) { int failure=0; int negative = !positive; int errorCode = (int)error; const char* errorName = TranslateError(error); va_list ap; va_start(ap,format); nsk_lvtrace(NSK_TRACE_AFTER,file,line,format,ap); if (negative || expected != JVMTI_ERROR_NONE) nsk_ltrace(NSK_TRACE_AFTER,file,line, " jvmti error: code=%d, name=%s\n",errorCode,errorName); if ((error == expected) == negative) { nsk_lvcomplain(file,line,format,ap); nsk_printf("# jvmti error: code=%d, name=%s\n",errorCode,errorName); if (expected != JVMTI_ERROR_NONE) nsk_printf("# error expected: code=%d, name=%s\n", expected, TranslateError(expected)); failure=1; }; va_end(ap); return !failure; } /*************************************************************/ JNIEXPORT jstring JNICALL Java_nsk_share_jvmti_ArgumentHandler_getAgentOptionsString(JNIEnv *jni, jobject obj) { jstring str_obj = NULL; if (!NSK_JNI_VERIFY(jni, (str_obj = jni->NewStringUTF(context.options.string)) != NULL)) { return NULL; } return str_obj; } /*************************************************************/ /** * This method will try to redefine the class (classToRedefine) by loading * physical file. pathToNewByteCode option which is passed * on OnLoad Phase also used. * * So This method will do a file read pathToByteCode+fileName+.class (total path). * Constrcuts a class objects and does a redefine of the class. * On successfull redefine this method will return eaither JNI_TRUE or JNI_FALSE * * Hint:: * 1) * If there are many redefine on same testcase, then please try to use * integer value (newclass00, newclass01, newclass02 , ....) way. * * 2) When you compile these please do keep, a metatag on testcase as * # build : native classes classes.redef * * 3) When you do build these classes are psysically located in build as. * * TESTBASE/bin/newclass0* directory. * eg: for nks/jvmti/scenarios/hotswap/HS204/hs204t001 you should see * TESTBASE/bin/newclass0* /nsk/hotswap/HS204/hs204t001/MyClass.class * */ int nsk_jvmti_redefineClass(jvmtiEnv * jvmti, jclass classToRedefine, const char * fileName) { redefineAttempted = NSK_TRUE; if (nsk_jvmti_findOptionValue(NSK_JVMTI_OPT_PATH_TO_NEW_BYTE_CODE) == NULL) { nsk_printf("# error expected: %s \n", NSK_JVMTI_OPT_PATH_TO_NEW_BYTE_CODE); nsk_printf("Hint :: missing java -agentlib:agentlib=%s=DirName, ($TESTBASE/bin) \n", NSK_JVMTI_OPT_PATH_TO_NEW_BYTE_CODE); return NSK_FALSE; } if (fileName == NULL) { nsk_printf("# error file name expected did not found \n"); return NSK_FALSE; } { char file [1024]; //= "DEFAULT"; sprintf(file,"%s/%s.class", nsk_jvmti_findOptionValue(NSK_JVMTI_OPT_PATH_TO_NEW_BYTE_CODE), fileName); nsk_printf("# info :: File = %s \n",file); { FILE *bytecode; unsigned char * classBytes; jvmtiError error; jint size; bytecode = fopen(file, "rb"); error= JVMTI_ERROR_NONE; if (bytecode == NULL) { nsk_printf("# error **Agent::error opening file %s \n",file); return NSK_FALSE; } nsk_printf("# info **Agent:: opening file %s \n",file); fseek(bytecode, 0, SEEK_END); size = ftell(bytecode); nsk_printf("# info file size= %ld\n",ftell(bytecode)); rewind(bytecode); error = jvmti->Allocate(size,&classBytes); if (error != JVMTI_ERROR_NONE) { nsk_printf(" Failed to create memory %s \n",TranslateError(error)); return NSK_FALSE; } if (((jint) fread(classBytes, 1,size,bytecode)) != size) { nsk_printf(" # error failed to read all the bytes , could be less or more \n"); return NSK_FALSE; } else { nsk_printf(" File red completely \n"); } fclose(bytecode); { jvmtiClassDefinition classDef; classDef.klass = classToRedefine; classDef.class_byte_count= size; classDef.class_bytes = classBytes; error = jvmti->RedefineClasses(1,&classDef); if (error != JVMTI_ERROR_NONE) { nsk_printf("# error occured while redefining %s ", TranslateError(error)); return NSK_FALSE; } } } } redefineSucceed= NSK_TRUE; return NSK_TRUE; } JNIEXPORT jboolean JNICALL Java_nsk_share_jvmti_RedefineAgent_redefineAttempted(JNIEnv *jni, jobject obj) { if (redefineAttempted == NSK_TRUE) { return JNI_TRUE; }else { return JNI_FALSE; } } JNIEXPORT jboolean JNICALL Java_nsk_share_jvmti_RedefineAgent_isRedefined(JNIEnv * jni, jobject obj) { if (redefineSucceed == NSK_TRUE) { return JNI_TRUE; }else { return JNI_FALSE; } } /** * This jni method is a Java wrapper for agent status. */ JNIEXPORT jboolean JNICALL Java_nsk_share_jvmti_RedefineAgent_agentStatus(JNIEnv * jni, jobject obj) { if (agentFailed == NSK_TRUE) { return JNI_FALSE; } else { return JNI_TRUE; } } void nsk_jvmti_getFileName(int redefineCnt, const char * dir, char * buf, size_t bufsize) { snprintf(buf, bufsize, PATH_FORMAT, DIR_NAME, redefineCnt, dir); buf[bufsize-1] = '\0'; } int nsk_jvmti_enableNotification(jvmtiEnv *jvmti,jvmtiEvent event, jthread thread) { jvmtiError rc=JVMTI_ERROR_NONE; rc = jvmti->SetEventNotificationMode(JVMTI_ENABLE, event, thread); if (rc != JVMTI_ERROR_NONE) { nsk_printf("# error Failed to set Notification for Event \n "); return NSK_FALSE; } return NSK_TRUE; } int nsk_jvmti_disableNotification(jvmtiEnv *jvmti,jvmtiEvent event, jthread thread) { jvmtiError rc=JVMTI_ERROR_NONE; rc = jvmti->SetEventNotificationMode(JVMTI_DISABLE, event, thread); if (rc != JVMTI_ERROR_NONE) { nsk_printf(" Failed to disaable Notification for Event "); return NSK_FALSE; } return NSK_TRUE; } void nsk_jvmti_agentFailed() { agentFailed = NSK_TRUE; } int isThreadExpected(jvmtiEnv *jvmti, jthread thread) { static const char *vm_jfr_buffer_thread_name = "VM JFR Buffer Thread"; static const char *jfr_request_timer_thread_name = "JFR request timer"; jvmtiThreadInfo threadinfo; NSK_JVMTI_VERIFY(jvmti->GetThreadInfo(thread, &threadinfo)); if (strcmp(threadinfo.name, vm_jfr_buffer_thread_name) == 0) return 0; if (strcmp(threadinfo.name, jfr_request_timer_thread_name) == 0) return 0; return 1; } #define SLEEP_DELAY 10L int suspendThreadAtMethod(jvmtiEnv *jvmti, jclass cls, jobject thread, jmethodID testMethod) { printf(">>>>>>>> Invoke SuspendThread()\n"); jvmtiError err = jvmti->SuspendThread(thread); if (err != JVMTI_ERROR_NONE) { printf("%s: Failed to call SuspendThread(): error=%d: %s\n", __FILE__, err, TranslateError(err)); return NSK_FALSE; } int result = NSK_TRUE; jmethodID method = NULL; jlocation loc; // We need to ensure that the thread is suspended at the right place when the top // frame belongs to the test rather than to incidental Java code (classloading, // JVMCI, etc). Below we do resume/suspend in the loop until the target method // is executed in the top frame or the loop counter exceeds the limit. for (int i = 0; i < 10; i++) { err = jvmti->GetFrameLocation(thread, 0, &method, &loc); if (err != JVMTI_ERROR_NONE) { printf("(GetFrameLocation) unexpected error: %s (%d)\n", TranslateError(err), err); result = NSK_FALSE; break; } char *name, *sig, *generic; jvmti->GetMethodName(method, &name, &sig, &generic); printf(">>> Attempt %d to suspend the thread. Top frame: \"%s%s\"\n", i, name, sig); if (method == testMethod) break; err = jvmti->ResumeThread(thread); if (err != JVMTI_ERROR_NONE) { printf("(ResumeThread) unexpected error: %s (%d)\n", TranslateError(err), err); result = NSK_FALSE; } mssleep(SLEEP_DELAY); err = jvmti->SuspendThread(thread); if (err != JVMTI_ERROR_NONE) { printf("(SuspendThread) unexpected error: %s (%d)\n", TranslateError(err), err); result = NSK_FALSE; } } if(method == testMethod) { printf("<<<<<<<< SuspendThread() is successfully done\n"); } else { char *name, *sig, *generic; jvmti->GetMethodName(testMethod, &name, &sig, &generic); printf("Failed in the suspendThread: was not able to suspend thread " "with required method \"%s%s\" on the top\n", name, sig); result = NSK_FALSE; } return result; } jint createRawMonitor(jvmtiEnv *env, const char *name, jrawMonitorID *monitor) { jvmtiError error = env->CreateRawMonitor(name, monitor); if (!NSK_JVMTI_VERIFY(error)) { return JNI_ERR; } return JNI_OK; } void exitOnError(jvmtiError error) { if (!NSK_JVMTI_VERIFY(error)) { exit(error); } } void rawMonitorEnter(jvmtiEnv *env, jrawMonitorID monitor) { jvmtiError error = env->RawMonitorEnter(monitor); exitOnError(error); } void rawMonitorExit(jvmtiEnv *env, jrawMonitorID monitor) { jvmtiError error = env->RawMonitorExit(monitor); exitOnError(error); } void rawMonitorNotify(jvmtiEnv *env, jrawMonitorID monitor) { jvmtiError error = env->RawMonitorNotify(monitor); exitOnError(error); } void rawMonitorWait(jvmtiEnv *env, jrawMonitorID monitor, jlong millis) { jvmtiError error = env->RawMonitorWait(monitor, millis); exitOnError(error); } void getPhase(jvmtiEnv *env, jvmtiPhase *phase) { jvmtiError error = env->GetPhase(phase); exitOnError(error); } /*************************************************************/ }