| /* Copyright (C) 2021 Free Software Foundation, Inc. |
| Contributed by Oracle. |
| |
| This file is part of GNU Binutils. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| This program 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 for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, 51 Franklin Street - Fifth Floor, Boston, |
| MA 02110-1301, USA. */ |
| |
| #include "config.h" |
| |
| #if defined(GPROFNG_JAVA_PROFILING) |
| #include <alloca.h> |
| #include <dlfcn.h> /* dlsym() */ |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <sys/param.h> /* MAXPATHLEN */ |
| |
| #include <jni.h> |
| #include <jvmti.h> |
| |
| #include "gp-defs.h" |
| #include "collector.h" |
| #include "gp-experiment.h" |
| #include "tsd.h" |
| |
| /* TprintfT(<level>,...) definitions. Adjust per module as needed */ |
| #define DBG_LT0 0 // for high-level configuration, unexpected errors/warnings |
| #define DBG_LT1 1 // for configuration details, warnings |
| #define DBG_LT2 2 |
| #define DBG_LT3 3 |
| |
| /* ARCH_STRLEN is defined in dbe, copied here */ |
| #define ARCH_STRLEN(s) ((CALL_UTIL(strlen)(s) + 4 ) & ~0x3) |
| |
| /* call frame */ |
| typedef struct |
| { |
| jint lineno; /* line number in the source file */ |
| jmethodID method_id; /* method executed in this frame */ |
| } JVMPI_CallFrame; |
| |
| /* call trace */ |
| typedef struct |
| { |
| JNIEnv *env_id; /* Env where trace was recorded */ |
| jint num_frames; /* number of frames in this trace */ |
| JVMPI_CallFrame *frames; /* frames */ |
| } JVMPI_CallTrace; |
| |
| extern void __collector_jprofile_enable_synctrace (void); |
| int __collector_jprofile_start_attach (void); |
| static int init_interface (CollectorInterface*); |
| static int open_experiment (const char *); |
| static int close_experiment (void); |
| static int detach_experiment (void); |
| static void jprof_find_asyncgetcalltrace (void); |
| static char *apistr = NULL; |
| |
| static ModuleInterface module_interface = { |
| "*"SP_JCLASSES_FILE, /* description, exempt from limit */ |
| init_interface, /* initInterface */ |
| open_experiment, /* openExperiment */ |
| NULL, /* startDataCollection */ |
| NULL, /* stopDataCollection */ |
| close_experiment, /* closeExperiment */ |
| detach_experiment /* detachExperiment (fork child) */ |
| }; |
| |
| static CollectorInterface *collector_interface = NULL; |
| static CollectorModule jprof_hndl = COLLECTOR_MODULE_ERR; |
| static int __collector_java_attach = 0; |
| static JavaVM *jvm; |
| static jmethodID getResource = NULL; |
| static jmethodID toExternalForm = NULL; |
| |
| /* Java profiling thread specific data */ |
| typedef struct TSD_Entry |
| { |
| JNIEnv *env; |
| hrtime_t tstamp; |
| } TSD_Entry; |
| |
| static unsigned tsd_key = COLLECTOR_TSD_INVALID_KEY; |
| static collector_mutex_t jclasses_lock = COLLECTOR_MUTEX_INITIALIZER; |
| static int java_gc_on = 0; |
| static int java_mem_mode = 0; |
| static int java_sync_mode = 0; |
| static int is_hotspot_vm = 0; |
| static void get_jvm_settings (); |
| static void rwrite (int fd, const void *buf, size_t nbyte); |
| static void addToDynamicArchive (const char* name, const unsigned char* class_data, int class_data_len); |
| static void (*AsyncGetCallTrace)(JVMPI_CallTrace*, jint, ucontext_t*) = NULL; |
| static void (*collector_heap_record)(int, int, void*) = NULL; |
| static void (*collector_jsync_begin)() = NULL; |
| static void (*collector_jsync_end)(hrtime_t, void *) = NULL; |
| |
| #define gethrtime collector_interface->getHiResTime |
| |
| /* |
| * JVMTI declarations |
| */ |
| |
| static jvmtiEnv *jvmti; |
| static void jvmti_VMInit (jvmtiEnv*, JNIEnv*, jthread); |
| static void jvmti_VMDeath (jvmtiEnv*, JNIEnv*); |
| static void jvmti_ThreadStart (jvmtiEnv*, JNIEnv*, jthread); |
| static void jvmti_ThreadEnd (jvmtiEnv*, JNIEnv*, jthread); |
| static void jvmti_CompiledMethodLoad (jvmtiEnv*, jmethodID, jint, const void*, |
| jint, const jvmtiAddrLocationMap*, const void*); |
| static void jvmti_CompiledMethodUnload (jvmtiEnv*, jmethodID, const void*); |
| static void jvmti_DynamicCodeGenerated (jvmtiEnv*, const char*, const void*, jint); |
| static void jvmti_ClassPrepare (jvmtiEnv*, JNIEnv*, jthread, jclass); |
| static void jvmti_ClassLoad (jvmtiEnv*, JNIEnv*, jthread, jclass); |
| //static void jvmti_ClassUnload( jvmtiEnv*, JNIEnv*, jthread, jclass ); |
| static void jvmti_MonitorEnter (jvmtiEnv *, JNIEnv*, jthread, jobject); |
| static void jvmti_MonitorEntered (jvmtiEnv *, JNIEnv*, jthread, jobject); |
| #if 0 |
| static void jvmti_MonitorWait (jvmtiEnv *, JNIEnv*, jthread, jobject, jlong); |
| static void jvmti_MonitorWaited (jvmtiEnv *, JNIEnv*, jthread, jobject, jboolean); |
| #endif |
| static void jvmti_ClassFileLoadHook (jvmtiEnv *jvmti_env, JNIEnv* jni_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); |
| static void jvmti_GarbageCollectionStart (jvmtiEnv *); |
| static void |
| jvmti_GarbageCollectionFinish (jvmtiEnv *); |
| jvmtiEventCallbacks callbacks = { |
| jvmti_VMInit, // 50 jvmtiEventVMInit; |
| jvmti_VMDeath, // 51 jvmtiEventVMDeath; |
| jvmti_ThreadStart, // 52 jvmtiEventThreadStart; |
| jvmti_ThreadEnd, // 53 jvmtiEventThreadEnd; |
| jvmti_ClassFileLoadHook, // 54 jvmtiEventClassFileLoadHook; |
| jvmti_ClassLoad, // 55 jvmtiEventClassLoad; |
| jvmti_ClassPrepare, // 56 jvmtiEventClassPrepare; |
| NULL, // 57 reserved57; |
| NULL, // 58 jvmtiEventException; |
| NULL, // 59 jvmtiEventExceptionCatch; |
| NULL, // 60 jvmtiEventSingleStep; |
| NULL, // 61 jvmtiEventFramePop; |
| NULL, // 62 jvmtiEventBreakpoint; |
| NULL, // 63 jvmtiEventFieldAccess; |
| NULL, // 64 jvmtiEventFieldModification; |
| NULL, // 65 jvmtiEventMethodEntry; |
| NULL, // 66 jvmtiEventMethodExit; |
| NULL, // 67 jvmtiEventNativeMethodBind; |
| jvmti_CompiledMethodLoad, // 68 jvmtiEventCompiledMethodLoad; |
| jvmti_CompiledMethodUnload, // 69 jvmtiEventCompiledMethodUnload; |
| jvmti_DynamicCodeGenerated, // 70 jvmtiEventDynamicCodeGenerated; |
| NULL, // 71 jvmtiEventDataDumpRequest; |
| NULL, // 72 jvmtiEventDataResetRequest; |
| NULL, /*jvmti_MonitorWait,*/ // 73 jvmtiEventMonitorWait; |
| NULL, /*jvmti_MonitorWaited,*/ // 74 jvmtiEventMonitorWaited; |
| jvmti_MonitorEnter, // 75 jvmtiEventMonitorContendedEnter; |
| jvmti_MonitorEntered, // 76 jvmtiEventMonitorContendedEntered; |
| NULL, // 77 jvmtiEventMonitorContendedExit; |
| NULL, // 78 jvmtiEventReserved; |
| NULL, // 79 jvmtiEventReserved; |
| NULL, // 80 jvmtiEventReserved; |
| jvmti_GarbageCollectionStart, // 81 jvmtiEventGarbageCollectionStart; |
| jvmti_GarbageCollectionFinish, // 82 jvmtiEventGarbageCollectionFinish; |
| NULL, // 83 jvmtiEventObjectFree; |
| NULL // 84 jvmtiEventVMObjectAlloc; |
| }; |
| |
| typedef jint (JNICALL JNI_GetCreatedJavaVMs_t)(JavaVM **, jsize, jsize *); |
| |
| int |
| init_interface (CollectorInterface *_collector_interface) |
| { |
| collector_interface = _collector_interface; |
| return COL_ERROR_NONE; |
| } |
| |
| static int |
| open_experiment (const char *exp) |
| { |
| if (collector_interface == NULL) |
| return COL_ERROR_JAVAINIT; |
| TprintfT (0, "jprofile: open_experiment %s\n", exp); |
| const char *params = collector_interface->getParams (); |
| const char *args = params; |
| while (args) |
| { |
| if (__collector_strStartWith (args, "j:") == 0) |
| { |
| args += 2; |
| break; |
| } |
| args = CALL_UTIL (strchr)(args, ';'); |
| if (args) |
| args++; |
| } |
| if (args == NULL) /* Java profiling not specified */ |
| return COL_ERROR_JAVAINIT; |
| tsd_key = collector_interface->createKey (sizeof ( TSD_Entry), NULL, NULL); |
| if (tsd_key == (unsigned) - 1) |
| { |
| TprintfT (0, "jprofile: TSD key create failed.\n"); |
| collector_interface->writeLog ("<event kind=\"%s\" id=\"%d\">TSD key not created</event>\n", |
| SP_JCMD_CERROR, COL_ERROR_JAVAINIT); |
| return COL_ERROR_JAVAINIT; |
| } |
| else |
| Tprintf (DBG_LT2, "jprofile: TSD key create succeeded %d.\n", tsd_key); |
| |
| args = params; |
| while (args) |
| { |
| if (__collector_strStartWith (args, "H:") == 0) |
| { |
| java_mem_mode = 1; |
| collector_heap_record = (void(*)(int, int, void*))dlsym (RTLD_DEFAULT, "__collector_heap_record"); |
| } |
| #if 0 |
| else if (__collector_strStartWith (args, "s:") == 0) |
| { |
| java_sync_mode = 1; |
| collector_jsync_begin = (void(*)(hrtime_t, void *))dlsym (RTLD_DEFAULT, "__collector_jsync_begin"); |
| collector_jsync_end = (void(*)(hrtime_t, void *))dlsym (RTLD_DEFAULT, "__collector_jsync_end"); |
| } |
| #endif |
| args = CALL_UTIL (strchr)(args, ';'); |
| if (args) |
| args++; |
| } |
| |
| /* synchronization tracing is enabled by the synctrace module, later in initialization */ |
| __collector_java_mode = 1; |
| java_gc_on = 1; |
| return COL_ERROR_NONE; |
| } |
| |
| /* routine called from the syntrace module to enable Java-API synctrace */ |
| void |
| __collector_jprofile_enable_synctrace () |
| { |
| if (__collector_java_mode == 0) |
| { |
| TprintfT (DBG_LT1, "jprofile: not turning on Java synctrace; Java mode not enabled\n"); |
| return; |
| } |
| java_sync_mode = 1; |
| collector_jsync_begin = (void(*)(hrtime_t, void *))dlsym (RTLD_DEFAULT, "__collector_jsync_begin"); |
| collector_jsync_end = (void(*)(hrtime_t, void *))dlsym (RTLD_DEFAULT, "__collector_jsync_end"); |
| TprintfT (DBG_LT1, "jprofile: turning on Java synctrace, and requesting events\n"); |
| } |
| |
| int |
| __collector_jprofile_start_attach (void) |
| { |
| if (!__collector_java_mode || __collector_java_asyncgetcalltrace_loaded) |
| return 0; |
| void *g_sHandle = RTLD_DEFAULT; |
| /* Now get the function addresses */ |
| JNI_GetCreatedJavaVMs_t *pfnGetCreatedJavaVMs; |
| pfnGetCreatedJavaVMs = (JNI_GetCreatedJavaVMs_t *) dlsym (g_sHandle, "JNI_GetCreatedJavaVMs"); |
| if (pfnGetCreatedJavaVMs != NULL) |
| { |
| TprintfT (0, "jprofile attach: pfnGetCreatedJavaVMs is detected.\n"); |
| JavaVM * vmBuf[1]; // XXXX only detect on jvm |
| jsize nVMs = 0; |
| (*pfnGetCreatedJavaVMs)(vmBuf, 1, &nVMs); |
| if (vmBuf[0] != NULL && nVMs > 0) |
| { |
| jvm = vmBuf[0]; |
| JNIEnv* jni_env = NULL; |
| (*jvm)->AttachCurrentThread (jvm, (void **) &jni_env, NULL); |
| Agent_OnLoad (jvm, NULL, NULL); |
| if ((*jvm)->GetEnv (jvm, (void **) &jni_env, JNI_VERSION_1_2) >= 0 && jni_env && jvmti) |
| { |
| jthread thread; |
| (*jvmti)->GetCurrentThread (jvmti, &thread); |
| jvmti_VMInit (jvmti, jni_env, thread); |
| (*jvmti)->GenerateEvents (jvmti, JVMTI_EVENT_COMPILED_METHOD_LOAD); |
| (*jvmti)->GenerateEvents (jvmti, JVMTI_EVENT_DYNAMIC_CODE_GENERATED); |
| __collector_java_attach = 1; |
| (*jvm)->DetachCurrentThread (jvm); |
| } |
| } |
| } |
| return 0; |
| } |
| |
| static int |
| close_experiment (void) |
| { |
| /* fixme XXXXX add content here */ |
| /* see detach_experiment() */ |
| __collector_java_mode = 0; |
| __collector_java_asyncgetcalltrace_loaded = 0; |
| __collector_java_attach = 0; |
| java_gc_on = 0; |
| java_mem_mode = 0; |
| java_sync_mode = 0; |
| is_hotspot_vm = 0; |
| __collector_mutex_init (&jclasses_lock); |
| tsd_key = COLLECTOR_TSD_INVALID_KEY; |
| TprintfT (0, "jprofile: experiment closed.\n"); |
| return 0; |
| } |
| |
| static int |
| detach_experiment (void) |
| /* fork child. Clean up state but don't write to experiment */ |
| { |
| __collector_java_mode = 0; |
| java_gc_on = 0; |
| jvm = NULL; |
| java_mem_mode = 0; |
| java_sync_mode = 0; |
| is_hotspot_vm = 0; |
| jvmti = NULL; |
| apistr = NULL; |
| __collector_mutex_init (&jclasses_lock); |
| tsd_key = COLLECTOR_TSD_INVALID_KEY; |
| TprintfT (0, "jprofile: detached from experiment.\n"); |
| return 0; |
| } |
| |
| JNIEXPORT jint JNICALL |
| JVM_OnLoad (JavaVM *vm, char *options, void *reserved) |
| { |
| jvmtiError err; |
| int use_jvmti = 0; |
| if (!__collector_java_mode) |
| { |
| TprintfT (DBG_LT1, "jprofile: JVM_OnLoad invoked with java mode disabled\n"); |
| return JNI_OK; |
| } |
| else |
| TprintfT (DBG_LT1, "jprofile: JVM_OnLoad invoked\n"); |
| jvm = vm; |
| jvmti = NULL; |
| if ((*jvm)->GetEnv (jvm, (void **) &jvmti, JVMTI_VERSION_1_0) >= 0 && jvmti) |
| { |
| TprintfT (DBG_LT1, "jprofile: JVMTI found\n"); |
| use_jvmti = 1; |
| } |
| if (!use_jvmti) |
| { |
| collector_interface->writeLog ("<event kind=\"%s\" id=\"%d\"/>\n", |
| SP_JCMD_CERROR, COL_ERROR_JVMNOTSUPP); |
| return JNI_ERR; |
| } |
| else |
| { |
| Tprintf (DBG_LT0, "\tjprofile: Initializing for JVMTI\n"); |
| apistr = "JVMTI 1.0"; |
| |
| // setup JVMTI |
| jvmtiCapabilities cpblts; |
| err = (*jvmti)->GetPotentialCapabilities (jvmti, &cpblts); |
| if (err == JVMTI_ERROR_NONE) |
| { |
| jvmtiCapabilities cpblts_set; |
| CALL_UTIL (memset)(&cpblts_set, 0, sizeof (cpblts_set)); |
| |
| /* Add only those capabilities that are among potential ones */ |
| cpblts_set.can_get_source_file_name = cpblts.can_get_source_file_name; |
| Tprintf (DBG_LT1, "\tjprofile: adding can_get_source_file_name capability: %u\n", cpblts.can_get_source_file_name); |
| |
| cpblts_set.can_generate_compiled_method_load_events = cpblts.can_generate_compiled_method_load_events; |
| Tprintf (DBG_LT1, "\tjprofile: adding can_generate_compiled_method_load_events capability: %u\n", cpblts.can_generate_compiled_method_load_events); |
| |
| if (java_sync_mode) |
| { |
| cpblts_set.can_generate_monitor_events = cpblts.can_generate_monitor_events; |
| Tprintf (DBG_LT1, "\tjprofile: adding can_generate_monitor_events capability: %u\n", cpblts.can_generate_monitor_events); |
| } |
| if (java_gc_on) |
| { |
| cpblts_set.can_generate_garbage_collection_events = cpblts.can_generate_garbage_collection_events; |
| Tprintf (DBG_LT1, "\tjprofile: adding can_generate_garbage_collection_events capability: %u\n", cpblts.can_generate_garbage_collection_events); |
| } |
| err = (*jvmti)->AddCapabilities (jvmti, &cpblts_set); |
| Tprintf (DBG_LT1, "\tjprofile: AddCapabilities() returns: %d\n", err); |
| } |
| err = (*jvmti)->SetEventCallbacks (jvmti, &callbacks, sizeof ( callbacks)); |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL); |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, NULL); |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, NULL); |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, NULL); |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_LOAD, NULL); |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_UNLOAD, NULL); |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL); |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_THREAD_START, NULL); |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_THREAD_END, NULL); |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL); |
| if (java_gc_on) |
| { |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_GARBAGE_COLLECTION_START, NULL); |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, NULL); |
| } |
| if (java_mem_mode) |
| { |
| // err = (*jvmti)->SetEventNotificationMode( jvmti, JVMTI_ENABLE, <no event for heap tracing> , NULL ); |
| collector_interface->writeLog ("<event kind=\"%s\" id=\"%d\"/>\n", |
| SP_JCMD_CWARN, COL_WARN_NO_JAVA_HEAP); |
| java_mem_mode = 0; |
| } |
| if (java_sync_mode) |
| { |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, NULL); |
| err = (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, NULL); |
| //err = (*jvmti)->SetEventNotificationMode( jvmti, JVMTI_ENABLE, JVMTI_EVENT_MONITOR_WAIT, NULL ); |
| //err = (*jvmti)->SetEventNotificationMode( jvmti, JVMTI_ENABLE, JVMTI_EVENT_MONITOR_WAITED, NULL ); |
| } |
| Tprintf (DBG_LT0, "\tjprofile: JVMTI initialized\n"); |
| } |
| |
| /* JVM still uses collector API on Solaris to notify us about dynamically generated code. |
| * If we ask it to generate events we'll end up with duplicate entries in the |
| * map file. |
| */ |
| if (use_jvmti) |
| { |
| err = (*jvmti)->GenerateEvents (jvmti, JVMTI_EVENT_DYNAMIC_CODE_GENERATED); |
| err = (*jvmti)->GenerateEvents (jvmti, JVMTI_EVENT_COMPILED_METHOD_LOAD); |
| } |
| Tprintf (DBG_LT1, "\tjprofile: JVM_OnLoad ok\n"); |
| return JNI_OK; |
| } |
| |
| /* This is currently just a placeholder */ |
| JNIEXPORT jint JNICALL |
| Agent_OnLoad (JavaVM *vm, char *options, void *reserved) |
| { |
| return JVM_OnLoad (vm, options, reserved); |
| } |
| |
| static void |
| rwrite (int fd, const void *buf, size_t nbyte) |
| { |
| size_t left = nbyte; |
| size_t res; |
| char *ptr = (char*) buf; |
| while (left > 0) |
| { |
| res = CALL_UTIL (write)(fd, ptr, left); |
| if (res == -1) |
| { |
| /* XXX: we can't write this record, we probably |
| * can't write anything else. Ignore. |
| */ |
| return; |
| } |
| left -= res; |
| ptr += res; |
| } |
| } |
| |
| void |
| get_jvm_settings () |
| { |
| jint res; |
| JNIEnv *jni; |
| jclass jcls; |
| jmethodID jmid; |
| jstring jstrin; |
| jstring jstrout; |
| const char *str; |
| res = (*jvm)->GetEnv (jvm, (void **) &jni, JNI_VERSION_1_2); |
| if (res < 0) |
| return; |
| |
| /* I'm not checking if results are valid as JVM is extremely |
| * sensitive to exceptions that might occur during these JNI calls |
| * and will die with a fatal error later anyway. |
| */ |
| jcls = (*jni)->FindClass (jni, "java/lang/System"); |
| jmid = (*jni)->GetStaticMethodID (jni, jcls, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"); |
| jstrin = (*jni)->NewStringUTF (jni, "java.class.path"); |
| jstrout = (*jni)->CallStaticObjectMethod (jni, jcls, jmid, jstrin); |
| str = jstrout ? (*jni)->GetStringUTFChars (jni, jstrout, NULL) : NULL; |
| if (str) |
| { |
| collector_interface->writeLog ("<setting %s=\"%s\"/>\n", SP_JCMD_SRCHPATH, str); |
| (*jni)->ReleaseStringUTFChars (jni, jstrout, str); |
| } |
| jstrin = (*jni)->NewStringUTF (jni, "sun.boot.class.path"); |
| jstrout = (*jni)->CallStaticObjectMethod (jni, jcls, jmid, jstrin); |
| str = jstrout ? (*jni)->GetStringUTFChars (jni, jstrout, NULL) : NULL; |
| if (str) |
| { |
| collector_interface->writeLog ("<setting %s=\"%s\"/>\n", SP_JCMD_SRCHPATH, str); |
| (*jni)->ReleaseStringUTFChars (jni, jstrout, str); |
| } |
| jstrin = (*jni)->NewStringUTF (jni, "java.home"); |
| jstrout = (*jni)->CallStaticObjectMethod (jni, jcls, jmid, jstrin); |
| str = jstrout ? (*jni)->GetStringUTFChars (jni, jstrout, NULL) : NULL; |
| if (str) |
| { |
| collector_interface->writeLog ("<setting %s=\"%s/../src.zip\"/>\n", SP_JCMD_SRCHPATH, str); |
| (*jni)->ReleaseStringUTFChars (jni, jstrout, str); |
| } |
| jstrin = (*jni)->NewStringUTF (jni, "java.vm.version"); |
| jstrout = (*jni)->CallStaticObjectMethod (jni, jcls, jmid, jstrin); |
| str = jstrout ? (*jni)->GetStringUTFChars (jni, jstrout, NULL) : NULL; |
| if (str) |
| { |
| (void) collector_interface->writeLog ("<profile name=\"jprofile\" %s=\"%s\" %s=\"%s\"/>\n", |
| SP_JCMD_JVERSION, str, "api", apistr != NULL ? apistr : "N/A"); |
| if (__collector_strStartWith (str, "1.4.2_02") < 0) |
| { |
| (void) collector_interface->writeLog ("<event kind=\"%s\" id=\"%d\"/>\n", |
| SP_JCMD_CWARN, COL_WARN_OLDJAVA); |
| } |
| (*jni)->ReleaseStringUTFChars (jni, jstrout, str); |
| } |
| is_hotspot_vm = 0; |
| jstrin = (*jni)->NewStringUTF (jni, "sun.management.compiler"); |
| jstrout = (*jni)->CallStaticObjectMethod (jni, jcls, jmid, jstrin); |
| str = jstrout ? (*jni)->GetStringUTFChars (jni, jstrout, NULL) : NULL; |
| if (str && __collector_strncmp (str, "HotSpot", 7) == 0) |
| is_hotspot_vm = 1; |
| |
| /* Emulate System.setProperty( "collector.init", "true") */ |
| jmid = (*jni)->GetStaticMethodID (jni, jcls, "setProperty", |
| "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); |
| jstrin = (*jni)->NewStringUTF (jni, "collector.init"); |
| jstrout = (*jni)->NewStringUTF (jni, "true"); |
| (*jni)->CallStaticObjectMethod (jni, jcls, jmid, jstrin, jstrout); |
| } |
| |
| /* |
| * JVMTI code |
| */ |
| |
| static void |
| jvmti_VMInit (jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread) |
| { |
| jint class_count = 0; |
| jclass *classes = NULL; |
| int i; |
| TprintfT (DBG_LT1, "jprofile: jvmti_VMInit called\n"); |
| get_jvm_settings (); |
| |
| /* determine loaded classes */ |
| (*jvmti_env)->GetLoadedClasses (jvmti_env, &class_count, &classes); |
| TprintfT (DBG_LT1, "jprofile: jvmti_VMInit initializing %d classes\n", class_count); |
| for (i = 0; i < class_count; i++) |
| { |
| // PushLocalFrame |
| jvmti_ClassPrepare (jvmti_env, jni_env, NULL, classes[i]); |
| // PopLocalFrame |
| // DeleteLocalRef( classes[i] ); |
| } |
| (*jvmti_env)->Deallocate (jvmti_env, (unsigned char*) classes); |
| getResource = (*jni_env)->GetMethodID (jni_env, (*jni_env)->FindClass (jni_env, "java/lang/ClassLoader"), "getResource", "(Ljava/lang/String;)Ljava/net/URL;"); |
| toExternalForm = (*jni_env)->GetMethodID (jni_env, (*jni_env)->FindClass (jni_env, "java/net/URL"), "toExternalForm", "()Ljava/lang/String;"); |
| |
| /* find the stack unwind routine */ |
| jprof_find_asyncgetcalltrace (); |
| } |
| |
| static void |
| jvmti_VMDeath (jvmtiEnv *jvmti_env, JNIEnv* jni_env) |
| { |
| __collector_java_mode = 0; |
| TprintfT (DBG_LT1, "jprofile: jvmti_VMDeath event received\n"); |
| } |
| |
| static void |
| jvmti_ThreadStart (jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread) |
| { |
| jvmtiError err; |
| jvmtiThreadInfo t_info; |
| char *thread_name, *group_name, *parent_name; |
| hrtime_t hrt; |
| collector_thread_t tid; |
| thread_name = group_name = parent_name = NULL; |
| hrt = gethrtime (); |
| tid = __collector_thr_self (); |
| TprintfT (DBG_LT1, "jprofile: jvmti_ThreadStart: thread: %lu jni_env=%p jthread=%p\n", |
| (unsigned long) tid, jni_env, thread); |
| err = (*jvmti_env)->GetThreadInfo (jvmti_env, thread, &t_info); |
| if (err == JVMTI_ERROR_NONE) |
| { |
| jvmtiThreadGroupInfo g_info; |
| thread_name = t_info.name; |
| if (t_info.thread_group) |
| { |
| err = (*jvmti_env)->GetThreadGroupInfo (jvmti_env, t_info.thread_group, &g_info); |
| if (err == JVMTI_ERROR_NONE) |
| { |
| group_name = g_info.name; |
| if (g_info.parent) |
| { |
| jvmtiThreadGroupInfo p_info; |
| err = (*jvmti_env)->GetThreadGroupInfo (jvmti_env, g_info.parent, &p_info); |
| if (err == JVMTI_ERROR_NONE) |
| { |
| parent_name = p_info.name; |
| // DeleteLocalRef( p_info.parent ); |
| } |
| // DeleteLocalRef( g_info.parent ); |
| } |
| } |
| } |
| // DeleteLocalRef( t_info.thread_group ); |
| // DeleteLocalRef( t_info.context_class_loader ); |
| } |
| if (thread_name == NULL) |
| thread_name = ""; |
| if (group_name == NULL) |
| group_name = ""; |
| if (parent_name == NULL) |
| parent_name = ""; |
| collector_interface->writeLog ("<event kind=\"%s\" tstamp=\"%u.%09u\" name=\"%s\" grpname=\"%s\" prntname=\"%s\" tid=\"%lu\" jthr=\"0x%lx\" jenv=\"0x%lx\"/>\n", |
| SP_JCMD_JTHRSTART, |
| (unsigned) (hrt / NANOSEC), (unsigned) (hrt % NANOSEC), |
| thread_name, |
| group_name, |
| parent_name, |
| (unsigned long) tid, |
| thread, |
| jni_env |
| ); |
| TSD_Entry *tsd = collector_interface->getKey (tsd_key); |
| if (tsd) |
| tsd->env = jni_env; |
| } |
| |
| static void |
| jvmti_ThreadEnd (jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread) |
| { |
| hrtime_t hrt = gethrtime (); |
| collector_thread_t tid = __collector_thr_self (); |
| TprintfT (DBG_LT1, "jprofile: jvmti_ThreadEnd: thread: %lu jni_env=%p jthread=%p\n", |
| (unsigned long) tid, jni_env, thread); |
| |
| collector_interface->writeLog ("<event kind=\"%s\" tstamp=\"%u.%09u\" tid=\"%lu\" jthr=\"0x%lx\" jenv=\"0x%lx\"/>\n", |
| SP_JCMD_JTHREND, |
| (unsigned) (hrt / NANOSEC), (unsigned) (hrt % NANOSEC), |
| (unsigned long) tid, |
| thread, |
| jni_env |
| ); |
| TSD_Entry *tsd = collector_interface->getKey (tsd_key); |
| if (tsd) |
| tsd->env = NULL; |
| } |
| |
| /* The following definitions are borrowed from file jvmticmlr.h, part of jdk7 */ |
| typedef enum |
| { |
| JVMTI_CMLR_DUMMY = 1, |
| JVMTI_CMLR_INLINE_INFO = 2 |
| } jvmtiCMLRKind; |
| |
| /* |
| * Record that represents arbitrary information passed through JVMTI |
| * CompiledMethodLoadEvent void pointer. |
| */ |
| typedef struct _jvmtiCompiledMethodLoadRecordHeader |
| { |
| jvmtiCMLRKind kind; /* id for the kind of info passed in the record */ |
| jint majorinfoversion; /* major and minor info version values. Init'ed */ |
| jint minorinfoversion; /* to current version value in jvmtiExport.cpp. */ |
| struct _jvmtiCompiledMethodLoadRecordHeader* next; |
| } jvmtiCompiledMethodLoadRecordHeader; |
| |
| /* |
| * Record that gives information about the methods on the compile-time |
| * stack at a specific pc address of a compiled method. Each element in |
| * the methods array maps to same element in the bcis array. |
| */ |
| typedef struct _PCStackInfo |
| { |
| void* pc; /* the pc address for this compiled method */ |
| jint numstackframes; /* number of methods on the stack */ |
| jmethodID* methods; /* array of numstackframes method ids */ |
| jint* bcis; /* array of numstackframes bytecode indices */ |
| } PCStackInfo; |
| |
| /* |
| * Record that contains inlining information for each pc address of |
| * an nmethod. |
| */ |
| typedef struct _jvmtiCompiledMethodLoadInlineRecord |
| { |
| jvmtiCompiledMethodLoadRecordHeader header; /* common header for casting */ |
| jint numpcs; /* number of pc descriptors in this nmethod */ |
| PCStackInfo* pcinfo; /* array of numpcs pc descriptors */ |
| } jvmtiCompiledMethodLoadInlineRecord; |
| |
| static void |
| jvmti_CompiledMethodLoad (jvmtiEnv *jvmti_env, jmethodID method, |
| jint code_size, const void *code_addr, jint map_length, |
| const jvmtiAddrLocationMap *map, |
| const void *compile_info) |
| { |
| TprintfT (DBG_LT2, "jprofile: jvmti_CompiledMethodLoad: mid=0x%lx addr=%p sz=0x%lu map=%p info=%p\n", |
| (unsigned long) method, code_addr, (long) code_size, map, compile_info); |
| char name[32]; |
| CALL_UTIL (snprintf)(name, sizeof (name), "0x%lx", (unsigned long) method); |
| |
| /* Parse compile_info to get pc -> bci mapping. |
| * Don't interpret compile_info from JVMs other than HotSpot. |
| */ |
| int lntsize = 0; |
| DT_lineno *lntable = NULL; |
| if (compile_info != NULL && is_hotspot_vm) |
| { |
| Tprintf (DBG_LT2, "Mapping from compile_info:\n"); |
| jvmtiCompiledMethodLoadRecordHeader *currec = |
| (jvmtiCompiledMethodLoadRecordHeader*) compile_info; |
| while (currec != NULL) |
| { |
| if (currec->kind == JVMTI_CMLR_INLINE_INFO) |
| { |
| jvmtiCompiledMethodLoadInlineRecord *inrec = |
| (jvmtiCompiledMethodLoadInlineRecord*) currec; |
| if (inrec->numpcs <= 0) |
| break; |
| lntsize = inrec->numpcs; |
| lntable = (DT_lineno*) alloca (lntsize * sizeof (DT_lineno)); |
| PCStackInfo *pcrec = inrec->pcinfo; |
| DT_lineno *lnorec = lntable; |
| for (int i = 0; i < lntsize; ++i) |
| { |
| for (int j = pcrec->numstackframes - 1; j >= 0; --j) |
| if (pcrec->methods[j] == method) |
| { |
| lnorec->offset = (char*) pcrec->pc - (char*) code_addr; |
| lnorec->lineno = pcrec->bcis[j]; |
| Tprintf (DBG_LT2, " pc: 0x%lx bci: 0x%lx\n", |
| (long) lnorec->offset, (long) lnorec->lineno); |
| ++lnorec; |
| break; |
| } |
| ++pcrec; |
| } |
| break; |
| } |
| currec = currec->next; |
| } |
| } |
| else if (map != NULL) |
| { |
| Tprintf (DBG_LT2, "Mapping from jvmtiAddrLocationMap:\n"); |
| lntsize = map_length; |
| lntable = (DT_lineno*) alloca (lntsize * sizeof (DT_lineno)); |
| DT_lineno *lnorec = lntable; |
| for (int i = 0; i < map_length; ++i) |
| { |
| lnorec->offset = (char*) map[i].start_address - (char*) code_addr; |
| lnorec->lineno = (unsigned int) map[i].location; |
| Tprintf (DBG_LT2, " pc: 0x%lx bci: 0x%lx\n", |
| (long) lnorec->offset, (long) lnorec->lineno); |
| ++lnorec; |
| } |
| } |
| __collector_int_func_load (DFUNC_JAVA, name, NULL, (void*) code_addr, |
| code_size, lntsize, lntable); |
| } |
| |
| static void |
| jvmti_CompiledMethodUnload (jvmtiEnv *jvmti_env, jmethodID method, const void* code_addr) |
| { |
| __collector_int_func_unload (DFUNC_API, (void*) code_addr); |
| } |
| |
| static void |
| jvmti_DynamicCodeGenerated (jvmtiEnv *jvmti_env, const char*name, const void *code_addr, jint code_size) |
| { |
| __collector_int_func_load (DFUNC_API, (char*) name, NULL, (void*) code_addr, |
| code_size, 0, NULL); |
| } |
| |
| static void |
| addToDynamicArchive (const char* name, const unsigned char* class_data, int class_data_len) |
| { |
| char path[MAXPATHLEN + 1]; |
| mode_t fmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; |
| mode_t dmode = fmode | S_IXUSR | S_IXGRP | S_IXOTH; |
| if (name == NULL) |
| name = ""; |
| const char *expdir = collector_interface->getExpDir (); |
| if (CALL_UTIL (strlen)(expdir) + |
| CALL_UTIL (strlen)(SP_DYNAMIC_CLASSES) + |
| CALL_UTIL (strlen)(name) + 8 > sizeof (path)) |
| return; |
| CALL_UTIL (snprintf)(path, sizeof (path), "%s/%s/%s.class", expdir, SP_DYNAMIC_CLASSES, name); |
| |
| /* Create all path components step by step starting with SP_DYNAMIC_CLASSES */ |
| char *str = path + CALL_UTIL (strlen)(expdir) + 1 + CALL_UTIL (strlen)(SP_DYNAMIC_CLASSES); |
| while (str) |
| { |
| *str = '\0'; |
| if (CALL_UTIL (mkdir)(path, dmode) != 0) |
| { |
| /* Checking for EEXIST is not enough, access() is more reliable */ |
| if (CALL_UTIL (access)(path, F_OK) != 0) |
| { |
| collector_interface->writeLog ("<event kind=\"%s\" id=\"%d\" ec=\"%d\">%s</event>\n", |
| SP_JCMD_CERROR, COL_ERROR_MKDIR, errno, path); |
| return; |
| } |
| } |
| *str++ = '/'; |
| str = CALL_UTIL (strchr)(str, '/'); |
| } |
| |
| int fd = CALL_UTIL (open)(path, O_WRONLY | O_CREAT | O_TRUNC, fmode); |
| if (fd < 0) |
| { |
| collector_interface->writeLog ("<event kind=\"%s\" id=\"%d\" ec=\"%d\">%s</event>\n", |
| SP_JCMD_CERROR, COL_ERROR_OVWOPEN, errno, path); |
| return; |
| } |
| rwrite (fd, class_data, class_data_len); |
| CALL_UTIL (close)(fd); |
| } |
| |
| static void |
| jvmti_ClassFileLoadHook (jvmtiEnv *jvmti_env, JNIEnv* jni_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) |
| { |
| jclass loaderlass; |
| int err; |
| jvmtiPhase phase_ptr; |
| char *cname = NULL; |
| (*jvmti_env)->GetPhase (jvmti_env, &phase_ptr); |
| |
| /* skip non live phases */ |
| if (phase_ptr != JVMTI_PHASE_LIVE) |
| return; |
| |
| /* skip system class loaders */ |
| if (!loader) |
| return; |
| loaderlass = (*jni_env)->GetObjectClass (jni_env, loader); |
| err = (*jvmti_env)->GetClassSignature (jvmti_env, loaderlass, &cname, NULL); |
| if (err != JVMTI_ERROR_NONE || !cname || *cname == (char) 0) |
| return; |
| |
| /* skip classes loaded with AppClassLoader (java.class.path) */ |
| if (__collector_strcmp (cname, "Lsun/misc/Launcher$AppClassLoader;") == 0) |
| return; |
| addToDynamicArchive (name, class_data, (int) class_data_len); |
| } |
| |
| #define NO_CLASS_NAME "<noname>" |
| #define NO_SOURCE_FILE "<Unknown>" |
| |
| static void |
| record_jclass (uint64_t class_id, hrtime_t hrt, const char *cname, const char *sname) |
| { |
| size_t clen = ARCH_STRLEN (cname); |
| size_t slen = ARCH_STRLEN (sname); |
| size_t sz = sizeof (ARCH_jclass) + clen + slen; |
| ARCH_jclass *jcls = (ARCH_jclass*) alloca (sz); |
| jcls->comm.tsize = sz; |
| jcls->comm.type = ARCH_JCLASS; |
| jcls->class_id = class_id; |
| jcls->tstamp = hrt; |
| char *str = (char*) (jcls + 1); |
| size_t i = CALL_UTIL (strlcpy)(str, cname, clen); |
| str += i; |
| while (i++ < clen) |
| *str++ = (char) 0; /* pad with 0's */ |
| i = CALL_UTIL (strlcpy)(str, sname, slen); |
| str += i; |
| while (i++ < slen) |
| *str++ = (char) 0; /* pad with 0's */ |
| collector_interface->writeDataPacket (jprof_hndl, (CM_Packet*) jcls); |
| } |
| |
| static void |
| record_jmethod (uint64_t class_id, uint64_t method_id, |
| const char *mname, const char *msign) |
| { |
| size_t mnlen = mname ? ARCH_STRLEN (mname) : 0; |
| size_t mslen = msign ? ARCH_STRLEN (msign) : 0; |
| size_t sz = sizeof (ARCH_jmethod) + mnlen + mslen; |
| ARCH_jmethod *jmth = (ARCH_jmethod*) alloca (sz); |
| if (jmth == NULL) |
| { |
| TprintfT (DBG_LT1, "jprofile: record_jmethod ERROR: failed to alloca(%ld)\n", (long) sz); |
| return; |
| } |
| jmth->comm.tsize = sz; |
| jmth->comm.type = ARCH_JMETHOD; |
| jmth->class_id = class_id; |
| jmth->method_id = method_id; |
| char *str = (char*) (jmth + 1); |
| if (mname) |
| { |
| size_t i = CALL_UTIL (strlcpy)(str, mname, mnlen); |
| str += i; |
| while (i++ < mnlen) |
| *str++ = (char) 0; /* pad with 0's */ |
| } |
| if (msign) |
| { |
| size_t i = CALL_UTIL (strlcpy)(str, msign, mslen); |
| str += i; |
| while (i++ < mslen) |
| *str++ = (char) 0; /* pad with 0's */ |
| } |
| collector_interface->writeDataPacket (jprof_hndl, (CM_Packet*) jmth); |
| } |
| |
| static void |
| jvmti_ClassPrepare (jvmtiEnv *jvmti_env, JNIEnv* jni_env, |
| jthread thread, jclass klass) |
| { |
| hrtime_t hrt; |
| jint mnum; |
| jmethodID *mptr; |
| char *cname, *sname; |
| char *str1 = NULL; |
| int err = (*jvmti_env)->GetClassSignature (jvmti_env, klass, &str1, NULL); |
| if (err != JVMTI_ERROR_NONE || str1 == NULL || *str1 == (char) 0) |
| cname = NO_CLASS_NAME; |
| else |
| cname = str1; |
| if (*cname != 'L') |
| { |
| DprintfT (SP_DUMP_JAVA | SP_DUMP_TIME, "jvmti_ClassPrepare: GetClassSignature failed. err=%d cname=%s\n", err, cname); |
| return; |
| } |
| char *str2 = NULL; |
| err = (*jvmti_env)->GetSourceFileName (jvmti_env, klass, &str2); |
| if (err != JVMTI_ERROR_NONE || str2 == NULL || *str2 == (char) 0) |
| sname = NO_SOURCE_FILE; |
| else |
| sname = str2; |
| DprintfT (SP_DUMP_JAVA | SP_DUMP_TIME, "jvmti_ClassPrepare: cname=%s sname=%s\n", STR (cname), STR (sname)); |
| |
| /* Lock the whole file */ |
| __collector_mutex_lock (&jclasses_lock); |
| hrt = gethrtime (); |
| record_jclass ((unsigned long) klass, hrt, cname, sname); |
| (*jvmti_env)->Deallocate (jvmti_env, (unsigned char *) str1); |
| (*jvmti_env)->Deallocate (jvmti_env, (unsigned char *) str2); |
| err = (*jvmti_env)->GetClassMethods (jvmti_env, klass, &mnum, &mptr); |
| if (err == JVMTI_ERROR_NONE) |
| { |
| for (int i = 0; i < mnum; i++) |
| { |
| char *mname, *msign; |
| err = (*jvmti_env)->GetMethodName (jvmti_env, mptr[i], &mname, &msign, NULL); |
| if (err != JVMTI_ERROR_NONE) |
| continue; |
| record_jmethod ((unsigned long) klass, (unsigned long) mptr[i], mname, msign); |
| // DeleteLocalRef( mptr[i] ); |
| } |
| (*jvmti_env)->Deallocate (jvmti_env, (unsigned char*) mptr); |
| } |
| /* Unlock the file */ |
| __collector_mutex_unlock (&jclasses_lock); |
| } |
| |
| /* |
| * The CLASS_LOAD event is enabled to enable AsyncGetCallTrace |
| */ |
| static void |
| jvmti_ClassLoad (jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, jclass klass) |
| { |
| char *cname; |
| char *str1 = NULL; |
| int err = (*jvmti_env)->GetClassSignature (jvmti_env, klass, &str1, NULL); |
| if (err != JVMTI_ERROR_NONE || str1 == NULL || *str1 == (char) 0) |
| cname = NO_CLASS_NAME; |
| else |
| cname = str1; |
| jstring str = NULL; |
| const char* resourceName; |
| jobject classLoader = NULL; |
| err = (*jvmti)->GetClassLoader (jvmti, klass, &classLoader); |
| DprintfT (SP_DUMP_JAVA | SP_DUMP_TIME, "jprofile: jvmti_ClassLoad err=%d cname=%s\n", err, STR (cname)); |
| if (err == 0) |
| { |
| if (classLoader == NULL) |
| { |
| // bootstrap class loader |
| resourceName = ""; |
| } |
| else |
| { |
| char* name = (char *) alloca ((CALL_UTIL (strlen)(str1) + 32) * sizeof (char)); |
| CALL_UTIL (strlcpy)(name, str1 + 1, CALL_UTIL (strlen)(str1)); |
| name[CALL_UTIL (strlen)(name) - 1] = '\0'; // remove the last ';' |
| char* p; |
| for (p = name; *p != '\0'; p++) |
| if (*p == '.') |
| *p = '/'; |
| CALL_UTIL (strlcat)(name, ".class", CALL_UTIL (strlen)(name) + CALL_UTIL (strlen)(".class") + 1); |
| if (getResource == NULL || toExternalForm == NULL) |
| { |
| resourceName = ""; |
| DprintfT (SP_DUMP_JAVA | SP_DUMP_TIME, "jvmti_ClassLoad: class %s failed to get path with method missing\n", STR (cname)); |
| } |
| else |
| { |
| jobject url = (*jni_env)->CallObjectMethod (jni_env, classLoader, getResource, (*jni_env)->NewStringUTF (jni_env, name)); |
| if (url == NULL) |
| { |
| resourceName = ""; |
| DprintfT (SP_DUMP_JAVA | SP_DUMP_TIME, "jvmti_ClassLoad: class %s failed to get path\n", STR (cname)); |
| } |
| else |
| { |
| str = (jstring) (*jni_env)->CallObjectMethod (jni_env, url, toExternalForm); |
| resourceName = (*jni_env)->GetStringUTFChars (jni_env, str, NULL); |
| DprintfT (SP_DUMP_JAVA | SP_DUMP_TIME, "jvmti_ClassLoad: ARCH_JCLASS_LOCATION(Ox%x) class_id=0x%lx className='%s' fileName '%s'\n", |
| (int) ARCH_JCLASS_LOCATION, (unsigned long) klass, STR (cname), STR (resourceName)); |
| size_t clen = ARCH_STRLEN (cname); |
| size_t slen = ARCH_STRLEN (resourceName); |
| size_t sz = sizeof (ARCH_jclass) + clen + slen; |
| ARCH_jclass_location *jcls = (ARCH_jclass_location*) alloca (sz); |
| jcls->comm.tsize = sz; |
| jcls->comm.type = ARCH_JCLASS_LOCATION; |
| jcls->class_id = (unsigned long) klass; |
| char *str = (char*) (jcls + 1); |
| size_t i = CALL_UTIL (strlcpy)(str, cname, clen); |
| str += i; |
| while (i++ < clen) |
| { |
| *str++ = (char) 0; /* pad with 0's */ |
| } |
| i = CALL_UTIL (strlcpy)(str, resourceName, slen); |
| str += i; |
| while (i++ < slen) |
| { |
| *str++ = (char) 0; /* pad with 0's */ |
| } |
| /* Lock the whole file */ |
| __collector_mutex_lock (&jclasses_lock); |
| collector_interface->writeDataPacket (jprof_hndl, (CM_Packet*) jcls); |
| /* Unlock the file */ |
| __collector_mutex_unlock (&jclasses_lock); |
| } |
| } |
| } |
| } |
| } |
| |
| static void |
| jvmti_MonitorEnter (jvmtiEnv *jvmti_env, JNIEnv* jni_env, |
| jthread thread, jobject object) |
| { |
| if (collector_jsync_begin) |
| collector_jsync_begin (); |
| TSD_Entry *tsd = collector_interface->getKey (tsd_key); |
| if (tsd == NULL) |
| return; |
| tsd->tstamp = gethrtime (); |
| } |
| |
| static void |
| jvmti_MonitorEntered (jvmtiEnv *jvmti_env, JNIEnv* jni_env, |
| jthread thread, jobject object) |
| { |
| TSD_Entry *tsd = collector_interface->getKey (tsd_key); |
| if (tsd == NULL) |
| return; |
| if (collector_jsync_end) |
| collector_jsync_end (tsd->tstamp, object); |
| } |
| |
| static void |
| jvmti_GarbageCollectionStart (jvmtiEnv *jvmti_env) |
| { |
| hrtime_t hrt = gethrtime (); |
| collector_interface->writeLog ("<event kind=\"%s\" tstamp=\"%u.%09u\"/>\n", |
| SP_JCMD_GCSTART, |
| (unsigned) (hrt / NANOSEC), (unsigned) (hrt % NANOSEC) |
| ); |
| TprintfT (DBG_LT1, "jprofile: jvmti_GarbageCollectionStart.\n"); |
| } |
| |
| static void |
| jvmti_GarbageCollectionFinish (jvmtiEnv *jvmti_env) |
| { |
| hrtime_t hrt = gethrtime (); |
| collector_interface->writeLog ("<event kind=\"%s\" tstamp=\"%u.%09u\"/>\n", |
| SP_JCMD_GCEND, |
| (unsigned) (hrt / NANOSEC), (unsigned) (hrt % NANOSEC) |
| ); |
| TprintfT (DBG_LT1, "jprofile: jvmti_GarbageCollectionFinish.\n"); |
| } |
| |
| #if 0 |
| static void |
| jvmti_MonitorWait (jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, |
| jobject object, jlong timed_out) |
| { |
| if (collector_sync_begin) |
| collector_sync_begin (); |
| TSD_Entry *tsd = collector_interface->getKey (tsd_key); |
| if (tsd == NULL) |
| return; |
| tsd->tstamp = gethrtime (); |
| } |
| |
| static void |
| jvmti_MonitorWaited (jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, |
| jobject object, jboolean timed_out) |
| { |
| TSD_Entry *tsd = collector_interface->getKey (tsd_key); |
| if (tsd == NULL) |
| return; |
| if (collector_sync_end) |
| collector_sync_end (tsd->tstamp, object); |
| } |
| #endif |
| |
| static void |
| jprof_find_asyncgetcalltrace () |
| { |
| void *jvmhandle; |
| if (__collector_VM_ReadByteInstruction == NULL) |
| __collector_VM_ReadByteInstruction = (int(*)()) dlsym (RTLD_DEFAULT, "Async_VM_ReadByteInstruction"); |
| |
| /* look for stack unwind function using default path */ |
| AsyncGetCallTrace = (void (*)(JVMPI_CallTrace*, jint, ucontext_t*)) |
| dlsym (RTLD_DEFAULT, "AsyncGetCallTrace"); |
| if (AsyncGetCallTrace != NULL) |
| { |
| __collector_java_asyncgetcalltrace_loaded = 1; |
| TprintfT (DBG_LT1, "jprofile: AsyncGetCallTrace found with RTLD_DEFAULT\n"); |
| } |
| else |
| { |
| /* not found there, find libjvm.so, and ask again */ |
| jvmhandle = dlopen ("libjvm.so", RTLD_LAZY | RTLD_NOLOAD); |
| if (jvmhandle != NULL) |
| { |
| AsyncGetCallTrace = (void (*)(JVMPI_CallTrace*, jint, ucontext_t*)) |
| dlsym (jvmhandle, "AsyncGetCallTrace"); |
| } |
| } |
| |
| if (AsyncGetCallTrace == NULL) |
| { |
| /* we could not find it -- write collector error */ |
| TprintfT (0, "jprofile: ERROR -- AsyncGetCallTrace not found in address space\n"); |
| char *err = dlerror (); |
| collector_interface->writeLog ("<event kind=\"%s\" id=\"%d\">%s</event>\n", |
| SP_JCMD_CERROR, COL_ERROR_JVMNOJSTACK, err ? err : ""); |
| __collector_java_mode = 0; |
| } |
| else |
| { |
| __collector_java_asyncgetcalltrace_loaded = 1; |
| TprintfT (DBG_LT1, "jprofile: AsyncGetCallTrace initialized in jprof_jvmpi_init_done_event\n"); |
| } |
| } |
| |
| int |
| __collector_ext_jstack_unwind (char *ptr, int sz, ucontext_t *uc) |
| { |
| if (AsyncGetCallTrace == NULL) |
| { |
| TprintfT (DBG_LT0, "jprofile: __collector_ext_jstack_unwind: AsyncGetCallTrace is NULL\n"); |
| return 0; |
| } |
| |
| TSD_Entry *tsd = collector_interface->getKey (tsd_key); |
| if (tsd == NULL) |
| { |
| TprintfT (DBG_LT3, "jprofile: __collector_ext_jstack_unwind: tsd is NULL\n"); |
| return 0; |
| } |
| if (__collector_java_attach && tsd->env == NULL && jvmti != NULL && jvm != NULL) |
| { |
| TprintfT (DBG_LT3, "jprofile: __collector_ext_jstack_unwind: tsd->env is NULL under attach\n"); |
| JNIEnv* jni_env = NULL; |
| (*jvm)->GetEnv (jvm, (void **) &jni_env, JNI_VERSION_1_2); |
| tsd->env = jni_env; |
| } |
| if (tsd->env == NULL) |
| { |
| TprintfT (DBG_LT3, "jprofile: __collector_ext_jstack_unwind: tsd->env is NULL\n"); |
| return 0; |
| } |
| |
| /* skip the Java stack whenever another signal handler is present */ |
| if (uc->uc_link) |
| { |
| TprintfT (DBG_LT3, "jprofile: __collector_ext_jstack_unwind: uc->uc_link is non-NULL\n"); |
| return 0; |
| } |
| /* we don't expect Java frames in signal handlers, so |
| * unroll the list of saved contexts to the topmost one |
| */ |
| while (uc->uc_link) |
| uc = uc->uc_link; |
| Java_info *jinfo = (Java_info*) ptr; |
| jinfo->kind = JAVA_INFO; |
| jinfo->hsize = sizeof (Java_info); |
| ptr += sizeof (Java_info); |
| sz -= sizeof (Java_info); |
| |
| JVMPI_CallTrace jtrace; |
| jtrace.env_id = tsd->env; |
| jtrace.frames = (JVMPI_CallFrame*) ptr; |
| |
| /* nframes is how many frames we have room for */ |
| jint nframes = sz / sizeof (JVMPI_CallFrame); |
| |
| #if WSIZE(64) |
| /* bug 6909545: garbage in 64-bit JAVA_INFO */ |
| CALL_UTIL (memset)(jtrace.frames, 0, nframes * sizeof (JVMPI_CallFrame)); |
| #endif |
| |
| #if ARCH(SPARC) |
| // 21328946 JDK bug 8129933 causes <no java callstack recorded> on sparc-Linux |
| // convert from ucontext_t to sigcontext |
| struct sigcontext sctx; |
| sctx.sigc_regs.tpc = uc->uc_mcontext.mc_gregs[MC_PC]; |
| __collector_memcpy (sctx.sigc_regs.u_regs, &uc->uc_mcontext.mc_gregs[3], sizeof (sctx.sigc_regs.u_regs)); |
| uc = (ucontext_t *) (&sctx); |
| #endif /* SPARC */ |
| AsyncGetCallTrace (&jtrace, nframes, uc); |
| |
| if (jtrace.num_frames == nframes) |
| { |
| JVMPI_CallFrame *last = &jtrace.frames[nframes - 1]; |
| last->method_id = (jmethodID) SP_TRUNC_STACK_MARKER; |
| last->lineno = 0; |
| } |
| |
| /* nframes is how many frames we actually got */ |
| nframes = jtrace.num_frames; |
| TprintfT (DBG_LT3, "jprofile: __collector_ext_jstack_unwind: AsyncGetCallTrace jtrace.numframes = %d\n", nframes); |
| if (nframes <= 0) |
| { |
| /* negative values are error codes */ |
| TprintfT (0, "jprofile: __collector_ext_jstack_unwind: AsyncGetCallTrace returned error: jtrace.numframes = %d\n", nframes); |
| nframes = 1; |
| JVMPI_CallFrame *err = (JVMPI_CallFrame*) ptr; |
| err->lineno = jtrace.num_frames; // bci = error code |
| err->method_id = 0; // artificial method id |
| } |
| jinfo->hsize += nframes * sizeof (JVMPI_CallFrame); |
| return jinfo->hsize; |
| } |
| |
| /* |
| * Collector Java API implementation |
| */ |
| void |
| Java_com_sun_forte_st_collector_CollectorAPI__1sample(JNIEnv *jEnv, jclass jCls, jstring jName) |
| { |
| JNIEnv *jni; |
| jint res = (*jvm)->GetEnv (jvm, (void **) &jni, JNI_VERSION_1_2); |
| if (res < 0) |
| return; |
| const char *name = jName ? (*jni)->GetStringUTFChars (jni, jName, NULL) : NULL; |
| __collector_sample ((char*) name); |
| } |
| |
| void |
| Java_com_sun_forte_st_collector_CollectorAPI__1pause(JNIEnv *jEnv, jclass jCls) |
| { |
| __collector_pause_m ("JAPI"); |
| } |
| |
| void |
| Java_com_sun_forte_st_collector_CollectorAPI__1resume(JNIEnv *jEnv, jclass jCls) |
| { |
| __collector_resume (); |
| } |
| |
| void |
| Java_com_sun_forte_st_collector_CollectorAPI__1terminate(JNIEnv *jEnv, jclass jCls) |
| { |
| __collector_terminate_expt (); |
| } |
| #endif /* GPROFNG_JAVA_PROFILING */ |
| |
| static void init_module () __attribute__ ((constructor)); |
| static void |
| init_module () |
| { |
| #if defined(GPROFNG_JAVA_PROFILING) |
| __collector_dlsym_guard = 1; |
| RegModuleFunc reg_module = (RegModuleFunc) dlsym (RTLD_DEFAULT, "__collector_register_module"); |
| __collector_dlsym_guard = 0; |
| if (reg_module) |
| { |
| jprof_hndl = reg_module (&module_interface); |
| TprintfT (0, "jprofile: init_module.\n"); |
| } |
| #endif /* GPROFNG_JAVA_PROFILING */ |
| } |
| |
| int __collector_java_mode = 0; |
| int __collector_java_asyncgetcalltrace_loaded = 0; |