/* * @(#)mtrace.c 1.25 05/01/04 * * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * -Redistribution of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * -Redistribution 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 Sun Microsystems, Inc. or the names of contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed, licensed or intended * for use in the design, construction, operation or maintenance of any * nuclear facility. */ #include "stdlib.h" #include "mtrace.h" #include "java_crw_demo.h" /* ------------------------------------------------------------------- */ /* Some constant maximum sizes */ #define MAX_TOKEN_LENGTH 16 #define MAX_THREAD_NAME_LENGTH 512 #define MAX_METHOD_NAME_LENGTH 1024 /* 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 Mtrace { * 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) * { * if ( engaged != 0 ) { * _method_entry(Thread.currentThread(), cnum, mnum); * } * } * private static native void _method_exit(Object thr, int cnum, int mnum); * public static void method_exit(int cnum, int mnum) * { * if ( engaged != 0 ) { * _method_exit(Thread.currentThread(), cnum, mnum); * } * } * } * * The engaged field allows us to inject all classes (even system classes) * and delay the actual calls to the native code until the VM has reached * a safe time to call native methods (Past the JVMTI VM_START event). * */ #define MTRACE_class mtrace/Mtrace /* Name of class we are using */ #define MTRACE_entry method_entry /* Name of java entry method */ #define MTRACE_exit method_exit /* Name of java exit method */ #define MTRACE_native_entry _method_entry /* Name of java entry native */ #define MTRACE_native_exit _method_exit /* Name of java exit native */ #define MTRACE_engaged engaged /* Name of java static field */ /* C macros to create strings from tokens */ #define _STRING(s) #s #define STRING(s) _STRING(s) /* ------------------------------------------------------------------- */ /* Data structure to hold method and class information in agent */ typedef struct CallChainInfo { const char *desc; /* String description */ jvmtiFrameInfo *frames; /* calling frames */ jint framesDepth; /* number or frames */ int calls; /* Count of calls from given place */ } CallChainInfo; typedef struct MethodInfo { const char *name; /* Method name */ const char *signature; /* Method signature */ int cccount; /* Call chains count */ CallChainInfo *callChains; /* Desription of call chains */ int calls; /* Method call count */ int returns; /* Method return count */ } MethodInfo; typedef struct ClassInfo { const char *name; /* Class name */ int mcount; /* Method count */ MethodInfo *methods; /* Method information */ int calls; /* Method call count for this class */ } ClassInfo; /* 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; int max_count; /* ClassInfo Table */ ClassInfo *classes; jint ccount; int traces_depth; /* 0 means do not collect traces, positive value is number of elements */ } GlobalAgentData; static GlobalAgentData *gdata; /* Every JVMTI interface returns an error code, which should be checked * to avoid any cascading errors down the line. * The interface GetErrorName() returns the actual enumeration constant * name, making the error messages much easier to understand. */ static void check_jvmti_error(jvmtiEnv *jvmti, jvmtiError errnum, const char *str) { if ( errnum != JVMTI_ERROR_NONE ) { char *errnum_str; errnum_str = NULL; (void)(*jvmti)->GetErrorName(jvmti, errnum, &errnum_str); fatal_error("ERROR: JVMTI: %d(%s): %s\n", errnum, (errnum_str==NULL?"Unknown":errnum_str), (str==NULL?"":str)); } } /* All memory allocated by JVMTI must be freed by the JVMTI Deallocate * interface. */ static void deallocate(jvmtiEnv *jvmti, void *ptr) { jvmtiError error; error = (*jvmti)->Deallocate(jvmti, ptr); check_jvmti_error(jvmti, error, "Cannot deallocate memory"); } /* Allocation of JVMTI managed memory */ static void * allocate(jvmtiEnv *jvmti, jint len) { jvmtiError error; void *ptr; error = (*jvmti)->Allocate(jvmti, len, (unsigned char **)&ptr); check_jvmti_error(jvmti, error, "Cannot allocate memory"); return ptr; } /* 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"); } /* Get a name for a jthread */ static void get_thread_name(jvmtiEnv *jvmti, jthread thread, char *tname, int maxlen) { jvmtiThreadInfo info; jvmtiError error; /* Make sure the stack variables are garbage free */ (void)memset(&info,0, sizeof(info)); /* Assume the name is unknown for now */ (void)strcpy(tname, "Unknown"); /* Get the thread information, which includes the name */ error = (*jvmti)->GetThreadInfo(jvmti, thread, &info); check_jvmti_error(jvmti, error, "Cannot get thread info"); /* The thread might not have a name, be careful here. */ if ( info.name != NULL ) { int len; /* Copy the thread name into tname if it will fit */ len = (int)strlen(info.name); if ( len < maxlen ) { (void)strcpy(tname, info.name); } /* Every string allocated by JVMTI needs to be freed */ deallocate(jvmti, (void*)info.name); } } /* Qsort class compare routine */ static int class_compar(const void *e1, const void *e2) { ClassInfo *c1 = (ClassInfo*)e1; ClassInfo *c2 = (ClassInfo*)e2; if ( c1->calls > c2->calls ) return 1; if ( c1->calls < c2->calls ) return -1; return 0; } /* Qsort method compare routine */ static int method_compar(const void *e1, const void *e2) { MethodInfo *m1 = (MethodInfo*)e1; MethodInfo *m2 = (MethodInfo*)e2; if ( m1->calls > m2->calls ) return 1; if ( m1->calls < m2->calls ) return -1; return 0; } /* Qsort method compare routine */ static int call_chain_compar(const void *e1, const void *e2) { CallChainInfo *cc1 = (CallChainInfo*)e1; CallChainInfo *cc2 = (CallChainInfo*)e2; if ( cc1->calls > cc2->calls ) return 1; if ( cc1->calls < cc2->calls ) return -1; return 0; } /* equals for two set of frames */ static int call_chains_equals(jvmtiFrameInfo *f1, jint c1, jvmtiFrameInfo *f2, jint c2) { if (f1 != NULL && f2 == NULL) return 0; // false if (f2 != NULL && f1 == NULL) return 0; // false if (c1 != c2) return 0; int mnum; for (mnum = 0; mnum < c1; mnum++) { if (f1[mnum].method != f2[mnum].method) return 0; } return 1; } /* Callback from java_crw_demo() that gives us mnum mappings */ static void mnum_callbacks(unsigned cnum, const char **names, const char**sigs, int mcount) { ClassInfo *cp; int mnum; if ( cnum >= (unsigned)gdata->ccount ) { fatal_error("ERROR: Class number out of range\n"); } if ( mcount == 0 ) { return; } cp = gdata->classes + (int)cnum; cp->calls = 0; cp->mcount = mcount; cp->methods = (MethodInfo*)calloc(mcount, sizeof(MethodInfo)); if ( cp->methods == NULL ) { fatal_error("ERROR: Out of malloc memory\n"); } for ( mnum = 0 ; mnum < mcount ; mnum++ ) { MethodInfo *mp; mp = cp->methods + mnum; mp->name = (const char *)strdup(names[mnum]); if ( mp->name == NULL ) { fatal_error("ERROR: Out of malloc memory\n"); } mp->signature = (const char *)strdup(sigs[mnum]); if ( mp->signature == NULL ) { fatal_error("ERROR: Out of malloc memory\n"); } mp->cccount = 0; mp->callChains = NULL; } } /* Java Native Method for entry */ static void MTRACE_native_entry(JNIEnv *env, jclass klass, jobject thread, jint cnum, jint mnum) { enter_critical_section(gdata->jvmti); { /* It's possible we get here right after VmDeath event, be careful */ if ( !gdata->vm_is_dead ) { ClassInfo *cp; MethodInfo *mp; if ( cnum >= gdata->ccount ) { fatal_error("ERROR: Class number out of range\n"); } cp = gdata->classes + cnum; if ( mnum >= cp->mcount ) { fatal_error("ERROR: Method number out of range\n"); } mp = cp->methods + mnum; if ( interested((char*)cp->name, (char*)mp->name, gdata->include, gdata->exclude) ) { mp->calls++; cp->calls++; if ( gdata->traces_depth ) { // find the stack trace jvmtiFrameInfo *frames; jint count; jvmtiError err; frames = (jvmtiFrameInfo*)calloc(gdata->traces_depth, sizeof(jvmtiFrameInfo)); err = (*(gdata->jvmti))->GetStackTrace(gdata->jvmti, thread, 2, gdata->traces_depth, frames, &count); // stdout_message("check stack for %s, err %d, count %d\n", mp->name, err, count); if (err == JVMTI_ERROR_NONE && count >= 1) { int found = 0; int i; if (mp->callChains != NULL) { for (i = 0; icccount; i++) { if (call_chains_equals(frames, count, (mp->callChains + i)->frames, (mp->callChains + i)->framesDepth)) { (mp->callChains + i)->calls++; found = 1; // stdout_message("incrementing\n", mp->name); break; } } } if (!found) { // stdout_message("creating new record %s\n", mp->name); // create new call chain record if ( mp->callChains == NULL ) { mp->callChains = (CallChainInfo*)malloc( sizeof(CallChainInfo)); } else { mp->callChains = (CallChainInfo*) realloc((void*)mp->callChains, (mp->cccount+1)*sizeof(CallChainInfo)); } if ( mp->callChains == NULL ) { fatal_error("ERROR: Out of malloc memory\n"); } char *desc, **methods, **classes; jclass jc; int len = 0; int methnum; methods = (char **)malloc(count*sizeof(char*));; classes = (char **)malloc(count*sizeof(char*));; for (methnum = 0; methnum < count; methnum++) { err = (*(gdata->jvmti))->GetMethodName(gdata->jvmti, frames[methnum].method, &methods[methnum], NULL, NULL); if (err != JVMTI_ERROR_NONE) { fatal_error("Cannot get method name"); } // safe for 16 classes (returned refrences) err = (*(gdata->jvmti))->GetMethodDeclaringClass(gdata->jvmti, frames[methnum].method, &jc); if (err != JVMTI_ERROR_NONE) { fatal_error("Cannot get declaring method for %s", methods[methnum]); } err = (*(gdata->jvmti))->GetClassSignature(gdata->jvmti, jc, &classes[methnum], NULL); if (err != JVMTI_ERROR_NONE) { fatal_error("Cannot get class name"); } len += strlen( methods[methnum] ); len += strlen( classes[methnum] ); } desc = malloc(len+2*count+1); desc[0] = '\0'; for (methnum = 0; methnum < count; methnum++) { strcat( desc, classes [methnum]); strcat( desc, "." ); strcat( desc, methods [methnum]); strcat( desc, "\n" ); err = (*(gdata->jvmti))->Deallocate(gdata->jvmti, methods[methnum]); if (err != JVMTI_ERROR_NONE) { fatal_error("Cannot deallocate memory"); } } // stdout_message("got stack trace:\n%s\n", desc); (mp->callChains + (mp->cccount)) ->desc = desc; (mp->callChains + (mp->cccount)) ->frames = frames; (mp->callChains + (mp->cccount)) ->framesDepth = count; (mp->callChains + (mp->cccount)) ->calls = 1; mp->cccount++; } } } } } } exit_critical_section(gdata->jvmti); } /* Java Native Method for exit */ static void MTRACE_native_exit(JNIEnv *env, jclass klass, jobject thread, jint cnum, jint mnum) { enter_critical_section(gdata->jvmti); { /* It's possible we get here right after VmDeath event, be careful */ if ( !gdata->vm_is_dead ) { ClassInfo *cp; MethodInfo *mp; if ( cnum >= gdata->ccount ) { fatal_error("ERROR: Class number out of range\n"); } cp = gdata->classes + cnum; if ( mnum >= cp->mcount ) { fatal_error("ERROR: Method number out of range\n"); } mp = cp->methods + mnum; if ( interested((char*)cp->name, (char*)mp->name, gdata->include, gdata->exclude) ) { mp->returns++; } } } exit_critical_section(gdata->jvmti); } /* Callback for JVMTI_EVENT_VM_START */ static void JNICALL cbVMStart(jvmtiEnv *jvmti, JNIEnv *env) { enter_critical_section(jvmti); { jclass klass; jfieldID field; int rc; /* Java Native Methods for class */ static JNINativeMethod registry[2] = { {STRING(MTRACE_native_entry), "(Ljava/lang/Object;II)V", (void*)&MTRACE_native_entry}, {STRING(MTRACE_native_exit), "(Ljava/lang/Object;II)V", (void*)&MTRACE_native_exit} }; /* The VM has started. */ stdout_message("VMStart\n"); /* Register Natives for class whose methods we use */ klass = (*env)->FindClass(env, STRING(MTRACE_class)); if ( klass == NULL ) { fatal_error("ERROR: JNI: Cannot find %s with FindClass\n", STRING(MTRACE_class)); } rc = (*env)->RegisterNatives(env, klass, registry, 2); if ( rc != 0 ) { fatal_error("ERROR: JNI: Cannot register native methods for %s\n", STRING(MTRACE_class)); } /* Engage calls. */ field = (*env)->GetStaticFieldID(env, klass, STRING(MTRACE_engaged), "I"); if ( field == NULL ) { fatal_error("ERROR: JNI: Cannot get field from %s\n", STRING(MTRACE_class)); } (*env)->SetStaticIntField(env, klass, field, 1); /* 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); { char tname[MAX_THREAD_NAME_LENGTH]; static jvmtiEvent events[] = { JVMTI_EVENT_THREAD_START, JVMTI_EVENT_THREAD_END }; int i; /* The VM has started. */ get_thread_name(jvmti, thread, tname, sizeof(tname)); stdout_message("VMInit %s\n", tname); /* The VM is now initialized, at this time we make our requests * for additional events. */ for( i=0; i < (int)(sizeof(events)/sizeof(jvmtiEvent)); i++) { jvmtiError error; /* Setup event notification modes */ error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, events[i], (jthread)NULL); check_jvmti_error(jvmti, error, "Cannot set event notification"); } } 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 MTRACE_class. */ klass = (*env)->FindClass(env, STRING(MTRACE_class)); if ( klass == NULL ) { fatal_error("ERROR: JNI: Cannot find %s with FindClass\n", STRING(MTRACE_class)); } field = (*env)->GetStaticFieldID(env, klass, STRING(MTRACE_engaged), "I"); if ( field == NULL ) { fatal_error("ERROR: JNI: Cannot get field from %s\n", STRING(MTRACE_class)); } (*env)->SetStaticIntField(env, klass, field, 0); /* 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; /* Dump out stats */ stdout_message("Begin Class Stats\n"); if ( gdata->ccount > 0 ) { int cnum; /* Sort table (in place) by number of method calls into class. */ /* Note: Do not use this table after this qsort! */ qsort(gdata->classes, gdata->ccount, sizeof(ClassInfo), &class_compar); /* Dump out gdata->max_count most called classes */ for ( cnum=gdata->ccount-1 ; cnum >= 0 && cnum >= gdata->ccount - gdata->max_count; cnum-- ) { ClassInfo *cp; int mnum, ccnum; cp = gdata->classes + cnum; stdout_message("Class %s %d calls\n", cp->name, cp->calls); if ( cp->calls==0 ) continue; /* Sort method table (in place) by number of method calls. */ /* Note: Do not use this table after this qsort! */ qsort(cp->methods, cp->mcount, sizeof(MethodInfo), &method_compar); for ( mnum=cp->mcount-1 ; mnum >= 0 ; mnum-- ) { MethodInfo *mp; mp = cp->methods + mnum; if ( mp->calls==0 ) continue; stdout_message("\tMethod %s %s %d calls %d returns\n", mp->name, mp->signature, mp->calls, mp->returns); qsort(mp->callChains, mp->cccount, sizeof(CallChainInfo), &call_chain_compar); for ( ccnum = 0; ccnum < mp->cccount; ccnum++ ) { CallChainInfo *cci; cci = mp->callChains + ccnum; stdout_message("\t\tCalled %d times from:\n%s", cci->calls, cci->desc); } } } } stdout_message("End Class Stats\n"); (void)fflush(stdout); } exit_critical_section(jvmti); } /* Callback for JVMTI_EVENT_THREAD_START */ static void JNICALL cbThreadStart(jvmtiEnv *jvmti, JNIEnv *env, jthread thread) { enter_critical_section(jvmti); { /* It's possible we get here right after VmDeath event, be careful */ if ( !gdata->vm_is_dead ) { char tname[MAX_THREAD_NAME_LENGTH]; get_thread_name(jvmti, thread, tname, sizeof(tname)); stdout_message("ThreadStart %s\n", tname); } } exit_critical_section(jvmti); } /* Callback for JVMTI_EVENT_THREAD_END */ static void JNICALL cbThreadEnd(jvmtiEnv *jvmti, JNIEnv *env, jthread thread) { enter_critical_section(jvmti); { /* It's possible we get here right after VmDeath event, be careful */ if ( !gdata->vm_is_dead ) { char tname[MAX_THREAD_NAME_LENGTH]; get_thread_name(jvmti, thread, tname, sizeof(tname)); stdout_message("ThreadEnd %s\n", tname); } } 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(MTRACE_class)) != 0 ) { jint cnum; int system_class; unsigned char *new_image; long new_length; ClassInfo *cp; /* Get unique number for every class file image loaded */ cnum = gdata->ccount++; /* Save away class information */ if ( gdata->classes == NULL ) { gdata->classes = (ClassInfo*)malloc( gdata->ccount*sizeof(ClassInfo)); } else { gdata->classes = (ClassInfo*) realloc((void*)gdata->classes, gdata->ccount*sizeof(ClassInfo)); } if ( gdata->classes == NULL ) { fatal_error("ERROR: Out of malloc memory\n"); } cp = gdata->classes + cnum; cp->name = (const char *)strdup(classname); if ( cp->name == NULL ) { fatal_error("ERROR: Out of malloc memory\n"); } cp->calls = 0; cp->mcount = 0; cp->methods = NULL; /* 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(MTRACE_class), "L" STRING(MTRACE_class) ";", STRING(MTRACE_entry), "(II)V", STRING(MTRACE_exit), "(II)V", NULL, NULL, NULL, NULL, &new_image, &new_length, NULL, &mnum_callbacks); /* 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 mtrace agent */ static void parse_agent_options(char *options) { char token[MAX_TOKEN_LENGTH]; char *next; gdata->max_count = 10; /* Default max=n */ /* 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 mtrace JVMTI demo agent\n"); stdout_message("\n"); stdout_message(" java -agent:mtrace[=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 max=n\t\t Only list top n classes\n"); stdout_message("\t stack=n\t\t Collect statistics about callers (n levels of method calls)\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,"max")==0 ) { char number[MAX_TOKEN_LENGTH]; /* Get the numeric option */ next = get_token(next, ",=", number, (int)sizeof(number)); /* Check for token scan error */ if ( next==NULL ) { fatal_error("ERROR: max=n option error\n"); } /* Save numeric value */ gdata->max_count = atoi(number); } else if ( strcmp(token,"stack")==0 ) { char number[MAX_TOKEN_LENGTH]; /* Get the numeric option */ next = get_token(next, ",=", number, (int)sizeof(number)); /* Check for token scan error */ if ( next==NULL ) { fatal_error("ERROR: stack=n option error\n"); } /* Save numeric value */ gdata->traces_depth = atoi(number); } 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 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 J2SE a 1.5 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; /* JVMTI_EVENT_THREAD_START */ callbacks.ThreadStart = &cbThreadStart; /* JVMTI_EVENT_THREAD_END */ callbacks.ThreadEnd = &cbThreadEnd; 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"); /* 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 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; } if ( gdata->classes != NULL ) { int cnum; for ( cnum = 0 ; cnum < gdata->ccount ; cnum++ ) { ClassInfo *cp; cp = gdata->classes + cnum; (void)free((void*)cp->name); if ( cp->mcount > 0 ) { int mnum; for ( mnum = 0 ; mnum < cp->mcount ; mnum++ ) { MethodInfo *mp; mp = cp->methods + mnum; (void)free((void*)mp->name); (void)free((void*)mp->signature); } (void)free((void*)cp->methods); } } (void)free((void*)gdata->classes); gdata->classes = NULL; } }