/* * Copyright (c) 2006, 2015, Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of Oracle nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * This source code is provided to illustrate the usage of a given feature * or technique and has been deliberately simplified. Additional steps * required for a production-quality application, such as security checks, * input validation and proper error handling, might not be present in * this sample code. */ #include "stdlib.h" #include "minst.h" #include "java_crw_demo.h" /* ------------------------------------------------------------------- */ /* Some constant maximum sizes */ #define MAX_TOKEN_LENGTH 80 #define MAX_METHOD_NAME_LENGTH 256 /* Some constant names that tie to Java class/method names. * We assume the Java class whose static methods we will be calling * looks like: * * public class Minst { * private static int engaged; * private static native void _method_entry(Object thr, int cnum, int mnum); * public static void method_entry(int cnum, int mnum) * { * ... * } * } * */ #define MINST_class Minst /* Name of class we are using */ #define MINST_entry method_entry /* Name of java entry method */ #define MINST_engaged engaged /* Name of java static field */ /* C macros to create strings from tokens */ #define _STRING(s) #s #define STRING(s) _STRING(s) /* ------------------------------------------------------------------- */ /* Global agent data structure */ typedef struct { /* JVMTI Environment */ jvmtiEnv *jvmti; jboolean vm_is_dead; jboolean vm_is_started; /* Data access Lock */ jrawMonitorID lock; /* Options */ char *include; char *exclude; /* Class Count/ID */ jint ccount; } GlobalAgentData; static GlobalAgentData *gdata; /* Enter a critical section by doing a JVMTI Raw Monitor Enter */ static void enter_critical_section(jvmtiEnv *jvmti) { jvmtiError error; error = (*jvmti)->RawMonitorEnter(jvmti, gdata->lock); check_jvmti_error(jvmti, error, "Cannot enter with raw monitor"); } /* Exit a critical section by doing a JVMTI Raw Monitor Exit */ static void exit_critical_section(jvmtiEnv *jvmti) { jvmtiError error; error = (*jvmti)->RawMonitorExit(jvmti, gdata->lock); check_jvmti_error(jvmti, error, "Cannot exit with raw monitor"); } /* Callback for JVMTI_EVENT_VM_START */ static void JNICALL cbVMStart(jvmtiEnv *jvmti, JNIEnv *env) { enter_critical_section(jvmti); { /* Indicate VM has started */ gdata->vm_is_started = JNI_TRUE; } exit_critical_section(jvmti); } /* Callback for JVMTI_EVENT_VM_INIT */ static void JNICALL cbVMInit(jvmtiEnv *jvmti, JNIEnv *env, jthread thread) { enter_critical_section(jvmti); { jclass klass; jfieldID field; /* Register Natives for class whose methods we use */ klass = (*env)->FindClass(env, STRING(MINST_class)); if ( klass == NULL ) { fatal_error("ERROR: JNI: Cannot find %s with FindClass\n", STRING(MINST_class)); } /* Engage calls. */ field = (*env)->GetStaticFieldID(env, klass, STRING(MINST_engaged), "I"); if ( field == NULL ) { fatal_error("ERROR: JNI: Cannot get field from %s\n", STRING(MINST_class)); } (*env)->SetStaticIntField(env, klass, field, 1); } exit_critical_section(jvmti); } /* Callback for JVMTI_EVENT_VM_DEATH */ static void JNICALL cbVMDeath(jvmtiEnv *jvmti, JNIEnv *env) { enter_critical_section(jvmti); { jclass klass; jfieldID field; /* The VM has died. */ stdout_message("VMDeath\n"); /* Disengage calls in MINST_class. */ klass = (*env)->FindClass(env, STRING(MINST_class)); if ( klass == NULL ) { fatal_error("ERROR: JNI: Cannot find %s with FindClass\n", STRING(MINST_class)); } field = (*env)->GetStaticFieldID(env, klass, STRING(MINST_engaged), "I"); if ( field == NULL ) { fatal_error("ERROR: JNI: Cannot get field from %s\n", STRING(MINST_class)); } (*env)->SetStaticIntField(env, klass, field, -1); /* The critical section here is important to hold back the VM death * until all other callbacks have completed. */ /* Since this critical section could be holding up other threads * in other event callbacks, we need to indicate that the VM is * dead so that the other callbacks can short circuit their work. * We don't expect any further events after VmDeath but we do need * to be careful that existing threads might be in our own agent * callback code. */ gdata->vm_is_dead = JNI_TRUE; } exit_critical_section(jvmti); } /* Callback for JVMTI_EVENT_CLASS_FILE_LOAD_HOOK */ static void JNICALL cbClassFileLoadHook(jvmtiEnv *jvmti, JNIEnv* env, jclass class_being_redefined, jobject loader, const char* name, jobject protection_domain, jint class_data_len, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) { enter_critical_section(jvmti); { /* It's possible we get here right after VmDeath event, be careful */ if ( !gdata->vm_is_dead ) { const char *classname; /* Name could be NULL */ if ( name == NULL ) { classname = java_crw_demo_classname(class_data, class_data_len, NULL); if ( classname == NULL ) { fatal_error("ERROR: No classname inside classfile\n"); } } else { classname = strdup(name); if ( classname == NULL ) { fatal_error("ERROR: Out of malloc memory\n"); } } *new_class_data_len = 0; *new_class_data = NULL; /* The tracker class itself? */ if ( interested((char*)classname, "", gdata->include, gdata->exclude) && strcmp(classname, STRING(MINST_class)) != 0 ) { jint cnum; int system_class; unsigned char *new_image; long new_length; /* Get unique number for every class file image loaded */ cnum = gdata->ccount++; /* Is it a system class? If the class load is before VmStart * then we will consider it a system class that should * be treated carefully. (See java_crw_demo) */ system_class = 0; if ( !gdata->vm_is_started ) { system_class = 1; } new_image = NULL; new_length = 0; /* Call the class file reader/write demo code */ java_crw_demo(cnum, classname, class_data, class_data_len, system_class, STRING(MINST_class), "L" STRING(MINST_class) ";", STRING(MINST_entry), "(II)V", NULL, NULL, NULL, NULL, NULL, NULL, &new_image, &new_length, NULL, NULL); /* If we got back a new class image, return it back as "the" * new class image. This must be JVMTI Allocate space. */ if ( new_length > 0 ) { unsigned char *jvmti_space; jvmti_space = (unsigned char *)allocate(jvmti, (jint)new_length); (void)memcpy((void*)jvmti_space, (void*)new_image, (int)new_length); *new_class_data_len = (jint)new_length; *new_class_data = jvmti_space; /* VM will deallocate */ } /* Always free up the space we get from java_crw_demo() */ if ( new_image != NULL ) { (void)free((void*)new_image); /* Free malloc() space with free() */ } } (void)free((void*)classname); } } exit_critical_section(jvmti); } /* Parse the options for this minst agent */ static void parse_agent_options(char *options) { char token[MAX_TOKEN_LENGTH]; char *next; /* Parse options and set flags in gdata */ if ( options==NULL ) { return; } /* Get the first token from the options string. */ next = get_token(options, ",=", token, sizeof(token)); /* While not at the end of the options string, process this option. */ while ( next != NULL ) { if ( strcmp(token,"help")==0 ) { stdout_message("The minst JVMTI demo agent\n"); stdout_message("\n"); stdout_message(" java -agent:minst[=options] ...\n"); stdout_message("\n"); stdout_message("The options are comma separated:\n"); stdout_message("\t help\t\t\t Print help information\n"); stdout_message("\t include=item\t\t Only these classes/methods\n"); stdout_message("\t exclude=item\t\t Exclude these classes/methods\n"); stdout_message("\n"); stdout_message("item\t Qualified class and/or method names\n"); stdout_message("\t\t e.g. (*.;Foobar.method;sun.*)\n"); stdout_message("\n"); exit(0); } else if ( strcmp(token,"include")==0 ) { int used; int maxlen; maxlen = MAX_METHOD_NAME_LENGTH; if ( gdata->include == NULL ) { gdata->include = (char*)calloc(maxlen+1, 1); used = 0; } else { used = (int)strlen(gdata->include); gdata->include[used++] = ','; gdata->include[used] = 0; gdata->include = (char*) realloc((void*)gdata->include, used+maxlen+1); } if ( gdata->include == NULL ) { fatal_error("ERROR: Out of malloc memory\n"); } /* Add this item to the list */ next = get_token(next, ",=", gdata->include+used, maxlen); /* Check for token scan error */ if ( next==NULL ) { fatal_error("ERROR: include option error\n"); } } else if ( strcmp(token,"exclude")==0 ) { int used; int maxlen; maxlen = MAX_METHOD_NAME_LENGTH; if ( gdata->exclude == NULL ) { gdata->exclude = (char*)calloc(maxlen+1, 1); used = 0; } else { used = (int)strlen(gdata->exclude); gdata->exclude[used++] = ','; gdata->exclude[used] = 0; gdata->exclude = (char*) realloc((void*)gdata->exclude, used+maxlen+1); } if ( gdata->exclude == NULL ) { fatal_error("ERROR: Out of malloc memory\n"); } /* Add this item to the list */ next = get_token(next, ",=", gdata->exclude+used, maxlen); /* Check for token scan error */ if ( next==NULL ) { fatal_error("ERROR: exclude option error\n"); } } else if ( token[0]!=0 ) { /* We got a non-empty token and we don't know what it is. */ fatal_error("ERROR: Unknown option: %s\n", token); } /* Get the next token (returns NULL if there are no more) */ next = get_token(next, ",=", token, sizeof(token)); } } /* Agent_OnLoad: This is called immediately after the shared library is * loaded. This is the first code executed. */ JNIEXPORT jint JNICALL DEF_Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { static GlobalAgentData data; jvmtiEnv *jvmti; jvmtiError error; jint res; jvmtiCapabilities capabilities; jvmtiEventCallbacks callbacks; /* Setup initial global agent data area * Use of static/extern data should be handled carefully here. * We need to make sure that we are able to cleanup after ourselves * so anything allocated in this library needs to be freed in * the Agent_OnUnload() function. */ (void)memset((void*)&data, 0, sizeof(data)); gdata = &data; /* First thing we need to do is get the jvmtiEnv* or JVMTI environment */ res = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_1); if (res != JNI_OK) { /* This means that the VM was unable to obtain this version of the * JVMTI interface, this is a fatal error. */ fatal_error("ERROR: Unable to access JVMTI Version 1 (0x%x)," " is your JDK a 5.0 or newer version?" " JNIEnv's GetEnv() returned %d\n", JVMTI_VERSION_1, res); } /* Here we save the jvmtiEnv* for Agent_OnUnload(). */ gdata->jvmti = jvmti; /* Parse any options supplied on java command line */ parse_agent_options(options); /* Immediately after getting the jvmtiEnv* we need to ask for the * capabilities this agent will need. In this case we need to make * sure that we can get all class load hooks. */ (void)memset(&capabilities,0, sizeof(capabilities)); capabilities.can_generate_all_class_hook_events = 1; error = (*jvmti)->AddCapabilities(jvmti, &capabilities); check_jvmti_error(jvmti, error, "Unable to get necessary JVMTI capabilities."); /* Next we need to provide the pointers to the callback functions to * to this jvmtiEnv* */ (void)memset(&callbacks,0, sizeof(callbacks)); /* JVMTI_EVENT_VM_START */ callbacks.VMStart = &cbVMStart; /* JVMTI_EVENT_VM_INIT */ callbacks.VMInit = &cbVMInit; /* JVMTI_EVENT_VM_DEATH */ callbacks.VMDeath = &cbVMDeath; /* JVMTI_EVENT_CLASS_FILE_LOAD_HOOK */ callbacks.ClassFileLoadHook = &cbClassFileLoadHook; error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks)); check_jvmti_error(jvmti, error, "Cannot set jvmti callbacks"); /* At first the only initial events we are interested in are VM * initialization, VM death, and Class File Loads. * Once the VM is initialized we will request more events. */ error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_START, (jthread)NULL); check_jvmti_error(jvmti, error, "Cannot set event notification"); error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, (jthread)NULL); check_jvmti_error(jvmti, error, "Cannot set event notification"); error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, (jthread)NULL); check_jvmti_error(jvmti, error, "Cannot set event notification"); error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, (jthread)NULL); check_jvmti_error(jvmti, error, "Cannot set event notification"); /* Here we create a raw monitor for our use in this agent to * protect critical sections of code. */ error = (*jvmti)->CreateRawMonitor(jvmti, "agent data", &(gdata->lock)); check_jvmti_error(jvmti, error, "Cannot create raw monitor"); /* Add demo jar file to boot classpath */ add_demo_jar_to_bootclasspath(jvmti, "minst"); /* We return JNI_OK to signify success */ return JNI_OK; } /* Agent_OnUnload: This is called immediately before the shared library is * unloaded. This is the last code executed. */ JNIEXPORT void JNICALL DEF_Agent_OnUnload(JavaVM *vm) { /* Make sure all malloc/calloc/strdup space is freed */ if ( gdata->include != NULL ) { (void)free((void*)gdata->include); gdata->include = NULL; } if ( gdata->exclude != NULL ) { (void)free((void*)gdata->exclude); gdata->exclude = NULL; } }