/* * 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 #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_OPTION_VAL_SEP '=' #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; } } int isOptSep(char c) { return isspace(c) || c == '~'; } /** * * The current option will not perform more than one * single option which given, this is due to places explained * in this question. * **/ /* * This whole play can be reduced with simple StringTokenizer (strtok). * */ int nsk_jvmti_parseOptions(const char options[]) { size_t len; const char* opt; int success = NSK_TRUE; context.options.string = NULL; context.options.count = 0; context.waittime = 2; if (options == NULL) return NSK_TRUE; len = strlen(options); context.options.string = (char*)malloc(len + 2); if (context.options.string == NULL) { nsk_complain("nsk_jvmti_parseOptions(): out of memory\n"); return NSK_FALSE; } strncpy(context.options.string, options, len); context.options.string[len] = '\0'; context.options.string[len+1] = '\0'; for (opt = context.options.string; ; ) { const char* opt_end; const char* val_sep; int opt_len=0; int val_len=0; int exit=1; while (*opt != '\0' && isOptSep(*opt)) opt++; if (*opt == '\0') break; val_sep = NULL; /* This should break when the first option it encounters other wise */ for (opt_end = opt, opt_len=0; !(*opt_end == '\0' || isOptSep(*opt_end)); opt_end++,opt_len++) { if (*opt_end == NSK_JVMTI_OPTION_VAL_SEP) { val_sep = opt_end; exit=0; break; } } if (exit == 1) break; /* now scan for the search for the option value end. */ exit =1; opt_end++; val_sep++; /** * I was expecting this jvmti_parseOptions(), * should be for multiple options as well. * If this break is not there then It will expects * to have. so a space should be sufficient as well. */ for(val_len=0; !(*opt_end == '\0' || isOptSep(*opt_end)); opt_end++,val_len++) { //if (*opt_end == NSK_JVMTI_OPTION_START) { // break; //} } if (!add_option(opt, opt_len, val_sep, val_len)) { success = NSK_FALSE; break; } opt_end++; opt = opt_end; } if (!success) { nsk_jvmti_free(); } 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; } 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); } /*************************************************************/ }